Skip to content

Commit a230cb7

Browse files
authored
Merge pull request #12623 from Microsoft/nestedIndexedAccess
Treat indexed access types 'T[K]' as type variables
2 parents 012159b + fe0b66a commit a230cb7

10 files changed

+876
-93
lines changed

src/compiler/checker.ts

Lines changed: 84 additions & 79 deletions
Large diffs are not rendered by default.

src/compiler/types.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2797,6 +2797,7 @@ namespace ts {
27972797
UnionOrIntersection = Union | Intersection,
27982798
StructuredType = Object | Union | Intersection,
27992799
StructuredOrTypeParameter = StructuredType | TypeParameter | Index,
2800+
TypeVariable = TypeParameter | IndexedAccess,
28002801

28012802
// 'Narrowable' types are types where narrowing actually narrows.
28022803
// This *should* be every type other than null, undefined, void, and never
@@ -2907,7 +2908,7 @@ namespace ts {
29072908
/* @internal */
29082909
resolvedProperties: SymbolTable; // Cache of resolved properties
29092910
/* @internal */
2910-
couldContainTypeParameters: boolean;
2911+
couldContainTypeVariables: boolean;
29112912
}
29122913

29132914
export interface UnionType extends UnionOrIntersectionType { }
@@ -2963,8 +2964,13 @@ namespace ts {
29632964
iteratorElementType?: Type;
29642965
}
29652966

2967+
export interface TypeVariable extends Type {
2968+
/* @internal */
2969+
resolvedIndexType: IndexType;
2970+
}
2971+
29662972
// Type parameters (TypeFlags.TypeParameter)
2967-
export interface TypeParameter extends Type {
2973+
export interface TypeParameter extends TypeVariable {
29682974
constraint: Type; // Constraint
29692975
/* @internal */
29702976
target?: TypeParameter; // Instantiation target
@@ -2973,20 +2979,21 @@ namespace ts {
29732979
/* @internal */
29742980
resolvedApparentType: Type;
29752981
/* @internal */
2976-
resolvedIndexType: IndexType;
2977-
/* @internal */
29782982
isThisType?: boolean;
29792983
}
29802984

2981-
export interface IndexType extends Type {
2982-
type: TypeParameter;
2983-
}
2984-
2985-
export interface IndexedAccessType extends Type {
2985+
// Indexed access types (TypeFlags.IndexedAccess)
2986+
// Possible forms are T[xxx], xxx[T], or xxx[keyof T], where T is a type variable
2987+
export interface IndexedAccessType extends TypeVariable {
29862988
objectType: Type;
29872989
indexType: Type;
29882990
}
29892991

2992+
// keyof T types (TypeFlags.Index)
2993+
export interface IndexType extends Type {
2994+
type: TypeVariable;
2995+
}
2996+
29902997
export const enum SignatureKind {
29912998
Call,
29922999
Construct,

tests/baselines/reference/isomorphicMappedTypeInference.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,32 @@ function f10(foo: Foo) {
118118
let x = validate(foo); // { a: number, readonly b: string }
119119
let y = clone(foo); // { a?: number, b: string }
120120
let z = validateAndClone(foo); // { a: number, b: string }
121-
}
121+
}
122+
123+
// Repro from #12606
124+
125+
type Func<T> = (...args: any[]) => T;
126+
type Spec<T> = {
127+
[P in keyof T]: Func<T[P]> | Spec<T[P]> ;
128+
};
129+
130+
/**
131+
* Given a spec object recursively mapping properties to functions, creates a function
132+
* producing an object of the same structure, by mapping each property to the result
133+
* of calling its associated function with the supplied arguments.
134+
*/
135+
declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
136+
137+
// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
138+
var g1 = applySpec({
139+
sum: (a: any) => 3,
140+
nested: {
141+
mul: (b: any) => "n"
142+
}
143+
});
144+
145+
// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
146+
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
122147

123148
//// [isomorphicMappedTypeInference.js]
124149
function box(x) {
@@ -210,6 +235,15 @@ function f10(foo) {
210235
var y = clone(foo); // { a?: number, b: string }
211236
var z = validateAndClone(foo); // { a: number, b: string }
212237
}
238+
// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
239+
var g1 = applySpec({
240+
sum: function (a) { return 3; },
241+
nested: {
242+
mul: function (b) { return "n"; }
243+
}
244+
});
245+
// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
246+
var g2 = applySpec({ foo: { bar: { baz: function (x) { return true; } } } });
213247

214248

215249
//// [isomorphicMappedTypeInference.d.ts]
@@ -254,3 +288,26 @@ declare type Foo = {
254288
readonly b: string;
255289
};
256290
declare function f10(foo: Foo): void;
291+
declare type Func<T> = (...args: any[]) => T;
292+
declare type Spec<T> = {
293+
[P in keyof T]: Func<T[P]> | Spec<T[P]>;
294+
};
295+
/**
296+
* Given a spec object recursively mapping properties to functions, creates a function
297+
* producing an object of the same structure, by mapping each property to the result
298+
* of calling its associated function with the supplied arguments.
299+
*/
300+
declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
301+
declare var g1: (...args: any[]) => {
302+
sum: number;
303+
nested: {
304+
mul: string;
305+
};
306+
};
307+
declare var g2: (...args: any[]) => {
308+
foo: {
309+
bar: {
310+
baz: boolean;
311+
};
312+
};
313+
};

tests/baselines/reference/isomorphicMappedTypeInference.symbols

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,69 @@ function f10(foo: Foo) {
393393
>validateAndClone : Symbol(validateAndClone, Decl(isomorphicMappedTypeInference.ts, 107, 69))
394394
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13))
395395
}
396+
397+
// Repro from #12606
398+
399+
type Func<T> = (...args: any[]) => T;
400+
>Func : Symbol(Func, Decl(isomorphicMappedTypeInference.ts, 119, 1))
401+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 123, 10))
402+
>args : Symbol(args, Decl(isomorphicMappedTypeInference.ts, 123, 16))
403+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 123, 10))
404+
405+
type Spec<T> = {
406+
>Spec : Symbol(Spec, Decl(isomorphicMappedTypeInference.ts, 123, 37))
407+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10))
408+
409+
[P in keyof T]: Func<T[P]> | Spec<T[P]> ;
410+
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 125, 5))
411+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10))
412+
>Func : Symbol(Func, Decl(isomorphicMappedTypeInference.ts, 119, 1))
413+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10))
414+
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 125, 5))
415+
>Spec : Symbol(Spec, Decl(isomorphicMappedTypeInference.ts, 123, 37))
416+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10))
417+
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 125, 5))
418+
419+
};
420+
421+
/**
422+
* Given a spec object recursively mapping properties to functions, creates a function
423+
* producing an object of the same structure, by mapping each property to the result
424+
* of calling its associated function with the supplied arguments.
425+
*/
426+
declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
427+
>applySpec : Symbol(applySpec, Decl(isomorphicMappedTypeInference.ts, 126, 2))
428+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 133, 27))
429+
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 133, 30))
430+
>Spec : Symbol(Spec, Decl(isomorphicMappedTypeInference.ts, 123, 37))
431+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 133, 27))
432+
>args : Symbol(args, Decl(isomorphicMappedTypeInference.ts, 133, 46))
433+
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 133, 27))
434+
435+
// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
436+
var g1 = applySpec({
437+
>g1 : Symbol(g1, Decl(isomorphicMappedTypeInference.ts, 136, 3))
438+
>applySpec : Symbol(applySpec, Decl(isomorphicMappedTypeInference.ts, 126, 2))
439+
440+
sum: (a: any) => 3,
441+
>sum : Symbol(sum, Decl(isomorphicMappedTypeInference.ts, 136, 20))
442+
>a : Symbol(a, Decl(isomorphicMappedTypeInference.ts, 137, 10))
443+
444+
nested: {
445+
>nested : Symbol(nested, Decl(isomorphicMappedTypeInference.ts, 137, 23))
446+
447+
mul: (b: any) => "n"
448+
>mul : Symbol(mul, Decl(isomorphicMappedTypeInference.ts, 138, 13))
449+
>b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 139, 14))
450+
}
451+
});
452+
453+
// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
454+
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
455+
>g2 : Symbol(g2, Decl(isomorphicMappedTypeInference.ts, 144, 3))
456+
>applySpec : Symbol(applySpec, Decl(isomorphicMappedTypeInference.ts, 126, 2))
457+
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 144, 20))
458+
>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 144, 27))
459+
>baz : Symbol(baz, Decl(isomorphicMappedTypeInference.ts, 144, 34))
460+
>x : Symbol(x, Decl(isomorphicMappedTypeInference.ts, 144, 41))
461+

tests/baselines/reference/isomorphicMappedTypeInference.types

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,3 +467,82 @@ function f10(foo: Foo) {
467467
>validateAndClone : <T>(obj: { readonly [P in keyof T]?: T[P] | undefined; }) => T
468468
>foo : Foo
469469
}
470+
471+
// Repro from #12606
472+
473+
type Func<T> = (...args: any[]) => T;
474+
>Func : Func<T>
475+
>T : T
476+
>args : any[]
477+
>T : T
478+
479+
type Spec<T> = {
480+
>Spec : Spec<T>
481+
>T : T
482+
483+
[P in keyof T]: Func<T[P]> | Spec<T[P]> ;
484+
>P : P
485+
>T : T
486+
>Func : Func<T>
487+
>T : T
488+
>P : P
489+
>Spec : Spec<T>
490+
>T : T
491+
>P : P
492+
493+
};
494+
495+
/**
496+
* Given a spec object recursively mapping properties to functions, creates a function
497+
* producing an object of the same structure, by mapping each property to the result
498+
* of calling its associated function with the supplied arguments.
499+
*/
500+
declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
501+
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
502+
>T : T
503+
>obj : Spec<T>
504+
>Spec : Spec<T>
505+
>T : T
506+
>args : any[]
507+
>T : T
508+
509+
// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
510+
var g1 = applySpec({
511+
>g1 : (...args: any[]) => { sum: number; nested: { mul: string; }; }
512+
>applySpec({ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }}) : (...args: any[]) => { sum: number; nested: { mul: string; }; }
513+
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
514+
>{ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }} : { sum: (a: any) => number; nested: { mul: (b: any) => string; }; }
515+
516+
sum: (a: any) => 3,
517+
>sum : (a: any) => number
518+
>(a: any) => 3 : (a: any) => number
519+
>a : any
520+
>3 : 3
521+
522+
nested: {
523+
>nested : { mul: (b: any) => string; }
524+
>{ mul: (b: any) => "n" } : { mul: (b: any) => string; }
525+
526+
mul: (b: any) => "n"
527+
>mul : (b: any) => string
528+
>(b: any) => "n" : (b: any) => string
529+
>b : any
530+
>"n" : "n"
531+
}
532+
});
533+
534+
// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
535+
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
536+
>g2 : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; }
537+
>applySpec({ foo: { bar: { baz: (x: any) => true } } }) : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; }
538+
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
539+
>{ foo: { bar: { baz: (x: any) => true } } } : { foo: { bar: { baz: (x: any) => boolean; }; }; }
540+
>foo : { bar: { baz: (x: any) => boolean; }; }
541+
>{ bar: { baz: (x: any) => true } } : { bar: { baz: (x: any) => boolean; }; }
542+
>bar : { baz: (x: any) => boolean; }
543+
>{ baz: (x: any) => true } : { baz: (x: any) => boolean; }
544+
>baz : (x: any) => boolean
545+
>(x: any) => true : (x: any) => boolean
546+
>x : any
547+
>true : true
548+

tests/baselines/reference/keyofAndIndexedAccess.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,48 @@ class OtherPerson {
250250
return getProperty(this, "parts")
251251
}
252252
}
253-
253+
254+
// Modified repro from #12544
255+
256+
function path<T, K1 extends keyof T>(obj: T, key1: K1): T[K1];
257+
function path<T, K1 extends keyof T, K2 extends keyof T[K1]>(obj: T, key1: K1, key2: K2): T[K1][K2];
258+
function path<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]>(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3];
259+
function path(obj: any, ...keys: (string | number)[]): any;
260+
function path(obj: any, ...keys: (string | number)[]): any {
261+
let result = obj;
262+
for (let k of keys) {
263+
result = result[k];
264+
}
265+
return result;
266+
}
267+
268+
type Thing = {
269+
a: { x: number, y: string },
270+
b: boolean
271+
};
272+
273+
274+
function f1(thing: Thing) {
275+
let x1 = path(thing, 'a'); // { x: number, y: string }
276+
let x2 = path(thing, 'a', 'y'); // string
277+
let x3 = path(thing, 'b'); // boolean
278+
let x4 = path(thing, ...['a', 'x']); // any
279+
}
280+
281+
// Repro from comment in #12114
282+
283+
const assignTo2 = <T, K1 extends keyof T, K2 extends keyof T[K1]>(object: T, key1: K1, key2: K2) =>
284+
(value: T[K1][K2]) => object[key1][key2] = value;
285+
286+
// Modified repro from #12573
287+
288+
declare function one<T>(handler: (t: T) => void): T
289+
var empty = one(() => {}) // inferred as {}, expected
290+
291+
type Handlers<T> = { [K in keyof T]: (t: T[K]) => void }
292+
declare function on<T>(handlerHash: Handlers<T>): T
293+
var hashOfEmpty1 = on({ test: () => {} }); // {}
294+
var hashOfEmpty2 = on({ test: (x: boolean) => {} }); // { test: boolean }
254295

255296
//// [keyofAndIndexedAccess.js]
256297
var __extends = (this && this.__extends) || function (d, b) {
@@ -430,6 +471,31 @@ var OtherPerson = (function () {
430471
};
431472
return OtherPerson;
432473
}());
474+
function path(obj) {
475+
var keys = [];
476+
for (var _i = 1; _i < arguments.length; _i++) {
477+
keys[_i - 1] = arguments[_i];
478+
}
479+
var result = obj;
480+
for (var _a = 0, keys_1 = keys; _a < keys_1.length; _a++) {
481+
var k = keys_1[_a];
482+
result = result[k];
483+
}
484+
return result;
485+
}
486+
function f1(thing) {
487+
var x1 = path(thing, 'a'); // { x: number, y: string }
488+
var x2 = path(thing, 'a', 'y'); // string
489+
var x3 = path(thing, 'b'); // boolean
490+
var x4 = path.apply(void 0, [thing].concat(['a', 'x'])); // any
491+
}
492+
// Repro from comment in #12114
493+
var assignTo2 = function (object, key1, key2) {
494+
return function (value) { return object[key1][key2] = value; };
495+
};
496+
var empty = one(function () { }); // inferred as {}, expected
497+
var hashOfEmpty1 = on({ test: function () { } }); // {}
498+
var hashOfEmpty2 = on({ test: function (x) { } }); // { test: boolean }
433499

434500

435501
//// [keyofAndIndexedAccess.d.ts]
@@ -551,3 +617,26 @@ declare class OtherPerson {
551617
constructor(parts: number);
552618
getParts(): number;
553619
}
620+
declare function path<T, K1 extends keyof T>(obj: T, key1: K1): T[K1];
621+
declare function path<T, K1 extends keyof T, K2 extends keyof T[K1]>(obj: T, key1: K1, key2: K2): T[K1][K2];
622+
declare function path<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]>(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3];
623+
declare function path(obj: any, ...keys: (string | number)[]): any;
624+
declare type Thing = {
625+
a: {
626+
x: number;
627+
y: string;
628+
};
629+
b: boolean;
630+
};
631+
declare function f1(thing: Thing): void;
632+
declare const assignTo2: <T, K1 extends keyof T, K2 extends keyof T[K1]>(object: T, key1: K1, key2: K2) => (value: T[K1][K2]) => T[K1][K2];
633+
declare function one<T>(handler: (t: T) => void): T;
634+
declare var empty: {};
635+
declare type Handlers<T> = {
636+
[K in keyof T]: (t: T[K]) => void;
637+
};
638+
declare function on<T>(handlerHash: Handlers<T>): T;
639+
declare var hashOfEmpty1: {};
640+
declare var hashOfEmpty2: {
641+
test: boolean;
642+
};

0 commit comments

Comments
 (0)