Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
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.
Expand Down
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) {
// 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 := t.AsStructuredType()
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 (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:
Original file line number Diff line number Diff line change
@@ -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;
}];
Original file line number Diff line number Diff line change
@@ -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)

Loading