diff --git a/CHANGELOG.md b/CHANGELOG.md index d407b27563c68..2c1fc06f6783f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ _**Note:** This is in reverse chronological order, so newer entries are added to the top._ +## Swift 5.9.2 + +* [SE-0407][]: + + Member macros can specify a list of protocols via the `conformances` argument to the macro role. The macro implementation will be provided with those protocols that are listed but have not already been implemented by the type to which the member macro is attached, in the same manner as extension macros. + + ```swift + @attached(member, conformances: Decodable, Encodable, names: named(init(from:), encode(to:))) +@attached(extension, conformances: Decodable, Encodable, names: named(init(from:), encode(to:))) +macro Codable() = #externalMacro(module: "MyMacros", type: "CodableMacro") + ``` + ## Swift 5.9 * [SE-0382][], [SE-0389][], [SE-0394][], [SE-0397][]: @@ -9830,6 +9842,7 @@ using the `.dynamicType` member to retrieve the type of an expression should mig [SE-0389]: https://github.com/apple/swift-evolution/blob/main/proposals/0389-attached-macros.md [SE-0394]: https://github.com/apple/swift-evolution/blob/main/proposals/0394-swiftpm-expression-macros.md [SE-0397]: https://github.com/apple/swift-evolution/blob/main/proposals/0397-freestanding-declaration-macros.md +[SE-0407]: https://github.com/apple/swift-evolution/blob/main/proposals/0407-member-macro-conformances.md [#64927]: [#42697]: [#42728]: diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 38b8a45cd9ae7..2665a203ad81d 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -8621,6 +8621,7 @@ class MacroDecl : public GenericContext, public ValueDecl { /// be added if this macro does not contain an extension role. void getIntroducedConformances( NominalTypeDecl *attachedTo, + MacroRole role, SmallVectorImpl &conformances) const; /// Returns a DeclName that represents arbitrary names. diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 00695f1b496de..740956b3d39c7 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -3337,10 +3337,10 @@ 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: diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index ea024e9f1d2d4..a6e7c755bc71f 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -360,7 +360,7 @@ SWIFT_REQUEST(TypeChecker, ResolveImplicitMemberRequest, SWIFT_REQUEST(TypeChecker, ResolveMacroRequest, ConcreteDeclRef(UnresolvedMacroReference, const Decl *), Cached, NoLocationInfo) -SWIFT_REQUEST(TypeChecker, ResolveExtensionMacroConformances, +SWIFT_REQUEST(TypeChecker, ResolveMacroConformances, ArrayRef(const MacroRoleAttr *, const Decl *), Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, ResolveTypeEraserTypeRequest, diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 2111ad11d7090..7a85ccb343103 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1387,7 +1387,7 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, // Print conformances, if present. auto conformances = evaluateOrDefault( D->getASTContext().evaluator, - ResolveExtensionMacroConformances{Attr, D}, + ResolveMacroConformances{Attr, D}, {}); if (!conformances.empty()) { Printer << ", conformances: "; diff --git a/lib/AST/ConformanceLookupTable.cpp b/lib/AST/ConformanceLookupTable.cpp index 85d2b523c1e34..74fb5d2c6df5b 100644 --- a/lib/AST/ConformanceLookupTable.cpp +++ b/lib/AST/ConformanceLookupTable.cpp @@ -503,7 +503,8 @@ void ConformanceLookupTable::addMacroGeneratedProtocols( MacroRole::Extension, [&](CustomAttr *attr, MacroDecl *macro) { SmallVector conformances; - macro->getIntroducedConformances(nominal, conformances); + macro->getIntroducedConformances( + nominal, MacroRole::Extension, conformances); for (auto *protocol : conformances) { addProtocol(protocol, attr->getLocation(), source); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 7b0ca4f344028..6388f154b2893 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -10826,15 +10826,16 @@ void MacroDecl::getIntroducedNames(MacroRole role, ValueDecl *attachedTo, void MacroDecl::getIntroducedConformances( NominalTypeDecl *attachedTo, + MacroRole role, SmallVectorImpl &conformances) const { - auto *attr = getMacroRoleAttr(MacroRole::Extension); + auto *attr = getMacroRoleAttr(role); if (!attr) return; auto &ctx = getASTContext(); auto constraintTypes = evaluateOrDefault( ctx.evaluator, - ResolveExtensionMacroConformances{attr, this}, + ResolveMacroConformances{attr, this}, {}); for (auto constraint : constraintTypes) { diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 4620488ff6599..c81fddb1f4c12 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -7151,7 +7151,7 @@ void AttributeChecker::visitMacroRoleAttr(MacroRoleAttr *attr) { (void)evaluateOrDefault( Ctx.evaluator, - ResolveExtensionMacroConformances{attr, D}, + ResolveMacroConformances{attr, D}, {}); } diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index 07eeebbc10baf..408c9c0b07ebf 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -1533,12 +1533,56 @@ swift::expandAttributes(CustomAttr *attr, MacroDecl *macro, Decl *member) { return macroSourceFile->getBufferID(); } +// Collect the protocol conformances that the macro asked about but were +// not already present on the declaration. +static TinyPtrVector getIntroducedConformances( + NominalTypeDecl *nominal, MacroRole role, MacroDecl *macro, + SmallVectorImpl *potentialConformances = nullptr) { + SmallVector potentialConformancesBuffer; + if (!potentialConformances) + potentialConformances = &potentialConformancesBuffer; + macro->getIntroducedConformances(nominal, role, *potentialConformances); + + TinyPtrVector 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); + } + } + + return introducedConformances; +} + llvm::Optional swift::expandMembers(CustomAttr *attr, MacroDecl *macro, Decl *decl) { + auto nominal = dyn_cast(decl); + if (!nominal) { + auto ext = dyn_cast(decl); + if (!ext) + return llvm::None; + + nominal = ext->getExtendedNominal(); + if (!nominal) + return llvm::None; + } + auto introducedConformances = getIntroducedConformances( + nominal, MacroRole::Member, macro); + // Evaluate the macro. auto macroSourceFile = ::evaluateAttachedMacro(macro, decl, attr, - /*passParentContext=*/false, MacroRole::Member); + /*passParentContext=*/false, MacroRole::Member, + introducedConformances); if (!macroSourceFile) return llvm::None; @@ -1611,22 +1655,9 @@ llvm::Optional swift::expandExtensions(CustomAttr *attr, 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); - if (existingConformances.empty()) { - introducedConformances.push_back(protocol); - } - } - + auto introducedConformances = getIntroducedConformances( + nominal, MacroRole::Extension, macro, &potentialConformances); auto macroSourceFile = ::evaluateAttachedMacro(macro, nominal, attr, /*passParentContext=*/false, role, introducedConformances); @@ -1815,9 +1846,9 @@ ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator, } ArrayRef -ResolveExtensionMacroConformances::evaluate(Evaluator &evaluator, - const MacroRoleAttr *attr, - const Decl *decl) const { +ResolveMacroConformances::evaluate(Evaluator &evaluator, + const MacroRoleAttr *attr, + const Decl *decl) const { auto *dc = decl->getDeclContext(); auto &ctx = dc->getASTContext(); diff --git a/test/Macros/Inputs/syntax_macro_definitions.swift b/test/Macros/Inputs/syntax_macro_definitions.swift index 9fb6bd553669c..c47f37923eeb8 100644 --- a/test/Macros/Inputs/syntax_macro_definitions.swift +++ b/test/Macros/Inputs/syntax_macro_definitions.swift @@ -2018,3 +2018,53 @@ public struct InitWithProjectedValueWrapperMacro: PeerMacro { ] } } + +public struct RequiredDefaultInitMacro: 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 \(type.trimmed): DefaultInit { + } + + """ + + return [ + decl.cast(ExtensionDeclSyntax.self) + ] + } +} + +extension RequiredDefaultInitMacro: MemberMacro { + public static func expansion( + of node: AttributeSyntax, + providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + fatalError("old swift-syntax") + } + + public static func expansion( + of node: AttributeSyntax, + providingMembersOf declaration: some DeclGroupSyntax, + conformingTo protocols: [TypeSyntax], + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + let decl: DeclSyntax + if declaration.is(ClassDeclSyntax.self) && protocols.isEmpty { + decl = "required init() { }" + } else { + decl = "init() { }" + } + return [ decl ] + } +} diff --git a/test/Macros/macro_expand_member_with_conformances.swift b/test/Macros/macro_expand_member_with_conformances.swift new file mode 100644 index 0000000000000..a02e834464e79 --- /dev/null +++ b/test/Macros/macro_expand_member_with_conformances.swift @@ -0,0 +1,22 @@ +// REQUIRES: swift_swift_parser + +// RUN: %empty-directory(%t) +// 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 + +// RUN: %target-typecheck-verify-swift -enable-experimental-feature ExtensionMacros -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -DTEST_DIAGNOSTICS -swift-version 5 -I %t +protocol DefaultInit { + init() +} + +@attached(extension, conformances: DefaultInit) +@attached(member, conformances: DefaultInit, names: named(init())) +macro DefaultInit() = #externalMacro(module: "MacroDefinition", type: "RequiredDefaultInitMacro") + +@DefaultInit +class C { } + +@DefaultInit +class D: C { } + +@DefaultInit +struct E { }