Tuple morph with un-annotated parameter infers `.infer` as `never` when the return depends on that parameter
Author: Bas950Created Jun 8, 2026Updated Jun 8, 2026
Search Terms
morph never, tuple morph infer never, => morph parameter never, scope morph output never, unannotated morph parameter
Context
- ArkType version: 2.2.0
- TypeScript version: 5.9.3
- Other context you think may be relevant (JS flavor, OS, etc.): Node 24.11.1, macOS 26.5 (arm64),
--strict
Repro
import { scope } from "arktype"
const m = scope({
Base: { text: "string" },
// ❌ tuple morph, parameter NOT annotated, return DEPENDS on the parameter
// -> `.infer` (output) is `never`
bug: ["Base", "=>", (d) => d],
// ✅ same morph with the parameter annotated -> `.infer` = { text: string }
fixedByAnnotation: ["Base", "=>", (d: { text: string }) => d],
// ✅ morph that IGNORES the parameter -> infers fine
okIgnoresParam: ["Base", "=>", () => ({ n: 1 })]
}).export()
// Probes — a type error on the line means that case's `.infer` is `never`:
const a: typeof m.bug.infer = { text: "x" } // ❌ Type '{ text: string }' is not assignable to type 'never'
const b: typeof m.fixedByAnnotation.infer = { text: "x" } // ✅ ok
const c: typeof m.okIgnoresParam.infer = { n: 1 } // ✅ okSummary
In a tuple-expression morph [In, "=>", fn], when fn's parameter is not explicitly annotated and fn's return type depends on that parameter, the resulting type's output (.infer) collapses to never.
Observations:
- It is not object-specific —
["string", "=>", (s) => s.length]collapses identically. - It is specifically about a parameter-dependent return. A morph that ignores its parameter and returns a fresh value (
() => ({ n: 1 })) infers correctly. - TypeScript emits no
TS7006(implicit-any) on the un-annotated parameter, so arktype is supplying a contextual input type — yet the output is stillnever. This suggests a circular inference: the parameter's contextual type appears to depend on the morph's output, which in turn depends on the parameter. - Workarounds: annotate the parameter (
(d: { text: string }) => d), or use the.pipe()method form (type({ text: "string" }).pipe((d) => d)), both of which infer the output correctly.
This is easy to hit in practice: an object-level normalizing morph (return the same object with one field rewritten) silently turns the whole type — and anything that references it — into never.
Source: arktypeio/arktype