#1624·arktype

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

Playground Link

typescript
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 }         // ✅ ok

Summary

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 still never. 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.