[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:
Hamish Knight
2025-11-13 11:54:26 +00:00
parent e383766e93
commit 45683f6561
20 changed files with 903 additions and 111 deletions

View File

@@ -273,6 +273,15 @@ static_assert(uint8_t(SelfAccessKind::LastSelfAccessKind) <
"Self Access Kind is too small to fit in SelfAccess kind bits. "
"Please expand ");
enum class MemberwiseInitKind {
/// The regular memberwise initializer.
Regular,
/// A compatibility memberwise initializer that includes all private
/// initialized variables. This only exists for migration purposes, and will
/// be removed in a future language mode.
Compatibility
};
enum class UsingSpecifier : uint8_t {
MainActor,
Nonisolated,
@@ -4634,9 +4643,10 @@ public:
ArrayRef<VarDecl *> getInitAccessorProperties() const;
/// Return a collection of all properties that will be part of the memberwise
/// initializer.
ArrayRef<VarDecl *> getMemberwiseInitProperties() const;
/// initializer.
ArrayRef<VarDecl *>
getMemberwiseInitProperties(MemberwiseInitKind initKind) const;
/// Establish a mapping between properties that could be iniitalized
/// via other properties by means of init accessors. This mapping is
/// one-to-many because we allow intersecting `initializes(...)`.
@@ -4679,11 +4689,11 @@ public:
}
/// Whether this declaration has a synthesized memberwise initializer.
bool hasMemberwiseInitializer() const;
bool hasMemberwiseInitializer(MemberwiseInitKind initKind) const;
/// Retrieves the synthesized memberwise initializer for this declaration,
/// or \c nullptr if it does not have one.
ConstructorDecl *getMemberwiseInitializer() const;
ConstructorDecl *getMemberwiseInitializer(MemberwiseInitKind initKind) const;
/// Retrieves the effective memberwise initializer for this declaration, or
/// \c nullptr if it does not have one.
@@ -6967,7 +6977,9 @@ public:
/// actual declared property (which may or may not be considered "stored"
/// as the moment) to the backing storage property. Otherwise, the stored
/// backing property will be treated as the member-initialized property.
bool isMemberwiseInitialized(bool preferDeclaredProperties) const;
bool isMemberwiseInitialized(
MemberwiseInitKind initKind, bool preferDeclaredProperties,
std::optional<AccessLevel> minAccess = std::nullopt) const;
/// Return the range of semantics attributes attached to this VarDecl.
auto getSemanticsAttrs() const
@@ -7728,6 +7740,7 @@ public:
enum class SILSynthesizeKind {
None,
MemberwiseInitializer,
CompatibilityMemberwiseInitializer,
DistributedActorFactory
// This enum currently needs to fit in a 2-bit bitfield.
@@ -8100,11 +8113,19 @@ public:
/// Note that this is a memberwise initializer and thus the body will be
/// generated by SILGen.
void setIsMemberwiseInitializer() {
void setIsMemberwiseInitializer(MemberwiseInitKind initKind) {
assert(getBodyKind() == BodyKind::None);
assert(isa<ConstructorDecl>(this));
setBodyKind(BodyKind::SILSynthesize);
setSILSynthesizeKind(SILSynthesizeKind::MemberwiseInitializer);
switch (initKind) {
case MemberwiseInitKind::Regular:
setSILSynthesizeKind(SILSynthesizeKind::MemberwiseInitializer);
break;
case MemberwiseInitKind::Compatibility:
setSILSynthesizeKind(
SILSynthesizeKind::CompatibilityMemberwiseInitializer);
break;
}
}
/// Mark that the body should be filled in to be a factory method for creating
@@ -8140,9 +8161,20 @@ public:
/// typechecking.
bool isBodySkipped() const;
bool isMemberwiseInitializer() const {
return getBodyKind() == BodyKind::SILSynthesize
&& getSILSynthesizeKind() == SILSynthesizeKind::MemberwiseInitializer;
/// Checks whether this is a memberwise initializer decl, and if so the kind,
/// otherwise \c std::nullopt.
std::optional<MemberwiseInitKind> isMemberwiseInitializer() const {
if (getBodyKind() != BodyKind::SILSynthesize)
return std::nullopt;
switch (getSILSynthesizeKind()) {
case SILSynthesizeKind::MemberwiseInitializer:
return MemberwiseInitKind::Regular;
case SILSynthesizeKind::CompatibilityMemberwiseInitializer:
return MemberwiseInitKind::Compatibility;
default:
return std::nullopt;
}
}
/// Determines whether this function represents a distributed actor
@@ -10196,9 +10228,22 @@ getAccessorNameForDiagnostic(AccessorDecl *accessor, bool article,
StringRef getAccessorNameForDiagnostic(AccessorKind accessorKind, bool article,
bool underscored);
/// Retrieve a textual representation for a memberwise initializer in a given
/// nominal decl.
void printMemberwiseInit(NominalTypeDecl *nominal, llvm::raw_ostream &out);
inline void simple_display(llvm::raw_ostream &out,
MemberwiseInitKind initKind) {
switch (initKind) {
case MemberwiseInitKind::Regular:
out << "regular";
break;
case MemberwiseInitKind::Compatibility:
out << "compatibility";
break;
}
}
/// Retrieve a textual representation for a particular kind of memberwise
/// initializer in a given nominal decl.
void printMemberwiseInit(NominalTypeDecl *nominal, MemberwiseInitKind initKind,
llvm::raw_ostream &out);
void simple_display(llvm::raw_ostream &out,
OptionSet<NominalTypeDecl::LookupDirectFlags> options);

View File

@@ -2706,6 +2706,15 @@ ERROR(spi_only_imports_not_enabled, none,
"'@_spiOnly' requires setting the frontend flag '-experimental-spi-only-imports'",
())
WARNING(warn_use_of_compat_memberwise_init,Deprecation,
"synthesized memberwise initializer no longer includes "
"%select{private properties with initial values|%1}0; uses of it "
"will be an error in a future Swift language mode", (bool, StringRef))
NOTE(insert_compat_memberwise_init,none,
"insert an explicit implementation of the memberwise initializer",())
NOTE(compat_memberwise_init_used_here,none,
"memberwise initializer used here",())
// Access level on imports
ERROR(access_level_on_import_unsupported, none,
"The access level %0 is unsupported on imports: "

View File

@@ -23,6 +23,9 @@ class Decl;
/// Extra information associated with a source file that is lazily created and
/// stored in a separately-allocated side structure.
struct SourceFileExtras {
/// Set of compatibility memberwise initializers that we have emitted a
/// warning for.
llvm::DenseSet<const ConstructorDecl *> DiagnosedCompatMemberwiseInits;
};
}

View File

@@ -1990,10 +1990,11 @@ public:
/// Request to obtain a list of properties that will be reflected in the parameters of a
/// memberwise initializer.
class MemberwiseInitPropertiesRequest :
public SimpleRequest<MemberwiseInitPropertiesRequest,
ArrayRef<VarDecl *>(NominalTypeDecl *),
RequestFlags::Cached> {
class MemberwiseInitPropertiesRequest
: public SimpleRequest<MemberwiseInitPropertiesRequest,
ArrayRef<VarDecl *>(NominalTypeDecl *,
MemberwiseInitKind),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;
@@ -2001,8 +2002,27 @@ private:
friend SimpleRequest;
// Evaluation.
ArrayRef<VarDecl *>
evaluate(Evaluator &evaluator, NominalTypeDecl *decl) const;
ArrayRef<VarDecl *> evaluate(Evaluator &evaluator, NominalTypeDecl *decl,
MemberwiseInitKind initKind) const;
public:
bool isCached() const { return true; }
};
/// The maximum access level the memberwise initializer can be for a given
/// nominal decl.
class MemberwiseInitMaxAccessLevel
: public SimpleRequest<MemberwiseInitMaxAccessLevel,
AccessLevel(NominalTypeDecl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;
private:
friend SimpleRequest;
// Evaluation.
AccessLevel evaluate(Evaluator &evaluator, NominalTypeDecl *nominal) const;
public:
bool isCached() const { return true; }
@@ -2843,7 +2863,8 @@ public:
/// Checks whether this type has a synthesized memberwise initializer.
class HasMemberwiseInitRequest
: public SimpleRequest<HasMemberwiseInitRequest, bool(StructDecl *),
: public SimpleRequest<HasMemberwiseInitRequest,
bool(StructDecl *, MemberwiseInitKind),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;
@@ -2852,7 +2873,8 @@ private:
friend SimpleRequest;
// Evaluation.
bool evaluate(Evaluator &evaluator, StructDecl *decl) const;
bool evaluate(Evaluator &evaluator, StructDecl *decl,
MemberwiseInitKind initKind) const;
public:
// Caching.
@@ -2862,7 +2884,8 @@ public:
/// Synthesizes a memberwise initializer for a given type.
class SynthesizeMemberwiseInitRequest
: public SimpleRequest<SynthesizeMemberwiseInitRequest,
ConstructorDecl *(NominalTypeDecl *),
ConstructorDecl *(NominalTypeDecl *,
MemberwiseInitKind),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;
@@ -2871,7 +2894,8 @@ private:
friend SimpleRequest;
// Evaluation.
ConstructorDecl *evaluate(Evaluator &evaluator, NominalTypeDecl *decl) const;
ConstructorDecl *evaluate(Evaluator &evaluator, NominalTypeDecl *decl,
MemberwiseInitKind initKind) const;
public:
// Caching.

View File

@@ -342,7 +342,10 @@ SWIFT_REQUEST(TypeChecker, InitAccessorPropertiesRequest,
ArrayRef<VarDecl *>(NominalTypeDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, MemberwiseInitPropertiesRequest,
ArrayRef<VarDecl *>(NominalTypeDecl *),
ArrayRef<VarDecl *>(NominalTypeDecl *, MemberwiseInitKind),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, MemberwiseInitMaxAccessLevel,
AccessLevel(NominalTypeDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, StructuralTypeRequest, Type(TypeAliasDecl *), Cached,
NoLocationInfo)
@@ -394,7 +397,7 @@ SWIFT_REQUEST(TypeChecker, AreAllStoredPropertiesDefaultInitableRequest,
SWIFT_REQUEST(TypeChecker, HasUserDefinedDesignatedInitRequest,
bool(NominalTypeDecl *), Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, HasMemberwiseInitRequest,
bool(StructDecl *), Cached, NoLocationInfo)
bool(StructDecl *, MemberwiseInitKind), Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, BraceHasExplicitReturnStmtRequest,
bool(const BraceStmt *),
Cached, NoLocationInfo)
@@ -434,7 +437,8 @@ SWIFT_REQUEST(TypeChecker, SPIGroupsRequest,
llvm::ArrayRef<Identifier>(Decl *),
SeparatelyCached | SplitCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, SynthesizeMemberwiseInitRequest,
ConstructorDecl *(NominalTypeDecl *), Cached, NoLocationInfo)
ConstructorDecl *(NominalTypeDecl *, MemberwiseInitKind),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ResolveEffectiveMemberwiseInitRequest,
ConstructorDecl *(NominalTypeDecl *), Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, HasDefaultInitRequest,

View File

@@ -533,6 +533,10 @@ EXPERIMENTAL_FEATURE(DefaultIsolationPerFile, false)
/// Enable @_lifetime attribute
SUPPRESSIBLE_EXPERIMENTAL_FEATURE(Lifetimes, true)
/// Excludes '(file)private' properties with initial values from the memberwise
/// initializer if there is a more accessible initializable property.
EXPERIMENTAL_FEATURE(ExcludePrivateFromMemberwiseInit, true)
/// Allow macro based aliases to be imported into Swift
EXPERIMENTAL_FEATURE(ImportMacroAliases, true)