Skip to content

Commit c55d0e2

Browse files
authored
Merge pull request #67106 from hborla/validate-extension-macros
[Macros] Add missing macro validation.
2 parents e374e3a + 04ea8b0 commit c55d0e2

11 files changed

+212
-74
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7271,6 +7271,9 @@ ERROR(local_extension_macro,none,
72717271
ERROR(extension_macro_invalid_conformance,none,
72727272
"invalid protocol conformance %0 in extension macro",
72737273
(Type))
7274+
ERROR(macro_attached_to_invalid_decl,none,
7275+
"'%0' macro cannot be attached to %1",
7276+
(StringRef, DescriptiveDeclKind))
72747277

72757278
ERROR(macro_resolve_circular_reference, none,
72767279
"circular reference resolving %select{freestanding|attached}0 macro %1",

lib/Sema/TypeCheckAttr.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3684,10 +3684,17 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) {
36843684
Ctx.evaluator, CustomAttrNominalRequest{attr, dc}, nullptr);
36853685

36863686
if (!nominal) {
3687+
if (attr->isInvalid())
3688+
return;
3689+
36873690
// Try resolving an attached macro attribute.
3688-
auto *macro = D->getResolvedMacro(attr);
3689-
if (macro || !attr->isValid())
3691+
if (auto *macro = D->getResolvedMacro(attr)) {
3692+
for (auto *roleAttr : macro->getAttrs().getAttributes<MacroRoleAttr>()) {
3693+
diagnoseInvalidAttachedMacro(roleAttr->getMacroRole(), D);
3694+
}
3695+
36903696
return;
3697+
}
36913698

36923699
// Diagnose errors.
36933700

lib/Sema/TypeCheckMacros.cpp

Lines changed: 121 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,112 @@ static Identifier makeIdentifier(ASTContext &ctx, std::nullptr_t) {
543543
return Identifier();
544544
}
545545

546+
bool swift::diagnoseInvalidAttachedMacro(MacroRole role,
547+
Decl *attachedTo) {
548+
switch (role) {
549+
case MacroRole::Expression:
550+
case MacroRole::Declaration:
551+
case MacroRole::CodeItem:
552+
llvm_unreachable("Invalid macro role for attached macro");
553+
554+
case MacroRole::Accessor:
555+
// Only var decls and subscripts have accessors.
556+
if (isa<AbstractStorageDecl>(attachedTo) && !isa<ParamDecl>(attachedTo))
557+
return false;
558+
559+
break;
560+
561+
case MacroRole::MemberAttribute:
562+
case MacroRole::Member:
563+
// Nominal types and extensions can have members.
564+
if (isa<NominalTypeDecl>(attachedTo) || isa<ExtensionDecl>(attachedTo))
565+
return false;
566+
567+
break;
568+
569+
case MacroRole::Peer:
570+
// Peer macros are allowed on everything except parameters.
571+
if (!isa<ParamDecl>(attachedTo))
572+
return false;
573+
574+
break;
575+
576+
case MacroRole::Conformance:
577+
case MacroRole::Extension:
578+
// Only primary declarations of nominal types
579+
if (isa<NominalTypeDecl>(attachedTo))
580+
return false;
581+
582+
break;
583+
}
584+
585+
attachedTo->diagnose(diag::macro_attached_to_invalid_decl,
586+
getMacroRoleString(role),
587+
attachedTo->getDescriptiveKind());
588+
return true;
589+
}
590+
591+
static void diagnoseInvalidDecl(Decl *decl,
592+
MacroDecl *macro,
593+
llvm::function_ref<bool(DeclName)> coversName) {
594+
auto &ctx = decl->getASTContext();
595+
596+
// Diagnose invalid declaration kinds.
597+
if (isa<ImportDecl>(decl) ||
598+
isa<OperatorDecl>(decl) ||
599+
isa<PrecedenceGroupDecl>(decl) ||
600+
isa<MacroDecl>(decl) ||
601+
isa<ExtensionDecl>(decl)) {
602+
decl->diagnose(diag::invalid_decl_in_macro_expansion,
603+
decl->getDescriptiveKind());
604+
decl->setInvalid();
605+
606+
if (auto *extension = dyn_cast<ExtensionDecl>(decl)) {
607+
extension->setExtendedNominal(nullptr);
608+
}
609+
610+
return;
611+
}
612+
613+
// Diagnose `@main` types.
614+
if (auto *mainAttr = decl->getAttrs().getAttribute<MainTypeAttr>()) {
615+
ctx.Diags.diagnose(mainAttr->getLocation(),
616+
diag::invalid_main_type_in_macro_expansion);
617+
mainAttr->setInvalid();
618+
}
619+
620+
// Diagnose default literal type overrides.
621+
if (auto *typeAlias = dyn_cast<TypeAliasDecl>(decl)) {
622+
auto name = typeAlias->getBaseIdentifier();
623+
#define EXPRESSIBLE_BY_LITERAL_PROTOCOL_WITH_NAME(_, __, typeName, \
624+
supportsOverride) \
625+
if (supportsOverride && name == makeIdentifier(ctx, typeName)) { \
626+
typeAlias->diagnose(diag::literal_type_in_macro_expansion, \
627+
makeIdentifier(ctx, typeName)); \
628+
typeAlias->setInvalid(); \
629+
return; \
630+
}
631+
#include "swift/AST/KnownProtocols.def"
632+
#undef EXPRESSIBLE_BY_LITERAL_PROTOCOL_WITH_NAME
633+
}
634+
635+
// Diagnose value decls with names not covered by the macro
636+
if (auto *value = dyn_cast<ValueDecl>(decl)) {
637+
auto name = value->getName();
638+
639+
// Unique names are always permitted.
640+
if (MacroDecl::isUniqueMacroName(name.getBaseName().userFacingName()))
641+
return;
642+
643+
if (coversName(name)) {
644+
return;
645+
}
646+
647+
value->diagnose(diag::invalid_macro_introduced_name,
648+
name, macro->getBaseName());
649+
}
650+
}
651+
546652
/// Diagnose macro expansions that produce any of the following declarations:
547653
/// - Import declarations
548654
/// - Operator and precedence group declarations
@@ -559,75 +665,33 @@ static void validateMacroExpansion(SourceFile *expansionBuffer,
559665
llvm::SmallVector<DeclName, 2> introducedNames;
560666
macro->getIntroducedNames(role, attachedTo, introducedNames);
561667

562-
llvm::SmallDenseSet<DeclName, 2> coversName(introducedNames.begin(),
563-
introducedNames.end());
668+
llvm::SmallDenseSet<DeclName, 2> introducedNameSet(
669+
introducedNames.begin(), introducedNames.end());
564670

565-
for (auto *decl : expansionBuffer->getTopLevelDecls()) {
566-
auto &ctx = decl->getASTContext();
671+
auto coversName = [&](DeclName name) -> bool {
672+
return (introducedNameSet.count(name) ||
673+
introducedNameSet.count(name.getBaseName()) ||
674+
introducedNameSet.count(MacroDecl::getArbitraryName()));
675+
};
567676

677+
for (auto *decl : expansionBuffer->getTopLevelDecls()) {
568678
// Certain macro roles can generate special declarations.
569679
if ((isa<AccessorDecl>(decl) && role == MacroRole::Accessor) ||
570-
(isa<ExtensionDecl>(decl) && role == MacroRole::Conformance) ||
571-
(isa<ExtensionDecl>(decl) && role == MacroRole::Extension)) { // FIXME: Check extension for generated names.
680+
(isa<ExtensionDecl>(decl) && role == MacroRole::Conformance)) {
572681
continue;
573682
}
574683

575-
// Diagnose invalid declaration kinds.
576-
if (isa<ImportDecl>(decl) ||
577-
isa<OperatorDecl>(decl) ||
578-
isa<PrecedenceGroupDecl>(decl) ||
579-
isa<MacroDecl>(decl) ||
580-
isa<ExtensionDecl>(decl)) {
581-
decl->diagnose(diag::invalid_decl_in_macro_expansion,
582-
decl->getDescriptiveKind());
583-
decl->setInvalid();
584-
585-
if (auto *extension = dyn_cast<ExtensionDecl>(decl)) {
586-
extension->setExtendedNominal(nullptr);
684+
if (role == MacroRole::Extension) {
685+
auto *extension = dyn_cast<ExtensionDecl>(decl);
686+
687+
for (auto *member : extension->getMembers()) {
688+
diagnoseInvalidDecl(member, macro, coversName);
587689
}
588690

589691
continue;
590692
}
591693

592-
// Diagnose `@main` types.
593-
if (auto *mainAttr = decl->getAttrs().getAttribute<MainTypeAttr>()) {
594-
ctx.Diags.diagnose(mainAttr->getLocation(),
595-
diag::invalid_main_type_in_macro_expansion);
596-
mainAttr->setInvalid();
597-
}
598-
599-
// Diagnose default literal type overrides.
600-
if (auto *typeAlias = dyn_cast<TypeAliasDecl>(decl)) {
601-
auto name = typeAlias->getBaseIdentifier();
602-
#define EXPRESSIBLE_BY_LITERAL_PROTOCOL_WITH_NAME(_, __, typeName, \
603-
supportsOverride) \
604-
if (supportsOverride && name == makeIdentifier(ctx, typeName)) { \
605-
typeAlias->diagnose(diag::literal_type_in_macro_expansion, \
606-
makeIdentifier(ctx, typeName)); \
607-
typeAlias->setInvalid(); \
608-
continue; \
609-
}
610-
#include "swift/AST/KnownProtocols.def"
611-
#undef EXPRESSIBLE_BY_LITERAL_PROTOCOL_WITH_NAME
612-
}
613-
614-
// Diagnose value decls with names not covered by the macro
615-
if (auto *value = dyn_cast<ValueDecl>(decl)) {
616-
auto name = value->getName();
617-
618-
// Unique names are always permitted.
619-
if (MacroDecl::isUniqueMacroName(name.getBaseName().userFacingName()))
620-
continue;
621-
622-
if (coversName.count(name) ||
623-
coversName.count(name.getBaseName()) ||
624-
coversName.count(MacroDecl::getArbitraryName())) {
625-
continue;
626-
}
627-
628-
value->diagnose(diag::invalid_macro_introduced_name,
629-
name, macro->getBaseName());
630-
}
694+
diagnoseInvalidDecl(decl, macro, coversName);
631695
}
632696
}
633697

lib/Sema/TypeCheckMacros.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ bool accessorMacroOnlyIntroducesObservers(
9090
bool accessorMacroIntroducesInitAccessor(
9191
MacroDecl *macro, const MacroRoleAttr *attr);
9292

93+
/// Diagnose an error if the given macro role does not apply
94+
/// to the declaration kind of \c attachedTo.
95+
bool diagnoseInvalidAttachedMacro(MacroRole role,
96+
Decl *attachedTo);
97+
9398
} // end namespace swift
9499

95100
#endif /* SWIFT_SEMA_TYPECHECKMACROS_H */

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,16 @@ public struct EmptyMacro: MemberMacro {
13141314
}
13151315
}
13161316

1317+
public struct EmptyPeerMacro: PeerMacro {
1318+
public static func expansion(
1319+
of node: AttributeSyntax,
1320+
providingPeersOf declaration: some DeclSyntaxProtocol,
1321+
in context: some MacroExpansionContext
1322+
) throws -> [DeclSyntax] {
1323+
return []
1324+
}
1325+
}
1326+
13171327
public struct EquatableMacro: ConformanceMacro {
13181328
public static func expansion(
13191329
of node: AttributeSyntax,

test/Macros/accessor_macros.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,7 @@ struct MyBrokenStruct {
112112
}
113113
}
114114

115+
@myPropertyWrapper
116+
struct CannotHaveAccessors {}
117+
// CHECK-DIAGS: 'accessor' macro cannot be attached to struct
115118
#endif

test/Macros/attached_macros_diags.swift

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
// REQUIRES: swift_swift_parser
22

3-
// RUN: %target-typecheck-verify-swift -swift-version 5 -module-name MacrosTest
3+
// RUN: %empty-directory(%t)
4+
// RUN: %host-build-swift -swift-version 5 -emit-library -o %t/%target-library-name(MacroDefinition) -parse-as-library -module-name=MacroDefinition %S/Inputs/syntax_macro_definitions.swift -g -no-toolchain-stdlib-rpath
5+
// RUN: %target-typecheck-verify-swift -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -disable-availability-checking -module-name MacrosTest
46

5-
@attached(accessor) macro m1() = #externalMacro(module: "MyMacros", type: "Macro1")
6-
// expected-warning@-1{{external macro implementation type 'MyMacros.Macro1' could not be found for macro 'm1()'}}
7-
// expected-note@-2{{'m1()' declared here}}
7+
@attached(peer) macro m1() = #externalMacro(module: "MacroDefinition", type: "EmptyPeerMacro")
88

9-
@attached(accessor) macro m2(_: Int) = #externalMacro(module: "MyMacros", type: "Macro2")
10-
// expected-warning@-1{{external macro implementation type 'MyMacros.Macro2' could not be found for macro 'm2'}}
11-
// expected-note@-2{{candidate has partially matching parameter list (Int)}}
12-
// expected-note@-3{{candidate expects value of type 'Int' for parameter #1 (got 'String')}}
9+
@attached(peer) macro m2(_: Int) = #externalMacro(module: "MacroDefinition", type: "EmptyPeerMacro")
10+
// expected-note@-1{{candidate has partially matching parameter list (Int)}}
11+
// expected-note@-2{{candidate expects value of type 'Int' for parameter #1 (got 'String')}}
1312

14-
@attached(accessor) macro m2(_: Double) = #externalMacro(module: "MyMacros", type: "Macro2")
15-
// expected-warning@-1{{external macro implementation type 'MyMacros.Macro2' could not be found for macro 'm2'}}
16-
// expected-note@-2{{candidate has partially matching parameter list (Double)}}
17-
// expected-note@-3{{candidate expects value of type 'Double' for parameter #1 (got 'String')}}
13+
@attached(peer) macro m2(_: Double) = #externalMacro(module: "MacroDefinition", type: "EmptyPeerMacro")
14+
// expected-note@-1{{candidate has partially matching parameter list (Double)}}
15+
// expected-note@-2{{candidate expects value of type 'Double' for parameter #1 (got 'String')}}
1816

19-
@attached(accessor) macro m3(message: String) = #externalMacro(module: "MyMacros", type: "Macro3")
20-
// expected-warning@-1{{external macro implementation type 'MyMacros.Macro3' could not be found for macro 'm3(message:)'}}
17+
@attached(peer) macro m3(message: String) = #externalMacro(module: "MacroDefinition", type: "EmptyPeerMacro")
2118

2219
@freestanding(expression) macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MyMacros", type: "StringifyMacro")
2320
// expected-warning@-1{{external macro implementation type 'MyMacros.StringifyMacro' could not be found for macro 'stringify'}}
@@ -38,7 +35,12 @@ struct SkipNestedType {
3835

3936
// We select the macro, not the property wrapper.
4037
@m1 var x: Int = 0
41-
// expected-error@-1{{external macro implementation type 'MyMacros.Macro1' could not be found for macro 'm1()'}}
38+
//expected-note@-1{{did you mean 'x'?}}
39+
40+
func test() {
41+
let _: m1<Int> = _x
42+
// expected-error@-1{{cannot find '_x' in scope}}
43+
}
4244
}
4345

4446
struct TestMacroArgs {

test/Macros/macro_expand_attributes.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,13 @@ expansionOrder.expandedMember = 27
140140

141141
// CHECK: setting 28
142142
expansionOrder.originalMember = 28
143+
144+
#if TEST_DIAGNOSTICS
145+
@wrapAllProperties
146+
typealias A = Int
147+
// expected-error@-1{{'memberAttribute' macro cannot be attached to type alias}}
148+
149+
@wrapAllProperties
150+
func noMembers() {}
151+
// expected-error@-1{{'memberAttribute' macro cannot be attached to global function}}
152+
#endif

test/Macros/macro_expand_extensions.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
// RUN: %target-swift-frontend -enable-experimental-feature ExtensionMacros -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
77
// RUN: %FileCheck -check-prefix=CHECK-DUMP %s < %t/expansions-dump.txt
88
// 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
9+
10+
// RUN: not %target-swift-frontend -enable-experimental-feature ExtensionMacros -swift-version 5 -typecheck -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -DTEST_DIAGNOSTICS -serialize-diagnostics-path %t/macro_expand.dia %s -emit-macro-expansion-files no-diagnostics
11+
// RUN: c-index-test -read-diagnostics %t/macro_expand.dia 2>&1 | %FileCheck -check-prefix CHECK-DIAGS %s
12+
913
// RUN: %target-build-swift -enable-experimental-feature ExtensionMacros -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) %s -o %t/main -module-name MacroUser -swift-version 5 -emit-tbd -emit-tbd-path %t/MacroUser.tbd -I %t
1014
// RUN: %target-codesign %t/main
1115
// RUN: %target-run %t/main | %FileCheck %s
@@ -106,4 +110,21 @@ func testLocal() {
106110
struct Local<Element> {}
107111
// expected-error@-1{{local type cannot have attached extension macro}}
108112
}
113+
114+
@DelegatedConformance
115+
typealias A = Int
116+
// expected-error@-1 {{'extension' macro cannot be attached to type alias}}
117+
118+
@DelegatedConformance
119+
extension Int {}
120+
// expected-error@-1 {{'extension' macro cannot be attached to extension}}
121+
122+
@attached(extension, conformances: P)
123+
macro UndocumentedNamesInExtension() = #externalMacro(module: "MacroDefinition", type: "DelegatedConformanceViaExtensionMacro")
124+
125+
@UndocumentedNamesInExtension
126+
struct S<Element> {}
127+
// expected-note@-1 {{in expansion of macro 'UndocumentedNamesInExtension' here}}
128+
129+
// CHECK-DIAGS: error: declaration name 'requirement()' is not covered by macro 'UndocumentedNamesInExtension'
109130
#endif

test/Macros/macro_expand_peers.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,13 @@ func testVarPeer() {
214214
_ = Date().value
215215
}
216216

217+
#if TEST_DIAGNOSTICS
218+
// Macros cannot be attached to function parameters
219+
220+
// expected-error@+1{{'peer' macro cannot be attached to parameter}}
221+
func test(@declareVarValuePeer x: Int) {}
222+
#endif
223+
217224
// Stored properties added via peer macros.
218225
@attached(peer, names: named(_foo))
219226
macro AddPeerStoredProperty() =

0 commit comments

Comments
 (0)