Skip to content

Improve (nonisolated) let checking #38118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "TypeCheckType.h"
#include "swift/Strings.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/ProtocolConformance.h"
Expand Down Expand Up @@ -300,7 +301,7 @@ Type swift::getExplicitGlobalActor(ClosureExpr *closure) {

/// Determine the isolation rules for a given declaration.
ActorIsolationRestriction ActorIsolationRestriction::forDeclaration(
ConcreteDeclRef declRef, bool fromExpression) {
ConcreteDeclRef declRef, const DeclContext *fromDC, bool fromExpression) {
auto decl = declRef.getDecl();

switch (decl->getKind()) {
Expand Down Expand Up @@ -355,7 +356,12 @@ ActorIsolationRestriction ActorIsolationRestriction::forDeclaration(
// actors.
bool isAccessibleAcrossActors = false;
if (auto var = dyn_cast<VarDecl>(decl)) {
if (var->isLet())
// A 'let' declaration is accessible across actors if it is either
// nonisolated or it is accessed from within the same module.
if (var->isLet() &&
(isolation == ActorIsolation::Independent ||
var->getDeclContext()->getParentModule() ==
fromDC->getParentModule()))
isAccessibleAcrossActors = true;
}

Expand Down Expand Up @@ -1492,7 +1498,8 @@ namespace {
bool result = false;
auto checkDiagnostic = [this, call, isPartialApply,
&result](ValueDecl *decl, SourceLoc argLoc) {
auto isolation = ActorIsolationRestriction::forDeclaration(decl);
auto isolation = ActorIsolationRestriction::forDeclaration(
decl, getDeclContext());
switch (isolation) {
case ActorIsolationRestriction::Unrestricted:
case ActorIsolationRestriction::Unsafe:
Expand Down Expand Up @@ -1611,11 +1618,6 @@ namespace {

// is it an access to a property?
if (isPropOrSubscript(decl)) {
// we assume let-bound properties are taken care of elsewhere,
// since they are never implicitly async.
assert(!isa<VarDecl>(decl) || cast<VarDecl>(decl)->isLet() == false
&& "unexpected let-bound property; never implicitly async!");

if (auto declRef = dyn_cast_or_null<DeclRefExpr>(context)) {
if (usageEnv(declRef) == VarRefUseEnv::Read) {

Expand Down Expand Up @@ -2092,7 +2094,8 @@ namespace {
// The decl referred to by the path component cannot be within an actor.
if (component.hasDeclRef()) {
auto concDecl = component.getDeclRef();
auto isolation = ActorIsolationRestriction::forDeclaration(concDecl);
auto isolation = ActorIsolationRestriction::forDeclaration(
concDecl, getDeclContext());

switch (isolation.getKind()) {
case ActorIsolationRestriction::Unsafe:
Expand Down Expand Up @@ -2210,7 +2213,8 @@ namespace {
return checkLocalCapture(valueRef, loc, declRefExpr);

switch (auto isolation =
ActorIsolationRestriction::forDeclaration(valueRef)) {
ActorIsolationRestriction::forDeclaration(
valueRef, getDeclContext())) {
case ActorIsolationRestriction::Unrestricted:
return false;

Expand Down Expand Up @@ -2249,7 +2253,8 @@ namespace {

auto member = memberRef.getDecl();
switch (auto isolation =
ActorIsolationRestriction::forDeclaration(memberRef)) {
ActorIsolationRestriction::forDeclaration(
memberRef, getDeclContext())) {
case ActorIsolationRestriction::Unrestricted:
return false;

Expand Down Expand Up @@ -3028,6 +3033,19 @@ ActorIsolation ActorIsolationRequest::evaluate(
// If this declaration has one of the actor isolation attributes, report
// that.
if (auto isolationFromAttr = getIsolationFromAttributes(value)) {
// Nonisolated declarations must involve Sendable types.
if (*isolationFromAttr == ActorIsolation::Independent) {
SubstitutionMap subs;
if (auto genericEnv = value->getInnermostDeclContext()
->getGenericEnvironmentOfContext()) {
subs = genericEnv->getForwardingSubstitutionMap();
}
diagnoseNonConcurrentTypesInReference(
ConcreteDeclRef(value, subs),
value->getDeclContext()->getParentModule(), value->getLoc(),
ConcurrentReferenceKind::Nonisolated);
}

return *isolationFromAttr;
}

Expand Down
5 changes: 4 additions & 1 deletion lib/Sema/TypeCheckConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ enum class ConcurrentReferenceKind {
LocalCapture,
/// Concurrent function
ConcurrentFunction,
/// Nonisolated declaration.
Nonisolated,
};

/// The isolation restriction in effect for a given declaration that is
Expand Down Expand Up @@ -202,7 +204,8 @@ class ActorIsolationRestriction {
/// \param fromExpression Indicates that the reference is coming from an
/// expression.
static ActorIsolationRestriction forDeclaration(
ConcreteDeclRef declRef, bool fromExpression = true);
ConcreteDeclRef declRef, const DeclContext *fromDC,
bool fromExpression = true);

operator Kind() const { return kind; };
};
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/TypeCheckDeclObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,8 @@ static bool checkObjCActorIsolation(const ValueDecl *VD,
auto behavior = behaviorLimitForObjCReason(Reason, VD->getASTContext());

switch (auto restriction = ActorIsolationRestriction::forDeclaration(
const_cast<ValueDecl *>(VD), /*fromExpression=*/false)) {
const_cast<ValueDecl *>(VD), VD->getDeclContext(),
/*fromExpression=*/false)) {
case ActorIsolationRestriction::CrossActorSelf:
// FIXME: Substitution map?
diagnoseNonConcurrentTypesInReference(
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2788,7 +2788,8 @@ bool ConformanceChecker::checkActorIsolation(
Type witnessGlobalActor;
switch (auto witnessRestriction =
ActorIsolationRestriction::forDeclaration(
witness, /*fromExpression=*/false)) {
witness, witness->getDeclContext(),
/*fromExpression=*/false)) {
case ActorIsolationRestriction::DistributedActorSelf: {
if (witness->isSynthesized()) {
// Some of our synthesized properties get special treatment,
Expand Down
7 changes: 7 additions & 0 deletions test/Concurrency/Inputs/OtherActors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
public class SomeClass { }

public actor OtherModuleActor {
public let a: Int = 1
public nonisolated let b: Int = 2
public let c: SomeClass = SomeClass()
}
21 changes: 20 additions & 1 deletion test/Concurrency/actor_isolation.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency -warn-concurrency
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/OtherActors.swiftmodule -module-name OtherActors %S/Inputs/OtherActors.swift
// RUN: %target-typecheck-verify-swift -I %t -enable-experimental-concurrency -warn-concurrency
// REQUIRES: concurrency

import OtherActors

let immutableGlobal: String = "hello"
var mutableGlobal: String = "can't touch this" // expected-note 5{{var declared here}}

Expand Down Expand Up @@ -800,6 +804,21 @@ func outsideSomeClassWithInits() { // expected-note 3 {{add '@MainActor' to make
SomeClassWithInits.staticIsolated() // expected-error{{call to main actor-isolated static method 'staticIsolated()' in a synchronous nonisolated context}}
}

// ----------------------------------------------------------------------
// nonisolated let and cross-module let
// ----------------------------------------------------------------------
func testCrossModuleLets(actor: OtherModuleActor) async {
_ = actor.a // expected-error{{expression is 'async' but is not marked with 'await'}}
// expected-note@-1{{property access is 'async'}}
_ = await actor.a // okay
_ = actor.b // okay
_ = actor.c // expected-error{{expression is 'async' but is not marked with 'await'}}
// expected-note@-1{{property access is 'async'}}
// expected-warning@-2{{cannot use property 'c' with a non-sendable type 'SomeClass' across actors}}
_ = await actor.c // expected-warning{{cannot use property 'c' with a non-sendable type 'SomeClass' across actors}}
}


// ----------------------------------------------------------------------
// Actor protocols.
// ----------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions test/Concurrency/concurrent_value_checking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ func testKeyPaths(dict: [NC: Int], nc: NC) {
_ = \HasNC.dict[nc] // expected-warning{{cannot form key path that captures non-sendable type 'NC'}}
}

// ----------------------------------------------------------------------
// Sendable restriction on nonisolated declarations.
// ----------------------------------------------------------------------
actor ANI {
// FIXME: improve diagnostics to talk about nonisolated
nonisolated let nc = NC() // expected-warning{{cannot use property 'nc' with a non-sendable type 'NC' across actors}}
nonisolated func f() -> NC? { nil } // expected-warning{{cannot call function returning non-sendable type 'NC?' across actors}}
}

// ----------------------------------------------------------------------
// Sendable restriction on conformances.
Expand Down