mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 03:23:08 -06:00
Merge pull request #17521 from Microsoft/deferLookupTypeResolution
Defer indexed access type resolution
This commit is contained in:
commit
313c93c861
@ -5788,8 +5788,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function isGenericMappedType(type: Type) {
|
||||
return getObjectFlags(type) & ObjectFlags.Mapped &&
|
||||
maybeTypeOfKind(getConstraintTypeFromMappedType(<MappedType>type), TypeFlags.TypeVariable | TypeFlags.Index);
|
||||
return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(<MappedType>type));
|
||||
}
|
||||
|
||||
function resolveStructuredTypeMembers(type: StructuredType): ResolvedType {
|
||||
@ -5901,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;
|
||||
@ -5972,11 +5975,18 @@ namespace ts {
|
||||
return stringType;
|
||||
}
|
||||
if (t.flags & TypeFlags.IndexedAccess) {
|
||||
const transformed = getTransformedIndexedAccessType(<IndexedAccessType>t);
|
||||
if (transformed) {
|
||||
return getBaseConstraint(transformed);
|
||||
}
|
||||
const baseObjectType = getBaseConstraint((<IndexedAccessType>t).objectType);
|
||||
const baseIndexType = getBaseConstraint((<IndexedAccessType>t).indexType);
|
||||
const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined;
|
||||
return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined;
|
||||
}
|
||||
if (isGenericMappedType(t)) {
|
||||
return emptyObjectType;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
@ -7604,26 +7614,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(<MappedType>type)) :
|
||||
type.flags & TypeFlags.UnionOrIntersection ? forEach((<UnionOrIntersectionType>type).types, isGenericObjectType) :
|
||||
false;
|
||||
}
|
||||
|
||||
function isGenericIndexType(type: Type): boolean {
|
||||
return type.flags & (TypeFlags.TypeVariable | TypeFlags.Index) ? true :
|
||||
type.flags & TypeFlags.UnionOrIntersection ? forEach((<UnionOrIntersectionType>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(<ObjectType>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((<IntersectionType>objectType).types, isStringIndexOnlyType)) {
|
||||
const regularTypes: Type[] = [];
|
||||
const stringIndexTypes: Type[] = [];
|
||||
for (const t of (<IntersectionType>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<T[P]> }[X], we
|
||||
// construct the type Box<T[X]>.
|
||||
if (isGenericMappedType(objectType)) {
|
||||
return getIndexedAccessForMappedType(<MappedType>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<T[P]> }[X], we construct the
|
||||
// type Box<T[X]>.
|
||||
if (isGenericMappedType(objectType)) {
|
||||
return getIndexedAccessForMappedType(<MappedType>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) {
|
||||
|
||||
64
tests/baselines/reference/deferredLookupTypeResolution.js
Normal file
64
tests/baselines/reference/deferredLookupTypeResolution.js
Normal file
@ -0,0 +1,64 @@
|
||||
//// [deferredLookupTypeResolution.ts]
|
||||
// Repro from #17456
|
||||
|
||||
type StringContains<S extends string, L extends string> = (
|
||||
{ [K in S]: 'true' } &
|
||||
{ [key: string]: 'false' }
|
||||
)[L]
|
||||
|
||||
type ObjectHasKey<O, L extends string> = StringContains<keyof O, L>
|
||||
|
||||
type First<T> = ObjectHasKey<T, '0'>; // 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 extends string, B extends string>(a: A, b: B): { [P in A | B]: any };
|
||||
|
||||
function f2<A extends string>(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<S extends string, L extends string> = ({
|
||||
[K in S]: 'true';
|
||||
} & {
|
||||
[key: string]: 'false';
|
||||
})[L];
|
||||
declare type ObjectHasKey<O, L extends string> = StringContains<keyof O, L>;
|
||||
declare type First<T> = ObjectHasKey<T, '0'>;
|
||||
declare type T1 = ObjectHasKey<{
|
||||
a: string;
|
||||
}, 'a'>;
|
||||
declare type T2 = ObjectHasKey<{
|
||||
a: string;
|
||||
}, 'b'>;
|
||||
declare function f1<A extends string, B extends string>(a: A, b: B): {
|
||||
[P in A | B]: any;
|
||||
};
|
||||
declare function f2<A extends string>(a: A): {
|
||||
[P in A | "x"]: any;
|
||||
};
|
||||
declare function f3(x: 'a' | 'b'): {
|
||||
a: any;
|
||||
b: any;
|
||||
x: any;
|
||||
};
|
||||
@ -0,0 +1,76 @@
|
||||
=== tests/cases/compiler/deferredLookupTypeResolution.ts ===
|
||||
// Repro from #17456
|
||||
|
||||
type StringContains<S extends string, L extends string> = (
|
||||
>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<O, L extends string> = StringContains<keyof O, L>
|
||||
>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<T> = ObjectHasKey<T, '0'>; // 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 extends string, B extends string>(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 extends string>(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))
|
||||
}
|
||||
|
||||
79
tests/baselines/reference/deferredLookupTypeResolution.types
Normal file
79
tests/baselines/reference/deferredLookupTypeResolution.types
Normal file
@ -0,0 +1,79 @@
|
||||
=== tests/cases/compiler/deferredLookupTypeResolution.ts ===
|
||||
// Repro from #17456
|
||||
|
||||
type StringContains<S extends string, L extends string> = (
|
||||
>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<O, L extends string> = StringContains<keyof O, L>
|
||||
>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<T> = ObjectHasKey<T, '0'>; // 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 extends string, B extends string>(a: A, b: B): { [P in A | B]: any };
|
||||
>f1 : <A extends string, B extends string>(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 extends string>(a: A) {
|
||||
>f2 : <A extends string>(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 extends string, B extends string>(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 extends string>(a: A) => { [P in A | B]: any; }
|
||||
>x : "a" | "b"
|
||||
}
|
||||
|
||||
@ -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<S extends string, L extends string> = ({ [K in S]: 'true' } & { [key: string]: 'false'})[L];
|
||||
|
||||
type ObjectHasKey<O, L extends string> = StringContains<keyof O, L>;
|
||||
|
||||
type A<T> = ObjectHasKey<T, '0'>;
|
||||
|
||||
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<T> = { true: 'true' }[ObjectHasKey<T, '1'>];
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
!!! error TS2536: Type '({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]' cannot be used to index type '{ true: "true"; }'.
|
||||
|
||||
type Juxtapose<T> = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey<T, '1'>];
|
||||
|
||||
// Error, "otherwise" is missing
|
||||
type DeepError<T> = { true: 'true' }[Juxtapose<T>];
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
!!! 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<T> = { true: 'true', otherwise: 'false' }[Juxtapose<T>];
|
||||
|
||||
55
tests/baselines/reference/deferredLookupTypeResolution2.js
Normal file
55
tests/baselines/reference/deferredLookupTypeResolution2.js
Normal file
@ -0,0 +1,55 @@
|
||||
//// [deferredLookupTypeResolution2.ts]
|
||||
// Repro from #17456
|
||||
|
||||
type StringContains<S extends string, L extends string> = ({ [K in S]: 'true' } & { [key: string]: 'false'})[L];
|
||||
|
||||
type ObjectHasKey<O, L extends string> = StringContains<keyof O, L>;
|
||||
|
||||
type A<T> = ObjectHasKey<T, '0'>;
|
||||
|
||||
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<T> = { true: 'true' }[ObjectHasKey<T, '1'>];
|
||||
|
||||
type Juxtapose<T> = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey<T, '1'>];
|
||||
|
||||
// Error, "otherwise" is missing
|
||||
type DeepError<T> = { true: 'true' }[Juxtapose<T>];
|
||||
|
||||
type DeepOK<T> = { true: 'true', otherwise: 'false' }[Juxtapose<T>];
|
||||
|
||||
|
||||
//// [deferredLookupTypeResolution2.js]
|
||||
"use strict";
|
||||
// Repro from #17456
|
||||
|
||||
|
||||
//// [deferredLookupTypeResolution2.d.ts]
|
||||
declare type StringContains<S extends string, L extends string> = ({
|
||||
[K in S]: 'true';
|
||||
} & {
|
||||
[key: string]: 'false';
|
||||
})[L];
|
||||
declare type ObjectHasKey<O, L extends string> = StringContains<keyof O, L>;
|
||||
declare type A<T> = ObjectHasKey<T, '0'>;
|
||||
declare type B = ObjectHasKey<[string, number], '1'>;
|
||||
declare type C = ObjectHasKey<[string, number], '2'>;
|
||||
declare type D = A<[string]>;
|
||||
declare type E<T> = {
|
||||
true: 'true';
|
||||
}[ObjectHasKey<T, '1'>];
|
||||
declare type Juxtapose<T> = ({
|
||||
true: 'otherwise';
|
||||
} & {
|
||||
[k: string]: 'true';
|
||||
})[ObjectHasKey<T, '1'>];
|
||||
declare type DeepError<T> = {
|
||||
true: 'true';
|
||||
}[Juxtapose<T>];
|
||||
declare type DeepOK<T> = {
|
||||
true: 'true';
|
||||
otherwise: 'false';
|
||||
}[Juxtapose<T>];
|
||||
28
tests/cases/compiler/deferredLookupTypeResolution.ts
Normal file
28
tests/cases/compiler/deferredLookupTypeResolution.ts
Normal file
@ -0,0 +1,28 @@
|
||||
// @strict: true
|
||||
// @declaration: true
|
||||
|
||||
// Repro from #17456
|
||||
|
||||
type StringContains<S extends string, L extends string> = (
|
||||
{ [K in S]: 'true' } &
|
||||
{ [key: string]: 'false' }
|
||||
)[L]
|
||||
|
||||
type ObjectHasKey<O, L extends string> = StringContains<keyof O, L>
|
||||
|
||||
type First<T> = ObjectHasKey<T, '0'>; // 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 extends string, B extends string>(a: A, b: B): { [P in A | B]: any };
|
||||
|
||||
function f2<A extends string>(a: A) {
|
||||
return f1(a, 'x');
|
||||
}
|
||||
|
||||
function f3(x: 'a' | 'b') {
|
||||
return f2(x);
|
||||
}
|
||||
24
tests/cases/compiler/deferredLookupTypeResolution2.ts
Normal file
24
tests/cases/compiler/deferredLookupTypeResolution2.ts
Normal file
@ -0,0 +1,24 @@
|
||||
// @strict: true
|
||||
// @declaration: true
|
||||
|
||||
// Repro from #17456
|
||||
|
||||
type StringContains<S extends string, L extends string> = ({ [K in S]: 'true' } & { [key: string]: 'false'})[L];
|
||||
|
||||
type ObjectHasKey<O, L extends string> = StringContains<keyof O, L>;
|
||||
|
||||
type A<T> = ObjectHasKey<T, '0'>;
|
||||
|
||||
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<T> = { true: 'true' }[ObjectHasKey<T, '1'>];
|
||||
|
||||
type Juxtapose<T> = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey<T, '1'>];
|
||||
|
||||
// Error, "otherwise" is missing
|
||||
type DeepError<T> = { true: 'true' }[Juxtapose<T>];
|
||||
|
||||
type DeepOK<T> = { true: 'true', otherwise: 'false' }[Juxtapose<T>];
|
||||
Loading…
x
Reference in New Issue
Block a user