diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 478db907d017d..c9b983dd788dd 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -8598,6 +8598,15 @@ class MacroDecl : public GenericContext, public ValueDecl { void getIntroducedNames(MacroRole role, ValueDecl *attachedTo, SmallVectorImpl &names) const; + /// Populate the \c conformances vector with the protocols that + /// this macro generates conformances to. + /// + /// Only extension macros can add conformances; no results will + /// be added if this macro does not contain an extension role. + void getIntroducedConformances( + NominalTypeDecl *attachedTo, + SmallVectorImpl &conformances) const; + /// Returns a DeclName that represents arbitrary names. static DeclName getArbitraryName() { return DeclName(); diff --git a/include/swift/AST/DeclContext.h b/include/swift/AST/DeclContext.h index 7a0029951f85b..d71dcb9780b59 100644 --- a/include/swift/AST/DeclContext.h +++ b/include/swift/AST/DeclContext.h @@ -155,6 +155,10 @@ enum class ConformanceEntryKind : unsigned { /// Implied by an explicitly-specified conformance. Implied, + + /// The conformance is generated by a macro that has not been + /// expanded yet. + PreMacroExpansion, }; /// Describes the kind of conformance lookup desired. @@ -168,6 +172,9 @@ 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 diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index f73bdc8029459..69bd13bd69abc 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2836,7 +2836,8 @@ NOTE(declared_protocol_conformance_here,none, "%select{%0 inherits conformance to protocol %2 from superclass|" "%0 declares conformance to protocol %2|" "%0 implicitly conforms to protocol %2 (via conformance to %3)|" - "%0 implicitly conforms to protocol %2}1 here", + "%0 implicitly conforms to protocol %2 |" + "conformance to %2 generated by macro }1 here", (Type, unsigned, Identifier, Identifier)) ERROR(witness_unavailable,none, @@ -7266,6 +7267,9 @@ ERROR(global_arbitrary_name,none, ERROR(local_extension_macro,none, "local type cannot have attached extension macro", ()) +ERROR(extension_macro_invalid_conformance,none, + "invalid protocol conformance %0 in extension macro", + (Type)) ERROR(macro_resolve_circular_reference, none, "circular reference resolving %select{freestanding|attached}0 macro %1", diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 0bda83072f469..456b8b60a18e0 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -441,7 +441,7 @@ class NormalProtocolConformance : public RootProtocolConformance, /// /// This should never be Inherited: that is handled by /// InheritedProtocolConformance. - llvm::PointerIntPair + llvm::PointerIntPair SourceKindAndImplyingConformance = {nullptr, ConformanceEntryKind::Explicit}; diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 3fd6b2c7022d1..b278bd49cc55e 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3336,6 +3336,25 @@ class ResolveMacroRequest void noteCycleStep(DiagnosticEngine &diags) const; }; +/// Returns the resolved constraint types that an extension macro +/// adds conformances to. +class ResolveExtensionMacroConformances + : public SimpleRequest(const MacroRoleAttr *, const Decl *), + RequestFlags::Cached> { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + ArrayRef evaluate(Evaluator &evaluator, + const MacroRoleAttr *, const Decl *) const; + +public: + bool isCached() const { return true; } +}; + class ResolveTypeEraserTypeRequest : public SimpleRequest(NominalTypeDecl *), - RequestFlags::Cached> { -public: - using SimpleRequest::SimpleRequest; - -private: - friend SimpleRequest; - - ArrayRef evaluate(Evaluator &evaluator, - NominalTypeDecl *nominal) const; - -public: - bool isCached() const { return true; } - void diagnoseCycle(DiagnosticEngine &diags) const; - void noteCycleStep(DiagnosticEngine &diags) const; -}; - /// Expand all extension macros attached to the given declaration. /// /// Produces the set of macro expansion buffer IDs. diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index abdab2e6eb716..8e697933619ca 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -360,6 +360,9 @@ SWIFT_REQUEST(TypeChecker, ResolveImplicitMemberRequest, SWIFT_REQUEST(TypeChecker, ResolveMacroRequest, ConcreteDeclRef(UnresolvedMacroReference, const Decl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, ResolveExtensionMacroConformances, + ArrayRef(const MacroRoleAttr *, const Decl *), + Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, ResolveTypeEraserTypeRequest, Type(ProtocolDecl *, TypeEraserAttr *), SeparatelyCached, NoLocationInfo) @@ -457,9 +460,6 @@ SWIFT_REQUEST(TypeChecker, ExpandMemberAttributeMacros, SWIFT_REQUEST(TypeChecker, ExpandAccessorMacros, ArrayRef(AbstractStorageDecl *), Cached, NoLocationInfo) -SWIFT_REQUEST(TypeChecker, ExpandConformanceMacros, - ArrayRef(NominalTypeDecl *), - Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, ExpandExtensionMacros, ArrayRef(NominalTypeDecl *), Cached, NoLocationInfo) diff --git a/lib/AST/ConformanceLookupTable.cpp b/lib/AST/ConformanceLookupTable.cpp index 62966fde34e13..81b0d4e3849cb 100644 --- a/lib/AST/ConformanceLookupTable.cpp +++ b/lib/AST/ConformanceLookupTable.cpp @@ -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,14 +289,8 @@ void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal, addInheritedProtocols(nominal, ConformanceSource::forExplicit(nominal)); - // Expand conformance macros. - ASTContext &ctx = nominal->getASTContext(); - (void)evaluateOrDefault( - ctx.evaluator, ExpandConformanceMacros{nominal}, { }); - - // Expand extension macros. - (void)evaluateOrDefault( - ctx.evaluator, ExpandExtensionMacros{nominal}, { }); + addMacroGeneratedProtocols( + nominal, ConformanceSource::forUnexpandedMacro(nominal)); }, [&](ExtensionDecl *ext, ArrayRef protos) { @@ -445,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: @@ -495,6 +497,20 @@ void ConformanceLookupTable::addInheritedProtocols( } } +void ConformanceLookupTable::addMacroGeneratedProtocols( + NominalTypeDecl *nominal, ConformanceSource source) { + nominal->forEachAttachedMacro( + MacroRole::Extension, + [&](CustomAttr *attr, MacroDecl *macro) { + SmallVector 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 @@ -534,6 +550,7 @@ static bool isReplaceable(ConformanceEntryKind kind) { switch (kind) { case ConformanceEntryKind::Implied: case ConformanceEntryKind::Synthesized: + case ConformanceEntryKind::PreMacroExpansion: return true; case ConformanceEntryKind::Explicit: @@ -562,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 { @@ -584,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 diff --git a/lib/AST/ConformanceLookupTable.h b/lib/AST/ConformanceLookupTable.h index 79ca72c4e90f6..b7a32601ce32f 100644 --- a/lib/AST/ConformanceLookupTable.h +++ b/lib/AST/ConformanceLookupTable.h @@ -83,13 +83,15 @@ class ConformanceLookupTable : public ASTAllocated { /// Describes the "source" of a conformance, indicating where the /// conformance came from. class ConformanceSource { - llvm::PointerIntPair Storage; + void *Storage; + + ConformanceEntryKind Kind; /// The location of the "unchecked" attribute, if there is one. SourceLoc uncheckedLoc; ConformanceSource(void *ptr, ConformanceEntryKind kind) - : Storage(ptr, kind) { } + : Storage(ptr), Kind(kind) { } public: /// Create an inherited conformance. @@ -126,6 +128,10 @@ class ConformanceLookupTable : public ASTAllocated { return ConformanceSource(dc, ConformanceEntryKind::Synthesized); } + static ConformanceSource forUnexpandedMacro(DeclContext *dc) { + return ConformanceSource(dc, ConformanceEntryKind::PreMacroExpansion); + } + /// Return a new conformance source with the given location of "@unchecked". ConformanceSource withUncheckedLoc(SourceLoc uncheckedLoc) { ConformanceSource result(*this); @@ -135,7 +141,7 @@ class ConformanceLookupTable : public ASTAllocated { } /// Retrieve the kind of conformance formed from this source. - ConformanceEntryKind getKind() const { return Storage.getInt(); } + ConformanceEntryKind getKind() const { return Kind; } /// Retrieve kind of the conformance for ranking purposes. /// @@ -148,6 +154,7 @@ class ConformanceLookupTable : public ASTAllocated { case ConformanceEntryKind::Explicit: case ConformanceEntryKind::Inherited: case ConformanceEntryKind::Synthesized: + case ConformanceEntryKind::PreMacroExpansion: return kind; case ConformanceEntryKind::Implied: @@ -169,28 +176,33 @@ class ConformanceLookupTable : public ASTAllocated { /// for the inheriting class. ClassDecl *getInheritingClass() const { assert(getKind() == ConformanceEntryKind::Inherited); - return static_cast(Storage.getPointer()); + return static_cast(Storage); } /// For an explicit conformance, retrieve the declaration context /// that specifies the conformance. DeclContext *getExplicitDeclContext() const { assert(getKind() == ConformanceEntryKind::Explicit); - return static_cast(Storage.getPointer()); + return static_cast(Storage); + } + + DeclContext *getMacroGeneratedDeclContext() const { + assert(getKind() == ConformanceEntryKind::PreMacroExpansion); + return static_cast(Storage); } /// For a synthesized conformance, retrieve the nominal type decl /// that will receive the conformance. ConformanceEntry *getImpliedSource() const { assert(getKind() == ConformanceEntryKind::Implied); - return static_cast(Storage.getPointer()); + return static_cast(Storage); } /// For a synthesized conformance, retrieve the nominal type decl /// that will receive the conformance. DeclContext *getSynthesizedDeclContext() const { assert(getKind() == ConformanceEntryKind::Synthesized); - return static_cast(Storage.getPointer()); + return static_cast(Storage); } /// Get the declaration context that this conformance will be @@ -226,6 +238,10 @@ class ConformanceLookupTable : public ASTAllocated { /// Whether this conformance is already "fixed" and cannot be superseded. bool isFixed() const { + // A conformance from an unexpanded macro can always be superseded. + if (getKind() == ConformanceEntryKind::PreMacroExpansion) + return true; + // If a conformance has been assigned, it cannot be superseded. if (getConformance()) return true; @@ -235,6 +251,7 @@ class ConformanceLookupTable : public ASTAllocated { case ConformanceEntryKind::Explicit: case ConformanceEntryKind::Implied: case ConformanceEntryKind::Synthesized: + case ConformanceEntryKind::PreMacroExpansion: return false; case ConformanceEntryKind::Inherited: @@ -341,6 +358,11 @@ class ConformanceLookupTable : public ASTAllocated { llvm::PointerUnion decl, ConformanceSource source); + /// Add the protocols added by attached extension macros that are not + /// yet expanded. + void addMacroGeneratedProtocols( + NominalTypeDecl *nominal, ConformanceSource source); + /// Expand the implied conformances for the given DeclContext. void expandImpliedConformances(NominalTypeDecl *nominal, DeclContext *dc); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 76a43b3439e6d..1de6e489ddc15 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -10695,6 +10695,43 @@ void MacroDecl::getIntroducedNames(MacroRole role, ValueDecl *attachedTo, } } +void MacroDecl::getIntroducedConformances( + NominalTypeDecl *attachedTo, + SmallVectorImpl &conformances) const { + auto *attr = getMacroRoleAttr(MacroRole::Extension); + if (!attr) + return; + + auto &ctx = getASTContext(); + auto constraintTypes = evaluateOrDefault( + ctx.evaluator, + ResolveExtensionMacroConformances{attr, this}, + {}); + + for (auto constraint : constraintTypes) { + assert(constraint->isConstraintType()); + + std::function addConstraint = + [&](Type constraint) -> void { + if (auto *proto = constraint->getAs()) { + conformances.push_back(proto->getProtocol()); + return; + } else if (auto *proto = constraint->getAs()) { + conformances.push_back(proto->getDecl()); + return; + } + + auto *composition = + constraint->castTo(); + for (auto constraint : composition->getMembers()) { + addConstraint(constraint); + } + }; + + addConstraint(constraint); + } +} + MacroDefinition MacroDecl::getDefinition() const { return evaluateOrDefault( getASTContext().evaluator, diff --git a/lib/AST/Module.cpp b/lib/AST/Module.cpp index efddbe35aca2f..d443b4f89dac1 100644 --- a/lib/AST/Module.cpp +++ b/lib/AST/Module.cpp @@ -1961,6 +1961,17 @@ LookupConformanceInModuleRequest::evaluate( if (!nominal || isa(nominal)) return ProtocolConformanceRef::forMissingOrInvalid(type, protocol); + // Expand conformances added by extension macros. + // + // FIXME: This expansion should only be done if the + // extension macro can generate a conformance to the + // given protocol, but conformance macros do not specify + // that information upfront. + (void)evaluateOrDefault( + ctx.evaluator, + ExpandExtensionMacros{nominal}, + { }); + // Find the (unspecialized) conformance. SmallVector conformances; if (!nominal->lookupConformance(protocol, conformances)) { diff --git a/lib/AST/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index 6729809022821..10bb94836bfd8 100644 --- a/lib/AST/ProtocolConformance.cpp +++ b/lib/AST/ProtocolConformance.cpp @@ -1302,6 +1302,7 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind) switch (conformance->getSourceKind()) { case ConformanceEntryKind::Explicit: case ConformanceEntryKind::Synthesized: + case ConformanceEntryKind::PreMacroExpansion: return true; case ConformanceEntryKind::Implied: case ConformanceEntryKind::Inherited: @@ -1313,6 +1314,7 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind) case ConformanceEntryKind::Explicit: case ConformanceEntryKind::Synthesized: case ConformanceEntryKind::Implied: + case ConformanceEntryKind::PreMacroExpansion: return true; case ConformanceEntryKind::Inherited: return false; @@ -1321,13 +1323,25 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind) 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::NonInherited: + case ConformanceLookupKind::ExcludeUnexpandedMacros: { // Look for a Sendable conformance globally. If it is synthesized // and matches this declaration context, use it. auto dc = getAsGenericContext(); diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index f77aefc92e2f2..e8c52dd003b01 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1857,22 +1857,6 @@ void ExpandAccessorMacros::noteCycleStep(DiagnosticEngine &diags) const { decl->getName()); } -void ExpandConformanceMacros::diagnoseCycle(DiagnosticEngine &diags) const { - auto decl = std::get<0>(getStorage()); - diags.diagnose(decl->getLoc(), - diag::macro_expand_circular_reference_entity, - "conformance", - decl->getName()); -} - -void ExpandConformanceMacros::noteCycleStep(DiagnosticEngine &diags) const { - auto decl = std::get<0>(getStorage()); - diags.diagnose(decl->getLoc(), - diag::macro_expand_circular_reference_entity_through, - "conformance", - decl->getName()); -} - void ExpandExtensionMacros::diagnoseCycle(DiagnosticEngine &diags) const { auto decl = std::get<0>(getStorage()); diags.diagnose(decl->getLoc(), diff --git a/lib/ASTGen/Sources/ASTGen/Macros.swift b/lib/ASTGen/Sources/ASTGen/Macros.swift index fe8df7d5b8eec..44f7221f46015 100644 --- a/lib/ASTGen/Sources/ASTGen/Macros.swift +++ b/lib/ASTGen/Sources/ASTGen/Macros.swift @@ -708,6 +708,8 @@ func expandAttachedMacro( discriminatorTextLength: Int, qualifiedTypeText: UnsafePointer, qualifiedTypeLength: Int, + conformanceListText: UnsafePointer, + conformanceListLength: Int, rawMacroRole: UInt8, customAttrSourceFilePtr: UnsafeRawPointer, customAttrSourceLocPointer: UnsafePointer?, @@ -764,6 +766,11 @@ func expandAttachedMacro( ) let qualifiedType = String(decoding: qualifiedTypeBuffer, as: UTF8.self) + let conformanceListBuffer = UnsafeBufferPointer( + start: conformanceListText, count: conformanceListLength + ) + let conformanceList = String(decoding: conformanceListBuffer, as: UTF8.self) + let expandedSource: String? switch MacroPluginKind(rawValue: macroKind)! { @@ -774,6 +781,7 @@ func expandAttachedMacro( rawMacroRole: rawMacroRole, discriminator: discriminator, qualifiedType: qualifiedType, + conformanceList: conformanceList, customAttrSourceFilePtr: customAttrSourceFilePtr, customAttrNode: customAttrNode, declarationSourceFilePtr: declarationSourceFilePtr, @@ -787,6 +795,7 @@ func expandAttachedMacro( rawMacroRole: rawMacroRole, discriminator: discriminator, qualifiedType: qualifiedType, + conformanceList: conformanceList, customAttrSourceFilePtr: customAttrSourceFilePtr, customAttrNode: customAttrNode, declarationSourceFilePtr: declarationSourceFilePtr, @@ -808,6 +817,7 @@ func expandAttachedMacroIPC( rawMacroRole: UInt8, discriminator: String, qualifiedType: String, + conformanceList: String, customAttrSourceFilePtr: UnsafePointer, customAttrNode: AttributeSyntax, declarationSourceFilePtr: UnsafePointer, @@ -856,6 +866,17 @@ func expandAttachedMacroIPC( extendedTypeSyntax = nil } + let conformanceListSyntax: PluginMessage.Syntax? + if (qualifiedType.isEmpty) { + conformanceListSyntax = nil + } else { + let placeholderDecl: DeclSyntax = + """ + struct Placeholder: \(raw: conformanceList) {} + """ + conformanceListSyntax = .init(syntax: Syntax(placeholderDecl))! + } + // Send the message. let message = HostToPluginMessage.expandAttachedMacro( macro: .init(moduleName: macro.moduleName, typeName: macro.typeName, name: macroName), @@ -864,7 +885,8 @@ func expandAttachedMacroIPC( attributeSyntax: customAttributeSyntax, declSyntax: declSyntax, parentDeclSyntax: parentDeclSyntax, - extendedTypeSyntax: extendedTypeSyntax) + extendedTypeSyntax: extendedTypeSyntax, + conformanceListSyntax: conformanceListSyntax) do { let expandedSource: String? let diagnostics: [PluginMessage.Diagnostic] @@ -929,6 +951,7 @@ func expandAttachedMacroInProcess( rawMacroRole: UInt8, discriminator: String, qualifiedType: String, + conformanceList: String, customAttrSourceFilePtr: UnsafePointer, customAttrNode: AttributeSyntax, declarationSourceFilePtr: UnsafePointer, @@ -976,6 +999,22 @@ func expandAttachedMacroInProcess( let parentDeclNode = parentDeclNode.map { sourceManager.detach($0) } let extendedType: TypeSyntax = "\(raw: qualifiedType)" + let conformanceListSyntax: InheritedTypeListSyntax? + if (conformanceList.isEmpty) { + conformanceListSyntax = nil + } else { + let placeholderDecl: DeclSyntax = + """ + struct Placeholder: \(raw: conformanceList) {} + """ + let placeholderStruct = placeholderDecl.cast(StructDeclSyntax.self) + if let inheritanceClause = placeholderStruct.inheritanceClause { + conformanceListSyntax = inheritanceClause.inheritedTypeCollection + } else { + conformanceListSyntax = nil + } + } + return SwiftSyntaxMacroExpansion.expandAttachedMacro( definition: macro, macroRole: MacroRole(rawMacroRole: rawMacroRole), @@ -983,6 +1022,7 @@ func expandAttachedMacroInProcess( declarationNode: declarationNode, parentDeclNode: parentDeclNode, extendedType: extendedType, + conformanceList: conformanceListSyntax, in: context ) } diff --git a/lib/Refactoring/Refactoring.cpp b/lib/Refactoring/Refactoring.cpp index f4a3eee3eb2db..7baad98c5aee7 100644 --- a/lib/Refactoring/Refactoring.cpp +++ b/lib/Refactoring/Refactoring.cpp @@ -8673,15 +8673,8 @@ getMacroExpansionBuffers(MacroDecl *macro, const CustomAttr *attr, Decl *decl) { allBufferIDs.append(bufferIDs.begin(), bufferIDs.end()); } - if (roles.contains(MacroRole::Conformance)) { - if (auto nominal = dyn_cast(decl)) { - auto bufferIDs = evaluateOrDefault( - ctx.evaluator, ExpandConformanceMacros{nominal}, { }); - allBufferIDs.append(bufferIDs.begin(), bufferIDs.end()); - } - } - - if (roles.contains(MacroRole::Extension)) { + if (roles.contains(MacroRole::Conformance) || + roles.contains(MacroRole::Extension)) { if (auto nominal = dyn_cast(decl)) { auto bufferIDs = evaluateOrDefault( ctx.evaluator, ExpandExtensionMacros{nominal}, { }); diff --git a/lib/SILGen/SILGenType.cpp b/lib/SILGen/SILGenType.cpp index 02a7b48a27c55..be3b668f67ca2 100644 --- a/lib/SILGen/SILGenType.cpp +++ b/lib/SILGen/SILGenType.cpp @@ -1132,6 +1132,9 @@ class SILGenType : public TypeMemberVisitor { // 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(conformance)) SGM.getWitnessTable(normal); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index f3729d33dc922..85075ca74f150 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -7136,24 +7136,10 @@ void AttributeChecker::visitMacroRoleAttr(MacroRoleAttr *attr) { } } - for (auto *typeExpr : attr->getConformances()) { - if (auto *typeRepr = typeExpr->getTypeRepr()) { - auto *dc = D->getDeclContext(); - auto resolved = - TypeResolution::forInterface( - dc, TypeResolverContext::GenericRequirement, - /*unboundTyOpener*/ nullptr, - /*placeholderHandler*/ nullptr, - /*packElementOpener*/ nullptr) - .resolveType(typeRepr); - - if (resolved->is()) { - attr->setInvalid(); - } else { - typeExpr->setType(MetatypeType::get(resolved)); - } - } - } + (void)evaluateOrDefault( + Ctx.evaluator, + ResolveExtensionMacroConformances{attr, D}, + {}); } namespace { diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index 85ae67077eb5f..b70488193ff97 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -1935,6 +1935,14 @@ class DeclChecker : public DeclVisitor { .fixItReplace(VD->getNameLoc(), "`" + VD->getBaseName().userFacingName().str() + "`"); } + + // Expand extension macros. + if (auto *nominal = dyn_cast(VD)) { + (void)evaluateOrDefault( + Ctx.evaluator, + ExpandExtensionMacros{nominal}, + { }); + } } } diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index 8a6b7403d77ae..ff73a1cd85605 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -17,6 +17,7 @@ #include "TypeCheckMacros.h" #include "../AST/InlinableText.h" #include "TypeChecker.h" +#include "TypeCheckType.h" #include "swift/ABI/MetadataValues.h" #include "swift/AST/ASTContext.h" #include "swift/AST/ASTMangler.h" @@ -75,6 +76,7 @@ extern "C" ptrdiff_t swift_ASTGen_expandAttachedMacro( void *diagEngine, void *macro, uint8_t externalKind, const char *discriminator, ptrdiff_t discriminatorLength, const char *qualifiedType, ptrdiff_t qualifiedTypeLength, + const char *conformances, ptrdiff_t conformancesLength, uint8_t rawMacroRole, void *customAttrSourceFile, const void *customAttrSourceLocation, void *declarationSourceFile, const void *declarationSourceLocation, @@ -867,8 +869,10 @@ static uint8_t getRawMacroRole(MacroRole role) { case MacroRole::MemberAttribute: return 3; case MacroRole::Member: return 4; case MacroRole::Peer: return 5; - case MacroRole::Conformance: return 6; case MacroRole::CodeItem: return 7; + // Use the same raw macro role for conformance and extension + // in ASTGen. + case MacroRole::Conformance: case MacroRole::Extension: return 8; } } @@ -1093,6 +1097,7 @@ swift::expandFreestandingMacro(MacroExpansionDecl *med) { static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, CustomAttr *attr, bool passParentContext, MacroRole role, + ArrayRef conformances = {}, StringRef discriminatorStr = "") { DeclContext *dc; if (role == MacroRole::Peer) { @@ -1156,7 +1161,7 @@ static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, std::string extendedType; { llvm::raw_string_ostream OS(extendedType); - if (role == MacroRole::Extension) { + if (role == MacroRole::Extension || role == MacroRole::Conformance) { auto *nominal = dyn_cast(attachedTo); PrintOptions options; options.FullyQualifiedExtendedTypesIfAmbiguous = true; @@ -1166,6 +1171,18 @@ static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, } } + std::string conformanceList; + { + llvm::raw_string_ostream OS(conformanceList); + if (role == MacroRole::Extension) { + for (auto *protocol : conformances) { + protocol->getDeclaredType()->print(OS); + } + } else { + OS << ""; + } + } + auto macroDef = macro->getDefinition(); switch (macroDef.kind) { case MacroDefinition::Kind::Undefined: @@ -1240,6 +1257,7 @@ static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, static_cast(externalDef->kind), discriminator->data(), discriminator->size(), extendedType.data(), extendedType.size(), + conformanceList.data(), conformanceList.size(), getRawMacroRole(role), astGenAttrSourceFile, attr->AtLoc.getOpaquePointerValue(), astGenDeclSourceFile, searchDecl->getStartLoc().getOpaquePointerValue(), @@ -1454,32 +1472,31 @@ llvm::Optional swift::expandPeers(CustomAttr *attr, MacroDecl *macro, return macroSourceFile->getBufferID(); } -ArrayRef -ExpandConformanceMacros::evaluate(Evaluator &evaluator, - NominalTypeDecl *nominal) const { - SmallVector bufferIDs; - nominal->forEachAttachedMacro(MacroRole::Conformance, - [&](CustomAttr *attr, MacroDecl *macro) { - if (auto bufferID = expandExtensions(attr, macro, - MacroRole::Conformance, - nominal)) - bufferIDs.push_back(*bufferID); - }); - - return nominal->getASTContext().AllocateCopy(bufferIDs); -} - ArrayRef ExpandExtensionMacros::evaluate(Evaluator &evaluator, NominalTypeDecl *nominal) const { SmallVector bufferIDs; - nominal->forEachAttachedMacro(MacroRole::Extension, - [&](CustomAttr *attr, MacroDecl *macro) { - if (auto bufferID = expandExtensions(attr, macro, - MacroRole::Extension, - nominal)) - bufferIDs.push_back(*bufferID); - }); + for (auto customAttrConst : nominal->getSemanticAttrs().getAttributes()) { + auto customAttr = const_cast(customAttrConst); + auto *macro = nominal->getResolvedMacro(customAttr); + + if (!macro) + continue; + + // Prefer the extension role + MacroRole role; + if (macro->getMacroRoles().contains(MacroRole::Extension)) { + role = MacroRole::Extension; + } else if (macro->getMacroRoles().contains(MacroRole::Conformance)) { + role = MacroRole::Conformance; + } else { + continue; + } + + if (auto bufferID = expandExtensions(customAttr, macro, + role, nominal)) + bufferIDs.push_back(*bufferID); + } return nominal->getASTContext().AllocateCopy(bufferIDs); } @@ -1492,9 +1509,33 @@ swift::expandExtensions(CustomAttr *attr, MacroDecl *macro, return llvm::None; } + // Collect the protocol conformances that the macro can add. The + // macro should not add conformances that are already stated in + // the original source. + + SmallVector potentialConformances; + macro->getIntroducedConformances(nominal, potentialConformances); + + SmallVector introducedConformances; + for (auto protocol : potentialConformances) { + SmallVector existingConformances; + nominal->lookupConformance(protocol, existingConformances); + + bool hasExistingConformance = llvm::any_of( + existingConformances, + [&](ProtocolConformance *conformance) { + return conformance->getSourceKind() != + ConformanceEntryKind::PreMacroExpansion; + }); + + if (!hasExistingConformance) { + introducedConformances.push_back(protocol); + } + } + auto macroSourceFile = ::evaluateAttachedMacro(macro, nominal, attr, /*passParentContext=*/false, - role); + role, introducedConformances); if (!macroSourceFile) return llvm::None; @@ -1635,6 +1676,43 @@ ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator, return macroExpansion->getMacroRef(); } +ArrayRef +ResolveExtensionMacroConformances::evaluate(Evaluator &evaluator, + const MacroRoleAttr *attr, + const Decl *decl) const { + auto *dc = decl->getDeclContext(); + auto &ctx = dc->getASTContext(); + + SmallVector protocols; + for (auto *typeExpr : attr->getConformances()) { + if (auto *typeRepr = typeExpr->getTypeRepr()) { + auto resolved = + TypeResolution::forInterface( + dc, TypeResolverContext::GenericRequirement, + /*unboundTyOpener*/ nullptr, + /*placeholderHandler*/ nullptr, + /*packElementOpener*/ nullptr) + .resolveType(typeRepr); + + if (resolved->is()) + continue; + + if (!resolved->isConstraintType()) { + diagnoseAndRemoveAttr( + decl, attr, + diag::extension_macro_invalid_conformance, + resolved); + continue; + } + + typeExpr->setType(MetatypeType::get(resolved)); + protocols.push_back(resolved); + } + } + + return ctx.AllocateCopy(protocols); +} + // MARK: for IDE. SourceFile *swift::evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, @@ -1642,7 +1720,7 @@ SourceFile *swift::evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, bool passParentContext, MacroRole role, StringRef discriminator) { return ::evaluateAttachedMacro(macro, attachedTo, attr, passParentContext, - role, discriminator); + role, {}, discriminator); } SourceFile * diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 17b7b4dce2065..93c956877253d 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -6444,7 +6444,8 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) { const auto defaultAccess = nominal->getFormalAccess(); // Check each of the conformances associated with this context. - auto conformances = idc->getLocalConformances(); + auto conformances = idc->getLocalConformances( + ConformanceLookupKind::ExcludeUnexpandedMacros); // The conformance checker bundle that checks all conformances in the context. auto &Context = dc->getASTContext(); diff --git a/test/Macros/Inputs/macro_expand_conformances_other.swift b/test/Macros/Inputs/macro_expand_conformances_other.swift index 0837ccdcb257e..e293fb12f262f 100644 --- a/test/Macros/Inputs/macro_expand_conformances_other.swift +++ b/test/Macros/Inputs/macro_expand_conformances_other.swift @@ -1,3 +1,6 @@ struct STest { var x = requireEquatable(S()) } + +@ConformanceViaExtension +class Child: Parent {} diff --git a/test/Macros/Inputs/syntax_macro_definitions.swift b/test/Macros/Inputs/syntax_macro_definitions.swift index 1526b566fc676..3e7e2f82e5b0f 100644 --- a/test/Macros/Inputs/syntax_macro_definitions.swift +++ b/test/Macros/Inputs/syntax_macro_definitions.swift @@ -1253,6 +1253,30 @@ public struct EquatableMacro: ConformanceMacro { } } +public struct ConformanceViaExtensionMacro: 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] { + if (protocols.isEmpty) { + return [] + } + + let decl: DeclSyntax = + """ + extension \(raw: type.trimmedDescription): MyProtocol { + } + """ + + return [ + decl.cast(ExtensionDeclSyntax.self) + ] + } +} + public struct HashableMacro: ConformanceMacro { public static func expansion( of node: AttributeSyntax, @@ -1299,13 +1323,18 @@ public struct DelegatedConformanceMacro: ConformanceMacro, MemberMacro { } } -extension DelegatedConformanceMacro: ExtensionMacro { +public struct DelegatedConformanceViaExtensionMacro: 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] { + if (protocols.isEmpty) { + return [] + } + let decl: DeclSyntax = """ extension \(raw: type.trimmedDescription): P where Element: P { diff --git a/test/Macros/extension_macro_plugin.swift b/test/Macros/extension_macro_plugin.swift index 55e0acfaa8f02..5204d9c74fb4b 100644 --- a/test/Macros/extension_macro_plugin.swift +++ b/test/Macros/extension_macro_plugin.swift @@ -24,7 +24,7 @@ // RUN: %FileCheck %s < %t/macro-expansions.txt @attached(extension, conformances: P, names: named(requirement)) -macro DelegatedConformance() = #externalMacro(module: "MacroDefinition", type: "DelegatedConformanceMacro") +macro DelegatedConformance() = #externalMacro(module: "MacroDefinition", type: "DelegatedConformanceViaExtensionMacro") protocol P { static func requirement() diff --git a/test/Macros/macro_expand_conformances.swift b/test/Macros/macro_expand_conformances.swift index cf85e22b94a5b..797b7cf5b064d 100644 --- a/test/Macros/macro_expand_conformances.swift +++ b/test/Macros/macro_expand_conformances.swift @@ -53,7 +53,8 @@ enum E { } // CHECK-DUMP: @__swiftmacro_25macro_expand_conformances1S9EquatablefMc_.swift -// CHECK-DUMP: extension S : Equatable {} +// CHECK-DUMP: extension S: Equatable { +// CHECK-DUMP: } // CHECK: true requireEquatable(S()) @@ -94,7 +95,8 @@ struct Wrapped: P { struct Generic {} // CHECK-DUMP: @__swiftmacro_25macro_expand_conformances7Generic20DelegatedConformancefMc_.swift -// CHECK-DUMP: extension Generic : P where Element: P {} +// CHECK-DUMP: extension Generic: P where Element: P { +// CHECK-DUMP: } func requiresP(_ value: (some P).Type) { value.requirement() diff --git a/test/Macros/macro_expand_conformances_multi.swift b/test/Macros/macro_expand_conformances_multi.swift index 5d4f3040a5116..aea0699eadcaf 100644 --- a/test/Macros/macro_expand_conformances_multi.swift +++ b/test/Macros/macro_expand_conformances_multi.swift @@ -4,7 +4,7 @@ // RUN: %host-build-swift -swift-version 5 -emit-library -o %t/%target-library-name(MacroDefinition) -module-name=MacroDefinition %S/Inputs/syntax_macro_definitions.swift -g -no-toolchain-stdlib-rpath // Make sure we see the conformances from another file. -// RUN: %target-typecheck-verify-swift -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -swift-version 5 -primary-file %S/Inputs/macro_expand_conformances_other.swift -DDISABLE_TOP_LEVEL_CODE +// RUN: %target-typecheck-verify-swift -enable-experimental-feature ExtensionMacros -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -swift-version 5 -primary-file %S/Inputs/macro_expand_conformances_other.swift -DDISABLE_TOP_LEVEL_CODE @attached(conformance) macro Equatable() = #externalMacro(module: "MacroDefinition", type: "EquatableMacro") @@ -24,3 +24,10 @@ func requireHashable(_ value: some Hashable) { @Equatable struct S {} +protocol MyProtocol {} + +@attached(extension, conformances: MyProtocol) +macro ConformanceViaExtension() = #externalMacro(module: "MacroDefinition", type: "ConformanceViaExtensionMacro") + +@ConformanceViaExtension +class Parent {} diff --git a/test/Macros/macro_expand_extensions.swift b/test/Macros/macro_expand_extensions.swift index 46d3ec0a44dac..139341a244d81 100644 --- a/test/Macros/macro_expand_extensions.swift +++ b/test/Macros/macro_expand_extensions.swift @@ -11,7 +11,7 @@ // RUN: %target-run %t/main | %FileCheck %s @attached(extension, conformances: P, names: named(requirement)) -macro DelegatedConformance() = #externalMacro(module: "MacroDefinition", type: "DelegatedConformanceMacro") +macro DelegatedConformance() = #externalMacro(module: "MacroDefinition", type: "DelegatedConformanceViaExtensionMacro") protocol P { static func requirement() @@ -55,6 +55,51 @@ struct Outer { // CHECK: Wrapped.requirement requiresP(Outer.Nested.self) +@DelegatedConformance +struct AlreadyConforms: P { + static func requirement() {} +} + +// CHECK-DUMP-LABEL: @__swiftmacro_23macro_expand_extensions15AlreadyConforms20DelegatedConformancefMe_.swift +// CHECK-DUMP-NOT: extension AlreadyConforms + +@DelegatedConformance +struct AlreadyConformsInExtension {} + +extension AlreadyConformsInExtension: P { + static func requirement() {} +} + +// CHECK-DUMP-LABEL: @__swiftmacro_23macro_expand_extensions26AlreadyConformsInExtension20DelegatedConformancefMe_.swift +// CHECK-DUMP-NOT: extension AlreadyConformsInExtension + +class ConformsExplicitly: P { + static func requirement() {} +} + +@DelegatedConformance +class InheritsConformance: ConformsExplicitly {} + +// CHECK-DUMP-LABEL: @__swiftmacro_23macro_expand_extensions19InheritsConformance09DelegatedE0fMe_.swift +// CHECK-DUMP-NOT: extension InheritsConformance + +@DelegatedConformance +class ConformsViaMacro { +} + +// CHECK-DUMP-LABEL: @__swiftmacro_23macro_expand_extensions16ConformsViaMacro20DelegatedConformancefMe_.swift +// CHECK-DUMP: extension ConformsViaMacro: P where Element: P { +// CHECK-DUMP: static func requirement() { +// CHECK-DUMP: Element.requirement() +// CHECK-DUMP: } +// CHECK-DUMP: } + +@DelegatedConformance +class InheritsConformanceViaMacro: ConformsViaMacro {} + +// CHECK-DUMP-LABEL: @__swiftmacro_23macro_expand_extensions27InheritsConformanceViaMacro09DelegatedE0fMe_.swift +// CHECK-DUMP-NOT: extension InheritsConformanceViaMacro + #if TEST_DIAGNOSTICS func testLocal() { @DelegatedConformance diff --git a/test/Serialization/Inputs/def_macro_plugin.swift b/test/Serialization/Inputs/def_macro_plugin.swift index 3533982b077b7..c34f54d8e08a8 100644 --- a/test/Serialization/Inputs/def_macro_plugin.swift +++ b/test/Serialization/Inputs/def_macro_plugin.swift @@ -57,8 +57,13 @@ public struct SendableMacro: ExtensionMacro { of node: AttributeSyntax, attachedTo: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, + conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { + if (protocols.isEmpty) { + return [] + } + let sendableExtension: DeclSyntax = """ extension \(type.trimmed): Sendable {} diff --git a/test/SourceKit/Macros/macro_basic.swift b/test/SourceKit/Macros/macro_basic.swift index 043a8491ef20d..2306ca02dc3a6 100644 --- a/test/SourceKit/Macros/macro_basic.swift +++ b/test/SourceKit/Macros/macro_basic.swift @@ -263,7 +263,8 @@ macro anonymousTypes(_: () -> String) = #externalMacro(module: "MacroDefinition" //##-- Expansion on a conformance macro. // RUN: %sourcekitd-test -req=refactoring.expand.macro -pos=51:5 %s -- ${COMPILER_ARGS[@]} | %FileCheck -check-prefix=CONFORMANCE_EXPAND %s // CONFORMANCE_EXPAND: source.edit.kind.active: -// CONFORMANCE_EXPAND-NEXT: 52:14-52:14 (@__swiftmacro_9MacroUser2S48HashablefMc_.swift) "extension S4 : Hashable {}" +// CONFORMANCE_EXPAND-NEXT: 52:14-52:14 (@__swiftmacro_9MacroUser2S48HashablefMc_.swift) "extension S4: Hashable { +// CONFORMANCE_EXPAND-NEXT: }" // CONFORMANCE_EXPAND-NEXT: source.edit.kind.active: // CONFORMANCE_EXPAND-NEXT: 51:1-51:10 "" diff --git a/test/SourceKit/Macros/syntactic_expansion.swift.expected b/test/SourceKit/Macros/syntactic_expansion.swift.expected index 90a0ccf5b0f65..d2e71ec1c398c 100644 --- a/test/SourceKit/Macros/syntactic_expansion.swift.expected +++ b/test/SourceKit/Macros/syntactic_expansion.swift.expected @@ -6,7 +6,8 @@ source.edit.kind.active: Element.requirement() }" source.edit.kind.active: - 13:2-13:2 "extension Generic : P where Element: P {}" + 13:2-13:2 "extension Generic: P where Element: P { +}" source.edit.kind.active: 5:3-5:21 "" source.edit.kind.active: @@ -105,4 +106,5 @@ source.edit.kind.active: Element.requirement() }" source.edit.kind.active: - 13:2-13:2 "extension Generic : P where Element: P {}" + 13:2-13:2 "extension Generic: P where Element: P { +}"