Skip to content

Commit 2aaa983

Browse files
committed
[Macros] Fix handling of extension macro conformances and witnesses
Fix two inter-related issues with extension macros that provide conformances to a protocol, the combined effect of which is that one cannot meaningfully provide extension macros that implement conformances to a protocol like Equatable or Hashable that also supports auto-synthesis. The first issue involves name lookup of operators provided by macro expansions. The logic for performing qualified lookup in addition to unqualified lookup (for operators) did not account for extension macros in the same manner as it did for member macros, so we would not find a macro-produced operator (such as operator==) in witness matching. The second issue is more fundamental, which is that the conformance lookup table would create `NormalProtocolConformance` instances for pre-macro-expansion conformance entries, even though these should always have been superseded by explicit conformances within the macro expansion buffers. The end result is that we could end up with two `NormalProtocolConformance` records for the same conformance. Some code was taught to ignore the pre-expansion placeholder conformances, other code was not. Instead, we now refuse to create a `NormalProtocolConformance` for the pre-expansion entries, and remove all of the special-case checks for this, so we always using the superseding explicit conformances produced by the macro expansions (or error if the macros don't produce them). Fixes rdar://113994346 / #66348 (cherry picked from commit fa48076)
1 parent 4aefcf8 commit 2aaa983

12 files changed

+108
-49
lines changed

include/swift/AST/DeclContext.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,6 @@ enum class ConformanceLookupKind : unsigned {
172172
/// All conformances except structurally-derived conformances, of which
173173
/// Sendable is the only one.
174174
NonStructural,
175-
/// Exclude conformances added by a macro that has not been expanded
176-
/// yet.
177-
ExcludeUnexpandedMacros,
178175
};
179176

180177
/// Describes a diagnostic for a conflict between two protocol

include/swift/AST/ProtocolConformance.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,8 @@ class NormalProtocolConformance : public RootProtocolConformance,
552552
assert((sourceKind == ConformanceEntryKind::Implied) ==
553553
(bool)implyingConformance &&
554554
"an implied conformance needs something that implies it");
555+
assert(sourceKind != ConformanceEntryKind::PreMacroExpansion &&
556+
"cannot create conformance pre-macro-expansion");
555557
SourceKindAndImplyingConformance = {implyingConformance, sourceKind};
556558
}
557559

lib/AST/ConformanceLookupTable.cpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,16 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
889889
if (!conformingDC)
890890
return nullptr;
891891

892+
// Never produce a conformance for a pre-macro-expansion conformance. They
893+
// are placeholders that will be superseded.
894+
if (entry->getKind() == ConformanceEntryKind::PreMacroExpansion) {
895+
if (auto supersedingEntry = entry->SupersededBy) {
896+
return getConformance(nominal, supersedingEntry);
897+
}
898+
899+
return nullptr;
900+
}
901+
892902
auto *conformingNominal = conformingDC->getSelfNominalTypeDecl();
893903

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

939+
NormalProtocolConformance *implyingConf = nullptr;
940+
if (entry->Source.getKind() == ConformanceEntryKind::Implied) {
941+
auto implyingEntry = entry->Source.getImpliedSource();
942+
auto origImplyingConf = getConformance(conformingNominal, implyingEntry);
943+
if (!origImplyingConf)
944+
return nullptr;
945+
946+
implyingConf = origImplyingConf->getRootNormalConformance();
947+
}
948+
929949
// Create or find the normal conformance.
930950
auto normalConf =
931951
ctx.getConformance(conformingType, protocol, conformanceLoc,
@@ -936,12 +956,6 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
936956
// early return at the start of this function.
937957
entry->Conformance = normalConf;
938958

939-
NormalProtocolConformance *implyingConf = nullptr;
940-
if (entry->Source.getKind() == ConformanceEntryKind::Implied) {
941-
auto implyingEntry = entry->Source.getImpliedSource();
942-
implyingConf = getConformance(conformingNominal, implyingEntry)
943-
->getRootNormalConformance();
944-
}
945959
normalConf->setSourceKindAndImplyingConformance(entry->Source.getKind(),
946960
implyingConf);
947961

lib/AST/NameLookup.cpp

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,17 @@ namespace {
17571757
};
17581758
}
17591759

1760+
/// Given an extension declaration, return the extended nominal type if the
1761+
/// extension was produced by expanding an extension or conformance macro from
1762+
/// the nominal declaration itself.
1763+
static NominalTypeDecl *nominalForExpandedExtensionDecl(ExtensionDecl *ext) {
1764+
if (!ext->isInMacroExpansionInContext())
1765+
return nullptr;
1766+
1767+
1768+
return ext->getSelfNominalTypeDecl();
1769+
}
1770+
17601771
PotentialMacroExpansions PotentialMacroExpansionsInContextRequest::evaluate(
17611772
Evaluator &evaluator, TypeOrExtensionDecl container) const {
17621773
/// The implementation here needs to be kept in sync with
@@ -1767,6 +1778,15 @@ PotentialMacroExpansions PotentialMacroExpansionsInContextRequest::evaluate(
17671778
auto containerDecl = container.getAsDecl();
17681779
forEachPotentialAttachedMacro(containerDecl, MacroRole::Member, nameTracker);
17691780

1781+
// If the container is an extension that was created from an extension macro,
1782+
// look at the nominal declaration to find any extension macros.
1783+
if (auto ext = dyn_cast<ExtensionDecl>(containerDecl)) {
1784+
if (auto nominal = nominalForExpandedExtensionDecl(ext)) {
1785+
forEachPotentialAttachedMacro(
1786+
nominal, MacroRole::Extension, nameTracker);
1787+
}
1788+
}
1789+
17701790
// Peer and freestanding declaration macros.
17711791
auto dc = container.getAsDeclContext();
17721792
auto idc = container.getAsIterableDeclContext();
@@ -1825,13 +1845,15 @@ populateLookupTableEntryFromMacroExpansions(ASTContext &ctx,
18251845
// names match.
18261846
{
18271847
MacroIntroducedNameTracker nameTracker;
1828-
if (auto nominal = dyn_cast<NominalTypeDecl>(container.getAsDecl())) {
1829-
forEachPotentialAttachedMacro(nominal, MacroRole::Extension, nameTracker);
1830-
if (nameTracker.shouldExpandForName(name)) {
1831-
(void)evaluateOrDefault(
1832-
ctx.evaluator,
1833-
ExpandExtensionMacros{nominal},
1834-
false);
1848+
if (auto ext = dyn_cast<ExtensionDecl>(container.getAsDecl())) {
1849+
if (auto nominal = nominalForExpandedExtensionDecl(ext)) {
1850+
forEachPotentialAttachedMacro(nominal, MacroRole::Extension, nameTracker);
1851+
if (nameTracker.shouldExpandForName(name)) {
1852+
(void)evaluateOrDefault(
1853+
ctx.evaluator,
1854+
ExpandExtensionMacros{nominal},
1855+
false);
1856+
}
18351857
}
18361858
}
18371859
}

lib/AST/ProtocolConformance.cpp

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,8 +1302,8 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
13021302
switch (conformance->getSourceKind()) {
13031303
case ConformanceEntryKind::Explicit:
13041304
case ConformanceEntryKind::Synthesized:
1305+
return true;
13051306
case ConformanceEntryKind::PreMacroExpansion:
1306-
return true;
13071307
case ConformanceEntryKind::Implied:
13081308
case ConformanceEntryKind::Inherited:
13091309
return false;
@@ -1314,34 +1314,22 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
13141314
case ConformanceEntryKind::Explicit:
13151315
case ConformanceEntryKind::Synthesized:
13161316
case ConformanceEntryKind::Implied:
1317-
case ConformanceEntryKind::PreMacroExpansion:
13181317
return true;
13191318
case ConformanceEntryKind::Inherited:
1319+
case ConformanceEntryKind::PreMacroExpansion:
13201320
return false;
13211321
}
13221322

13231323
case ConformanceLookupKind::All:
13241324
case ConformanceLookupKind::NonStructural:
13251325
return true;
1326-
1327-
case ConformanceLookupKind::ExcludeUnexpandedMacros:
1328-
switch (conformance->getSourceKind()) {
1329-
case ConformanceEntryKind::PreMacroExpansion:
1330-
return false;
1331-
case ConformanceEntryKind::Explicit:
1332-
case ConformanceEntryKind::Synthesized:
1333-
case ConformanceEntryKind::Implied:
1334-
case ConformanceEntryKind::Inherited:
1335-
return true;
1336-
}
13371326
}
13381327
});
13391328

13401329
// If we want to add structural conformances, do so now.
13411330
switch (lookupKind) {
13421331
case ConformanceLookupKind::All:
1343-
case ConformanceLookupKind::NonInherited:
1344-
case ConformanceLookupKind::ExcludeUnexpandedMacros: {
1332+
case ConformanceLookupKind::NonInherited: {
13451333
// Look for a Sendable conformance globally. If it is synthesized
13461334
// and matches this declaration context, use it.
13471335
auto dc = getAsGenericContext();

lib/SIL/IR/SILSymbolVisitor.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,6 @@ class SILSymbolVisitorImpl : public ASTVisitor<SILSymbolVisitorImpl> {
238238
void addConformances(const IterableDeclContext *IDC) {
239239
for (auto conformance :
240240
IDC->getLocalConformances(ConformanceLookupKind::NonInherited)) {
241-
if (conformance->getSourceKind() == ConformanceEntryKind::PreMacroExpansion)
242-
continue;
243-
244241
auto protocol = conformance->getProtocol();
245242
if (Ctx.getOpts().PublicSymbolsOnly &&
246243
getDeclLinkage(protocol) != FormalLinkage::PublicUnique)

lib/SILGen/SILGenType.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,9 +1132,6 @@ class SILGenType : public TypeMemberVisitor<SILGenType> {
11321132
// are existential and do not have witness tables.
11331133
for (auto *conformance : theType->getLocalConformances(
11341134
ConformanceLookupKind::NonInherited)) {
1135-
if (conformance->getSourceKind() == ConformanceEntryKind::PreMacroExpansion)
1136-
continue;
1137-
11381135
assert(conformance->isComplete());
11391136
if (auto *normal = dyn_cast<NormalProtocolConformance>(conformance))
11401137
SGM.getWitnessTable(normal);

lib/Sema/TypeCheckMacros.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,15 +1593,7 @@ llvm::Optional<unsigned> swift::expandExtensions(CustomAttr *attr,
15931593
for (auto protocol : potentialConformances) {
15941594
SmallVector<ProtocolConformance *, 2> existingConformances;
15951595
nominal->lookupConformance(protocol, existingConformances);
1596-
1597-
bool hasExistingConformance = llvm::any_of(
1598-
existingConformances,
1599-
[&](ProtocolConformance *conformance) {
1600-
return conformance->getSourceKind() !=
1601-
ConformanceEntryKind::PreMacroExpansion;
1602-
});
1603-
1604-
if (!hasExistingConformance) {
1596+
if (existingConformances.empty()) {
16051597
introducedConformances.push_back(protocol);
16061598
}
16071599
}

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6443,8 +6443,7 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
64436443
const auto defaultAccess = nominal->getFormalAccess();
64446444

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

64496448
// The conformance checker bundle that checks all conformances in the context.
64506449
auto &Context = dc->getASTContext();

test/IDE/complete_optionset.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ public macro OptionSet<RawType>() =
2828
// MEMBER_STATIC: Decl[StaticVar]/CurrNominal: secondDay[#ShippingOptions#]; name=secondDay
2929
// MEMBER_STATIC: Decl[StaticVar]/CurrNominal: priority[#ShippingOptions#]; name=priority
3030
// MEMBER_STATIC: Decl[StaticVar]/CurrNominal: standard[#ShippingOptions#]; name=standard
31-
// MEMBER_STATIC: Decl[TypeAlias]/CurrNominal: ArrayLiteralElement[#ShippingOptions#]; name=ArrayLiteralElement
3231
// MEMBER_STATIC: Decl[TypeAlias]/CurrNominal: Element[#ShippingOptions#]; name=Element
3332
// MEMBER_STATIC: Decl[Constructor]/Super/IsSystem: init()[#ShippingOptions#]; name=init()
3433
// MEMBER_STATIC: Decl[Constructor]/Super/IsSystem: init({#(sequence): Sequence#})[#ShippingOptions#]; name=init(:)

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,39 @@ public struct EquatableMacro: ConformanceMacro {
13351335
}
13361336
}
13371337

1338+
public struct EquatableViaMembersMacro: ExtensionMacro {
1339+
public static func expansion(
1340+
of node: AttributeSyntax,
1341+
attachedTo decl: some DeclGroupSyntax,
1342+
providingExtensionsOf type: some TypeSyntaxProtocol,
1343+
conformingTo protocols: [TypeSyntax],
1344+
in context: some MacroExpansionContext
1345+
) throws -> [ExtensionDeclSyntax] {
1346+
let comparisons: [String] = decl.storedProperties().map { property in
1347+
guard let binding = property.bindings.first,
1348+
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else {
1349+
return "true"
1350+
}
1351+
1352+
return "lhs.\(identifier) == rhs.\(identifier)"
1353+
}
1354+
1355+
let condition = comparisons.joined(separator: " && ")
1356+
let equalOperator: DeclSyntax = """
1357+
static func ==(lhs: \(type.trimmed), rhs: \(type.trimmed)) -> Bool {
1358+
return \(raw: condition)
1359+
}
1360+
"""
1361+
1362+
let ext: DeclSyntax = """
1363+
extension \(type.trimmed): Equatable {
1364+
\(equalOperator)
1365+
}
1366+
"""
1367+
return [ext.cast(ExtensionDeclSyntax.self)]
1368+
}
1369+
}
1370+
13381371
public struct ConformanceViaExtensionMacro: ExtensionMacro {
13391372
public static func expansion(
13401373
of node: AttributeSyntax,

test/Macros/macro_expand_extensions.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,22 @@ macro AvailableEquatable() = #externalMacro(module: "MacroDefinition", type: "Co
165165
struct TestAvailability {
166166
static let x : any Equatable.Type = TestAvailability.self
167167
}
168+
169+
@attached(extension, conformances: Equatable, names: named(==))
170+
macro Equatable() = #externalMacro(module: "MacroDefinition", type: "EquatableViaMembersMacro")
171+
172+
@propertyWrapper
173+
struct NotEquatable<T> {
174+
var wrappedValue: T
175+
}
176+
177+
@Equatable
178+
struct HasPropertyWrappers {
179+
@NotEquatable
180+
var value: Int = 0
181+
}
182+
183+
func requiresEquatable<T: Equatable>(_: T) { }
184+
func testHasPropertyWrappers(hpw: HasPropertyWrappers) {
185+
requiresEquatable(hpw)
186+
}

0 commit comments

Comments
 (0)