Skip to content

Commit e0facd2

Browse files
committed
[Macros] Provide member macros with information about "missing" conformances
Provide member macros with similar information about conformances to what extension macros receive, allowing member macros to document which conformances they care about (e.g., Decodable) and then receiving the list of conformances that aren't already available for the type in question. For example, a macro such as @attached(member, conformances: Decodable, Encodable, names: named(init(from:), encode(to:))) macro Codable() = ... Expanded on a type that is not already Decodable/Encodable would be provided with Decodable and Encodable (via the new `missingConformancesTo:` argument to the macro implementation) when the type itself does not conform to those types. Member macros still cannot produce conformances, so this is likely to be used in conjunction with extension macros most of the time. The extension macro declares the conformance, and can also declare any members that shouldn't be part of the primary type definition---such as initializers that shouldn't suppress the memberwise initializer. On the other hand, the member macro will need to define any members that must be in the primary definition, such as required initializers, members that must be overridable by subclasses, and stored properties. Codable synthesis is an example that benefits from member macros with conformances, because for classes it wants to introduce a required initializer for decoding and an overridable encode operation, and these must be members of the nominal type itself. Specifically, the `Codable` macro above is likely to have two attached member roles: @attached(member, conformances: Decodable, Encodable, names: named(init(from:), encode(to:))) @attached(extension, conformances: Decodable, Encodable, names: named(init(from:), encode(to:))) macro Codable() = ... where the "extension" role is responsible for defining the conformance (always), and the "member" creates the appropriate members for classes (`init` vs. `required init`). The desire for this feature is tracked by rdar://112532829.
1 parent c3553a7 commit e0facd2

10 files changed

+135
-29
lines changed

include/swift/AST/Decl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8621,6 +8621,7 @@ class MacroDecl : public GenericContext, public ValueDecl {
86218621
/// be added if this macro does not contain an extension role.
86228622
void getIntroducedConformances(
86238623
NominalTypeDecl *attachedTo,
8624+
MacroRole role,
86248625
SmallVectorImpl<ProtocolDecl *> &conformances) const;
86258626

86268627
/// Returns a DeclName that represents arbitrary names.

include/swift/AST/TypeCheckRequests.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3337,10 +3337,10 @@ class ResolveMacroRequest
33373337
void noteCycleStep(DiagnosticEngine &diags) const;
33383338
};
33393339

3340-
/// Returns the resolved constraint types that an extension macro
3341-
/// adds conformances to.
3342-
class ResolveExtensionMacroConformances
3343-
: public SimpleRequest<ResolveExtensionMacroConformances,
3340+
/// Returns the resolved constraint types that a macro references conformances
3341+
/// to.
3342+
class ResolveMacroConformances
3343+
: public SimpleRequest<ResolveMacroConformances,
33443344
ArrayRef<Type>(const MacroRoleAttr *, const Decl *),
33453345
RequestFlags::Cached> {
33463346
public:

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ SWIFT_REQUEST(TypeChecker, ResolveImplicitMemberRequest,
360360
SWIFT_REQUEST(TypeChecker, ResolveMacroRequest,
361361
ConcreteDeclRef(UnresolvedMacroReference, const Decl *),
362362
Cached, NoLocationInfo)
363-
SWIFT_REQUEST(TypeChecker, ResolveExtensionMacroConformances,
363+
SWIFT_REQUEST(TypeChecker, ResolveMacroConformances,
364364
ArrayRef<Type>(const MacroRoleAttr *, const Decl *),
365365
Cached, NoLocationInfo)
366366
SWIFT_REQUEST(TypeChecker, ResolveTypeEraserTypeRequest,

lib/AST/Attr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1387,7 +1387,7 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
13871387
// Print conformances, if present.
13881388
auto conformances = evaluateOrDefault(
13891389
D->getASTContext().evaluator,
1390-
ResolveExtensionMacroConformances{Attr, D},
1390+
ResolveMacroConformances{Attr, D},
13911391
{});
13921392
if (!conformances.empty()) {
13931393
Printer << ", conformances: ";

lib/AST/ConformanceLookupTable.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,8 @@ void ConformanceLookupTable::addMacroGeneratedProtocols(
503503
MacroRole::Extension,
504504
[&](CustomAttr *attr, MacroDecl *macro) {
505505
SmallVector<ProtocolDecl *, 2> conformances;
506-
macro->getIntroducedConformances(nominal, conformances);
506+
macro->getIntroducedConformances(
507+
nominal, MacroRole::Extension, conformances);
507508

508509
for (auto *protocol : conformances) {
509510
addProtocol(protocol, attr->getLocation(), source);

lib/AST/Decl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10826,15 +10826,16 @@ void MacroDecl::getIntroducedNames(MacroRole role, ValueDecl *attachedTo,
1082610826

1082710827
void MacroDecl::getIntroducedConformances(
1082810828
NominalTypeDecl *attachedTo,
10829+
MacroRole role,
1082910830
SmallVectorImpl<ProtocolDecl *> &conformances) const {
10830-
auto *attr = getMacroRoleAttr(MacroRole::Extension);
10831+
auto *attr = getMacroRoleAttr(role);
1083110832
if (!attr)
1083210833
return;
1083310834

1083410835
auto &ctx = getASTContext();
1083510836
auto constraintTypes = evaluateOrDefault(
1083610837
ctx.evaluator,
10837-
ResolveExtensionMacroConformances{attr, this},
10838+
ResolveMacroConformances{attr, this},
1083810839
{});
1083910840

1084010841
for (auto constraint : constraintTypes) {

lib/Sema/TypeCheckAttr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7151,7 +7151,7 @@ void AttributeChecker::visitMacroRoleAttr(MacroRoleAttr *attr) {
71517151

71527152
(void)evaluateOrDefault(
71537153
Ctx.evaluator,
7154-
ResolveExtensionMacroConformances{attr, D},
7154+
ResolveMacroConformances{attr, D},
71557155
{});
71567156
}
71577157

lib/Sema/TypeCheckMacros.cpp

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,12 +1533,56 @@ swift::expandAttributes(CustomAttr *attr, MacroDecl *macro, Decl *member) {
15331533
return macroSourceFile->getBufferID();
15341534
}
15351535

1536+
// Collect the protocol conformances that the macro asked about but were
1537+
// not already present on the declaration.
1538+
static TinyPtrVector<ProtocolDecl *> getIntroducedConformances(
1539+
NominalTypeDecl *nominal, MacroRole role, MacroDecl *macro,
1540+
SmallVectorImpl<ProtocolDecl *> *potentialConformances = nullptr) {
1541+
SmallVector<ProtocolDecl *, 2> potentialConformancesBuffer;
1542+
if (!potentialConformances)
1543+
potentialConformances = &potentialConformancesBuffer;
1544+
macro->getIntroducedConformances(nominal, role, *potentialConformances);
1545+
1546+
TinyPtrVector<ProtocolDecl *> introducedConformances;
1547+
for (auto protocol : *potentialConformances) {
1548+
SmallVector<ProtocolConformance *, 2> existingConformances;
1549+
nominal->lookupConformance(protocol, existingConformances);
1550+
1551+
bool hasExistingConformance = llvm::any_of(
1552+
existingConformances,
1553+
[&](ProtocolConformance *conformance) {
1554+
return conformance->getSourceKind() !=
1555+
ConformanceEntryKind::PreMacroExpansion;
1556+
});
1557+
1558+
if (!hasExistingConformance) {
1559+
introducedConformances.push_back(protocol);
1560+
}
1561+
}
1562+
1563+
return introducedConformances;
1564+
}
1565+
15361566
llvm::Optional<unsigned> swift::expandMembers(CustomAttr *attr,
15371567
MacroDecl *macro, Decl *decl) {
1568+
auto nominal = dyn_cast<NominalTypeDecl>(decl);
1569+
if (!nominal) {
1570+
auto ext = dyn_cast<ExtensionDecl>(decl);
1571+
if (!ext)
1572+
return llvm::None;
1573+
1574+
nominal = ext->getExtendedNominal();
1575+
if (!nominal)
1576+
return llvm::None;
1577+
}
1578+
auto introducedConformances = getIntroducedConformances(
1579+
nominal, MacroRole::Member, macro);
1580+
15381581
// Evaluate the macro.
15391582
auto macroSourceFile =
15401583
::evaluateAttachedMacro(macro, decl, attr,
1541-
/*passParentContext=*/false, MacroRole::Member);
1584+
/*passParentContext=*/false, MacroRole::Member,
1585+
introducedConformances);
15421586
if (!macroSourceFile)
15431587
return llvm::None;
15441588

@@ -1611,22 +1655,9 @@ llvm::Optional<unsigned> swift::expandExtensions(CustomAttr *attr,
16111655
return llvm::None;
16121656
}
16131657

1614-
// Collect the protocol conformances that the macro can add. The
1615-
// macro should not add conformances that are already stated in
1616-
// the original source.
1617-
16181658
SmallVector<ProtocolDecl *, 2> potentialConformances;
1619-
macro->getIntroducedConformances(nominal, potentialConformances);
1620-
1621-
SmallVector<ProtocolDecl *, 2> introducedConformances;
1622-
for (auto protocol : potentialConformances) {
1623-
SmallVector<ProtocolConformance *, 2> existingConformances;
1624-
nominal->lookupConformance(protocol, existingConformances);
1625-
if (existingConformances.empty()) {
1626-
introducedConformances.push_back(protocol);
1627-
}
1628-
}
1629-
1659+
auto introducedConformances = getIntroducedConformances(
1660+
nominal, MacroRole::Extension, macro, &potentialConformances);
16301661
auto macroSourceFile = ::evaluateAttachedMacro(macro, nominal, attr,
16311662
/*passParentContext=*/false,
16321663
role, introducedConformances);
@@ -1815,9 +1846,9 @@ ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator,
18151846
}
18161847

18171848
ArrayRef<Type>
1818-
ResolveExtensionMacroConformances::evaluate(Evaluator &evaluator,
1819-
const MacroRoleAttr *attr,
1820-
const Decl *decl) const {
1849+
ResolveMacroConformances::evaluate(Evaluator &evaluator,
1850+
const MacroRoleAttr *attr,
1851+
const Decl *decl) const {
18211852
auto *dc = decl->getDeclContext();
18221853
auto &ctx = dc->getASTContext();
18231854

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2018,3 +2018,53 @@ public struct InitWithProjectedValueWrapperMacro: PeerMacro {
20182018
]
20192019
}
20202020
}
2021+
2022+
public struct RequiredDefaultInitMacro: ExtensionMacro {
2023+
public static func expansion(
2024+
of node: AttributeSyntax,
2025+
attachedTo decl: some DeclGroupSyntax,
2026+
providingExtensionsOf type: some TypeSyntaxProtocol,
2027+
conformingTo protocols: [TypeSyntax],
2028+
in context: some MacroExpansionContext
2029+
) throws -> [ExtensionDeclSyntax] {
2030+
if protocols.isEmpty {
2031+
return []
2032+
}
2033+
2034+
let decl: DeclSyntax =
2035+
"""
2036+
extension \(type.trimmed): DefaultInit {
2037+
}
2038+
2039+
"""
2040+
2041+
return [
2042+
decl.cast(ExtensionDeclSyntax.self)
2043+
]
2044+
}
2045+
}
2046+
2047+
extension RequiredDefaultInitMacro: MemberMacro {
2048+
public static func expansion(
2049+
of node: AttributeSyntax,
2050+
providingMembersOf declaration: some DeclGroupSyntax,
2051+
in context: some MacroExpansionContext
2052+
) throws -> [DeclSyntax] {
2053+
fatalError("old swift-syntax")
2054+
}
2055+
2056+
public static func expansion(
2057+
of node: AttributeSyntax,
2058+
providingMembersOf declaration: some DeclGroupSyntax,
2059+
implementingRequirementsFor protocols: [TypeSyntax],
2060+
in context: some MacroExpansionContext
2061+
) throws -> [DeclSyntax] {
2062+
let decl: DeclSyntax
2063+
if declaration.is(ClassDeclSyntax.self) && protocols.isEmpty {
2064+
decl = "required init() { }"
2065+
} else {
2066+
decl = "init() { }"
2067+
}
2068+
return [ decl ]
2069+
}
2070+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// REQUIRES: swift_swift_parser
2+
3+
// RUN: %empty-directory(%t)
4+
// 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
5+
6+
// 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
7+
protocol DefaultInit {
8+
init()
9+
}
10+
11+
@attached(extension, conformances: DefaultInit)
12+
@attached(member, conformances: DefaultInit, names: named(init()))
13+
macro DefaultInit() = #externalMacro(module: "MacroDefinition", type: "RequiredDefaultInitMacro")
14+
15+
@DefaultInit
16+
class C { }
17+
18+
@DefaultInit
19+
class D: C { }
20+
21+
@DefaultInit
22+
struct E { }

0 commit comments

Comments
 (0)