Skip to content

[CodeSynthesis] Make sure that init synthesis expands macros #67721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 140 additions & 96 deletions lib/Sema/CodeSynthesis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,45 @@ static void diagnoseMissingRequiredInitializer(
diag::required_initializer_here);
}

/// FIXME: This is temporary until we come up with a way to overcome circularity
/// issues.
///
/// This method is intended to be used only in places that expect
/// lazy and property wrapper backing storage synthesis has happened
/// or can tolerate absence of such properties.
///
/// \param typeDecl The nominal type to enumerate current properties and their
/// auxiliary vars for.
///
/// \param callback The callback to be called for each property and auxiliary
/// var associated with the given type. The callback should return `true` to
/// indicate that enumeration should continue and `false` otherwise.
///
/// \returns true which indicates "failure" if callback returns `false`
/// at least once.
static bool enumerateCurrentPropertiesAndAuxiliaryVars(
NominalTypeDecl *typeDecl, llvm::function_ref<bool(VarDecl *)> callback) {
for (auto *member :
typeDecl->getImplementationContext()->getCurrentMembers()) {
if (auto *var = dyn_cast<VarDecl>(member)) {
if (!callback(var))
return true;
}

bool hadErrors = false;
member->visitAuxiliaryDecls([&](Decl *auxDecl) {
if (auto *auxVar = dyn_cast<VarDecl>(auxDecl)) {
hadErrors |= !callback(auxVar);
}
});

if (hadErrors)
return true;
}

return false;
}

bool AreAllStoredPropertiesDefaultInitableRequest::evaluate(
Evaluator &evaluator, NominalTypeDecl *decl) const {
assert(!hasClangImplementation(decl));
Expand All @@ -849,62 +888,68 @@ bool AreAllStoredPropertiesDefaultInitableRequest::evaluate(
decl->collectPropertiesInitializableByInitAccessors(
initializedViaInitAccessor);

for (auto member : decl->getImplementationContext()->getMembers()) {
// If a stored property lacks an initial value and if there is no way to
// synthesize an initial value (e.g. for an optional) then we suppress
// generation of the default initializer.
if (auto pbd = dyn_cast<PatternBindingDecl>(member)) {
// Static variables are irrelevant.
if (pbd->isStatic()) {
continue;
}
llvm::SmallPtrSet<PatternBindingDecl *, 4> checked;
return !enumerateCurrentPropertiesAndAuxiliaryVars(
decl, [&](VarDecl *property) {
auto *pbd = property->getParentPatternBinding();
if (!pbd || !checked.insert(pbd).second)
return true;

// If a stored property lacks an initial value and if there is no way to
// synthesize an initial value (e.g. for an optional) then we suppress
// generation of the default initializer.

// Static variables are irrelevant.
if (pbd->isStatic())
return true;

for (auto idx : range(pbd->getNumPatternEntries())) {
bool HasStorage = false;
bool CheckDefaultInitializer = true;
pbd->getPattern(idx)->forEachVariable([&HasStorage,
&CheckDefaultInitializer,
&initializedViaInitAccessor](
VarDecl *VD) {
// If one of the bound variables is @NSManaged, go ahead no matter
// what.
if (VD->getAttrs().hasAttribute<NSManagedAttr>())
CheckDefaultInitializer = false;

// If this property is covered by one or more init accessor(s)
// check whether at least one of them is initializable.
auto initAccessorProperties =
llvm::make_range(initializedViaInitAccessor.equal_range(VD));
if (llvm::any_of(initAccessorProperties, [&](const auto &entry) {
auto *property = entry.second->getParentPatternBinding();
return property->isInitialized(0) ||
property->isDefaultInitializable();
}))
return;

if (VD->hasStorageOrWrapsStorage())
HasStorage = true;

// Treat an init accessor property that doesn't initialize other
// properties as stored for initialization purposes.
if (auto *initAccessor = VD->getAccessor(AccessorKind::Init)) {
HasStorage |= initAccessor->getInitializedProperties().empty();
}
});

for (auto idx : range(pbd->getNumPatternEntries())) {
bool HasStorage = false;
bool CheckDefaultInitializer = true;
pbd->getPattern(idx)->forEachVariable(
[&HasStorage, &CheckDefaultInitializer,
&initializedViaInitAccessor](VarDecl *VD) {
// If one of the bound variables is @NSManaged, go ahead no matter
// what.
if (VD->getAttrs().hasAttribute<NSManagedAttr>())
CheckDefaultInitializer = false;

// If this property is covered by one or more init accessor(s)
// check whether at least one of them is initializable.
auto initAccessorProperties =
llvm::make_range(initializedViaInitAccessor.equal_range(VD));
if (llvm::any_of(initAccessorProperties, [&](const auto &entry) {
auto *property =
entry.second->getParentPatternBinding();
return property->isInitialized(0) ||
property->isDefaultInitializable();
}))
return;

if (VD->hasStorageOrWrapsStorage())
HasStorage = true;

// Treat an init accessor property that doesn't initialize other
// properties as stored for initialization purposes.
if (auto *initAccessor = VD->getAccessor(AccessorKind::Init)) {
HasStorage |= initAccessor->getInitializedProperties().empty();
}
});

if (!HasStorage) continue;

if (pbd->isInitialized(idx)) continue;

// If we cannot default initialize the property, we cannot
// synthesize a default initializer for the class.
if (CheckDefaultInitializer && !pbd->isDefaultInitializable())
return false;
}
}
}
if (!HasStorage)
return true;

return true;
if (pbd->isInitialized(idx))
return true;

// If we cannot default initialize the property, we cannot
// synthesize a default initializer for the class.
if (CheckDefaultInitializer && !pbd->isDefaultInitializable()) {
return false;
}
}
return true;
});
}

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

for (auto *member : decl->getMembers()) {
if (auto *var = dyn_cast<VarDecl>(member)) {
// If this is a backing storage property for a property wrapper,
// skip it.
if (var->getOriginalWrappedProperty())
continue;

if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true))
continue;
if (enumerateCurrentPropertiesAndAuxiliaryVars(decl, [&](VarDecl *var) {
if (var->isStatic())
return true;

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

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

// Record all of the properties initialized by calling init accessor.
auto properties = initAccessor->getInitializedProperties();
initializedProperties.insert(var);
initializedProperties.insert(properties.begin(), properties.end());
continue;
}

switch (initializedViaAccessor.count(var)) {
// Not covered by an init accessor.
case 0:
initializedProperties.insert(var);
continue;
// Check whether use of init accessors results in access to
// uninitialized properties.
if (auto *initAccessor = var->getAccessor(AccessorKind::Init)) {
// Make sure that all properties accessed by init accessor
// are previously initialized.
for (auto *property : initAccessor->getAccessedProperties()) {
if (!initializedProperties.count(property))
invalidOrderings.push_back({var, property->getName()});
}

// Covered by a single init accessor, we'll handle that
// once we get to the property with init accessor.
case 1:
continue;
// Record all of the properties initialized by calling init accessor.
auto properties = initAccessor->getInitializedProperties();
initializedProperties.insert(var);
initializedProperties.insert(properties.begin(), properties.end());
return true;
}

// Covered by more than one init accessor which means that we
// cannot synthesize memberwise initializer due to intersecting
// initializations.
default:
return false;
}
}
}
switch (initializedViaAccessor.count(var)) {
// Not covered by an init accessor.
case 0:
initializedProperties.insert(var);
return true;

// Covered by a single init accessor, we'll handle that
// once we get to the property with init accessor.
case 1:
return true;

// Covered by more than one init accessor which means that we
// cannot synthesize memberwise initializer due to intersecting
// initializations.
default:
return false;
}
}))
return false;

if (invalidOrderings.empty())
return !initializedProperties.empty();
Expand Down
21 changes: 21 additions & 0 deletions test/Macros/Inputs/syntax_macro_definitions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1928,3 +1928,24 @@ public struct NestedMagicLiteralMacro: ExpressionMacro {
"""
}
}

public struct InitWithProjectedValueWrapperMacro: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
return [
"""
private var _value: Wrapper
var _$value: Wrapper {
@storageRestrictions(initializes: _value)
init {
self._value = newValue
}
get { _value }
}
"""
]
}
}
31 changes: 31 additions & 0 deletions test/Macros/init_accessor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// RUN: %empty-directory(%t)
// 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
// RUN: %target-typecheck-verify-swift -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -DTEST_DIAGNOSTICS -swift-version 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
// RUN: %target-codesign %t/main
// RUN: %target-run %t/main

// REQUIRES: swift_swift_parser, executable_test

@attached(peer, names: named(_value), named(_$value))
macro Wrapped() = #externalMacro(module: "MacroDefinition",
type: "InitWithProjectedValueWrapperMacro")

@propertyWrapper
struct Wrapper {
var wrappedValue: Int {
1
}
var projectedValue: Wrapper {
self
}
}

struct Test {
@Wrapped
var value: Int { 1 }
}

let test = Test(_$value: Wrapper())
print(test.value)
// CHECK: 1