mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-08 08:44:37 +08:00
Dispatch a keyof read whose member is unknown per declared key
`settings[key]` over a settings record with an `unknown` member (`type: unknown`) refuses even though every field reads through a checked-dynamic slot: the checker's answer collapses to `unknown` because the union of the field types absorbs into it, so there is no typed join for the keyed read to dispatch into. Recognize that shape — the read's type is `unknown`, the shape has no index signature, and every declared field's value is checked-dynamic representable — and dispatch per declared key answering a checked- dynamic read. The key type is the record's own `keyof`, so the answer is always present. Corpus 8523 pins the read differentially. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3328ed2aa7
commit
e198a9eec2
@@ -1534,6 +1534,20 @@ export function lowerRecordKeyRead(
|
||||
recordKeyResultOk(lowerer, shape, DYN)
|
||||
)
|
||||
declared = DYN;
|
||||
// A `keyof` read over a record that holds an 'unknown' member: the
|
||||
// checker's answer collapses to 'unknown' (the union of every field
|
||||
// type absorbs into it), so there is no typed join to dispatch into.
|
||||
// Each declared field still reads through its checked-dynamic slot —
|
||||
// the very value the checker's 'unknown' can hold — so dispatch per
|
||||
// declared key and answer a checked-dynamic read. The key type is the
|
||||
// record's own `keyof`, so the answer is present whenever the read is.
|
||||
if (
|
||||
!declared &&
|
||||
(lowerer.typeOf(expr).flags & ts.TypeFlags.Unknown) !== 0 &&
|
||||
!shape.indexValue &&
|
||||
recordKeyResultOk(lowerer, shape, DYN)
|
||||
)
|
||||
declared = DYN;
|
||||
if (!declared) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8523-settings-keyof-dynamic-read.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// A `keyof` read over a record that holds an `unknown` member: the
|
||||
// checker's answer collapses to `unknown` (the union of every field type
|
||||
// absorbs into it), so there is no typed join. The read dispatches per
|
||||
// declared key through the checked-dynamic slot instead.
|
||||
interface Retry {
|
||||
baseDelayMs?: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
interface Settings {
|
||||
$schema?: string;
|
||||
enabledModels?: string[];
|
||||
branchSummary: unknown;
|
||||
fullscreenWheelScrollLines?: number | string;
|
||||
modelThinkingLevels?: { [key: string]: string };
|
||||
retry?: Retry;
|
||||
outputPad?: number;
|
||||
}
|
||||
function read(settings: Settings, key: keyof Settings): unknown {
|
||||
return settings[key];
|
||||
}
|
||||
const settings: Settings = {
|
||||
branchSummary: { note: "x", count: 2 },
|
||||
outputPad: 4,
|
||||
enabledModels: ["m"],
|
||||
fullscreenWheelScrollLines: "wide",
|
||||
modelThinkingLevels: { high: "big" },
|
||||
retry: { enabled: true },
|
||||
};
|
||||
const keys: (keyof Settings)[] = [
|
||||
"outputPad",
|
||||
"enabledModels",
|
||||
"branchSummary",
|
||||
"retry",
|
||||
"$schema",
|
||||
"fullscreenWheelScrollLines",
|
||||
];
|
||||
for (const key of keys) {
|
||||
const value = read(settings, key);
|
||||
console.log(key, typeof value, value === undefined ? "absent" : JSON.stringify(value));
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 1300, "san": 2700 }
|
||||
Reference in New Issue
Block a user