Skip to content

Task.AsUniTask() fails to compile from engine-free asmdef consumers (CS0012 for UnityEngine.Awaitable) #716

Description

@lhRG84

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions