Skip to content

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

@lhRG84

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions