diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4de0c7a2a6c37..0d3ac082f2ad8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2583,10 +2583,8 @@ namespace ts { } function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - return createMappedTypeNodeFromType(type); - } + if (isGenericMappedType(type)) { + return createMappedTypeNodeFromType(type); } const resolved = resolveStructuredTypeMembers(type); @@ -3489,11 +3487,9 @@ namespace ts { } function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - writeMappedType(type); - return; - } + if (isGenericMappedType(type)) { + writeMappedType(type); + return; } const resolved = resolveStructuredTypeMembers(type); @@ -5792,8 +5788,7 @@ namespace ts { } function isGenericMappedType(type: Type) { - return getObjectFlags(type) & ObjectFlags.Mapped && - maybeTypeOfKind(getConstraintTypeFromMappedType(type), TypeFlags.TypeVariable | TypeFlags.Index); + return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(type)); } function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { @@ -5905,6 +5900,10 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { + const transformed = getTransformedIndexedAccessType(type); + if (transformed) { + return transformed; + } const baseObjectType = getBaseConstraintOfType(type.objectType); const baseIndexType = getBaseConstraintOfType(type.indexType); return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined; @@ -5976,11 +5975,18 @@ namespace ts { return stringType; } if (t.flags & TypeFlags.IndexedAccess) { + const transformed = getTransformedIndexedAccessType(t); + if (transformed) { + return getBaseConstraint(transformed); + } const baseObjectType = getBaseConstraint((t).objectType); const baseIndexType = getBaseConstraint((t).indexType); const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined; return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined; } + if (isGenericMappedType(t)) { + return emptyObjectType; + } return t; } } @@ -7602,26 +7608,73 @@ namespace ts { return instantiateType(getTemplateTypeFromMappedType(type), templateMapper); } - function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { - // If the index type is generic, if the object type is generic and doesn't originate in an expression, - // or if the object type is a mapped type with a generic constraint, we are performing a higher-order - // index access where we cannot meaningfully access the properties of the object type. Note that for a - // generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to - // preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved - // eagerly using the constraint type of 'this' at the given location. - if (maybeTypeOfKind(indexType, TypeFlags.TypeVariable | TypeFlags.Index) || - maybeTypeOfKind(objectType, TypeFlags.TypeVariable) && !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) || - isGenericMappedType(objectType)) { + function isGenericObjectType(type: Type): boolean { + return type.flags & TypeFlags.TypeVariable ? true : + getObjectFlags(type) & ObjectFlags.Mapped ? isGenericIndexType(getConstraintTypeFromMappedType(type)) : + type.flags & TypeFlags.UnionOrIntersection ? forEach((type).types, isGenericObjectType) : + false; + } + + function isGenericIndexType(type: Type): boolean { + return type.flags & (TypeFlags.TypeVariable | TypeFlags.Index) ? true : + type.flags & TypeFlags.UnionOrIntersection ? forEach((type).types, isGenericIndexType) : + false; + } + + // Return true if the given type is a non-generic object type with a string index signature and no + // other members. + function isStringIndexOnlyType(type: Type) { + if (type.flags & TypeFlags.Object && !isGenericMappedType(type)) { + const t = resolveStructuredTypeMembers(type); + return t.properties.length === 0 && + t.callSignatures.length === 0 && t.constructSignatures.length === 0 && + t.stringIndexInfo && !t.numberIndexInfo; + } + return false; + } + + // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or + // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a + // transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed + // access types with default property values as expressed by D. + function getTransformedIndexedAccessType(type: IndexedAccessType): Type { + const objectType = type.objectType; + if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isStringIndexOnlyType)) { + const regularTypes: Type[] = []; + const stringIndexTypes: Type[] = []; + for (const t of (objectType).types) { + if (isStringIndexOnlyType(t)) { + stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String)); + } + else { + regularTypes.push(t); + } + } + return getUnionType([ + getIndexedAccessType(getIntersectionType(regularTypes), type.indexType), + getIntersectionType(stringIndexTypes) + ]); + } + return undefined; + } + + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type { + // If the object type is a mapped type { [P in K]: E }, where K is generic, we instantiate E using a mapper + // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we + // construct the type Box. + if (isGenericMappedType(objectType)) { + return getIndexedAccessForMappedType(objectType, indexType, accessNode); + } + // Otherwise, if the index type is generic, or if the object type is generic and doesn't originate in an + // expression, we are performing a higher-order index access where we cannot meaningfully access the properties + // of the object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates + // in an expression. This is to preserve backwards compatibility. For example, an element access 'this["foo"]' + // has always been resolved eagerly using the constraint type of 'this' at the given location. + if (isGenericIndexType(indexType) || !(accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression) && isGenericObjectType(objectType)) { if (objectType.flags & TypeFlags.Any) { return objectType; } - // If the object type is a mapped type { [P in K]: E }, we instantiate E using a mapper that substitutes - // the index type for P. For example, for an index access { [P in K]: Box }[X], we construct the - // type Box. - if (isGenericMappedType(objectType)) { - return getIndexedAccessForMappedType(objectType, indexType, accessNode); - } - // Otherwise we defer the operation by creating an indexed access type. + // Defer the operation by creating an indexed access type. const id = objectType.id + "," + indexType.id; let type = indexedAccessTypes.get(id); if (!type) { @@ -18657,6 +18710,8 @@ namespace ts { } function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); } diff --git a/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt b/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt index e207468122bc0..01c5bd6b2e2c0 100644 --- a/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt +++ b/tests/baselines/reference/anyIndexedAccessArrayNoException.errors.txt @@ -1,8 +1,11 @@ +tests/cases/compiler/anyIndexedAccessArrayNoException.ts(1,12): error TS1122: A tuple type element list cannot be empty. tests/cases/compiler/anyIndexedAccessArrayNoException.ts(1,12): error TS2538: Type '[]' cannot be used as an index type. -==== tests/cases/compiler/anyIndexedAccessArrayNoException.ts (1 errors) ==== +==== tests/cases/compiler/anyIndexedAccessArrayNoException.ts (2 errors) ==== var x: any[[]]; ~~ +!!! error TS1122: A tuple type element list cannot be empty. + ~~ !!! error TS2538: Type '[]' cannot be used as an index type. \ No newline at end of file diff --git a/tests/baselines/reference/deferredLookupTypeResolution.js b/tests/baselines/reference/deferredLookupTypeResolution.js new file mode 100644 index 0000000000000..5f8edd63e574b --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution.js @@ -0,0 +1,64 @@ +//// [deferredLookupTypeResolution.ts] +// Repro from #17456 + +type StringContains = ( + { [K in S]: 'true' } & + { [key: string]: 'false' } + )[L] + +type ObjectHasKey = StringContains + +type First = ObjectHasKey; // Should be deferred + +type T1 = ObjectHasKey<{ a: string }, 'a'>; // 'true' +type T2 = ObjectHasKey<{ a: string }, 'b'>; // 'false' + +// Verify that mapped type isn't eagerly resolved in type-to-string operation + +declare function f1(a: A, b: B): { [P in A | B]: any }; + +function f2(a: A) { + return f1(a, 'x'); +} + +function f3(x: 'a' | 'b') { + return f2(x); +} + + +//// [deferredLookupTypeResolution.js] +"use strict"; +// Repro from #17456 +function f2(a) { + return f1(a, 'x'); +} +function f3(x) { + return f2(x); +} + + +//// [deferredLookupTypeResolution.d.ts] +declare type StringContains = ({ + [K in S]: 'true'; +} & { + [key: string]: 'false'; +})[L]; +declare type ObjectHasKey = StringContains; +declare type First = ObjectHasKey; +declare type T1 = ObjectHasKey<{ + a: string; +}, 'a'>; +declare type T2 = ObjectHasKey<{ + a: string; +}, 'b'>; +declare function f1(a: A, b: B): { + [P in A | B]: any; +}; +declare function f2(a: A): { + [P in A | "x"]: any; +}; +declare function f3(x: 'a' | 'b'): { + a: any; + b: any; + x: any; +}; diff --git a/tests/baselines/reference/deferredLookupTypeResolution.symbols b/tests/baselines/reference/deferredLookupTypeResolution.symbols new file mode 100644 index 0000000000000..022dc3cc2f41e --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution.symbols @@ -0,0 +1,76 @@ +=== tests/cases/compiler/deferredLookupTypeResolution.ts === +// Repro from #17456 + +type StringContains = ( +>StringContains : Symbol(StringContains, Decl(deferredLookupTypeResolution.ts, 0, 0)) +>S : Symbol(S, Decl(deferredLookupTypeResolution.ts, 2, 20)) +>L : Symbol(L, Decl(deferredLookupTypeResolution.ts, 2, 37)) + + { [K in S]: 'true' } & +>K : Symbol(K, Decl(deferredLookupTypeResolution.ts, 3, 7)) +>S : Symbol(S, Decl(deferredLookupTypeResolution.ts, 2, 20)) + + { [key: string]: 'false' } +>key : Symbol(key, Decl(deferredLookupTypeResolution.ts, 4, 7)) + + )[L] +>L : Symbol(L, Decl(deferredLookupTypeResolution.ts, 2, 37)) + +type ObjectHasKey = StringContains +>ObjectHasKey : Symbol(ObjectHasKey, Decl(deferredLookupTypeResolution.ts, 5, 6)) +>O : Symbol(O, Decl(deferredLookupTypeResolution.ts, 7, 18)) +>L : Symbol(L, Decl(deferredLookupTypeResolution.ts, 7, 20)) +>StringContains : Symbol(StringContains, Decl(deferredLookupTypeResolution.ts, 0, 0)) +>O : Symbol(O, Decl(deferredLookupTypeResolution.ts, 7, 18)) +>L : Symbol(L, Decl(deferredLookupTypeResolution.ts, 7, 20)) + +type First = ObjectHasKey; // Should be deferred +>First : Symbol(First, Decl(deferredLookupTypeResolution.ts, 7, 67)) +>T : Symbol(T, Decl(deferredLookupTypeResolution.ts, 9, 11)) +>ObjectHasKey : Symbol(ObjectHasKey, Decl(deferredLookupTypeResolution.ts, 5, 6)) +>T : Symbol(T, Decl(deferredLookupTypeResolution.ts, 9, 11)) + +type T1 = ObjectHasKey<{ a: string }, 'a'>; // 'true' +>T1 : Symbol(T1, Decl(deferredLookupTypeResolution.ts, 9, 37)) +>ObjectHasKey : Symbol(ObjectHasKey, Decl(deferredLookupTypeResolution.ts, 5, 6)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 11, 24)) + +type T2 = ObjectHasKey<{ a: string }, 'b'>; // 'false' +>T2 : Symbol(T2, Decl(deferredLookupTypeResolution.ts, 11, 43)) +>ObjectHasKey : Symbol(ObjectHasKey, Decl(deferredLookupTypeResolution.ts, 5, 6)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 12, 24)) + +// Verify that mapped type isn't eagerly resolved in type-to-string operation + +declare function f1(a: A, b: B): { [P in A | B]: any }; +>f1 : Symbol(f1, Decl(deferredLookupTypeResolution.ts, 12, 43)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 16, 20)) +>B : Symbol(B, Decl(deferredLookupTypeResolution.ts, 16, 37)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 16, 56)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 16, 20)) +>b : Symbol(b, Decl(deferredLookupTypeResolution.ts, 16, 61)) +>B : Symbol(B, Decl(deferredLookupTypeResolution.ts, 16, 37)) +>P : Symbol(P, Decl(deferredLookupTypeResolution.ts, 16, 72)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 16, 20)) +>B : Symbol(B, Decl(deferredLookupTypeResolution.ts, 16, 37)) + +function f2(a: A) { +>f2 : Symbol(f2, Decl(deferredLookupTypeResolution.ts, 16, 91)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 18, 12)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 18, 30)) +>A : Symbol(A, Decl(deferredLookupTypeResolution.ts, 18, 12)) + + return f1(a, 'x'); +>f1 : Symbol(f1, Decl(deferredLookupTypeResolution.ts, 12, 43)) +>a : Symbol(a, Decl(deferredLookupTypeResolution.ts, 18, 30)) +} + +function f3(x: 'a' | 'b') { +>f3 : Symbol(f3, Decl(deferredLookupTypeResolution.ts, 20, 1)) +>x : Symbol(x, Decl(deferredLookupTypeResolution.ts, 22, 12)) + + return f2(x); +>f2 : Symbol(f2, Decl(deferredLookupTypeResolution.ts, 16, 91)) +>x : Symbol(x, Decl(deferredLookupTypeResolution.ts, 22, 12)) +} + diff --git a/tests/baselines/reference/deferredLookupTypeResolution.types b/tests/baselines/reference/deferredLookupTypeResolution.types new file mode 100644 index 0000000000000..d9486d30b07c6 --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution.types @@ -0,0 +1,79 @@ +=== tests/cases/compiler/deferredLookupTypeResolution.ts === +// Repro from #17456 + +type StringContains = ( +>StringContains : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>S : S +>L : L + + { [K in S]: 'true' } & +>K : K +>S : S + + { [key: string]: 'false' } +>key : string + + )[L] +>L : L + +type ObjectHasKey = StringContains +>ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>O : O +>L : L +>StringContains : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>O : O +>L : L + +type First = ObjectHasKey; // Should be deferred +>First : ({ [K in S]: "true"; } & { [key: string]: "false"; })["0"] +>T : T +>ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>T : T + +type T1 = ObjectHasKey<{ a: string }, 'a'>; // 'true' +>T1 : "true" +>ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>a : string + +type T2 = ObjectHasKey<{ a: string }, 'b'>; // 'false' +>T2 : "false" +>ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>a : string + +// Verify that mapped type isn't eagerly resolved in type-to-string operation + +declare function f1(a: A, b: B): { [P in A | B]: any }; +>f1 : (a: A, b: B) => { [P in A | B]: any; } +>A : A +>B : B +>a : A +>A : A +>b : B +>B : B +>P : P +>A : A +>B : B + +function f2(a: A) { +>f2 : (a: A) => { [P in A | B]: any; } +>A : A +>a : A +>A : A + + return f1(a, 'x'); +>f1(a, 'x') : { [P in A | B]: any; } +>f1 : (a: A, b: B) => { [P in A | B]: any; } +>a : A +>'x' : "x" +} + +function f3(x: 'a' | 'b') { +>f3 : (x: "a" | "b") => { a: any; b: any; x: any; } +>x : "a" | "b" + + return f2(x); +>f2(x) : { a: any; b: any; x: any; } +>f2 : (a: A) => { [P in A | B]: any; } +>x : "a" | "b" +} + diff --git a/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt b/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt new file mode 100644 index 0000000000000..f6bbe72f1a627 --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt @@ -0,0 +1,31 @@ +tests/cases/compiler/deferredLookupTypeResolution2.ts(14,13): error TS2536: Type '({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]' cannot be used to index type '{ true: "true"; }'. +tests/cases/compiler/deferredLookupTypeResolution2.ts(19,21): error TS2536: Type '({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]]' cannot be used to index type '{ true: "true"; }'. + + +==== tests/cases/compiler/deferredLookupTypeResolution2.ts (2 errors) ==== + // Repro from #17456 + + type StringContains = ({ [K in S]: 'true' } & { [key: string]: 'false'})[L]; + + type ObjectHasKey = StringContains; + + type A = ObjectHasKey; + + type B = ObjectHasKey<[string, number], '1'>; // "true" + type C = ObjectHasKey<[string, number], '2'>; // "false" + type D = A<[string]>; // "true" + + // Error, "false" not handled + type E = { true: 'true' }[ObjectHasKey]; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type '({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]' cannot be used to index type '{ true: "true"; }'. + + type Juxtapose = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey]; + + // Error, "otherwise" is missing + type DeepError = { true: 'true' }[Juxtapose]; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type '({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]]' cannot be used to index type '{ true: "true"; }'. + + type DeepOK = { true: 'true', otherwise: 'false' }[Juxtapose]; + \ No newline at end of file diff --git a/tests/baselines/reference/deferredLookupTypeResolution2.js b/tests/baselines/reference/deferredLookupTypeResolution2.js new file mode 100644 index 0000000000000..97289f47f9cc6 --- /dev/null +++ b/tests/baselines/reference/deferredLookupTypeResolution2.js @@ -0,0 +1,55 @@ +//// [deferredLookupTypeResolution2.ts] +// Repro from #17456 + +type StringContains = ({ [K in S]: 'true' } & { [key: string]: 'false'})[L]; + +type ObjectHasKey = StringContains; + +type A = ObjectHasKey; + +type B = ObjectHasKey<[string, number], '1'>; // "true" +type C = ObjectHasKey<[string, number], '2'>; // "false" +type D = A<[string]>; // "true" + +// Error, "false" not handled +type E = { true: 'true' }[ObjectHasKey]; + +type Juxtapose = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey]; + +// Error, "otherwise" is missing +type DeepError = { true: 'true' }[Juxtapose]; + +type DeepOK = { true: 'true', otherwise: 'false' }[Juxtapose]; + + +//// [deferredLookupTypeResolution2.js] +"use strict"; +// Repro from #17456 + + +//// [deferredLookupTypeResolution2.d.ts] +declare type StringContains = ({ + [K in S]: 'true'; +} & { + [key: string]: 'false'; +})[L]; +declare type ObjectHasKey = StringContains; +declare type A = ObjectHasKey; +declare type B = ObjectHasKey<[string, number], '1'>; +declare type C = ObjectHasKey<[string, number], '2'>; +declare type D = A<[string]>; +declare type E = { + true: 'true'; +}[ObjectHasKey]; +declare type Juxtapose = ({ + true: 'otherwise'; +} & { + [k: string]: 'true'; +})[ObjectHasKey]; +declare type DeepError = { + true: 'true'; +}[Juxtapose]; +declare type DeepOK = { + true: 'true'; + otherwise: 'false'; +}[Juxtapose]; diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt index 1e88e2abc4341..0634c3419e014 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt @@ -15,6 +15,7 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(35,21): error tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(36,21): error TS2538: Type 'boolean' cannot be used as an index type. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(41,31): error TS2538: Type 'boolean' cannot be used as an index type. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(46,16): error TS2538: Type 'boolean' cannot be used as an index type. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(49,12): error TS1122: A tuple type element list cannot be empty. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(63,33): error TS2345: Argument of type '"size"' is not assignable to parameter of type '"name" | "width" | "height" | "visible"'. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(64,33): error TS2345: Argument of type '"name" | "size"' is not assignable to parameter of type '"name" | "width" | "height" | "visible"'. Type '"size"' is not assignable to type '"name" | "width" | "height" | "visible"'. @@ -28,7 +29,7 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(76,5): error tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'. -==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (24 errors) ==== +==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (25 errors) ==== class Shape { name: string; width: number; @@ -112,6 +113,8 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error type T60 = {}["toString"]; type T61 = []["toString"]; + ~~ +!!! error TS1122: A tuple type element list cannot be empty. declare let cond: boolean; diff --git a/tests/cases/compiler/deferredLookupTypeResolution.ts b/tests/cases/compiler/deferredLookupTypeResolution.ts new file mode 100644 index 0000000000000..6c9853266ad3d --- /dev/null +++ b/tests/cases/compiler/deferredLookupTypeResolution.ts @@ -0,0 +1,28 @@ +// @strict: true +// @declaration: true + +// Repro from #17456 + +type StringContains = ( + { [K in S]: 'true' } & + { [key: string]: 'false' } + )[L] + +type ObjectHasKey = StringContains + +type First = ObjectHasKey; // Should be deferred + +type T1 = ObjectHasKey<{ a: string }, 'a'>; // 'true' +type T2 = ObjectHasKey<{ a: string }, 'b'>; // 'false' + +// Verify that mapped type isn't eagerly resolved in type-to-string operation + +declare function f1(a: A, b: B): { [P in A | B]: any }; + +function f2(a: A) { + return f1(a, 'x'); +} + +function f3(x: 'a' | 'b') { + return f2(x); +} diff --git a/tests/cases/compiler/deferredLookupTypeResolution2.ts b/tests/cases/compiler/deferredLookupTypeResolution2.ts new file mode 100644 index 0000000000000..4aa18c092ba91 --- /dev/null +++ b/tests/cases/compiler/deferredLookupTypeResolution2.ts @@ -0,0 +1,24 @@ +// @strict: true +// @declaration: true + +// Repro from #17456 + +type StringContains = ({ [K in S]: 'true' } & { [key: string]: 'false'})[L]; + +type ObjectHasKey = StringContains; + +type A = ObjectHasKey; + +type B = ObjectHasKey<[string, number], '1'>; // "true" +type C = ObjectHasKey<[string, number], '2'>; // "false" +type D = A<[string]>; // "true" + +// Error, "false" not handled +type E = { true: 'true' }[ObjectHasKey]; + +type Juxtapose = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey]; + +// Error, "otherwise" is missing +type DeepError = { true: 'true' }[Juxtapose]; + +type DeepOK = { true: 'true', otherwise: 'false' }[Juxtapose];