Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e5f6ed0

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

9 files changed

+629-381Lines changed: 629 additions & 381 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/checker.ts‎

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+32-15Lines 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
Collapse file

‎src/compiler/types.ts‎

Copy file name to clipboardExpand all lines: src/compiler/types.ts
+9-7Lines 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 {
Collapse file

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

Copy file name to clipboardExpand all lines: tests/baselines/reference/api/tsserverlibrary.d.ts
+2-1Lines 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 {
Collapse file

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

Copy file name to clipboardExpand all lines: tests/baselines/reference/api/typescript.d.ts
+2-1Lines 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 {
Collapse file

‎tests/baselines/reference/conditionalTypes1.errors.txt‎

Copy file name to clipboardExpand all lines: tests/baselines/reference/conditionalTypes1.errors.txt
+18-2Lines 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

Collapse file

‎tests/baselines/reference/conditionalTypes1.js‎

Copy file name to clipboardExpand all lines: tests/baselines/reference/conditionalTypes1.js
+47Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.