Skip to content

Commit 05ac2b3

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 c6077ee commit 05ac2b3

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
@@ -7099,6 +7099,9 @@ ERROR(macro_accessor_missing_from_expansion,none,
70997099
"expansion of macro %0 did not produce a %select{non-|}1observing "
71007100
"accessor",
71017101
(DeclName, bool))
7102+
ERROR(macro_init_accessor_not_documented,none,
7103+
"expansion of macro %0 produced an unexpected 'init' accessor",
7104+
(DeclName))
71027105

71037106
ERROR(macro_resolve_circular_reference, none,
71047107
"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
@@ -1272,6 +1272,19 @@ bool swift::accessorMacroOnlyIntroducesObservers(
12721272
return false;
12731273
}
12741274

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

1297-
if (accessor->isObservingAccessor())
1298-
continue;
1311+
if (accessor->isInitAccessor())
1312+
foundInitAccessor = true;
12991313

1300-
foundNonObservingAccessor = true;
1314+
if (!accessor->isObservingAccessor())
1315+
foundNonObservingAccessor = true;
13011316
}
13021317

13031318
auto roleAttr = macro->getMacroRoleAttr(MacroRole::Accessor);
@@ -1321,6 +1336,14 @@ Optional<unsigned> swift::expandAccessors(
13211336
!expectedNonObservingAccessor);
13221337
}
13231338

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

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
@@ -33,7 +33,7 @@ func validateMemberwiseInitializers() {
3333
@Observable
3434
struct DefiniteInitialization {
3535
var field: Int
36-
36+
3737
init(field: Int) {
3838
self.field = field
3939
}
@@ -60,21 +60,21 @@ class ContainsIUO {
6060
}
6161

6262
class NonObservable {
63-
63+
6464
}
6565

6666
@Observable
6767
class InheritsFromNonObservable: NonObservable {
68-
68+
6969
}
7070

7171
protocol NonObservableProtocol {
72-
72+
7373
}
7474

7575
@Observable
7676
class ConformsToNonObservableProtocol: NonObservableProtocol {
77-
77+
7878
}
7979

8080
struct NonObservableContainer {
@@ -89,19 +89,19 @@ class ImplementsAccessAndMutation {
8989
var field = 3
9090
let accessCalled: (PartialKeyPath<ImplementsAccessAndMutation>) -> Void
9191
let withMutationCalled: (PartialKeyPath<ImplementsAccessAndMutation>) -> Void
92-
92+
9393
init(accessCalled: @escaping (PartialKeyPath<ImplementsAccessAndMutation>) -> Void, withMutationCalled: @escaping (PartialKeyPath<ImplementsAccessAndMutation>) -> Void) {
9494
self.accessCalled = accessCalled
9595
self.withMutationCalled = withMutationCalled
9696
}
97-
97+
9898
internal func access<Member>(
9999
keyPath: KeyPath<ImplementsAccessAndMutation , Member>
100100
) {
101101
accessCalled(keyPath)
102102
_$observationRegistrar.access(self, keyPath: keyPath)
103103
}
104-
104+
105105
internal func withMutation<Member, T>(
106106
keyPath: KeyPath<ImplementsAccessAndMutation , Member>,
107107
_ mutation: () throws -> T
@@ -126,7 +126,7 @@ class Entity {
126126
class Person : Entity {
127127
var firstName = ""
128128
var lastName = ""
129-
129+
130130
var friends = [Person]()
131131

132132
var fullName: String { firstName + " " + lastName }
@@ -163,7 +163,7 @@ class HasIntermediaryConformance: Intermediary { }
163163

164164
class CapturedState<State>: @unchecked Sendable {
165165
var state: State
166-
166+
167167
init(state: State) {
168168
self.state = state
169169
}

0 commit comments

Comments
 (0)