diff --git a/stdlib/public/BackDeployConcurrency/TaskLocal.swift b/stdlib/public/BackDeployConcurrency/TaskLocal.swift index d5eeb4d89f647..63883f9dbaac5 100644 --- a/stdlib/public/BackDeployConcurrency/TaskLocal.swift +++ b/stdlib/public/BackDeployConcurrency/TaskLocal.swift @@ -102,6 +102,7 @@ public final class TaskLocal: Sendable, CustomStringConvertible self.defaultValue = defaultValue } + @_alwaysEmitIntoClient var key: Builtin.RawPointer { unsafeBitCast(self, to: Builtin.RawPointer.self) } @@ -135,6 +136,10 @@ public final class TaskLocal: Sendable, CustomStringConvertible /// If the value is a reference type, it will be retained for the duration of /// the operation closure. @discardableResult + @inlinable + @_unsafeInheritExecutor + @available(SwiftStdlib 5.1, *) // back deploy requires we declare the availability explicitly on this method + @_backDeploy(before: SwiftStdlib 5.8) public func withValue(_ valueDuringOperation: Value, operation: () async throws -> R, file: String = #file, line: UInt = #line) async rethrows -> R { // check if we're not trying to bind a value from an illegal context; this may crash @@ -159,6 +164,7 @@ public final class TaskLocal: Sendable, CustomStringConvertible /// /// If the value is a reference type, it will be retained for the duration of /// the operation closure. + @inlinable @discardableResult public func withValue(_ valueDuringOperation: Value, operation: () throws -> R, file: String = #file, line: UInt = #line) rethrows -> R { @@ -212,6 +218,7 @@ public final class TaskLocal: Sendable, CustomStringConvertible // ==== ------------------------------------------------------------------------ @available(SwiftStdlib 5.1, *) +@usableFromInline @_silgen_name("swift_task_localValuePush") func _taskLocalValuePush( key: Builtin.RawPointer/*: Key*/, @@ -219,6 +226,7 @@ func _taskLocalValuePush( ) // where Key: TaskLocal @available(SwiftStdlib 5.1, *) +@usableFromInline @_silgen_name("swift_task_localValuePop") func _taskLocalValuePop() diff --git a/stdlib/public/Concurrency/TaskLocal.swift b/stdlib/public/Concurrency/TaskLocal.swift index 90f06449c5f79..b208ad62c5ad7 100644 --- a/stdlib/public/Concurrency/TaskLocal.swift +++ b/stdlib/public/Concurrency/TaskLocal.swift @@ -102,6 +102,7 @@ public final class TaskLocal: Sendable, CustomStringConvertible self.defaultValue = defaultValue } + @_alwaysEmitIntoClient var key: Builtin.RawPointer { unsafeBitCast(self, to: Builtin.RawPointer.self) } @@ -134,7 +135,11 @@ public final class TaskLocal: Sendable, CustomStringConvertible /// /// If the value is a reference type, it will be retained for the duration of /// the operation closure. + @inlinable @discardableResult + @_unsafeInheritExecutor + @available(SwiftStdlib 5.1, *) // back deploy requires we declare the availability explicitly on this method + @_backDeploy(before: SwiftStdlib 5.8) public func withValue(_ valueDuringOperation: Value, operation: () async throws -> R, file: String = #fileID, line: UInt = #line) async rethrows -> R { // check if we're not trying to bind a value from an illegal context; this may crash @@ -159,6 +164,7 @@ public final class TaskLocal: Sendable, CustomStringConvertible /// /// If the value is a reference type, it will be retained for the duration of /// the operation closure. + @inlinable @discardableResult public func withValue(_ valueDuringOperation: Value, operation: () throws -> R, file: String = #fileID, line: UInt = #line) rethrows -> R { @@ -212,6 +218,7 @@ public final class TaskLocal: Sendable, CustomStringConvertible // ==== ------------------------------------------------------------------------ @available(SwiftStdlib 5.1, *) +@usableFromInline @_silgen_name("swift_task_localValuePush") func _taskLocalValuePush( key: Builtin.RawPointer/*: Key*/, @@ -219,6 +226,7 @@ func _taskLocalValuePush( ) // where Key: TaskLocal @available(SwiftStdlib 5.1, *) +@usableFromInline @_silgen_name("swift_task_localValuePop") func _taskLocalValuePop() diff --git a/test/Concurrency/Runtime/async_task_locals_basic.swift b/test/Concurrency/Runtime/async_task_locals_basic.swift index 056f9136341ae..8aae82873b7bd 100644 --- a/test/Concurrency/Runtime/async_task_locals_basic.swift +++ b/test/Concurrency/Runtime/async_task_locals_basic.swift @@ -199,6 +199,24 @@ func withLocal_body_mustNotEscape() async { _ = something // silence not used warning } +@available(SwiftStdlib 5.1, *) +actor Worker { + @TaskLocal + static var declaredInActor: String = "" + + func setAndRead() async { + print("setAndRead") // CHECK: setAndRead + await Worker.$declaredInActor.withValue("value-1") { + await printTaskLocalAsync(Worker.$declaredInActor) // CHECK-NEXT: TaskLocal(defaultValue: ) (value-1) + } + } +} + +@available(SwiftStdlib 5.1, *) +func inside_actor() async { + await Worker().setAndRead() +} + @available(SwiftStdlib 5.1, *) @main struct Main { static func main() async { @@ -210,5 +228,6 @@ func withLocal_body_mustNotEscape() async { await nested_3_onlyTopContributes() await nested_3_onlyTopContributesAsync() await nested_3_onlyTopContributesMixed() + await inside_actor() } } diff --git a/test/Concurrency/async_task_locals_basic_warnings.swift b/test/Concurrency/async_task_locals_basic_warnings.swift new file mode 100644 index 0000000000000..462f01767a949 --- /dev/null +++ b/test/Concurrency/async_task_locals_basic_warnings.swift @@ -0,0 +1,23 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module -emit-module-path %t/OtherActors.swiftmodule -module-name OtherActors %S/Inputs/OtherActors.swift -disable-availability-checking +// RUN: %target-typecheck-verify-swift -I %t -disable-availability-checking -warn-concurrency -parse-as-library +// REQUIRES: concurrency + +// REQUIRES: concurrency + +@available(SwiftStdlib 5.1, *) +actor Test { + + @TaskLocal static var local: Int? + + func run() async { + // This should NOT produce any warnings, the closure withValue uses is @Sendable: + await Test.$local.withValue(42) { + await work() + } + } + + func work() async { + print("Hello \(Test.local ?? 0)") + } +}