Skip to content

[Concurrency] Add an experimental macro for wrapping a function body in a new task. #79729

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 1 commit into from
Mar 4, 2025
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
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<SafeAttr>() ||
decl->getAttrs().hasAttribute<UnsafeAttr>())
Expand Down
1 change: 1 addition & 0 deletions lib/Macros/Sources/SwiftMacros/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ add_swift_macro_library(SwiftMacros
OptionSetMacro.swift
DebugDescriptionMacro.swift
DistributedResolvableMacro.swift
StartTaskMacro.swift
SyntaxExtensions.swift
TaskLocalMacro.swift
SwiftifyImportMacro.swift
Expand Down
53 changes: 53 additions & 0 deletions lib/Macros/Sources/SwiftMacros/StartTaskMacro.swift
Original file line number Diff line number Diff line change
@@ -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)
"""
]
}
}
13 changes: 13 additions & 0 deletions lib/Sema/TypeCheckMacros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions stdlib/public/Concurrency/Actor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ internal func _enqueueOnMain(_ job: UnownedJob)
@available(SwiftStdlib 5.1, *)
@freestanding(expression)
public macro isolation<T>() -> T = Builtin.IsolationMacro

@available(SwiftStdlib 5.1, *)
@attached(body)
public macro StartTask() =
#externalMacro(module: "SwiftMacros", type: "StartTaskMacro")
#endif

#if $IsolatedAny
Expand Down
18 changes: 18 additions & 0 deletions test/Macros/start_task.swift
Original file line number Diff line number Diff line change
@@ -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()
}