A `pluck` helper extracting a key from objects. Generic over both the element type and the key. Looked right. Callers passed an array typed as `any[]` (from a JSON parse), the constraint degenerated, the result type was `any[]`. Downstream errors.
The vulnerable diff
// lib/pluck.ts
function pluck<T, K extends keyof T>(arr: T[], key: K): T[K][] {
return arr.map(o => o[key]);
}
// callers/show-users.ts
const data = JSON.parse(raw); // type: any
const ids = pluck(data, "id"); // K extends keyof any = string | number | symbol
// ids: any[] — no useful type.What is wrong
Generic constraints depend on the input type for their narrowing power. `<T, K extends keyof T>` works beautifully when T is a concrete type — `keyof T` then narrows K to the literal keys. When T is `any`, `keyof any` is `string | number | symbol`, which constrains nothing. The fix is either to fix the input type upstream (parse JSON into a known type with Zod or similar) or to require a narrower constraint via `as const` arrays in tests.
The attack
Symptom: linter green, runtime wrong:
// pluck(data, "nonexistent") // TS doesn't error if data: any
// returns array of undefined, no warning.Cascades: downstream code that expected `string[]` from `pluck(users, 'name')` gets `any[]`, misses other type errors.
Mesrai's review comment
mesraipilot · Bot · reviewed 30 sec ago
[mesrai] [code-review] [Language] [Generics] [low]
`pluck` constraint depends on a typed input. When the input is
`any[]`, the constraint provides no inference and the result is
`any[]`.
Two fixes:
1. Force a typed input at the call site by parsing JSON:
const data = UserSchema.array().parse(JSON.parse(raw));
// data: User[]
const ids = pluck(data, "id"); // ids: User['id'][]
2. Or change pluck's signature to refuse `any`:
function pluck<T extends Record<string, unknown>, K extends keyof T>(
arr: T[], key: K
): T[K][] { return arr.map(o => o[key]); }
// Now pluck(anyArr, ...) won't compile.
Usually option 1 is right — the bug is at the JSON.parse boundary,
not the helper.The fix
// callers/show-users.ts — fixed
const UserSchema = z.object({ id: z.string(), name: z.string() });
const data = UserSchema.array().parse(JSON.parse(raw));
const ids = pluck(data, "id"); // ids: string[]Validate at the JSON boundary. The pluck helper now operates on a precisely typed input. Result type is exact.
Why human review missed it
Generic constraints behave correctly only with precise inputs. `any` poisons inference and there is no compile error to indicate it. Mesrai's rule pack catches generic calls where the type argument is inferred as `any`/`unknown` from a JSON parse or similar trust boundary.
Related rules + further reading
Mesrai rule pack: language/generic-any-poisoning — flags generic functions whose inference depends on a widened input type.
TypeScript handbook: Type Inference.
Common when integrating typed code with untyped JSON ingest.
Takeaway
Generics infer from inputs. Widen the input and inference degrades. Parse at boundaries; generics stay sharp.