Skip to content

Commit 5084caa

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 363e437 commit 5084caa

10 files changed

+135
-37
lines changed

include/swift/AST/Decl.h

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

86248625
/// 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
@@ -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,

lib/AST/Attr.cpp

Lines changed: 1 addition & 1 deletion
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: ";

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
@@ -10838,15 +10838,16 @@ void MacroDecl::getIntroducedNames(MacroRole role, ValueDecl *attachedTo,
1083810838

1083910839
void MacroDecl::getIntroducedConformances(
1084010840
NominalTypeDecl *attachedTo,
10841+
MacroRole role,
1084110842
SmallVectorImpl<ProtocolDecl *> &conformances) const {
10842-
auto *attr = getMacroRoleAttr(MacroRole::Extension);
10843+
auto *attr = getMacroRoleAttr(role);
1084310844
if (!attr)
1084410845
return;
1084510846

1084610847
auto &ctx = getASTContext();
1084710848
auto constraintTypes = evaluateOrDefault(
1084810849
ctx.evaluator,
10849-
ResolveExtensionMacroConformances{attr, this},
10850+
ResolveMacroConformances{attr, this},
1085010851
{});
1085110852

1085210853
for (auto constraint : constraintTypes) {

lib/Sema/TypeCheckAttr.cpp

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

71797179
(void)evaluateOrDefault(
71807180
Ctx.evaluator,
7181-
ResolveExtensionMacroConformances{attr, D},
7181+
ResolveMacroConformances{attr, D},
71827182
{});
71837183
}
71847184

lib/Sema/TypeCheckMacros.cpp

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,12 +1500,56 @@ swift::expandAttributes(CustomAttr *attr, MacroDecl *macro, Decl *member) {
15001500
return macroSourceFile->getBufferID();
15011501
}
15021502

1503+
// Collect the protocol conformances that the macro asked about but were
1504+
// not already present on the declaration.
1505+
static TinyPtrVector<ProtocolDecl *> getIntroducedConformances(
1506+
NominalTypeDecl *nominal, MacroRole role, MacroDecl *macro,
1507+
SmallVectorImpl<ProtocolDecl *> *potentialConformances = nullptr) {
1508+
SmallVector<ProtocolDecl *, 2> potentialConformancesBuffer;
1509+
if (!potentialConformances)
1510+
potentialConformances = &potentialConformancesBuffer;
1511+
macro->getIntroducedConformances(nominal, role, *potentialConformances);
1512+
1513+
TinyPtrVector<ProtocolDecl *> introducedConformances;
1514+
for (auto protocol : *potentialConformances) {
1515+
SmallVector<ProtocolConformance *, 2> existingConformances;
1516+
nominal->lookupConformance(protocol, existingConformances);
1517+
1518+
bool hasExistingConformance = llvm::any_of(
1519+
existingConformances,
1520+
[&](ProtocolConformance *conformance) {
1521+
return conformance->getSourceKind() !=
1522+
ConformanceEntryKind::PreMacroExpansion;
1523+
});
1524+
1525+
if (!hasExistingConformance) {
1526+
introducedConformances.push_back(protocol);
1527+
}
1528+
}
1529+
1530+
return introducedConformances;
1531+
}
1532+
15031533
llvm::Optional<unsigned> swift::expandMembers(CustomAttr *attr,
15041534
MacroDecl *macro, Decl *decl) {
1535+
auto nominal = dyn_cast<NominalTypeDecl>(decl);
1536+
if (!nominal) {
1537+
auto ext = dyn_cast<ExtensionDecl>(decl);
1538+
if (!ext)
1539+
return llvm::None;
1540+
1541+
nominal = ext->getExtendedNominal();
1542+
if (!nominal)
1543+
return llvm::None;
1544+
}
1545+
auto introducedConformances = getIntroducedConformances(
1546+
nominal, MacroRole::Member, macro);
1547+
15051548
// Evaluate the macro.
15061549
auto macroSourceFile =
15071550
::evaluateAttachedMacro(macro, decl, attr,
1508-
/*passParentContext=*/false, MacroRole::Member);
1551+
/*passParentContext=*/false, MacroRole::Member,
1552+
introducedConformances);
15091553
if (!macroSourceFile)
15101554
return llvm::None;
15111555

@@ -1577,30 +1621,9 @@ swift::expandExtensions(CustomAttr *attr, MacroDecl *macro,
15771621
return llvm::None;
15781622
}
15791623

1580-
// Collect the protocol conformances that the macro can add. The
1581-
// macro should not add conformances that are already stated in
1582-
// the original source.
1583-
15841624
SmallVector<ProtocolDecl *, 2> potentialConformances;
1585-
macro->getIntroducedConformances(nominal, potentialConformances);
1586-
1587-
SmallVector<ProtocolDecl *, 2> introducedConformances;
1588-
for (auto protocol : potentialConformances) {
1589-
SmallVector<ProtocolConformance *, 2> existingConformances;
1590-
nominal->lookupConformance(protocol, existingConformances);
1591-
1592-
bool hasExistingConformance = llvm::any_of(
1593-
existingConformances,
1594-
[&](ProtocolConformance *conformance) {
1595-
return conformance->getSourceKind() !=
1596-
ConformanceEntryKind::PreMacroExpansion;
1597-
});
1598-
1599-
if (!hasExistingConformance) {
1600-
introducedConformances.push_back(protocol);
1601-
}
1602-
}
1603-
1625+
auto introducedConformances = getIntroducedConformances(
1626+
nominal, MacroRole::Extension, macro, &potentialConformances);
16041627
auto macroSourceFile = ::evaluateAttachedMacro(macro, nominal, attr,
16051628
/*passParentContext=*/false,
16061629
role, introducedConformances);
@@ -1789,9 +1812,9 @@ ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator,
17891812
}
17901813

17911814
ArrayRef<Type>
1792-
ResolveExtensionMacroConformances::evaluate(Evaluator &evaluator,
1793-
const MacroRoleAttr *attr,
1794-
const Decl *decl) const {
1815+
ResolveMacroConformances::evaluate(Evaluator &evaluator,
1816+
const MacroRoleAttr *attr,
1817+
const Decl *decl) const {
17951818
auto *dc = decl->getDeclContext();
17961819
auto &ctx = dc->getASTContext();
17971820

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,3 +1871,53 @@ public struct NestedMagicLiteralMacro: ExpressionMacro {
18711871
"""
18721872
}
18731873
}
1874+
1875+
public struct RequiredDefaultInitMacro: ExtensionMacro {
1876+
public static func expansion(
1877+
of node: AttributeSyntax,
1878+
attachedTo decl: some DeclGroupSyntax,
1879+
providingExtensionsOf type: some TypeSyntaxProtocol,
1880+
conformingTo protocols: [TypeSyntax],
1881+
in context: some MacroExpansionContext
1882+
) throws -> [ExtensionDeclSyntax] {
1883+
if protocols.isEmpty {
1884+
return []
1885+
}
1886+
1887+
let decl: DeclSyntax =
1888+
"""
1889+
extension \(type.trimmed): DefaultInit {
1890+
}
1891+
1892+
"""
1893+
1894+
return [
1895+
decl.cast(ExtensionDeclSyntax.self)
1896+
]
1897+
}
1898+
}
1899+
1900+
extension RequiredDefaultInitMacro: MemberMacro {
1901+
public static func expansion(
1902+
of node: AttributeSyntax,
1903+
providingMembersOf declaration: some DeclGroupSyntax,
1904+
in context: some MacroExpansionContext
1905+
) throws -> [DeclSyntax] {
1906+
fatalError("old swift-syntax")
1907+
}
1908+
1909+
public static func expansion(
1910+
of node: AttributeSyntax,
1911+
providingMembersOf declaration: some DeclGroupSyntax,
1912+
preexistingConformances protocols: [TypeSyntax],
1913+
in context: some MacroExpansionContext
1914+
) throws -> [DeclSyntax] {
1915+
let decl: DeclSyntax
1916+
if declaration.is(ClassDeclSyntax.self) && protocols.isEmpty {
1917+
decl = "required init() { }"
1918+
} else {
1919+
decl = "init() { }"
1920+
}
1921+
return [ decl ]
1922+
}
1923+
}
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)