Skip to content

Commit d276006

Browse files
committed
[Macros] Eliminate ordering dependency that suppressed conformingTo protocols
When a macro that has both 'member' and 'extension' roles is on a type, and both list conforming protocols, the order in which those roles were evaluated in the compiler could change the set of protocols passed to the macro expansion function (via `conformingTo:`). Specifically, if the extension macro was expanded first, the member macro would see the extension providing the conformance to one of its protocols, and not pass down that protocol to the member macro's `conformingTo:`. Ensure that we account for already-expanded extension macros that define conformances. Fixes rdar://137080876.
1 parent 80278a4 commit d276006

File tree

3 files changed

+102
-2
lines changed

3 files changed

+102
-2
lines changed

lib/Sema/TypeCheckMacros.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,8 +1788,20 @@ static TinyPtrVector<ProtocolDecl *> getIntroducedConformances(
17881788
bool hasExistingConformance = llvm::any_of(
17891789
existingConformances,
17901790
[&](ProtocolConformance *conformance) {
1791-
return conformance->getSourceKind() !=
1792-
ConformanceEntryKind::PreMacroExpansion;
1791+
// The conformance is coming from a macro expansion, so ignore it.
1792+
if (conformance->getSourceKind() ==
1793+
ConformanceEntryKind::PreMacroExpansion)
1794+
return false;
1795+
1796+
// Check whether the conformance comes from an extension defined by
1797+
// a macro.
1798+
if (auto conformingExt =
1799+
dyn_cast<ExtensionDecl>(conformance->getDeclContext())) {
1800+
if (conformingExt->isInMacroExpansionInContext())
1801+
return false;
1802+
}
1803+
1804+
return true;
17931805
});
17941806

17951807
if (!hasExistingConformance) {

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1808,6 +1808,46 @@ public struct AddAllConformancesMacro: ExtensionMacro {
18081808
}
18091809
}
18101810

1811+
public struct ListConformancesMacro { }
1812+
1813+
extension ListConformancesMacro: ExtensionMacro {
1814+
public static func expansion(
1815+
of node: AttributeSyntax,
1816+
attachedTo decl: some DeclGroupSyntax,
1817+
providingExtensionsOf type: some TypeSyntaxProtocol,
1818+
conformingTo protocols: [TypeSyntax],
1819+
in context: some MacroExpansionContext
1820+
) throws -> [ExtensionDeclSyntax] {
1821+
protocols.map { proto in
1822+
let decl: DeclSyntax =
1823+
"""
1824+
extension \(type): \(proto) {}
1825+
"""
1826+
return decl.cast(ExtensionDeclSyntax.self)
1827+
}
1828+
}
1829+
}
1830+
1831+
extension ListConformancesMacro: MemberMacro {
1832+
public static func expansion(
1833+
of node: AttributeSyntax,
1834+
providingMembersOf declaration: some DeclGroupSyntax,
1835+
conformingTo protocols: [TypeSyntax],
1836+
in context: some MacroExpansionContext
1837+
) throws -> [DeclSyntax] {
1838+
let typeName = declaration.asProtocol(NamedDeclSyntax.self)!.name.text
1839+
1840+
let protocolNames: [ExprSyntax] = protocols.map { "\(literal: $0.trimmedDescription)" }
1841+
let protocolsArray: ExprSyntax =
1842+
"[ \(raw: protocolNames.map { $0.description }.joined(separator: ", ")) ]"
1843+
let unknownDecl: DeclSyntax =
1844+
"""
1845+
@_nonoverride static func conformances() -> [String: [String]] { [ \(literal: typeName): \(protocolsArray) ] }
1846+
"""
1847+
return [unknownDecl]
1848+
}
1849+
}
1850+
18111851
public struct AlwaysAddCodable: ExtensionMacro {
18121852
public static func expansion(
18131853
of node: AttributeSyntax,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// REQUIRES: swift_swift_parser, executable_test
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+
// Check for errors first
7+
// RUN: %target-swift-frontend -swift-version 5 -typecheck -load-plugin-library %t/%target-library-name(MacroDefinition) %s -I %t -disable-availability-checking
8+
9+
// RUN: %target-swift-frontend -swift-version 5 -typecheck -load-plugin-library %t/%target-library-name(MacroDefinition) %s -I %t -disable-availability-checking -dump-macro-expansions > %t/expansions-dump.txt 2>&1
10+
// RUN: %FileCheck -check-prefix=CHECK-DUMP %s < %t/expansions-dump.txt
11+
12+
13+
protocol P1 {}
14+
protocol P2 {}
15+
16+
@attached(extension, conformances: P1, P2)
17+
@attached(member, conformances: P1, P2, names: named(conformances))
18+
macro ListConformances() = #externalMacro(module: "MacroDefinition", type: "ListConformancesMacro")
19+
20+
21+
// CHECK-DUMP: [ "Root": [ "P1", "P2" ] ]
22+
// CHECK-DUMP: extension Root: P1
23+
// CHECK-DUMP: extension Root: P2
24+
@ListConformances
25+
class Root {
26+
// CHECK-DUMP: extension OtherRoot: P1
27+
// CHECK-DUMP: extension OtherRoot: P2
28+
var other: OtherRoot?
29+
}
30+
31+
// CHECK-DUMP: [ "P1Root": [ "P2" ] ]
32+
// CHECK-DUMP-NOT: extension P1Root: P1
33+
// CHECK-DUMP: extension P1Root: P2
34+
@ListConformances
35+
class P1Root: P1 { }
36+
37+
// CHECK-DUMP: [ "OtherRoot": [ "P1", "P2" ] ]
38+
@ListConformances
39+
class OtherRoot {
40+
// CHECK-DUMP-NOT: extension OtherP1Root: P1
41+
// CHECK-DUMP: extension OtherP1Root: P2
42+
var other: OtherP1Root?
43+
}
44+
45+
// CHECK-DUMP: [ "OtherP1Root": [ "P2" ] ]
46+
@ListConformances
47+
class OtherP1Root: P1 { }
48+

0 commit comments

Comments
 (0)