mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[Sema] Exclude private initialized vars from memberwise initializer
Exclude properties with initial values from the memberwise initializer if they are less accessible than the most accessible property, up to `internal`. Introduce a compatibility overload that continues to include the same properties as before until the next language mode. This is gated behind the `ExcludePrivateFromMemberwiseInit` feature. rdar://122416579
This commit is contained in:
@@ -285,9 +285,11 @@ static ParamDecl *createMemberwiseInitParameter(DeclContext *DC,
|
||||
///
|
||||
/// \returns The newly-created constructor, which has already been type-checked
|
||||
/// (but has not been added to the containing struct or class).
|
||||
static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl,
|
||||
ImplicitConstructorKind ICK,
|
||||
ASTContext &ctx) {
|
||||
static ConstructorDecl *
|
||||
createImplicitConstructor(NominalTypeDecl *decl, ImplicitConstructorKind ICK,
|
||||
std::optional<MemberwiseInitKind> memberwiseKind,
|
||||
ASTContext &ctx) {
|
||||
ASSERT(ICK != ImplicitConstructorKind::Memberwise || memberwiseKind);
|
||||
assert(!decl->hasClangNode());
|
||||
|
||||
SourceLoc Loc = decl->getLoc();
|
||||
@@ -299,7 +301,7 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl,
|
||||
if (ICK == ImplicitConstructorKind::Memberwise) {
|
||||
assert(isa<StructDecl>(decl) && "Only struct have memberwise constructor");
|
||||
|
||||
for (auto var : decl->getMemberwiseInitProperties()) {
|
||||
for (auto var : decl->getMemberwiseInitProperties(memberwiseKind.value())) {
|
||||
accessLevel = std::min(accessLevel, var->getFormalAccess());
|
||||
params.push_back(createMemberwiseInitParameter(decl, Loc, var));
|
||||
}
|
||||
@@ -432,7 +434,7 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl,
|
||||
}
|
||||
|
||||
if (ICK == ImplicitConstructorKind::Memberwise) {
|
||||
ctor->setIsMemberwiseInitializer();
|
||||
ctor->setIsMemberwiseInitializer(memberwiseKind.value());
|
||||
|
||||
if (!ctx.LangOpts.hasFeature(Feature::IsolatedDefaultValues)) {
|
||||
addNonIsolatedToSynthesized(decl, ctor);
|
||||
@@ -1368,7 +1370,8 @@ void TypeChecker::addImplicitConstructors(NominalTypeDecl *decl) {
|
||||
|
||||
// Force the memberwise and default initializers if the type has them.
|
||||
// FIXME: We need to be more lazy about synthesizing constructors.
|
||||
(void)decl->getMemberwiseInitializer();
|
||||
(void)decl->getMemberwiseInitializer(MemberwiseInitKind::Regular);
|
||||
(void)decl->getMemberwiseInitializer(MemberwiseInitKind::Compatibility);
|
||||
(void)decl->getDefaultInitializer();
|
||||
}
|
||||
|
||||
@@ -1458,9 +1461,8 @@ ResolveImplicitMemberRequest::evaluate(Evaluator &evaluator,
|
||||
return std::make_tuple<>();
|
||||
}
|
||||
|
||||
bool
|
||||
HasMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
StructDecl *decl) const {
|
||||
bool HasMemberwiseInitRequest::evaluate(Evaluator &evaluator, StructDecl *decl,
|
||||
MemberwiseInitKind initKind) const {
|
||||
if (!shouldAttemptInitializerSynthesis(decl))
|
||||
return false;
|
||||
|
||||
@@ -1469,6 +1471,30 @@ HasMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
if (hasUserDefinedDesignatedInit(evaluator, decl))
|
||||
return false;
|
||||
|
||||
auto &ctx = decl->getASTContext();
|
||||
if (initKind == MemberwiseInitKind::Compatibility) {
|
||||
// Only generate the regular memberwise init if the feature is disabled.
|
||||
if (!ctx.LangOpts.hasFeature(Feature::ExcludePrivateFromMemberwiseInit))
|
||||
return false;
|
||||
|
||||
// In the next language mode we should stop creating the compatibility
|
||||
// initializer.
|
||||
using namespace version;
|
||||
if (ctx.isLanguageModeAtLeast(Version::getFutureMajorLanguageVersion()))
|
||||
return false;
|
||||
|
||||
// If there are no extra properties present for the compatibility
|
||||
// initializer we don't need to synthesize it. The compatibility initializer
|
||||
// is guaranteed to be a superset of the regular initializer so we can just
|
||||
// compare sizes.
|
||||
using Kind = MemberwiseInitKind;
|
||||
auto regularProps = decl->getMemberwiseInitProperties(Kind::Regular);
|
||||
auto compatProps = decl->getMemberwiseInitProperties(Kind::Compatibility);
|
||||
ASSERT(regularProps.size() <= compatProps.size() && "Must be superset");
|
||||
if (regularProps.size() == compatProps.size())
|
||||
return false;
|
||||
}
|
||||
|
||||
std::multimap<VarDecl *, VarDecl *> initializedViaAccessor;
|
||||
decl->collectPropertiesInitializableByInitAccessors(initializedViaAccessor);
|
||||
|
||||
@@ -1482,7 +1508,7 @@ HasMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
if (var->getOriginalWrappedProperty())
|
||||
return true;
|
||||
|
||||
if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true))
|
||||
if (!var->isMemberwiseInitialized(initKind, /*preferDeclared=*/true))
|
||||
return true;
|
||||
|
||||
// Check whether use of init accessors results in access to
|
||||
@@ -1526,16 +1552,14 @@ HasMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
return !initializedProperties.empty();
|
||||
|
||||
{
|
||||
auto &diags = decl->getASTContext().Diags;
|
||||
|
||||
diags.diagnose(
|
||||
ctx.Diags.diagnose(
|
||||
decl, diag::cannot_synthesize_memberwise_due_to_property_init_order);
|
||||
|
||||
for (const auto &invalid : invalidOrderings) {
|
||||
auto *accessor = invalid.first->getAccessor(AccessorKind::Init);
|
||||
diags.diagnose(accessor->getLoc(),
|
||||
diag::out_of_order_access_in_init_accessor,
|
||||
invalid.first->getName(), invalid.second);
|
||||
ctx.Diags.diagnose(accessor->getLoc(),
|
||||
diag::out_of_order_access_in_init_accessor,
|
||||
invalid.first->getName(), invalid.second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1544,11 +1568,20 @@ HasMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
|
||||
ConstructorDecl *
|
||||
SynthesizeMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
NominalTypeDecl *decl) const {
|
||||
NominalTypeDecl *decl,
|
||||
MemberwiseInitKind initKind) const {
|
||||
ASSERT(decl->hasMemberwiseInitializer(initKind));
|
||||
|
||||
// Create the implicit memberwise constructor.
|
||||
auto &ctx = decl->getASTContext();
|
||||
auto ctor =
|
||||
createImplicitConstructor(decl, ImplicitConstructorKind::Memberwise, ctx);
|
||||
auto ctor = createImplicitConstructor(
|
||||
decl, ImplicitConstructorKind::Memberwise, initKind, ctx);
|
||||
|
||||
// The diagnostic for the compatibility memberwise initializer assumes it's
|
||||
// fileprivate at most, make sure that's true.
|
||||
ASSERT(initKind != MemberwiseInitKind::Compatibility ||
|
||||
ctor->getFormalAccess() <= AccessLevel::FilePrivate);
|
||||
|
||||
decl->addMember(ctor);
|
||||
return ctor;
|
||||
}
|
||||
@@ -1556,6 +1589,14 @@ SynthesizeMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
ConstructorDecl *
|
||||
ResolveEffectiveMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
NominalTypeDecl *decl) const {
|
||||
// For now, use the compatibility memberwise initializer that includes all
|
||||
// private properties when ExcludePrivateFromMemberwiseInit is enabled.
|
||||
// FIXME: Can we switch to the regular?
|
||||
auto &ctx = decl->getASTContext();
|
||||
auto initKind = MemberwiseInitKind::Regular;
|
||||
if (ctx.LangOpts.hasFeature(Feature::ExcludePrivateFromMemberwiseInit))
|
||||
initKind = MemberwiseInitKind::Compatibility;
|
||||
|
||||
// Compute the access level for the memberwise initializer. The minimum of:
|
||||
// - Public, by default. This enables public nominal types to have public
|
||||
// memberwise initializers.
|
||||
@@ -1565,19 +1606,46 @@ ResolveEffectiveMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
// memberwise initializer causes a redeclaration error.
|
||||
// - The minimum access level of memberwise-initialized properties in the
|
||||
// nominal type declaration.
|
||||
// FIXME: This doesn't correctly handle init accessors.
|
||||
auto accessLevel = AccessLevel::Public;
|
||||
for (auto *member : decl->getMembers()) {
|
||||
auto *var = dyn_cast<VarDecl>(member);
|
||||
if (!var || !var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true))
|
||||
if (!var)
|
||||
continue;
|
||||
if (!var->isMemberwiseInitialized(initKind, /*preferDeclared=*/true))
|
||||
continue;
|
||||
|
||||
accessLevel = std::min(accessLevel, var->getFormalAccess());
|
||||
}
|
||||
auto &ctx = decl->getASTContext();
|
||||
|
||||
// If a memberwise initializer exists, set its access level and return it.
|
||||
if (auto *initDecl = decl->getMemberwiseInitializer()) {
|
||||
initDecl->overwriteAccess(accessLevel);
|
||||
return initDecl;
|
||||
// First try the compatibility initializer, then the regular if it contains
|
||||
// all the necessary properties.
|
||||
{
|
||||
auto *initDecl = [&]() -> ConstructorDecl * {
|
||||
using Kind = MemberwiseInitKind;
|
||||
|
||||
// If we don't have the feature just take the regular init.
|
||||
if (!ctx.LangOpts.hasFeature(Feature::ExcludePrivateFromMemberwiseInit))
|
||||
return decl->getMemberwiseInitializer(Kind::Regular);
|
||||
|
||||
// If we have the feature, we can always use the compat init if present.
|
||||
if (auto *init = decl->getMemberwiseInitializer(Kind::Compatibility))
|
||||
return init;
|
||||
|
||||
// If there is no compatibility init, check to see if the regular init
|
||||
// has all the necessary properties.
|
||||
auto regularProps = decl->getMemberwiseInitProperties(Kind::Regular);
|
||||
auto compatProps = decl->getMemberwiseInitProperties(Kind::Compatibility);
|
||||
if (regularProps.size() != compatProps.size())
|
||||
return nullptr;
|
||||
|
||||
return decl->getMemberwiseInitializer(Kind::Regular);
|
||||
}();
|
||||
if (initDecl) {
|
||||
initDecl->overwriteAccess(accessLevel);
|
||||
return initDecl;
|
||||
}
|
||||
}
|
||||
|
||||
auto isEffectiveMemberwiseInitializer = [&](ConstructorDecl *initDecl) {
|
||||
@@ -1628,7 +1696,7 @@ ResolveEffectiveMemberwiseInitRequest::evaluate(Evaluator &evaluator,
|
||||
// return it.
|
||||
if (!memberwiseInitDecl) {
|
||||
memberwiseInitDecl = createImplicitConstructor(
|
||||
decl, ImplicitConstructorKind::Memberwise, ctx);
|
||||
decl, ImplicitConstructorKind::Memberwise, initKind, ctx);
|
||||
memberwiseInitDecl->overwriteAccess(accessLevel);
|
||||
decl->addMember(memberwiseInitDecl);
|
||||
}
|
||||
@@ -1691,7 +1759,8 @@ SynthesizeDefaultInitRequest::evaluate(Evaluator &evaluator,
|
||||
auto ctorKind = decl->isDistributedActor() ?
|
||||
ImplicitConstructorKind::DefaultDistributedActor :
|
||||
ImplicitConstructorKind::Default;
|
||||
if (auto ctor = createImplicitConstructor(decl, ctorKind, ctx)) {
|
||||
if (auto ctor = createImplicitConstructor(
|
||||
decl, ctorKind, /*memberwiseKind*/ std::nullopt, ctx)) {
|
||||
// Add the constructor.
|
||||
decl->addMember(ctor);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user