diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 500a5b51ff544..19525b3a4a2b2 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -7810,6 +7810,10 @@ ERROR(conformance_macro,none, "conformance macros are replaced by extension macros", ()) +ERROR(experimental_macro,none, + "macro %0 is experimental", + (DeclName)) + ERROR(macro_resolve_circular_reference, none, "circular reference resolving %select{freestanding|attached}0 macro %1", (bool, DeclName)) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 4598154205bfd..0cf59c6fbc8f1 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -473,6 +473,9 @@ EXPERIMENTAL_FEATURE(ExtensibleEnums, true) /// Allow isolated conformances. EXPERIMENTAL_FEATURE(IsolatedConformances, true) +/// Syntax sugar features for concurrency. +EXPERIMENTAL_FEATURE(ConcurrencySyntaxSugar, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 1f339b8ef1b5c..f464537433700 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -350,6 +350,10 @@ static bool usesFeatureIsolatedConformances(Decl *decl) { return false; } +static bool usesFeatureConcurrencySyntaxSugar(Decl *decl) { + return false; +} + static bool usesFeatureMemorySafetyAttributes(Decl *decl) { if (decl->getAttrs().hasAttribute() || decl->getAttrs().hasAttribute()) diff --git a/lib/Macros/Sources/SwiftMacros/CMakeLists.txt b/lib/Macros/Sources/SwiftMacros/CMakeLists.txt index 6c6a9784000d9..5f719dd7778c6 100644 --- a/lib/Macros/Sources/SwiftMacros/CMakeLists.txt +++ b/lib/Macros/Sources/SwiftMacros/CMakeLists.txt @@ -14,6 +14,7 @@ add_swift_macro_library(SwiftMacros OptionSetMacro.swift DebugDescriptionMacro.swift DistributedResolvableMacro.swift + StartTaskMacro.swift SyntaxExtensions.swift TaskLocalMacro.swift SwiftifyImportMacro.swift diff --git a/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift b/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift new file mode 100644 index 0000000000000..428030e14f137 --- /dev/null +++ b/lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftDiagnostics +import SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +struct TaskMacroDiagnostic: DiagnosticMessage { + static func diagnose(at node: some SyntaxProtocol) -> Diagnostic { + Diagnostic(node: Syntax(node), message: Self.init()) + } + + var message: String { + "'@StartTask' macro can only be used on functions with an implementation" + } + + var severity: DiagnosticSeverity { .error } + + var diagnosticID: MessageID { + MessageID(domain: "_Concurrency", id: "StartMacro.\(self)") + } +} + + +public struct StartTaskMacro: BodyMacro { + public static func expansion( + of node: AttributeSyntax, + providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax, + in context: some MacroExpansionContext + ) throws -> [CodeBlockItemSyntax] { + guard let taskBody = declaration.body else { + context.diagnose(TaskMacroDiagnostic.diagnose(at: node)) + return [] + } + + return [ + """ + Task \(taskBody) + """ + ] + } +} diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index 2f5dafb14a61c..520cb257dd2d7 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -1735,6 +1735,19 @@ ExpandBodyMacroRequest::evaluate(Evaluator &evaluator, if (bufferID) return; + // '@StartTask' is gated behind the 'ConcurrencySyntaxSugar' + // experimental feature. + auto &ctx = fn->getASTContext(); + if (macro->getParentModule()->getName().is("_Concurrency") && + macro->getBaseIdentifier().is("StartTask") && + !ctx.LangOpts.hasFeature(Feature::ConcurrencySyntaxSugar)) { + ctx.Diags.diagnose( + customAttr->getLocation(), + diag::experimental_macro, + macro->getName()); + return; + } + auto macroSourceFile = ::evaluateAttachedMacro( macro, fn, customAttr, false, MacroRole::Body); if (!macroSourceFile) diff --git a/stdlib/public/Concurrency/Actor.swift b/stdlib/public/Concurrency/Actor.swift index 02b522ea830c6..aecd99eb52473 100644 --- a/stdlib/public/Concurrency/Actor.swift +++ b/stdlib/public/Concurrency/Actor.swift @@ -98,6 +98,11 @@ internal func _enqueueOnMain(_ job: UnownedJob) @available(SwiftStdlib 5.1, *) @freestanding(expression) public macro isolation() -> T = Builtin.IsolationMacro + +@available(SwiftStdlib 5.1, *) +@attached(body) +public macro StartTask() = + #externalMacro(module: "SwiftMacros", type: "StartTaskMacro") #endif #if $IsolatedAny diff --git a/test/Macros/start_task.swift b/test/Macros/start_task.swift new file mode 100644 index 0000000000000..2f56849a1ec62 --- /dev/null +++ b/test/Macros/start_task.swift @@ -0,0 +1,18 @@ +// REQUIRES: swift_swift_parser, swift_feature_ConcurrencySyntaxSugar + +// RUN: %target-swift-frontend -typecheck -plugin-path %swift-plugin-dir -enable-experimental-feature ConcurrencySyntaxSugar -language-mode 6 %s -dump-macro-expansions 2>&1 | %FileCheck %s + +func f() async {} + +// CHECK-LABEL: @__swiftmacro_10start_task4sync9StartTaskfMb_.swift +// CHECK: Task { +// CHECK: await f() +// CHECK: } + +@StartTask +func sync() { + await f() +} + + +