Summary
Calling someTask.AsUniTask() from an asmdef with "noEngineReferences": true produces:
error CS0012: The type 'Awaitable' is defined in an assembly that is not referenced.
You must add a reference to assembly 'UnityEngine.CoreModule, ...'.
The receiver is System.Threading.Tasks.Task — nothing engine-related at the call site. The error comes from overload resolution pulling in the UnityAwaitableExtensions.AsUniTask(this
UnityEngine.Awaitable) candidate, even though it isn't applicable.
Environment
- UniTask: 2.5.11
- Unity: 6000.x (any Unity ≥ 2023.1, since Runtime/UnityAwaitableExtensions.cs is gated on UNITY_2023_1_OR_NEWER)
Repro
Consumer asmdef:
{ "name": "MyApp.Logic", "references": ["UniTask"], "noEngineReferences": true }
Call site:
using Cysharp.Threading.Tasks;
using System.Threading.Tasks;
public class Foo
{
public void Run(Task t) => t.AsUniTask().Forget();
}
→ CS0012.
Diagnosis
Runtime/UnityAwaitableExtensions.cs declares its extensions in the same Cysharp.Threading.Tasks namespace as UniTaskExtensions.AsUniTask(this Task). To check applicability of every
AsUniTask candidate in scope, the compiler must resolve every candidate's parameter types — including UnityEngine.Awaitable, which the consumer's asmdef can't see. A single optional,
engine-specific bridge therefore contaminates overload resolution for every AsUniTask call.
Why it matters
Clean-architecture Unity projects often mark Application/Domain layers as engine-free so business logic stays unit-testable outside the player. Those layers regularly need to bridge
third-party Task-returning APIs (network SDKs, etc.) into UniTask. Today, every such bridge forces noEngineReferences: false, dropping the layering guarantee.
Concrete case I hit: an engine-free Application layer calling a third-party SDK whose async API returns Task; .AsUniTask().Forget() fails to compile despite the call never touching
Awaitable.
Prior reports of the same packaging pattern
This isn't a one-off — the same root cause (monolithic UniTask assembly + extensions referencing optional engine surface) has surfaced repeatedly:
- #222 — UniTask fails to compile when Physics / Physics2D modules are disabled
- #35 — Request to separate editor scripts into their own assembly
- #572 — Pain consuming UniTask.NetCore from Unity
- #416 — Originating issue for UnityAwaitableExtensions, no discussion of the engine-free-consumer side effect at the time
The structural fix is the same in each case: split optional surface into its own asmdef so consumers can opt in.
Proposed fixes
A — sub-namespace the Awaitable extensions
Move UnityAwaitableExtensions from Cysharp.Threading.Tasks to Cysharp.Threading.Tasks.UnityEngine (or similar). Consumers' existing using Cysharp.Threading.Tasks; stops bringing the
Awaitable methods into extension-method scope; engine-aware code adds using Cysharp.Threading.Tasks.UnityEngine;. One-line change. Breaking for users who relied on the bridge being in the
root namespace; documentable.
B — split into its own asmdef
Mirror what UniTask.Linq already does: create UniTask.UnityAwaitable.asmdef containing only UnityAwaitableExtensions.cs, gated on UNITY_2023_1_OR_NEWER, referencing UniTask. Engine-free
consumers don't reference it; engine-aware consumers do. Structurally cleaner, more consistent with the existing UniTask.Linq partition. Breaking for asmdef references; one-line migration
per consuming asmdef.
Workarounds available today
- Static-method form: UniTaskExtensions.AsUniTask(task).Forget(); — pins resolution to one class.
- Wrap in an async UniTaskVoid helper that awaits the Task directly (no .AsUniTask() call).
- Flip noEngineReferences: false (loses the layering guarantee).
Happy to PR either fix if there's a preferred direction.
Summary
Calling someTask.AsUniTask() from an asmdef with "noEngineReferences": true produces:
error CS0012: The type 'Awaitable' is defined in an assembly that is not referenced.
You must add a reference to assembly 'UnityEngine.CoreModule, ...'.
The receiver is System.Threading.Tasks.Task — nothing engine-related at the call site. The error comes from overload resolution pulling in the UnityAwaitableExtensions.AsUniTask(this
UnityEngine.Awaitable) candidate, even though it isn't applicable.
Environment
Repro
Consumer asmdef:
{ "name": "MyApp.Logic", "references": ["UniTask"], "noEngineReferences": true }
Call site:
using Cysharp.Threading.Tasks;
using System.Threading.Tasks;
public class Foo
{
public void Run(Task t) => t.AsUniTask().Forget();
}
→ CS0012.
Diagnosis
Runtime/UnityAwaitableExtensions.cs declares its extensions in the same Cysharp.Threading.Tasks namespace as UniTaskExtensions.AsUniTask(this Task). To check applicability of every
AsUniTask candidate in scope, the compiler must resolve every candidate's parameter types — including UnityEngine.Awaitable, which the consumer's asmdef can't see. A single optional,
engine-specific bridge therefore contaminates overload resolution for every AsUniTask call.
Why it matters
Clean-architecture Unity projects often mark Application/Domain layers as engine-free so business logic stays unit-testable outside the player. Those layers regularly need to bridge
third-party Task-returning APIs (network SDKs, etc.) into UniTask. Today, every such bridge forces noEngineReferences: false, dropping the layering guarantee.
Concrete case I hit: an engine-free Application layer calling a third-party SDK whose async API returns Task; .AsUniTask().Forget() fails to compile despite the call never touching
Awaitable.
Prior reports of the same packaging pattern
This isn't a one-off — the same root cause (monolithic UniTask assembly + extensions referencing optional engine surface) has surfaced repeatedly:
The structural fix is the same in each case: split optional surface into its own asmdef so consumers can opt in.
Proposed fixes
A — sub-namespace the Awaitable extensions
Move UnityAwaitableExtensions from Cysharp.Threading.Tasks to Cysharp.Threading.Tasks.UnityEngine (or similar). Consumers' existing using Cysharp.Threading.Tasks; stops bringing the
Awaitable methods into extension-method scope; engine-aware code adds using Cysharp.Threading.Tasks.UnityEngine;. One-line change. Breaking for users who relied on the bridge being in the
root namespace; documentable.
B — split into its own asmdef
Mirror what UniTask.Linq already does: create UniTask.UnityAwaitable.asmdef containing only UnityAwaitableExtensions.cs, gated on UNITY_2023_1_OR_NEWER, referencing UniTask. Engine-free
consumers don't reference it; engine-aware consumers do. Structurally cleaner, more consistent with the existing UniTask.Linq partition. Breaking for asmdef references; one-line migration
per consuming asmdef.
Workarounds available today
Happy to PR either fix if there's a preferred direction.