Skip to content

Commit 7651a7f

Browse files
authored
Merge pull request #67999 from DougGregor/observer-macros-on-computed-properties-5.9
[5.9] Improve checking of macro-generated accessors against documented names
2 parents 1a48e2d + 49c7f09 commit 7651a7f

File tree

11 files changed

+127
-37
lines changed

11 files changed

+127
-37
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7187,10 +7187,13 @@ ERROR(invalid_macro_role_for_macro_syntax,none,
71877187
(unsigned))
71887188
ERROR(macro_cannot_introduce_names,none,
71897189
"'%0' macros are not allowed to introduce names", (StringRef))
7190-
ERROR(macro_accessor_missing_from_expansion,none,
7191-
"expansion of macro %0 did not produce a %select{non-|}1observing "
7192-
"accessor",
7193-
(DeclName, bool))
7190+
ERROR(macro_nonobserving_accessor_missing_from_expansion,none,
7191+
"expansion of macro %0 did not produce a non-observing accessor "
7192+
"(such as 'get') as expected",
7193+
(DeclName))
7194+
ERROR(macro_nonobserver_unexpected_in_expansion,none,
7195+
"expansion of macro %0 produced an unexpected %1",
7196+
(DeclName, DescriptiveDeclKind))
71947197
ERROR(macro_init_accessor_not_documented,none,
71957198
"expansion of macro %0 produced an unexpected 'init' accessor",
71967199
(DeclName))

lib/Sema/TypeCheckAttr.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3665,7 +3665,13 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) {
36653665
// Try resolving an attached macro attribute.
36663666
if (auto *macro = D->getResolvedMacro(attr)) {
36673667
for (auto *roleAttr : macro->getAttrs().getAttributes<MacroRoleAttr>()) {
3668-
diagnoseInvalidAttachedMacro(roleAttr->getMacroRole(), D);
3668+
auto role = roleAttr->getMacroRole();
3669+
if (isInvalidAttachedMacro(role, D)) {
3670+
diagnoseAndRemoveAttr(attr,
3671+
diag::macro_attached_to_invalid_decl,
3672+
getMacroRoleString(role),
3673+
D->getDescriptiveKind());
3674+
}
36693675
}
36703676

36713677
return;

lib/Sema/TypeCheckMacros.cpp

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

546-
bool swift::diagnoseInvalidAttachedMacro(MacroRole role,
547-
Decl *attachedTo) {
546+
bool swift::isInvalidAttachedMacro(MacroRole role,
547+
Decl *attachedTo) {
548548
switch (role) {
549549
case MacroRole::Expression:
550550
case MacroRole::Declaration:
@@ -582,9 +582,6 @@ bool swift::diagnoseInvalidAttachedMacro(MacroRole role,
582582
break;
583583
}
584584

585-
attachedTo->diagnose(diag::macro_attached_to_invalid_decl,
586-
getMacroRoleString(role),
587-
attachedTo->getDescriptiveKind());
588585
return true;
589586
}
590587

@@ -1362,7 +1359,9 @@ bool swift::accessorMacroOnlyIntroducesObservers(
13621359
for (auto name : attr->getNames()) {
13631360
if (name.getKind() == MacroIntroducedDeclNameKind::Named &&
13641361
(name.getName().getBaseName().userFacingName() == "willSet" ||
1365-
name.getName().getBaseName().userFacingName() == "didSet")) {
1362+
name.getName().getBaseName().userFacingName() == "didSet" ||
1363+
name.getName().getBaseName().getKind() ==
1364+
DeclBaseName::Kind::Constructor)) {
13661365
foundObserver = true;
13671366
} else {
13681367
// Introduces something other than an observer.
@@ -1413,24 +1412,28 @@ llvm::Optional<unsigned> swift::expandAccessors(AbstractStorageDecl *storage,
14131412
// Trigger parsing of the sequence of accessor declarations. This has the
14141413
// side effect of registering those accessor declarations with the storage
14151414
// declaration, so there is nothing further to do.
1416-
bool foundNonObservingAccessor = false;
1417-
bool foundNonObservingAccessorInMacro = false;
1418-
bool foundInitAccessor = false;
1415+
AccessorDecl *foundNonObservingAccessor = nullptr;
1416+
AccessorDecl *foundNonObservingAccessorInMacro = nullptr;
1417+
AccessorDecl *foundInitAccessor = nullptr;
14191418
for (auto accessor : storage->getAllAccessors()) {
1420-
if (accessor->isInitAccessor())
1421-
foundInitAccessor = true;
1419+
if (accessor->isInitAccessor()) {
1420+
if (!foundInitAccessor)
1421+
foundInitAccessor = accessor;
1422+
continue;
1423+
}
14221424

14231425
if (!accessor->isObservingAccessor()) {
1424-
foundNonObservingAccessor = true;
1426+
if (!foundNonObservingAccessor)
1427+
foundNonObservingAccessor = accessor;
14251428

1426-
if (accessor->isInMacroExpansionInContext())
1427-
foundNonObservingAccessorInMacro = true;
1429+
if (!foundNonObservingAccessorInMacro &&
1430+
accessor->isInMacroExpansionInContext())
1431+
foundNonObservingAccessorInMacro = accessor;
14281432
}
14291433
}
14301434

14311435
auto roleAttr = macro->getMacroRoleAttr(MacroRole::Accessor);
1432-
bool expectedNonObservingAccessor =
1433-
!accessorMacroOnlyIntroducesObservers(macro, roleAttr);
1436+
bool expectObservers = accessorMacroOnlyIntroducesObservers(macro, roleAttr);
14341437
if (foundNonObservingAccessorInMacro) {
14351438
// If any non-observing accessor was added, mark the initializer as
14361439
// subsumed unless it has init accessor, because the initializer in
@@ -1451,11 +1454,24 @@ llvm::Optional<unsigned> swift::expandAccessors(AbstractStorageDecl *storage,
14511454
storage->removeAccessor(accessor);
14521455
}
14531456

1454-
// Make sure we got non-observing accessors exactly where we expected to.
1455-
if (foundNonObservingAccessor != expectedNonObservingAccessor) {
1457+
// If the macro told us to expect only observing accessors, but the macro
1458+
// produced a non-observing accessor, it could have converted a stored
1459+
// property into a computed one without telling us pre-expansion. Produce
1460+
// an error to prevent this.
1461+
if (expectObservers && foundNonObservingAccessorInMacro) {
1462+
storage->diagnose(
1463+
diag::macro_nonobserver_unexpected_in_expansion, macro->getName(),
1464+
foundNonObservingAccessorInMacro->getDescriptiveKind());
1465+
}
1466+
1467+
// We expected to get a non-observing accessor, but there isn't one (from
1468+
// the macro or elsewhere), meaning that we counted on this macro to make
1469+
// this stored property into a a computed property... but it didn't.
1470+
// Produce an error.
1471+
if (!expectObservers && !foundNonObservingAccessor) {
14561472
storage->diagnose(
1457-
diag::macro_accessor_missing_from_expansion, macro->getName(),
1458-
!expectedNonObservingAccessor);
1473+
diag::macro_nonobserving_accessor_missing_from_expansion,
1474+
macro->getName());
14591475
}
14601476

14611477
// 'init' accessors must be documented in the macro role attribute.

lib/Sema/TypeCheckMacros.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ bool accessorMacroOnlyIntroducesObservers(
8989
bool accessorMacroIntroducesInitAccessor(
9090
MacroDecl *macro, const MacroRoleAttr *attr);
9191

92-
/// Diagnose an error if the given macro role does not apply
92+
/// Return true if the given macro role does not apply
9393
/// to the declaration kind of \c attachedTo.
94-
bool diagnoseInvalidAttachedMacro(MacroRole role,
95-
Decl *attachedTo);
94+
bool isInvalidAttachedMacro(MacroRole role,
95+
Decl *attachedTo);
9696

9797
} // end namespace swift
9898

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,26 @@ extension PropertyWrapperSkipsComputedMacro: AccessorMacro, Macro {
516516
}
517517
}
518518

519+
public struct WillSetMacro: AccessorMacro {
520+
public static func expansion(
521+
of node: AttributeSyntax,
522+
providingAccessorsOf declaration: some DeclSyntaxProtocol,
523+
in context: some MacroExpansionContext
524+
) throws -> [AccessorDeclSyntax] {
525+
guard let varDecl = declaration.as(VariableDeclSyntax.self),
526+
let binding = varDecl.bindings.first,
527+
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else {
528+
return []
529+
}
530+
531+
return [
532+
"""
533+
willSet { }
534+
"""
535+
]
536+
}
537+
}
538+
519539
public struct WrapAllProperties: MemberAttributeMacro {
520540
public static func expansion(
521541
of node: AttributeSyntax,

test/Macros/accessor_macros.swift

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
// RUN: %empty-directory(%t)
44
// 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
55

6-
// First check for no errors.
7-
// RUN: %target-typecheck-verify-swift -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition)
8-
96
// Check for expected errors.
7+
// RUN: %target-typecheck-verify-swift -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -DTEST_DIAGNOSTICS -verify-ignore-unknown
108
// RUN: not %target-swift-frontend -typecheck -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -DTEST_DIAGNOSTICS %s > %t/diags.txt 2>&1
119
// RUN: %FileCheck -check-prefix=CHECK-DIAGS %s < %t/diags.txt
1210

@@ -98,21 +96,62 @@ ms.favoriteColor = "Yellow"
9896
struct MyBrokenStruct {
9997
var _birthDate: MyWrapperThingy<Date?> = .init(storage: nil)
10098

99+
// expected-note@+2 2{{in expansion of macro 'myPropertyWrapper'}}
101100
@myPropertyWrapper
102101
var birthDate: Date? {
103102
// CHECK-DIAGS: variable already has a getter
104103
// CHECK-DIAGS: in expansion of macro
105104
// CHECK-DIAGS: previous definition of getter here
106105
get { fatalError("Boom") }
106+
// expected-note @-1{{previous definition of getter here}}
107107

108108
// CHECK-DIAGS: variable already has a setter
109109
// CHECK-DIAGS: in expansion of macro
110110
// CHECK-DIAGS: previous definition of setter here
111111
set { fatalError("Boom") }
112+
// expected-note @-1{{previous definition of setter here}}
112113
}
113114
}
114115

116+
// expected-error@+1{{'accessor' macro cannot be attached to struct}}
115117
@myPropertyWrapper
116118
struct CannotHaveAccessors {}
117119
// CHECK-DIAGS: 'accessor' macro cannot be attached to struct
118120
#endif
121+
122+
123+
124+
@attached(accessor, names: named(willSet))
125+
macro SkipsComputed() =
126+
#externalMacro(module: "MacroDefinition", type: "PropertyWrapperSkipsComputedMacro")
127+
128+
struct HasComputed {
129+
@SkipsComputed
130+
var value: Int { 17 }
131+
}
132+
133+
@attached(accessor, names: named(willSet))
134+
macro AddWillSet() =
135+
#externalMacro(module: "MacroDefinition", type: "WillSetMacro")
136+
137+
@attached(accessor)
138+
macro AddWillSetSneakily() =
139+
#externalMacro(module: "MacroDefinition", type: "WillSetMacro")
140+
141+
@attached(accessor, names: named(willSet))
142+
macro MakeComputedSneakily() =
143+
#externalMacro(module: "MacroDefinition", type: "PropertyWrapperMacro")
144+
145+
struct HasStoredTests {
146+
@AddWillSet var x: Int = 0
147+
148+
#if TEST_DIAGNOSTICS
149+
@AddWillSetSneakily var y: Int = 0
150+
// expected-error@-1{{expansion of macro 'AddWillSetSneakily()' did not produce a non-observing accessor (such as 'get') as expected}}
151+
152+
@MakeComputedSneakily var z: Int = 0
153+
// expected-error@-1{{expansion of macro 'MakeComputedSneakily()' produced an unexpected getter}}
154+
// expected-note@-2 2{{in expansion of macro}}
155+
// expected-note@-3 2{{'z' declared here}}
156+
#endif
157+
}

test/Macros/macro_expand_attributes.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,9 @@ expansionOrder.originalMember = 28
144144
#if TEST_DIAGNOSTICS
145145
@wrapAllProperties
146146
typealias A = Int
147-
// expected-error@-1{{'memberAttribute' macro cannot be attached to type alias}}
147+
// expected-error@-2{{'memberAttribute' macro cannot be attached to type alias}}
148148

149149
@wrapAllProperties
150150
func noMembers() {}
151-
// expected-error@-1{{'memberAttribute' macro cannot be attached to global function}}
151+
// expected-error@-2{{'memberAttribute' macro cannot be attached to global function}}
152152
#endif

test/Macros/macro_expand_extensions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ func testLocal() {
113113

114114
@DelegatedConformance
115115
typealias A = Int
116-
// expected-error@-1 {{'extension' macro cannot be attached to type alias}}
116+
// expected-error@-2 {{'extension' macro cannot be attached to type alias}}
117117

118118
@DelegatedConformance
119119
extension Int {}
120-
// expected-error@-1 {{'extension' macro cannot be attached to extension}}
120+
// expected-error@-2 {{'extension' macro cannot be attached to extension}}
121121

122122
@attached(extension, conformances: P)
123123
macro UndocumentedNamesInExtension() = #externalMacro(module: "MacroDefinition", type: "DelegatedConformanceViaExtensionMacro")

test/Macros/macro_expand_synthesized_members.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ macro addMembersQuotedInit() = #externalMacro(module: "MacroDefinition", type: "
2222
#if TEST_DIAGNOSTICS
2323
@addMembers
2424
import Swift
25-
// expected-error@-1 {{'member' macro cannot be attached to import}}
25+
// expected-error@-2 {{'member' macro cannot be attached to import}}
2626
#endif
2727

2828
@addMembers

test/Serialization/macros.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func test(a: Int, b: Int) {
2121

2222
struct TestStruct {
2323
@myWrapper var x: Int
24-
// expected-error@-1{{expansion of macro 'myWrapper()' did not produce a non-observing accessor}}
24+
// expected-error@-1{{expansion of macro 'myWrapper()' did not produce a non-observing accessor (such as 'get') as expected}}
2525
}
2626

2727
@ArbitraryMembers

test/stdlib/Observation/Observable.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ class IsolatedInstance {
155155
var test = "hello"
156156
}
157157

158+
@Observable
159+
class IgnoredComputed {
160+
@ObservationIgnored
161+
var message: String { "hello" }
162+
}
163+
158164
@Observable
159165
class ClassHasExistingConformance: Observable { }
160166

0 commit comments

Comments
 (0)