mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
Read T[keyof T] as unknown for T bound to an unknown-valued record
Inside a generic body Object.entries(t) over T extends Record<string, unknown> types its values 'keyof T extends never ? unknown : T[keyof T]'. An instance whose T maps to the checked-dynamic value now reads both branches as unknown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c17eec0261
commit
c208973d75
@@ -5139,11 +5139,15 @@ function mapGenericIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | nu
|
||||
const obj = type.getObjectType();
|
||||
if (obj === undefined || !(obj.flags & ts.TypeFlags.TypeParameter)) return null;
|
||||
const bound = resolveTypeParam(obj);
|
||||
const idx = type.getIndexType();
|
||||
if (idx === undefined) return null;
|
||||
// `T[keyof T]` over a T bound to an unknown-valued record view
|
||||
// (`Record<string, unknown>` maps to the checked-dynamic value, see
|
||||
// mapRecordType): every member reads as unknown.
|
||||
if (bound?.kind === "dyn" && idx.isIndexType() && idx.getTarget() === obj) return DYN;
|
||||
if (!bound || bound.kind !== "record") return null;
|
||||
const shape = shapes.get(bound.shapeId);
|
||||
if (!shape || shape.tuple) return null;
|
||||
const idx = type.getIndexType();
|
||||
if (idx === undefined) return null;
|
||||
let covered: IrType[] | null = null;
|
||||
if (idx.isStringLiteralType()) {
|
||||
const f = shape.fields.find((x) => x.name === idx.value);
|
||||
@@ -5199,6 +5203,17 @@ function mapBoundKeyofNeverConditional(type: ts.Type, ctx: TypeMapperCtx): IrTyp
|
||||
const target = check.getTarget();
|
||||
if (!target || !(target.flags & ts.TypeFlags.TypeParameter)) return null;
|
||||
const bound = resolveTypeParam(target);
|
||||
// A T bound to the checked-dynamic value (`Record<string, unknown>`, an
|
||||
// unknown-valued record view) has no static key set to decide the
|
||||
// branch, but both branches of the lib's `keyof T extends never ?
|
||||
// unknown : T[keyof T]` read as unknown there: one answer either way.
|
||||
if (bound?.kind === "dyn") {
|
||||
const whenEmpty = type.getTrueType();
|
||||
const otherwise = type.getFalseType();
|
||||
const a = whenEmpty ? mapType(whenEmpty, ctx) : null;
|
||||
const b = otherwise ? mapType(otherwise, ctx) : null;
|
||||
return a && b && typeEquals(a, b) ? a : null;
|
||||
}
|
||||
if (bound?.kind !== "record") return null;
|
||||
const shape = shapes.get(bound.shapeId);
|
||||
if (!shape || shape.tuple) return null;
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6468-generic-unknown-record-entries.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Inside a generic body, `Object.entries(tags)` over `T extends
|
||||
// Record<string, unknown>` reads `[string, T[keyof T]]` elements. An
|
||||
// instance whose T is the unknown-valued record view reads every member
|
||||
// as unknown; a record instance reads the union of its member types.
|
||||
function upper(text: string): string {
|
||||
return text.toUpperCase();
|
||||
}
|
||||
|
||||
function scrub(text: string): string {
|
||||
return text.replace(/secret/g, "***");
|
||||
}
|
||||
|
||||
function describe<T extends Record<string, unknown>>(tags: T): string[] {
|
||||
return Object.entries(tags).map(
|
||||
([key, value]) => `${key}=${typeof value === "string" ? upper(value) : String(value)}`,
|
||||
);
|
||||
}
|
||||
|
||||
function redact<T extends Record<string, unknown>>(tags: T): T {
|
||||
return Object.fromEntries(
|
||||
Object.entries(tags).map(([key, value]) => [key, typeof value === "string" ? scrub(value) : value]),
|
||||
) as T;
|
||||
}
|
||||
|
||||
function flatten(value: unknown): Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : { value };
|
||||
}
|
||||
|
||||
console.log(describe(flatten({ b: 1, a: "x", c: true })));
|
||||
console.log(describe(flatten(5)));
|
||||
console.log(describe({ name: "n", size: 3 }));
|
||||
const cleaned = redact(flatten({ token: "secret", count: 2 }));
|
||||
console.log(cleaned, Object.keys(cleaned));
|
||||
Reference in New Issue
Block a user