Skip to content

Enable ConcurrentValue checking as part of Concurrency mode. #36084

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
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
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4316,6 +4316,9 @@ WARNING(non_concurrent_property_type,none,
"cannot use %0 %1 with a non-concurrent-value type %2 "
"%select{across actors|from concurrently-executed code}3",
(DescriptiveDeclKind, DeclName, Type, bool))
WARNING(non_concurrent_keypath_capture,none,
"cannot form key path that captures non-concurrent-value type %0",
(Type))
ERROR(non_concurrent_type_member,none,
"%select{stored property %1|associated value %1}0 of "
"'ConcurrentValue'-conforming %2 %3 has non-concurrent-value type %4",
Expand Down
3 changes: 0 additions & 3 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,6 @@ namespace swift {
/// Enable experimental concurrency model.
bool EnableExperimentalConcurrency = false;

/// Enable experimental ConcurrentValue checking.
bool EnableExperimentalConcurrentValueChecking = false;

/// Enable experimental flow-sensitive concurrent captures.
bool EnableExperimentalFlowSensitiveConcurrentCaptures = false;

Expand Down
4 changes: 0 additions & 4 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,6 @@ def enable_experimental_concurrency :
Flag<["-"], "enable-experimental-concurrency">,
HelpText<"Enable experimental concurrency model">;

def enable_experimental_concurrent_value_checking :
Flag<["-"], "enable-experimental-concurrent-value-checking">,
HelpText<"Enable ConcurrentValue checking">;

def enable_experimental_flow_sensitive_concurrent_captures :
Flag<["-"], "enable-experimental-flow-sensitive-concurrent-captures">,
HelpText<"Enable flow-sensitive concurrent captures">;
Expand Down
2 changes: 0 additions & 2 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,6 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,

Opts.EnableExperimentalConcurrency |=
Args.hasArg(OPT_enable_experimental_concurrency);
Opts.EnableExperimentalConcurrentValueChecking |=
Args.hasArg(OPT_enable_experimental_concurrent_value_checking);
Opts.EnableExperimentalFlowSensitiveConcurrentCaptures |=
Args.hasArg(OPT_enable_experimental_flow_sensitive_concurrent_captures);

Expand Down
83 changes: 17 additions & 66 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -793,71 +793,6 @@ static bool isConcurrentValueType(const DeclContext *dc, Type type) {
return checker.visit(type);
}

Optional<NonConcurrentType> NonConcurrentType::get(
const DeclContext *dc, ConcreteDeclRef declRef) {
// For functions, check the parameter and result types.
SubstitutionMap subs = declRef.getSubstitutions();
if (auto function = dyn_cast<AbstractFunctionDecl>(declRef.getDecl())) {
for (auto param : *function->getParameters()) {
Type paramType = param->getInterfaceType().subst(subs);
if (!isConcurrentValueType(dc, paramType)) {
return NonConcurrentType {
Kind::Parameter, ConcreteDeclRef(param, subs), paramType };
}
}

// Check the result type of a function.
if (auto func = dyn_cast<FuncDecl>(function)) {
Type resultType = func->getResultInterfaceType().subst(subs);
if (!isConcurrentValueType(dc, resultType)) {
return NonConcurrentType { Kind::Result, declRef, resultType };
}
}

// Check the "self" type of an instance method.
if (function->isInstanceMember()) {
if (auto selfParam = function->getImplicitSelfDecl()) {
Type paramType = selfParam->getInterfaceType().subst(subs);
if (!isConcurrentValueType(dc, paramType)) {
return NonConcurrentType {
Kind::Parameter, ConcreteDeclRef(selfParam, subs),
paramType };
}
}
}
} else if (auto var = dyn_cast<VarDecl>(declRef.getDecl())) {
Type propertyType = var->getValueInterfaceType().subst(subs);
if (!isConcurrentValueType(dc, propertyType)) {
return NonConcurrentType {
Kind::Property, declRef, propertyType };
}
}

return None;
}

void NonConcurrentType::diagnose(SourceLoc loc) {
ASTContext &ctx = declRef.getDecl()->getASTContext();

switch (kind) {
case Parameter:
ctx.Diags.diagnose(loc, diag::non_concurrent_param_type, type);
break;

case Result:
ctx.Diags.diagnose(loc, diag::non_concurrent_result_type, type);
break;

case Property: {
auto var = cast<VarDecl>(declRef.getDecl());
ctx.Diags.diagnose(loc, diag::non_concurrent_property_type,
var->getDescriptiveKind(), var->getName(),
type, var->isLocalCapture());
break;
}
}
}

static bool diagnoseNonConcurrentParameter(
SourceLoc loc, ConcurrentReferenceKind refKind, ConcreteDeclRef declRef,
ParamDecl *param, Type paramType) {
Expand Down Expand Up @@ -888,7 +823,7 @@ bool swift::diagnoseNonConcurrentTypesInReference(
ConcreteDeclRef declRef, const DeclContext *dc, SourceLoc loc,
ConcurrentReferenceKind refKind) {
// Bail out immediately if we aren't supposed to do this checking.
if (!dc->getASTContext().LangOpts.EnableExperimentalConcurrentValueChecking)
if (!dc->getASTContext().LangOpts.EnableExperimentalConcurrency)
return false;

// For functions, check the parameter and result types.
Expand Down Expand Up @@ -1176,6 +1111,22 @@ namespace {
}
}

// Key paths require any captured values to be ConcurrentValue-conforming.
if (auto keyPath = dyn_cast<KeyPathExpr>(expr)) {
for (const auto &component : keyPath->getComponents()) {
auto indexExpr = component.getIndexExpr();
if (!indexExpr || !indexExpr->getType())
continue;

if (ctx.LangOpts.EnableExperimentalConcurrency &&
!isConcurrentValueType(getDeclContext(), indexExpr->getType())) {
ctx.Diags.diagnose(
component.getLoc(), diag::non_concurrent_keypath_capture,
indexExpr->getType());
}
}
}

// The children of #selector expressions are not evaluated, so we do not
// need to do isolation checking there. This is convenient because such
// expressions tend to violate restrictions on the use of instance
Expand Down
30 changes: 0 additions & 30 deletions lib/Sema/TypeCheckConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,36 +66,6 @@ enum class ConcurrentReferenceKind {
ConcurrentFunction,
};

/// Describes why or where a particular entity has a non-concurrent-value type.
struct NonConcurrentType {
enum Kind {
/// A function parameter is a non-concurrent-value type.
Parameter,
/// The result of a function is a non-concurrent-value type.
Result,
/// The type of a property is a non-concurrent-value type.
Property,
} kind;

/// The declaration reference.
ConcreteDeclRef declRef;

/// The non-concurrent-value type being diagnosed.
Type type;

/// Determine whether a reference to the given declaration involves a
/// non-concurrent-value type. If it does, return the reason. Otherwise,
/// return \c None.
///
/// \param dc The declaration context from which the reference occurs.
/// \param declRef The reference to the declaration.
static Optional<NonConcurrentType> get(
const DeclContext *dc, ConcreteDeclRef declRef);

/// Diagnose the non-concurrent-value type at the given source location.
void diagnose(SourceLoc loc);
};

/// The isolation restriction in effect for a given declaration that is
/// referenced from source.
class ActorIsolationRestriction {
Expand Down
11 changes: 11 additions & 0 deletions test/Concurrency/actor_call_implicitly_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,22 @@ func blender(_ peeler : () -> Void) {

@OrangeActor func makeSmoothie() async {
await wisk({})
// expected-warning@-1{{cannot pass argument of non-concurrent-value type 'Any' across actors}}
await wisk(1)
// expected-warning@-1{{cannot pass argument of non-concurrent-value type 'Any' across actors}}
await (peelBanana)()
await (((((peelBanana)))))()
await (((wisk)))((wisk)((wisk)(1)))
// expected-warning@-1 3{{cannot pass argument of non-concurrent-value type 'Any' across actors}}

blender((peelBanana)) // expected-error {{global function 'peelBanana()' isolated to global actor 'BananaActor' can not be referenced from different global actor 'OrangeActor'}}
await wisk(peelBanana) // expected-error {{global function 'peelBanana()' isolated to global actor 'BananaActor' can not be referenced from different global actor 'OrangeActor'}}
// expected-warning@-1{{cannot pass argument of non-concurrent-value type 'Any' across actors}}

await wisk(wisk) // expected-error {{global function 'wisk' isolated to global actor 'BananaActor' can not be referenced from different global actor 'OrangeActor'}}
// expected-warning@-1{{cannot pass argument of non-concurrent-value type 'Any' across actors}}
await (((wisk)))(((wisk))) // expected-error {{global function 'wisk' isolated to global actor 'BananaActor' can not be referenced from different global actor 'OrangeActor'}}
// expected-warning@-1{{cannot pass argument of non-concurrent-value type 'Any' across actors}}

// expected-warning@+2 {{no calls to 'async' functions occur within 'await' expression}}
// expected-error@+1 {{global function 'wisk' isolated to global actor 'BananaActor' can not be referenced from different global actor 'OrangeActor'}}
Expand Down Expand Up @@ -205,13 +211,18 @@ actor Calculator {

@OrangeActor func doSomething() async {
let _ = (await bananaAdd(1))(2)
// expected-warning@-1{{cannot call function returning non-concurrent-value type}}
let _ = await (await bananaAdd(1))(2) // expected-warning{{no calls to 'async' functions occur within 'await' expression}}
// expected-warning@-1{{cannot call function returning non-concurrent-value type}}

let calc = Calculator()

let _ = (await calc.addCurried(1))(2)
// expected-warning@-1{{cannot call function returning non-concurrent-value type}}
let _ = await (await calc.addCurried(1))(2) // expected-warning{{no calls to 'async' functions occur within 'await' expression}}
// expected-warning@-1{{cannot call function returning non-concurrent-value type}}

let plusOne = await calc.addCurried(await calc.add(0, 1))
// expected-warning@-1{{cannot call function returning non-concurrent-value type}}
let _ = plusOne(2)
}
2 changes: 1 addition & 1 deletion test/Concurrency/async_cancellation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func test_cancellation_guard_isCancelled(_ any: Any) async -> PictureData {
return PictureData.value("...")
}

struct SomeFile {
struct SomeFile: ConcurrentValue {
func close() {}
}

Expand Down
8 changes: 4 additions & 4 deletions test/Concurrency/async_task_groups.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func test_taskGroup_quorum_thenCancel() async {
case yay
case nay
}
struct Follower {
struct Follower: ConcurrentValue {
init(_ name: String) {}
func vote() async throws -> Vote {
// "randomly" vote yes or no
Expand Down Expand Up @@ -181,13 +181,13 @@ func test_taskGroup_quorum_thenCancel() async {
_ = await gatherQuorum(followers: [Follower("A"), Follower("B"), Follower("C")])
}

extension Collection {
extension Collection where Self: ConcurrentValue, Element: ConcurrentValue, Self.Index: ConcurrentValue {

/// Just another example of how one might use task groups.
func map<T>(
func map<T: ConcurrentValue>(
parallelism requestedParallelism: Int? = nil/*system default*/,
// ordered: Bool = true, /
_ transform: (Element) async throws -> T
_ transform: @concurrent (Element) async throws -> T
) async throws -> [T] { // TODO: can't use rethrows here, maybe that's just life though; rdar://71479187 (rethrows is a bit limiting with async functions that use task groups)
let defaultParallelism = 2
let parallelism = requestedParallelism ?? defaultParallelism
Expand Down
19 changes: 18 additions & 1 deletion test/Concurrency/concurrent_value_checking.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency -enable-experimental-concurrent-value-checking
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency
// REQUIRES: concurrency

class NotConcurrent { }
Expand Down Expand Up @@ -122,6 +122,23 @@ func testConcurrency() {
}
}

// ----------------------------------------------------------------------
// ConcurrentValue restriction on key paths.
// ----------------------------------------------------------------------
class NC: Hashable {
func hash(into: inout Hasher) { }
static func==(_: NC, _: NC) -> Bool { true }
}

class HasNC {
var dict: [NC: Int] = [:]
}

func testKeyPaths(dict: [NC: Int], nc: NC) {
_ = \HasNC.dict[nc] // expected-warning{{cannot form key path that captures non-concurrent-value type 'NC'}}
}


// ----------------------------------------------------------------------
// ConcurrentValue restriction on conformances.
// ----------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/concurrent_value_checking_objc.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency -enable-experimental-concurrent-value-checking
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency

// REQUIRES: concurrency
// REQUIRES: objc_interop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func testCaseTrivialValue4() {
// expected-note @-8 {{capturing use}}
}

class Klass {
class Klass: UnsafeConcurrentValue {
var next: Klass? = nil
}
func inoutUserKlass(_ k: inout Klass) {}
Expand Down Expand Up @@ -130,7 +130,7 @@ func testCaseClassInoutField() {
// Non Trivial Value Type //
////////////////////////////

struct NonTrivialValueType {
struct NonTrivialValueType: ConcurrentValue {
var i: Int
var k: Klass? = nil

Expand Down Expand Up @@ -182,7 +182,7 @@ protocol MyProt {
var k: Klass? { get set }
}

func testCaseAddressOnlyAllocBoxToStackable<T : MyProt>(i : T) {
func testCaseAddressOnlyAllocBoxToStackable<T : MyProt & ConcurrentValue>(i : T) {
var i2 = i
f {
print(i2.i + 17)
Expand All @@ -199,7 +199,7 @@ func testCaseAddressOnlyAllocBoxToStackable<T : MyProt>(i : T) {

// Alloc box to stack can't handle this test case, so show off a bit and make
// sure we can emit a great diagnostic here!
func testCaseAddressOnlyNoAllocBoxToStackable<T : MyProt>(i : T) {
func testCaseAddressOnlyNoAllocBoxToStackable<T : MyProt & ConcurrentValue>(i : T) {
let f2 = F()
var i2 = i
f2.useConcurrent {
Expand Down
1 change: 1 addition & 0 deletions test/attr/attr_objc_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ actor MyActor {
// CHECK: @objc func doBigJobOrFail(_: Int) async throws -> (AnyObject, Int)
// CHECK-DUMP: func_decl{{.*}}doBigJobOrFail{{.*}}foreign_async=@convention(block) (Optional<AnyObject>, Int, Optional<Error>) -> (),completion_handler_param=1,error_param=2
@objc func doBigJobOrFail(_: Int) async throws -> (AnyObject, Int) { return (self, 0) }
// expected-warning@-1{{cannot call function returning non-concurrent-value type '(AnyObject, Int)' across actors}}

// Actor-isolated entities cannot be exposed to Objective-C.
@objc func synchronousBad() { } // expected-error{{actor-isolated instance method 'synchronousBad()' cannot be @objc}}
Expand Down