Skip to content

Commit 0bd898e

Browse files
committed
[Macros] Allow extension macros to suppress conformances that are already
stated in the original source. If an extension macro can introduce protocol conformances, macro expansion will check which of those protocols already have a stated conformance in the original source. The protocols that don't will be passed as arguments to extension macro expansion, indicating to the macro that it should only add conformances to those protocols.
1 parent e44c11f commit 0bd898e

17 files changed

+305
-23
lines changed

include/swift/AST/Decl.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8598,6 +8598,15 @@ class MacroDecl : public GenericContext, public ValueDecl {
85988598
void getIntroducedNames(MacroRole role, ValueDecl *attachedTo,
85998599
SmallVectorImpl<DeclName> &names) const;
86008600

8601+
/// Populate the \c conformances vector with the protocols that
8602+
/// this macro generates conformances to.
8603+
///
8604+
/// Only extension macros can add conformances; no results will
8605+
/// be added if this macro does not contain an extension role.
8606+
void getIntroducedConformances(
8607+
NominalTypeDecl *attachedTo,
8608+
SmallVectorImpl<ProtocolDecl *> &conformances) const;
8609+
86018610
/// Returns a DeclName that represents arbitrary names.
86028611
static DeclName getArbitraryName() {
86038612
return DeclName();

include/swift/AST/DeclContext.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ enum class ConformanceEntryKind : unsigned {
155155

156156
/// Implied by an explicitly-specified conformance.
157157
Implied,
158+
159+
/// The conformance is generated by a macro that has not been
160+
/// expanded yet.
161+
PreMacroExpansion,
158162
};
159163

160164
/// Describes the kind of conformance lookup desired.
@@ -168,6 +172,9 @@ enum class ConformanceLookupKind : unsigned {
168172
/// All conformances except structurally-derived conformances, of which
169173
/// Sendable is the only one.
170174
NonStructural,
175+
/// Exclude conformances added by a macro that has not been expanded
176+
/// yet.
177+
ExcludeUnexpandedMacros,
171178
};
172179

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

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2836,7 +2836,8 @@ NOTE(declared_protocol_conformance_here,none,
28362836
"%select{%0 inherits conformance to protocol %2 from superclass|"
28372837
"%0 declares conformance to protocol %2|"
28382838
"%0 implicitly conforms to protocol %2 (via conformance to %3)|"
2839-
"%0 implicitly conforms to protocol %2}1 here",
2839+
"%0 implicitly conforms to protocol %2 |"
2840+
"conformance to %2 generated by macro }1 here",
28402841
(Type, unsigned, Identifier, Identifier))
28412842

28422843
ERROR(witness_unavailable,none,
@@ -7266,6 +7267,9 @@ ERROR(global_arbitrary_name,none,
72667267
ERROR(local_extension_macro,none,
72677268
"local type cannot have attached extension macro",
72687269
())
7270+
ERROR(extension_macro_invalid_conformance,none,
7271+
"invalid protocol conformance %0 in extension macro",
7272+
(Type))
72697273

72707274
ERROR(macro_resolve_circular_reference, none,
72717275
"circular reference resolving %select{freestanding|attached}0 macro %1",

include/swift/AST/ProtocolConformance.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ class NormalProtocolConformance : public RootProtocolConformance,
441441
///
442442
/// This should never be Inherited: that is handled by
443443
/// InheritedProtocolConformance.
444-
llvm::PointerIntPair<NormalProtocolConformance *, 2, ConformanceEntryKind>
444+
llvm::PointerIntPair<NormalProtocolConformance *, 3, ConformanceEntryKind>
445445
SourceKindAndImplyingConformance = {nullptr,
446446
ConformanceEntryKind::Explicit};
447447

lib/AST/ConformanceLookupTable.cpp

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ DeclContext *ConformanceLookupTable::ConformanceSource::getDeclContext() const {
4343

4444
case ConformanceEntryKind::Synthesized:
4545
return getSynthesizedDeclContext();
46+
47+
case ConformanceEntryKind::PreMacroExpansion:
48+
return getMacroGeneratedDeclContext();
4649
}
4750

4851
llvm_unreachable("Unhandled ConformanceEntryKind in switch.");
@@ -104,6 +107,10 @@ void ConformanceLookupTable::ConformanceEntry::dump(raw_ostream &os,
104107
case ConformanceEntryKind::Synthesized:
105108
os << " synthesized";
106109
break;
110+
111+
case ConformanceEntryKind::PreMacroExpansion:
112+
os << " unexpanded macro";
113+
break;
107114
}
108115

109116
if (auto conf = getConformance()) {
@@ -282,10 +289,8 @@ void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal,
282289
addInheritedProtocols(nominal,
283290
ConformanceSource::forExplicit(nominal));
284291

285-
// Expand extension macros.
286-
ASTContext &ctx = nominal->getASTContext();
287-
(void)evaluateOrDefault(
288-
ctx.evaluator, ExpandExtensionMacros{nominal}, { });
292+
addMacroGeneratedProtocols(
293+
nominal, ConformanceSource::forUnexpandedMacro(nominal));
289294
},
290295
[&](ExtensionDecl *ext,
291296
ArrayRef<ConformanceConstructionInfo> protos) {
@@ -441,6 +446,7 @@ bool ConformanceLookupTable::addProtocol(ProtocolDecl *protocol, SourceLoc loc,
441446
switch (existingEntry->getKind()) {
442447
case ConformanceEntryKind::Explicit:
443448
case ConformanceEntryKind::Inherited:
449+
case ConformanceEntryKind::PreMacroExpansion:
444450
return false;
445451

446452
case ConformanceEntryKind::Implied:
@@ -491,6 +497,20 @@ void ConformanceLookupTable::addInheritedProtocols(
491497
}
492498
}
493499

500+
void ConformanceLookupTable::addMacroGeneratedProtocols(
501+
NominalTypeDecl *nominal, ConformanceSource source) {
502+
nominal->forEachAttachedMacro(
503+
MacroRole::Extension,
504+
[&](CustomAttr *attr, MacroDecl *macro) {
505+
SmallVector<ProtocolDecl *, 2> conformances;
506+
macro->getIntroducedConformances(nominal, conformances);
507+
508+
for (auto *protocol : conformances) {
509+
addProtocol(protocol, attr->getLocation(), source);
510+
}
511+
});
512+
}
513+
494514
void ConformanceLookupTable::expandImpliedConformances(NominalTypeDecl *nominal,
495515
DeclContext *dc) {
496516
// Note: recursive type-checking implies that AllConformances
@@ -530,6 +550,7 @@ static bool isReplaceable(ConformanceEntryKind kind) {
530550
switch (kind) {
531551
case ConformanceEntryKind::Implied:
532552
case ConformanceEntryKind::Synthesized:
553+
case ConformanceEntryKind::PreMacroExpansion:
533554
return true;
534555

535556
case ConformanceEntryKind::Explicit:
@@ -558,6 +579,23 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
558579
: Ordering::After);
559580
}
560581

582+
ConformanceEntryKind lhsKind = lhs->getRankingKind();
583+
ConformanceEntryKind rhsKind = rhs->getRankingKind();
584+
585+
// Pre-expanded macro conformances are always superseded by
586+
// conformances written in source. If the conformance is not
587+
// written in the original source, the pre-expanded conformance
588+
// will be superseded by the conformance in the macro expansion
589+
// buffer.
590+
if (lhsKind == ConformanceEntryKind::PreMacroExpansion ||
591+
rhsKind == ConformanceEntryKind::PreMacroExpansion) {
592+
if (lhsKind != rhsKind) {
593+
return (lhs->getKind() < rhs->getKind()
594+
? Ordering::Before
595+
: Ordering::After);
596+
}
597+
}
598+
561599
// If one entry is fixed and the other is not, we have our answer.
562600
if (lhs->isFixed() != rhs->isFixed()) {
563601
auto isReplaceableOrMarker = [](ConformanceEntry *entry) -> bool {
@@ -580,9 +618,6 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
580618
return lhs->isFixed() ? Ordering::Before : Ordering::After;
581619
}
582620

583-
ConformanceEntryKind lhsKind = lhs->getRankingKind();
584-
ConformanceEntryKind rhsKind = rhs->getRankingKind();
585-
586621
if (lhsKind != ConformanceEntryKind::Implied ||
587622
rhsKind != ConformanceEntryKind::Implied) {
588623
// If both conformances are non-replaceable, diagnose the

lib/AST/ConformanceLookupTable.h

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,15 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
8383
/// Describes the "source" of a conformance, indicating where the
8484
/// conformance came from.
8585
class ConformanceSource {
86-
llvm::PointerIntPair<void *, 2, ConformanceEntryKind> Storage;
86+
void *Storage;
87+
88+
ConformanceEntryKind Kind;
8789

8890
/// The location of the "unchecked" attribute, if there is one.
8991
SourceLoc uncheckedLoc;
9092

9193
ConformanceSource(void *ptr, ConformanceEntryKind kind)
92-
: Storage(ptr, kind) { }
94+
: Storage(ptr), Kind(kind) { }
9395

9496
public:
9597
/// Create an inherited conformance.
@@ -126,6 +128,10 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
126128
return ConformanceSource(dc, ConformanceEntryKind::Synthesized);
127129
}
128130

131+
static ConformanceSource forUnexpandedMacro(DeclContext *dc) {
132+
return ConformanceSource(dc, ConformanceEntryKind::PreMacroExpansion);
133+
}
134+
129135
/// Return a new conformance source with the given location of "@unchecked".
130136
ConformanceSource withUncheckedLoc(SourceLoc uncheckedLoc) {
131137
ConformanceSource result(*this);
@@ -135,7 +141,7 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
135141
}
136142

137143
/// Retrieve the kind of conformance formed from this source.
138-
ConformanceEntryKind getKind() const { return Storage.getInt(); }
144+
ConformanceEntryKind getKind() const { return Kind; }
139145

140146
/// Retrieve kind of the conformance for ranking purposes.
141147
///
@@ -148,6 +154,7 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
148154
case ConformanceEntryKind::Explicit:
149155
case ConformanceEntryKind::Inherited:
150156
case ConformanceEntryKind::Synthesized:
157+
case ConformanceEntryKind::PreMacroExpansion:
151158
return kind;
152159

153160
case ConformanceEntryKind::Implied:
@@ -169,28 +176,33 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
169176
/// for the inheriting class.
170177
ClassDecl *getInheritingClass() const {
171178
assert(getKind() == ConformanceEntryKind::Inherited);
172-
return static_cast<ClassDecl *>(Storage.getPointer());
179+
return static_cast<ClassDecl *>(Storage);
173180
}
174181

175182
/// For an explicit conformance, retrieve the declaration context
176183
/// that specifies the conformance.
177184
DeclContext *getExplicitDeclContext() const {
178185
assert(getKind() == ConformanceEntryKind::Explicit);
179-
return static_cast<DeclContext *>(Storage.getPointer());
186+
return static_cast<DeclContext *>(Storage);
187+
}
188+
189+
DeclContext *getMacroGeneratedDeclContext() const {
190+
assert(getKind() == ConformanceEntryKind::PreMacroExpansion);
191+
return static_cast<DeclContext *>(Storage);
180192
}
181193

182194
/// For a synthesized conformance, retrieve the nominal type decl
183195
/// that will receive the conformance.
184196
ConformanceEntry *getImpliedSource() const {
185197
assert(getKind() == ConformanceEntryKind::Implied);
186-
return static_cast<ConformanceEntry *>(Storage.getPointer());
198+
return static_cast<ConformanceEntry *>(Storage);
187199
}
188200

189201
/// For a synthesized conformance, retrieve the nominal type decl
190202
/// that will receive the conformance.
191203
DeclContext *getSynthesizedDeclContext() const {
192204
assert(getKind() == ConformanceEntryKind::Synthesized);
193-
return static_cast<DeclContext *>(Storage.getPointer());
205+
return static_cast<DeclContext *>(Storage);
194206
}
195207

196208
/// Get the declaration context that this conformance will be
@@ -226,6 +238,10 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
226238

227239
/// Whether this conformance is already "fixed" and cannot be superseded.
228240
bool isFixed() const {
241+
// A conformance from an unexpanded macro can always be superseded.
242+
if (getKind() == ConformanceEntryKind::PreMacroExpansion)
243+
return true;
244+
229245
// If a conformance has been assigned, it cannot be superseded.
230246
if (getConformance())
231247
return true;
@@ -235,6 +251,7 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
235251
case ConformanceEntryKind::Explicit:
236252
case ConformanceEntryKind::Implied:
237253
case ConformanceEntryKind::Synthesized:
254+
case ConformanceEntryKind::PreMacroExpansion:
238255
return false;
239256

240257
case ConformanceEntryKind::Inherited:
@@ -341,6 +358,11 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
341358
llvm::PointerUnion<const TypeDecl *, const ExtensionDecl *> decl,
342359
ConformanceSource source);
343360

361+
/// Add the protocols added by attached extension macros that are not
362+
/// yet expanded.
363+
void addMacroGeneratedProtocols(
364+
NominalTypeDecl *nominal, ConformanceSource source);
365+
344366
/// Expand the implied conformances for the given DeclContext.
345367
void expandImpliedConformances(NominalTypeDecl *nominal, DeclContext *dc);
346368

lib/AST/Decl.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10695,6 +10695,38 @@ void MacroDecl::getIntroducedNames(MacroRole role, ValueDecl *attachedTo,
1069510695
}
1069610696
}
1069710697

10698+
void MacroDecl::getIntroducedConformances(
10699+
NominalTypeDecl *attachedTo,
10700+
SmallVectorImpl<ProtocolDecl *> &conformances) const {
10701+
auto *attr = getMacroRoleAttr(MacroRole::Extension);
10702+
if (!attr)
10703+
return;
10704+
10705+
for (auto repr : attr->getConformances()) {
10706+
auto constraint = repr->getInstanceType();
10707+
assert(constraint->isConstraintType());
10708+
10709+
std::function<void(Type)> addConstraint =
10710+
[&](Type constraint) -> void {
10711+
if (auto *proto = constraint->getAs<ParameterizedProtocolType>()) {
10712+
conformances.push_back(proto->getProtocol());
10713+
return;
10714+
} else if (auto *proto = constraint->getAs<ProtocolType>()) {
10715+
conformances.push_back(proto->getDecl());
10716+
return;
10717+
}
10718+
10719+
auto *composition =
10720+
constraint->castTo<ProtocolCompositionType>();
10721+
for (auto constraint : composition->getMembers()) {
10722+
addConstraint(constraint);
10723+
}
10724+
};
10725+
10726+
addConstraint(constraint);
10727+
}
10728+
}
10729+
1069810730
MacroDefinition MacroDecl::getDefinition() const {
1069910731
return evaluateOrDefault(
1070010732
getASTContext().evaluator,

lib/AST/Module.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,17 @@ LookupConformanceInModuleRequest::evaluate(
19611961
if (!nominal || isa<ProtocolDecl>(nominal))
19621962
return ProtocolConformanceRef::forMissingOrInvalid(type, protocol);
19631963

1964+
// Expand conformances added by extension macros.
1965+
//
1966+
// FIXME: This expansion should only be done if the
1967+
// extension macro can generate a conformance to the
1968+
// given protocol, but conformance macros do not specify
1969+
// that information upfront.
1970+
(void)evaluateOrDefault(
1971+
ctx.evaluator,
1972+
ExpandExtensionMacros{nominal},
1973+
{ });
1974+
19641975
// Find the (unspecialized) conformance.
19651976
SmallVector<ProtocolConformance *, 2> conformances;
19661977
if (!nominal->lookupConformance(protocol, conformances)) {

lib/AST/ProtocolConformance.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,7 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
13021302
switch (conformance->getSourceKind()) {
13031303
case ConformanceEntryKind::Explicit:
13041304
case ConformanceEntryKind::Synthesized:
1305+
case ConformanceEntryKind::PreMacroExpansion:
13051306
return true;
13061307
case ConformanceEntryKind::Implied:
13071308
case ConformanceEntryKind::Inherited:
@@ -1313,6 +1314,7 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
13131314
case ConformanceEntryKind::Explicit:
13141315
case ConformanceEntryKind::Synthesized:
13151316
case ConformanceEntryKind::Implied:
1317+
case ConformanceEntryKind::PreMacroExpansion:
13161318
return true;
13171319
case ConformanceEntryKind::Inherited:
13181320
return false;
@@ -1321,13 +1323,25 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
13211323
case ConformanceLookupKind::All:
13221324
case ConformanceLookupKind::NonStructural:
13231325
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+
}
13241337
}
13251338
});
13261339

13271340
// If we want to add structural conformances, do so now.
13281341
switch (lookupKind) {
13291342
case ConformanceLookupKind::All:
1330-
case ConformanceLookupKind::NonInherited: {
1343+
case ConformanceLookupKind::NonInherited:
1344+
case ConformanceLookupKind::ExcludeUnexpandedMacros: {
13311345
// Look for a Sendable conformance globally. If it is synthesized
13321346
// and matches this declaration context, use it.
13331347
auto dc = getAsGenericContext();

0 commit comments

Comments
 (0)