diff --git a/packages/compiler/src/frontend/lowering/class-to-primitive.ts b/packages/compiler/src/frontend/lowering/class-to-primitive.ts index 516a62f3c..f3a6b23fa 100644 --- a/packages/compiler/src/frontend/lowering/class-to-primitive.ts +++ b/packages/compiler/src/frontend/lowering/class-to-primitive.ts @@ -270,8 +270,8 @@ function refuse(lowerer: Lowerer, node: ts.Node, info: ClassInfo, what: string): } /** ToString(ToPrimitive(value, hint)) of a union whose arms are program - * class instances and primitives (`Box | undefined`): each arm converts as - * it would alone. Null for other unions. */ + * class instances, URLs and primitives (`Box | undefined`, `string | URL`): + * each arm converts as it would alone. Null for other unions. */ export function classUnionToString( lowerer: Lowerer, value: IrExpr, @@ -281,9 +281,11 @@ export function classUnionToString( if (value.type.kind !== "union") return null; const unionId = value.type.unionId; const arms = lowerer.unions.get(unionId)?.arms; - if (!arms || !arms.some((arm) => arm.kind === "object")) return null; + if (!arms || !arms.some((arm) => arm.kind === "object" || arm.kind === "url")) return null; const convertible = (arm: IrType): boolean => { if (PRIMITIVE_KINDS.has(arm.kind)) return true; + // A URL arm converts through its href, like String(url). + if (arm.kind === "url") return true; if (arm.kind !== "object") return false; const info = lowerer.classes.get(arm.className); return info !== undefined && plainProgramClass(info); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6250-string-of-string-or-url.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6250-string-of-string-or-url.ts.json new file mode 100644 index 000000000..437bbfcb9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6250-string-of-string-or-url.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6250-string-of-string-or-url.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6250-string-of-string-or-url.ts b/tests/corpus/6250-string-of-string-or-url.ts new file mode 100644 index 000000000..3241d79e8 --- /dev/null +++ b/tests/corpus/6250-string-of-string-or-url.ts @@ -0,0 +1,22 @@ +// String(), template literals and concatenation over a `string | URL` +// union: the string arm passes through and the URL arm converts through its +// href, exactly as each would alone (an origin URL keeps its trailing +// slash, the origin string does not). +function key(origin: string | URL): string { + return String(origin); +} + +function label(origin: string | URL | undefined): string { + return `[${origin}]`; +} + +const url = new URL("http://127.0.0.1:8080"); +console.log(key("http://127.0.0.1:8080")); +console.log(key(url)); +console.log(key(new URL("https://example.com/a/b?x=1#h"))); +console.log(label(url), label("plain"), label(undefined)); +const both: (string | URL)[] = ["a", url]; +console.log(both.map((o) => "" + o).join(" ")); +const map = new Map(); +map.set(key(url.origin), 1); +console.log(map.get(String(url.origin)), map.get(String(url)));