blob: 5c81b9b87f48ab3400e1809009ff61cc11df51fe [file] [log] [blame]
//===--- TypeCheckConcurrency.cpp - Concurrency ---------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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 type checking support for Swift's concurrency model.
//
//===----------------------------------------------------------------------===//
#include "TypeCheckConcurrency.h"
#include "TypeChecker.h"
#include "TypeCheckType.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/NameLookupRequests.h"
#include "swift/AST/TypeCheckRequests.h"
using namespace swift;
/// Determine whether it makes sense to infer an attribute in the given
/// context.
static bool shouldInferAttributeInContext(const DeclContext *dc) {
auto sourceFile = dc->getParentSourceFile();
if (!sourceFile)
return false;
switch (sourceFile->Kind) {
case SourceFileKind::Interface:
case SourceFileKind::SIL:
return false;
case SourceFileKind::Library:
case SourceFileKind::Main:
return true;
}
}
/// Check whether the @asyncHandler attribute can be applied to the given
/// function declaration.
///
/// \param diagnose Whether to emit a diagnostic when a problem is encountered.
///
/// \returns \c true if there was a problem with adding the attribute, \c false
/// otherwise.
static bool checkAsyncHandler(FuncDecl *func, bool diagnose) {
if (!func->getResultInterfaceType()->isVoid()) {
if (diagnose) {
func->diagnose(diag::asynchandler_returns_value)
.highlight(func->getResultTypeSourceRange());
}
return true;
}
if (func->hasThrows()) {
if (diagnose) {
func->diagnose(diag::asynchandler_throws)
.fixItRemove(func->getThrowsLoc());
}
return true;
}
if (func->hasAsync()) {
if (diagnose) {
func->diagnose(diag::asynchandler_async)
.fixItRemove(func->getAsyncLoc());
}
return true;
}
for (auto param : *func->getParameters()) {
if (param->isInOut()) {
if (diagnose) {
param->diagnose(diag::asynchandler_inout_parameter)
.fixItRemove(param->getSpecifierLoc());
}
return true;
}
if (auto fnType = param->getInterfaceType()->getAs<FunctionType>()) {
if (fnType->isNoEscape()) {
if (diagnose) {
param->diagnose(diag::asynchandler_noescape_closure_parameter);
}
return true;
}
}
}
if (func->isMutating()) {
if (diagnose) {
auto diag = func->diagnose(diag::asynchandler_mutating);
if (auto mutatingAttr = func->getAttrs().getAttribute<MutatingAttr>()) {
diag.fixItRemove(mutatingAttr->getRange());
}
}
return true;
}
return false;
}
void swift::addAsyncNotes(AbstractFunctionDecl const* func) {
assert(func);
if (!isa<DestructorDecl>(func))
func->diagnose(diag::note_add_async_to_function, func->getName());
// TODO: we need a source location for effects attributes so that we
// can also emit a fix-it that inserts 'async' in the right place for func.
// It's possibly a bit tricky to get the right source location from
// just the AbstractFunctionDecl, but it's important to circle-back
// to this.
if (func->canBeAsyncHandler()) {
func->diagnose(
diag::note_add_asynchandler_to_function, func->getName())
.fixItInsert(func->getAttributeInsertionLoc(false), "@asyncHandler ");
}
}
bool IsAsyncHandlerRequest::evaluate(
Evaluator &evaluator, FuncDecl *func) const {
// Check whether the attribute was explicitly specified.
if (auto attr = func->getAttrs().getAttribute<AsyncHandlerAttr>()) {
// Check for well-formedness.
if (checkAsyncHandler(func, /*diagnose=*/true)) {
attr->setInvalid();
return false;
}
return true;
}
if (!shouldInferAttributeInContext(func->getDeclContext()))
return false;
// Are we in a context where inference is possible?
auto dc = func->getDeclContext();
if (!dc->getSelfClassDecl() || !dc->getParentSourceFile() || !func->hasBody())
return false;
// Is it possible to infer @asyncHandler for this function at all?
if (!func->canBeAsyncHandler())
return false;
if (!dc->getSelfClassDecl()->isActor())
return false;
// Add an implicit @asyncHandler attribute and return true. We're done.
auto addImplicitAsyncHandlerAttr = [&] {
func->getAttrs().add(new (func->getASTContext()) AsyncHandlerAttr(true));
return true;
};
// Check whether any of the conformances in the context of the function
// implies @asyncHandler.
{
auto idc = cast<IterableDeclContext>(dc->getAsDecl());
auto conformances = evaluateOrDefault(
dc->getASTContext().evaluator,
LookupAllConformancesInContextRequest{idc}, { });
for (auto conformance : conformances) {
auto protocol = conformance->getProtocol();
for (auto found : protocol->lookupDirect(func->getName())) {
if (!isa<ProtocolDecl>(found->getDeclContext()))
continue;
auto requirement = dyn_cast<FuncDecl>(found);
if (!requirement)
continue;
if (!requirement->isAsyncHandler())
continue;
auto witness = conformance->getWitnessDecl(requirement);
if (witness != func)
continue;
return addImplicitAsyncHandlerAttr();
}
}
}
// Look through dynamic replacements.
if (auto replaced = func->getDynamicallyReplacedDecl()) {
if (auto replacedFunc = dyn_cast<FuncDecl>(replaced))
if (replacedFunc->isAsyncHandler())
return addImplicitAsyncHandlerAttr();
}
return false;
}
bool CanBeAsyncHandlerRequest::evaluate(
Evaluator &evaluator, FuncDecl *func) const {
return !checkAsyncHandler(func, /*diagnose=*/false);
}
bool IsActorRequest::evaluate(
Evaluator &evaluator, ClassDecl *classDecl) const {
// If concurrency is not enabled, we don't have actors.
auto actorAttr = classDecl->getAttrs().getAttribute<ActorAttr>();
// If there is a superclass, we can infer actor-ness from it.
if (auto superclassDecl = classDecl->getSuperclassDecl()) {
// The superclass is an actor, so we are, too.
if (superclassDecl->isActor())
return true;
// The superclass is 'NSObject', which is known to have no state and no
// superclass.
if (superclassDecl->isNSObject() && actorAttr != nullptr)
return true;
// This class cannot be an actor; complain if the 'actor' modifier was
// provided.
if (actorAttr) {
classDecl->diagnose(
diag::actor_with_nonactor_superclass, superclassDecl->getName())
.highlight(actorAttr->getRange());
}
return false;
}
return actorAttr != nullptr;
}
bool IsDefaultActorRequest::evaluate(
Evaluator &evaluator, ClassDecl *classDecl) const {
// If the class isn't an actor class, it's not a default actor.
if (!classDecl->isActor())
return false;
// If there is a superclass, and it's an actor class, we defer
// the decision to it.
if (auto superclassDecl = classDecl->getSuperclassDecl()) {
// If the superclass is an actor, we inherit its default-actor-ness.
if (superclassDecl->isActor())
return superclassDecl->isDefaultActor();
// If the superclass is not an actor class, it can only be
// a default actor if it's NSObject. (For now, other classes simply
// can't be actors at all.) We don't need to diagnose this; we
// should've done that already in isActor().
if (!superclassDecl->isNSObject())
return false;
}
// If the class has explicit custom-actor methods, it's not
// a default actor.
if (classDecl->hasExplicitCustomActorMethods())
return false;
return true;
}
static bool isDeclNotAsAccessibleAsParent(ValueDecl *decl,
NominalTypeDecl *parent) {
return decl->getFormalAccess() <
std::min(parent->getFormalAccess(), AccessLevel::Public);
}
VarDecl *GlobalActorInstanceRequest::evaluate(
Evaluator &evaluator, NominalTypeDecl *nominal) const {
auto globalActorAttr = nominal->getAttrs().getAttribute<GlobalActorAttr>();
if (!globalActorAttr)
return nullptr;
// Ensure that the actor protocol has been loaded.
ASTContext &ctx = nominal->getASTContext();
auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
if (!actorProto) {
nominal->diagnose(diag::concurrency_lib_missing, "Actor");
return nullptr;
}
// Global actors have a static property "shared" that provides an actor
// instance. The value must
SmallVector<ValueDecl *, 4> decls;
nominal->lookupQualified(
nominal, DeclNameRef(ctx.Id_shared), NL_QualifiedDefault, decls);
VarDecl *sharedVar = nullptr;
llvm::TinyPtrVector<VarDecl *> candidates;
for (auto decl : decls) {
auto var = dyn_cast<VarDecl>(decl);
if (!var)
continue;
auto varDC = var->getDeclContext();
if (var->isStatic() &&
!isDeclNotAsAccessibleAsParent(var, nominal) &&
!(isa<ExtensionDecl>(varDC) &&
cast<ExtensionDecl>(varDC)->isConstrainedExtension()) &&
TypeChecker::conformsToProtocol(
varDC->mapTypeIntoContext(var->getValueInterfaceType()),
actorProto, nominal)) {
sharedVar = var;
break;
}
candidates.push_back(var);
}
// If we found a suitable candidate, we're done.
if (sharedVar)
return sharedVar;
// Complain about the lack of a suitable 'shared' property.
{
auto primaryDiag = nominal->diagnose(
diag::global_actor_missing_shared, nominal->getName());
// If there were no candidates, provide a Fix-It with a prototype.
if (candidates.empty() && nominal->getBraces().Start.isValid()) {
// Figure out the indentation we need.
SourceLoc sharedInsertionLoc = Lexer::getLocForEndOfToken(
ctx.SourceMgr, nominal->getBraces().Start);
StringRef extraIndent;
StringRef currentIndent = Lexer::getIndentationForLine(
ctx.SourceMgr, sharedInsertionLoc, &extraIndent);
std::string stubIndent = (currentIndent + extraIndent).str();
// From the string to add the declaration.
std::string sharedDeclString = "\n" + stubIndent;
if (nominal->getFormalAccess() >= AccessLevel::Public)
sharedDeclString += "public ";
sharedDeclString += "static let shared = <#actor instance#>";
primaryDiag.fixItInsert(sharedInsertionLoc, sharedDeclString);
}
}
// Remark about all of the candidates that failed (and why).
for (auto candidate : candidates) {
if (!candidate->isStatic()) {
candidate->diagnose(diag::global_actor_shared_not_static)
.fixItInsert(candidate->getAttributeInsertionLoc(true), "static ");
continue;
}
if (isDeclNotAsAccessibleAsParent(candidate, nominal)) {
AccessLevel needAccessLevel = std::min(
nominal->getFormalAccess(), AccessLevel::Public);
auto diag = candidate->diagnose(
diag::global_actor_shared_inaccessible,
getAccessLevelSpelling(candidate->getFormalAccess()),
getAccessLevelSpelling(needAccessLevel));
if (auto attr = candidate->getAttrs().getAttribute<AccessControlAttr>()) {
if (needAccessLevel == AccessLevel::Internal) {
diag.fixItRemove(attr->getRange());
} else {
diag.fixItReplace(
attr->getRange(), getAccessLevelSpelling(needAccessLevel));
}
} else {
diag.fixItInsert(
candidate->getAttributeInsertionLoc(true),
getAccessLevelSpelling(needAccessLevel));
}
continue;
}
if (auto ext = dyn_cast<ExtensionDecl>(candidate->getDeclContext())) {
if (ext->isConstrainedExtension()) {
candidate->diagnose(diag::global_actor_shared_constrained_extension);
continue;
}
}
Type varType = candidate->getDeclContext()->mapTypeIntoContext(
candidate->getValueInterfaceType());
candidate->diagnose(diag::global_actor_shared_non_actor_type, varType);
}
return nullptr;
}
Optional<std::pair<CustomAttr *, NominalTypeDecl *>>
GlobalActorAttributeRequest::evaluate(
Evaluator &evaluator, Decl *decl) const {
ASTContext &ctx = decl->getASTContext();
auto dc = decl->getDeclContext();
CustomAttr *globalActorAttr = nullptr;
NominalTypeDecl *globalActorNominal = nullptr;
for (auto attr : decl->getAttrs().getAttributes<CustomAttr>()) {
auto mutableAttr = const_cast<CustomAttr *>(attr);
// Figure out which nominal declaration this custom attribute refers to.
auto nominal = evaluateOrDefault(ctx.evaluator,
CustomAttrNominalRequest{mutableAttr, dc},
nullptr);
// Ignore unresolvable custom attributes.
if (!nominal)
continue;
// We are only interested in global actor types.
if (!nominal->isGlobalActor())
continue;
// Only a single global actor can be applied to a given declaration.
if (globalActorAttr) {
decl->diagnose(
diag::multiple_global_actors, globalActorNominal->getName(),
nominal->getName());
continue;
}
globalActorAttr = const_cast<CustomAttr *>(attr);
globalActorNominal = nominal;
}
if (!globalActorAttr)
return None;
// Check that a global actor attribute makes sense on this kind of
// declaration.
if (auto nominal = dyn_cast<NominalTypeDecl>(decl)) {
// Nominal types are okay...
if (auto classDecl = dyn_cast<ClassDecl>(nominal)){
if (classDecl->isActor()) {
// ... except for actor classes.
nominal->diagnose(diag::global_actor_on_actor_class, nominal->getName())
.highlight(globalActorAttr->getRangeWithAt());
return None;
}
}
} else if (auto storage = dyn_cast<AbstractStorageDecl>(decl)) {
// Subscripts and properties are fine...
if (auto var = dyn_cast<VarDecl>(storage)) {
if (var->getDeclContext()->isLocalContext()) {
var->diagnose(diag::global_actor_on_local_variable, var->getName())
.highlight(globalActorAttr->getRangeWithAt());
return None;
}
// Global actors don't make sense on a stored property of a struct.
if (var->hasStorage() && var->getDeclContext()->getSelfStructDecl() &&
var->isInstanceMember()) {
var->diagnose(diag::global_actor_on_struct_property, var->getName())
.highlight(globalActorAttr->getRangeWithAt());
return None;
}
}
} else if (isa<ExtensionDecl>(decl)) {
// Extensions are okay.
} else if (isa<ConstructorDecl>(decl) || isa<FuncDecl>(decl)) {
// Functions are okay.
} else {
// Everything else is disallowed.
decl->diagnose(diag::global_actor_disallowed, decl->getDescriptiveKind());
return None;
}
return std::make_pair(globalActorAttr, globalActorNominal);
}
/// Determine the isolation rules for a given declaration.
ActorIsolationRestriction ActorIsolationRestriction::forDeclaration(
ConcreteDeclRef declRef) {
auto decl = declRef.getDecl();
switch (decl->getKind()) {
case DeclKind::AssociatedType:
case DeclKind::Class:
case DeclKind::Enum:
case DeclKind::Extension:
case DeclKind::GenericTypeParam:
case DeclKind::OpaqueType:
case DeclKind::Protocol:
case DeclKind::Struct:
case DeclKind::TypeAlias:
// Types are always available.
return forUnrestricted();
case DeclKind::Constructor:
case DeclKind::EnumCase:
case DeclKind::EnumElement:
// Type-level entities don't require isolation.
return forUnrestricted();
case DeclKind::IfConfig:
case DeclKind::Import:
case DeclKind::InfixOperator:
case DeclKind::MissingMember:
case DeclKind::Module:
case DeclKind::PatternBinding:
case DeclKind::PostfixOperator:
case DeclKind::PoundDiagnostic:
case DeclKind::PrecedenceGroup:
case DeclKind::PrefixOperator:
case DeclKind::TopLevelCode:
// Non-value entities don't require isolation.
return forUnrestricted();
case DeclKind::Destructor:
// Destructors don't require isolation.
return forUnrestricted();
case DeclKind::Param:
case DeclKind::Var:
// 'let' declarations are immutable, so there are no restrictions on
// their access.
if (cast<VarDecl>(decl)->isLet())
return forUnrestricted();
LLVM_FALLTHROUGH;
case DeclKind::Accessor:
case DeclKind::Func:
case DeclKind::Subscript:
// A function that provides an asynchronous context has no restrictions
// on its access.
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
if (func->isAsyncContext())
return forUnrestricted();
}
// Local captures can only be referenced in their local context or a
// context that is guaranteed not to run concurrently with it.
if (cast<ValueDecl>(decl)->isLocalCapture())
return forLocalCapture(decl->getDeclContext());
// Determine the actor isolation of the given declaration.
switch (auto isolation = getActorIsolation(cast<ValueDecl>(decl))) {
case ActorIsolation::ActorInstance:
// Protected actor instance members can only be accessed on 'self'.
return forActorSelf(isolation.getActor());
case ActorIsolation::GlobalActor: {
Type actorType = isolation.getGlobalActor();
if (auto subs = declRef.getSubstitutions())
actorType = actorType.subst(subs);
return forGlobalActor(actorType);
}
case ActorIsolation::Independent:
case ActorIsolation::IndependentUnsafe:
// Actor-independent have no restrictions on their access.
return forUnrestricted();
case ActorIsolation::Unspecified:
return forUnsafe();
}
}
}
namespace {
/// Describes the important parts of a partial apply thunk.
struct PartialApplyThunkInfo {
Expr *base;
Expr *fn;
bool isEscaping;
};
}
/// Try to decompose a call that might be an invocation of a partial apply
/// thunk.
static Optional<PartialApplyThunkInfo> decomposePartialApplyThunk(
ApplyExpr *apply, Expr *parent) {
// Check for a call to the outer closure in the thunk.
auto outerAutoclosure = dyn_cast<AutoClosureExpr>(apply->getFn());
if (!outerAutoclosure ||
outerAutoclosure->getThunkKind()
!= AutoClosureExpr::Kind::DoubleCurryThunk)
return None;
auto memberFn = outerAutoclosure->getUnwrappedCurryThunkExpr();
if (!memberFn)
return None;
// Determine whether the partial apply thunk was immediately converted to
// noescape.
bool isEscaping = true;
if (auto conversion = dyn_cast_or_null<FunctionConversionExpr>(parent)) {
auto fnType = conversion->getType()->getAs<FunctionType>();
isEscaping = fnType && !fnType->isNoEscape();
}
return PartialApplyThunkInfo{apply->getArg(), memberFn, isEscaping};
}
/// Find the immediate member reference in the given expression.
static Optional<std::pair<ConcreteDeclRef, SourceLoc>>
findMemberReference(Expr *expr) {
if (auto declRef = dyn_cast<DeclRefExpr>(expr))
return std::make_pair(declRef->getDeclRef(), declRef->getLoc());
if (auto otherCtor = dyn_cast<OtherConstructorDeclRefExpr>(expr)) {
return std::make_pair(otherCtor->getDeclRef(), otherCtor->getLoc());
}
return None;
}
/// Return true if the callee of an ApplyExpr is async
///
/// Note that this must be called after the implicitlyAsync flag has been set,
/// or implicitly async calls will not return the correct value.
static bool isAsyncCall(const ApplyExpr *call) {
if (call->implicitlyAsync())
return true;
// Effectively the same as doing a
// `cast_or_null<FunctionType>(call->getFn()->getType())`, check the
// result of that and then checking `isAsync` if it's defined.
Type funcTypeType = call->getFn()->getType();
if (!funcTypeType)
return false;
FunctionType *funcType = funcTypeType->castTo<FunctionType>();
return funcType->isAsync();
}
namespace {
/// Check for adherence to the actor isolation rules, emitting errors
/// when actor-isolated declarations are used in an unsafe manner.
class ActorIsolationChecker : public ASTWalker {
ASTContext &ctx;
SmallVector<const DeclContext *, 4> contextStack;
SmallVector<ApplyExpr*, 4> applyStack;
const DeclContext *getDeclContext() const {
return contextStack.back();
}
public:
ActorIsolationChecker(const DeclContext *dc) : ctx(dc->getASTContext()) {
contextStack.push_back(dc);
}
/// Searches the applyStack from back to front for the inner-most CallExpr
/// and marks that CallExpr as implicitly async.
///
/// NOTE: Crashes if no CallExpr was found.
///
/// For example, for global actor function `curryAdd`, if we have:
/// ((curryAdd 1) 2)
/// then we want to mark the inner-most CallExpr, `(curryAdd 1)`.
///
/// The same goes for calls to member functions, such as calc.add(1, 2),
/// aka ((add calc) 1 2), looks like this:
///
/// (call_expr
/// (dot_syntax_call_expr
/// (declref_expr add)
/// (declref_expr calc))
/// (tuple_expr
/// ...))
///
/// and we reach up to mark the CallExpr.
void markNearestCallAsImplicitlyAsync() {
assert(applyStack.size() > 0 && "not contained within an Apply?");
const auto End = applyStack.rend();
for (auto I = applyStack.rbegin(); I != End; ++I)
if (auto call = dyn_cast<CallExpr>(*I)) {
call->setImplicitlyAsync(true);
return;
}
llvm_unreachable("expected a CallExpr in applyStack!");
}
bool shouldWalkCaptureInitializerExpressions() override { return true; }
bool shouldWalkIntoTapExpression() override { return false; }
bool walkToDeclPre(Decl *D) override {
// Don't walk into functions; they'll be handled separately.
if (isa<AbstractFunctionDecl>(D))
return false;
return true;
}
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
if (auto *closure = dyn_cast<AbstractClosureExpr>(expr)) {
closure->setActorIsolation(determineClosureIsolation(closure));
contextStack.push_back(closure);
return { true, expr };
}
if (auto inout = dyn_cast<InOutExpr>(expr)) {
if (!applyStack.empty())
diagnoseInOutArg(applyStack.back(), inout, false);
}
if (auto lookup = dyn_cast<LookupExpr>(expr)) {
checkMemberReference(lookup->getBase(), lookup->getMember(),
lookup->getLoc());
return { true, expr };
}
if (auto declRef = dyn_cast<DeclRefExpr>(expr)) {
checkNonMemberReference(declRef->getDeclRef(), declRef->getLoc());
return { true, expr };
}
if (auto apply = dyn_cast<ApplyExpr>(expr)) {
applyStack.push_back(apply); // record this encounter
// If this is a call to a partial apply thunk, decompose it to check it
// like based on the original written syntax, e.g., "self.method".
if (auto partialApply = decomposePartialApplyThunk(
apply, Parent.getAsExpr())) {
if (auto memberRef = findMemberReference(partialApply->fn)) {
// NOTE: partially-applied thunks are never annotated as
// implicitly async, regardless of whether they are escaping.
checkMemberReference(
partialApply->base, memberRef->first, memberRef->second,
partialApply->isEscaping, /*maybeImplicitAsync=*/false);
partialApply->base->walk(*this);
// manual clean-up since normal traversal is skipped
assert(applyStack.back() == apply);
applyStack.pop_back();
return { false, expr };
}
}
}
// NOTE: SelfApplyExpr is a subtype of ApplyExpr
if (auto call = dyn_cast<SelfApplyExpr>(expr)) {
Expr *fn = call->getFn()->getValueProvidingExpr();
if (auto memberRef = findMemberReference(fn)) {
checkMemberReference(
call->getArg(), memberRef->first, memberRef->second,
/*isEscapingPartialApply=*/false, /*maybeImplicitAsync=*/true);
call->getArg()->walk(*this);
if (applyStack.size() >= 2) {
ApplyExpr *outerCall = applyStack[applyStack.size() - 2];
if (isAsyncCall(outerCall)) {
// This call is a partial application within an async call.
// If the partial application take a value inout, it is bad.
if (InOutExpr *inoutArg = dyn_cast<InOutExpr>(
call->getArg()->getSemanticsProvidingExpr()))
diagnoseInOutArg(outerCall, inoutArg, true);
}
}
// manual clean-up since normal traversal is skipped
assert(applyStack.back() == dyn_cast<ApplyExpr>(expr));
applyStack.pop_back();
return { false, expr };
}
}
return { true, expr };
}
Expr *walkToExprPost(Expr *expr) override {
if (auto *closure = dyn_cast<AbstractClosureExpr>(expr)) {
assert(contextStack.back() == closure);
contextStack.pop_back();
}
if (auto *apply = dyn_cast<ApplyExpr>(expr)) {
assert(applyStack.back() == apply);
applyStack.pop_back();
}
return expr;
}
private:
/// If the expression is a reference to `self`, return the 'self' parameter.
static VarDecl *getSelfReference(Expr *expr) {
// Look through identity expressions and implicit conversions.
Expr *prior;
do {
prior = expr;
expr = expr->getSemanticsProvidingExpr();
if (auto conversion = dyn_cast<ImplicitConversionExpr>(expr))
expr = conversion->getSubExpr();
} while (prior != expr);
// 'super' references always act on self.
if (auto super = dyn_cast<SuperRefExpr>(expr))
return super->getSelf();
// Declaration references to 'self'.
if (auto declRef = dyn_cast<DeclRefExpr>(expr)) {
if (auto var = dyn_cast<VarDecl>(declRef->getDecl()))
if (var->isSelfParameter())
return var;
}
// Not a self reference.
return nullptr;
}
/// Note that the given actor member is isolated.
static void noteIsolatedActorMember(ValueDecl *decl) {
// FIXME: Make this diagnostic more sensitive to the isolation context
// of the declaration.
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
func->diagnose(diag::actor_isolated_sync_func,
decl->getDescriptiveKind(),
decl->getName());
} else if (isa<VarDecl>(decl)) {
decl->diagnose(diag::actor_mutable_state);
} else {
decl->diagnose(diag::kind_declared_here, decl->getDescriptiveKind());
}
}
/// Determine whether code in the given use context might execute
/// concurrently with code in the definition context.
bool mayExecuteConcurrentlyWith(
const DeclContext *useContext, const DeclContext *defContext) {
// Walk the context chain from the use to the definition.
while (useContext != defContext) {
// If we find an escaping closure, it can be run concurrently.
if (auto closure = dyn_cast<AbstractClosureExpr>(useContext)) {
if (isEscapingClosure(closure))
return true;
}
// If we find a local function, it can escape and be run concurrently.
if (auto func = dyn_cast<AbstractFunctionDecl>(useContext)) {
if (func->isLocalCapture())
return true;
}
// If we hit a module-scope context, it's not concurrent.
useContext = useContext->getParent();
if (useContext->isModuleScopeContext())
return false;
}
// We hit the same context, so it won't execute concurrently.
return false;
}
// Retrieve the nearest enclosing actor context.
static ClassDecl *getNearestEnclosingActorContext(const DeclContext *dc) {
while (!dc->isModuleScopeContext()) {
if (dc->isTypeContext()) {
if (auto classDecl = dc->getSelfClassDecl()) {
if (classDecl->isActor())
return classDecl;
}
}
dc = dc->getParent();
}
return nullptr;
}
/// Diagnose a reference to an unsafe entity.
///
/// \returns true if we diagnosed the entity, \c false otherwise.
bool diagnoseReferenceToUnsafe(ValueDecl *value, SourceLoc loc) {
// Only diagnose unsafe concurrent accesses within the context of an
// actor. This is globally unsafe, but locally enforceable.
if (!getNearestEnclosingActorContext(getDeclContext()))
return false;
// Only diagnose direct references to mutable shared state. This is
// globally unsafe, but reduces the noise.
if (!isa<VarDecl>(value) || !cast<VarDecl>(value)->hasStorage())
return false;
ctx.Diags.diagnose(
loc, diag::shared_mutable_state_access,
value->getDescriptiveKind(), value->getName());
value->diagnose(diag::kind_declared_here, value->getDescriptiveKind());
return true;
}
/// Diagnose an inout argument passed into an async call
///
/// \returns true if we diagnosed the entity, \c false otherwise.
bool diagnoseInOutArg(const ApplyExpr *call, const InOutExpr *arg,
bool isPartialApply) {
// check that the call is actually async
if (!isAsyncCall(call))
return false;
Expr *subArg = arg->getSubExpr();
if (LookupExpr *baseArg = dyn_cast<LookupExpr>(subArg)) {
while (LookupExpr *nextLayer = dyn_cast<LookupExpr>(baseArg->getBase()))
baseArg = nextLayer;
// subArg: the actual property being passed inout
// baseArg: the property in the actor who's property is being passed
// inout
auto memberDecl = baseArg->getMember().getDecl();
auto isolation = ActorIsolationRestriction::forDeclaration(memberDecl);
switch (isolation) {
case ActorIsolationRestriction::Unrestricted:
case ActorIsolationRestriction::LocalCapture:
case ActorIsolationRestriction::Unsafe:
case ActorIsolationRestriction::GlobalActor: // TODO: handle global
// actors
break;
case ActorIsolationRestriction::ActorSelf: {
if (isPartialApply) {
// The partially applied InoutArg is a property of actor. This can
// really only happen when the property is a struct with a mutating
// async method.
if (auto partialApply = dyn_cast<ApplyExpr>(call->getFn())) {
ValueDecl *fnDecl =
cast<DeclRefExpr>(partialApply->getFn())->getDecl();
ctx.Diags.diagnose(
call->getLoc(), diag::actor_isolated_mutating_func,
fnDecl->getName(), memberDecl->getDescriptiveKind(),
memberDecl->getName());
return true;
}
} else {
ctx.Diags.diagnose(
subArg->getLoc(), diag::actor_isolated_inout_state,
memberDecl->getDescriptiveKind(), memberDecl->getName(),
call->implicitlyAsync());
return true;
}
}
}
}
return false;
}
/// Get the actor isolation of the innermost relevant context.
ActorIsolation getInnermostIsolatedContext(const DeclContext *constDC) {
// Retrieve the actor isolation for a declaration somewhere in our
// declaration context chain and map it into our own context so that
// the types can be compared.
auto getActorIsolation = [constDC](ValueDecl *value) {
switch (auto isolation = swift::getActorIsolation(value)) {
case ActorIsolation::ActorInstance:
case ActorIsolation::Independent:
case ActorIsolation::IndependentUnsafe:
case ActorIsolation::Unspecified:
return isolation;
case ActorIsolation::GlobalActor:
return ActorIsolation::forGlobalActor(
constDC->mapTypeIntoContext(isolation.getGlobalActor()));
}
};
auto dc = const_cast<DeclContext *>(constDC);
while (!dc->isModuleScopeContext()) {
// Look through non-escaping closures.
if (auto closure = dyn_cast<AbstractClosureExpr>(dc)) {
if (auto type = closure->getType()) {
if (auto fnType = type->getAs<AnyFunctionType>()) {
if (fnType->isNoEscape()) {
dc = closure->getParent();
continue;
}
}
}
}
// Functions have actor isolation defined on them.
if (auto func = dyn_cast<AbstractFunctionDecl>(dc))
return getActorIsolation(func);
// Subscripts have actor isolation defined on them.
if (auto subscript = dyn_cast<SubscriptDecl>(dc))
return getActorIsolation(subscript);
// Pattern binding declarations have actor isolation defined on their
// properties, if any.
if (auto init = dyn_cast<PatternBindingInitializer>(dc)) {
auto var = init->getBinding()->getAnchoringVarDecl(
init->getBindingIndex());
if (var)
return getActorIsolation(var);
return ActorIsolation::forUnspecified();
}
return ActorIsolation::forUnspecified();
}
// At module scope, actor independence with safety is assumed.
return ActorIsolation::forIndependent(ActorIndependentKind::Safe);
}
/// Check a reference to an entity within a global actor.
bool checkGlobalActorReference(
ValueDecl *value, SourceLoc loc, Type globalActor) {
/// Returns true if this global actor reference is the callee of an Apply.
/// NOTE: This check mutates the identified ApplyExpr if it returns true!
auto inspectForImplicitlyAsync = [&] () -> bool {
// Is this global actor reference outside of an ApplyExpr?
if (applyStack.size() == 0)
return false;
// Check our applyStack metadata from the traversal.
// Our goal is to identify whether this global actor reference appears
// as the called value of the enclosing ApplyExpr. We cannot simply
// inspect Parent here because of expressions like (callee)()
ApplyExpr *apply = applyStack.back();
Expr *fn = apply->getFn()->getValueProvidingExpr();
if (auto memberRef = findMemberReference(fn)) {
auto concDecl = memberRef->first;
if (value == concDecl.getDecl() && !apply->implicitlyAsync()) {
// then this ValueDecl appears as the called value of the ApplyExpr.
markNearestCallAsImplicitlyAsync();
return true;
}
}
return false;
};
auto declContext = getDeclContext();
switch (auto contextIsolation =
getInnermostIsolatedContext(declContext)) {
case ActorIsolation::ActorInstance:
if (inspectForImplicitlyAsync())
return false;
ctx.Diags.diagnose(
loc, diag::global_actor_from_instance_actor_context,
value->getDescriptiveKind(), value->getName(), globalActor,
contextIsolation.getActor()->getName());
noteIsolatedActorMember(value);
return true;
case ActorIsolation::GlobalActor: {
// If the global actor types are the same, we're done.
if (contextIsolation.getGlobalActor()->isEqual(globalActor))
return false;
// Otherwise, we check if this decl reference is the callee of the
// enclosing Apply, making it OK as an implicitly async call.
if (inspectForImplicitlyAsync())
return false;
// Otherwise, this is a problematic global actor decl reference.
ctx.Diags.diagnose(
loc, diag::global_actor_from_other_global_actor_context,
value->getDescriptiveKind(), value->getName(), globalActor,
contextIsolation.getGlobalActor());
noteIsolatedActorMember(value);
return true;
}
case ActorIsolation::Independent:
case ActorIsolation::IndependentUnsafe:
if (inspectForImplicitlyAsync())
return false;
ctx.Diags.diagnose(
loc, diag::global_actor_from_nonactor_context,
value->getDescriptiveKind(), value->getName(), globalActor,
/*actorIndependent=*/true);
noteIsolatedActorMember(value);
return true;
case ActorIsolation::Unspecified: {
// NOTE: we must always inspect for implicitlyAsync
bool implicitlyAsyncCall = inspectForImplicitlyAsync();
bool didEmitDiagnostic = false;
auto emitError = [&](bool justNote = false) {
didEmitDiagnostic = true;
if (!justNote) {
ctx.Diags.diagnose(
loc, diag::global_actor_from_nonactor_context,
value->getDescriptiveKind(), value->getName(), globalActor,
/*actorIndependent=*/false);
}
noteIsolatedActorMember(value);
};
if (AbstractFunctionDecl const* fn =
dyn_cast_or_null<AbstractFunctionDecl>(declContext->getAsDecl())) {
bool isAsyncContext = fn->isAsyncContext();
if (implicitlyAsyncCall && isAsyncContext)
return didEmitDiagnostic; // definitely an OK reference.
// otherwise, there's something wrong.
// if it's an implicitly-async call in a non-async context,
// then we know later type-checking will raise an error,
// so we just emit a note pointing out that callee of the call is
// implicitly async.
emitError(/*justNote=*/implicitlyAsyncCall);
// otherwise, if it's any kind of global-actor reference within
// this synchronous function, we'll additionally suggest becoming
// part of the global actor associated with the reference,
// since this function is not associated with an actor.
if (isa<FuncDecl>(fn) && !isAsyncContext) {
didEmitDiagnostic = true;
fn->diagnose(diag::note_add_globalactor_to_function,
globalActor->getWithoutParens().getString(),
fn->getDescriptiveKind(),
fn->getName(),
globalActor)
.fixItInsert(fn->getAttributeInsertionLoc(false),
diag::insert_globalactor_attr, globalActor);
}
} else {
// just the generic error with note.
emitError();
}
return didEmitDiagnostic;
} // end Unspecified case
} // end switch
llvm_unreachable("unhandled actor isolation kind!");
}
/// Check a reference to a local or global.
bool checkNonMemberReference(ConcreteDeclRef valueRef, SourceLoc loc) {
if (!valueRef)
return false;
auto value = valueRef.getDecl();
switch (auto isolation =
ActorIsolationRestriction::forDeclaration(valueRef)) {
case ActorIsolationRestriction::Unrestricted:
return false;
case ActorIsolationRestriction::ActorSelf:
llvm_unreachable("non-member reference into an actor");
case ActorIsolationRestriction::GlobalActor:
return checkGlobalActorReference(
value, loc, isolation.getGlobalActor());
case ActorIsolationRestriction::LocalCapture:
// Only diagnose unsafe concurrent accesses within the context of an
// actor. This is globally unsafe, but locally enforceable.
if (!getNearestEnclosingActorContext(getDeclContext()))
return false;
// Check whether we are in a context that will not execute concurrently
// with the context of 'self'.
if (mayExecuteConcurrentlyWith(
getDeclContext(), isolation.getLocalContext())) {
ctx.Diags.diagnose(
loc, diag::concurrent_access_local,
value->getDescriptiveKind(), value->getName());
value->diagnose(
diag::kind_declared_here, value->getDescriptiveKind());
return true;
}
return false;
case ActorIsolationRestriction::Unsafe:
return diagnoseReferenceToUnsafe(value, loc);
}
llvm_unreachable("unhandled actor isolation kind!");
}
/// Check a reference with the given base expression to the given member.
/// Returns true iff the member reference refers to actor-isolated state
/// in an invalid or unsafe way such that a diagnostic was emitted.
bool checkMemberReference(
Expr *base, ConcreteDeclRef memberRef, SourceLoc memberLoc,
bool isEscapingPartialApply = false,
bool maybeImplicitAsync = false) {
if (!base || !memberRef)
return false;
auto member = memberRef.getDecl();
switch (auto isolation =
ActorIsolationRestriction::forDeclaration(memberRef)) {
case ActorIsolationRestriction::Unrestricted:
return false;
case ActorIsolationRestriction::ActorSelf: {
// Must reference actor-isolated state on 'self'.
auto selfVar = getSelfReference(base);
if (!selfVar) {
// actor-isolated non-self calls are implicitly async and thus OK.
if (maybeImplicitAsync && isa<AbstractFunctionDecl>(member)) {
markNearestCallAsImplicitlyAsync();
return false;
}
ctx.Diags.diagnose(
memberLoc, diag::actor_isolated_non_self_reference,
member->getDescriptiveKind(),
member->getName(),
isolation.getActorClass() ==
getNearestEnclosingActorContext(getDeclContext()));
noteIsolatedActorMember(member);
return true;
}
// Check whether the context of 'self' is actor-isolated.
switch (auto contextIsolation = getActorIsolation(
cast<ValueDecl>(selfVar->getDeclContext()->getAsDecl()))) {
case ActorIsolation::ActorInstance:
// An escaping partial application of something that is part of
// the actor's isolated state is never permitted.
if (isEscapingPartialApply) {
ctx.Diags.diagnose(
memberLoc, diag::actor_isolated_partial_apply,
member->getDescriptiveKind(),
member->getName());
noteIsolatedActorMember(member);
return true;
}
break;
case ActorIsolation::IndependentUnsafe:
case ActorIsolation::Unspecified:
// Okay
break;
case ActorIsolation::Independent:
// The 'self' is for an actor-independent member, which means
// we cannot refer to actor-isolated state.
ctx.Diags.diagnose(
memberLoc, diag::actor_isolated_self_independent_context,
member->getDescriptiveKind(),
member->getName());
noteIsolatedActorMember(member);
return true;
case ActorIsolation::GlobalActor:
// The 'self' is for a member that's part of a global actor, which
// means we cannot refer to actor-isolated state.
ctx.Diags.diagnose(
memberLoc, diag::actor_isolated_global_actor_context,
member->getDescriptiveKind(),
member->getName(),
contextIsolation.getGlobalActor());
noteIsolatedActorMember(member);
return true;
}
// Check whether we are in a context that will not execute concurrently
// with the context of 'self'.
if (mayExecuteConcurrentlyWith(
getDeclContext(), selfVar->getDeclContext())) {
ctx.Diags.diagnose(
memberLoc, diag::actor_isolated_concurrent_access,
member->getDescriptiveKind(), member->getName());
noteIsolatedActorMember(member);
return true;
}
// It's fine.
return false;
}
case ActorIsolationRestriction::GlobalActor:
return checkGlobalActorReference(
member, memberLoc, isolation.getGlobalActor());
case ActorIsolationRestriction::LocalCapture:
llvm_unreachable("Locals cannot be referenced with member syntax");
case ActorIsolationRestriction::Unsafe:
// This case is hit when passing actor state inout to functions in some
// cases. The error is emitted by diagnoseInOutArg.
return false;
}
llvm_unreachable("unhandled actor isolation kind!");
}
/// Determine whether this closure is escaping.
static bool isEscapingClosure(const AbstractClosureExpr *closure) {
if (auto type = closure->getType()) {
if (auto fnType = type->getAs<AnyFunctionType>())
return !fnType->isNoEscape();
}
return true;
}
/// Determine the isolation of a particular closure.
///
/// This function assumes that enclosing closures have already had their
/// isolation checked.
ClosureActorIsolation determineClosureIsolation(
AbstractClosureExpr *closure) {
// An escaping closure is always actor-independent.
if (isEscapingClosure(closure))
return ClosureActorIsolation::forIndependent();
// A non-escaping closure gets its isolation from its context.
Optional<ActorIsolation> parentIsolation;
auto parentDC = closure->getParent();
switch (parentDC->getContextKind()) {
case DeclContextKind::AbstractClosureExpr: {
auto parentClosureIsolation = cast<AbstractClosureExpr>(parentDC)
->getActorIsolation();
switch (parentClosureIsolation) {
case ClosureActorIsolation::Independent:
parentIsolation = ActorIsolation::forIndependent(
ActorIndependentKind::Safe);
break;
case ClosureActorIsolation::ActorInstance: {
auto selfDecl = parentClosureIsolation.getActorInstance();
auto actorClass = selfDecl->getType()->getRValueType()
->getClassOrBoundGenericClass();
assert(actorClass && "Bad closure actor isolation?");
parentIsolation = ActorIsolation::forActorInstance(actorClass);
break;
}
case ClosureActorIsolation::GlobalActor:
parentIsolation = ActorIsolation::forGlobalActor(
parentClosureIsolation.getGlobalActor());
break;
}
break;
}
case DeclContextKind::AbstractFunctionDecl:
case DeclContextKind::SubscriptDecl:
parentIsolation = getActorIsolation(
cast<ValueDecl>(parentDC->getAsDecl()));
break;
case DeclContextKind::EnumElementDecl:
case DeclContextKind::ExtensionDecl:
case DeclContextKind::FileUnit:
case DeclContextKind::GenericTypeDecl:
case DeclContextKind::Initializer:
case DeclContextKind::Module:
case DeclContextKind::SerializedLocal:
case DeclContextKind::TopLevelCodeDecl:
return ClosureActorIsolation::forIndependent();
}
// We must have parent isolation determined to get here.
assert(parentIsolation && "Missing parent isolation?");
switch (*parentIsolation) {
case ActorIsolation::Independent:
case ActorIsolation::IndependentUnsafe:
case ActorIsolation::Unspecified:
return ClosureActorIsolation::forIndependent();
case ActorIsolation::GlobalActor: {
Type globalActorType = closure->mapTypeIntoContext(
parentIsolation->getGlobalActor()->mapTypeOutOfContext());
return ClosureActorIsolation::forGlobalActor(globalActorType);
}
case ActorIsolation::ActorInstance: {
SmallVector<CapturedValue, 2> localCaptures;
closure->getCaptureInfo().getLocalCaptures(localCaptures);
for (const auto &localCapture : localCaptures) {
if (localCapture.isDynamicSelfMetadata())
continue;
auto var = dyn_cast_or_null<VarDecl>(localCapture.getDecl());
if (!var)
continue;
// If we have captured the 'self' parameter, the closure is isolated
// to that actor instance.
if (var->isSelfParameter()) {
return ClosureActorIsolation::forActorInstance(var);
}
}
// When 'self' is not captured, this closure is actor-independent.
return ClosureActorIsolation::forIndependent();
}
}
}
};
}
void swift::checkTopLevelActorIsolation(TopLevelCodeDecl *decl) {
ActorIsolationChecker checker(decl);
decl->getBody()->walk(checker);
}
void swift::checkFunctionActorIsolation(AbstractFunctionDecl *decl) {
ActorIsolationChecker checker(decl);
if (auto body = decl->getBody()) {
body->walk(checker);
}
if (auto ctor = dyn_cast<ConstructorDecl>(decl))
if (auto superInit = ctor->getSuperInitCall())
superInit->walk(checker);
}
void swift::checkInitializerActorIsolation(Initializer *init, Expr *expr) {
ActorIsolationChecker checker(init);
expr->walk(checker);
}
void swift::checkEnumElementActorIsolation(
EnumElementDecl *element, Expr *expr) {
ActorIsolationChecker checker(element);
expr->walk(checker);
}
void swift::checkPropertyWrapperActorIsolation(
PatternBindingDecl *binding, Expr *expr) {
ActorIsolationChecker checker(binding->getDeclContext());
expr->walk(checker);
}
/// Determine actor isolation solely from attributes.
///
/// \returns the actor isolation determined from attributes alone (with no
/// inference rules). Returns \c None if there were no attributes on this
/// declaration.
static Optional<ActorIsolation> getIsolationFromAttributes(Decl *decl) {
// Look up attributes on the declaration that can affect its actor isolation.
// If any of them are present, use that attribute.
auto independentAttr = decl->getAttrs().getAttribute<ActorIndependentAttr>();
auto globalActorAttr = decl->getGlobalActorAttr();
unsigned numIsolationAttrs =
(independentAttr ? 1 : 0) + (globalActorAttr ? 1 : 0);
if (numIsolationAttrs == 0)
return None;
// Only one such attribute is valid.
if (numIsolationAttrs > 1) {
DeclName name;
if (auto value = dyn_cast<ValueDecl>(decl)) {
name = value->getName();
} else if (auto ext = dyn_cast<ExtensionDecl>(decl)) {
if (auto selfTypeDecl = ext->getSelfNominalTypeDecl())
name = selfTypeDecl->getName();
}
decl->diagnose(
diag::actor_isolation_multiple_attr, decl->getDescriptiveKind(),
name, independentAttr->getAttrName(),
globalActorAttr->second->getName().str())
.highlight(independentAttr->getRangeWithAt())
.highlight(globalActorAttr->first->getRangeWithAt());
}
// If the declaration is explicitly marked @actorIndependent, report it as
// independent.
if (independentAttr) {
return ActorIsolation::forIndependent(independentAttr->getKind());
}
// If the declaration is marked with a global actor, report it as being
// part of that global actor.
if (globalActorAttr) {
ASTContext &ctx = decl->getASTContext();
auto dc = decl->getInnermostDeclContext();
Type globalActorType = evaluateOrDefault(
ctx.evaluator,
CustomAttrTypeRequest{
globalActorAttr->first, dc, CustomAttrTypeKind::GlobalActor},
Type());
if (!globalActorType || globalActorType->hasError())
return ActorIsolation::forUnspecified();
return ActorIsolation::forGlobalActor(
globalActorType->mapTypeOutOfContext());
}
llvm_unreachable("Forgot about an attribute?");
}
/// Infer isolation from witnessed protocol requirements.
static Optional<ActorIsolation> getIsolationFromWitnessedRequirements(
ValueDecl *value) {
auto dc = value->getDeclContext();
auto idc = dyn_cast_or_null<IterableDeclContext>(dc->getAsDecl());
if (!idc)
return None;
if (dc->getSelfProtocolDecl())
return None;
// Walk through each of the conformances in this context, collecting any
// requirements that have actor isolation.
auto conformances = evaluateOrDefault(
dc->getASTContext().evaluator,
LookupAllConformancesInContextRequest{idc}, { });
using IsolatedRequirement =
std::tuple<ProtocolConformance *, ActorIsolation, ValueDecl *>;
SmallVector<IsolatedRequirement, 2> isolatedRequirements;
for (auto conformance : conformances) {
auto protocol = conformance->getProtocol();
for (auto found : protocol->lookupDirect(value->getName())) {
if (!isa<ProtocolDecl>(found->getDeclContext()))
continue;
auto requirement = dyn_cast<ValueDecl>(found);
if (!requirement || isa<TypeDecl>(requirement))
continue;
auto requirementIsolation = getActorIsolation(requirement);
if (requirementIsolation.isUnspecified())
continue;
auto witness = conformance->getWitnessDecl(requirement);
if (witness != value)
continue;
isolatedRequirements.push_back(
IsolatedRequirement{conformance, requirementIsolation, requirement});
}
}
// Filter out duplicate actors.
SmallPtrSet<CanType, 2> globalActorTypes;
bool sawActorIndependent = false;
isolatedRequirements.erase(
std::remove_if(isolatedRequirements.begin(), isolatedRequirements.end(),
[&](IsolatedRequirement &isolated) {
auto isolation = std::get<1>(isolated);
switch (isolation) {
case ActorIsolation::ActorInstance:
llvm_unreachable("protocol requirements cannot be actor instances");
case ActorIsolation::Independent:
case ActorIsolation::IndependentUnsafe:
// We only need one @actorIndependent.
if (sawActorIndependent)
return true;
sawActorIndependent = true;
return false;
case ActorIsolation::Unspecified:
return true;
case ActorIsolation::GlobalActor: {
// Substitute into the global actor type.
auto conformance = std::get<0>(isolated);
auto requirementSubs = SubstitutionMap::getProtocolSubstitutions(
conformance->getProtocol(), dc->getSelfTypeInContext(),
ProtocolConformanceRef(conformance));
Type globalActor = isolation.getGlobalActor().subst(requirementSubs);
if (!globalActorTypes.insert(globalActor->getCanonicalType()).second)
return true;
// Update the global actor type, now that we've done this substitution.
std::get<1>(isolated) = ActorIsolation::forGlobalActor(globalActor);
return false;
}
}
}),
isolatedRequirements.end());
if (isolatedRequirements.size() != 1)
return None;
return std::get<1>(isolatedRequirements.front());
}
ActorIsolation ActorIsolationRequest::evaluate(
Evaluator &evaluator, ValueDecl *value) const {
// If this declaration has one of the actor isolation attributes, report
// that.
if (auto isolationFromAttr = getIsolationFromAttributes(value)) {
return *isolationFromAttr;
}
// Determine the default isolation for this declaration, which may still be
// overridden by other inference rules.
ActorIsolation defaultIsolation = ActorIsolation::forUnspecified();
// Check for instance members of actor classes, which are part of
// actor-isolated state.
auto classDecl = value->getDeclContext()->getSelfClassDecl();
if (classDecl && classDecl->isActor() && value->isInstanceMember()) {
defaultIsolation = ActorIsolation::forActorInstance(classDecl);
}
// Disable inference of actor attributes outside of normal Swift source files.
if (!shouldInferAttributeInContext(value->getDeclContext()))
return defaultIsolation;
// Function used when returning an inferred isolation.
auto inferredIsolation = [&](ActorIsolation inferred) {
// Add an implicit attribute to capture the actor isolation that was
// inferred, so that (e.g.) it will be printed and serialized.
ASTContext &ctx = value->getASTContext();
switch (inferred) {
// FIXME: if the context is 'unsafe', is it fine to infer the 'safe' one?
case ActorIsolation::IndependentUnsafe:
case ActorIsolation::Independent:
value->getAttrs().add(new (ctx) ActorIndependentAttr(
ActorIndependentKind::Safe, /*IsImplicit=*/true));
break;
case ActorIsolation::GlobalActor: {
auto typeExpr = TypeExpr::createImplicit(inferred.getGlobalActor(), ctx);
auto attr = CustomAttr::create(
ctx, SourceLoc(), typeExpr, /*implicit=*/true);
value->getAttrs().add(attr);
break;
}
case ActorIsolation::ActorInstance:
case ActorIsolation::Unspecified:
// Nothing to do.
break;
}
return inferred;
};
// If the declaration overrides another declaration, it must have the same
// actor isolation.
if (auto overriddenValue = value->getOverriddenDecl()) {
if (auto isolation = getActorIsolation(overriddenValue)) {
SubstitutionMap subs;
if (auto env = value->getInnermostDeclContext()
->getGenericEnvironmentOfContext()) {
subs = SubstitutionMap::getOverrideSubstitutions(
overriddenValue, value, subs);
}
return inferredIsolation(isolation.subst(subs));
}
}
// If this is an accessor, use the actor isolation of its storage
// declaration.
if (auto accessor = dyn_cast<AccessorDecl>(value)) {
return getActorIsolation(accessor->getStorage());
}
// If the declaration witnesses a protocol requirement that is isolated,
// use that.
if (auto witnessedIsolation = getIsolationFromWitnessedRequirements(value)) {
return inferredIsolation(*witnessedIsolation);
}
// If the declaration is a class with a superclass that has specified
// isolation, use that.
if (auto classDecl = dyn_cast<ClassDecl>(value)) {
if (auto superclassDecl = classDecl->getSuperclassDecl()) {
auto superclassIsolation = getActorIsolation(superclassDecl);
if (!superclassIsolation.isUnspecified()) {
if (superclassIsolation.requiresSubstitution()) {
Type superclassType = classDecl->getSuperclass();
if (!superclassType)
return ActorIsolation::forUnspecified();
SubstitutionMap subs = superclassType->getMemberSubstitutionMap(
classDecl->getModuleContext(), classDecl);
superclassIsolation = superclassIsolation.subst(subs);
}
return inferredIsolation(superclassIsolation);
}
}
}
// If the declaration is in an extension that has one of the isolation
// attributes, use that.
if (auto ext = dyn_cast<ExtensionDecl>(value->getDeclContext())) {
if (auto isolationFromAttr = getIsolationFromAttributes(ext)) {
return inferredIsolation(*isolationFromAttr);
}
}
// If the declaration is in a nominal type (or extension thereof) that
// has isolation, use that.
if (auto selfTypeDecl = value->getDeclContext()->getSelfNominalTypeDecl()) {
auto selfTypeIsolation = getActorIsolation(selfTypeDecl);
if (!selfTypeIsolation.isUnspecified()) {
return inferredIsolation(selfTypeIsolation);
}
}
// Default isolation for this member.
return defaultIsolation;
}
ActorIsolation swift::getActorIsolation(ValueDecl *value) {
auto &ctx = value->getASTContext();
return evaluateOrDefault(
ctx.evaluator, ActorIsolationRequest{value},
ActorIsolation::forUnspecified());
}
void swift::checkOverrideActorIsolation(ValueDecl *value) {
if (isa<TypeDecl>(value))
return;
auto overridden = value->getOverriddenDecl();
if (!overridden)
return;
// Determine the actor isolation of this declaration.
auto isolation = getActorIsolation(value);
// Determine the actor isolation of the overridden function.=
auto overriddenIsolation = getActorIsolation(overridden);
if (overriddenIsolation.requiresSubstitution()) {
SubstitutionMap subs;
if (auto env = value->getInnermostDeclContext()
->getGenericEnvironmentOfContext()) {
subs = SubstitutionMap::getOverrideSubstitutions(overridden, value, subs);
overriddenIsolation = overriddenIsolation.subst(subs);
}
}
// If the isolation matches, we're done.
if (isolation == overriddenIsolation)
return;
// Isolation mismatch. Diagnose it.
value->diagnose(
diag::actor_isolation_override_mismatch, isolation,
value->getDescriptiveKind(), value->getName(), overriddenIsolation);
overridden->diagnose(diag::overridden_here);
}