Skip to content

Commit b641c54

Browse files
committed
Generalize AbstractStorageDecl::isSettable() to return a three-state value
This operation determines whether a particular storage declaration, when accessed from a particular location, is mutable or not. It has a particular semantic that `let` declarations, when accessed from an initializer, are considered mutable even though they can only be assigned. There is similar logic for init accessors. Tease apart "truly mutable" from "initializable because we're in an initializer", introducing AbstractStorageDecl::mutability() to represent all three states. isSettable() remains available as a thin shim over mutability() and all clients are unchanged thus far, making this a no-op refactoring.
1 parent c20331b commit b641c54

File tree

2 files changed

+115
-54
lines changed

2 files changed

+115
-54
lines changed

include/swift/AST/Decl.h

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5574,6 +5574,21 @@ class BuiltinTupleDecl final : public NominalTypeDecl {
55745574
}
55755575
};
55765576

5577+
/// Describes whether a particular storage declaration is mutable.
5578+
enum class StorageMutability {
5579+
/// The storage is immutable, meaning that it can neither be assigned
5580+
/// to nor passed inout.
5581+
Immutable,
5582+
5583+
/// The storage is mutable, meaning that it can be assigned and pased
5584+
/// inout.
5585+
Mutable,
5586+
5587+
/// The storage is immutable, but can be asigned for the purposes of
5588+
/// initialization.
5589+
Initializable
5590+
};
5591+
55775592
/// AbstractStorageDecl - This is the common superclass for VarDecl and
55785593
/// SubscriptDecl, representing potentially settable memory locations.
55795594
class AbstractStorageDecl : public ValueDecl {
@@ -5745,8 +5760,31 @@ class AbstractStorageDecl : public ValueDecl {
57455760
/// Determine whether references to this storage declaration may appear
57465761
/// on the left-hand side of an assignment, as the operand of a
57475762
/// `&` or 'inout' operator, or as a component in a writable key path.
5748-
bool isSettable(const DeclContext *UseDC,
5749-
const DeclRefExpr *base = nullptr) const;
5763+
bool isSettable(const DeclContext *useDC,
5764+
const DeclRefExpr *base = nullptr) const {
5765+
switch (mutability(useDC, base)) {
5766+
case StorageMutability::Immutable:
5767+
return false;
5768+
case StorageMutability::Mutable:
5769+
case StorageMutability::Initializable:
5770+
return true;
5771+
}
5772+
}
5773+
5774+
/// Determine the mutability of this storage declaration when
5775+
/// accessed from a given declaration context.
5776+
StorageMutability mutability(const DeclContext *useDC,
5777+
const DeclRefExpr *base = nullptr) const;
5778+
5779+
/// Determine the mutability of this storage declaration when
5780+
/// accessed from a given declaration context in Swift.
5781+
///
5782+
/// This method differs only from 'mutability()' in its handling of
5783+
/// 'optional' storage requirements, which lack support for direct
5784+
/// writes in Swift.
5785+
StorageMutability mutabilityInSwift(
5786+
const DeclContext *useDC,
5787+
const DeclRefExpr *base = nullptr) const;
57505788

57515789
/// Determine whether references to this storage declaration in Swift may
57525790
/// appear on the left-hand side of an assignment, as the operand of a
@@ -5755,8 +5793,16 @@ class AbstractStorageDecl : public ValueDecl {
57555793
/// This method is equivalent to \c isSettable with the exception of
57565794
/// 'optional' storage requirements, which lack support for direct writes
57575795
/// in Swift.
5758-
bool isSettableInSwift(const DeclContext *UseDC,
5759-
const DeclRefExpr *base = nullptr) const;
5796+
bool isSettableInSwift(const DeclContext *useDC,
5797+
const DeclRefExpr *base = nullptr) const {
5798+
switch (mutabilityInSwift(useDC, base)) {
5799+
case StorageMutability::Immutable:
5800+
return false;
5801+
case StorageMutability::Mutable:
5802+
case StorageMutability::Initializable:
5803+
return true;
5804+
}
5805+
}
57605806

57615807
/// Does this storage declaration have explicitly-defined accessors
57625808
/// written in the source?
@@ -6069,13 +6115,10 @@ class VarDecl : public AbstractStorageDecl {
60696115
/// precisely point to the variable type because of type aliases.
60706116
SourceRange getTypeSourceRangeForDiagnostics() const;
60716117

6072-
/// Returns whether the var is settable in the specified context: this
6073-
/// is either because it is a stored var, because it has a custom setter, or
6074-
/// is a let member in an initializer.
6075-
///
6076-
/// Pass a null context and null base to check if it's always settable.
6077-
bool isSettable(const DeclContext *UseDC,
6078-
const DeclRefExpr *base = nullptr) const;
6118+
/// Determine the mutability of this variable declaration when
6119+
/// accessed from a given declaration context.
6120+
StorageMutability mutability(const DeclContext *useDC,
6121+
const DeclRefExpr *base = nullptr) const;
60796122

60806123
/// Return the parent pattern binding that may provide an initializer for this
60816124
/// VarDecl. This returns null if there is none associated with the VarDecl.
@@ -9337,26 +9380,6 @@ findGenericParameterReferences(const ValueDecl *value, CanGenericSignature sig,
93379380
bool treatNonResultCovarianceAsInvariant,
93389381
std::optional<unsigned> skipParamIndex);
93399382

9340-
inline bool AbstractStorageDecl::isSettable(const DeclContext *UseDC,
9341-
const DeclRefExpr *base) const {
9342-
if (auto vd = dyn_cast<VarDecl>(this))
9343-
return vd->isSettable(UseDC, base);
9344-
9345-
auto sd = cast<SubscriptDecl>(this);
9346-
return sd->supportsMutation();
9347-
}
9348-
9349-
inline bool
9350-
AbstractStorageDecl::isSettableInSwift(const DeclContext *UseDC,
9351-
const DeclRefExpr *base) const {
9352-
// TODO: Writing to an optional storage requirement is not supported in Swift.
9353-
if (getAttrs().hasAttribute<OptionalAttr>()) {
9354-
return false;
9355-
}
9356-
9357-
return isSettable(UseDC, base);
9358-
}
9359-
93609383
inline void
93619384
AbstractStorageDecl::overwriteSetterAccess(AccessLevel accessLevel) {
93629385
Accessors.setInt(accessLevel);

lib/AST/Decl.cpp

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3091,6 +3091,34 @@ bool AbstractStorageDecl::isSetterMutating() const {
30913091
IsSetterMutatingRequest{const_cast<AbstractStorageDecl *>(this)}, {});
30923092
}
30933093

3094+
StorageMutability
3095+
AbstractStorageDecl::mutability(const DeclContext *useDC,
3096+
const DeclRefExpr *base) const {
3097+
if (auto vd = dyn_cast<VarDecl>(this))
3098+
return vd->mutability(useDC, base);
3099+
3100+
auto sd = cast<SubscriptDecl>(this);
3101+
return sd->supportsMutation() ? StorageMutability::Mutable
3102+
: StorageMutability::Immutable;
3103+
}
3104+
3105+
/// Determine the mutability of this storage declaration when
3106+
/// accessed from a given declaration context in Swift.
3107+
///
3108+
/// This method differs only from 'mutability()' in its handling of
3109+
/// 'optional' storage requirements, which lack support for direct
3110+
/// writes in Swift.
3111+
StorageMutability
3112+
AbstractStorageDecl::mutabilityInSwift(const DeclContext *useDC,
3113+
const DeclRefExpr *base) const {
3114+
// TODO: Writing to an optional storage requirement is not supported in Swift.
3115+
if (getAttrs().hasAttribute<OptionalAttr>()) {
3116+
return StorageMutability::Immutable;
3117+
}
3118+
3119+
return mutability(useDC, base);
3120+
}
3121+
30943122
OpaqueReadOwnership AbstractStorageDecl::getOpaqueReadOwnership() const {
30953123
ASTContext &ctx = getASTContext();
30963124
return evaluateOrDefault(ctx.evaluator,
@@ -7263,32 +7291,39 @@ Type VarDecl::getTypeInContext() const {
72637291
return getDeclContext()->mapTypeIntoContext(getInterfaceType());
72647292
}
72657293

7294+
/// Translate an "is mutable" bit into a StorageMutability value.
7295+
static StorageMutability storageIsMutable(bool isMutable) {
7296+
return isMutable ? StorageMutability::Mutable
7297+
: StorageMutability::Immutable;
7298+
}
7299+
72667300
/// Returns whether the var is settable in the specified context: this
72677301
/// is either because it is a stored var, because it has a custom setter, or
72687302
/// is a let member in an initializer.
7269-
bool VarDecl::isSettable(const DeclContext *UseDC,
7270-
const DeclRefExpr *base) const {
7303+
StorageMutability
7304+
VarDecl::mutability(const DeclContext *UseDC,
7305+
const DeclRefExpr *base) const {
72717306
// Parameters are settable or not depending on their ownership convention.
72727307
if (auto *PD = dyn_cast<ParamDecl>(this))
7273-
return !PD->isImmutableInFunctionBody();
7308+
return storageIsMutable(!PD->isImmutableInFunctionBody());
72747309

72757310
// If this is a 'var' decl, then we're settable if we have storage or a
72767311
// setter.
72777312
if (!isLet()) {
72787313
if (hasInitAccessor()) {
72797314
if (auto *ctor = dyn_cast_or_null<ConstructorDecl>(UseDC)) {
72807315
if (base && ctor->getImplicitSelfDecl() != base->getDecl())
7281-
return supportsMutation();
7282-
return true;
7316+
return storageIsMutable(supportsMutation());
7317+
return StorageMutability::Initializable;
72837318
}
72847319
}
72857320

7286-
return supportsMutation();
7321+
return storageIsMutable(supportsMutation());
72877322
}
72887323

72897324
// Static 'let's are always immutable.
72907325
if (isStatic()) {
7291-
return false;
7326+
return StorageMutability::Immutable;
72927327
}
72937328

72947329
//
@@ -7298,11 +7333,11 @@ bool VarDecl::isSettable(const DeclContext *UseDC,
72987333

72997334
// Debugger expression 'let's are initialized through a side-channel.
73007335
if (isDebuggerVar())
7301-
return false;
7336+
return StorageMutability::Immutable;
73027337

73037338
// 'let's are only ever settable from a specific DeclContext.
73047339
if (UseDC == nullptr)
7305-
return false;
7340+
return StorageMutability::Immutable;
73067341

73077342
// 'let' properties in structs/classes are only ever settable in their
73087343
// designated initializer(s) or by init accessors.
@@ -7314,61 +7349,64 @@ bool VarDecl::isSettable(const DeclContext *UseDC,
73147349
// Check whether this property is part of `initializes` list,
73157350
// and allow assignment/mutation if so. DI would be responsible
73167351
// for checking for re-assignment.
7317-
return accessor->isInitAccessor() &&
7352+
if (accessor->isInitAccessor() &&
73187353
llvm::is_contained(accessor->getInitializedProperties(),
7319-
const_cast<VarDecl *>(this));
7354+
const_cast<VarDecl *>(this)))
7355+
return StorageMutability::Initializable;
7356+
7357+
return StorageMutability::Immutable;
73207358
}
73217359

73227360
auto *CD = dyn_cast<ConstructorDecl>(UseDC);
7323-
if (!CD) return false;
7324-
7361+
if (!CD) return StorageMutability::Immutable;
7362+
73257363
auto *CDC = CD->getDeclContext();
73267364

73277365
// 'let' properties are not valid inside protocols.
73287366
if (CDC->getExtendedProtocolDecl())
7329-
return false;
7367+
return StorageMutability::Immutable;
73307368

73317369
// If this init is defined inside of the same type (or in an extension
73327370
// thereof) as the let property, then it is mutable.
73337371
if (CDC->getSelfNominalTypeDecl() !=
73347372
getDeclContext()->getSelfNominalTypeDecl())
7335-
return false;
7373+
return StorageMutability::Immutable;
73367374

73377375
if (base && CD->getImplicitSelfDecl() != base->getDecl())
7338-
return false;
7376+
return StorageMutability::Immutable;
73397377

73407378
// If this is a convenience initializer (i.e. one that calls
73417379
// self.init), then let properties are never mutable in it. They are
73427380
// only mutable in designated initializers.
73437381
auto initKindAndExpr = CD->getDelegatingOrChainedInitKind();
73447382
if (initKindAndExpr.initKind == BodyInitKind::Delegating)
7345-
return false;
7383+
return StorageMutability::Immutable;
73467384

7347-
return true;
7385+
return StorageMutability::Initializable;
73487386
}
73497387

73507388
// If the 'let' has a value bound to it but has no PBD, then it is
73517389
// already initializedand not settable.
73527390
if (getParentPatternBinding() == nullptr)
7353-
return false;
7391+
return StorageMutability::Immutable;
73547392

73557393
// If the 'let' has an explicitly written initializer with a pattern binding,
73567394
// then it isn't settable.
73577395
if (isParentInitialized())
7358-
return false;
7396+
return StorageMutability::Immutable;
73597397

73607398
// Normal lets (e.g. globals) are only mutable in the context of the
73617399
// declaration. To handle top-level code properly, we look through
73627400
// the TopLevelCode decl on the use (if present) since the vardecl may be
73637401
// one level up.
73647402
if (getDeclContext() == UseDC)
7365-
return true;
7403+
return StorageMutability::Initializable;
73667404

73677405
if (isa<TopLevelCodeDecl>(UseDC) &&
73687406
getDeclContext() == UseDC->getParent())
7369-
return true;
7407+
return StorageMutability::Initializable;
73707408

7371-
return false;
7409+
return StorageMutability::Immutable;
73727410
}
73737411

73747412
bool VarDecl::isLazilyInitializedGlobal() const {

0 commit comments

Comments
 (0)