Skip to content

Commit fc59689

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 281207f commit fc59689

File tree

3 files changed

+182
-91
lines changed

3 files changed

+182
-91
lines changed

lib/Sema/CodeSynthesis.cpp

Lines changed: 134 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,44 @@ 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 : typeDecl->getCurrentMembers()) {
863+
if (auto *var = dyn_cast<VarDecl>(member)) {
864+
if (!callback(var))
865+
return true;
866+
}
867+
868+
bool hadErrors = false;
869+
member->visitAuxiliaryDecls([&](Decl *auxDecl) {
870+
if (auto *auxVar = dyn_cast<VarDecl>(auxDecl)) {
871+
hadErrors |= !callback(auxVar);
872+
}
873+
});
874+
875+
if (hadErrors)
876+
return true;
877+
}
878+
879+
return false;
880+
}
881+
844882
bool AreAllStoredPropertiesDefaultInitableRequest::evaluate(
845883
Evaluator &evaluator, NominalTypeDecl *decl) const {
846884
assert(!hasClangImplementation(decl));
@@ -849,55 +887,61 @@ bool AreAllStoredPropertiesDefaultInitableRequest::evaluate(
849887
decl->collectPropertiesInitializableByInitAccessors(
850888
initializedViaInitAccessor);
851889

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-
}
861-
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-
}))
882-
return;
883-
884-
if (VD->hasStorageOrWrapsStorage())
885-
HasStorage = true;
886-
});
887-
888-
if (!HasStorage) continue;
889-
890-
if (pbd->isInitialized(idx)) continue;
891-
892-
// If we cannot default initialize the property, we cannot
893-
// synthesize a default initializer for the class.
894-
if (CheckDefaultInitializer && !pbd->isDefaultInitializable())
895-
return false;
896-
}
897-
}
898-
}
899-
900-
return true;
890+
llvm::SmallPtrSet<PatternBindingDecl *, 4> checked;
891+
return !enumerateCurrentPropertiesAndAuxiliaryVars(
892+
decl, [&](VarDecl *property) {
893+
auto *pbd = property->getParentPatternBinding();
894+
if (!pbd || !checked.insert(pbd).second)
895+
return true;
896+
897+
// If a stored property lacks an initial value and if there is no way to
898+
// synthesize an initial value (e.g. for an optional) then we suppress
899+
// generation of the default initializer.
900+
901+
// Static variables are irrelevant.
902+
if (pbd->isStatic())
903+
return true;
904+
905+
for (auto idx : range(pbd->getNumPatternEntries())) {
906+
bool HasStorage = false;
907+
bool CheckDefaultInitializer = true;
908+
pbd->getPattern(idx)->forEachVariable([&HasStorage,
909+
&CheckDefaultInitializer,
910+
&initializedViaInitAccessor](
911+
VarDecl *VD) {
912+
// If one of the bound variables is @NSManaged, go ahead no matter
913+
// what.
914+
if (VD->getAttrs().hasAttribute<NSManagedAttr>())
915+
CheckDefaultInitializer = false;
916+
917+
// If this property is covered by one or more init accessor(s)
918+
// check whether at least one of them is initializable.
919+
auto initAccessorProperties =
920+
llvm::make_range(initializedViaInitAccessor.equal_range(VD));
921+
if (llvm::any_of(initAccessorProperties, [&](const auto &entry) {
922+
auto *property = entry.second->getParentPatternBinding();
923+
return property->isInitialized(0);
924+
}))
925+
return;
926+
927+
if (VD->hasStorageOrWrapsStorage())
928+
HasStorage = true;
929+
});
930+
931+
if (!HasStorage)
932+
return true;
933+
934+
if (pbd->isInitialized(idx))
935+
return true;
936+
937+
// If we cannot default initialize the property, we cannot
938+
// synthesize a default initializer for the class.
939+
if (CheckDefaultInitializer && !pbd->isDefaultInitializable()) {
940+
return false;
941+
}
942+
}
943+
return true;
944+
});
901945
}
902946

903947
static bool areAllStoredPropertiesDefaultInitializable(Evaluator &eval,
@@ -1304,53 +1348,52 @@ HasMemberwiseInitRequest::evaluate(Evaluator &evaluator,
13041348
llvm::SmallPtrSet<VarDecl *, 4> initializedProperties;
13051349
llvm::SmallVector<std::pair<VarDecl *, Identifier>> invalidOrderings;
13061350

1307-
for (auto *member : decl->getMembers()) {
1308-
if (auto *var = dyn_cast<VarDecl>(member)) {
1309-
// If this is a backing storage property for a property wrapper,
1310-
// skip it.
1311-
if (var->getOriginalWrappedProperty())
1312-
continue;
1313-
1314-
if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true))
1315-
continue;
1351+
if (enumerateCurrentPropertiesAndAuxiliaryVars(decl, [&](VarDecl *var) {
1352+
if (var->isStatic())
1353+
return true;
13161354

1317-
// Check whether use of init accessors results in access to uninitialized
1318-
// properties.
1355+
if (var->getOriginalWrappedProperty())
1356+
return true;
13191357

1320-
if (auto *initAccessor = var->getAccessor(AccessorKind::Init)) {
1321-
// Make sure that all properties accessed by init accessor
1322-
// are previously initialized.
1323-
for (auto *property : initAccessor->getAccessedProperties()) {
1324-
if (!initializedProperties.count(property))
1325-
invalidOrderings.push_back({var, property->getName()});
1326-
}
1358+
if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true))
1359+
return true;
13271360

1328-
// Record all of the properties initialized by calling init accessor.
1329-
auto properties = initAccessor->getInitializedProperties();
1330-
initializedProperties.insert(var);
1331-
initializedProperties.insert(properties.begin(), properties.end());
1332-
continue;
1333-
}
1334-
1335-
switch (initializedViaAccessor.count(var)) {
1336-
// Not covered by an init accessor.
1337-
case 0:
1338-
initializedProperties.insert(var);
1339-
continue;
1361+
// Check whether use of init accessors results in access to
1362+
// uninitialized properties.
1363+
if (auto *initAccessor = var->getAccessor(AccessorKind::Init)) {
1364+
// Make sure that all properties accessed by init accessor
1365+
// are previously initialized.
1366+
for (auto *property : initAccessor->getAccessedProperties()) {
1367+
if (!initializedProperties.count(property))
1368+
invalidOrderings.push_back({var, property->getName()});
1369+
}
13401370

1341-
// Covered by a single init accessor, we'll handle that
1342-
// once we get to the property with init accessor.
1343-
case 1:
1344-
continue;
1371+
// Record all of the properties initialized by calling init accessor.
1372+
auto properties = initAccessor->getInitializedProperties();
1373+
initializedProperties.insert(var);
1374+
initializedProperties.insert(properties.begin(), properties.end());
1375+
return true;
1376+
}
13451377

1346-
// Covered by more than one init accessor which means that we
1347-
// cannot synthesize memberwise initializer due to intersecting
1348-
// initializations.
1349-
default:
1350-
return false;
1351-
}
1352-
}
1353-
}
1378+
switch (initializedViaAccessor.count(var)) {
1379+
// Not covered by an init accessor.
1380+
case 0:
1381+
initializedProperties.insert(var);
1382+
return true;
1383+
1384+
// Covered by a single init accessor, we'll handle that
1385+
// once we get to the property with init accessor.
1386+
case 1:
1387+
return true;
1388+
1389+
// Covered by more than one init accessor which means that we
1390+
// cannot synthesize memberwise initializer due to intersecting
1391+
// initializations.
1392+
default:
1393+
return false;
1394+
}
1395+
}))
1396+
return false;
13541397

13551398
if (invalidOrderings.empty())
13561399
return !initializedProperties.empty();

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,3 +1871,24 @@ public struct NestedMagicLiteralMacro: ExpressionMacro {
18711871
"""
18721872
}
18731873
}
1874+
1875+
public struct InitWithProjectedValueWrapperMacro: PeerMacro {
1876+
public static func expansion(
1877+
of node: AttributeSyntax,
1878+
providingPeersOf declaration: some DeclSyntaxProtocol,
1879+
in context: some MacroExpansionContext
1880+
) throws -> [DeclSyntax] {
1881+
return [
1882+
"""
1883+
private var _value: Wrapper
1884+
var _$value: Wrapper {
1885+
@storageRestrictions(initializes: _value)
1886+
init {
1887+
self._value = newValue
1888+
}
1889+
get { _value }
1890+
}
1891+
"""
1892+
]
1893+
}
1894+
}

test/Macros/init_accessor.swift

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

0 commit comments

Comments
 (0)