Fix String conversion after union narrowing (#423)

- Extract primitive arms proven by TypeScript before string conversion.

- Cover guarded record values in the differential corpus.
This commit is contained in:
Chris Tate
2026-09-25 08:19:18 -05:00
committed by GitHub
parent 93af33085e
commit 58bd6749b5
3 changed files with 43 additions and 0 deletions
@@ -5393,6 +5393,24 @@ export function ensureString(lowerer: Lowerer, e: IrExpr, node: ts.Node): IrExpr
if (stringable) {
return { kind: "toString", operand: e, type: STRING, loc: e.loc };
}
// Runtime-optional locals can retain their stored union after the
// checker has narrowed this use to a primitive arm. Validate that
// arm before converting it; an unguarded object arm stays fenced.
const narrowed = lowerer.mapTypeOf(lowerer.typeOf(node));
if (
narrowed &&
(narrowed.kind === "string" || narrowed.kind === "f64" ||
narrowed.kind === "bool" || narrowed.kind === "bigint")
) {
const helper = lowerer.narrowedArmHelper(e.type.unionId, narrowed, e.loc);
if (helper) {
return ensureString(
lowerer,
{ kind: "call", callee: helper, args: [e], type: narrowed, loc: e.loc },
node,
);
}
}
lowerer.unsupported(
"SC1090",
node,
@@ -6913,6 +6913,12 @@
],
"diags": []
},
"<repo>/tests/corpus/3024-string-number-narrow-for-in.ts": {
"order": [
"<repo>/tests/corpus/3024-string-number-narrow-for-in.ts"
],
"diags": []
},
"<repo>/tests/corpus/303-break-continue.ts": {
"order": [
"<repo>/tests/corpus/303-break-continue.ts"
@@ -0,0 +1,19 @@
type RelayHeaders = Record<string, string | string[] | number | undefined>;
function relayHeaders(source: RelayHeaders, strip: Set<string>): Record<string, string> {
const headers: Record<string, string> = {};
for (const name in source) {
const value = source[name];
if (value === undefined || strip.has(name.toLowerCase())) continue;
if (typeof value === "string") {
headers[name] = value;
} else if (typeof value === "number") {
headers[name] = String(value);
} else {
headers[name] = value.join(", ");
}
}
return headers;
}
console.log(JSON.stringify(relayHeaders({ a: 1, b: ["x", "y"], c: "plain", d: undefined, E: 2 }, new Set(["e"]))));