Skip to content

Commit c219452

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 32cf39a commit c219452

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?
@@ -6073,13 +6119,10 @@ class VarDecl : public AbstractStorageDecl {
60736119
/// precisely point to the variable type because of type aliases.
60746120
SourceRange getTypeSourceRangeForDiagnostics() const;
60756121

6076-
/// Returns whether the var is settable in the specified context: this
6077-
/// is either because it is a stored var, because it has a custom setter, or
6078-
/// is a let member in an initializer.
6079-
///
6080-
/// Pass a null context and null base to check if it's always settable.
6081-
bool isSettable(const DeclContext *UseDC,
6082-
const DeclRefExpr *base = nullptr) const;
6122+
/// Determine the mutability of this variable declaration when
6123+
/// accessed from a given declaration context.
6124+
StorageMutability mutability(const DeclContext *useDC,
6125+
const DeclRefExpr *base = nullptr) const;
60836126

60846127
/// Return the parent pattern binding that may provide an initializer for this
60856128
/// VarDecl. This returns null if there is none associated with the VarDecl.
@@ -9348,26 +9391,6 @@ findGenericParameterReferences(const ValueDecl *value, CanGenericSignature sig,
93489391
bool treatNonResultCovarianceAsInvariant,
93499392
std::optional<unsigned> skipParamIndex);
93509393

9351-
inline bool AbstractStorageDecl::isSettable(const DeclContext *UseDC,
9352-
const DeclRefExpr *base) const {
9353-
if (auto vd = dyn_cast<VarDecl>(this))
9354-
return vd->isSettable(UseDC, base);
9355-
9356-
auto sd = cast<SubscriptDecl>(this);
9357-
return sd->supportsMutation();
9358-
}
9359-
9360-
inline bool
9361-
AbstractStorageDecl::isSettableInSwift(const DeclContext *UseDC,
9362-
const DeclRefExpr *base) const {
9363-
// TODO: Writing to an optional storage requirement is not supported in Swift.
9364-
if (getAttrs().hasAttribute<OptionalAttr>()) {
9365-
return false;
9366-
}
9367-
9368-
return isSettable(UseDC, base);
9369-
}
9370-
93719394
inline void
93729395
AbstractStorageDecl::overwriteSetterAccess(AccessLevel accessLevel) {
93739396
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,
@@ -7267,32 +7295,39 @@ Type VarDecl::getTypeInContext() const {
72677295
return getDeclContext()->mapTypeIntoContext(getInterfaceType());
72687296
}
72697297

7298+
/// Translate an "is mutable" bit into a StorageMutability value.
7299+
static StorageMutability storageIsMutable(bool isMutable) {
7300+
return isMutable ? StorageMutability::Mutable
7301+
: StorageMutability::Immutable;
7302+
}
7303+
72707304
/// Returns whether the var is settable in the specified context: this
72717305
/// is either because it is a stored var, because it has a custom setter, or
72727306
/// is a let member in an initializer.
7273-
bool VarDecl::isSettable(const DeclContext *UseDC,
7274-
const DeclRefExpr *base) const {
7307+
StorageMutability
7308+
VarDecl::mutability(const DeclContext *UseDC,
7309+
const DeclRefExpr *base) const {
72757310
// Parameters are settable or not depending on their ownership convention.
72767311
if (auto *PD = dyn_cast<ParamDecl>(this))
7277-
return !PD->isImmutableInFunctionBody();
7312+
return storageIsMutable(!PD->isImmutableInFunctionBody());
72787313

72797314
// If this is a 'var' decl, then we're settable if we have storage or a
72807315
// setter.
72817316
if (!isLet()) {
72827317
if (hasInitAccessor()) {
72837318
if (auto *ctor = dyn_cast_or_null<ConstructorDecl>(UseDC)) {
72847319
if (base && ctor->getImplicitSelfDecl() != base->getDecl())
7285-
return supportsMutation();
7286-
return true;
7320+
return storageIsMutable(supportsMutation());
7321+
return StorageMutability::Initializable;
72877322
}
72887323
}
72897324

7290-
return supportsMutation();
7325+
return storageIsMutable(supportsMutation());
72917326
}
72927327

72937328
// Static 'let's are always immutable.
72947329
if (isStatic()) {
7295-
return false;
7330+
return StorageMutability::Immutable;
72967331
}
72977332

72987333
//
@@ -7302,11 +7337,11 @@ bool VarDecl::isSettable(const DeclContext *UseDC,
73027337

73037338
// Debugger expression 'let's are initialized through a side-channel.
73047339
if (isDebuggerVar())
7305-
return false;
7340+
return StorageMutability::Immutable;
73067341

73077342
// 'let's are only ever settable from a specific DeclContext.
73087343
if (UseDC == nullptr)
7309-
return false;
7344+
return StorageMutability::Immutable;
73107345

73117346
// 'let' properties in structs/classes are only ever settable in their
73127347
// designated initializer(s) or by init accessors.
@@ -7318,61 +7353,64 @@ bool VarDecl::isSettable(const DeclContext *UseDC,
73187353
// Check whether this property is part of `initializes` list,
73197354
// and allow assignment/mutation if so. DI would be responsible
73207355
// for checking for re-assignment.
7321-
return accessor->isInitAccessor() &&
7356+
if (accessor->isInitAccessor() &&
73227357
llvm::is_contained(accessor->getInitializedProperties(),
7323-
const_cast<VarDecl *>(this));
7358+
const_cast<VarDecl *>(this)))
7359+
return StorageMutability::Initializable;
7360+
7361+
return StorageMutability::Immutable;
73247362
}
73257363

73267364
auto *CD = dyn_cast<ConstructorDecl>(UseDC);
7327-
if (!CD) return false;
7328-
7365+
if (!CD) return StorageMutability::Immutable;
7366+
73297367
auto *CDC = CD->getDeclContext();
73307368

73317369
// 'let' properties are not valid inside protocols.
73327370
if (CDC->getExtendedProtocolDecl())
7333-
return false;
7371+
return StorageMutability::Immutable;
73347372

73357373
// If this init is defined inside of the same type (or in an extension
73367374
// thereof) as the let property, then it is mutable.
73377375
if (CDC->getSelfNominalTypeDecl() !=
73387376
getDeclContext()->getSelfNominalTypeDecl())
7339-
return false;
7377+
return StorageMutability::Immutable;
73407378

73417379
if (base && CD->getImplicitSelfDecl() != base->getDecl())
7342-
return false;
7380+
return StorageMutability::Immutable;
73437381

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

7351-
return true;
7389+
return StorageMutability::Initializable;
73527390
}
73537391

73547392
// If the 'let' has a value bound to it but has no PBD, then it is
73557393
// already initializedand not settable.
73567394
if (getParentPatternBinding() == nullptr)
7357-
return false;
7395+
return StorageMutability::Immutable;
73587396

73597397
// If the 'let' has an explicitly written initializer with a pattern binding,
73607398
// then it isn't settable.
73617399
if (isParentInitialized())
7362-
return false;
7400+
return StorageMutability::Immutable;
73637401

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

73717409
if (isa<TopLevelCodeDecl>(UseDC) &&
73727410
getDeclContext() == UseDC->getParent())
7373-
return true;
7411+
return StorageMutability::Initializable;
73747412

7375-
return false;
7413+
return StorageMutability::Immutable;
73767414
}
73777415

73787416
bool VarDecl::isLazilyInitializedGlobal() const {

0 commit comments

Comments
 (0)