Skip to content

TypeScript any, unknown, and attribution failure all collapse to the same JavaType.Unknown singleton #8534

Description

@eoliphan

any, unknown, and attribution failure all collapse to the same JavaType.Unknown singleton

Component: rewrite-javascript
Versions: rewrite-javascript 8.90.0, Moderne CLI 4.6.1, TypeScript 5.9.3, Node 26.7.0

What happens

Three semantically distinct situations produce the same payload-free interned value:

  1. A parameter or expression declared any — a deliberate, resolved type.
  2. A parameter or expression declared unknown — also deliberate and resolved.
  3. A genuine type-attribution failure — the parser could not determine the type.

In type-mapping.ts, the single TypeFlags.Any branch matches only intrinsicName === 'error', so
declared any falls through to the terminal return Type.unknownType. Roughly a dozen other sites
return the same singleton for real failures. There is no TypeFlags.Unknown branch at all, so the
unknown keyword lands there too. There is no any primitive (fromKeyword has no 'any' case).

The value carries no marker, flag, or provenance slot, and signs as <unknown>.

Minimal reproduction

// e-any-vs-unknown.ts
export function takesAny(x: any): void { void x; }
export function takesUnknown(y: unknown): void { void y; }

Both parameter types arrive as the same JavaType.Unknown instance that an unresolvable type
produces. Nothing distinguishes them from each other or from a failure.

Expected

A representable distinction — for example, separate kinds, or a payload on Unknown recording
whether it originated from declared any, declared unknown, or a failed resolution. Line 220
already reads intrinsicName to isolate 'error', so the discrimination point exists; what is
missing is somewhere to record the result.

Why this matters downstream (motivating case)

We build a code-property-graph frontend used for cross-repository code navigation. It must
distinguish "this parameter is deliberately untyped" from "we failed to type this parameter",
because the two demand opposite responses: the first is the author's decision and is final; the
second is a gap that better configuration or a fixed parser could close.

At 8.90.0 we cannot tell them apart. Concretely, on one repository 1,159 of our unresolved-callee
events are this value, while the source contains only 107 explicit any annotations and zero uses of
the unknown keyword — so most of them are probably genuine failures, but we cannot demonstrate
that
, and we therefore cannot state an honest ceiling for TypeScript call resolution. The
measurement is blocked by the conflation, not by the rate.

Related

  • Do not treat missing type attribution as proof of semantic equality #8333 established the principle that missing type attribution must not be read as information
    (the SemanticallyEqual/RemoveSelfAssignment incident). This is the same principle one layer
    down: here, information (any was declared) is indistinguishable from its absence. Cited as an
    analogous principle, not as the same defect.

Offer

The change looks small and well-scoped. We are willing to prepare a PR adding the
any/unknown/error discrimination if the maintainers indicate which representation they would
accept — a distinct Kind, or a payload on Unknown.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions