Skip to content

Sendable checking for overrides #59067

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

Closed
Closed
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
10 changes: 6 additions & 4 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4649,8 +4649,8 @@ ERROR(isolated_parameter_not_actor,none,
WARNING(non_sendable_param_type,none,
"non-sendable type %0 %select{passed in call to %4 %2 %3|"
"passed in implicitly asynchronous call to %4 %2 %3|"
"in parameter of %4 %2 %3 satisfying non-isolated protocol "
"requirement|"
"in parameter of %4 %2 %3 satisfying protocol requirement|"
"in parameter of %4 overriding %2 %3|"
"in parameter of %4 '@objc' %2 %3}1 cannot cross actor boundary",
(Type, unsigned, DescriptiveDeclKind, DeclName, ActorIsolation))
WARNING(non_sendable_call_param_type,none,
Expand All @@ -4660,7 +4660,8 @@ WARNING(non_sendable_call_param_type,none,
WARNING(non_sendable_result_type,none,
"non-sendable type %0 returned by %select{call to %4 %2 %3|"
"implicitly asynchronous call to %4 %2 %3|"
"%4 %2 %3 satisfying non-isolated protocol requirement|"
"%4 %2 %3 satisfying protocol requirement|"
"%4 overriding %2 %3|"
"%4 '@objc' %2 %3}1 cannot cross actor boundary",
(Type, unsigned, DescriptiveDeclKind, DeclName, ActorIsolation))
WARNING(non_sendable_call_result_type,none,
Expand All @@ -4671,7 +4672,8 @@ WARNING(non_sendable_property_type,none,
"non-sendable type %0 in %select{"
"%select{asynchronous access to %5 %1 %2|"
"implicitly asynchronous access to %5 %1 %2|"
"conformance of %5 %1 %2 to non-isolated protocol requirement|"
"conformance of %5 %1 %2 to protocol requirement|"
"%5 overriding %1 %2|"
"%5 '@objc' %1 %2}4|captured local %1 %2}3 cannot "
"cross %select{actor|task}3 boundary",
(Type, DescriptiveDeclKind, DeclName, bool, unsigned, ActorIsolation))
Expand Down
80 changes: 45 additions & 35 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3489,19 +3489,25 @@ static ActorIsolation getOverriddenIsolationFor(ValueDecl *value) {
return isolation.subst(subs);
}

static ConcreteDeclRef getDeclRefInContext(ValueDecl *value) {
auto declContext = value->getInnermostDeclContext();
if (auto genericEnv = declContext->getGenericEnvironmentOfContext()) {
return ConcreteDeclRef(
value, genericEnv->getForwardingSubstitutionMap());
}

return ConcreteDeclRef(value);
}

/// Generally speaking, the isolation of the decl that overrides
/// must match the overridden decl. But there are a number of exceptions,
/// e.g., the decl that overrides can be nonisolated.
/// \param isolation the isolation of the overriding declaration.
static OverrideIsolationResult validOverrideIsolation(
ValueDecl *value, ActorIsolation isolation,
ValueDecl *overridden, ActorIsolation overriddenIsolation) {
ConcreteDeclRef valueRef(value);
ConcreteDeclRef valueRef = getDeclRefInContext(value);
auto declContext = value->getInnermostDeclContext();
if (auto genericEnv = declContext->getGenericEnvironmentOfContext()) {
valueRef = ConcreteDeclRef(
value, genericEnv->getForwardingSubstitutionMap());
}

auto refResult = ActorReferenceResult::forReference(
valueRef, SourceLoc(), declContext, None, None,
Expand All @@ -3520,9 +3526,7 @@ static OverrideIsolationResult validOverrideIsolation(
if (isAsyncDecl(overridden) ||
isAccessibleAcrossActors(
overridden, refResult.isolation, declContext)) {
// FIXME: Perform Sendable checking here because we're entering an
// actor.
return OverrideIsolationResult::Allowed;
return OverrideIsolationResult::Sendable;
}

// If the overridden declaration is from Objective-C with no actor
Expand Down Expand Up @@ -3898,7 +3902,9 @@ void swift::checkOverrideActorIsolation(ValueDecl *value) {
return;

case OverrideIsolationResult::Sendable:
// FIXME: Do the Sendable check.
diagnoseNonSendableTypesInReference(
getDeclRefInContext(value), value->getInnermostDeclContext(),
value->getLoc(), SendableCheckReason::Override);
return;

case OverrideIsolationResult::Disallowed:
Expand Down Expand Up @@ -4836,9 +4842,8 @@ bool swift::isThrowsDecl(ConcreteDeclRef declRef) {
return false;
}

bool swift::isAccessibleAcrossActors(
ValueDecl *value, const ActorIsolation &isolation,
const DeclContext *fromDC, Optional<ReferencedActor> actorInstance) {
/// Determine whether a reference to this value isn't actually a value.
static bool isNonValueReference(const ValueDecl *value) {
switch (value->getKind()) {
case DeclKind::AssociatedType:
case DeclKind::Class:
Expand All @@ -4849,13 +4854,7 @@ bool swift::isAccessibleAcrossActors(
case DeclKind::Protocol:
case DeclKind::Struct:
case DeclKind::TypeAlias:
return true;

case DeclKind::EnumCase:
case DeclKind::EnumElement:
// Type-level entities are always accessible across actors.
return true;

case DeclKind::IfConfig:
case DeclKind::Import:
case DeclKind::InfixOperator:
Expand All @@ -4867,16 +4866,26 @@ bool swift::isAccessibleAcrossActors(
case DeclKind::PrecedenceGroup:
case DeclKind::PrefixOperator:
case DeclKind::TopLevelCode:
// Non-value entities are always accessible across actors.
return true;

case DeclKind::Destructor:
// Destructors are always accessible across actors.
return true;

case DeclKind::EnumElement:
case DeclKind::Constructor:
// Initializers are accessible across actors unless they are global-actor
// qualified.
case DeclKind::Param:
case DeclKind::Var:
case DeclKind::Accessor:
case DeclKind::Func:
case DeclKind::Subscript:
return false;
}
}

bool swift::isAccessibleAcrossActors(
ValueDecl *value, const ActorIsolation &isolation,
const DeclContext *fromDC, Optional<ReferencedActor> actorInstance) {
// Initializers and enum elements are accessible across actors unless they
// are global-actor qualified.
if (isa<ConstructorDecl>(value) || isa<EnumElementDecl>(value)) {
switch (isolation) {
case ActorIsolation::ActorInstance:
case ActorIsolation::Independent:
Expand All @@ -4887,19 +4896,15 @@ bool swift::isAccessibleAcrossActors(
case ActorIsolation::GlobalActor:
return false;
}
}

case DeclKind::Param:
case DeclKind::Var:
// 'let' declarations are immutable, so some of them can be accessed across
// actors.
return varIsSafeAcrossActors(
fromDC->getParentModule(), cast<VarDecl>(value), isolation);

case DeclKind::Accessor:
case DeclKind::Func:
case DeclKind::Subscript:
return false;
// 'let' declarations are immutable, so some of them can be accessed across
// actors.
if (auto var = dyn_cast<VarDecl>(value)) {
return varIsSafeAcrossActors(fromDC->getParentModule(), var, isolation);
}

return false;
}

ActorReferenceResult ActorReferenceResult::forSameConcurrencyDomain(
Expand Down Expand Up @@ -4948,6 +4953,11 @@ ActorReferenceResult ActorReferenceResult::forReference(
declIsolation = declIsolation.subst(declRef.getSubstitutions());
}

// If the entity we are referencing is not a value, we're in thesame
// concurrency domain.
if (isNonValueReference(declRef.getDecl()))
return forSameConcurrencyDomain(declIsolation);

// Compute the isolation of the context, if not provided.
ActorIsolation contextIsolation = ActorIsolation::forUnspecified();
if (knownContextIsolation) {
Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/TypeCheckConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ enum class SendableCheckReason {
/// actor isolation.
Conformance,

/// An override of a function.
Override,

/// The declaration is being exposed to Objective-C.
ObjC,
};
Expand Down
4 changes: 2 additions & 2 deletions test/Concurrency/concurrent_value_checking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ protocol AsyncProto {
}

extension A1: AsyncProto {
func asyncMethod(_: NotConcurrent) async { } // expected-warning{{non-sendable type 'NotConcurrent' in parameter of actor-isolated instance method 'asyncMethod' satisfying non-isolated protocol requirement cannot cross actor boundary}}
func asyncMethod(_: NotConcurrent) async { } // expected-warning{{non-sendable type 'NotConcurrent' in parameter of actor-isolated instance method 'asyncMethod' satisfying protocol requirement cannot cross actor boundary}}
}

protocol MainActorProto {
Expand All @@ -232,7 +232,7 @@ protocol MainActorProto {

class SomeClass: MainActorProto {
@SomeGlobalActor
func asyncMainMethod(_: NotConcurrent) async { } // expected-warning{{non-sendable type 'NotConcurrent' in parameter of global actor 'SomeGlobalActor'-isolated instance method 'asyncMainMethod' satisfying non-isolated protocol requirement cannot cross actor boundary}}
func asyncMainMethod(_: NotConcurrent) async { } // expected-warning{{non-sendable type 'NotConcurrent' in parameter of global actor 'SomeGlobalActor'-isolated instance method 'asyncMainMethod' satisfying protocol requirement cannot cross actor boundary}}
}

// ----------------------------------------------------------------------
Expand Down
16 changes: 8 additions & 8 deletions test/Concurrency/sendable_conformance_checking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ protocol AsyncProtocolWithNotSendable {
// actor's domain.
@available(SwiftStdlib 5.1, *)
actor A3: AsyncProtocolWithNotSendable {
func f() async -> NotSendable { NotSendable() } // expected-warning{{non-sendable type 'NotSendable' returned by actor-isolated instance method 'f()' satisfying non-isolated protocol requirement cannot cross actor boundary}}
func f() async -> NotSendable { NotSendable() } // expected-warning{{non-sendable type 'NotSendable' returned by actor-isolated instance method 'f()' satisfying protocol requirement cannot cross actor boundary}}

var prop: NotSendable { // expected-warning{{non-sendable type 'NotSendable' in conformance of actor-isolated property 'prop' to non-isolated protocol requirement cannot cross actor boundary}}
var prop: NotSendable { // expected-warning{{non-sendable type 'NotSendable' in conformance of actor-isolated property 'prop' to protocol requirement cannot cross actor boundary}}
get async {
NotSendable()
}
Expand All @@ -53,9 +53,9 @@ actor A3: AsyncProtocolWithNotSendable {
// actor's domain.
@available(SwiftStdlib 5.1, *)
actor A4: AsyncProtocolWithNotSendable {
func f() -> NotSendable { NotSendable() } // expected-warning{{non-sendable type 'NotSendable' returned by actor-isolated instance method 'f()' satisfying non-isolated protocol requirement cannot cross actor boundary}}
func f() -> NotSendable { NotSendable() } // expected-warning{{non-sendable type 'NotSendable' returned by actor-isolated instance method 'f()' satisfying protocol requirement cannot cross actor boundary}}

var prop: NotSendable { // expected-warning{{non-sendable type 'NotSendable' in conformance of actor-isolated property 'prop' to non-isolated protocol requirement cannot cross actor boundary}}
var prop: NotSendable { // expected-warning{{non-sendable type 'NotSendable' in conformance of actor-isolated property 'prop' to protocol requirement cannot cross actor boundary}}
get {
NotSendable()
}
Expand Down Expand Up @@ -98,9 +98,9 @@ protocol AsyncThrowingProtocolWithNotSendable {
// actor's domain.
@available(SwiftStdlib 5.1, *)
actor A7: AsyncThrowingProtocolWithNotSendable {
func f() async -> NotSendable { NotSendable() } // expected-warning{{non-sendable type 'NotSendable' returned by actor-isolated instance method 'f()' satisfying non-isolated protocol requirement cannot cross actor boundary}}
func f() async -> NotSendable { NotSendable() } // expected-warning{{non-sendable type 'NotSendable' returned by actor-isolated instance method 'f()' satisfying protocol requirement cannot cross actor boundary}}

var prop: NotSendable { // expected-warning{{non-sendable type 'NotSendable' in conformance of actor-isolated property 'prop' to non-isolated protocol requirement cannot cross actor boundary}}
var prop: NotSendable { // expected-warning{{non-sendable type 'NotSendable' in conformance of actor-isolated property 'prop' to protocol requirement cannot cross actor boundary}}
get async {
NotSendable()
}
Expand All @@ -111,9 +111,9 @@ actor A7: AsyncThrowingProtocolWithNotSendable {
// actor's domain.
@available(SwiftStdlib 5.1, *)
actor A8: AsyncThrowingProtocolWithNotSendable {
func f() -> NotSendable { NotSendable() } // expected-warning{{non-sendable type 'NotSendable' returned by actor-isolated instance method 'f()' satisfying non-isolated protocol requirement cannot cross actor boundary}}
func f() -> NotSendable { NotSendable() } // expected-warning{{non-sendable type 'NotSendable' returned by actor-isolated instance method 'f()' satisfying protocol requirement cannot cross actor boundary}}

var prop: NotSendable { // expected-warning{{non-sendable type 'NotSendable' in conformance of actor-isolated property 'prop' to non-isolated protocol requirement cannot cross actor boundary}}
var prop: NotSendable { // expected-warning{{non-sendable type 'NotSendable' in conformance of actor-isolated property 'prop' to protocol requirement cannot cross actor boundary}}
get {
NotSendable()
}
Expand Down
28 changes: 28 additions & 0 deletions test/Concurrency/sendable_override_checking.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// RUN: %target-typecheck-verify-swift
// REQUIRES: concurrency

@available(SwiftStdlib 5.1, *)
class NotSendable { // expected-note 2{{class 'NotSendable' does not conform to the 'Sendable' protocol}}
}

@available(SwiftStdlib 5.1, *)
@available(*, unavailable)
extension NotSendable: Sendable { }

@available(SwiftStdlib 5.1, *)
class Super {
func f(_: NotSendable) async { }
@MainActor func g1(_: NotSendable) { }
@MainActor func g2(_: NotSendable) async { }
}

@available(SwiftStdlib 5.1, *)
class Sub: Super {
@MainActor override func f(_: NotSendable) async { }
// expected-warning@-1{{non-sendable type 'NotSendable' in parameter of main actor-isolated overriding instance method 'f' cannot cross actor boundary}}

nonisolated override func g1(_: NotSendable) { } // okay, synchronous

nonisolated override func g2(_: NotSendable) async { }
// expected-warning@-1{{non-sendable type 'NotSendable' in parameter of nonisolated overriding instance method 'g2' cannot cross actor boundary}}
}
2 changes: 1 addition & 1 deletion test/Distributed/distributed_protocol_isolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ protocol Server {
func send<Message: Codable>(message: Message) async throws -> String
}
actor MyServer : Server {
func send<Message: Codable>(message: Message) throws -> String { "" } // expected-warning{{non-sendable type 'Message' in parameter of actor-isolated instance method 'send(message:)' satisfying non-isolated protocol requirement cannot cross actor boundary}}
func send<Message: Codable>(message: Message) throws -> String { "" } // expected-warning{{non-sendable type 'Message' in parameter of actor-isolated instance method 'send(message:)' satisfying protocol requirement cannot cross actor boundary}}
}

protocol AsyncThrowsAll {
Expand Down