Skip to content

Commit 7b09944

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 5d6746d commit 7b09944

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
@@ -8643,6 +8643,7 @@ class MacroDecl : public GenericContext, public ValueDecl {
86438643
/// be added if this macro does not contain an extension role.
86448644
void getIntroducedConformances(
86458645
NominalTypeDecl *attachedTo,
8646+
MacroRole role,
86468647
SmallVectorImpl<ProtocolDecl *> &conformances) const;
86478648

86488649
/// 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
@@ -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);

lib/AST/Decl.cpp

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

1084310843
void MacroDecl::getIntroducedConformances(
1084410844
NominalTypeDecl *attachedTo,
10845+
MacroRole role,
1084510846
SmallVectorImpl<ProtocolDecl *> &conformances) const {
10846-
auto *attr = getMacroRoleAttr(MacroRole::Extension);
10847+
auto *attr = getMacroRoleAttr(role);
1084710848
if (!attr)
1084810849
return;
1084910850

1085010851
auto &ctx = getASTContext();
1085110852
auto constraintTypes = evaluateOrDefault(
1085210853
ctx.evaluator,
10853-
ResolveExtensionMacroConformances{attr, this},
10854+
ResolveMacroConformances{attr, this},
1085410855
{});
1085510856

1085610857
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 & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,12 +1523,56 @@ swift::expandAttributes(CustomAttr *attr, MacroDecl *macro, Decl *member) {
15231523
return macroSourceFile->getBufferID();
15241524
}
15251525

1526+
// Collect the protocol conformances that the macro asked about but were
1527+
// not already present on the declaration.
1528+
static TinyPtrVector<ProtocolDecl *> getIntroducedConformances(
1529+
NominalTypeDecl *nominal, MacroRole role, MacroDecl *macro,
1530+
SmallVectorImpl<ProtocolDecl *> *potentialConformances = nullptr) {
1531+
SmallVector<ProtocolDecl *, 2> potentialConformancesBuffer;
1532+
if (!potentialConformances)
1533+
potentialConformances = &potentialConformancesBuffer;
1534+
macro->getIntroducedConformances(nominal, role, *potentialConformances);
1535+
1536+
TinyPtrVector<ProtocolDecl *> introducedConformances;
1537+
for (auto protocol : *potentialConformances) {
1538+
SmallVector<ProtocolConformance *, 2> existingConformances;
1539+
nominal->lookupConformance(protocol, existingConformances);
1540+
1541+
bool hasExistingConformance = llvm::any_of(
1542+
existingConformances,
1543+
[&](ProtocolConformance *conformance) {
1544+
return conformance->getSourceKind() !=
1545+
ConformanceEntryKind::PreMacroExpansion;
1546+
});
1547+
1548+
if (!hasExistingConformance) {
1549+
introducedConformances.push_back(protocol);
1550+
}
1551+
}
1552+
1553+
return introducedConformances;
1554+
}
1555+
15261556
llvm::Optional<unsigned> swift::expandMembers(CustomAttr *attr,
15271557
MacroDecl *macro, Decl *decl) {
1558+
auto nominal = dyn_cast<NominalTypeDecl>(decl);
1559+
if (!nominal) {
1560+
auto ext = dyn_cast<ExtensionDecl>(decl);
1561+
if (!ext)
1562+
return llvm::None;
1563+
1564+
nominal = ext->getExtendedNominal();
1565+
if (!nominal)
1566+
return llvm::None;
1567+
}
1568+
auto introducedConformances = getIntroducedConformances(
1569+
nominal, MacroRole::Member, macro);
1570+
15281571
// Evaluate the macro.
15291572
auto macroSourceFile =
15301573
::evaluateAttachedMacro(macro, decl, attr,
1531-
/*passParentContext=*/false, MacroRole::Member);
1574+
/*passParentContext=*/false, MacroRole::Member,
1575+
introducedConformances);
15321576
if (!macroSourceFile)
15331577
return llvm::None;
15341578

@@ -1600,22 +1644,9 @@ swift::expandExtensions(CustomAttr *attr, MacroDecl *macro,
16001644
return llvm::None;
16011645
}
16021646

1603-
// Collect the protocol conformances that the macro can add. The
1604-
// macro should not add conformances that are already stated in
1605-
// the original source.
1606-
16071647
SmallVector<ProtocolDecl *, 2> potentialConformances;
1608-
macro->getIntroducedConformances(nominal, potentialConformances);
1609-
1610-
SmallVector<ProtocolDecl *, 2> introducedConformances;
1611-
for (auto protocol : potentialConformances) {
1612-
SmallVector<ProtocolConformance *, 2> existingConformances;
1613-
nominal->lookupConformance(protocol, existingConformances);
1614-
if (existingConformances.empty()) {
1615-
introducedConformances.push_back(protocol);
1616-
}
1617-
}
1618-
1648+
auto introducedConformances = getIntroducedConformances(
1649+
nominal, MacroRole::Extension, macro, &potentialConformances);
16191650
auto macroSourceFile = ::evaluateAttachedMacro(macro, nominal, attr,
16201651
/*passParentContext=*/false,
16211652
role, introducedConformances);
@@ -1804,9 +1835,9 @@ ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator,
18041835
}
18051836

18061837
ArrayRef<Type>
1807-
ResolveExtensionMacroConformances::evaluate(Evaluator &evaluator,
1808-
const MacroRoleAttr *attr,
1809-
const Decl *decl) const {
1838+
ResolveMacroConformances::evaluate(Evaluator &evaluator,
1839+
const MacroRoleAttr *attr,
1840+
const Decl *decl) const {
18101841
auto *dc = decl->getDeclContext();
18111842
auto &ctx = dc->getASTContext();
18121843

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,3 +1915,53 @@ public struct NestedMagicLiteralMacro: ExpressionMacro {
19151915
"""
19161916
}
19171917
}
1918+
1919+
public struct RequiredDefaultInitMacro: ExtensionMacro {
1920+
public static func expansion(
1921+
of node: AttributeSyntax,
1922+
attachedTo decl: some DeclGroupSyntax,
1923+
providingExtensionsOf type: some TypeSyntaxProtocol,
1924+
conformingTo protocols: [TypeSyntax],
1925+
in context: some MacroExpansionContext
1926+
) throws -> [ExtensionDeclSyntax] {
1927+
if protocols.isEmpty {
1928+
return []
1929+
}
1930+
1931+
let decl: DeclSyntax =
1932+
"""
1933+
extension \(type.trimmed): DefaultInit {
1934+
}
1935+
1936+
"""
1937+
1938+
return [
1939+
decl.cast(ExtensionDeclSyntax.self)
1940+
]
1941+
}
1942+
}
1943+
1944+
extension RequiredDefaultInitMacro: MemberMacro {
1945+
public static func expansion(
1946+
of node: AttributeSyntax,
1947+
providingMembersOf declaration: some DeclGroupSyntax,
1948+
in context: some MacroExpansionContext
1949+
) throws -> [DeclSyntax] {
1950+
fatalError("old swift-syntax")
1951+
}
1952+
1953+
public static func expansion(
1954+
of node: AttributeSyntax,
1955+
providingMembersOf declaration: some DeclGroupSyntax,
1956+
conformingTo protocols: [TypeSyntax],
1957+
in context: some MacroExpansionContext
1958+
) throws -> [DeclSyntax] {
1959+
let decl: DeclSyntax
1960+
if declaration.is(ClassDeclSyntax.self) && protocols.isEmpty {
1961+
decl = "required init() { }"
1962+
} else {
1963+
decl = "init() { }"
1964+
}
1965+
return [ decl ]
1966+
}
1967+
}
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)