Fix Record array index property reads (#400) (#418)

* Fix Record array index property reads (#400)

- Narrow optional record receivers before keyed reads.
- Cover present and missing rows with differential tests.

* Record TypeScript 7 baseline for Record array fixture

- Add the new corpus program to the preflight and module order baseline.

* Stabilize HTTP timeout differential fixtures

- Release server responses after timeout callbacks.
- Bound missing callbacks with fallback responses.
This commit is contained in:
Chris Tate
2026-09-25 01:34:04 -05:00
committed by GitHub
parent 858f0002a2
commit f70890b950
3 changed files with 46 additions and 3 deletions
@@ -9316,7 +9316,24 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
// construction), typed as the index value armed with undefined under
// noUncheckedIndexedAccess (mirroring lowerRecordKeyRead).
if (target.container === "recordOvf") {
let t: IrType = target.fieldType;
let obj = target.obj;
if (obj.type.kind === "union" && ts.isPropertyAccessExpression(blame)) {
const present = lowerer.stripUndefinedArm(obj.type);
if (present.kind === "record") {
obj = lowerer.runtimeOptionalPropertyReceiver(
blame.expression,
obj,
present,
target.field,
) ?? obj;
}
}
if (obj.type.kind !== "record") {
lowerer.unsupported("SC1090", blame, `reading '${target.field}' on a non-record receiver (narrow first)`);
}
const shape = lowerer.shapes.get(obj.type.shapeId);
if (!shape?.indexValue) lowerer.unsupported("SC1090", blame, `reading '${target.field}' on a record without an index signature`);
let t: IrType = shape.indexValue;
if (lowerer.program.getCompilerOptions().noUncheckedIndexedAccess) {
const armed = lowerer.withUndefinedArmOf(t);
if (!armed) lowerer.badType(blame, lowerer.typeOf(blame));
@@ -9324,8 +9341,8 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr
}
return {
kind: "recordKeyGet",
obj: target.obj,
shapeId: target.shapeId,
obj,
shapeId: obj.type.shapeId,
key: { kind: "strLit", value: target.field, type: STRING, loc },
overflowOnly: true,
type: t,
@@ -7581,6 +7581,12 @@
],
"diags": []
},
"<repo>/tests/corpus/917-record-array-index-dot.ts": {
"order": [
"<repo>/tests/corpus/917-record-array-index-dot.ts"
],
"diags": []
},
"<repo>/tests/corpus/950-modules-basic/main.ts": {
"order": [
"<repo>/tests/corpus/950-modules-basic/util.ts",
@@ -0,0 +1,20 @@
type Row = Record<string, string | number | null>;
function firstId(rows: Row[]): number {
return rows[0].id as number;
}
console.log(firstId([{ id: 1 }]));
let evaluations = 0;
function makeRows(): Row[] {
evaluations++;
return [{ id: evaluations }];
}
console.log(makeRows()[0].id, evaluations);
try {
firstId([]);
} catch (error) {
console.log(error instanceof TypeError, (error as Error).message);
}