Skip to content

Commit 5d6746d

Browse files
committed
Improve checking of macro-generated accessors against documented names
The checking of the accessors generated by a macro against the documented set of accessors for the macro is slightly too strict and produces misleading error messages. Make the check slightly looser in the case where an observer-producing macro (such as `@ObservationIgnored`) is applied to a computed property. Here, we would diagnose that the observer did not in fact produce any observers, even though it couldn't have: computed properties don't get observers. Remove the diagnostic in this case. While here, add some tests and improve the wording of diagnostics a bit. Fixes rdar://113710199.
1 parent b7bfaf3 commit 5d6746d

File tree

6 files changed

+110
-23
lines changed

6 files changed

+110
-23
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7228,10 +7228,13 @@ ERROR(invalid_macro_role_for_macro_syntax,none,
72287228
(unsigned))
72297229
ERROR(macro_cannot_introduce_names,none,
72307230
"'%0' macros are not allowed to introduce names", (StringRef))
7231-
ERROR(macro_accessor_missing_from_expansion,none,
7232-
"expansion of macro %0 did not produce a %select{non-|}1observing "
7233-
"accessor",
7234-
(DeclName, bool))
7231+
ERROR(macro_nonobserving_accessor_missing_from_expansion,none,
7232+
"expansion of macro %0 did not produce a non-observing accessor "
7233+
"(such as 'get') as expected",
7234+
(DeclName))
7235+
ERROR(macro_nonobserver_unexpected_in_expansion,none,
7236+
"expansion of macro %0 produced an unexpected %1",
7237+
(DeclName, DescriptiveDeclKind))
72357238
ERROR(macro_init_accessor_not_documented,none,
72367239
"expansion of macro %0 produced an unexpected 'init' accessor",
72377240
(DeclName))

lib/Sema/TypeCheckMacros.cpp

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,7 +1358,9 @@ bool swift::accessorMacroOnlyIntroducesObservers(
13581358
for (auto name : attr->getNames()) {
13591359
if (name.getKind() == MacroIntroducedDeclNameKind::Named &&
13601360
(name.getName().getBaseName().userFacingName() == "willSet" ||
1361-
name.getName().getBaseName().userFacingName() == "didSet")) {
1361+
name.getName().getBaseName().userFacingName() == "didSet" ||
1362+
name.getName().getBaseName().getKind() ==
1363+
DeclBaseName::Kind::Constructor)) {
13621364
foundObserver = true;
13631365
} else {
13641366
// Introduces something other than an observer.
@@ -1409,24 +1411,28 @@ llvm::Optional<unsigned> swift::expandAccessors(AbstractStorageDecl *storage,
14091411
// Trigger parsing of the sequence of accessor declarations. This has the
14101412
// side effect of registering those accessor declarations with the storage
14111413
// declaration, so there is nothing further to do.
1412-
bool foundNonObservingAccessor = false;
1413-
bool foundNonObservingAccessorInMacro = false;
1414-
bool foundInitAccessor = false;
1414+
AccessorDecl *foundNonObservingAccessor = nullptr;
1415+
AccessorDecl *foundNonObservingAccessorInMacro = nullptr;
1416+
AccessorDecl *foundInitAccessor = nullptr;
14151417
for (auto accessor : storage->getAllAccessors()) {
1416-
if (accessor->isInitAccessor())
1417-
foundInitAccessor = true;
1418+
if (accessor->isInitAccessor()) {
1419+
if (!foundInitAccessor)
1420+
foundInitAccessor = accessor;
1421+
continue;
1422+
}
14181423

14191424
if (!accessor->isObservingAccessor()) {
1420-
foundNonObservingAccessor = true;
1425+
if (!foundNonObservingAccessor)
1426+
foundNonObservingAccessor = accessor;
14211427

1422-
if (accessor->isInMacroExpansionInContext())
1423-
foundNonObservingAccessorInMacro = true;
1428+
if (!foundNonObservingAccessorInMacro &&
1429+
accessor->isInMacroExpansionInContext())
1430+
foundNonObservingAccessorInMacro = accessor;
14241431
}
14251432
}
14261433

14271434
auto roleAttr = macro->getMacroRoleAttr(MacroRole::Accessor);
1428-
bool expectedNonObservingAccessor =
1429-
!accessorMacroOnlyIntroducesObservers(macro, roleAttr);
1435+
bool expectObservers = accessorMacroOnlyIntroducesObservers(macro, roleAttr);
14301436
if (foundNonObservingAccessorInMacro) {
14311437
// If any non-observing accessor was added, mark the initializer as
14321438
// subsumed unless it has init accessor, because the initializer in
@@ -1447,11 +1453,24 @@ llvm::Optional<unsigned> swift::expandAccessors(AbstractStorageDecl *storage,
14471453
storage->removeAccessor(accessor);
14481454
}
14491455

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

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

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,26 @@ extension PropertyWrapperSkipsComputedMacro: AccessorMacro, Macro {
506506
}
507507
}
508508

509+
public struct WillSetMacro: AccessorMacro {
510+
public static func expansion(
511+
of node: AttributeSyntax,
512+
providingAccessorsOf declaration: some DeclSyntaxProtocol,
513+
in context: some MacroExpansionContext
514+
) throws -> [AccessorDeclSyntax] {
515+
guard let varDecl = declaration.as(VariableDeclSyntax.self),
516+
let binding = varDecl.bindings.first,
517+
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else {
518+
return []
519+
}
520+
521+
return [
522+
"""
523+
willSet { }
524+
"""
525+
]
526+
}
527+
}
528+
509529
public struct WrapAllProperties: MemberAttributeMacro {
510530
public static func expansion(
511531
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@+1 2{{in expansion of macro 'myPropertyWrapper' on property 'birthDate' here}}
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 ('CannotHaveAccessors')}}
115117
@myPropertyWrapper
116118
struct CannotHaveAccessors {}
117119
// CHECK-DIAGS: 'accessor' macro cannot be attached to struct ('CannotHaveAccessors')
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/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
@@ -157,6 +157,12 @@ class IsolatedInstance {
157157
var test = "hello"
158158
}
159159

160+
@Observable
161+
class IgnoredComputed {
162+
@ObservationIgnored
163+
var message: String { "hello" }
164+
}
165+
160166
@Observable
161167
class ClassHasExistingConformance: Observable { }
162168

0 commit comments

Comments
 (0)