Skip to content

[5.9] Two fixes for extension macros with conformances #67981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions include/swift/AST/DeclContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,6 @@ enum class ConformanceLookupKind : unsigned {
/// All conformances except structurally-derived conformances, of which
/// Sendable is the only one.
NonStructural,
/// Exclude conformances added by a macro that has not been expanded
/// yet.
ExcludeUnexpandedMacros,
};

/// Describes a diagnostic for a conflict between two protocol
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,8 @@ class NormalProtocolConformance : public RootProtocolConformance,
assert((sourceKind == ConformanceEntryKind::Implied) ==
(bool)implyingConformance &&
"an implied conformance needs something that implies it");
assert(sourceKind != ConformanceEntryKind::PreMacroExpansion &&
"cannot create conformance pre-macro-expansion");
SourceKindAndImplyingConformance = {implyingConformance, sourceKind};
}

Expand Down
26 changes: 20 additions & 6 deletions lib/AST/ConformanceLookupTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,16 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
if (!conformingDC)
return nullptr;

// Never produce a conformance for a pre-macro-expansion conformance. They
// are placeholders that will be superseded.
if (entry->getKind() == ConformanceEntryKind::PreMacroExpansion) {
if (auto supersedingEntry = entry->SupersededBy) {
return getConformance(nominal, supersedingEntry);
}

return nullptr;
}

auto *conformingNominal = conformingDC->getSelfNominalTypeDecl();

// Form the conformance.
Expand Down Expand Up @@ -926,6 +936,16 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
? conformingNominal->getLoc()
: cast<ExtensionDecl>(conformingDC)->getLoc();

NormalProtocolConformance *implyingConf = nullptr;
if (entry->Source.getKind() == ConformanceEntryKind::Implied) {
auto implyingEntry = entry->Source.getImpliedSource();
auto origImplyingConf = getConformance(conformingNominal, implyingEntry);
if (!origImplyingConf)
return nullptr;

implyingConf = origImplyingConf->getRootNormalConformance();
}

// Create or find the normal conformance.
auto normalConf =
ctx.getConformance(conformingType, protocol, conformanceLoc,
Expand All @@ -936,12 +956,6 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
// early return at the start of this function.
entry->Conformance = normalConf;

NormalProtocolConformance *implyingConf = nullptr;
if (entry->Source.getKind() == ConformanceEntryKind::Implied) {
auto implyingEntry = entry->Source.getImpliedSource();
implyingConf = getConformance(conformingNominal, implyingEntry)
->getRootNormalConformance();
}
normalConf->setSourceKindAndImplyingConformance(entry->Source.getKind(),
implyingConf);

Expand Down
36 changes: 29 additions & 7 deletions lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,17 @@ namespace {
};
}

/// Given an extension declaration, return the extended nominal type if the
/// extension was produced by expanding an extension or conformance macro from
/// the nominal declaration itself.
static NominalTypeDecl *nominalForExpandedExtensionDecl(ExtensionDecl *ext) {
if (!ext->isInMacroExpansionInContext())
return nullptr;


return ext->getSelfNominalTypeDecl();
}

PotentialMacroExpansions PotentialMacroExpansionsInContextRequest::evaluate(
Evaluator &evaluator, TypeOrExtensionDecl container) const {
/// The implementation here needs to be kept in sync with
Expand All @@ -1767,6 +1778,15 @@ PotentialMacroExpansions PotentialMacroExpansionsInContextRequest::evaluate(
auto containerDecl = container.getAsDecl();
forEachPotentialAttachedMacro(containerDecl, MacroRole::Member, nameTracker);

// If the container is an extension that was created from an extension macro,
// look at the nominal declaration to find any extension macros.
if (auto ext = dyn_cast<ExtensionDecl>(containerDecl)) {
if (auto nominal = nominalForExpandedExtensionDecl(ext)) {
forEachPotentialAttachedMacro(
nominal, MacroRole::Extension, nameTracker);
}
}

// Peer and freestanding declaration macros.
auto dc = container.getAsDeclContext();
auto idc = container.getAsIterableDeclContext();
Expand Down Expand Up @@ -1825,13 +1845,15 @@ populateLookupTableEntryFromMacroExpansions(ASTContext &ctx,
// names match.
{
MacroIntroducedNameTracker nameTracker;
if (auto nominal = dyn_cast<NominalTypeDecl>(container.getAsDecl())) {
forEachPotentialAttachedMacro(nominal, MacroRole::Extension, nameTracker);
if (nameTracker.shouldExpandForName(name)) {
(void)evaluateOrDefault(
ctx.evaluator,
ExpandExtensionMacros{nominal},
false);
if (auto ext = dyn_cast<ExtensionDecl>(container.getAsDecl())) {
if (auto nominal = nominalForExpandedExtensionDecl(ext)) {
forEachPotentialAttachedMacro(nominal, MacroRole::Extension, nameTracker);
if (nameTracker.shouldExpandForName(name)) {
(void)evaluateOrDefault(
ctx.evaluator,
ExpandExtensionMacros{nominal},
false);
}
}
}
}
Expand Down
18 changes: 3 additions & 15 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1302,8 +1302,8 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
switch (conformance->getSourceKind()) {
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Synthesized:
return true;
case ConformanceEntryKind::PreMacroExpansion:
return true;
case ConformanceEntryKind::Implied:
case ConformanceEntryKind::Inherited:
return false;
Expand All @@ -1314,34 +1314,22 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Synthesized:
case ConformanceEntryKind::Implied:
case ConformanceEntryKind::PreMacroExpansion:
return true;
case ConformanceEntryKind::Inherited:
case ConformanceEntryKind::PreMacroExpansion:
return false;
}

case ConformanceLookupKind::All:
case ConformanceLookupKind::NonStructural:
return true;

case ConformanceLookupKind::ExcludeUnexpandedMacros:
switch (conformance->getSourceKind()) {
case ConformanceEntryKind::PreMacroExpansion:
return false;
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Synthesized:
case ConformanceEntryKind::Implied:
case ConformanceEntryKind::Inherited:
return true;
}
}
});

// If we want to add structural conformances, do so now.
switch (lookupKind) {
case ConformanceLookupKind::All:
case ConformanceLookupKind::NonInherited:
case ConformanceLookupKind::ExcludeUnexpandedMacros: {
case ConformanceLookupKind::NonInherited: {
// Look for a Sendable conformance globally. If it is synthesized
// and matches this declaration context, use it.
auto dc = getAsGenericContext();
Expand Down
3 changes: 0 additions & 3 deletions lib/SIL/IR/SILSymbolVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,6 @@ class SILSymbolVisitorImpl : public ASTVisitor<SILSymbolVisitorImpl> {
void addConformances(const IterableDeclContext *IDC) {
for (auto conformance :
IDC->getLocalConformances(ConformanceLookupKind::NonInherited)) {
if (conformance->getSourceKind() == ConformanceEntryKind::PreMacroExpansion)
continue;

auto protocol = conformance->getProtocol();
if (Ctx.getOpts().PublicSymbolsOnly &&
getDeclLinkage(protocol) != FormalLinkage::PublicUnique)
Expand Down
3 changes: 0 additions & 3 deletions lib/SILGen/SILGenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1132,9 +1132,6 @@ class SILGenType : public TypeMemberVisitor<SILGenType> {
// are existential and do not have witness tables.
for (auto *conformance : theType->getLocalConformances(
ConformanceLookupKind::NonInherited)) {
if (conformance->getSourceKind() == ConformanceEntryKind::PreMacroExpansion)
continue;

assert(conformance->isComplete());
if (auto *normal = dyn_cast<NormalProtocolConformance>(conformance))
SGM.getWitnessTable(normal);
Expand Down
20 changes: 8 additions & 12 deletions lib/Sema/TypeCheckMacros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1238,9 +1238,13 @@ static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo,
{
llvm::raw_string_ostream OS(conformanceList);
if (role == MacroRole::Extension) {
for (auto *protocol : conformances) {
protocol->getDeclaredType()->print(OS);
}
llvm::interleave(
conformances,
[&](const ProtocolDecl *protocol) {
protocol->getDeclaredType()->print(OS);
},
[&] { OS << ", "; }
);
} else {
OS << "";
}
Expand Down Expand Up @@ -1593,15 +1597,7 @@ llvm::Optional<unsigned> swift::expandExtensions(CustomAttr *attr,
for (auto protocol : potentialConformances) {
SmallVector<ProtocolConformance *, 2> existingConformances;
nominal->lookupConformance(protocol, existingConformances);

bool hasExistingConformance = llvm::any_of(
existingConformances,
[&](ProtocolConformance *conformance) {
return conformance->getSourceKind() !=
ConformanceEntryKind::PreMacroExpansion;
});

if (!hasExistingConformance) {
if (existingConformances.empty()) {
introducedConformances.push_back(protocol);
}
}
Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6443,8 +6443,7 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
const auto defaultAccess = nominal->getFormalAccess();

// Check each of the conformances associated with this context.
auto conformances = idc->getLocalConformances(
ConformanceLookupKind::ExcludeUnexpandedMacros);
auto conformances = idc->getLocalConformances();

// The conformance checker bundle that checks all conformances in the context.
auto &Context = dc->getASTContext();
Expand Down
1 change: 0 additions & 1 deletion test/IDE/complete_optionset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public macro OptionSet<RawType>() =
// MEMBER_STATIC: Decl[StaticVar]/CurrNominal: secondDay[#ShippingOptions#]; name=secondDay
// MEMBER_STATIC: Decl[StaticVar]/CurrNominal: priority[#ShippingOptions#]; name=priority
// MEMBER_STATIC: Decl[StaticVar]/CurrNominal: standard[#ShippingOptions#]; name=standard
// MEMBER_STATIC: Decl[TypeAlias]/CurrNominal: ArrayLiteralElement[#ShippingOptions#]; name=ArrayLiteralElement
// MEMBER_STATIC: Decl[TypeAlias]/CurrNominal: Element[#ShippingOptions#]; name=Element
// MEMBER_STATIC: Decl[Constructor]/Super/IsSystem: init()[#ShippingOptions#]; name=init()
// MEMBER_STATIC: Decl[Constructor]/Super/IsSystem: init({#(sequence): Sequence#})[#ShippingOptions#]; name=init(:)
51 changes: 51 additions & 0 deletions test/Macros/Inputs/syntax_macro_definitions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,39 @@ public struct EquatableMacro: ConformanceMacro {
}
}

public struct EquatableViaMembersMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo decl: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
let comparisons: [String] = decl.storedProperties().map { property in
guard let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else {
return "true"
}

return "lhs.\(identifier) == rhs.\(identifier)"
}

let condition = comparisons.joined(separator: " && ")
let equalOperator: DeclSyntax = """
static func ==(lhs: \(type.trimmed), rhs: \(type.trimmed)) -> Bool {
return \(raw: condition)
}
"""

let ext: DeclSyntax = """
extension \(type.trimmed): Equatable {
\(equalOperator)
}
"""
return [ext.cast(ExtensionDeclSyntax.self)]
}
}

public struct ConformanceViaExtensionMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
Expand Down Expand Up @@ -1481,6 +1514,24 @@ public struct ConditionallyAvailableConformance: ExtensionMacro {
}
}

public struct AddAllConformancesMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo decl: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
protocols.map { proto in
let decl: DeclSyntax =
"""
extension \(type): \(proto) {}
"""
return decl.cast(ExtensionDeclSyntax.self)
}
}
}

public struct AlwaysAddCodable: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
Expand Down
33 changes: 33 additions & 0 deletions test/Macros/macro_expand_extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,36 @@ macro AvailableEquatable() = #externalMacro(module: "MacroDefinition", type: "Co
struct TestAvailability {
static let x : any Equatable.Type = TestAvailability.self
}

protocol P1 {}
protocol P2 {}

@attached(extension, conformances: P1, P2)
macro AddAllConformances() = #externalMacro(module: "MacroDefinition", type: "AddAllConformancesMacro")

@AddAllConformances
struct MultipleConformances {}

// CHECK-DUMP: extension MultipleConformances: P1 {
// CHECK-DUMP: }
// CHECK-DUMP: extension MultipleConformances: P2 {
// CHECK-DUMP: }

@attached(extension, conformances: Equatable, names: named(==))
macro Equatable() = #externalMacro(module: "MacroDefinition", type: "EquatableViaMembersMacro")

@propertyWrapper
struct NotEquatable<T> {
var wrappedValue: T
}

@Equatable
struct HasPropertyWrappers {
@NotEquatable
var value: Int = 0
}

func requiresEquatable<T: Equatable>(_: T) { }
func testHasPropertyWrappers(hpw: HasPropertyWrappers) {
requiresEquatable(hpw)
}