Skip to content

Commit 24e3fe0

Browse files
authored
Merge pull request #66509 from DougGregor/unambiguous-freestanding-macro-role
[Macros] Provide the freestanding macro role for expansion operations.
2 parents 160433b + c5ec389 commit 24e3fe0

13 files changed

+115
-11
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7146,6 +7146,8 @@ NOTE(macro_remove_result_type,none,
71467146
())
71477147
NOTE(macro_make_freestanding_expression,none,
71487148
"make this macro a freestanding expression macro", ())
7149+
ERROR(macro_multiple_freestanding_roles,none,
7150+
"macro can only have a single freestanding role", ())
71497151
ERROR(macro_expansion_missing_pound,none,
71507152
"expansion of macro %0 requires leading '#'", (DeclName))
71517153
ERROR(macro_expansion_missing_arguments,none,

lib/ASTGen/Sources/ASTGen/Macros.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ extension MacroRole {
6767
case 0x10: self = .member
6868
case 0x20: self = .peer
6969
case 0x40: self = .conformance
70+
case 0x80: self = .codeItem
71+
7072
default: fatalError("unknown macro role")
7173
}
7274
}
@@ -414,6 +416,7 @@ func expandFreestandingMacro(
414416
macroKind: UInt8,
415417
discriminatorText: UnsafePointer<UInt8>,
416418
discriminatorTextLength: Int,
419+
rawMacroRole: UInt8,
417420
sourceFilePtr: UnsafeRawPointer,
418421
sourceLocationPtr: UnsafePointer<UInt8>?,
419422
expandedSourcePointer: UnsafeMutablePointer<UnsafePointer<UInt8>?>,
@@ -446,18 +449,21 @@ func expandFreestandingMacro(
446449
)
447450
let discriminator = String(decoding: discriminatorBuffer, as: UTF8.self)
448451

452+
let macroRole = MacroRole(rawMacroRole: rawMacroRole)
449453
let expandedSource: String?
450454
switch MacroPluginKind(rawValue: macroKind)! {
451455
case .InProcess:
452456
expandedSource = expandFreestandingMacroInProcess(
453457
macroPtr: macroPtr,
458+
macroRole: macroRole,
454459
diagEnginePtr: diagEnginePtr,
455460
expansionSyntax: expansion,
456461
sourceFilePtr: sourceFilePtr,
457462
discriminator: discriminator)
458463
case .Executable:
459464
expandedSource = expandFreestandingMacroIPC(
460465
macroPtr: macroPtr,
466+
macroRole: macroRole,
461467
diagEnginePtr: diagEnginePtr,
462468
expansionSyntax: expansion,
463469
sourceFilePtr: sourceFilePtr,
@@ -485,6 +491,7 @@ func expandFreestandingMacro(
485491

486492
func expandFreestandingMacroIPC(
487493
macroPtr: UnsafeRawPointer,
494+
macroRole: MacroRole,
488495
diagEnginePtr: UnsafeMutablePointer<UInt8>,
489496
expansionSyntax: FreestandingMacroExpansionSyntax,
490497
sourceFilePtr: UnsafePointer<ExportedSourceFile>,
@@ -502,9 +509,21 @@ func expandFreestandingMacroIPC(
502509

503510
let macro = macroPtr.assumingMemoryBound(to: ExportedExecutableMacro.self).pointee
504511

512+
// Map the macro role.
513+
let pluginMacroRole: PluginMessage.MacroRole
514+
switch macroRole {
515+
case .accessor, .member, .memberAttribute, .peer, .conformance:
516+
preconditionFailure("unhandled macro role for freestanding macro")
517+
518+
case .expression: pluginMacroRole = .expression
519+
case .declaration: pluginMacroRole = .freeStandingDeclaration
520+
case .codeItem: pluginMacroRole = .codeItem
521+
}
522+
505523
// Send the message.
506524
let message = HostToPluginMessage.expandFreestandingMacro(
507525
macro: .init(moduleName: macro.moduleName, typeName: macro.typeName, name: macroName),
526+
macroRole: pluginMacroRole,
508527
discriminator: discriminator,
509528
syntax: PluginMessage.Syntax(syntax: Syntax(expansionSyntax), in: sourceFilePtr)!)
510529
do {
@@ -541,6 +560,7 @@ func expandFreestandingMacroIPC(
541560

542561
func expandFreestandingMacroInProcess(
543562
macroPtr: UnsafeRawPointer,
563+
macroRole: MacroRole,
544564
diagEnginePtr: UnsafeMutablePointer<UInt8>,
545565
expansionSyntax: FreestandingMacroExpansionSyntax,
546566
sourceFilePtr: UnsafePointer<ExportedSourceFile>,
@@ -580,6 +600,7 @@ func expandFreestandingMacroInProcess(
580600

581601
return SwiftSyntaxMacroExpansion.expandFreestandingMacro(
582602
definition: macro,
603+
macroRole: macroRole,
583604
node: node,
584605
in: context
585606
)

lib/ASTGen/Sources/ASTGen/PluginMessages.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internal enum HostToPluginMessage: Codable {
2020
/// Expand a '@freestanding' macro.
2121
case expandFreestandingMacro(
2222
macro: PluginMessage.MacroReference,
23+
macroRole: PluginMessage.MacroRole? = nil,
2324
discriminator: String,
2425
syntax: PluginMessage.Syntax
2526
)
@@ -91,6 +92,7 @@ internal enum PluginToHostMessage: Codable {
9192
case member
9293
case peer
9394
case conformance
95+
case codeItem
9496
}
9597

9698
struct SourceLocation: Codable {

lib/IDETool/SyntacticMacroExpansion.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ void SyntacticMacroExpansionInstance::expand(
412412
SourceFile *SF, const MacroExpansionSpecifier &expansion,
413413
SourceEditConsumer &consumer) {
414414

415-
// Find the expansion at 'expantion.offset'.
415+
// Find the expansion at 'expansion.offset'.
416416
MacroExpansionFinder expansionFinder(
417417
SourceMgr,
418418
SourceMgr.getLocForOffset(*SF->getBufferID(), expansion.offset));

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2017,6 +2017,17 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
20172017
llvm_unreachable("should always be type-checked already");
20182018
}
20192019

2020+
/// Determine the number of bits set.
2021+
static unsigned numBitsSet(uint64_t value) {
2022+
unsigned count = 0;
2023+
for (uint64_t i : range(0, 63)) {
2024+
if (value & (uint64_t(1) << i))
2025+
++count;
2026+
}
2027+
2028+
return count;
2029+
}
2030+
20202031
void visitMacroDecl(MacroDecl *MD) {
20212032
TypeChecker::checkDeclAttributes(MD);
20222033
checkAccessControl(MD);
@@ -2077,6 +2088,13 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
20772088
.fixItRemove(SourceRange(MD->arrowLoc, resultTypeRepr->getEndLoc()));
20782089
}
20792090
}
2091+
2092+
// A macro can only have a single freestanding macro role.
2093+
MacroRoles freestandingRolesInhabited =
2094+
MD->getMacroRoles() & getFreestandingMacroRoles();
2095+
if (numBitsSet(freestandingRolesInhabited.toRaw()) > 1) {
2096+
MD->diagnose(diag::macro_multiple_freestanding_roles);
2097+
}
20802098
}
20812099

20822100
void visitMacroExpansionDecl(MacroExpansionDecl *MED) {

lib/Sema/TypeCheckMacros.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ extern "C" ptrdiff_t swift_ASTGen_checkMacroDefinition(
6666

6767
extern "C" ptrdiff_t swift_ASTGen_expandFreestandingMacro(
6868
void *diagEngine, void *macro, uint8_t externalKind,
69-
const char *discriminator, ptrdiff_t discriminatorLength, void *sourceFile,
69+
const char *discriminator, ptrdiff_t discriminatorLength,
70+
uint8_t rawMacroRole, void *sourceFile,
7071
const void *sourceLocation, const char **evaluatedSource,
7172
ptrdiff_t *evaluatedSourceLength);
7273

@@ -901,6 +902,13 @@ evaluateFreestandingMacro(FreestandingMacroExpansion *expansion,
901902
#endif
902903
});
903904

905+
// Only one freestanding macro role is permitted, so look at the roles to
906+
// figure out which one to use.
907+
MacroRole macroRole =
908+
macroRoles.contains(MacroRole::Expression) ? MacroRole::Expression
909+
: macroRoles.contains(MacroRole::Declaration) ? MacroRole::Declaration
910+
: MacroRole::CodeItem;
911+
904912
auto macroDef = macro->getDefinition();
905913
switch (macroDef.kind) {
906914
case MacroDefinition::Kind::Undefined:
@@ -961,7 +969,8 @@ evaluateFreestandingMacro(FreestandingMacroExpansion *expansion,
961969
swift_ASTGen_expandFreestandingMacro(
962970
&ctx.Diags, externalDef->opaqueHandle,
963971
static_cast<uint32_t>(externalDef->kind), discriminator->data(),
964-
discriminator->size(), astGenSourceFile,
972+
discriminator->size(),
973+
static_cast<uint32_t>(macroRole), astGenSourceFile,
965974
expansion->getSourceRange().Start.getOpaquePointerValue(),
966975
&evaluatedSourceAddress, &evaluatedSourceLength);
967976
if (!evaluatedSourceAddress)

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,26 @@ public struct StringifyMacro: ExpressionMacro {
7373
}
7474
}
7575

76+
public struct ExprAndDeclMacro: ExpressionMacro, DeclarationMacro {
77+
public static func expansion(
78+
of macro: some FreestandingMacroExpansionSyntax,
79+
in context: some MacroExpansionContext
80+
) -> ExprSyntax {
81+
guard let argument = macro.argumentList.first?.expression else {
82+
fatalError("boom")
83+
}
84+
85+
return "(\(argument), \(StringLiteralExprSyntax(content: argument.description)))"
86+
}
87+
88+
public static func expansion(
89+
of macro: some FreestandingMacroExpansionSyntax,
90+
in context: some MacroExpansionContext
91+
) -> [DeclSyntax] {
92+
return []
93+
}
94+
}
95+
7696
public struct StringifyAndTryMacro: ExpressionMacro {
7797
public static func expansion(
7898
of macro: some FreestandingMacroExpansionSyntax,

test/Macros/macro_expand.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,3 +510,27 @@ func testHasEqualsSelf(
510510
_ = (zP == true)
511511
_ = (wP == true)
512512
}
513+
514+
// Macro whose implementation is both an expression and declaration macro.
515+
@freestanding(declaration)
516+
macro AsDeclMacro<T>(_ value: T) = #externalMacro(module: "MacroDefinition", type: "ExprAndDeclMacro")
517+
518+
@freestanding(expression)
519+
macro AsExprMacro<T>(_ value: T) -> (T, String) = #externalMacro(module: "MacroDefinition", type: "ExprAndDeclMacro")
520+
521+
func testExpressionAndDeclarationMacro() {
522+
#AsExprMacro(1 + 1) // expected-warning{{expression of type '(Int, String)' is unused}}
523+
struct Inner {
524+
#AsDeclMacro(1 + 1)
525+
}
526+
#AsDeclMacro(1 + 1)
527+
}
528+
529+
// Expression macro implementation with declaration macro role
530+
@freestanding(declaration) macro stringifyAsDeclMacro<T>(_ value: T) = #externalMacro(module: "MacroDefinition", type: "StringifyMacro")
531+
func testExpressionAsDeclarationMacro() {
532+
#if TEST_DIAGNOSTICS
533+
#stringifyAsDeclMacro(1+1)
534+
// expected-error@-1{{macro implementation type 'StringifyMacro' doesn't conform to required protocol 'DeclarationMacro' (from macro 'stringifyAsDeclMacro')}}
535+
#endif
536+
}

test/Macros/macro_plugin_basic.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323

2424
// CHECK: ->(plugin:[[#PID:]]) {"getCapability":{}}
2525
// CHECK: <-(plugin:[[#PID]]) {"getCapabilityResult":{"capability":{"protocolVersion":1}}}
26-
// CHECK: ->(plugin:[[#PID]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"testString","typeName":"TestStringMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":5,"offset":301},"source":"#testString(123)"}}}
26+
// CHECK: ->(plugin:[[#PID]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"testString","typeName":"TestStringMacro"},"macroRole":"expression","syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":5,"offset":301},"source":"#testString(123)"}}}
2727
// CHECK: <-(plugin:[[#PID]]) {"expandFreestandingMacroResult":{"diagnostics":[],"expandedSource":"\"123\"\n + \"foo \""}}
28-
// CHECK: ->(plugin:[[#PID]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"testStringWithError","typeName":"TestStringWithErrorMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":6,"offset":336},"source":"#testStringWithError(321)"}}}
28+
// CHECK: ->(plugin:[[#PID]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"testStringWithError","typeName":"TestStringWithErrorMacro"},"macroRole":"expression","syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":6,"offset":336},"source":"#testStringWithError(321)"}}}
2929
// CHECK: <-(plugin:[[#PID]]) {"expandFreestandingMacroResult":{"diagnostics":[{"fixIts":[],"highlights":[],"message":"message from plugin","notes":[],"position":{"fileName":"BUILD_DIR{{.*}}test.swift","offset":336},"severity":"error"}],"expandedSource":"\"bar\""}}
3030

3131
//--- test.swift

test/Macros/macro_plugin_error.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323

2424
// CHECK: ->(plugin:[[#PID1:]]) {"getCapability":{}}
2525
// CHECK-NEXT: <-(plugin:[[#PID1]]) {"getCapabilityResult":{"capability":{"protocolVersion":1}}}
26-
// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"fooMacro","typeName":"FooMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":6,"offset":[[#]]},"source":"#fooMacro(1)"}}}
26+
// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"fooMacro","typeName":"FooMacro"},"macroRole":"expression","syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":6,"offset":[[#]]},"source":"#fooMacro(1)"}}}
2727
// CHECK-NEXT: <-(plugin:[[#PID1]]) {"invalidResponse":{}}
28-
// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"fooMacro","typeName":"FooMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":8,"offset":[[#]]},"source":"#fooMacro(2)"}}}
28+
// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"fooMacro","typeName":"FooMacro"},"macroRole":"expression","syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":8,"offset":[[#]]},"source":"#fooMacro(2)"}}}
2929
// ^ This messages causes the mock plugin exit because there's no matching expected message.
3030

31-
// CHECK: ->(plugin:[[#PID2:]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"fooMacro","typeName":"FooMacro"},"syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":10,"offset":[[#]]},"source":"#fooMacro(3)"}}}
31+
// CHECK: ->(plugin:[[#PID2:]]) {"expandFreestandingMacro":{"discriminator":"$s{{.+}}","macro":{"moduleName":"TestPlugin","name":"fooMacro","typeName":"FooMacro"},"macroRole":"expression","syntax":{"kind":"expression","location":{"column":19,"fileID":"MyApp/test.swift","fileName":"BUILD_DIR{{.+}}test.swift","line":10,"offset":[[#]]},"source":"#fooMacro(3)"}}}
3232
// CHECK-NEXT: <-(plugin:[[#PID2:]]) {"expandFreestandingMacroResult":{"diagnostics":[],"expandedSource":"3.description"}}
3333

3434
//--- test.swift

test/Macros/macro_plugin_server.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@
3737
// CHECK-NEXT: <-(plugin:[[#PID1]]) {"loadPluginLibraryResult":{"diagnostics":[],"loaded":true}}
3838
// CHECK-NEXT: ->(plugin:[[#PID1]]) {"loadPluginLibrary":{"libraryPath":"BUILD_DIR{{.*}}plugins/libEvilMacros.dylib","moduleName":"EvilMacros"}}
3939
// CHECK-NEXT: <-(plugin:[[#PID1]]) {"loadPluginLibraryResult":{"diagnostics":[],"loaded":true}}
40-
// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"${{.*}}","macro":{"moduleName":"MacroDefinition","name":"stringify","typeName":"StringifyMacro"},"syntax":{"kind":"expression","location":{{{.+}}},"source":"#stringify(a + b)"}}}
40+
// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"${{.*}}","macro":{"moduleName":"MacroDefinition","name":"stringify","typeName":"StringifyMacro"},"macroRole":"expression","syntax":{"kind":"expression","location":{{{.+}}},"source":"#stringify(a + b)"}}}
4141
// CHECK-NEXT: <-(plugin:[[#PID1]]) {"expandFreestandingMacroResult":{"diagnostics":[],"expandedSource":"(a + b, \"a + b\")"}}
42-
// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"${{.*}}","macro":{"moduleName":"EvilMacros","name":"evil","typeName":"CrashingMacro"},"syntax":{"kind":"expression","location":{{{.+}}},"source":"#evil(42)"}}}
42+
// CHECK-NEXT: ->(plugin:[[#PID1]]) {"expandFreestandingMacro":{"discriminator":"${{.*}}","macro":{"moduleName":"EvilMacros","name":"evil","typeName":"CrashingMacro"},"macroRole":"expression","syntax":{"kind":"expression","location":{{{.+}}},"source":"#evil(42)"}}}
4343
// ^ This crashes the plugin server.
4444

4545
// CHECK-NEXT: ->(plugin:[[#PID2:]]) {"loadPluginLibrary":{"libraryPath":"BUILD_DIR{{.*}}plugins/libMacroDefinition.dylib","moduleName":"MacroDefinition"}}
4646
// CHECK-NEXT: <-(plugin:[[#PID2]]) {"loadPluginLibraryResult":{"diagnostics":[],"loaded":true}}
4747
// CHECK-NEXT: ->(plugin:[[#PID2]]) {"loadPluginLibrary":{"libraryPath":"BUILD_DIR{{.*}}plugins/libEvilMacros.dylib","moduleName":"EvilMacros"}}
4848
// CHECK-NEXT: <-(plugin:[[#PID2]]) {"loadPluginLibraryResult":{"diagnostics":[],"loaded":true}}
49-
// CHECK-NEXT: ->(plugin:[[#PID2]]) {"expandFreestandingMacro":{"discriminator":"${{.*}}","macro":{"moduleName":"MacroDefinition","name":"stringify","typeName":"StringifyMacro"},"syntax":{"kind":"expression","location":{{{.+}}},"source":"#stringify(b + a)"}}}
49+
// CHECK-NEXT: ->(plugin:[[#PID2]]) {"expandFreestandingMacro":{"discriminator":"${{.*}}","macro":{"moduleName":"MacroDefinition","name":"stringify","typeName":"StringifyMacro"},"macroRole":"expression","syntax":{"kind":"expression","location":{{{.+}}},"source":"#stringify(b + a)"}}}
5050
// CHECK-NEXT: <-(plugin:[[#PID2]]) {"expandFreestandingMacroResult":{"diagnostics":[],"expandedSource":"(b + a, \"b + a\")"}}
5151

5252
@freestanding(expression) macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MacroDefinition", type: "StringifyMacro")

test/Macros/macros_diagnostics.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,10 @@ struct SomeType {
211211

212212
@freestanding(declaration) macro nonExpressionReturnsVoid<T>(_: T) -> Void = #externalMacro(module: "A", type: "B")
213213
// expected-warning@-1{{external macro implementation type}}
214+
215+
216+
@freestanding(expression)
217+
@freestanding(declaration)
218+
macro multipleFreestandingRoles<T>(_: T) -> Void = #externalMacro(module: "A", type: "B")
219+
// expected-warning@-1{{external macro implementation type}}
220+
// expected-error@-2{{macro can only have a single freestanding role}}

test/Macros/parsing.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ protocol Q { associatedtype Assoc }
3535
@freestanding(expression) @freestanding(declaration, names: named(Foo)) @attached(accessor)
3636
macro m10(_: String) = #externalMacro(module: "A", type: "M4")
3737
// expected-warning@-1{{external macro implementation type 'A.M4' could not be found for macro 'm10'}}
38+
// expected-error@-2{{macro can only have a single freestanding role}}
3839

3940
@attached(
4041
accessor,

0 commit comments

Comments
 (0)