Skip to content

Commit 596da31

Browse files
committed
[SE-0407] 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`). Tracked by rdar://112532829.
1 parent 74e22dc commit 596da31

11 files changed

+148
-29
lines changed

Diff for: CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
> **Note**\
44
> This is in reverse chronological order, so newer entries are added to the top.
55

6+
## Swift 5.9.2
7+
8+
* [SE-0407][]:
9+
10+
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.
11+
12+
```swift
13+
@attached(member, conformances: Decodable, Encodable, names: named(init(from:), encode(to:)))
14+
@attached(extension, conformances: Decodable, Encodable, names: named(init(from:), encode(to:)))
15+
macro Codable() = #externalMacro(module: "MyMacros", type: "CodableMacro")
16+
```
17+
618
## Swift 5.9
719

820
* [SE-0382][], [SE-0389][], [SE-0394][], [SE-0397][]:
@@ -9833,6 +9845,7 @@ using the `.dynamicType` member to retrieve the type of an expression should mig
98339845
[SE-0389]: https://github.com/apple/swift-evolution/blob/main/proposals/0389-attached-macros.md
98349846
[SE-0394]: https://github.com/apple/swift-evolution/blob/main/proposals/0394-swiftpm-expression-macros.md
98359847
[SE-0397]: https://github.com/apple/swift-evolution/blob/main/proposals/0397-freestanding-declaration-macros.md
9848+
[SE-0407]: https://github.com/apple/swift-evolution/blob/main/proposals/0407-member-macro-conformances.md
98369849
[#64927]: <https://github.com/apple/swift/issues/64927>
98379850
[#42697]: <https://github.com/apple/swift/issues/42697>
98389851
[#42728]: <https://github.com/apple/swift/issues/42728>

Diff for: include/swift/AST/Decl.h

+1
Original file line numberDiff line numberDiff line change
@@ -8701,6 +8701,7 @@ class MacroDecl : public GenericContext, public ValueDecl {
87018701
/// be added if this macro does not contain an extension role.
87028702
void getIntroducedConformances(
87038703
NominalTypeDecl *attachedTo,
8704+
MacroRole role,
87048705
SmallVectorImpl<ProtocolDecl *> &conformances) const;
87058706

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

Diff for: include/swift/AST/TypeCheckRequests.h

+4-4
Original file line numberDiff line numberDiff line change
@@ -3338,10 +3338,10 @@ class ResolveMacroRequest
33383338
void noteCycleStep(DiagnosticEngine &diags) const;
33393339
};
33403340

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

Diff for: include/swift/AST/TypeCheckerTypeIDZone.def

+1-1
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ SWIFT_REQUEST(TypeChecker, ResolveImplicitMemberRequest,
363363
SWIFT_REQUEST(TypeChecker, ResolveMacroRequest,
364364
ConcreteDeclRef(UnresolvedMacroReference, const Decl *),
365365
Cached, NoLocationInfo)
366-
SWIFT_REQUEST(TypeChecker, ResolveExtensionMacroConformances,
366+
SWIFT_REQUEST(TypeChecker, ResolveMacroConformances,
367367
ArrayRef<Type>(const MacroRoleAttr *, const Decl *),
368368
Cached, NoLocationInfo)
369369
SWIFT_REQUEST(TypeChecker, ResolveTypeEraserTypeRequest,

Diff for: lib/AST/Attr.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1393,7 +1393,7 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
13931393
// Print conformances, if present.
13941394
auto conformances = evaluateOrDefault(
13951395
D->getASTContext().evaluator,
1396-
ResolveExtensionMacroConformances{Attr, D},
1396+
ResolveMacroConformances{Attr, D},
13971397
{});
13981398
if (!conformances.empty()) {
13991399
Printer << ", conformances: ";

Diff for: lib/AST/ConformanceLookupTable.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,8 @@ void ConformanceLookupTable::addMacroGeneratedProtocols(
506506
MacroRole::Extension,
507507
[&](CustomAttr *attr, MacroDecl *macro) {
508508
SmallVector<ProtocolDecl *, 2> conformances;
509-
macro->getIntroducedConformances(nominal, conformances);
509+
macro->getIntroducedConformances(
510+
nominal, MacroRole::Extension, conformances);
510511

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

Diff for: lib/AST/Decl.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -10886,15 +10886,16 @@ void MacroDecl::getIntroducedNames(MacroRole role, ValueDecl *attachedTo,
1088610886

1088710887
void MacroDecl::getIntroducedConformances(
1088810888
NominalTypeDecl *attachedTo,
10889+
MacroRole role,
1088910890
SmallVectorImpl<ProtocolDecl *> &conformances) const {
10890-
auto *attr = getMacroRoleAttr(MacroRole::Extension);
10891+
auto *attr = getMacroRoleAttr(role);
1089110892
if (!attr)
1089210893
return;
1089310894

1089410895
auto &ctx = getASTContext();
1089510896
auto constraintTypes = evaluateOrDefault(
1089610897
ctx.evaluator,
10897-
ResolveExtensionMacroConformances{attr, this},
10898+
ResolveMacroConformances{attr, this},
1089810899
{});
1089910900

1090010901
for (auto constraint : constraintTypes) {

Diff for: lib/Sema/TypeCheckAttr.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -6948,7 +6948,7 @@ void AttributeChecker::visitMacroRoleAttr(MacroRoleAttr *attr) {
69486948

69496949
(void)evaluateOrDefault(
69506950
Ctx.evaluator,
6951-
ResolveExtensionMacroConformances{attr, D},
6951+
ResolveMacroConformances{attr, D},
69526952
{});
69536953
}
69546954

Diff for: lib/Sema/TypeCheckMacros.cpp

+50-19
Original file line numberDiff line numberDiff line change
@@ -1548,12 +1548,56 @@ swift::expandAttributes(CustomAttr *attr, MacroDecl *macro, Decl *member) {
15481548
return macroSourceFile->getBufferID();
15491549
}
15501550

1551+
// Collect the protocol conformances that the macro asked about but were
1552+
// not already present on the declaration.
1553+
static TinyPtrVector<ProtocolDecl *> getIntroducedConformances(
1554+
NominalTypeDecl *nominal, MacroRole role, MacroDecl *macro,
1555+
SmallVectorImpl<ProtocolDecl *> *potentialConformances = nullptr) {
1556+
SmallVector<ProtocolDecl *, 2> potentialConformancesBuffer;
1557+
if (!potentialConformances)
1558+
potentialConformances = &potentialConformancesBuffer;
1559+
macro->getIntroducedConformances(nominal, role, *potentialConformances);
1560+
1561+
TinyPtrVector<ProtocolDecl *> introducedConformances;
1562+
for (auto protocol : *potentialConformances) {
1563+
SmallVector<ProtocolConformance *, 2> existingConformances;
1564+
nominal->lookupConformance(protocol, existingConformances);
1565+
1566+
bool hasExistingConformance = llvm::any_of(
1567+
existingConformances,
1568+
[&](ProtocolConformance *conformance) {
1569+
return conformance->getSourceKind() !=
1570+
ConformanceEntryKind::PreMacroExpansion;
1571+
});
1572+
1573+
if (!hasExistingConformance) {
1574+
introducedConformances.push_back(protocol);
1575+
}
1576+
}
1577+
1578+
return introducedConformances;
1579+
}
1580+
15511581
llvm::Optional<unsigned> swift::expandMembers(CustomAttr *attr,
15521582
MacroDecl *macro, Decl *decl) {
1583+
auto nominal = dyn_cast<NominalTypeDecl>(decl);
1584+
if (!nominal) {
1585+
auto ext = dyn_cast<ExtensionDecl>(decl);
1586+
if (!ext)
1587+
return llvm::None;
1588+
1589+
nominal = ext->getExtendedNominal();
1590+
if (!nominal)
1591+
return llvm::None;
1592+
}
1593+
auto introducedConformances = getIntroducedConformances(
1594+
nominal, MacroRole::Member, macro);
1595+
15531596
// Evaluate the macro.
15541597
auto macroSourceFile =
15551598
::evaluateAttachedMacro(macro, decl, attr,
1556-
/*passParentContext=*/false, MacroRole::Member);
1599+
/*passParentContext=*/false, MacroRole::Member,
1600+
introducedConformances);
15571601
if (!macroSourceFile)
15581602
return llvm::None;
15591603

@@ -1625,22 +1669,9 @@ swift::expandExtensions(CustomAttr *attr, MacroDecl *macro,
16251669
return llvm::None;
16261670
}
16271671

1628-
// Collect the protocol conformances that the macro can add. The
1629-
// macro should not add conformances that are already stated in
1630-
// the original source.
1631-
16321672
SmallVector<ProtocolDecl *, 2> potentialConformances;
1633-
macro->getIntroducedConformances(nominal, potentialConformances);
1634-
1635-
SmallVector<ProtocolDecl *, 2> introducedConformances;
1636-
for (auto protocol : potentialConformances) {
1637-
SmallVector<ProtocolConformance *, 2> existingConformances;
1638-
nominal->lookupConformance(protocol, existingConformances);
1639-
if (existingConformances.empty()) {
1640-
introducedConformances.push_back(protocol);
1641-
}
1642-
}
1643-
1673+
auto introducedConformances = getIntroducedConformances(
1674+
nominal, MacroRole::Extension, macro, &potentialConformances);
16441675
auto macroSourceFile = ::evaluateAttachedMacro(macro, nominal, attr,
16451676
/*passParentContext=*/false,
16461677
role, introducedConformances);
@@ -1830,9 +1861,9 @@ ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator,
18301861
}
18311862

18321863
ArrayRef<Type>
1833-
ResolveExtensionMacroConformances::evaluate(Evaluator &evaluator,
1834-
const MacroRoleAttr *attr,
1835-
const Decl *decl) const {
1864+
ResolveMacroConformances::evaluate(Evaluator &evaluator,
1865+
const MacroRoleAttr *attr,
1866+
const Decl *decl) const {
18361867
auto *dc = decl->getDeclContext();
18371868
auto &ctx = dc->getASTContext();
18381869

Diff for: test/Macros/Inputs/syntax_macro_definitions.swift

+50
Original file line numberDiff line numberDiff line change
@@ -2004,3 +2004,53 @@ public struct InitWithProjectedValueWrapperMacro: PeerMacro {
20042004
]
20052005
}
20062006
}
2007+
2008+
public struct RequiredDefaultInitMacro: ExtensionMacro {
2009+
public static func expansion(
2010+
of node: AttributeSyntax,
2011+
attachedTo decl: some DeclGroupSyntax,
2012+
providingExtensionsOf type: some TypeSyntaxProtocol,
2013+
conformingTo protocols: [TypeSyntax],
2014+
in context: some MacroExpansionContext
2015+
) throws -> [ExtensionDeclSyntax] {
2016+
if protocols.isEmpty {
2017+
return []
2018+
}
2019+
2020+
let decl: DeclSyntax =
2021+
"""
2022+
extension \(type.trimmed): DefaultInit {
2023+
}
2024+
2025+
"""
2026+
2027+
return [
2028+
decl.cast(ExtensionDeclSyntax.self)
2029+
]
2030+
}
2031+
}
2032+
2033+
extension RequiredDefaultInitMacro: MemberMacro {
2034+
public static func expansion(
2035+
of node: AttributeSyntax,
2036+
providingMembersOf declaration: some DeclGroupSyntax,
2037+
in context: some MacroExpansionContext
2038+
) throws -> [DeclSyntax] {
2039+
fatalError("old swift-syntax")
2040+
}
2041+
2042+
public static func expansion(
2043+
of node: AttributeSyntax,
2044+
providingMembersOf declaration: some DeclGroupSyntax,
2045+
conformingTo protocols: [TypeSyntax],
2046+
in context: some MacroExpansionContext
2047+
) throws -> [DeclSyntax] {
2048+
let decl: DeclSyntax
2049+
if declaration.is(ClassDeclSyntax.self) && protocols.isEmpty {
2050+
decl = "required init() { }"
2051+
} else {
2052+
decl = "init() { }"
2053+
}
2054+
return [ decl ]
2055+
}
2056+
}
+22
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)