Task.AsUniTask() fails to compile from engine-free asmdef consumers (CS0012 for UnityEngine.Awaitable)
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 11.2k
- Forks
- 1k
- PR merge metrics
- No merged PRs in 30d
Description
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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with Runtime/UnityAwaitableExtensions.cs and the existing UniTask.Linq assembly definition, then reproduce the CS0012 case using the noEngineReferences asmdef and Task.AsUniTask() call described here. Compare the proposed namespace and separate-asmdef approaches, and confirm done when an engine-free consumer compiles without UnityEngine.CoreModule while engine-aware Awaitable usage remains available.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, unity
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100