Skip to content

Commit a0573e0

Browse files
committed
[CodeSynthesis] Make sure that init synthesis expands macros
Previously both `AreAllStoredPropertiesDefaultInitableRequest` and `HasMemberwiseInitRequest` checked only "current" properties of the type but macro expansions can add new stored and/or init accessor properties that affect this logic so we need to make sure that macro expansions happen and new properties are accounted for. Note that the original idea was to use `getStoredProperties()` but that runs into multiple circularity issues related to lazy properties. Resolves: rdar://112153201
1 parent 5ba4ce5 commit a0573e0

File tree

3 files changed

+192
-96
lines changed

3 files changed

+192
-96
lines changed

lib/Sema/CodeSynthesis.cpp

Lines changed: 140 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,45 @@ static void diagnoseMissingRequiredInitializer(
841841
diag::required_initializer_here);
842842
}
843843

844+
/// FIXME: This is temporary until we come up with a way to overcome circularity
845+
/// issues.
846+
///
847+
/// This method is intended to be used only in places that expect
848+
/// lazy and property wrapper backing storage synthesis has happened
849+
/// or can tolerate absence of such properties.
850+
///
851+
/// \param typeDecl The nominal type to enumerate current properties and their
852+
/// auxiliary vars for.
853+
///
854+
/// \param callback The callback to be called for each property and auxiliary
855+
/// var associated with the given type. The callback should return `true` to
856+
/// indicate that enumeration should continue and `false` otherwise.
857+
///
858+
/// \returns true which indicates "failure" if callback returns `false`
859+
/// at least once.
860+
static bool enumerateCurrentPropertiesAndAuxiliaryVars(
861+
NominalTypeDecl *typeDecl, llvm::function_ref<bool(VarDecl *)> callback) {
862+
for (auto *member :
863+
typeDecl->getImplementationContext()->getCurrentMembers()) {
864+
if (auto *var = dyn_cast<VarDecl>(member)) {
865+
if (!callback(var))
866+
return true;
867+
}
868+
869+
bool hadErrors = false;
870+
member->visitAuxiliaryDecls([&](Decl *auxDecl) {
871+
if (auto *auxVar = dyn_cast<VarDecl>(auxDecl)) {
872+
hadErrors |= !callback(auxVar);
873+
}
874+
});
875+
876+
if (hadErrors)
877+
return true;
878+
}
879+
880+
return false;
881+
}
882+
844883
bool AreAllStoredPropertiesDefaultInitableRequest::evaluate(
845884
Evaluator &evaluator, NominalTypeDecl *decl) const {
846885
assert(!hasClangImplementation(decl));
@@ -849,62 +888,68 @@ bool AreAllStoredPropertiesDefaultInitableRequest::evaluate(
849888
decl->collectPropertiesInitializableByInitAccessors(
850889
initializedViaInitAccessor);
851890

852-
for (auto member : decl->getImplementationContext()->getMembers()) {
853-
// If a stored property lacks an initial value and if there is no way to
854-
// synthesize an initial value (e.g. for an optional) then we suppress
855-
// generation of the default initializer.
856-
if (auto pbd = dyn_cast<PatternBindingDecl>(member)) {
857-
// Static variables are irrelevant.
858-
if (pbd->isStatic()) {
859-
continue;
860-
}
891+
llvm::SmallPtrSet<PatternBindingDecl *, 4> checked;
892+
return !enumerateCurrentPropertiesAndAuxiliaryVars(
893+
decl, [&](VarDecl *property) {
894+
auto *pbd = property->getParentPatternBinding();
895+
if (!pbd || !checked.insert(pbd).second)
896+
return true;
897+
898+
// If a stored property lacks an initial value and if there is no way to
899+
// synthesize an initial value (e.g. for an optional) then we suppress
900+
// generation of the default initializer.
901+
902+
// Static variables are irrelevant.
903+
if (pbd->isStatic())
904+
return true;
905+
906+
for (auto idx : range(pbd->getNumPatternEntries())) {
907+
bool HasStorage = false;
908+
bool CheckDefaultInitializer = true;
909+
pbd->getPattern(idx)->forEachVariable([&HasStorage,
910+
&CheckDefaultInitializer,
911+
&initializedViaInitAccessor](
912+
VarDecl *VD) {
913+
// If one of the bound variables is @NSManaged, go ahead no matter
914+
// what.
915+
if (VD->getAttrs().hasAttribute<NSManagedAttr>())
916+
CheckDefaultInitializer = false;
917+
918+
// If this property is covered by one or more init accessor(s)
919+
// check whether at least one of them is initializable.
920+
auto initAccessorProperties =
921+
llvm::make_range(initializedViaInitAccessor.equal_range(VD));
922+
if (llvm::any_of(initAccessorProperties, [&](const auto &entry) {
923+
auto *property = entry.second->getParentPatternBinding();
924+
return property->isInitialized(0) ||
925+
property->isDefaultInitializable();
926+
}))
927+
return;
928+
929+
if (VD->hasStorageOrWrapsStorage())
930+
HasStorage = true;
931+
932+
// Treat an init accessor property that doesn't initialize other
933+
// properties as stored for initialization purposes.
934+
if (auto *initAccessor = VD->getAccessor(AccessorKind::Init)) {
935+
HasStorage |= initAccessor->getInitializedProperties().empty();
936+
}
937+
});
861938

862-
for (auto idx : range(pbd->getNumPatternEntries())) {
863-
bool HasStorage = false;
864-
bool CheckDefaultInitializer = true;
865-
pbd->getPattern(idx)->forEachVariable(
866-
[&HasStorage, &CheckDefaultInitializer,
867-
&initializedViaInitAccessor](VarDecl *VD) {
868-
// If one of the bound variables is @NSManaged, go ahead no matter
869-
// what.
870-
if (VD->getAttrs().hasAttribute<NSManagedAttr>())
871-
CheckDefaultInitializer = false;
872-
873-
// If this property is covered by one or more init accessor(s)
874-
// check whether at least one of them is initializable.
875-
auto initAccessorProperties =
876-
llvm::make_range(initializedViaInitAccessor.equal_range(VD));
877-
if (llvm::any_of(initAccessorProperties, [&](const auto &entry) {
878-
auto *property =
879-
entry.second->getParentPatternBinding();
880-
return property->isInitialized(0) ||
881-
property->isDefaultInitializable();
882-
}))
883-
return;
884-
885-
if (VD->hasStorageOrWrapsStorage())
886-
HasStorage = true;
887-
888-
// Treat an init accessor property that doesn't initialize other
889-
// properties as stored for initialization purposes.
890-
if (auto *initAccessor = VD->getAccessor(AccessorKind::Init)) {
891-
HasStorage |= initAccessor->getInitializedProperties().empty();
892-
}
893-
});
894-
895-
if (!HasStorage) continue;
896-
897-
if (pbd->isInitialized(idx)) continue;
898-
899-
// If we cannot default initialize the property, we cannot
900-
// synthesize a default initializer for the class.
901-
if (CheckDefaultInitializer && !pbd->isDefaultInitializable())
902-
return false;
903-
}
904-
}
905-
}
939+
if (!HasStorage)
940+
return true;
906941

907-
return true;
942+
if (pbd->isInitialized(idx))
943+
return true;
944+
945+
// If we cannot default initialize the property, we cannot
946+
// synthesize a default initializer for the class.
947+
if (CheckDefaultInitializer && !pbd->isDefaultInitializable()) {
948+
return false;
949+
}
950+
}
951+
return true;
952+
});
908953
}
909954

910955
static bool areAllStoredPropertiesDefaultInitializable(Evaluator &eval,
@@ -1311,53 +1356,52 @@ HasMemberwiseInitRequest::evaluate(Evaluator &evaluator,
13111356
llvm::SmallPtrSet<VarDecl *, 4> initializedProperties;
13121357
llvm::SmallVector<std::pair<VarDecl *, Identifier>> invalidOrderings;
13131358

1314-
for (auto *member : decl->getMembers()) {
1315-
if (auto *var = dyn_cast<VarDecl>(member)) {
1316-
// If this is a backing storage property for a property wrapper,
1317-
// skip it.
1318-
if (var->getOriginalWrappedProperty())
1319-
continue;
1320-
1321-
if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true))
1322-
continue;
1359+
if (enumerateCurrentPropertiesAndAuxiliaryVars(decl, [&](VarDecl *var) {
1360+
if (var->isStatic())
1361+
return true;
13231362

1324-
// Check whether use of init accessors results in access to uninitialized
1325-
// properties.
1363+
if (var->getOriginalWrappedProperty())
1364+
return true;
13261365

1327-
if (auto *initAccessor = var->getAccessor(AccessorKind::Init)) {
1328-
// Make sure that all properties accessed by init accessor
1329-
// are previously initialized.
1330-
for (auto *property : initAccessor->getAccessedProperties()) {
1331-
if (!initializedProperties.count(property))
1332-
invalidOrderings.push_back({var, property->getName()});
1333-
}
1366+
if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true))
1367+
return true;
13341368

1335-
// Record all of the properties initialized by calling init accessor.
1336-
auto properties = initAccessor->getInitializedProperties();
1337-
initializedProperties.insert(var);
1338-
initializedProperties.insert(properties.begin(), properties.end());
1339-
continue;
1340-
}
1341-
1342-
switch (initializedViaAccessor.count(var)) {
1343-
// Not covered by an init accessor.
1344-
case 0:
1345-
initializedProperties.insert(var);
1346-
continue;
1369+
// Check whether use of init accessors results in access to
1370+
// uninitialized properties.
1371+
if (auto *initAccessor = var->getAccessor(AccessorKind::Init)) {
1372+
// Make sure that all properties accessed by init accessor
1373+
// are previously initialized.
1374+
for (auto *property : initAccessor->getAccessedProperties()) {
1375+
if (!initializedProperties.count(property))
1376+
invalidOrderings.push_back({var, property->getName()});
1377+
}
13471378

1348-
// Covered by a single init accessor, we'll handle that
1349-
// once we get to the property with init accessor.
1350-
case 1:
1351-
continue;
1379+
// Record all of the properties initialized by calling init accessor.
1380+
auto properties = initAccessor->getInitializedProperties();
1381+
initializedProperties.insert(var);
1382+
initializedProperties.insert(properties.begin(), properties.end());
1383+
return true;
1384+
}
13521385

1353-
// Covered by more than one init accessor which means that we
1354-
// cannot synthesize memberwise initializer due to intersecting
1355-
// initializations.
1356-
default:
1357-
return false;
1358-
}
1359-
}
1360-
}
1386+
switch (initializedViaAccessor.count(var)) {
1387+
// Not covered by an init accessor.
1388+
case 0:
1389+
initializedProperties.insert(var);
1390+
return true;
1391+
1392+
// Covered by a single init accessor, we'll handle that
1393+
// once we get to the property with init accessor.
1394+
case 1:
1395+
return true;
1396+
1397+
// Covered by more than one init accessor which means that we
1398+
// cannot synthesize memberwise initializer due to intersecting
1399+
// initializations.
1400+
default:
1401+
return false;
1402+
}
1403+
}))
1404+
return false;
13611405

13621406
if (invalidOrderings.empty())
13631407
return !initializedProperties.empty();

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1928,3 +1928,24 @@ public struct NestedMagicLiteralMacro: ExpressionMacro {
19281928
"""
19291929
}
19301930
}
1931+
1932+
public struct InitWithProjectedValueWrapperMacro: PeerMacro {
1933+
public static func expansion(
1934+
of node: AttributeSyntax,
1935+
providingPeersOf declaration: some DeclSyntaxProtocol,
1936+
in context: some MacroExpansionContext
1937+
) throws -> [DeclSyntax] {
1938+
return [
1939+
"""
1940+
private var _value: Wrapper
1941+
var _$value: Wrapper {
1942+
@storageRestrictions(initializes: _value)
1943+
init {
1944+
self._value = newValue
1945+
}
1946+
get { _value }
1947+
}
1948+
"""
1949+
]
1950+
}
1951+
}

test/Macros/init_accessor.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %empty-directory(%t)
2+
// 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
3+
// RUN: %target-typecheck-verify-swift -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -DTEST_DIAGNOSTICS -swift-version 5
4+
// RUN: %target-build-swift -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) %s -o %t/main -module-name MacroUser -swift-version 5
5+
// RUN: %target-codesign %t/main
6+
// RUN: %target-run %t/main
7+
8+
// REQUIRES: swift_swift_parser, executable_test
9+
10+
@attached(peer, names: named(_value), named(_$value))
11+
macro Wrapped() = #externalMacro(module: "MacroDefinition",
12+
type: "InitWithProjectedValueWrapperMacro")
13+
14+
@propertyWrapper
15+
struct Wrapper {
16+
var wrappedValue: Int {
17+
1
18+
}
19+
var projectedValue: Wrapper {
20+
self
21+
}
22+
}
23+
24+
struct Test {
25+
@Wrapped
26+
var value: Int { 1 }
27+
}
28+
29+
let test = Test(_$value: Wrapper())
30+
print(test.value)
31+
// CHECK: 1

0 commit comments

Comments
 (0)