From 01449575df7084cca2df2ef339cdb0552972b681 Mon Sep 17 00:00:00 2001 From: Camille Barneaud <1693643+gadcam@users.noreply.github.com> Date: Sat, 10 Oct 2026 19:33:43 +0200 Subject: [PATCH 1/2] Dedupe identical object literal types before subtype reduction removeSubtypes compares every pair of constituents, which makes array literals with many records quadratic. Large JSON imports hit this hard: the colornerd.json file from #48364 (29K objects) never finishes, using gigabytes for the relation cache. Object literal types whose properties have the same names, flags and already-resolved types are identical, so the pairwise loop would keep only the first of them anyway. Remove them up front, keyed by a hash of their shape. Property types are only read when already resolved, so the new check never triggers type resolution. Co-Authored-By: Claude Opus 5.5 --- tsc/internal/checker/checker.go | 62 ++++++ ...IdenticalObjectLiteralsSubtypeReduction.js | 94 +++++++++ ...icalObjectLiteralsSubtypeReduction.symbols | 109 ++++++++++ ...nticalObjectLiteralsSubtypeReduction.types | 191 ++++++++++++++++++ ...IdenticalObjectLiteralsSubtypeReduction.ts | 33 +++ 5 files changed, 489 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.js create mode 100644 tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.types create mode 100644 tsc/testdata/tests/cases/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 345ea00ce1437..c7061eff224ed 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -26479,6 +26479,9 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type { if cached := c.subtypeReductionCache[key]; cached != nil { return cached } + if hasObjectTypes { + types = c.removeIdenticalObjectLiteralTypes(types) + } // We assume that redundant primitive types have already been removed from the types array and that there // are no any and unknown types in the array. Thus, the only possible supertypes for primitive types are empty // object types, and if none of those are present we can exclude primitive types from the subtype check. @@ -26560,6 +26563,65 @@ func (c *Checker) removeSubtypes(types []*Type, hasObjectTypes bool) []*Type { return types } +// removeIdenticalObjectLiteralTypes removes object literal types with the same shape as an earlier type in the list. +// The pairwise loop in removeSubtypes would keep only the first of them anyway, but at a quadratic cost for large +// arrays of records such as imported JSON files. +func (c *Checker) removeIdenticalObjectLiteralTypes(types []*Type) []*Type { + var seen map[CacheHashKey]struct{} + return core.Filter(types, func(t *Type) bool { + key, ok := c.getObjectLiteralShapeKey(t) + if !ok { + return true + } + if _, ok := seen[key]; ok { + return false + } + if seen == nil { + seen = make(map[CacheHashKey]struct{}) + } + seen[key] = struct{}{} + return true + }) +} + +func (c *Checker) getObjectLiteralShapeKey(t *Type) (CacheHashKey, bool) { + if t.flags&TypeFlagsObject == 0 || !isObjectLiteralType(t) { + return CacheHashKey{}, false + } + resolved := c.resolveStructuredTypeMembers(t) + if len(resolved.signatures) != 0 || len(resolved.indexInfos) != 0 { + return CacheHashKey{}, false + } + var b keyBuilder + b.writeUint32(uint32(t.objectFlags & (ObjectFlagsFreshLiteral | ObjectFlagsJSLiteral | ObjectFlagsContainsSpread | ObjectFlagsObjectLiteralPatternWithComputedProperties))) + b.writeInt(len(resolved.properties)) + for _, p := range resolved.properties { + propType := c.getResolvedTypeOfObjectLiteralProperty(p) + if propType == nil { + return CacheHashKey{}, false + } + b.writeInt(len(p.Name())) + b.writeString(p.Name()) + b.writeUint32(uint32(p.Flags())) + b.writeUint32(uint32(p.CheckFlags())) + b.writeType(propType) + } + return b.hash(), true +} + +// getResolvedTypeOfObjectLiteralProperty returns the type of a plain object literal property only if it is already +// resolved, so that computing a shape key never triggers type resolution (see #46981). +func (c *Checker) getResolvedTypeOfObjectLiteralProperty(p *ast.Symbol) *Type { + if p.Flags()&(ast.SymbolFlagsAccessor|ast.SymbolFlagsMethod) != 0 || + p.CheckFlags()&(ast.CheckFlagsDeferredType|ast.CheckFlagsInstantiated|ast.CheckFlagsMapped|ast.CheckFlagsReverseMapped) != 0 { + return nil + } + if links := c.valueSymbolLinks.TryGet(p); links != nil { + return links.resolvedType + } + return nil +} + func (c *Checker) intersectTypes(type1 *Type, type2 *Type) *Type { switch { case type1 == nil: diff --git a/tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.js b/tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.js new file mode 100644 index 0000000000000..01cc1595aa6ba --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.js @@ -0,0 +1,94 @@ +//// [tests/cases/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.ts] //// + +//// [data.json] +[ + { "code": "#f3dc00", "label": "526-G", "name": "Saharagelb" }, + { "code": "#f6ca00", "label": "527-G", "name": "Buttergelb" }, + { "name": "Primelgelb", "label": "504-G", "code": "#ffbe00" }, + { "name": "Rapsgelb", "label": 505, "code": "#ffb000" }, + { "code": "#ff9a00", "label": "506-G", "name": "Maisgelb" }, + { "code": "#ff8200", "label": "507-G", "name": null }, + { "code": "#ff6c00", "label": "508-G", "name": null, "extra": true }, + { "name": "Orangegelb", "label": 509, "code": "#ff5a00" } +] + +//// [main.ts] +// https://github.com/microsoft/TypeScript/issues/48364 +import data from "./data.json"; + +export const fromJson = data; + +declare const o: { x: number }; +declare const n: number; + +export const differentOrder = [{ a: 1, b: "" }, { b: "", a: 1 }, { a: 2, b: "x" }]; +export const optionalProperties = [{ a: 1, b: n > 0 ? "" : undefined }, { a: 2, b: n > 0 ? "x" : undefined }]; +export const spreads = [{ ...o }, { ...o }, { ...o, y: "" }]; +export const methods = [{ m() { return 1; } }, { m() { return 2; } }, { m() { return ""; } }]; +export const accessors = [{ get a() { return 1; } }, { get a() { return 2; } }, { a: 3 }]; +export const constAssertion = [{ a: 1 }, { a: 1 }, { a: 2 }] as const; + + +//// [main.js] +// https://github.com/microsoft/TypeScript/issues/48364 +import data from "./data.json"; +export const fromJson = data; +export const differentOrder = [{ a: 1, b: "" }, { b: "", a: 1 }, { a: 2, b: "x" }]; +export const optionalProperties = [{ a: 1, b: n > 0 ? "" : undefined }, { a: 2, b: n > 0 ? "x" : undefined }]; +export const spreads = [{ ...o }, { ...o }, { ...o, y: "" }]; +export const methods = [{ m() { return 1; } }, { m() { return 2; } }, { m() { return ""; } }]; +export const accessors = [{ get a() { return 1; } }, { get a() { return 2; } }, { a: 3 }]; +export const constAssertion = [{ a: 1 }, { a: 1 }, { a: 2 }]; + + +//// [main.d.ts] +export declare const fromJson: ({ + code: string; + label: string; + name: string; + extra?: undefined; +} | { + name: string; + label: number; + code: string; + extra?: undefined; +} | { + code: string; + label: string; + name: null; + extra?: undefined; +} | { + code: string; + label: string; + name: null; + extra: boolean; +})[]; +export declare const differentOrder: { + a: number; + b: string; +}[]; +export declare const optionalProperties: { + a: number; + b: string | undefined; +}[]; +export declare const spreads: ({ + x: number; +} | { + x: number; + y: string; +})[]; +export declare const methods: ({ + m(): number; +} | { + m(): string; +})[]; +export declare const accessors: { + readonly a: number; +}[]; +export declare const constAssertion: readonly [{ + readonly a: 1; +}, { + readonly a: 1; +}, { + readonly a: 2; +}]; diff --git a/tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.symbols b/tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.symbols new file mode 100644 index 0000000000000..65cd3300998c7 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.symbols @@ -0,0 +1,109 @@ +//// [tests/cases/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.ts] //// + +=== /data.json === +[ + { "code": "#f3dc00", "label": "526-G", "name": "Saharagelb" }, +>"code" : Symbol("code", Decl(data.json, 1, 5)) +>"label" : Symbol("label", Decl(data.json, 1, 24)) +>"name" : Symbol("name", Decl(data.json, 1, 42)) + + { "code": "#f6ca00", "label": "527-G", "name": "Buttergelb" }, +>"code" : Symbol("code", Decl(data.json, 2, 5)) +>"label" : Symbol("label", Decl(data.json, 2, 24)) +>"name" : Symbol("name", Decl(data.json, 2, 42)) + + { "name": "Primelgelb", "label": "504-G", "code": "#ffbe00" }, +>"name" : Symbol("name", Decl(data.json, 3, 5)) +>"label" : Symbol("label", Decl(data.json, 3, 27)) +>"code" : Symbol("code", Decl(data.json, 3, 45)) + + { "name": "Rapsgelb", "label": 505, "code": "#ffb000" }, +>"name" : Symbol("name", Decl(data.json, 4, 5)) +>"label" : Symbol("label", Decl(data.json, 4, 25)) +>"code" : Symbol("code", Decl(data.json, 4, 39)) + + { "code": "#ff9a00", "label": "506-G", "name": "Maisgelb" }, +>"code" : Symbol("code", Decl(data.json, 5, 5)) +>"label" : Symbol("label", Decl(data.json, 5, 24)) +>"name" : Symbol("name", Decl(data.json, 5, 42)) + + { "code": "#ff8200", "label": "507-G", "name": null }, +>"code" : Symbol("code", Decl(data.json, 6, 5)) +>"label" : Symbol("label", Decl(data.json, 6, 24)) +>"name" : Symbol("name", Decl(data.json, 6, 42)) + + { "code": "#ff6c00", "label": "508-G", "name": null, "extra": true }, +>"code" : Symbol("code", Decl(data.json, 7, 5)) +>"label" : Symbol("label", Decl(data.json, 7, 24)) +>"name" : Symbol("name", Decl(data.json, 7, 42)) +>"extra" : Symbol("extra", Decl(data.json, 7, 56)) + + { "name": "Orangegelb", "label": 509, "code": "#ff5a00" } +>"name" : Symbol("name", Decl(data.json, 8, 5)) +>"label" : Symbol("label", Decl(data.json, 8, 27)) +>"code" : Symbol("code", Decl(data.json, 8, 41)) + +] + +=== /main.ts === +// https://github.com/microsoft/TypeScript/issues/48364 +import data from "./data.json"; +>data : Symbol(data, Decl(main.ts, 1, 6)) + +export const fromJson = data; +>fromJson : Symbol(fromJson, Decl(main.ts, 3, 12)) +>data : Symbol(data, Decl(main.ts, 1, 6)) + +declare const o: { x: number }; +>o : Symbol(o, Decl(main.ts, 5, 13)) +>x : Symbol(x, Decl(main.ts, 5, 18)) + +declare const n: number; +>n : Symbol(n, Decl(main.ts, 6, 13)) + +export const differentOrder = [{ a: 1, b: "" }, { b: "", a: 1 }, { a: 2, b: "x" }]; +>differentOrder : Symbol(differentOrder, Decl(main.ts, 8, 12)) +>a : Symbol(a, Decl(main.ts, 8, 32)) +>b : Symbol(b, Decl(main.ts, 8, 38)) +>b : Symbol(b, Decl(main.ts, 8, 49)) +>a : Symbol(a, Decl(main.ts, 8, 56)) +>a : Symbol(a, Decl(main.ts, 8, 66)) +>b : Symbol(b, Decl(main.ts, 8, 72)) + +export const optionalProperties = [{ a: 1, b: n > 0 ? "" : undefined }, { a: 2, b: n > 0 ? "x" : undefined }]; +>optionalProperties : Symbol(optionalProperties, Decl(main.ts, 9, 12)) +>a : Symbol(a, Decl(main.ts, 9, 36)) +>b : Symbol(b, Decl(main.ts, 9, 42)) +>n : Symbol(n, Decl(main.ts, 6, 13)) +>undefined : Symbol(undefined) +>a : Symbol(a, Decl(main.ts, 9, 73)) +>b : Symbol(b, Decl(main.ts, 9, 79)) +>n : Symbol(n, Decl(main.ts, 6, 13)) +>undefined : Symbol(undefined) + +export const spreads = [{ ...o }, { ...o }, { ...o, y: "" }]; +>spreads : Symbol(spreads, Decl(main.ts, 10, 12)) +>o : Symbol(o, Decl(main.ts, 5, 13)) +>o : Symbol(o, Decl(main.ts, 5, 13)) +>o : Symbol(o, Decl(main.ts, 5, 13)) +>y : Symbol(y, Decl(main.ts, 10, 51)) + +export const methods = [{ m() { return 1; } }, { m() { return 2; } }, { m() { return ""; } }]; +>methods : Symbol(methods, Decl(main.ts, 11, 12)) +>m : Symbol(m, Decl(main.ts, 11, 25)) +>m : Symbol(m, Decl(main.ts, 11, 48)) +>m : Symbol(m, Decl(main.ts, 11, 71)) + +export const accessors = [{ get a() { return 1; } }, { get a() { return 2; } }, { a: 3 }]; +>accessors : Symbol(accessors, Decl(main.ts, 12, 12)) +>a : Symbol(a, Decl(main.ts, 12, 27)) +>a : Symbol(a, Decl(main.ts, 12, 54)) +>a : Symbol(a, Decl(main.ts, 12, 81)) + +export const constAssertion = [{ a: 1 }, { a: 1 }, { a: 2 }] as const; +>constAssertion : Symbol(constAssertion, Decl(main.ts, 13, 12)) +>a : Symbol(a, Decl(main.ts, 13, 32)) +>a : Symbol(a, Decl(main.ts, 13, 42)) +>a : Symbol(a, Decl(main.ts, 13, 52)) +>const : Symbol(const) + diff --git a/tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.types b/tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.types new file mode 100644 index 0000000000000..fc01238fb4e8e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.types @@ -0,0 +1,191 @@ +//// [tests/cases/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.ts] //// + +=== /data.json === +[ +>[ { "code": "#f3dc00", "label": "526-G", "name": "Saharagelb" }, { "code": "#f6ca00", "label": "527-G", "name": "Buttergelb" }, { "name": "Primelgelb", "label": "504-G", "code": "#ffbe00" }, { "name": "Rapsgelb", "label": 505, "code": "#ffb000" }, { "code": "#ff9a00", "label": "506-G", "name": "Maisgelb" }, { "code": "#ff8200", "label": "507-G", "name": null }, { "code": "#ff6c00", "label": "508-G", "name": null, "extra": true }, { "name": "Orangegelb", "label": 509, "code": "#ff5a00" }] : ({ code: string; label: string; name: string; } | { name: string; label: number; code: string; } | { code: string; label: string; name: null; } | { code: string; label: string; name: null; extra: boolean; })[] + + { "code": "#f3dc00", "label": "526-G", "name": "Saharagelb" }, +>{ "code": "#f3dc00", "label": "526-G", "name": "Saharagelb" } : { code: string; label: string; name: string; } +>"code" : string +>"#f3dc00" : "#f3dc00" +>"label" : string +>"526-G" : "526-G" +>"name" : string +>"Saharagelb" : "Saharagelb" + + { "code": "#f6ca00", "label": "527-G", "name": "Buttergelb" }, +>{ "code": "#f6ca00", "label": "527-G", "name": "Buttergelb" } : { code: string; label: string; name: string; } +>"code" : string +>"#f6ca00" : "#f6ca00" +>"label" : string +>"527-G" : "527-G" +>"name" : string +>"Buttergelb" : "Buttergelb" + + { "name": "Primelgelb", "label": "504-G", "code": "#ffbe00" }, +>{ "name": "Primelgelb", "label": "504-G", "code": "#ffbe00" } : { name: string; label: string; code: string; } +>"name" : string +>"Primelgelb" : "Primelgelb" +>"label" : string +>"504-G" : "504-G" +>"code" : string +>"#ffbe00" : "#ffbe00" + + { "name": "Rapsgelb", "label": 505, "code": "#ffb000" }, +>{ "name": "Rapsgelb", "label": 505, "code": "#ffb000" } : { name: string; label: number; code: string; } +>"name" : string +>"Rapsgelb" : "Rapsgelb" +>"label" : number +>505 : 505 +>"code" : string +>"#ffb000" : "#ffb000" + + { "code": "#ff9a00", "label": "506-G", "name": "Maisgelb" }, +>{ "code": "#ff9a00", "label": "506-G", "name": "Maisgelb" } : { code: string; label: string; name: string; } +>"code" : string +>"#ff9a00" : "#ff9a00" +>"label" : string +>"506-G" : "506-G" +>"name" : string +>"Maisgelb" : "Maisgelb" + + { "code": "#ff8200", "label": "507-G", "name": null }, +>{ "code": "#ff8200", "label": "507-G", "name": null } : { code: string; label: string; name: null; } +>"code" : string +>"#ff8200" : "#ff8200" +>"label" : string +>"507-G" : "507-G" +>"name" : null + + { "code": "#ff6c00", "label": "508-G", "name": null, "extra": true }, +>{ "code": "#ff6c00", "label": "508-G", "name": null, "extra": true } : { code: string; label: string; name: null; extra: boolean; } +>"code" : string +>"#ff6c00" : "#ff6c00" +>"label" : string +>"508-G" : "508-G" +>"name" : null +>"extra" : boolean +>true : true + + { "name": "Orangegelb", "label": 509, "code": "#ff5a00" } +>{ "name": "Orangegelb", "label": 509, "code": "#ff5a00" } : { name: string; label: number; code: string; } +>"name" : string +>"Orangegelb" : "Orangegelb" +>"label" : number +>509 : 509 +>"code" : string +>"#ff5a00" : "#ff5a00" + +] + +=== /main.ts === +// https://github.com/microsoft/TypeScript/issues/48364 +import data from "./data.json"; +>data : ({ code: string; label: string; name: string; extra?: undefined; } | { name: string; label: number; code: string; extra?: undefined; } | { code: string; label: string; name: null; extra?: undefined; } | { code: string; label: string; name: null; extra: boolean; })[] + +export const fromJson = data; +>fromJson : ({ code: string; label: string; name: string; extra?: undefined; } | { name: string; label: number; code: string; extra?: undefined; } | { code: string; label: string; name: null; extra?: undefined; } | { code: string; label: string; name: null; extra: boolean; })[] +>data : ({ code: string; label: string; name: string; extra?: undefined; } | { name: string; label: number; code: string; extra?: undefined; } | { code: string; label: string; name: null; extra?: undefined; } | { code: string; label: string; name: null; extra: boolean; })[] + +declare const o: { x: number }; +>o : { x: number; } +>x : number + +declare const n: number; +>n : number + +export const differentOrder = [{ a: 1, b: "" }, { b: "", a: 1 }, { a: 2, b: "x" }]; +>differentOrder : { a: number; b: string; }[] +>[{ a: 1, b: "" }, { b: "", a: 1 }, { a: 2, b: "x" }] : { a: number; b: string; }[] +>{ a: 1, b: "" } : { a: number; b: string; } +>a : number +>1 : 1 +>b : string +>"" : "" +>{ b: "", a: 1 } : { b: string; a: number; } +>b : string +>"" : "" +>a : number +>1 : 1 +>{ a: 2, b: "x" } : { a: number; b: string; } +>a : number +>2 : 2 +>b : string +>"x" : "x" + +export const optionalProperties = [{ a: 1, b: n > 0 ? "" : undefined }, { a: 2, b: n > 0 ? "x" : undefined }]; +>optionalProperties : { a: number; b: string | undefined; }[] +>[{ a: 1, b: n > 0 ? "" : undefined }, { a: 2, b: n > 0 ? "x" : undefined }] : { a: number; b: string | undefined; }[] +>{ a: 1, b: n > 0 ? "" : undefined } : { a: number; b: string | undefined; } +>a : number +>1 : 1 +>b : string | undefined +>n > 0 ? "" : undefined : "" | undefined +>n > 0 : boolean +>n : number +>0 : 0 +>"" : "" +>undefined : undefined +>{ a: 2, b: n > 0 ? "x" : undefined } : { a: number; b: string | undefined; } +>a : number +>2 : 2 +>b : string | undefined +>n > 0 ? "x" : undefined : "x" | undefined +>n > 0 : boolean +>n : number +>0 : 0 +>"x" : "x" +>undefined : undefined + +export const spreads = [{ ...o }, { ...o }, { ...o, y: "" }]; +>spreads : ({ x: number; } | { x: number; y: string; })[] +>[{ ...o }, { ...o }, { ...o, y: "" }] : ({ x: number; } | { x: number; y: string; })[] +>{ ...o } : { x: number; } +>o : { x: number; } +>{ ...o } : { x: number; } +>o : { x: number; } +>{ ...o, y: "" } : { x: number; y: string; } +>o : { x: number; } +>y : string +>"" : "" + +export const methods = [{ m() { return 1; } }, { m() { return 2; } }, { m() { return ""; } }]; +>methods : ({ m(): number; } | { m(): string; })[] +>[{ m() { return 1; } }, { m() { return 2; } }, { m() { return ""; } }] : ({ m(): number; } | { m(): string; })[] +>{ m() { return 1; } } : { m(): number; } +>m : () => number +>1 : 1 +>{ m() { return 2; } } : { m(): number; } +>m : () => number +>2 : 2 +>{ m() { return ""; } } : { m(): string; } +>m : () => string +>"" : "" + +export const accessors = [{ get a() { return 1; } }, { get a() { return 2; } }, { a: 3 }]; +>accessors : { readonly a: number; }[] +>[{ get a() { return 1; } }, { get a() { return 2; } }, { a: 3 }] : { readonly a: number; }[] +>{ get a() { return 1; } } : { readonly a: number; } +>a : number +>1 : 1 +>{ get a() { return 2; } } : { readonly a: number; } +>a : number +>2 : 2 +>{ a: 3 } : { a: number; } +>a : number +>3 : 3 + +export const constAssertion = [{ a: 1 }, { a: 1 }, { a: 2 }] as const; +>constAssertion : readonly [{ readonly a: 1; }, { readonly a: 1; }, { readonly a: 2; }] +>[{ a: 1 }, { a: 1 }, { a: 2 }] as const : readonly [{ readonly a: 1; }, { readonly a: 1; }, { readonly a: 2; }] +>[{ a: 1 }, { a: 1 }, { a: 2 }] : readonly [{ readonly a: 1; }, { readonly a: 1; }, { readonly a: 2; }] +>{ a: 1 } : { readonly a: 1; } +>a : 1 +>1 : 1 +>{ a: 1 } : { readonly a: 1; } +>a : 1 +>1 : 1 +>{ a: 2 } : { readonly a: 2; } +>a : 2 +>2 : 2 + diff --git a/tsc/testdata/tests/cases/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.ts b/tsc/testdata/tests/cases/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.ts new file mode 100644 index 0000000000000..c938fe7f13ad9 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/arrayLiteralIdenticalObjectLiteralsSubtypeReduction.ts @@ -0,0 +1,33 @@ +// @strict: true +// @declaration: true +// @module: esnext +// @moduleResolution: bundler +// @resolveJsonModule: true + +// @Filename: /data.json +[ + { "code": "#f3dc00", "label": "526-G", "name": "Saharagelb" }, + { "code": "#f6ca00", "label": "527-G", "name": "Buttergelb" }, + { "name": "Primelgelb", "label": "504-G", "code": "#ffbe00" }, + { "name": "Rapsgelb", "label": 505, "code": "#ffb000" }, + { "code": "#ff9a00", "label": "506-G", "name": "Maisgelb" }, + { "code": "#ff8200", "label": "507-G", "name": null }, + { "code": "#ff6c00", "label": "508-G", "name": null, "extra": true }, + { "name": "Orangegelb", "label": 509, "code": "#ff5a00" } +] + +// @Filename: /main.ts +// https://github.com/microsoft/TypeScript/issues/48364 +import data from "./data.json"; + +export const fromJson = data; + +declare const o: { x: number }; +declare const n: number; + +export const differentOrder = [{ a: 1, b: "" }, { b: "", a: 1 }, { a: 2, b: "x" }]; +export const optionalProperties = [{ a: 1, b: n > 0 ? "" : undefined }, { a: 2, b: n > 0 ? "x" : undefined }]; +export const spreads = [{ ...o }, { ...o }, { ...o, y: "" }]; +export const methods = [{ m() { return 1; } }, { m() { return 2; } }, { m() { return ""; } }]; +export const accessors = [{ get a() { return 1; } }, { get a() { return 2; } }, { a: 3 }]; +export const constAssertion = [{ a: 1 }, { a: 1 }, { a: 2 }] as const; From 822cd37dcddbb5c0aaa2bb20fa72883f38c76d60 Mon Sep 17 00:00:00 2001 From: Camille Barneaud <1693643+gadcam@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:14:15 +0200 Subject: [PATCH 2/2] Only use already resolved members for the shape key; add regression test Co-Authored-By: Claude Opus 5.5 --- tsc/internal/checker/checker.go | 7 ++- ...yLiteralManyObjectLiteralsNotTooComplex.ts | 63 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tsc/testdata/tests/cases/compiler/arrayLiteralManyObjectLiteralsNotTooComplex.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index c7061eff224ed..b05ee40bbdcb1 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -26585,10 +26585,11 @@ func (c *Checker) removeIdenticalObjectLiteralTypes(types []*Type) []*Type { } func (c *Checker) getObjectLiteralShapeKey(t *Type) (CacheHashKey, bool) { - if t.flags&TypeFlagsObject == 0 || !isObjectLiteralType(t) { + // Only use members that are already resolved, so that computing the key never triggers resolution. + if t.flags&TypeFlagsObject == 0 || !isObjectLiteralType(t) || t.objectFlags&ObjectFlagsMembersResolved == 0 { return CacheHashKey{}, false } - resolved := c.resolveStructuredTypeMembers(t) + resolved := t.AsStructuredType() if len(resolved.signatures) != 0 || len(resolved.indexInfos) != 0 { return CacheHashKey{}, false } @@ -26610,7 +26611,7 @@ func (c *Checker) getObjectLiteralShapeKey(t *Type) (CacheHashKey, bool) { } // getResolvedTypeOfObjectLiteralProperty returns the type of a plain object literal property only if it is already -// resolved, so that computing a shape key never triggers type resolution (see #46981). +// resolved (see #46981). func (c *Checker) getResolvedTypeOfObjectLiteralProperty(p *ast.Symbol) *Type { if p.Flags()&(ast.SymbolFlagsAccessor|ast.SymbolFlagsMethod) != 0 || p.CheckFlags()&(ast.CheckFlagsDeferredType|ast.CheckFlagsInstantiated|ast.CheckFlagsMapped|ast.CheckFlagsReverseMapped) != 0 { diff --git a/tsc/testdata/tests/cases/compiler/arrayLiteralManyObjectLiteralsNotTooComplex.ts b/tsc/testdata/tests/cases/compiler/arrayLiteralManyObjectLiteralsNotTooComplex.ts new file mode 100644 index 0000000000000..28774143669e5 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/arrayLiteralManyObjectLiteralsNotTooComplex.ts @@ -0,0 +1,63 @@ +// @strict: true +// @noEmit: true +// @noTypesAndSymbols: true + +// Many records of one shape followed by records of another shape used to exceed the subtype reduction limit. +// https://github.com/microsoft/TypeScript/issues/48364 +export const records = [ + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, { a: "" }, + { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, + { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, + { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, + { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, + { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, { a: 0 }, +];