Skip to content

Commit 1209ef8

Browse files
committed
Ensure that macros within init accessors are expanded early enough
Now that we've made accessor macro expansion more lazy, ensure that when querying for init accessors (e.g., to build a memberwise initializer), we also expand any accessor macros that might produce an init accessor. This is a partial step toward the real goal, which is that `AbstractStorageDecl::getAccessor()` should lazily expand macros if needed. Update the Observable macro to document that it produces an `init` accessor.
1 parent 4cb720e commit 1209ef8

File tree

7 files changed

+106
-24
lines changed

7 files changed

+106
-24
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7185,6 +7185,9 @@ ERROR(macro_accessor_missing_from_expansion,none,
71857185
"expansion of macro %0 did not produce a %select{non-|}1observing "
71867186
"accessor",
71877187
(DeclName, bool))
7188+
ERROR(macro_init_accessor_not_documented,none,
7189+
"expansion of macro %0 produced an unexpected 'init' accessor",
7190+
(DeclName))
71887191

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

lib/Sema/TypeCheckMacros.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,19 @@ bool swift::accessorMacroOnlyIntroducesObservers(
12711271
return false;
12721272
}
12731273

1274+
bool swift::accessorMacroIntroducesInitAccessor(
1275+
MacroDecl *macro, const MacroRoleAttr *attr
1276+
) {
1277+
for (auto name : attr->getNames()) {
1278+
if (name.getKind() == MacroIntroducedDeclNameKind::Named &&
1279+
(name.getName().getBaseName().getKind() ==
1280+
DeclBaseName::Kind::Constructor))
1281+
return true;
1282+
}
1283+
1284+
return false;
1285+
}
1286+
12741287
Optional<unsigned> swift::expandAccessors(
12751288
AbstractStorageDecl *storage, CustomAttr *attr, MacroDecl *macro
12761289
) {
@@ -1288,15 +1301,17 @@ Optional<unsigned> swift::expandAccessors(
12881301
// side effect of registering those accessor declarations with the storage
12891302
// declaration, so there is nothing further to do.
12901303
bool foundNonObservingAccessor = false;
1304+
bool foundInitAccessor = false;
12911305
for (auto decl : macroSourceFile->getTopLevelItems()) {
12921306
auto accessor = dyn_cast_or_null<AccessorDecl>(decl.dyn_cast<Decl *>());
12931307
if (!accessor)
12941308
continue;
12951309

1296-
if (accessor->isObservingAccessor())
1297-
continue;
1310+
if (accessor->isInitAccessor())
1311+
foundInitAccessor = true;
12981312

1299-
foundNonObservingAccessor = true;
1313+
if (!accessor->isObservingAccessor())
1314+
foundNonObservingAccessor = true;
13001315
}
13011316

13021317
auto roleAttr = macro->getMacroRoleAttr(MacroRole::Accessor);
@@ -1320,6 +1335,14 @@ Optional<unsigned> swift::expandAccessors(
13201335
!expectedNonObservingAccessor);
13211336
}
13221337

1338+
// 'init' accessors must be documented in the macro role attribute.
1339+
if (foundInitAccessor &&
1340+
!accessorMacroIntroducesInitAccessor(macro, roleAttr)) {
1341+
storage->diagnose(
1342+
diag::macro_init_accessor_not_documented, macro->getName());
1343+
// FIXME: Add the appropriate "names: named(init)".
1344+
}
1345+
13231346
return macroSourceFile->getBufferID();
13241347
}
13251348

lib/Sema/TypeCheckMacros.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ Optional<unsigned> expandConformances(CustomAttr *attr, MacroDecl *macro,
8282
bool accessorMacroOnlyIntroducesObservers(
8383
MacroDecl *macro, const MacroRoleAttr *attr);
8484

85+
/// Determine whether an accessor macro (defined with the given role attribute)
86+
/// introduces an init accessor.
87+
bool accessorMacroIntroducesInitAccessor(
88+
MacroDecl *macro, const MacroRoleAttr *attr);
89+
8590
} // end namespace swift
8691

8792
#endif /* SWIFT_SEMA_TYPECHECKMACROS_H */

lib/Sema/TypeCheckStorage.cpp

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,16 @@ static bool hasStoredProperties(NominalTypeDecl *decl,
106106
|| (decl != implDecl))));
107107
}
108108

109-
static void computeLoweredStoredProperties(NominalTypeDecl *decl,
110-
IterableDeclContext *implDecl) {
109+
namespace {
110+
enum class LoweredPropertiesReason {
111+
Stored,
112+
Memberwise
113+
};
114+
}
115+
116+
static void computeLoweredProperties(NominalTypeDecl *decl,
117+
IterableDeclContext *implDecl,
118+
LoweredPropertiesReason reason) {
111119
// Expand synthesized member macros.
112120
auto &ctx = decl->getASTContext();
113121
(void)evaluateOrDefault(ctx.evaluator,
@@ -127,15 +135,20 @@ static void computeLoweredStoredProperties(NominalTypeDecl *decl,
127135
if (!var || var->isStatic())
128136
continue;
129137

130-
if (var->getAttrs().hasAttribute<LazyAttr>())
131-
(void) var->getLazyStorageProperty();
138+
if (reason == LoweredPropertiesReason::Stored) {
139+
if (var->getAttrs().hasAttribute<LazyAttr>())
140+
(void) var->getLazyStorageProperty();
132141

133-
if (var->hasAttachedPropertyWrapper()) {
134-
(void) var->getPropertyWrapperAuxiliaryVariables();
135-
(void) var->getPropertyWrapperInitializerInfo();
142+
if (var->hasAttachedPropertyWrapper()) {
143+
(void) var->getPropertyWrapperAuxiliaryVariables();
144+
(void) var->getPropertyWrapperInitializerInfo();
145+
}
136146
}
137147
}
138148

149+
if (reason != LoweredPropertiesReason::Stored)
150+
return;
151+
139152
// If this is an actor, check conformance to the Actor protocol to
140153
// ensure that the actor storage will get created (if needed).
141154
if (auto classDecl = dyn_cast<ClassDecl>(decl)) {
@@ -164,6 +177,11 @@ static void computeLoweredStoredProperties(NominalTypeDecl *decl,
164177
}
165178
}
166179

180+
static void computeLoweredStoredProperties(NominalTypeDecl *decl,
181+
IterableDeclContext *implDecl) {
182+
computeLoweredProperties(decl, implDecl, LoweredPropertiesReason::Stored);
183+
}
184+
167185
/// Enumerate both the stored properties and missing members,
168186
/// in a deterministic order.
169187
static void enumerateStoredPropertiesAndMissing(
@@ -283,13 +301,46 @@ StoredPropertiesAndMissingMembersRequest::evaluate(Evaluator &evaluator,
283301
return decl->getASTContext().AllocateCopy(results);
284302
}
285303

304+
/// Determine whether the given variable has an init accessor.
305+
static bool hasInitAccessor(VarDecl *var) {
306+
if (var->getAccessor(AccessorKind::Init))
307+
return true;
308+
309+
// Look to see whether it is possible that there is an init accessor.
310+
bool hasInitAccessor = false;
311+
namelookup::forEachPotentialAttachedMacro(
312+
var, MacroRole::Accessor,
313+
[&](MacroDecl *macro, const MacroRoleAttr *attr) {
314+
if (accessorMacroIntroducesInitAccessor(macro, attr))
315+
hasInitAccessor = true;
316+
});
317+
318+
// There is no chance for an init accessor, so we're done.
319+
if (!hasInitAccessor)
320+
return false;
321+
322+
// We might get an init accessor by expanding accessor macros; do so now.
323+
(void)evaluateOrDefault(
324+
var->getASTContext().evaluator, ExpandAccessorMacros{var}, { });
325+
326+
return var->getAccessor(AccessorKind::Init);
327+
}
328+
286329
ArrayRef<VarDecl *>
287330
InitAccessorPropertiesRequest::evaluate(Evaluator &evaluator,
288331
NominalTypeDecl *decl) const {
332+
IterableDeclContext *implDecl = decl->getImplementationContext();
333+
334+
if (!hasStoredProperties(decl, implDecl))
335+
return ArrayRef<VarDecl *>();
336+
337+
// Make sure we expand what we need to to get all of the properties.
338+
computeLoweredProperties(decl, implDecl, LoweredPropertiesReason::Memberwise);
339+
289340
SmallVector<VarDecl *, 4> results;
290341
for (auto *member : decl->getMembers()) {
291342
auto *var = dyn_cast<VarDecl>(member);
292-
if (!var || !var->getAccessor(AccessorKind::Init)) {
343+
if (!var || var->isStatic() || !hasInitAccessor(var)) {
293344
continue;
294345
}
295346

stdlib/public/Observation/Sources/Observation/Observable.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
#endif
2424
@attached(memberAttribute)
2525
@attached(conformance)
26-
public macro Observable() =
26+
public macro Observable() =
2727
#externalMacro(module: "ObservationMacros", type: "ObservableMacro")
2828

2929
@available(SwiftStdlib 5.9, *)
30-
@attached(accessor)
30+
@attached(accessor, names: named(init), named(get), named(set))
3131
#if OBSERVATION_SUPPORTS_PEER_MACROS
3232
@attached(peer, names: prefixed(_))
3333
#endif

test/ModuleInterface/Observable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: %target-swift-emit-module-interface(%t/Library.swiftinterface) %s -module-name Library -plugin-path %swift-host-lib-dir/plugins -disable-availability-checking
2+
// RUN: %target-swift-emit-module-interface(%t/Library.swiftinterface) %s -module-name Library -plugin-path %swift-host-lib-dir/plugins -disable-availability-checking -enable-experimental-feature InitAccessors
33
// RUN: %target-swift-typecheck-module-from-interface(%t/Library.swiftinterface) -module-name Library -disable-availability-checking
44
// RUN: %FileCheck %s < %t/Library.swiftinterface
55

test/stdlib/Observation/Observable.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func validateMemberwiseInitializers() {
3535
@Observable
3636
struct DefiniteInitialization {
3737
var field: Int
38-
38+
3939
init(field: Int) {
4040
self.field = field
4141
}
@@ -62,21 +62,21 @@ class ContainsIUO {
6262
}
6363

6464
class NonObservable {
65-
65+
6666
}
6767

6868
@Observable
6969
class InheritsFromNonObservable: NonObservable {
70-
70+
7171
}
7272

7373
protocol NonObservableProtocol {
74-
74+
7575
}
7676

7777
@Observable
7878
class ConformsToNonObservableProtocol: NonObservableProtocol {
79-
79+
8080
}
8181

8282
struct NonObservableContainer {
@@ -91,19 +91,19 @@ class ImplementsAccessAndMutation {
9191
var field = 3
9292
let accessCalled: (PartialKeyPath<ImplementsAccessAndMutation>) -> Void
9393
let withMutationCalled: (PartialKeyPath<ImplementsAccessAndMutation>) -> Void
94-
94+
9595
init(accessCalled: @escaping (PartialKeyPath<ImplementsAccessAndMutation>) -> Void, withMutationCalled: @escaping (PartialKeyPath<ImplementsAccessAndMutation>) -> Void) {
9696
self.accessCalled = accessCalled
9797
self.withMutationCalled = withMutationCalled
9898
}
99-
99+
100100
internal func access<Member>(
101101
keyPath: KeyPath<ImplementsAccessAndMutation , Member>
102102
) {
103103
accessCalled(keyPath)
104104
_$observationRegistrar.access(self, keyPath: keyPath)
105105
}
106-
106+
107107
internal func withMutation<Member, T>(
108108
keyPath: KeyPath<ImplementsAccessAndMutation , Member>,
109109
_ mutation: () throws -> T
@@ -128,7 +128,7 @@ class Entity {
128128
class Person : Entity {
129129
var firstName = ""
130130
var lastName = ""
131-
131+
132132
var friends = [Person]()
133133

134134
var fullName: String { firstName + " " + lastName }
@@ -165,7 +165,7 @@ class HasIntermediaryConformance: Intermediary { }
165165

166166
class CapturedState<State>: @unchecked Sendable {
167167
var state: State
168-
168+
169169
init(state: State) {
170170
self.state = state
171171
}

0 commit comments

Comments
 (0)