Skip to content

[coro] Add @:coroutine.scope and @:coroutine.restrictedSuspension#12594

Open
Simn wants to merge 4 commits intodevelopmentfrom
coro-scope
Open

[coro] Add @:coroutine.scope and @:coroutine.restrictedSuspension#12594
Simn wants to merge 4 commits intodevelopmentfrom
coro-scope

Conversation

@Simn
Copy link
Member

@Simn Simn commented Feb 11, 2026

We need just a little bit more compiler support for hxcoro that relates to coroutine scopes. This gives special semantics to the first argument of a @:coroutine function, which works like this:

@:coroutine.scope

This now gives an error:

@:coroutine.scope
class MyScope {}

function main() {
	@:coroutine function outer(outerScope:MyScope) {
		@:coroutine function inner(innerScope:MyScope) {
			outerScope; // Invalid usage of a coroutine scope in a different coroutine scope
		}
	}
}

You can think of these scopes as some kind of "coroutine-this", where keeping an orderly structure is important. This is relevant for structured concurrency where we really don't want users to accidentally have children mess with their parent's scope. For example, I've had code like this before:

import hxcoro.CoroRun;

function main() {
	CoroRun.run(node -> {
		node.async(_ -> {
			node.async(_ -> {});
		});
	});
}

Since the child coro doesn't shadow node, this accesses the parent's node in its body, which is not very structured. With this PR there's now a proper error.

Note that this only restricts access if there exists a nested scope, so normal capturing in closures still works as expected, which includes coroutines that don't have a scope:

@:coroutine.scope
class MyScope {}

function main() {
	@:coroutine function outer(outerScope:MyScope) {
		@:coroutine function inner() {
			outerScope; // ok
		}
	}
}

@:coroutine.restrictedSuspension

This meta implies @:coroutine.scope and restricts which coroutines we're allowed to call:

@:coroutine function someOtherCoro() {}

@:coroutine.restrictedSuspension
class MyRestrictedScope {
	public function new() {}

	@:coroutine public function scopeCoro() {
		someOtherCoro();
	}
}

function main() {
	@:coroutine function runRestricted(scope:MyRestrictedScope) {
		someOtherCoro(); // Invalid suspension call on restricted suspension scope
		scope.scopeCoro(); // ok
	}
}

In the current implementation it allows all @:coroutine field access calls on the current scope, as well as direct calls if the scope supports that:

import haxe.coro.Coroutine;

@:coroutine.restrictedSuspension
typedef MyRestrictedScope = Coroutine<() -> Void>;

function main() {
	@:coroutine function runRestricted(scope:MyRestrictedScope) {
		scope(); // ok
	}
}

There might be more cases we want to allow, which can be looked into later. This feature is necessary in order to support generators which are not supposed to invoke arbitrary coroutines.

@skial skial mentioned this pull request Feb 12, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant