[Macros] Expand extension macros at the top-level

Ensure we always expand extension macros after the top-level decl
for the given attached decl. This ensures correct unqualified lookup
behavior, and bans macro implementations from extending the
unqualified name (they're expected to use `providingExtensionsOf`
instead, which uses the qualified name).

rdar://148119538
This commit is contained in:
Hamish Knight
2025-03-28 21:35:59 +00:00
parent bc5c8f51bd
commit aaeb38d7f4
4 changed files with 45 additions and 1 deletions

View File

@@ -957,8 +957,12 @@ static CharSourceRange getExpansionInsertionRange(MacroRole role,
}
case MacroRole::Extension: {
// Extensions are expanded at the top-level.
auto *NTD = cast<NominalTypeDecl>(target.get<Decl *>());
auto *topLevelDecl = NTD->getTopmostDeclarationDeclContext();
SourceLoc afterDeclLoc =
Lexer::getLocForEndOfToken(sourceMgr, target.getEndLoc());
Lexer::getLocForEndOfToken(sourceMgr, topLevelDecl->getEndLoc());
return CharSourceRange(afterDeclLoc, 0);
}

View File

@@ -1628,6 +1628,20 @@ public struct FooExtensionMacro: ExtensionMacro {
}
}
public struct BadExtensionMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
// Note this is purposefully not using `providingExtensionsOf`.
let unqualifiedName = declaration.as(StructDeclSyntax.self)!.name.trimmed
return [try ExtensionDeclSyntax("extension \(unqualifiedName) {}")]
}
}
public struct ConformanceViaExtensionMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,

View File

@@ -161,6 +161,17 @@ struct TestUndocumentedEncodable {}
// CHECK-DIAGS: error: conformance to 'Codable' (aka 'Decodable & Encodable') is not covered by macro 'UndocumentedEncodable'
@attached(extension)
macro BadExtension() = #externalMacro(module: "MacroDefinition", type: "BadExtensionMacro")
// Make sure 'extension Foo' is rejected here as it needs to
// be a qualified reference.
struct HasSomeNestedType {
@BadExtension // expected-note {{in expansion of macro 'BadExtension' on struct 'SomeNestedType' here}}
struct SomeNestedType {}
}
// CHECK-DIAGS: error: cannot find type 'SomeNestedType' in scope
#endif
@attached(extension, conformances: Equatable)

View File

@@ -121,6 +121,14 @@ func remoteCall<Result: ConjureRemoteValue>(function: String, arguments: [String
return Result.conjureValue()
}
@attached(extension, conformances: Equatable)
macro AddEquatable() = #externalMacro(module: "MacroDefinition", type: "EquatableMacro")
struct HasNestedType {
@AddEquatable
struct Inner {}
}
// REQUIRES: swift_swift_parser, executable_test, shell, asserts
// REQUIRES: swift_feature_PreambleMacros
@@ -379,3 +387,10 @@ func remoteCall<Result: ConjureRemoteValue>(function: String, arguments: [String
// BODY_EXPAND-NEXT: return try await remoteCall(function: "f", arguments: ["a": a, "b": b])
// BODY_EXPAND-NEXT: }"
// BODY_EXPAND-NEXT: source.edit.kind.active:
// Make sure the extension is added at the top level.
// RUN: %sourcekitd-test -req=refactoring.expand.macro -pos=128:4 %s -- ${COMPILER_ARGS[@]} | %FileCheck -check-prefix=ADD_EQUATABLE_EXPAND %s
// ADD_EQUATABLE_EXPAND: source.edit.kind.active:
// ADD_EQUATABLE_EXPAND-NEXT: 130:2-130:2 (@__swiftmacro_9MacroUser13HasNestedTypeV5Inner12AddEquatablefMe_.swift) "extension HasNestedType.Inner: Equatable {
// ADD_EQUATABLE_EXPAND-NEXT: }"
// ADD_EQUATABLE_EXPAND-NEXT: source.edit.kind.active: