[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.
This commit is contained in:
Holly Borla
2023-06-30 14:08:56 -07:00
parent e44c11f56f
commit 0bd898eb12
17 changed files with 305 additions and 23 deletions

View File

@@ -43,6 +43,9 @@ DeclContext *ConformanceLookupTable::ConformanceSource::getDeclContext() const {
case ConformanceEntryKind::Synthesized:
return getSynthesizedDeclContext();
case ConformanceEntryKind::PreMacroExpansion:
return getMacroGeneratedDeclContext();
}
llvm_unreachable("Unhandled ConformanceEntryKind in switch.");
@@ -104,6 +107,10 @@ void ConformanceLookupTable::ConformanceEntry::dump(raw_ostream &os,
case ConformanceEntryKind::Synthesized:
os << " synthesized";
break;
case ConformanceEntryKind::PreMacroExpansion:
os << " unexpanded macro";
break;
}
if (auto conf = getConformance()) {
@@ -282,10 +289,8 @@ void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal,
addInheritedProtocols(nominal,
ConformanceSource::forExplicit(nominal));
// Expand extension macros.
ASTContext &ctx = nominal->getASTContext();
(void)evaluateOrDefault(
ctx.evaluator, ExpandExtensionMacros{nominal}, { });
addMacroGeneratedProtocols(
nominal, ConformanceSource::forUnexpandedMacro(nominal));
},
[&](ExtensionDecl *ext,
ArrayRef<ConformanceConstructionInfo> protos) {
@@ -441,6 +446,7 @@ bool ConformanceLookupTable::addProtocol(ProtocolDecl *protocol, SourceLoc loc,
switch (existingEntry->getKind()) {
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Inherited:
case ConformanceEntryKind::PreMacroExpansion:
return false;
case ConformanceEntryKind::Implied:
@@ -491,6 +497,20 @@ void ConformanceLookupTable::addInheritedProtocols(
}
}
void ConformanceLookupTable::addMacroGeneratedProtocols(
NominalTypeDecl *nominal, ConformanceSource source) {
nominal->forEachAttachedMacro(
MacroRole::Extension,
[&](CustomAttr *attr, MacroDecl *macro) {
SmallVector<ProtocolDecl *, 2> conformances;
macro->getIntroducedConformances(nominal, conformances);
for (auto *protocol : conformances) {
addProtocol(protocol, attr->getLocation(), source);
}
});
}
void ConformanceLookupTable::expandImpliedConformances(NominalTypeDecl *nominal,
DeclContext *dc) {
// Note: recursive type-checking implies that AllConformances
@@ -530,6 +550,7 @@ static bool isReplaceable(ConformanceEntryKind kind) {
switch (kind) {
case ConformanceEntryKind::Implied:
case ConformanceEntryKind::Synthesized:
case ConformanceEntryKind::PreMacroExpansion:
return true;
case ConformanceEntryKind::Explicit:
@@ -558,6 +579,23 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
: Ordering::After);
}
ConformanceEntryKind lhsKind = lhs->getRankingKind();
ConformanceEntryKind rhsKind = rhs->getRankingKind();
// Pre-expanded macro conformances are always superseded by
// conformances written in source. If the conformance is not
// written in the original source, the pre-expanded conformance
// will be superseded by the conformance in the macro expansion
// buffer.
if (lhsKind == ConformanceEntryKind::PreMacroExpansion ||
rhsKind == ConformanceEntryKind::PreMacroExpansion) {
if (lhsKind != rhsKind) {
return (lhs->getKind() < rhs->getKind()
? Ordering::Before
: Ordering::After);
}
}
// If one entry is fixed and the other is not, we have our answer.
if (lhs->isFixed() != rhs->isFixed()) {
auto isReplaceableOrMarker = [](ConformanceEntry *entry) -> bool {
@@ -580,9 +618,6 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
return lhs->isFixed() ? Ordering::Before : Ordering::After;
}
ConformanceEntryKind lhsKind = lhs->getRankingKind();
ConformanceEntryKind rhsKind = rhs->getRankingKind();
if (lhsKind != ConformanceEntryKind::Implied ||
rhsKind != ConformanceEntryKind::Implied) {
// If both conformances are non-replaceable, diagnose the