Skip to content

[Concurrency] Allow nonisolated to prevent global actor inference. #76395

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 3 commits into from
Sep 19, 2024
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
2 changes: 1 addition & 1 deletion include/swift/AST/DeclAttr.def
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ SIMPLE_DECL_ATTR(reasync, Reasync,
OnFunc | OnConstructor | RejectByParser | ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove,
109)
CONTEXTUAL_DECL_ATTR(nonisolated, Nonisolated,
DeclModifier | OnFunc | OnConstructor | OnVar | OnSubscript | ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove,
DeclModifier | OnFunc | OnConstructor | OnVar | OnSubscript | OnProtocol | OnExtension | OnClass | OnStruct | OnEnum | ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove,
112)
CONTEXTUAL_SIMPLE_DECL_ATTR(distributed, DistributedActor,
DeclModifier | OnClass | OnFunc | OnAccessor | OnVar | ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove,
Expand Down
1 change: 1 addition & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ EXPERIMENTAL_FEATURE(PreambleMacros, false)
EXPERIMENTAL_FEATURE(TupleConformances, false)
EXPERIMENTAL_FEATURE(FullTypedThrows, false)
EXPERIMENTAL_FEATURE(SameElementRequirements, false)
EXPERIMENTAL_FEATURE(GlobalActorInferenceCutoff, false)

// Whether to enable @_used and @_section attributes
EXPERIMENTAL_FEATURE(SymbolLinkageMarkers, true)
Expand Down
1 change: 1 addition & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ UNINTERESTING_FEATURE(FixedArrays)
UNINTERESTING_FEATURE(GroupActorErrors)
UNINTERESTING_FEATURE(SameElementRequirements)
UNINTERESTING_FEATURE(UnspecifiedMeansMainActorIsolated)
UNINTERESTING_FEATURE(GlobalActorInferenceCutoff)

static bool usesFeatureSendingArgsAndResults(Decl *decl) {
auto isFunctionTypeWithSending = [](Type type) {
Expand Down
49 changes: 33 additions & 16 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7127,6 +7127,15 @@ void AttributeChecker::visitNonisolatedAttr(NonisolatedAttr *attr) {
// 'nonisolated' can be applied to global and static/class variables
// that do not have storage.
auto dc = D->getDeclContext();
auto &ctx = D->getASTContext();

if (!ctx.LangOpts.hasFeature(Feature::GlobalActorInferenceCutoff)) {
if (isa<ProtocolDecl>(D) || isa<ExtensionDecl>(D) || isa<ClassDecl>(D) ||
isa<StructDecl>(D) || isa<EnumDecl>(D)) {
diagnoseAndRemoveAttr(attr, diag::invalid_decl_modifier, attr);
return;
}
}

// nonisolated(unsafe) is unsafe, but only under strict concurrency.
if (attr->isUnsafe() &&
Expand All @@ -7139,9 +7148,8 @@ void AttributeChecker::visitNonisolatedAttr(NonisolatedAttr *attr) {
auto type = var->getTypeInContext();
if (var->hasStorage()) {
{
// 'nonisolated' can not be applied to mutable stored properties unless
// qualified as 'unsafe', or is of a Sendable type on a Sendable
// value type.
// A stored property can be 'nonisolated' if it is a 'Sendable' member
// of a 'Sendable' value type.
bool canBeNonisolated = false;
if (auto nominal = dc->getSelfStructDecl()) {
if (nominal->getDeclaredTypeInContext()->isSendableType() &&
Expand All @@ -7150,26 +7158,35 @@ void AttributeChecker::visitNonisolatedAttr(NonisolatedAttr *attr) {
}
}

if (ctx.LangOpts.hasFeature(Feature::GlobalActorInferenceCutoff)) {
// Additionally, a stored property of a non-'Sendable' type can be
// explicitly marked 'nonisolated'.
if (auto parentDecl = dc->getDeclaredTypeInContext())
if (!parentDecl->isSendableType()) {
canBeNonisolated = true;
}
}

// Otherwise, this stored property has to be qualified as 'unsafe'.
if (var->supportsMutation() && !attr->isUnsafe() && !canBeNonisolated) {
diagnoseAndRemoveAttr(attr, diag::nonisolated_mutable_storage)
.fixItInsertAfter(attr->getRange().End, "(unsafe)");
var->diagnose(diag::nonisolated_mutable_storage_note, var);
return;
}
}

// 'nonisolated' without '(unsafe)' is not allowed on non-Sendable
// variables.
if (!attr->isUnsafe() && !type->hasError()) {
bool diagnosed = diagnoseIfAnyNonSendableTypes(
type,
SendableCheckContext(dc),
Type(),
SourceLoc(),
attr->getLocation(),
diag::nonisolated_non_sendable);
if (diagnosed)
return;
// 'nonisolated' without '(unsafe)' is not allowed on non-Sendable
// variables, unless they are a member of a non-'Sendable' type.
if (!attr->isUnsafe() && !type->hasError()) {
if (!(canBeNonisolated &&
ctx.LangOpts.hasFeature(Feature::GlobalActorInferenceCutoff))) {
bool diagnosed = diagnoseIfAnyNonSendableTypes(
type, SendableCheckContext(dc), Type(), SourceLoc(),
attr->getLocation(), diag::nonisolated_non_sendable);
if (diagnosed)
return;
}
}
}

if (auto nominal = dyn_cast<NominalTypeDecl>(dc)) {
Expand Down
68 changes: 0 additions & 68 deletions test/Concurrency/mutable_storage_nonisolated.swift

This file was deleted.

169 changes: 169 additions & 0 deletions test/Concurrency/nonisolated_rules.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// RUN: %target-swift-frontend -disable-availability-checking -swift-version 6 -enable-experimental-feature GlobalActorInferenceCutoff -parse-as-library %s -emit-sil -o /dev/null -verify -strict-concurrency=complete

// REQUIRES: concurrency
// REQUIRES: asserts

@MainActor
protocol GloballyIsolated {}

// expected-note@+1 {{class 'NonSendable' does not conform to the 'Sendable' protocol}}
class NonSendable {}

@propertyWrapper struct P {
var wrappedValue = 0
}

// MARK: - Structs

struct ImplicitlySendable {
var a: Int

// always okay
nonisolated let b = 0
nonisolated var c: Int { 0 }
nonisolated var d = 0

// never okay
nonisolated lazy var e = 0 // expected-error {{'nonisolated' is not supported on lazy properties}}
@P nonisolated var f = 0 // expected-error {{'nonisolated' is not supported on properties with property wrappers}}
}

struct ImplicitlyNonSendable {
let a: NonSendable

// always okay
nonisolated let b = 0
nonisolated var c: Int { 0 }
nonisolated var d = 0

// never okay
nonisolated lazy var e = 0 // expected-error {{'nonisolated' is not supported on lazy properties}}
@P nonisolated var f = 0 // expected-error {{'nonisolated' is not supported on properties with property wrappers}}
}

public struct PublicSendable: Sendable {
// always okay
nonisolated let b = 0
nonisolated var c: Int { 0 }
nonisolated var d = 0

// never okay
nonisolated lazy var e = 0 // expected-error {{'nonisolated' is not supported on lazy properties}}
@P nonisolated var f = 0 // expected-error {{'nonisolated' is not supported on properties with property wrappers}}
}

public struct PublicNonSendable {
// always okay
nonisolated let b = 0
nonisolated var c: Int { 0 }
nonisolated var d = 0

// never okay
nonisolated lazy var e = 0 // expected-error {{'nonisolated' is not supported on lazy properties}}
@P nonisolated var f = 0 // expected-error {{'nonisolated' is not supported on properties with property wrappers}}
}


nonisolated struct NonisolatedStruct: GloballyIsolated {
var x: NonSendable
var y: Int = 1

init(x: NonSendable) {
self.x = x // okay
}

struct Nested: GloballyIsolated {
// expected-note@+1 {{mutation of this property is only permitted within the actor}}
var z: NonSendable
nonisolated init(z: NonSendable) {
// expected-error@+1 {{main actor-isolated property 'z' can not be mutated from a nonisolated context}}
self.z = z
}
}
}

@MainActor struct S {
var value: NonSendable // globally-isolated
struct Nested {} // 'Nested' is not @MainActor-isolated
}

// expected-note@+1 {{calls to global function 'requireMain()' from outside of its actor context are implicitly asynchronous}}
@MainActor func requireMain() {}

nonisolated struct S1: GloballyIsolated {
var x: NonSendable
func f() {
// expected-error@+1 {{call to main actor-isolated global function 'requireMain()' in a synchronous nonisolated context}}
requireMain()
}
}

// MARK: - Protocols

nonisolated protocol Refined: GloballyIsolated {}

struct A: Refined {
var x: NonSendable
init(x: NonSendable) {
self.x = x // okay
}
}

// MARK: - Extensions

nonisolated extension GloballyIsolated {
var x: NonSendable { .init () }
func implicitlyNonisolated() {}
}

struct C: GloballyIsolated {
nonisolated func explicitlyNonisolated() {
let _ = x // okay
implicitlyNonisolated() // okay
}
}

// MARK: - Enums

nonisolated enum E: GloballyIsolated {
func implicitlyNonisolated() {}
init() {}
}

struct TestEnum {
nonisolated func call() {
E().implicitlyNonisolated() // okay
}
}

// MARK: - Classes

nonisolated class K: GloballyIsolated {
var x: NonSendable
init(x: NonSendable) {
self.x = x // okay
}
}

// MARK: - Storage of non-Sendable

class KlassA {
nonisolated var test: NonSendable = NonSendable()
}

// MARK: - Restrictions

@MainActor
nonisolated struct Conflict {}
// expected-error@-1 {{struct 'Conflict' has multiple actor-isolation attributes ('nonisolated' and 'MainActor')}}

struct B: Sendable {
// expected-error@+1 {{'nonisolated' can not be applied to variable with non-'Sendable' type 'NonSendable}}
nonisolated let test: NonSendable
}

final class KlassB: Sendable {
// expected-note@+2 {{convert 'test' to a 'let' constant or consider declaring it 'nonisolated(unsafe)' if manually managing concurrency safety}}
// expected-error@+1 {{'nonisolated' cannot be applied to mutable stored properties}}
nonisolated var test: Int = 1
}