blob: c874465c696bb61415f6786b6989f522f6ec3e41 [file] [log] [blame]
//===--- TypeCheckStorage.cpp - Checking Properties and Subscripts -------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements semantic analysis for properties, subscripts as well
// as their accessors.
//
//===----------------------------------------------------------------------===//
#include "CodeSynthesis.h"
#include "TypeChecker.h"
#include "TypeCheckDecl.h"
#include "TypeCheckType.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/DiagnosticsParse.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/Expr.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/Pattern.h"
#include "swift/AST/PropertyWrappers.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/AST/Types.h"
using namespace swift;
/// Set each bound variable in the pattern to have an error type.
void swift::setBoundVarsTypeError(Pattern *pattern, ASTContext &ctx) {
pattern->forEachVariable([&](VarDecl *var) {
// Don't change the type of a variable that we've been able to
// compute a type for.
if (var->hasType() && !var->getType()->hasError())
return;
var->markInvalid();
});
}
/// Build a default initializer for the given type.
Expr *TypeChecker::buildDefaultInitializer(Type type) {
// Default-initialize optional types and weak values to 'nil'.
if (type->getReferenceStorageReferent()->getOptionalObjectType())
return new (Context) NilLiteralExpr(SourceLoc(), /*Implicit=*/true);
// Build tuple literals for tuple types.
if (auto tupleType = type->getAs<TupleType>()) {
SmallVector<Expr *, 2> inits;
for (const auto &elt : tupleType->getElements()) {
if (elt.isVararg())
return nullptr;
auto eltInit = buildDefaultInitializer(elt.getType());
if (!eltInit)
return nullptr;
inits.push_back(eltInit);
}
return TupleExpr::createImplicit(Context, inits, { });
}
// We don't default-initialize anything else.
return nullptr;
}
/// Does the context allow pattern bindings that don't bind any variables?
static bool contextAllowsPatternBindingWithoutVariables(DeclContext *dc) {
// Property decls in type context must bind variables.
if (dc->isTypeContext())
return false;
// Global variable decls must bind variables, except in scripts.
if (dc->isModuleScopeContext()) {
if (dc->getParentSourceFile()
&& dc->getParentSourceFile()->isScriptMode())
return true;
return false;
}
return true;
}
static bool hasStoredProperties(NominalTypeDecl *decl) {
return (isa<StructDecl>(decl) ||
(isa<ClassDecl>(decl) && !decl->hasClangNode()));
}
static void computeLoweredStoredProperties(NominalTypeDecl *decl) {
// Just walk over the members of the type, forcing backing storage
// for lazy properties and property wrappers to be synthesized.
for (auto *member : decl->getMembers()) {
auto *var = dyn_cast<VarDecl>(member);
if (!var || var->isStatic())
continue;
if (var->getAttrs().hasAttribute<LazyAttr>())
(void) var->getLazyStorageProperty();
if (var->hasAttachedPropertyWrapper())
(void) var->getPropertyWrapperBackingProperty();
}
}
llvm::Expected<ArrayRef<VarDecl *>>
StoredPropertiesRequest::evaluate(Evaluator &evaluator,
NominalTypeDecl *decl) const {
if (!hasStoredProperties(decl))
return ArrayRef<VarDecl *>();
SmallVector<VarDecl *, 4> results;
// Unless we're in a source file we don't have to do anything
// special to lower lazy properties and property wrappers.
if (isa<SourceFile>(decl->getModuleScopeContext()))
computeLoweredStoredProperties(decl);
for (auto *member : decl->getMembers()) {
if (auto *var = dyn_cast<VarDecl>(member))
if (!var->isStatic() && var->hasStorage())
results.push_back(var);
}
return decl->getASTContext().AllocateCopy(results);
}
llvm::Expected<ArrayRef<Decl *>>
StoredPropertiesAndMissingMembersRequest::evaluate(Evaluator &evaluator,
NominalTypeDecl *decl) const {
if (!hasStoredProperties(decl))
return ArrayRef<Decl *>();
SmallVector<Decl *, 4> results;
// Unless we're in a source file we don't have to do anything
// special to lower lazy properties and property wrappers.
if (isa<SourceFile>(decl->getModuleScopeContext()))
computeLoweredStoredProperties(decl);
for (auto *member : decl->getMembers()) {
if (auto *var = dyn_cast<VarDecl>(member))
if (!var->isStatic() && var->hasStorage())
results.push_back(var);
if (auto missing = dyn_cast<MissingMemberDecl>(member))
if (missing->getNumberOfFieldOffsetVectorEntries() > 0)
results.push_back(missing);
}
return decl->getASTContext().AllocateCopy(results);
}
/// Validate the \c entryNumber'th entry in \c binding.
static void validatePatternBindingEntry(TypeChecker &tc,
PatternBindingDecl *binding,
unsigned entryNumber) {
// If the pattern already has a type, we're done.
if (binding->getPattern(entryNumber)->hasType())
return;
// Resolve the pattern.
auto *pattern = tc.resolvePattern(binding->getPattern(entryNumber),
binding->getDeclContext(),
/*isStmtCondition*/true);
if (!pattern) {
binding->setInvalid();
binding->getPattern(entryNumber)->setType(ErrorType::get(tc.Context));
return;
}
binding->setPattern(entryNumber, pattern,
binding->getPatternList()[entryNumber].getInitContext());
// Validate 'static'/'class' on properties in nominal type decls.
auto StaticSpelling = binding->getStaticSpelling();
if (StaticSpelling != StaticSpellingKind::None &&
isa<ExtensionDecl>(binding->getDeclContext())) {
if (auto *NTD = binding->getDeclContext()->getSelfNominalTypeDecl()) {
if (!isa<ClassDecl>(NTD)) {
if (StaticSpelling == StaticSpellingKind::KeywordClass) {
tc.diagnose(binding, diag::class_var_not_in_class, false)
.fixItReplace(binding->getStaticLoc(), "static");
tc.diagnose(NTD, diag::extended_type_declared_here);
}
}
}
}
// Check the pattern. We treat type-checking a PatternBindingDecl like
// type-checking an expression because that's how the initial binding is
// checked, and they have the same effect on the file's dependencies.
//
// In particular, it's /not/ correct to check the PBD's DeclContext because
// top-level variables in a script file are accessible from other files,
// even though the PBD is inside a TopLevelCodeDecl.
TypeResolutionOptions options(TypeResolverContext::PatternBindingDecl);
if (binding->isInitialized(entryNumber)) {
// If we have an initializer, we can also have unknown types.
options |= TypeResolutionFlags::AllowUnspecifiedTypes;
options |= TypeResolutionFlags::AllowUnboundGenerics;
}
if (tc.typeCheckPattern(pattern, binding->getDeclContext(), options)) {
setBoundVarsTypeError(pattern, tc.Context);
binding->setInvalid();
pattern->setType(ErrorType::get(tc.Context));
return;
}
// If the pattern didn't get a type or if it contains an unbound generic type,
// we'll need to check the initializer.
if (!pattern->hasType() || pattern->getType()->hasUnboundGenericType()) {
if (tc.typeCheckPatternBinding(binding, entryNumber))
return;
// A pattern binding at top level is not allowed to pick up another decl's
// opaque result type as its type by type inference.
if (!binding->getDeclContext()->isLocalContext()
&& binding->getInit(entryNumber)->getType()->hasOpaqueArchetype()) {
// TODO: Check whether the type is the pattern binding's own opaque type.
tc.diagnose(binding, diag::inferred_opaque_type,
binding->getInit(entryNumber)->getType());
}
}
// If the pattern binding appears in a type or library file context, then
// it must bind at least one variable.
if (!contextAllowsPatternBindingWithoutVariables(binding->getDeclContext())) {
llvm::SmallVector<VarDecl*, 2> vars;
binding->getPattern(entryNumber)->collectVariables(vars);
if (vars.empty()) {
// Selector for error message.
enum : unsigned {
Property,
GlobalVariable,
};
tc.diagnose(binding->getPattern(entryNumber)->getLoc(),
diag::pattern_binds_no_variables,
binding->getDeclContext()->isTypeContext()
? Property : GlobalVariable);
}
}
}
/// Validate the entries in the given pattern binding declaration.
void swift::validatePatternBindingEntries(TypeChecker &tc,
PatternBindingDecl *binding) {
if (binding->hasValidationStarted())
return;
DeclValidationRAII IBV(binding);
for (unsigned i = 0, e = binding->getNumPatternEntries(); i != e; ++i)
validatePatternBindingEntry(tc, binding, i);
}
llvm::Expected<bool>
IsGetterMutatingRequest::evaluate(Evaluator &evaluator,
AbstractStorageDecl *storage) const {
bool result = (!storage->isStatic() &&
doesContextHaveValueSemantics(storage->getDeclContext()));
// 'lazy' overrides the normal accessor-based rules and heavily
// restricts what accessors can be used. The getter is considered
// mutating if this is instance storage on a value type.
if (storage->getAttrs().hasAttribute<LazyAttr>()) {
return result;
}
// If we have an attached property wrapper, the getter's mutating-ness
// depends on the composition of the wrappers.
if (auto var = dyn_cast<VarDecl>(storage)) {
if (auto mut = var->getPropertyWrapperMutability()) {
return mut->Getter == PropertyWrapperMutability::Mutating
&& result;
}
}
auto checkMutability = [&](AccessorKind kind) -> bool {
auto *accessor = storage->getParsedAccessor(kind);
if (!accessor)
return false;
return accessor->isMutating();
};
// Protocol requirements are always written as '{ get }' or '{ get set }';
// the @_borrowed attribute determines if getReadImpl() becomes Get or Read.
if (isa<ProtocolDecl>(storage->getDeclContext()))
return checkMutability(AccessorKind::Get);
switch (storage->getReadImpl()) {
case ReadImplKind::Stored:
case ReadImplKind::Inherited:
return false;
case ReadImplKind::Get:
return checkMutability(AccessorKind::Get);
case ReadImplKind::Address:
return checkMutability(AccessorKind::Address);
case ReadImplKind::Read:
return checkMutability(AccessorKind::Read);
}
llvm_unreachable("bad impl kind");
}
llvm::Expected<bool>
IsSetterMutatingRequest::evaluate(Evaluator &evaluator,
AbstractStorageDecl *storage) const {
// By default, the setter is mutating if we have an instance member of a
// value type, but this can be overridden below.
bool result = (!storage->isStatic() &&
doesContextHaveValueSemantics(storage->getDeclContext()));
// If we have an attached property wrapper, the setter is mutating
// or not based on the composition of the wrappers.
if (auto var = dyn_cast<VarDecl>(storage)) {
if (auto mut = var->getPropertyWrapperMutability()) {
return mut->Setter == PropertyWrapperMutability::Mutating
&& result;
}
}
auto impl = storage->getImplInfo();
switch (impl.getWriteImpl()) {
case WriteImplKind::Immutable:
case WriteImplKind::Stored:
// Instance member setters are mutating; static property setters and
// top-level setters are not.
// It's important that we use this logic for "immutable" storage
// in order to handle initialization of let-properties.
return result;
case WriteImplKind::StoredWithObservers:
case WriteImplKind::InheritedWithObservers:
case WriteImplKind::Set: {
auto *setter = storage->getParsedAccessor(AccessorKind::Set);
if (setter)
result = setter->isMutating();
// As a special extra check, if the user also gave us a modify
// coroutine, check that it has the same mutatingness as the setter.
// TODO: arguably this should require the spelling to match even when
// it's the implied value.
auto modifyAccessor = storage->getParsedAccessor(AccessorKind::Modify);
if (impl.getReadWriteImpl() == ReadWriteImplKind::Modify &&
modifyAccessor != nullptr) {
auto modifyResult = modifyAccessor->isMutating();
if ((result || storage->isGetterMutating()) != modifyResult) {
modifyAccessor->diagnose(
diag::modify_mutatingness_differs_from_setter,
modifyResult ? SelfAccessKind::Mutating
: SelfAccessKind::NonMutating,
modifyResult ? SelfAccessKind::NonMutating
: SelfAccessKind::Mutating);
if (setter)
setter->diagnose(diag::previous_accessor, "setter", 0);
modifyAccessor->setInvalid();
}
}
return result;
}
case WriteImplKind::MutableAddress:
return storage->getParsedAccessor(AccessorKind::MutableAddress)
->isMutating();
case WriteImplKind::Modify:
return storage->getParsedAccessor(AccessorKind::Modify)
->isMutating();
}
llvm_unreachable("bad storage kind");
}
llvm::Expected<OpaqueReadOwnership>
OpaqueReadOwnershipRequest::evaluate(Evaluator &evaluator,
AbstractStorageDecl *storage) const {
return (storage->getAttrs().hasAttribute<BorrowedAttr>()
? OpaqueReadOwnership::Borrowed
: OpaqueReadOwnership::Owned);
}
/// Insert the specified decl into the DeclContext's member list. If the hint
/// decl is specified, the new decl is inserted next to the hint.
static void addMemberToContextIfNeeded(Decl *D, DeclContext *DC,
Decl *Hint = nullptr) {
if (auto *ntd = dyn_cast<NominalTypeDecl>(DC)) {
ntd->addMember(D, Hint);
} else if (auto *ed = dyn_cast<ExtensionDecl>(DC)) {
ed->addMember(D, Hint);
} else {
assert((DC->isLocalContext() || isa<FileUnit>(DC)) &&
"Unknown declcontext");
}
}
/// Build a parameter list which can forward the formal index parameters of a
/// declaration.
///
/// \param prefix optional arguments to be prefixed onto the index
/// forwarding pattern.
static ParameterList *
buildIndexForwardingParamList(AbstractStorageDecl *storage,
ArrayRef<ParamDecl*> prefix,
ASTContext &context) {
auto subscript = dyn_cast<SubscriptDecl>(storage);
// Fast path: if this isn't a subscript, just use whatever we have.
if (!subscript)
return ParameterList::create(context, prefix);
// Clone the parameter list over for a new decl, so we get new ParamDecls.
auto indices = subscript->getIndices()->clone(context,
ParameterList::Implicit);
// Give all of the parameters meaningless names so that we can forward
// them properly. If it's declared anonymously, SILGen will think
// it's unused.
// TODO: use some special DeclBaseName for this?
for (auto param : indices->getArray()) {
if (!param->hasName())
param->setName(context.getIdentifier("anonymous"));
assert(param->hasName());
}
if (prefix.empty())
return indices;
// Otherwise, we need to build up a new parameter list.
SmallVector<ParamDecl*, 4> elements;
// Start with the fields we were given, if there are any.
elements.append(prefix.begin(), prefix.end());
elements.append(indices->begin(), indices->end());
return ParameterList::create(context, elements);
}
/// Create the generic parameters needed for the given accessor, if any.
static GenericParamList *createAccessorGenericParams(
AbstractStorageDecl *storage) {
// Accessors of generic subscripts get a copy of the subscript's
// generic parameter list, because they're not nested inside the
// subscript.
if (auto *subscript = dyn_cast<SubscriptDecl>(storage)) {
if (auto genericParams = subscript->getGenericParams())
return genericParams->clone(subscript->getDeclContext());
}
return nullptr;
}
static bool doesAccessorHaveBody(AccessorDecl *accessor) {
// Protocol requirements don't have bodies.
//
// FIXME: Revisit this if we ever get 'real' default implementations.
if (isa<ProtocolDecl>(accessor->getDeclContext()))
return false;
auto *storage = accessor->getStorage();
// NSManaged getters and setters don't have bodies.
if (storage->getAttrs().hasAttribute<NSManagedAttr>())
if (accessor->isGetterOrSetter())
return false;
return true;
}
/// Build a reference to the subscript index variables for this subscript
/// accessor.
static Expr *buildSubscriptIndexReference(ASTContext &ctx,
AccessorDecl *accessor) {
// Pull out the body parameters, which we should have cloned
// previously to be forwardable. Drop the initial buffer/value
// parameter in accessors that have one.
auto params = accessor->getParameters()->getArray();
auto accessorKind = accessor->getAccessorKind();
// Ignore the value parameter of a setter.
if (accessorKind == AccessorKind::Set) {
params = params.slice(1);
}
// Okay, everything else should be forwarded, build the expression.
return buildArgumentForwardingExpr(params, ctx);
}
namespace {
enum class TargetImpl {
/// We're doing an ordinary storage reference.
Ordinary,
/// We're referencing the physical storage created for the storage.
Storage,
/// We're referencing this specific implementation of the storage, not
/// an override of it.
Implementation,
/// We're referencing the superclass's implementation of the storage.
Super,
/// We're referencing the backing property for a property with a wrapper
/// through the 'value' property.
Wrapper,
/// We're referencing the backing property for a property with a wrapper
/// through the 'projectedValue' property.
WrapperStorage,
};
} // end anonymous namespace
namespace {
/// Describes the information needed to perform property wrapper access via
/// the enclosing self.
struct EnclosingSelfPropertyWrapperAccess {
/// The (genreric) subscript that will be used to perform the access.
SubscriptDecl *subscript;
/// The property being accessed.
VarDecl *accessedProperty;
};
}
/// Determine whether the given property should be accessed via the enclosing-self access pattern.
static Optional<EnclosingSelfPropertyWrapperAccess>
getEnclosingSelfPropertyWrapperAccess(VarDecl *property, bool forProjected) {
// The enclosing-self pattern only applies to instance properties of
// classes.
if (!property->isInstanceMember())
return None;
auto classDecl = property->getDeclContext()->getSelfClassDecl();
if (!classDecl)
return None;
// The pattern currently only works with the outermost property wrapper.
Type outermostWrapperType = property->getPropertyWrapperBackingPropertyType();
if (!outermostWrapperType)
return None;
NominalTypeDecl *wrapperTypeDecl = outermostWrapperType->getAnyNominal();
if (!wrapperTypeDecl)
return None;
// Look for a generic subscript that fits the general form we need.
auto wrapperInfo = wrapperTypeDecl->getPropertyWrapperTypeInfo();
auto subscript =
forProjected ? wrapperInfo.enclosingInstanceProjectedSubscript
: wrapperInfo.enclosingInstanceWrappedSubscript;
if (!subscript)
return None;
EnclosingSelfPropertyWrapperAccess result;
result.subscript = subscript;
if (forProjected) {
result.accessedProperty =
property->getPropertyWrapperBackingPropertyInfo().storageWrapperVar;
} else {
result.accessedProperty = property;
}
return result;
}
/// Build an l-value for the storage of a declaration.
static Expr *buildStorageReference(AccessorDecl *accessor,
AbstractStorageDecl *storage,
TargetImpl target,
bool isLValue,
ASTContext &ctx) {
// Local function to "finish" the expression, creating a member reference
// to the given sequence of underlying variables.
Optional<EnclosingSelfPropertyWrapperAccess> enclosingSelfAccess;
llvm::TinyPtrVector<VarDecl *> underlyingVars;
auto finish = [&](Expr *result) -> Expr * {
for (auto underlyingVar : underlyingVars) {
auto subs = result->getType()
->getWithoutSpecifierType()
->getContextSubstitutionMap(
accessor->getParentModule(),
underlyingVar->getDeclContext());
ConcreteDeclRef memberRef(underlyingVar, subs);
auto *memberRefExpr = new (ctx) MemberRefExpr(
result, SourceLoc(), memberRef, DeclNameLoc(), /*Implicit=*/true);
auto type = underlyingVar->getValueInterfaceType().subst(subs);
if (isLValue)
type = LValueType::get(type);
memberRefExpr->setType(type);
result = memberRefExpr;
}
return result;
};
VarDecl *selfDecl = accessor->getImplicitSelfDecl();
AccessSemantics semantics;
SelfAccessorKind selfAccessKind;
Type selfTypeForAccess = (selfDecl ? selfDecl->getType() : Type());
auto *genericEnv = accessor->getGenericEnvironment();
SubstitutionMap subs;
if (genericEnv)
subs = genericEnv->getForwardingSubstitutionMap();
switch (target) {
case TargetImpl::Ordinary:
semantics = AccessSemantics::Ordinary;
selfAccessKind = SelfAccessorKind::Peer;
break;
case TargetImpl::Storage:
semantics = AccessSemantics::DirectToStorage;
selfAccessKind = SelfAccessorKind::Peer;
break;
case TargetImpl::Implementation:
semantics = AccessSemantics::DirectToImplementation;
selfAccessKind = SelfAccessorKind::Peer;
break;
case TargetImpl::Super:
// If this really is an override, use a super-access.
if (auto override = storage->getOverriddenDecl()) {
semantics = AccessSemantics::Ordinary;
selfAccessKind = SelfAccessorKind::Super;
auto *baseClass = override->getDeclContext()->getSelfClassDecl();
selfTypeForAccess = selfTypeForAccess->getSuperclassForDecl(baseClass);
subs =
selfTypeForAccess->getContextSubstitutionMap(
accessor->getParentModule(),
baseClass);
storage = override;
// Otherwise do a self-reference, which is dynamically bogus but
// should be statically valid. This should only happen in invalid cases.
} else {
semantics = AccessSemantics::Ordinary;
selfAccessKind = SelfAccessorKind::Peer;
}
break;
case TargetImpl::Wrapper: {
auto var = cast<VarDecl>(accessor->getStorage());
auto *backing = var->getPropertyWrapperBackingProperty();
// Error recovery.
if (!backing) {
auto type = storage->getValueInterfaceType();
if (isLValue)
type = LValueType::get(type);
return new (ctx) ErrorExpr(SourceRange(), type);
}
storage = backing;
// If the outermost property wrapper uses the enclosing self pattern,
// record that.
unsigned lastWrapperIdx = var->getAttachedPropertyWrappers().size();
unsigned firstWrapperIdx = 0;
enclosingSelfAccess =
getEnclosingSelfPropertyWrapperAccess(var, /*forProjected=*/false);
if (enclosingSelfAccess)
firstWrapperIdx = 1;
// Perform accesses to the wrappedValues along the composition chain.
for (unsigned i : range(firstWrapperIdx, lastWrapperIdx)) {
auto wrapperInfo = var->getAttachedPropertyWrapperTypeInfo(i);
underlyingVars.push_back(wrapperInfo.valueVar);
}
semantics = AccessSemantics::DirectToStorage;
selfAccessKind = SelfAccessorKind::Peer;
break;
}
case TargetImpl::WrapperStorage: {
auto var =
cast<VarDecl>(accessor->getStorage())->getOriginalWrappedProperty();
auto *backing = var->getPropertyWrapperBackingProperty();
// Error recovery.
if (!backing) {
auto type = storage->getValueInterfaceType();
if (isLValue)
type = LValueType::get(type);
return new (ctx) ErrorExpr(SourceRange(), type);
}
storage = backing;
enclosingSelfAccess =
getEnclosingSelfPropertyWrapperAccess(var, /*forProjected=*/true);
if (!enclosingSelfAccess) {
underlyingVars.push_back(
var->getAttachedPropertyWrapperTypeInfo(0).projectedValueVar);
}
semantics = AccessSemantics::DirectToStorage;
selfAccessKind = SelfAccessorKind::Peer;
break;
}
}
if (!selfDecl) {
assert(target != TargetImpl::Super);
auto *storageDRE = new (ctx) DeclRefExpr(storage, DeclNameLoc(),
/*IsImplicit=*/true, semantics);
auto type = storage->getValueInterfaceType().subst(subs);
if (isLValue)
type = LValueType::get(type);
storageDRE->setType(type);
return finish(storageDRE);
}
bool isMemberLValue = isLValue;
// If we're acessing a property wrapper, determine if the
// intermediate access requires an lvalue.
if (underlyingVars.size() > 0) {
isMemberLValue = underlyingVars[0]->isGetterMutating();
if (isLValue)
isMemberLValue |= underlyingVars[0]->isSetterMutating();
}
bool isSelfLValue = storage->isGetterMutating();
if (isMemberLValue)
isSelfLValue |= storage->isSetterMutating();
Expr *selfDRE =
buildSelfReference(selfDecl, selfAccessKind, isSelfLValue,
ctx);
if (isSelfLValue)
selfTypeForAccess = LValueType::get(selfTypeForAccess);
if (!selfDRE->getType()->isEqual(selfTypeForAccess)) {
assert(selfAccessKind == SelfAccessorKind::Super);
selfDRE = new (ctx) DerivedToBaseExpr(selfDRE, selfTypeForAccess);
}
Expr *lookupExpr;
ConcreteDeclRef memberRef(storage, subs);
auto type = storage->getValueInterfaceType().subst(subs);
if (isMemberLValue)
type = LValueType::get(type);
// When we are performing access via a property wrapper's static subscript
// that accepts the enclosing self along with key paths, form that subscript
// operation now.
if (enclosingSelfAccess) {
Type storageType = storage->getValueInterfaceType().subst(subs);
// Metatype instance for the wrapper type itself.
TypeExpr *wrapperMetatype = TypeExpr::createImplicit(storageType, ctx);
// Key path referring to the property being accessed.
Expr *propertyKeyPath = new (ctx) KeyPathDotExpr(SourceLoc());
propertyKeyPath = new (ctx) UnresolvedDotExpr(
propertyKeyPath, SourceLoc(),
enclosingSelfAccess->accessedProperty->getFullName(), DeclNameLoc(),
/*Implicit=*/true);
propertyKeyPath = new (ctx) KeyPathExpr(
SourceLoc(), nullptr, propertyKeyPath);
// Key path referring to the backing storage property.
Expr *storageKeyPath = new (ctx) KeyPathDotExpr(SourceLoc());
storageKeyPath = new (ctx) UnresolvedDotExpr(
storageKeyPath, SourceLoc(), storage->getFullName(), DeclNameLoc(),
/*Implicit=*/true);
storageKeyPath = new (ctx) KeyPathExpr(
SourceLoc(), nullptr, storageKeyPath);
Expr *args[3] = {
selfDRE,
propertyKeyPath,
storageKeyPath
};
SubscriptDecl *subscriptDecl = enclosingSelfAccess->subscript;
auto &tc = static_cast<TypeChecker&>(*ctx.getLazyResolver());
lookupExpr = SubscriptExpr::create(
ctx, wrapperMetatype, SourceLoc(), args,
subscriptDecl->getFullName().getArgumentNames(), { }, SourceLoc(),
nullptr, subscriptDecl, /*Implicit=*/true);
tc.typeCheckExpression(lookupExpr, accessor);
// Make sure we produce an lvalue only when desired.
if (isMemberLValue != lookupExpr->getType()->is<LValueType>()) {
if (isMemberLValue) {
// Strip off an extraneous load.
if (auto load = dyn_cast<LoadExpr>(lookupExpr))
lookupExpr = load->getSubExpr();
} else {
lookupExpr = new (ctx) LoadExpr(
lookupExpr, lookupExpr->getType()->getRValueType());
}
}
} else if (auto subscript = dyn_cast<SubscriptDecl>(storage)) {
Expr *indices = buildSubscriptIndexReference(ctx, accessor);
lookupExpr = SubscriptExpr::create(ctx, selfDRE, indices, memberRef,
/*IsImplicit=*/true, semantics);
if (selfAccessKind == SelfAccessorKind::Super)
cast<LookupExpr>(lookupExpr)->setIsSuper(true);
lookupExpr->setType(type);
} else {
lookupExpr = new (ctx) MemberRefExpr(selfDRE, SourceLoc(), memberRef,
DeclNameLoc(), /*IsImplicit=*/true,
semantics);
if (selfAccessKind == SelfAccessorKind::Super)
cast<LookupExpr>(lookupExpr)->setIsSuper(true);
lookupExpr->setType(type);
}
return finish(lookupExpr);
}
/// Load the value of VD. If VD is an @override of another value, we call the
/// superclass getter. Otherwise, we do a direct load of the value.
static Expr *
createPropertyLoadOrCallSuperclassGetter(AccessorDecl *accessor,
AbstractStorageDecl *storage,
TargetImpl target,
ASTContext &ctx) {
return buildStorageReference(accessor, storage, target, /*isLValue=*/false,
ctx);
}
static Optional<ProtocolConformanceRef>
checkConformanceToNSCopying(ASTContext &ctx, VarDecl *var, Type type) {
auto dc = var->getDeclContext();
auto proto = ctx.getNSCopyingDecl();
if (proto) {
auto result = TypeChecker::conformsToProtocol(type, proto, dc, None);
if (result)
return result;
}
ctx.Diags.diagnose(var->getLoc(), diag::nscopying_doesnt_conform);
return None;
}
static std::pair<Type, bool> getUnderlyingTypeOfVariable(VarDecl *var) {
Type type = var->getType()->getReferenceStorageReferent();
if (Type objectType = type->getOptionalObjectType()) {
return {objectType, true};
} else {
return {type, false};
}
}
Optional<ProtocolConformanceRef>
TypeChecker::checkConformanceToNSCopying(VarDecl *var) {
Type type = getUnderlyingTypeOfVariable(var).first;
return ::checkConformanceToNSCopying(Context, var, type);
}
/// Synthesize the code to store 'Val' to 'VD', given that VD has an @NSCopying
/// attribute on it. We know that VD is a stored property in a class, so we
/// just need to generate something like "self.property = val.copy(zone: nil)"
/// here. This does some type checking to validate that the call will succeed.
static Expr *synthesizeCopyWithZoneCall(Expr *Val, VarDecl *VD,
ASTContext &Ctx) {
// We support @NSCopying on class types (which conform to NSCopying),
// protocols which conform, and option types thereof.
auto underlyingTypeAndIsOptional = getUnderlyingTypeOfVariable(VD);
auto underlyingType = underlyingTypeAndIsOptional.first;
auto isOptional = underlyingTypeAndIsOptional.second;
// The element type must conform to NSCopying. If not, emit an error and just
// recovery by synthesizing without the copy call.
auto conformance = checkConformanceToNSCopying(Ctx, VD, underlyingType);
if (!conformance)
return Val;
//- (id)copyWithZone:(NSZone *)zone;
DeclName copyWithZoneName(Ctx, Ctx.getIdentifier("copy"), { Ctx.Id_with });
FuncDecl *copyMethod = nullptr;
for (auto member : conformance->getRequirement()->getMembers()) {
if (auto func = dyn_cast<FuncDecl>(member)) {
if (func->getFullName() == copyWithZoneName) {
copyMethod = func;
break;
}
}
}
assert(copyMethod != nullptr);
// If we have an optional type, we have to "?" the incoming value to only
// evaluate the subexpression if the incoming value is non-null.
if (isOptional) {
Val = new (Ctx) BindOptionalExpr(Val, SourceLoc(), 0);
Val->setType(underlyingType);
}
SubstitutionMap subs =
SubstitutionMap::get(copyMethod->getGenericSignature(),
{underlyingType},
ArrayRef<ProtocolConformanceRef>(*conformance));
ConcreteDeclRef copyMethodRef(copyMethod, subs);
auto copyMethodType = copyMethod->getInterfaceType()
->castTo<GenericFunctionType>()
->substGenericArgs(subs);
auto DRE = new (Ctx) DeclRefExpr(copyMethodRef, DeclNameLoc(),
/*IsImplicit=*/true);
DRE->setType(copyMethodType);
// Drop the self type
copyMethodType = copyMethodType->getResult()->castTo<FunctionType>();
auto DSCE = new (Ctx) DotSyntaxCallExpr(DRE, SourceLoc(), Val);
DSCE->setImplicit();
DSCE->setType(copyMethodType);
DSCE->setThrows(false);
Expr *Nil = new (Ctx) NilLiteralExpr(SourceLoc(), /*implicit*/true);
Nil->setType(copyMethodType->getParams()[0].getParameterType());
auto *Call = CallExpr::createImplicit(Ctx, DSCE, { Nil }, { Ctx.Id_with });
Call->setType(copyMethodType->getResult());
Call->setThrows(false);
TypeLoc ResultTy;
ResultTy.setType(VD->getType());
// If we're working with non-optional types, we're forcing the cast.
if (!isOptional) {
auto *Cast =
new (Ctx) ForcedCheckedCastExpr(Call, SourceLoc(), SourceLoc(),
TypeLoc::withoutLoc(underlyingType));
Cast->setCastKind(CheckedCastKind::ValueCast);
Cast->setType(underlyingType);
Cast->setImplicit();
return Cast;
}
// We're working with optional types, so perform a conditional checked
// downcast.
auto *Cast =
new (Ctx) ConditionalCheckedCastExpr(Call, SourceLoc(), SourceLoc(),
TypeLoc::withoutLoc(underlyingType));
Cast->setCastKind(CheckedCastKind::ValueCast);
Cast->setType(OptionalType::get(underlyingType));
Cast->setImplicit();
// Use OptionalEvaluationExpr to evaluate the "?".
auto *Result = new (Ctx) OptionalEvaluationExpr(Cast);
Result->setType(OptionalType::get(underlyingType));
return Result;
}
/// In a synthesized accessor body, store 'value' to the appropriate element.
///
/// If the property is an override, we call the superclass setter.
/// Otherwise, we do a direct store of the value.
static
void createPropertyStoreOrCallSuperclassSetter(AccessorDecl *accessor,
Expr *value,
AbstractStorageDecl *storage,
TargetImpl target,
SmallVectorImpl<ASTNode> &body,
ASTContext &ctx) {
// If the storage is an @NSCopying property, then we store the
// result of a copyWithZone call on the value, not the value itself.
if (auto property = dyn_cast<VarDecl>(storage)) {
if (property->getAttrs().hasAttribute<NSCopyingAttr>())
value = synthesizeCopyWithZoneCall(value, property, ctx);
}
// Error recovery.
if (value->getType()->hasError())
return;
Expr *dest = buildStorageReference(accessor, storage, target,
/*isLValue=*/true, ctx);
// A lazy property setter will store a value of type T into underlying storage
// of type T?.
auto destType = dest->getType()->getWithoutSpecifierType();
// Error recovery.
if (destType->hasError())
return;
if (!destType->isEqual(value->getType())) {
assert(destType->getOptionalObjectType()->isEqual(value->getType()));
value = new (ctx) InjectIntoOptionalExpr(value, destType);
}
auto *assign = new (ctx) AssignExpr(dest, SourceLoc(), value,
/*IsImplicit=*/true);
assign->setType(ctx.TheEmptyTupleType);
body.push_back(assign);
}
LLVM_ATTRIBUTE_UNUSED
static bool isSynthesizedComputedProperty(AbstractStorageDecl *storage) {
return (storage->getAttrs().hasAttribute<LazyAttr>() ||
storage->getAttrs().hasAttribute<NSManagedAttr>() ||
(isa<VarDecl>(storage) &&
cast<VarDecl>(storage)->hasAttachedPropertyWrapper()));
}
/// Synthesize the body of a trivial getter. For a non-member vardecl or one
/// which is not an override of a base class property, it performs a direct
/// storage load. For an override of a base member property, it chains up to
/// super.
static std::pair<BraceStmt *, bool>
synthesizeTrivialGetterBody(AccessorDecl *getter, TargetImpl target,
ASTContext &ctx) {
auto storage = getter->getStorage();
assert(!isSynthesizedComputedProperty(storage) ||
target == TargetImpl::Wrapper ||
target == TargetImpl::WrapperStorage);
SourceLoc loc = storage->getLoc();
Expr *result =
createPropertyLoadOrCallSuperclassGetter(getter, storage, target, ctx);
ASTNode returnStmt = new (ctx) ReturnStmt(SourceLoc(), result,
/*IsImplicit=*/true);
return { BraceStmt::create(ctx, loc, returnStmt, loc, true),
/*isTypeChecked=*/true };
}
/// Synthesize the body of a getter which just directly accesses the
/// underlying storage.
static std::pair<BraceStmt *, bool>
synthesizeTrivialGetterBody(AccessorDecl *getter, ASTContext &ctx) {
assert(getter->getStorage()->hasStorage());
return synthesizeTrivialGetterBody(getter, TargetImpl::Storage, ctx);
}
/// Synthesize the body of a getter which just delegates to its superclass
/// implementation.
static std::pair<BraceStmt *, bool>
synthesizeInheritedGetterBody(AccessorDecl *getter, ASTContext &ctx) {
// This should call the superclass getter.
return synthesizeTrivialGetterBody(getter, TargetImpl::Super, ctx);
}
/// Synthesize the body of a getter which just delegates to an addressor.
static std::pair<BraceStmt *, bool>
synthesizeAddressedGetterBody(AccessorDecl *getter, ASTContext &ctx) {
assert(getter->getStorage()->getParsedAccessor(AccessorKind::Address));
// This should call the addressor.
return synthesizeTrivialGetterBody(getter, TargetImpl::Implementation, ctx);
}
/// Synthesize the body of a getter which just delegates to a read
/// coroutine accessor.
static std::pair<BraceStmt *, bool>
synthesizeReadCoroutineGetterBody(AccessorDecl *getter, ASTContext &ctx) {
assert(getter->getStorage()->getParsedAccessor(AccessorKind::Read));
// This should call the read coroutine.
return synthesizeTrivialGetterBody(getter, TargetImpl::Implementation, ctx);
}
namespace {
/// This ASTWalker explores an expression tree looking for expressions (which
/// are DeclContext's) and changes their parent DeclContext to NewDC.
class RecontextualizeClosures : public ASTWalker {
DeclContext *NewDC;
public:
RecontextualizeClosures(DeclContext *NewDC) : NewDC(NewDC) {}
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
// If we find a closure, update its declcontext and do *not* walk into it.
if (auto CE = dyn_cast<AbstractClosureExpr>(E)) {
CE->setParent(NewDC);
return { false, E };
}
if (auto CLE = dyn_cast<CaptureListExpr>(E)) {
// Make sure to recontextualize any decls in the capture list as well.
for (auto &CLE : CLE->getCaptureList()) {
CLE.Var->setDeclContext(NewDC);
CLE.Init->setDeclContext(NewDC);
}
}
// Unlike a closure, a TapExpr is not a DeclContext, so we need to
// recontextualize its variable and then anything else in its body.
// FIXME: Might be better to change walkToDeclPre() and walkToStmtPre()
// below, but I don't know what other effects that might have.
if (auto TE = dyn_cast<TapExpr>(E)) {
TE->getVar()->setDeclContext(NewDC);
for (auto node : TE->getBody()->getElements())
node.walk(RecontextualizeClosures(NewDC));
}
return { true, E };
}
/// We don't want to recurse into declarations or statements.
bool walkToDeclPre(Decl *) override { return false; }
std::pair<bool, Stmt*> walkToStmtPre(Stmt *S) override { return {false,S}; }
};
} // end anonymous namespace
/// Synthesize the getter for a lazy property with the specified storage
/// vardecl.
static std::pair<BraceStmt *, bool>
synthesizeLazyGetterBody(AccessorDecl *Get, VarDecl *VD, VarDecl *Storage,
ASTContext &Ctx) {
// FIXME: Remove TypeChecker dependencies below.
auto &TC = *(TypeChecker *) Ctx.getLazyResolver();
// The getter checks the optional, storing the initial value in if nil. The
// specific pattern we generate is:
// get {
// if let tmp1 = storage {
// return tmp1
// }
// let tmp2 : Ty = <<initializer expression>>
// storage = tmp2
// return tmp2
// }
SmallVector<ASTNode, 6> Body;
// Load the existing storage and store it into the 'tmp1' temporary.
auto *Tmp1VD = new (Ctx) VarDecl(/*IsStatic*/false, VarDecl::Introducer::Let,
/*IsCaptureList*/false, SourceLoc(),
Ctx.getIdentifier("tmp1"), Get);
Tmp1VD->setInterfaceType(VD->getValueInterfaceType());
Tmp1VD->setHasNonPatternBindingInit();
Tmp1VD->setImplicit();
auto *Named = new (Ctx) NamedPattern(Tmp1VD, /*implicit*/true);
Named->setType(Tmp1VD->getType());
auto *Let = new (Ctx) VarPattern(SourceLoc(), /*let*/true, Named,
/*implict*/true);
Let->setType(Named->getType());
auto *Some = new (Ctx) OptionalSomePattern(Let, SourceLoc(),
/*implicit*/true);
Some->setElementDecl(Ctx.getOptionalSomeDecl());
Some->setType(OptionalType::get(Let->getType()));
auto *StoredValueExpr =
createPropertyLoadOrCallSuperclassGetter(Get, Storage,
TargetImpl::Storage, Ctx);
SmallVector<StmtConditionElement, 1> Cond;
Cond.emplace_back(SourceLoc(), Some, StoredValueExpr);
// Build the early return inside the if.
auto *Tmp1DRE = new (Ctx) DeclRefExpr(Tmp1VD, DeclNameLoc(), /*Implicit*/true,
AccessSemantics::Ordinary);
Tmp1DRE->setType(Tmp1VD->getType());
auto *Return = new (Ctx) ReturnStmt(SourceLoc(), Tmp1DRE,
/*implicit*/true);
// Build the "if" around the early return.
Body.push_back(new (Ctx) IfStmt(LabeledStmtInfo(),
SourceLoc(), Ctx.AllocateCopy(Cond), Return,
/*elseloc*/SourceLoc(), /*else*/nullptr,
/*implicit*/ true));
auto *Tmp2VD = new (Ctx) VarDecl(/*IsStatic*/false, VarDecl::Introducer::Let,
/*IsCaptureList*/false, SourceLoc(),
Ctx.getIdentifier("tmp2"),
Get);
Tmp2VD->setInterfaceType(VD->getValueInterfaceType());
Tmp2VD->setImplicit();
// Take the initializer from the PatternBindingDecl for VD.
// TODO: This doesn't work with complicated patterns like:
// lazy var (a,b) = foo()
auto PBD = VD->getParentPatternBinding();
unsigned entryIndex = PBD->getPatternEntryIndexForVarDecl(VD);
Expr *InitValue;
if (PBD->getPatternList()[entryIndex].getInit()) {
PBD->setInitializerSubsumed(entryIndex);
if (!PBD->isInitializerChecked(entryIndex))
TC.typeCheckPatternBinding(PBD, entryIndex);
InitValue = PBD->getPatternList()[entryIndex].getInit();
} else {
InitValue = new (Ctx) ErrorExpr(SourceRange(), Tmp2VD->getType());
}
// Recontextualize any closure declcontexts nested in the initializer to
// realize that they are in the getter function.
Get->getImplicitSelfDecl()->setDeclContext(Get);
InitValue->walk(RecontextualizeClosures(Get));
// Wrap the initializer in a LazyInitializerExpr to avoid walking it twice.
auto initType = InitValue->getType();
InitValue = new (Ctx) LazyInitializerExpr(InitValue);
InitValue->setType(initType);
Pattern *Tmp2PBDPattern = new (Ctx) NamedPattern(Tmp2VD, /*implicit*/true);
Tmp2PBDPattern =
TypedPattern::createImplicit(Ctx, Tmp2PBDPattern, Tmp2VD->getType());
auto *Tmp2PBD = PatternBindingDecl::createImplicit(
Ctx, StaticSpellingKind::None, Tmp2PBDPattern, InitValue, Get,
/*VarLoc*/ InitValue->getStartLoc());
Body.push_back(Tmp2PBD);
Body.push_back(Tmp2VD);
// Assign tmp2 into storage.
auto Tmp2DRE = new (Ctx) DeclRefExpr(Tmp2VD, DeclNameLoc(), /*Implicit*/true,
AccessSemantics::DirectToStorage);
Tmp2DRE->setType(Tmp2VD->getType());
createPropertyStoreOrCallSuperclassSetter(Get, Tmp2DRE, Storage,
TargetImpl::Storage, Body, Ctx);
// Return tmp2.
Tmp2DRE = new (Ctx) DeclRefExpr(Tmp2VD, DeclNameLoc(), /*Implicit*/true,
AccessSemantics::DirectToStorage);
Tmp2DRE->setType(Tmp2VD->getType());
Body.push_back(new (Ctx) ReturnStmt(SourceLoc(), Tmp2DRE, /*implicit*/true));
return { BraceStmt::create(Ctx, VD->getLoc(), Body, VD->getLoc(),
/*implicit*/true),
/*isTypeChecked=*/true };
}
/// Synthesize the body of a getter for a property wrapper, which
/// delegates to the wrapper's "value" property.
static std::pair<BraceStmt *, bool>
synthesizePropertyWrapperGetterBody(AccessorDecl *getter, ASTContext &ctx) {
return synthesizeTrivialGetterBody(getter, TargetImpl::Wrapper, ctx);
}
static std::pair<BraceStmt *, bool>
synthesizeInvalidAccessor(AccessorDecl *accessor, ASTContext &ctx) {
auto loc = accessor->getLoc();
return { BraceStmt::create(ctx, loc, ArrayRef<ASTNode>(), loc, true), true };
}
static std::pair<BraceStmt *, bool>
synthesizeGetterBody(AccessorDecl *getter, ASTContext &ctx) {
auto storage = getter->getStorage();
// Synthesize the getter for a lazy property or property wrapper.
if (auto var = dyn_cast<VarDecl>(storage)) {
if (var->getAttrs().hasAttribute<LazyAttr>()) {
auto *storage = var->getLazyStorageProperty();
return synthesizeLazyGetterBody(getter, var, storage, ctx);
}
if (var->hasAttachedPropertyWrapper()) {
return synthesizePropertyWrapperGetterBody(getter, ctx);
}
if (var->getOriginalWrappedProperty(
PropertyWrapperSynthesizedPropertyKind::StorageWrapper)) {
return synthesizeTrivialGetterBody(getter, TargetImpl::WrapperStorage,
ctx);
}
}
if (getter->hasForcedStaticDispatch()) {
return synthesizeTrivialGetterBody(getter, TargetImpl::Ordinary, ctx);
}
switch (getter->getStorage()->getReadImpl()) {
case ReadImplKind::Stored:
return synthesizeTrivialGetterBody(getter, ctx);
case ReadImplKind::Get:
return synthesizeInvalidAccessor(getter, ctx);
case ReadImplKind::Inherited:
return synthesizeInheritedGetterBody(getter, ctx);
case ReadImplKind::Address:
return synthesizeAddressedGetterBody(getter, ctx);
case ReadImplKind::Read:
return synthesizeReadCoroutineGetterBody(getter, ctx);
}
llvm_unreachable("bad ReadImplKind");
}
/// Synthesize the body of a setter which just stores to the given storage
/// declaration (which doesn't have to be the storage for the setter).
static std::pair<BraceStmt *, bool>
synthesizeTrivialSetterBodyWithStorage(AccessorDecl *setter,
TargetImpl target,
AbstractStorageDecl *storageToUse,
ASTContext &ctx) {
SourceLoc loc = setter->getStorage()->getLoc();
VarDecl *valueParamDecl = setter->getParameters()->get(0);
auto *valueDRE =
new (ctx) DeclRefExpr(valueParamDecl, DeclNameLoc(), /*IsImplicit=*/true);
valueDRE->setType(valueParamDecl->getType());
SmallVector<ASTNode, 1> setterBody;
createPropertyStoreOrCallSuperclassSetter(setter, valueDRE, storageToUse,
target, setterBody, ctx);
return { BraceStmt::create(ctx, loc, setterBody, loc, true),
/*isTypeChecked=*/true };
}
static std::pair<BraceStmt *, bool>
synthesizeTrivialSetterBody(AccessorDecl *setter, ASTContext &ctx) {
auto storage = setter->getStorage();
assert(!isSynthesizedComputedProperty(storage));
return synthesizeTrivialSetterBodyWithStorage(setter, TargetImpl::Storage,
storage, ctx);
}
/// Synthesize the body of a setter for a property wrapper, which
/// delegates to the wrapper's "value" property.
static std::pair<BraceStmt *, bool>
synthesizePropertyWrapperSetterBody(AccessorDecl *setter, ASTContext &ctx) {
return synthesizeTrivialSetterBodyWithStorage(setter, TargetImpl::Wrapper,
setter->getStorage(), ctx);
}
/// Synthesize the body of a setter which just delegates to a mutable
/// addressor.
static std::pair<BraceStmt *, bool>
synthesizeMutableAddressSetterBody(AccessorDecl *setter, ASTContext &ctx) {
// This should call the mutable addressor.
return synthesizeTrivialSetterBodyWithStorage(setter,
TargetImpl::Implementation,
setter->getStorage(), ctx);
}
/// Synthesize the body of a setter which just delegates to a modify
/// coroutine accessor.
static std::pair<BraceStmt *, bool>
synthesizeModifyCoroutineSetterBody(AccessorDecl *setter, ASTContext &ctx) {
// This should call the modify coroutine.
return synthesizeTrivialSetterBodyWithStorage(setter,
TargetImpl::Implementation,
setter->getStorage(), ctx);
}
static Expr *maybeWrapInOutExpr(Expr *expr, ASTContext &ctx) {
if (auto lvalueType = expr->getType()->getAs<LValueType>()) {
auto type = lvalueType->getObjectType();
return new (ctx) InOutExpr(SourceLoc(), expr, type, true);
}
return expr;
}
/// Given a VarDecl with a willSet: and/or didSet: specifier, synthesize the
/// setter which calls them.
static std::pair<BraceStmt *, bool>
synthesizeObservedSetterBody(AccessorDecl *Set, TargetImpl target,
ASTContext &Ctx) {
auto VD = cast<VarDecl>(Set->getStorage());
SourceLoc Loc = VD->getLoc();
// Start by finding the decls for 'self' and 'value'.
auto *SelfDecl = Set->getImplicitSelfDecl();
VarDecl *ValueDecl = Set->getParameters()->get(0);
bool IsSelfLValue = VD->isSetterMutating();
SubstitutionMap subs;
if (auto *genericEnv = Set->getGenericEnvironment())
subs = genericEnv->getForwardingSubstitutionMap();
// The setter loads the oldValue, invokes willSet with the incoming value,
// does a direct store, then invokes didSet with the oldValue.
SmallVector<ASTNode, 6> SetterBody;
auto callObserver = [&](AccessorDecl *observer, VarDecl *arg) {
ConcreteDeclRef ref(observer, subs);
auto type = observer->getInterfaceType().subst(subs);
Expr *Callee = new (Ctx) DeclRefExpr(ref, DeclNameLoc(), /*imp*/true);
Callee->setType(type);
auto *ValueDRE = new (Ctx) DeclRefExpr(arg, DeclNameLoc(), /*imp*/true);
ValueDRE->setType(arg->getType());
if (SelfDecl) {
auto *SelfDRE = buildSelfReference(SelfDecl, SelfAccessorKind::Peer,
IsSelfLValue, Ctx);
SelfDRE = maybeWrapInOutExpr(SelfDRE, Ctx);
auto *DSCE = new (Ctx) DotSyntaxCallExpr(Callee, SourceLoc(), SelfDRE);
if (auto funcType = type->getAs<FunctionType>())
type = funcType->getResult();
DSCE->setType(type);
DSCE->setThrows(false);
Callee = DSCE;
}
auto *Call = CallExpr::createImplicit(Ctx, Callee, { ValueDRE },
{ Identifier() });
if (auto funcType = type->getAs<FunctionType>())
type = funcType->getResult();
Call->setType(type);
Call->setThrows(false);
SetterBody.push_back(Call);
};
// If there is a didSet, it will take the old value. Load it into a temporary
// 'let' so we have it for later.
// TODO: check the body of didSet to only do this load (which may call the
// superclass getter) if didSet takes an argument.
VarDecl *OldValue = nullptr;
if (VD->getParsedAccessor(AccessorKind::DidSet)) {
Expr *OldValueExpr
= buildStorageReference(Set, VD, target, /*isLValue=*/true, Ctx);
OldValueExpr = new (Ctx) LoadExpr(OldValueExpr, VD->getType());
OldValue = new (Ctx) VarDecl(/*IsStatic*/false, VarDecl::Introducer::Let,
/*IsCaptureList*/false, SourceLoc(),
Ctx.getIdentifier("tmp"), Set);
OldValue->setImplicit();
OldValue->setInterfaceType(VD->getValueInterfaceType());
auto *tmpPattern = new (Ctx) NamedPattern(OldValue, /*implicit*/ true);
auto *tmpPBD = PatternBindingDecl::createImplicit(
Ctx, StaticSpellingKind::None, tmpPattern, OldValueExpr, Set);
SetterBody.push_back(tmpPBD);
SetterBody.push_back(OldValue);
}
if (auto willSet = VD->getParsedAccessor(AccessorKind::WillSet))
callObserver(willSet, ValueDecl);
// Create an assignment into the storage or call to superclass setter.
auto *ValueDRE = new (Ctx) DeclRefExpr(ValueDecl, DeclNameLoc(), true);
ValueDRE->setType(ValueDecl->getType());
createPropertyStoreOrCallSuperclassSetter(Set, ValueDRE, VD, target,
SetterBody, Ctx);
if (auto didSet = VD->getParsedAccessor(AccessorKind::DidSet))
callObserver(didSet, OldValue);
return { BraceStmt::create(Ctx, Loc, SetterBody, Loc, true),
/*isTypeChecked=*/true };
}
static std::pair<BraceStmt *, bool>
synthesizeStoredWithObserversSetterBody(AccessorDecl *setter, ASTContext &ctx) {
return synthesizeObservedSetterBody(setter, TargetImpl::Storage, ctx);
}
static std::pair<BraceStmt *, bool>
synthesizeInheritedWithObserversSetterBody(AccessorDecl *setter,
ASTContext &ctx) {
return synthesizeObservedSetterBody(setter, TargetImpl::Super, ctx);
}
static std::pair<BraceStmt *, bool>
synthesizeSetterBody(AccessorDecl *setter, ASTContext &ctx) {
auto storage = setter->getStorage();
// Synthesize the setter for a lazy property or property wrapper.
if (auto var = dyn_cast<VarDecl>(storage)) {
if (var->getAttrs().hasAttribute<LazyAttr>()) {
// Lazy property setters write to the underlying storage.
auto *storage = var->getLazyStorageProperty();
return synthesizeTrivialSetterBodyWithStorage(setter, TargetImpl::Storage,
storage, ctx);
}
if (var->hasAttachedPropertyWrapper()) {
if (var->getParsedAccessor(AccessorKind::WillSet) ||
var->getParsedAccessor(AccessorKind::DidSet)) {
return synthesizeObservedSetterBody(setter, TargetImpl::Wrapper, ctx);
}
return synthesizePropertyWrapperSetterBody(setter, ctx);
}
// Synthesize a getter for the storage wrapper property of a property
// with an attached wrapper.
if (auto original = var->getOriginalWrappedProperty(
PropertyWrapperSynthesizedPropertyKind::StorageWrapper)) {
auto backingVar = original->getPropertyWrapperBackingProperty();
return synthesizeTrivialSetterBodyWithStorage(setter,
TargetImpl::WrapperStorage,
backingVar, ctx);
}
}
switch (storage->getWriteImpl()) {
case WriteImplKind::Immutable:
llvm_unreachable("synthesizing setter from immutable storage");
case WriteImplKind::Stored:
return synthesizeTrivialSetterBody(setter, ctx);
case WriteImplKind::StoredWithObservers:
return synthesizeStoredWithObserversSetterBody(setter, ctx);
case WriteImplKind::InheritedWithObservers:
return synthesizeInheritedWithObserversSetterBody(setter, ctx);
case WriteImplKind::Set:
return synthesizeInvalidAccessor(setter, ctx);
case WriteImplKind::MutableAddress:
return synthesizeMutableAddressSetterBody(setter, ctx);
case WriteImplKind::Modify:
return synthesizeModifyCoroutineSetterBody(setter, ctx);
}
llvm_unreachable("bad ReadImplKind");
}
static std::pair<BraceStmt *, bool>
synthesizeCoroutineAccessorBody(AccessorDecl *accessor, ASTContext &ctx) {
assert(accessor->isCoroutine());
auto storage = accessor->getStorage();
auto target = (accessor->hasForcedStaticDispatch()
? TargetImpl::Ordinary
: TargetImpl::Implementation);
SourceLoc loc = storage->getLoc();
SmallVector<ASTNode, 1> body;
bool isLValue = accessor->getAccessorKind() == AccessorKind::Modify;
// Build a reference to the storage.
Expr *ref = buildStorageReference(accessor, storage, target, isLValue, ctx);
// Wrap it with an `&` marker if this is a modify.
ref = maybeWrapInOutExpr(ref, ctx);
// Yield it.
YieldStmt *yield = YieldStmt::create(ctx, loc, loc, ref, loc, true);
body.push_back(yield);
return { BraceStmt::create(ctx, loc, body, loc, true),
/*isTypeChecked=*/true };
}
/// Synthesize the body of a read coroutine.
static std::pair<BraceStmt *, bool>
synthesizeReadCoroutineBody(AccessorDecl *read, ASTContext &ctx) {
assert(read->getStorage()->getReadImpl() != ReadImplKind::Read);
return synthesizeCoroutineAccessorBody(read, ctx);
}
/// Synthesize the body of a modify coroutine.
static std::pair<BraceStmt *, bool>
synthesizeModifyCoroutineBody(AccessorDecl *modify, ASTContext &ctx) {
#ifndef NDEBUG
auto impl = modify->getStorage()->getReadWriteImpl();
assert(impl != ReadWriteImplKind::Modify &&
impl != ReadWriteImplKind::Immutable);
#endif
return synthesizeCoroutineAccessorBody(modify, ctx);
}
static std::pair<BraceStmt *, bool>
synthesizeAccessorBody(AbstractFunctionDecl *fn, void *) {
auto *accessor = cast<AccessorDecl>(fn);
auto &ctx = accessor->getASTContext();
if (ctx.Stats)
ctx.Stats->getFrontendCounters().NumAccessorBodiesSynthesized++;
switch (accessor->getAccessorKind()) {
case AccessorKind::Get:
return synthesizeGetterBody(accessor, ctx);
case AccessorKind::Set:
return synthesizeSetterBody(accessor, ctx);
case AccessorKind::Read:
return synthesizeReadCoroutineBody(accessor, ctx);
case AccessorKind::Modify:
return synthesizeModifyCoroutineBody(accessor, ctx);
case AccessorKind::WillSet:
case AccessorKind::DidSet:
case AccessorKind::Address:
case AccessorKind::MutableAddress:
break;
}
llvm_unreachable("bad synthesized function kind");
}
static void finishImplicitAccessor(AccessorDecl *accessor,
ASTContext &ctx) {
accessor->setImplicit();
if (ctx.Stats)
ctx.Stats->getFrontendCounters().NumAccessorsSynthesized++;
if (doesAccessorHaveBody(accessor))
accessor->setBodySynthesizer(&synthesizeAccessorBody);
}
static AccessorDecl *createGetterPrototype(AbstractStorageDecl *storage,
ASTContext &ctx) {
SourceLoc loc = storage->getLoc();
ParamDecl *selfDecl = nullptr;
if (storage->getDeclContext()->isTypeContext()) {
if (storage->getAttrs().hasAttribute<LazyAttr>()) {
// For lazy properties, steal the 'self' from the initializer context.
auto *varDecl = cast<VarDecl>(storage);
auto *bindingDecl = varDecl->getParentPatternBinding();
auto *bindingInit = cast<PatternBindingInitializer>(
bindingDecl->getPatternEntryForVarDecl(varDecl).getInitContext());
selfDecl = bindingInit->getImplicitSelfDecl();
}
}
GenericParamList *genericParams = createAccessorGenericParams(storage);
// Add an index-forwarding clause.
auto *getterParams = buildIndexForwardingParamList(storage, {}, ctx);
SourceLoc staticLoc;
if (storage->isStatic())
staticLoc = storage->getLoc();
auto getter = AccessorDecl::create(
ctx, loc, /*AccessorKeywordLoc*/ loc,
AccessorKind::Get, storage,
staticLoc, StaticSpellingKind::None,
/*Throws=*/false, /*ThrowsLoc=*/SourceLoc(),
genericParams,
getterParams,
TypeLoc(),
storage->getDeclContext());
// If we're stealing the 'self' from a lazy initializer, set it now.
// Note that we don't re-parent the 'self' declaration to be part of
// the getter until we synthesize the body of the getter later.
if (selfDecl)
*getter->getImplicitSelfDeclStorage() = selfDecl;
if (storage->isGetterMutating())
getter->setSelfAccessKind(SelfAccessKind::Mutating);
else
getter->setSelfAccessKind(SelfAccessKind::NonMutating);
if (!storage->requiresOpaqueAccessor(AccessorKind::Get))
getter->setForcedStaticDispatch(true);
finishImplicitAccessor(getter, ctx);
return getter;
}
static AccessorDecl *createSetterPrototype(AbstractStorageDecl *storage,
ASTContext &ctx,
AccessorDecl *getter = nullptr) {
assert(storage->supportsMutation());
SourceLoc loc = storage->getLoc();
bool isMutating = storage->isSetterMutating();
GenericParamList *genericParams = createAccessorGenericParams(storage);
// Add a "(value : T, indices...)" argument list.
auto *param = new (ctx) ParamDecl(SourceLoc(), SourceLoc(),
Identifier(), loc,
ctx.getIdentifier("value"),
storage->getDeclContext());
param->setSpecifier(ParamSpecifier::Default);
param->setImplicit();
auto *params = buildIndexForwardingParamList(storage, param, ctx);
auto setter = AccessorDecl::create(
ctx, loc, /*AccessorKeywordLoc*/ SourceLoc(),
AccessorKind::Set, storage,
/*StaticLoc=*/SourceLoc(), StaticSpellingKind::None,
/*Throws=*/false, /*ThrowsLoc=*/SourceLoc(),
genericParams, params,
TypeLoc(),
storage->getDeclContext());
if (isMutating)
setter->setSelfAccessKind(SelfAccessKind::Mutating);
else
setter->setSelfAccessKind(SelfAccessKind::NonMutating);
// All mutable storage requires a setter.
assert(storage->requiresOpaqueAccessor(AccessorKind::Set));
finishImplicitAccessor(setter, ctx);
return setter;
}
static AccessorDecl *
createCoroutineAccessorPrototype(AbstractStorageDecl *storage,
AccessorKind kind,
ASTContext &ctx) {
assert(kind == AccessorKind::Read || kind == AccessorKind::Modify);
SourceLoc loc = storage->getLoc();
bool isMutating = storage->isGetterMutating();
if (kind == AccessorKind::Modify)
isMutating |= storage->isSetterMutating();
auto dc = storage->getDeclContext();
// The forwarding index parameters.
auto *params = buildIndexForwardingParamList(storage, {}, ctx);
// Coroutine accessors always return ().
Type retTy = TupleType::getEmpty(ctx);
GenericParamList *genericParams = createAccessorGenericParams(storage);
auto *accessor = AccessorDecl::create(
ctx, loc, /*AccessorKeywordLoc=*/SourceLoc(),
kind, storage,
/*StaticLoc=*/SourceLoc(), StaticSpellingKind::None,
/*Throws=*/false, /*ThrowsLoc=*/SourceLoc(),
genericParams, params, TypeLoc::withoutLoc(retTy), dc);
if (isMutating)
accessor->setSelfAccessKind(SelfAccessKind::Mutating);
else
accessor->setSelfAccessKind(SelfAccessKind::NonMutating);
// If the storage does not provide this accessor as an opaque accessor,
// we can't add a dynamically-dispatched method entry for the accessor,
// so force it to be statically dispatched. ("final" would be inappropriate
// because the property can still be overridden.)
if (!storage->requiresOpaqueAccessor(kind))
accessor->setForcedStaticDispatch(true);
// Make sure the coroutine is available enough to access
// the storage (and its getters/setters if it has them).
SmallVector<const Decl *, 2> asAvailableAs;
asAvailableAs.push_back(storage);
if (FuncDecl *getter = storage->getParsedAccessor(AccessorKind::Get)) {
asAvailableAs.push_back(getter);
}
if (kind == AccessorKind::Modify) {
if (FuncDecl *setter = storage->getParsedAccessor(AccessorKind::Set)) {
asAvailableAs.push_back(setter);
}
}
AvailabilityInference::applyInferredAvailableAttrs(accessor,
asAvailableAs, ctx);
finishImplicitAccessor(accessor, ctx);
return accessor;
}
static AccessorDecl *
createReadCoroutinePrototype(AbstractStorageDecl *storage,
ASTContext &ctx) {
return createCoroutineAccessorPrototype(storage, AccessorKind::Read, ctx);
}
static AccessorDecl *
createModifyCoroutinePrototype(AbstractStorageDecl *storage,
ASTContext &ctx) {
return createCoroutineAccessorPrototype(storage, AccessorKind::Modify, ctx);
}
llvm::Expected<AccessorDecl *>
SynthesizeAccessorRequest::evaluate(Evaluator &evaluator,
AbstractStorageDecl *storage,
AccessorKind kind) const {
auto &ctx = storage->getASTContext();
switch (kind) {
case AccessorKind::Get:
return createGetterPrototype(storage, ctx);
case AccessorKind::Set:
return createSetterPrototype(storage, ctx);
case AccessorKind::Read:
return createReadCoroutinePrototype(storage, ctx);
case AccessorKind::Modify:
return createModifyCoroutinePrototype(storage, ctx);
#define OPAQUE_ACCESSOR(ID, KEYWORD)
#define ACCESSOR(ID) \
case AccessorKind::ID:
#include "swift/AST/AccessorKinds.def"
llvm_unreachable("not an opaque accessor");
}
llvm_unreachable("Unhandled AccessorKind in switch");
}
llvm::Expected<bool>
RequiresOpaqueAccessorsRequest::evaluate(Evaluator &evaluator,
VarDecl *var) const {
// Nameless vars from interface files should not have any accessors.
// TODO: Replace this check with a broader check that all storage decls
// from interface files have all their accessors up front.
if (var->getBaseName().empty())
return false;
// Computed properties always require opaque accessors.
if (!var->getImplInfo().isSimpleStored())
return true;
// The backing storage for a lazy property does require opaque accessors.
if (var->isLazyStorageProperty())
return false;
auto *dc = var->getDeclContext();
// Local stored variables don't require opaque accessors.
if (dc->isLocalContext()) {
return false;
} else if (dc->isModuleScopeContext()) {
// Fixed-layout global variables don't require opaque accessors.
if (!var->isResilient() && !var->isNativeDynamic())
return false;
// Stored properties imported from Clang don't require opaque accessors.
} else if (auto *structDecl = dyn_cast<StructDecl>(dc)) {
if (structDecl->hasClangNode())
return false;
}
// Stored properties in SIL mode don't get accessors.
// But we might need to create opaque accessors for them.
if (auto sourceFile = dc->getParentSourceFile()) {
if (sourceFile->Kind == SourceFileKind::SIL) {
if (!var->getParsedAccessor(AccessorKind::Get))
return false;
}
}
// Everything else requires opaque accessors.
return true;
}
llvm::Expected<bool>
RequiresOpaqueModifyCoroutineRequest::evaluate(Evaluator &evaluator,
AbstractStorageDecl *storage) const {
// Only for mutable storage.
if (!storage->supportsMutation())
return false;
auto *dc = storage->getDeclContext();
// Local properties don't have an opaque modify coroutine.
if (dc->isLocalContext())
return false;
// Fixed-layout global properties don't have an opaque modify coroutine.
if (dc->isModuleScopeContext() && !storage->isResilient())
return false;
// Imported storage declarations don't have an opaque modify coroutine.
if (storage->hasClangNode())
return false;
// Dynamic storage does not have an opaque modify coroutine.
if (dc->getSelfClassDecl())
if (storage->isObjCDynamic())
return false;
// Requirements of ObjC protocols don't have an opaque modify coroutine.
if (auto protoDecl = dyn_cast<ProtocolDecl>(dc))
if (protoDecl->isObjC())
return false;
return true;
}
/// Mark the accessor as transparent if we can.
///
/// If the storage is inside a fixed-layout nominal type, we can mark the
/// accessor as transparent, since in this case we just want it for abstraction
/// purposes (i.e., to make access to the variable uniform and to be able to
/// put the getter in a vtable).
///
/// If the storage is for a global stored property or a stored property of a
/// resilient type, we are synthesizing accessors to present a resilient
/// interface to the storage and they should not be transparent.
llvm::Expected<bool>
IsAccessorTransparentRequest::evaluate(Evaluator &evaluator,
AccessorDecl *accessor) const {
auto *storage = accessor->getStorage();
if (storage->isTransparent())
return true;
if (accessor->getAttrs().hasAttribute<TransparentAttr>())
return true;
if (!accessor->isImplicit())
return false;
if (!doesAccessorHaveBody(accessor))
return false;
auto *DC = accessor->getDeclContext();
auto *nominalDecl = DC->getSelfNominalTypeDecl();
// Global variable accessors are not @_transparent.
if (!nominalDecl)
return false;
// Accessors for resilient properties are not @_transparent.
if (storage->isResilient())
return false;
// Accessors for classes with @objc ancestry are not @_transparent,
// since they use a field offset variable which is not exported.
if (auto *classDecl = dyn_cast<ClassDecl>(nominalDecl))
if (classDecl->checkAncestry(AncestryFlags::ObjC))
return false;
// Accessors synthesized on-demand are never transaprent.
if (accessor->hasForcedStaticDispatch())
return false;
if (accessor->getAccessorKind() == AccessorKind::Get ||
accessor->getAccessorKind() == AccessorKind::Set) {
// Getters and setters for lazy properties are not @_transparent.
if (storage->getAttrs().hasAttribute<LazyAttr>())
return false;
// Getters/setters for a property with a wrapper are not @_transparent if
// the backing variable has more-restrictive access than the original
// property. The same goes for its storage wrapper.
if (auto var = dyn_cast<VarDecl>(storage)) {
if (auto backingVar = var->getPropertyWrapperBackingProperty()) {
if (backingVar->getFormalAccess() < var->getFormalAccess())
return false;
}
if (auto original = var->getOriginalWrappedProperty(
PropertyWrapperSynthesizedPropertyKind::StorageWrapper)) {
auto backingVar = original->getPropertyWrapperBackingProperty();
if (backingVar->getFormalAccess() < var->getFormalAccess())
return false;
}
}
}
switch (accessor->getAccessorKind()) {
case AccessorKind::Get:
break;
case AccessorKind::Set:
switch (storage->getWriteImpl()) {
case WriteImplKind::Set:
// Setters for property wrapper are OK, unless there are observers.
// FIXME: This should be folded into the WriteImplKind below.
if (auto var = dyn_cast<VarDecl>(storage)) {
if (var->hasAttachedPropertyWrapper()) {
if (var->getParsedAccessor(AccessorKind::DidSet) ||
var->getParsedAccessor(AccessorKind::WillSet))
return false;
break;
} else if (var->getOriginalWrappedProperty(
PropertyWrapperSynthesizedPropertyKind::StorageWrapper)) {
break;
}
}
// Anything else should not have a synthesized setter.
LLVM_FALLTHROUGH;
case WriteImplKind::Immutable:
llvm_unreachable("should not be synthesizing accessor in this case");
case WriteImplKind::StoredWithObservers:
case WriteImplKind::InheritedWithObservers:
// Setters for observed properties are not @_transparent (because the
// observers are private) and cannot be referenced from a transparent
// method).
return false;
case WriteImplKind::Stored:
case WriteImplKind::MutableAddress:
case WriteImplKind::Modify:
break;
}
break;
case AccessorKind::Read:
case AccessorKind::Modify:
break;
case AccessorKind::WillSet:
case AccessorKind::DidSet:
case AccessorKind::Address:
case AccessorKind::MutableAddress:
llvm_unreachable("bad synthesized function kind");
}
return true;
}
llvm::Expected<VarDecl *>
LazyStoragePropertyRequest::evaluate(Evaluator &evaluator,
VarDecl *VD) const {
assert(isa<SourceFile>(VD->getDeclContext()->getModuleScopeContext()));
assert(VD->getAttrs().hasAttribute<LazyAttr>());
auto &Context = VD->getASTContext();
// Create the storage property as an optional of VD's type.
SmallString<64> NameBuf;
NameBuf += "$__lazy_storage_$_";
NameBuf += VD->getName().str();
auto StorageName = Context.getIdentifier(NameBuf);
auto StorageInterfaceTy = OptionalType::get(VD->getInterfaceType());
auto StorageTy = OptionalType::get(VD->getType());
auto *Storage = new (Context) VarDecl(/*IsStatic*/false, VarDecl::Introducer::Var,
/*IsCaptureList*/false, VD->getLoc(),
StorageName,
VD->getDeclContext());
Storage->setInterfaceType(StorageInterfaceTy);
Storage->setLazyStorageProperty(true);
Storage->setUserAccessible(false);
// The storage is implicit and private.
Storage->setImplicit();
Storage->overwriteAccess(AccessLevel::Private);
Storage->overwriteSetterAccess(AccessLevel::Private);
addMemberToContextIfNeeded(Storage, VD->getDeclContext(), VD);
// Create the pattern binding decl for the storage decl. This will get
// default initialized to nil.
Pattern *PBDPattern = new (Context) NamedPattern(Storage, /*implicit*/true);
PBDPattern->setType(StorageTy);
PBDPattern = TypedPattern::createImplicit(Context, PBDPattern, StorageTy);
auto *InitExpr = new (Context) NilLiteralExpr(SourceLoc(), /*Implicit=*/true);
InitExpr->setType(Storage->getType());
auto *PBD = PatternBindingDecl::createImplicit(
Context, StaticSpellingKind::None, PBDPattern, InitExpr,
VD->getDeclContext(), /*VarLoc*/ VD->getLoc());
PBD->setInitializerChecked(0);
addMemberToContextIfNeeded(PBD, VD->getDeclContext(), Storage);
return Storage;
}
/// Synthesize a computed property `$foo` for a property with an attached
/// wrapper that has a `projectedValue` property.
static VarDecl *synthesizePropertyWrapperStorageWrapperProperty(
ASTContext &ctx, VarDecl *var, Type wrapperType,
VarDecl *wrapperVar) {
// If the original property has a @_projectedValueProperty attribute, use
// that to find the storage wrapper property.
if (auto attr = var->getAttrs().getAttribute<ProjectedValuePropertyAttr>()){
SmallVector<ValueDecl *, 2> declsFound;
auto projectionName = attr->ProjectionPropertyName;
auto dc = var->getDeclContext();
if (dc->isTypeContext()) {
dc->lookupQualified(dc->getSelfNominalTypeDecl(), projectionName,
NL_QualifiedDefault, declsFound);
} else if (dc->isModuleScopeContext()) {
dc->lookupQualified(dc->getParentModule(), projectionName,
NL_QualifiedDefault, declsFound);
} else {
llvm_unreachable("Property wrappers don't work in local contexts");
}
if (declsFound.size() == 1 && isa<VarDecl>(declsFound.front())) {
auto property = cast<VarDecl>(declsFound.front());
property->setOriginalWrappedProperty(var);
return property;
}
ctx.Diags.diagnose(attr->getLocation(),
diag::property_wrapper_projection_value_missing,
projectionName);
attr->setInvalid();
}
// Compute the name of the storage type.
SmallString<64> nameBuf;
nameBuf = "$";
nameBuf += var->getName().str();
Identifier name = ctx.getIdentifier(nameBuf);
// Determine the type of the property.
Type propertyType = wrapperType->getTypeOfMember(
var->getModuleContext(), wrapperVar,
wrapperVar->getValueInterfaceType());
// Form the property.
auto dc = var->getDeclContext();
VarDecl *property = new (ctx) VarDecl(/*IsStatic=*/var->isStatic(),
VarDecl::Introducer::Var,
/*IsCaptureList=*/false,
var->getLoc(),
name, dc);
property->setInterfaceType(propertyType);
property->setImplicit();
property->setOriginalWrappedProperty(var);
addMemberToContextIfNeeded(property, dc, var);
// Create the pattern binding declaration for the property.
Pattern *pbdPattern = new (ctx) NamedPattern(property, /*implicit=*/true);
pbdPattern->setType(propertyType);
pbdPattern = TypedPattern::createImplicit(ctx, pbdPattern, propertyType);
auto pbd = PatternBindingDecl::createImplicit(
ctx, property->getCorrectStaticSpelling(), pbdPattern,
/*init*/nullptr, dc, SourceLoc());
addMemberToContextIfNeeded(pbd, dc, var);
pbd->setStatic(var->isStatic());
// Determine the access level for the property.
property->overwriteAccess(var->getFormalAccess());
// Determine setter access.
property->overwriteSetterAccess(var->getSetterFormalAccess());
// Add the accessors we need.
bool hasSetter = wrapperVar->isSettable(nullptr) &&
wrapperVar->isSetterAccessibleFrom(var->getInnermostDeclContext());
if (hasSetter)
property->setImplInfo(StorageImplInfo::getMutableComputed());
else
property->setImplInfo(StorageImplInfo::getImmutableComputed());
var->getAttrs().add(
new (ctx) ProjectedValuePropertyAttr(name, SourceLoc(), SourceRange(),
/*Implicit=*/true));
return property;
}
static void typeCheckSynthesizedWrapperInitializer(
PatternBindingDecl *pbd, VarDecl *backingVar, PatternBindingDecl *parentPBD,
Expr *&initializer) {
// Figure out the context in which the initializer was written.
DeclContext *originalDC = parentPBD->getDeclContext();
if (!originalDC->isLocalContext()) {
auto initContext =
cast_or_null<PatternBindingInitializer>(parentPBD->getInitContext(0));
if (initContext)
originalDC = initContext;
}
// Type-check the initialization.
ASTContext &ctx = pbd->getASTContext();
auto &tc = *static_cast<TypeChecker *>(ctx.getLazyResolver());
tc.typeCheckExpression(initializer, originalDC);
if (auto initializerContext =
dyn_cast_or_null<Initializer>(
pbd->getPatternEntryForVarDecl(backingVar).getInitContext())) {
tc.contextualizeInitializer(initializerContext, initializer);
}
tc.checkPropertyWrapperErrorHandling(pbd, initializer);
}
static PropertyWrapperMutability::Value
getGetterMutatingness(VarDecl *var) {
return var->isGetterMutating()
? PropertyWrapperMutability::Mutating
: PropertyWrapperMutability::Nonmutating;
}
static PropertyWrapperMutability::Value
getSetterMutatingness(VarDecl *var, DeclContext *dc) {
if (!var->isSettable(nullptr) ||
!var->isSetterAccessibleFrom(dc))
return PropertyWrapperMutability::DoesntExist;
return var->isSetterMutating()
? PropertyWrapperMutability::Mutating
: PropertyWrapperMutability::Nonmutating;
}
llvm::Expected<Optional<PropertyWrapperMutability>>
PropertyWrapperMutabilityRequest::evaluate(Evaluator &,
VarDecl *var) const {
VarDecl *originalVar = var;
unsigned numWrappers = originalVar->getAttachedPropertyWrappers().size();
bool isProjectedValue = false;
if (numWrappers < 1) {
originalVar = var->getOriginalWrappedProperty(
PropertyWrapperSynthesizedPropertyKind::StorageWrapper);
if (!originalVar)
return None;
numWrappers = originalVar->getAttachedPropertyWrappers().size();
isProjectedValue = true;
}
if (var->getParsedAccessor(AccessorKind::Get))
return None;
if (var->getParsedAccessor(AccessorKind::Set))
return None;
// Figure out which member we're looking through.
auto varMember = isProjectedValue
? &PropertyWrapperTypeInfo::projectedValueVar
: &PropertyWrapperTypeInfo::valueVar;
// Start with the traits from the outermost wrapper.
auto firstWrapper = originalVar->getAttachedPropertyWrapperTypeInfo(0);
if (firstWrapper.*varMember == nullptr)
return None;
PropertyWrapperMutability result;
result.Getter = getGetterMutatingness(firstWrapper.*varMember);
result.Setter = getSetterMutatingness(firstWrapper.*varMember,
var->getInnermostDeclContext());
// Compose the traits of the following wrappers.
for (unsigned i = 1; i < numWrappers && !isProjectedValue; ++i) {
assert(var == originalVar);
auto wrapper = var->getAttachedPropertyWrapperTypeInfo(i);
if (!wrapper.valueVar)
return None;
PropertyWrapperMutability nextResult;
nextResult.Getter =
result.composeWith(getGetterMutatingness(wrapper.valueVar));
// A property must have a getter, so we can't compose a wrapper that
// exposes a mutating getter wrapped inside a get-only wrapper.
if (nextResult.Getter == PropertyWrapperMutability::DoesntExist) {
auto &ctx = var->getASTContext();
ctx.Diags.diagnose(var->getAttachedPropertyWrappers()[i]->getLocation(),
diag::property_wrapper_mutating_get_composed_to_get_only,
var->getAttachedPropertyWrappers()[i]->getTypeLoc(),
var->getAttachedPropertyWrappers()[i-1]->getTypeLoc());
return None;
}
nextResult.Setter =
result.composeWith(getSetterMutatingness(wrapper.valueVar,
var->getInnermostDeclContext()));
result = nextResult;
}
assert(result.Getter != PropertyWrapperMutability::DoesntExist
&& "getter must exist");
return result;
}
llvm::Expected<PropertyWrapperBackingPropertyInfo>
PropertyWrapperBackingPropertyInfoRequest::evaluate(Evaluator &evaluator,
VarDecl *var) const {
// Determine the type of the backing property.
auto wrapperType = var->getPropertyWrapperBackingPropertyType();
if (!wrapperType || wrapperType->hasError())
return PropertyWrapperBackingPropertyInfo();
auto wrapperInfo = var->getAttachedPropertyWrapperTypeInfo(0);
if (!wrapperInfo)
return PropertyWrapperBackingPropertyInfo();
// Compute the name of the storage type.
ASTContext &ctx = var->getASTContext();
SmallString<64> nameBuf;
nameBuf = "_";
nameBuf += var->getName().str();
Identifier name = ctx.getIdentifier(nameBuf);
// Determine the type of the storage.
auto dc = var->getDeclContext();
Type storageInterfaceType = wrapperType;
Type storageType = dc->mapTypeIntoContext(storageInterfaceType);
// Make sure that the property type matches the value of the
// wrapper type.
if (!storageInterfaceType->hasError()) {
Type expectedPropertyType =
computeWrappedValueType(var, storageInterfaceType);
Type propertyType = var->getValueInterfaceType();
assert(propertyType);
if (!expectedPropertyType->hasError() &&
!propertyType->hasError() &&
!propertyType->isEqual(expectedPropertyType)) {
var->diagnose(diag::property_wrapper_incompatible_property,
propertyType, wrapperType);
if (auto nominalWrapper = wrapperType->getAnyNominal()) {
nominalWrapper->diagnose(diag::property_wrapper_declared_here,
nominalWrapper->getFullName());
}
}
}
// Create the backing storage property and note it in the cache.
VarDecl *backingVar = new (ctx) VarDecl(/*IsStatic=*/var->isStatic(),
VarDecl::Introducer::Var,
/*IsCaptureList=*/false,
var->getLoc(),
name, dc);
backingVar->setInterfaceType(storageInterfaceType);
backingVar->setImplicit();
backingVar->setOriginalWrappedProperty(var);
// The backing storage is 'private'.
backingVar->overwriteAccess(AccessLevel::Private);
backingVar->overwriteSetterAccess(AccessLevel::Private);
addMemberToContextIfNeeded(backingVar, dc, var);
// Create the pattern binding declaration for the backing property.
Pattern *pbdPattern = new (ctx) NamedPattern(backingVar, /*implicit=*/true);
pbdPattern->setType(storageType);
pbdPattern = TypedPattern::createImplicit(ctx, pbdPattern, storageType);
auto pbd = PatternBindingDecl::createImplicit(
ctx, var->getCorrectStaticSpelling(), pbdPattern,
/*init*/ nullptr, dc, SourceLoc());
addMemberToContextIfNeeded(pbd, dc, var);
pbd->setStatic(var->isStatic());
// Take the initializer from the original property.
auto parentPBD = var->getParentPatternBinding();
unsigned patternNumber = parentPBD->getPatternEntryIndexForVarDecl(var);
// Force the default initializer to come into existence, if there is one,
// and the wrapper doesn't provide its own.
if (!parentPBD->isInitialized(patternNumber)
&& parentPBD->isDefaultInitializable(patternNumber)
&& !wrapperInfo.defaultInit) {
auto &tc = *static_cast<TypeChecker *>(ctx.getLazyResolver());
auto ty = parentPBD->getPattern(patternNumber)->getType();
if (auto defaultInit = tc.buildDefaultInitializer(ty))
parentPBD->setInit(patternNumber, defaultInit);
}
if (parentPBD->isInitialized(patternNumber) &&
!parentPBD->isInitializerChecked(patternNumber)) {
auto &tc = *static_cast<TypeChecker *>(ctx.getLazyResolver());
tc.typeCheckPatternBinding(parentPBD, patternNumber);
}
Expr *originalInitialValue = nullptr;
if (Expr *init = parentPBD->getInit(patternNumber)) {
pbd->setInit(0, init);
pbd->setInitializerChecked(0);
originalInitialValue = findOriginalPropertyWrapperInitialValue(var, init);
} else if (!parentPBD->isInitialized(patternNumber) &&
wrapperInfo.defaultInit) {
// FIXME: Record this expression somewhere so that DI can perform the
// initialization itself.
auto typeExpr = TypeExpr::createImplicit(storageType, ctx);
Expr *initializer = CallExpr::createImplicit(ctx, typeExpr, {}, { });
typeCheckSynthesizedWrapperInitializer(pbd, backingVar, parentPBD,
initializer);
pbd->setInit(0, initializer);
pbd->setInitializerChecked(0);
}
// If there is a projection property (projectedValue) in the wrapper,
// synthesize a computed property for '$foo'.
VarDecl *storageVar = nullptr;
if (wrapperInfo.projectedValueVar) {
storageVar = synthesizePropertyWrapperStorageWrapperProperty(
ctx, var, storageInterfaceType, wrapperInfo.projectedValueVar);
}
// Get the property wrapper information.
if (!var->allAttachedPropertyWrappersHaveInitialValueInit() &&
!originalInitialValue) {
return PropertyWrapperBackingPropertyInfo(
backingVar, storageVar, nullptr, nullptr, nullptr);
}
// Form the initialization of the backing property from a value of the
// original property's type.
OpaqueValueExpr *origValue =
new (ctx) OpaqueValueExpr(var->getSourceRange(), var->getType(),
/*isPlaceholder=*/true);
Expr *initializer = buildPropertyWrapperInitialValueCall(
var, storageType, origValue,
/*ignoreAttributeArgs=*/!originalInitialValue);
typeCheckSynthesizedWrapperInitializer(
pbd, backingVar, parentPBD, initializer);
return PropertyWrapperBackingPropertyInfo(
backingVar, storageVar, originalInitialValue, initializer, origValue);
}
/// Given a storage declaration in a protocol, set it up with the right
/// StorageImpl and add the right set of opaque accessors.
static void finishProtocolStorageImplInfo(AbstractStorageDecl *storage,
StorageImplInfo &info) {
if (auto *var = dyn_cast<VarDecl>(storage)) {
SourceLoc typeLoc;
if (auto *repr = var->getTypeRepr())
typeLoc = repr->getLoc();
if (info.hasStorage()) {
// Protocols cannot have stored properties.
if (var->isLet()) {
var->diagnose(diag::protocol_property_must_be_computed_var)
.fixItReplace(var->getParentPatternBinding()->getLoc(), "var")
.fixItInsertAfter(typeLoc, " { get }");
} else {
auto diag = var->diagnose(diag::protocol_property_must_be_computed);
auto braces = var->getBracesRange();
if (braces.isValid())
diag.fixItReplace(braces, "{ get <#set#> }");
else
diag.fixItInsertAfter(typeLoc, " { get <#set#> }");
}
}
}
auto protocol = cast<ProtocolDecl>(storage->getDeclContext());
if (protocol->isObjC()) {
info = StorageImplInfo::getComputed(info.supportsMutation());
} else {
info = StorageImplInfo::getOpaque(info.supportsMutation(),
storage->getOpaqueReadOwnership());
}
}
/// This emits a diagnostic with a fixit to remove the attribute.
template<typename ...ArgTypes>
void diagnoseAndRemoveAttr(Decl *D, DeclAttribute *attr,
ArgTypes &&...Args) {
auto &ctx = D->getASTContext();
ctx.Diags.diagnose(attr->getLocation(), std::forward<ArgTypes>(Args)...)
.fixItRemove(attr->getRangeWithAt());
}
static void finishLazyVariableImplInfo(VarDecl *var,
StorageImplInfo &info) {
auto *attr = var->getAttrs().getAttribute<LazyAttr>();
// It cannot currently be used on let's since we don't have a mutability model
// that supports it.
if (var->isLet())
diagnoseAndRemoveAttr(var, attr, diag::lazy_not_on_let);
// lazy must have an initializer.
if (!var->getParentInitializer())
diagnoseAndRemoveAttr(var, attr, diag::lazy_requires_initializer);
bool invalid = false;
if (isa<ProtocolDecl>(var->getDeclContext())) {
diagnoseAndRemoveAttr(var, attr, diag::lazy_not_in_protocol);
invalid = true;
}
// Lazy properties must be written as stored properties in the source.
if (!info.isSimpleStored()) {
diagnoseAndRemoveAttr(var, attr,
info.hasStorage()
? diag::lazy_not_observable
: diag::lazy_not_on_computed);
invalid = true;
}
// The pattern binding must only bind a single variable.
if (!var->getParentPatternBinding()->getSingleVar())
diagnoseAndRemoveAttr(var, attr, diag::lazy_requires_single_var);
if (!invalid)
info = StorageImplInfo::getMutableComputed();
}
static void finishPropertyWrapperImplInfo(VarDecl *var,
StorageImplInfo &info) {
auto parentSF = var->getDeclContext()->getParentSourceFile();
if (!parentSF)
return;
// Properties with wrappers must not declare a getter or setter.
if (!info.hasStorage() && parentSF->Kind != SourceFileKind::Interface) {
auto &ctx = parentSF->getASTContext();
for (auto attr : var->getAttrs().getAttributes<CustomAttr>())
ctx.Diags.diagnose(attr->getLocation(), diag::property_wrapper_computed);
return;
}
bool wrapperSetterIsUsable = false;
if (var->getParsedAccessor(AccessorKind::Set)) {
wrapperSetterIsUsable = true;
} else if (parentSF && parentSF->Kind != SourceFileKind::Interface
&& !var->isLet()) {
if (auto comp = var->getPropertyWrapperMutability()) {
wrapperSetterIsUsable =
comp->Setter != PropertyWrapperMutability::DoesntExist;
} else {
wrapperSetterIsUsable = true;
}
}
if (wrapperSetterIsUsable)
info = StorageImplInfo::getMutableComputed();
else
info = StorageImplInfo::getImmutableComputed();
}
static void finishNSManagedImplInfo(VarDecl *var,
StorageImplInfo &info) {
auto *attr = var->getAttrs().getAttribute<NSManagedAttr>();
if (var->isLet())
diagnoseAndRemoveAttr(var, attr, diag::attr_NSManaged_let_property);
auto diagnoseNotStored = [&](unsigned kind) {
diagnoseAndRemoveAttr(var, attr, diag::attr_NSManaged_not_stored, kind);
};
// @NSManaged properties must be written as stored.
if (info.isSimpleStored()) {
// @NSManaged properties end up being computed; complain if there is
// an initializer.
if (var->getParentInitializer()) {
auto &Diags = var->getASTContext().Diags;
Diags.diagnose(attr->getLocation(), diag::attr_NSManaged_initial_value)
.highlight(var->getParentInitializer()->getSourceRange());
}
// Otherwise, ok.
info = StorageImplInfo::getMutableComputed();
} else if (info.getReadImpl() == ReadImplKind::Address ||
info.getWriteImpl() == WriteImplKind::MutableAddress) {
diagnoseNotStored(/*addressed*/ 2);
} else if (info.getWriteImpl() == WriteImplKind::StoredWithObservers ||
info.getWriteImpl() == WriteImplKind::InheritedWithObservers) {
diagnoseNotStored(/*observing*/ 1);
} else {
diagnoseNotStored(/*computed*/ 0);
}
}
static void finishStorageImplInfo(AbstractStorageDecl *storage,
StorageImplInfo &info) {
auto dc = storage->getDeclContext();
if (auto var = dyn_cast<VarDecl>(storage)) {
if (!info.hasStorage()) {
if (auto *init = var->getParentInitializer()) {
auto &Diags = var->getASTContext().Diags;
Diags.diagnose(init->getLoc(), diag::getset_init)
.highlight(init->getSourceRange());
}
}
if (var->getAttrs().hasAttribute<LazyAttr>()) {
finishLazyVariableImplInfo(var, info);
} else if (var->getAttrs().hasAttribute<NSManagedAttr>()) {
finishNSManagedImplInfo(var, info);
} else if (var->hasAttachedPropertyWrapper()) {
finishPropertyWrapperImplInfo(var, info);
}
}
if (isa<ProtocolDecl>(dc))
finishProtocolStorageImplInfo(storage, info);
// If we have a stored property in an unsupported context, diagnose
// and change it to computed to avoid confusing SILGen.
// Note: Stored properties in protocols are diagnosed in
// finishProtocolStorageImplInfo().
if (info.hasStorage() && !storage->isStatic()) {
if (isa<EnumDecl>(dc)) {
storage->diagnose(diag::enum_stored_property);
info = StorageImplInfo::getMutableComputed();
} else if (isa<ExtensionDecl>(dc) &&
!storage->getAttrs().getAttribute<DynamicReplacementAttr>()) {
storage->diagnose(diag::extension_stored_property);
info = (info.supportsMutation()
? StorageImplInfo::getMutableComputed()
: StorageImplInfo::getImmutableComputed());
}
}
}
/// Gets the storage info of the provided storage decl if it has the
/// @_hasStorage attribute and it's not in SIL mode.
///
/// In this case, we say the decl is:
///
/// Read:
/// - Stored, always
/// Write:
/// - Stored, if the decl is a 'var'.
/// - StoredWithObservers, if the decl has a setter
/// - This indicates that the original decl had a 'didSet' and/or 'willSet'
/// - InheritedWithObservers, if the decl has a setter and is an overridde.
/// - Immutable, if the decl is a 'let' or it does not have a setter.
/// ReadWrite:
/// - Stored, if the decl has no accessors listed.
/// - Immutable, if the decl is a 'let' or it does not have a setter.
/// - MaterializeToTemporary, if the decl has a setter.
static StorageImplInfo classifyWithHasStorageAttr(VarDecl *var) {
WriteImplKind writeImpl;
ReadWriteImplKind readWriteImpl;
if (var->getParsedAccessor(AccessorKind::Get) &&
var->getParsedAccessor(AccessorKind::Set)) {
// If we see `@_hasStorage var x: T { get set }`, then our property has
// willSet/didSet observers.
writeImpl = var->getAttrs().hasAttribute<OverrideAttr>() ?
WriteImplKind::InheritedWithObservers :
WriteImplKind::StoredWithObservers;
readWriteImpl = ReadWriteImplKind::MaterializeToTemporary;
} else if (var->isLet()) {
writeImpl = WriteImplKind::Immutable;
readWriteImpl = ReadWriteImplKind::Immutable;
} else {
// Default to stored writes.
writeImpl = WriteImplKind::Stored;
readWriteImpl = ReadWriteImplKind::Stored;
}
// Always force Stored reads if @_hasStorage is present.
return StorageImplInfo(ReadImplKind::Stored, writeImpl, readWriteImpl);
}
llvm::Expected<StorageImplInfo>
StorageImplInfoRequest::evaluate(Evaluator &evaluator,
AbstractStorageDecl *storage) const {
if (auto *param = dyn_cast<ParamDecl>(storage)) {
return StorageImplInfo::getSimpleStored(
param->isInOut()
? StorageIsMutable
: StorageIsNotMutable);
}
if (auto *var = dyn_cast<VarDecl>(storage)) {
// Allow the @_hasStorage attribute to override all the accessors we parsed
// when making the final classification.
if (var->getAttrs().hasAttribute<HasStorageAttr>()) {
// The SIL rules for @_hasStorage are slightly different from the non-SIL
// rules. In SIL mode, @_hasStorage marks that the type is simply stored,
// and the only thing that determines mutability is the existence of the
// setter.
//
// FIXME: SIL should not be special cased here. The behavior should be
// consistent between SIL and non-SIL.
// The strategy here should be to keep track of all opaque accessors
// along with enough information to access the storage trivially
// if allowed. This could be a representational change to
// StorageImplInfo such that it keeps a bitset of listed accessors
// and dynamically determines the access strategy from that.
auto *SF = storage->getDeclContext()->getParentSourceFile();
if (SF && SF->Kind == SourceFileKind::SIL)
return StorageImplInfo::getSimpleStored(
var->getParsedAccessor(AccessorKind::Set)
? StorageIsMutable
: StorageIsNotMutable);
return classifyWithHasStorageAttr(var);
}
}
bool hasWillSet = storage->getParsedAccessor(AccessorKind::WillSet);
bool hasDidSet = storage->getParsedAccessor(AccessorKind::DidSet);
bool hasSetter = storage->getParsedAccessor(AccessorKind::Set);
bool hasModify = storage->getParsedAccessor(AccessorKind::Modify);
bool hasMutableAddress = storage->getParsedAccessor(AccessorKind::MutableAddress);
// 'get', 'read', and a non-mutable addressor are all exclusive.
ReadImplKind readImpl;
if (storage->getParsedAccessor(AccessorKind::Get)) {
readImpl = ReadImplKind::Get;
} else if (storage->getParsedAccessor(AccessorKind::Read)) {
readImpl = ReadImplKind::Read;
} else if (storage->getParsedAccessor(AccessorKind::Address)) {
readImpl = ReadImplKind::Address;
// If there's a writing accessor of any sort, there must also be a
// reading accessor.
} else if (hasSetter || hasModify || hasMutableAddress) {
readImpl = ReadImplKind::Get;
// Subscripts always have to have some sort of accessor; they can't be
// purely stored.
} else if (isa<SubscriptDecl>(storage)) {
readImpl = ReadImplKind::Get;
// Check if we have observers.
} else if (hasWillSet || hasDidSet) {
if (storage->getAttrs().hasAttribute<OverrideAttr>()) {
readImpl = ReadImplKind::Inherited;
} else {
readImpl = ReadImplKind::Stored;
}
// Otherwise, it's stored.
} else {
readImpl = ReadImplKind::Stored;
}
// Prefer using 'set' and 'modify' over a mutable addressor.
WriteImplKind writeImpl;
ReadWriteImplKind readWriteImpl;
if (hasSetter) {
writeImpl = WriteImplKind::Set;
if (hasModify) {
readWriteImpl = ReadWriteImplKind::Modify;
} else {
readWriteImpl = ReadWriteImplKind::MaterializeToTemporary;
}
} else if (hasModify) {
writeImpl = WriteImplKind::Modify;
readWriteImpl = ReadWriteImplKind::Modify;
} else if (hasMutableAddress) {
writeImpl = WriteImplKind::MutableAddress;
readWriteImpl = ReadWriteImplKind::MutableAddress;
// Check if we have observers.
} else if (readImpl == ReadImplKind::Inherited) {
writeImpl = WriteImplKind::InheritedWithObservers;
readWriteImpl = ReadWriteImplKind::MaterializeToTemporary;
// Otherwise, it's stored.
} else if (readImpl == ReadImplKind::Stored &&
!cast<VarDecl>(storage)->isLet()) {
if (hasWillSet || hasDidSet) {
writeImpl = WriteImplKind::StoredWithObservers;
readWriteImpl = ReadWriteImplKind::MaterializeToTemporary;
} else {
writeImpl = WriteImplKind::Stored;
readWriteImpl = ReadWriteImplKind::Stored;
}
// Otherwise, it's immutable.
} else {
writeImpl = WriteImplKind::Immutable;
readWriteImpl = ReadWriteImplKind::Immutable;
}
StorageImplInfo info(readImpl, writeImpl, readWriteImpl);
finishStorageImplInfo(storage, info);
return info;
}