Skip to content

Commit e5f6ed0

Browse files
authored
Merge pull request microsoft#22707 from Microsoft/fixIndexedAccessInConditionalType
Fix indexed access in conditional type
2 parents de4a69c + be4f2d9 commit e5f6ed0

File tree

9 files changed

+629
-381
lines changed

9 files changed

+629
-381
lines changed

src/compiler/checker.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3040,7 +3040,7 @@ namespace ts {
30403040
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
30413041
}
30423042
if (type.flags & TypeFlags.Substitution) {
3043-
return typeToTypeNodeHelper((<SubstitutionType>type).typeParameter, context);
3043+
return typeToTypeNodeHelper((<SubstitutionType>type).typeVariable, context);
30443044
}
30453045

30463046
Debug.fail("Should be unreachable.");
@@ -7305,7 +7305,7 @@ namespace ts {
73057305
const res = tryGetDeclaredTypeOfSymbol(symbol);
73067306
if (res) {
73077307
return checkNoTypeArguments(node, symbol) ?
7308-
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeParameter(<TypeParameter>res, node) : res :
7308+
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable(<TypeParameter>res, node) : res :
73097309
unknownType;
73107310
}
73117311

@@ -7344,25 +7344,36 @@ namespace ts {
73447344
}
73457345
}
73467346

7347-
function getSubstitutionType(typeParameter: TypeParameter, substitute: Type) {
7347+
function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) {
73487348
const result = <SubstitutionType>createType(TypeFlags.Substitution);
7349-
result.typeParameter = typeParameter;
7349+
result.typeVariable = typeVariable;
73507350
result.substitute = substitute;
73517351
return result;
73527352
}
73537353

7354-
function getConstrainedTypeParameter(typeParameter: TypeParameter, node: Node) {
7354+
function isUnaryTupleTypeNode(node: TypeNode) {
7355+
return node.kind === SyntaxKind.TupleType && (<TupleTypeNode>node).elementTypes.length === 1;
7356+
}
7357+
7358+
function getImpliedConstraint(typeVariable: TypeVariable, checkNode: TypeNode, extendsNode: TypeNode): Type {
7359+
return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(typeVariable, (<TupleTypeNode>checkNode).elementTypes[0], (<TupleTypeNode>extendsNode).elementTypes[0]) :
7360+
getActualTypeVariable(getTypeFromTypeNode(checkNode)) === typeVariable ? getTypeFromTypeNode(extendsNode) :
7361+
undefined;
7362+
}
7363+
7364+
function getConstrainedTypeVariable(typeVariable: TypeVariable, node: Node) {
73557365
let constraints: Type[];
73567366
while (isPartOfTypeNode(node)) {
73577367
const parent = node.parent;
73587368
if (parent.kind === SyntaxKind.ConditionalType && node === (<ConditionalTypeNode>parent).trueType) {
7359-
if (getTypeFromTypeNode((<ConditionalTypeNode>parent).checkType) === typeParameter) {
7360-
constraints = append(constraints, getTypeFromTypeNode((<ConditionalTypeNode>parent).extendsType));
7369+
const constraint = getImpliedConstraint(typeVariable, (<ConditionalTypeNode>parent).checkType, (<ConditionalTypeNode>parent).extendsType);
7370+
if (constraint) {
7371+
constraints = append(constraints, constraint);
73617372
}
73627373
}
73637374
node = parent;
73647375
}
7365-
return constraints ? getSubstitutionType(typeParameter, getIntersectionType(append(constraints, typeParameter))) : typeParameter;
7376+
return constraints ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable))) : typeVariable;
73667377
}
73677378

73687379
function isJSDocTypeReference(node: TypeReferenceType): node is TypeReferenceNode {
@@ -8258,7 +8269,13 @@ namespace ts {
82588269
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
82598270
const links = getNodeLinks(node);
82608271
if (!links.resolvedType) {
8261-
links.resolvedType = getIndexedAccessType(getTypeFromTypeNode(node.objectType), getTypeFromTypeNode(node.indexType), node);
8272+
const objectType = getTypeFromTypeNode(node.objectType);
8273+
const indexType = getTypeFromTypeNode(node.indexType);
8274+
const resolved = getIndexedAccessType(objectType, indexType, node);
8275+
links.resolvedType = resolved.flags & TypeFlags.IndexedAccess &&
8276+
(<IndexedAccessType>resolved).objectType === objectType &&
8277+
(<IndexedAccessType>resolved).indexType === indexType ?
8278+
getConstrainedTypeVariable(<IndexedAccessType>resolved, node) : resolved;
82628279
}
82638280
return links.resolvedType;
82648281
}
@@ -8278,8 +8295,8 @@ namespace ts {
82788295
return links.resolvedType;
82798296
}
82808297

8281-
function getActualTypeParameter(type: Type) {
8282-
return type.flags & TypeFlags.Substitution ? (<SubstitutionType>type).typeParameter : type;
8298+
function getActualTypeVariable(type: Type) {
8299+
return type.flags & TypeFlags.Substitution ? (<SubstitutionType>type).typeVariable : type;
82838300
}
82848301

82858302
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper): Type {
@@ -8323,7 +8340,7 @@ namespace ts {
83238340
}
83248341
}
83258342
// Return a deferred type for a check that is neither definitely true nor definitely false
8326-
const erasedCheckType = getActualTypeParameter(checkType);
8343+
const erasedCheckType = getActualTypeVariable(checkType);
83278344
const result = <ConditionalType>createType(TypeFlags.Conditional);
83288345
result.root = root;
83298346
result.checkType = erasedCheckType;
@@ -9043,7 +9060,7 @@ namespace ts {
90439060
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
90449061
}
90459062
if (type.flags & TypeFlags.Substitution) {
9046-
return mapper((<SubstitutionType>type).typeParameter);
9063+
return instantiateType((<SubstitutionType>type).typeVariable, mapper);
90479064
}
90489065
}
90499066
return type;
@@ -9650,10 +9667,10 @@ namespace ts {
96509667
target = (<LiteralType>target).regularType;
96519668
}
96529669
if (source.flags & TypeFlags.Substitution) {
9653-
source = relation === definitelyAssignableRelation ? (<SubstitutionType>source).typeParameter : (<SubstitutionType>source).substitute;
9670+
source = relation === definitelyAssignableRelation ? (<SubstitutionType>source).typeVariable : (<SubstitutionType>source).substitute;
96549671
}
96559672
if (target.flags & TypeFlags.Substitution) {
9656-
target = (<SubstitutionType>target).typeParameter;
9673+
target = (<SubstitutionType>target).typeVariable;
96579674
}
96589675

96599676
// both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases

src/compiler/types.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3845,6 +3845,8 @@ namespace ts {
38453845
constraint?: Type;
38463846
}
38473847

3848+
export type TypeVariable = TypeParameter | IndexedAccessType;
3849+
38483850
// keyof T types (TypeFlags.Index)
38493851
export interface IndexType extends InstantiableType {
38503852
type: InstantiableType | UnionOrIntersectionType;
@@ -3876,14 +3878,14 @@ namespace ts {
38763878
}
38773879

38783880
// Type parameter substitution (TypeFlags.Substitution)
3879-
// Substitution types are created for type parameter references that occur in the true branch
3880-
// of a conditional type. For example, in 'T extends string ? Foo<T> : Bar<T>', the reference to
3881-
// T in Foo<T> is resolved as a substitution type that substitutes 'string & T' for T. Thus, if
3882-
// Foo has a 'string' constraint on its type parameter, T will satisfy it. Substitution types
3883-
// disappear upon instantiation (just like type parameters).
3881+
// Substitution types are created for type parameters or indexed access types that occur in the
3882+
// true branch of a conditional type. For example, in 'T extends string ? Foo<T> : Bar<T>', the
3883+
// reference to T in Foo<T> is resolved as a substitution type that substitutes 'string & T' for T.
3884+
// Thus, if Foo has a 'string' constraint on its type parameter, T will satisfy it. Substitution
3885+
// types disappear upon instantiation (just like type parameters).
38843886
export interface SubstitutionType extends InstantiableType {
3885-
typeParameter: TypeParameter; // Target type parameter
3886-
substitute: Type; // Type to substitute for type parameter
3887+
typeVariable: TypeVariable; // Target type variable
3888+
substitute: Type; // Type to substitute for type parameter
38873889
}
38883890

38893891
export const enum SignatureKind {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2192,6 +2192,7 @@ declare namespace ts {
21922192
indexType: Type;
21932193
constraint?: Type;
21942194
}
2195+
type TypeVariable = TypeParameter | IndexedAccessType;
21952196
interface IndexType extends InstantiableType {
21962197
type: InstantiableType | UnionOrIntersectionType;
21972198
}
@@ -2216,7 +2217,7 @@ declare namespace ts {
22162217
resolvedFalseType?: Type;
22172218
}
22182219
interface SubstitutionType extends InstantiableType {
2219-
typeParameter: TypeParameter;
2220+
typeVariable: TypeVariable;
22202221
substitute: Type;
22212222
}
22222223
enum SignatureKind {

tests/baselines/reference/api/typescript.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2192,6 +2192,7 @@ declare namespace ts {
21922192
indexType: Type;
21932193
constraint?: Type;
21942194
}
2195+
type TypeVariable = TypeParameter | IndexedAccessType;
21952196
interface IndexType extends InstantiableType {
21962197
type: InstantiableType | UnionOrIntersectionType;
21972198
}
@@ -2216,7 +2217,7 @@ declare namespace ts {
22162217
resolvedFalseType?: Type;
22172218
}
22182219
interface SubstitutionType extends InstantiableType {
2219-
typeParameter: TypeParameter;
2220+
typeVariable: TypeVariable;
22202221
substitute: Type;
22212222
}
22222223
enum SignatureKind {

tests/baselines/reference/conditionalTypes1.errors.txt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(159,5): error TS2
6464
tests/cases/conformance/types/conditional/conditionalTypes1.ts(160,5): error TS2322: Type 'T' is not assignable to type 'ZeroOf<T>'.
6565
Type 'string | number' is not assignable to type 'ZeroOf<T>'.
6666
Type 'string' is not assignable to type 'ZeroOf<T>'.
67-
tests/cases/conformance/types/conditional/conditionalTypes1.ts(250,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'z' must be of type 'T1', but here has type 'Foo<T & U>'.
68-
tests/cases/conformance/types/conditional/conditionalTypes1.ts(275,43): error TS2322: Type 'T95<U>' is not assignable to type 'T94<U>'.
67+
tests/cases/conformance/types/conditional/conditionalTypes1.ts(255,9): error TS2403: Subsequent variable declarations must have the same type. Variable 'z' must be of type 'T1', but here has type 'Foo<T & U>'.
68+
tests/cases/conformance/types/conditional/conditionalTypes1.ts(280,43): error TS2322: Type 'T95<U>' is not assignable to type 'T94<U>'.
6969
Type 'boolean' is not assignable to type 'true'.
7070

7171

@@ -318,6 +318,11 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(275,43): error TS
318318
!!! error TS2322: Type 'string' is not assignable to type 'ZeroOf<T>'.
319319
}
320320

321+
type T35<T extends { a: string, b: number }> = T[];
322+
type T36<T> = T extends { a: string } ? T extends { b: number } ? T35<T> : never : never;
323+
type T37<T> = T extends { b: number } ? T extends { a: string } ? T35<T> : never : never;
324+
type T38<T> = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35<T> : never : never;
325+
321326
type Extends<T, U> = T extends U ? true : false;
322327
type If<C extends boolean, T, F> = C extends true ? T : F;
323328
type Not<C extends boolean> = If<C, false, true>;
@@ -477,4 +482,15 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(275,43): error TS
477482

478483
type Test1 = NonFooKeys1<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
479484
type Test2 = NonFooKeys2<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
485+
486+
// Repro from #21729
487+
488+
interface Foo2 { foo: string; }
489+
interface Bar2 { bar: string; }
490+
type FooBar = Foo2 | Bar2;
491+
declare interface ExtractFooBar<FB extends FooBar> { }
492+
493+
type Extracted<Struct> = {
494+
[K in keyof Struct]: Struct[K] extends FooBar ? ExtractFooBar<Struct[K]> : Struct[K];
495+
}
480496

tests/baselines/reference/conditionalTypes1.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ function f21<T extends number | string>(x: T, y: ZeroOf<T>) {
161161
y = x; // Error
162162
}
163163

164+
type T35<T extends { a: string, b: number }> = T[];
165+
type T36<T> = T extends { a: string } ? T extends { b: number } ? T35<T> : never : never;
166+
type T37<T> = T extends { b: number } ? T extends { a: string } ? T35<T> : never : never;
167+
type T38<T> = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35<T> : never : never;
168+
164169
type Extends<T, U> = T extends U ? true : false;
165170
type If<C extends boolean, T, F> = C extends true ? T : F;
166171
type Not<C extends boolean> = If<C, false, true>;
@@ -315,6 +320,17 @@ type NonFooKeys2<T extends object> = Exclude<keyof T, 'foo'>;
315320

316321
type Test1 = NonFooKeys1<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
317322
type Test2 = NonFooKeys2<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
323+
324+
// Repro from #21729
325+
326+
interface Foo2 { foo: string; }
327+
interface Bar2 { bar: string; }
328+
type FooBar = Foo2 | Bar2;
329+
declare interface ExtractFooBar<FB extends FooBar> { }
330+
331+
type Extracted<Struct> = {
332+
[K in keyof Struct]: Struct[K] extends FooBar ? ExtractFooBar<Struct[K]> : Struct[K];
333+
}
318334

319335

320336
//// [conditionalTypes1.js]
@@ -517,6 +533,25 @@ declare type ZeroOf<T extends number | string | boolean> = T extends number ? 0
517533
declare function zeroOf<T extends number | string | boolean>(value: T): ZeroOf<T>;
518534
declare function f20<T extends string>(n: number, b: boolean, x: number | boolean, y: T): void;
519535
declare function f21<T extends number | string>(x: T, y: ZeroOf<T>): void;
536+
declare type T35<T extends {
537+
a: string;
538+
b: number;
539+
}> = T[];
540+
declare type T36<T> = T extends {
541+
a: string;
542+
} ? T extends {
543+
b: number;
544+
} ? T35<T> : never : never;
545+
declare type T37<T> = T extends {
546+
b: number;
547+
} ? T extends {
548+
a: string;
549+
} ? T35<T> : never : never;
550+
declare type T38<T> = [T] extends [{
551+
a: string;
552+
}] ? [T] extends [{
553+
b: number;
554+
}] ? T35<T> : never : never;
520555
declare type Extends<T, U> = T extends U ? true : false;
521556
declare type If<C extends boolean, T, F> = C extends true ? T : F;
522557
declare type Not<C extends boolean> = If<C, false, true>;
@@ -624,3 +659,15 @@ declare type Test2 = NonFooKeys2<{
624659
bar: 2;
625660
baz: 3;
626661
}>;
662+
interface Foo2 {
663+
foo: string;
664+
}
665+
interface Bar2 {
666+
bar: string;
667+
}
668+
declare type FooBar = Foo2 | Bar2;
669+
declare interface ExtractFooBar<FB extends FooBar> {
670+
}
671+
declare type Extracted<Struct> = {
672+
[K in keyof Struct]: Struct[K] extends FooBar ? ExtractFooBar<Struct[K]> : Struct[K];
673+
};

0 commit comments

Comments
 (0)