Skip to content

Commit

Permalink
Merge pull request #12623 from Microsoft/nestedIndexedAccess
Browse files Browse the repository at this point in the history
Treat indexed access types 'T[K]' as type variables
  • Loading branch information
ahejlsberg authored Dec 2, 2016
2 parents 012159b + fe0b66a commit a230cb7
Show file tree
Hide file tree
Showing 10 changed files with 876 additions and 93 deletions.
163 changes: 84 additions & 79 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

25 changes: 16 additions & 9 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,7 @@ namespace ts {
UnionOrIntersection = Union | Intersection,
StructuredType = Object | Union | Intersection,
StructuredOrTypeParameter = StructuredType | TypeParameter | Index,
TypeVariable = TypeParameter | IndexedAccess,

// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Expand Down Expand Up @@ -2907,7 +2908,7 @@ namespace ts {
/* @internal */
resolvedProperties: SymbolTable; // Cache of resolved properties
/* @internal */
couldContainTypeParameters: boolean;
couldContainTypeVariables: boolean;
}

export interface UnionType extends UnionOrIntersectionType { }
Expand Down Expand Up @@ -2963,8 +2964,13 @@ namespace ts {
iteratorElementType?: Type;
}

export interface TypeVariable extends Type {
/* @internal */
resolvedIndexType: IndexType;
}

// Type parameters (TypeFlags.TypeParameter)
export interface TypeParameter extends Type {
export interface TypeParameter extends TypeVariable {
constraint: Type; // Constraint
/* @internal */
target?: TypeParameter; // Instantiation target
Expand All @@ -2973,20 +2979,21 @@ namespace ts {
/* @internal */
resolvedApparentType: Type;
/* @internal */
resolvedIndexType: IndexType;
/* @internal */
isThisType?: boolean;
}

export interface IndexType extends Type {
type: TypeParameter;
}

export interface IndexedAccessType extends Type {
// Indexed access types (TypeFlags.IndexedAccess)
// Possible forms are T[xxx], xxx[T], or xxx[keyof T], where T is a type variable
export interface IndexedAccessType extends TypeVariable {
objectType: Type;
indexType: Type;
}

// keyof T types (TypeFlags.Index)
export interface IndexType extends Type {
type: TypeVariable;
}

export const enum SignatureKind {
Call,
Construct,
Expand Down
59 changes: 58 additions & 1 deletion tests/baselines/reference/isomorphicMappedTypeInference.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,32 @@ function f10(foo: Foo) {
let x = validate(foo); // { a: number, readonly b: string }
let y = clone(foo); // { a?: number, b: string }
let z = validateAndClone(foo); // { a: number, b: string }
}
}

// Repro from #12606

type Func<T> = (...args: any[]) => T;
type Spec<T> = {
[P in keyof T]: Func<T[P]> | Spec<T[P]> ;
};

/**
* Given a spec object recursively mapping properties to functions, creates a function
* producing an object of the same structure, by mapping each property to the result
* of calling its associated function with the supplied arguments.
*/
declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;

// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
var g1 = applySpec({
sum: (a: any) => 3,
nested: {
mul: (b: any) => "n"
}
});

// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });

//// [isomorphicMappedTypeInference.js]
function box(x) {
Expand Down Expand Up @@ -210,6 +235,15 @@ function f10(foo) {
var y = clone(foo); // { a?: number, b: string }
var z = validateAndClone(foo); // { a: number, b: string }
}
// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
var g1 = applySpec({
sum: function (a) { return 3; },
nested: {
mul: function (b) { return "n"; }
}
});
// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
var g2 = applySpec({ foo: { bar: { baz: function (x) { return true; } } } });


//// [isomorphicMappedTypeInference.d.ts]
Expand Down Expand Up @@ -254,3 +288,26 @@ declare type Foo = {
readonly b: string;
};
declare function f10(foo: Foo): void;
declare type Func<T> = (...args: any[]) => T;
declare type Spec<T> = {
[P in keyof T]: Func<T[P]> | Spec<T[P]>;
};
/**
* Given a spec object recursively mapping properties to functions, creates a function
* producing an object of the same structure, by mapping each property to the result
* of calling its associated function with the supplied arguments.
*/
declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
declare var g1: (...args: any[]) => {
sum: number;
nested: {
mul: string;
};
};
declare var g2: (...args: any[]) => {
foo: {
bar: {
baz: boolean;
};
};
};
66 changes: 66 additions & 0 deletions tests/baselines/reference/isomorphicMappedTypeInference.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,69 @@ function f10(foo: Foo) {
>validateAndClone : Symbol(validateAndClone, Decl(isomorphicMappedTypeInference.ts, 107, 69))
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13))
}

// Repro from #12606

type Func<T> = (...args: any[]) => T;
>Func : Symbol(Func, Decl(isomorphicMappedTypeInference.ts, 119, 1))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 123, 10))
>args : Symbol(args, Decl(isomorphicMappedTypeInference.ts, 123, 16))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 123, 10))

type Spec<T> = {
>Spec : Symbol(Spec, Decl(isomorphicMappedTypeInference.ts, 123, 37))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10))

[P in keyof T]: Func<T[P]> | Spec<T[P]> ;
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 125, 5))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10))
>Func : Symbol(Func, Decl(isomorphicMappedTypeInference.ts, 119, 1))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10))
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 125, 5))
>Spec : Symbol(Spec, Decl(isomorphicMappedTypeInference.ts, 123, 37))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10))
>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 125, 5))

};

/**
* Given a spec object recursively mapping properties to functions, creates a function
* producing an object of the same structure, by mapping each property to the result
* of calling its associated function with the supplied arguments.
*/
declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
>applySpec : Symbol(applySpec, Decl(isomorphicMappedTypeInference.ts, 126, 2))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 133, 27))
>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 133, 30))
>Spec : Symbol(Spec, Decl(isomorphicMappedTypeInference.ts, 123, 37))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 133, 27))
>args : Symbol(args, Decl(isomorphicMappedTypeInference.ts, 133, 46))
>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 133, 27))

// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
var g1 = applySpec({
>g1 : Symbol(g1, Decl(isomorphicMappedTypeInference.ts, 136, 3))
>applySpec : Symbol(applySpec, Decl(isomorphicMappedTypeInference.ts, 126, 2))

sum: (a: any) => 3,
>sum : Symbol(sum, Decl(isomorphicMappedTypeInference.ts, 136, 20))
>a : Symbol(a, Decl(isomorphicMappedTypeInference.ts, 137, 10))

nested: {
>nested : Symbol(nested, Decl(isomorphicMappedTypeInference.ts, 137, 23))

mul: (b: any) => "n"
>mul : Symbol(mul, Decl(isomorphicMappedTypeInference.ts, 138, 13))
>b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 139, 14))
}
});

// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
>g2 : Symbol(g2, Decl(isomorphicMappedTypeInference.ts, 144, 3))
>applySpec : Symbol(applySpec, Decl(isomorphicMappedTypeInference.ts, 126, 2))
>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 144, 20))
>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 144, 27))
>baz : Symbol(baz, Decl(isomorphicMappedTypeInference.ts, 144, 34))
>x : Symbol(x, Decl(isomorphicMappedTypeInference.ts, 144, 41))

79 changes: 79 additions & 0 deletions tests/baselines/reference/isomorphicMappedTypeInference.types
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,82 @@ function f10(foo: Foo) {
>validateAndClone : <T>(obj: { readonly [P in keyof T]?: T[P] | undefined; }) => T
>foo : Foo
}

// Repro from #12606

type Func<T> = (...args: any[]) => T;
>Func : Func<T>
>T : T
>args : any[]
>T : T

type Spec<T> = {
>Spec : Spec<T>
>T : T

[P in keyof T]: Func<T[P]> | Spec<T[P]> ;
>P : P
>T : T
>Func : Func<T>
>T : T
>P : P
>Spec : Spec<T>
>T : T
>P : P

};

/**
* Given a spec object recursively mapping properties to functions, creates a function
* producing an object of the same structure, by mapping each property to the result
* of calling its associated function with the supplied arguments.
*/
declare function applySpec<T>(obj: Spec<T>): (...args: any[]) => T;
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
>T : T
>obj : Spec<T>
>Spec : Spec<T>
>T : T
>args : any[]
>T : T

// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } }
var g1 = applySpec({
>g1 : (...args: any[]) => { sum: number; nested: { mul: string; }; }
>applySpec({ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }}) : (...args: any[]) => { sum: number; nested: { mul: string; }; }
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
>{ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }} : { sum: (a: any) => number; nested: { mul: (b: any) => string; }; }

sum: (a: any) => 3,
>sum : (a: any) => number
>(a: any) => 3 : (a: any) => number
>a : any
>3 : 3

nested: {
>nested : { mul: (b: any) => string; }
>{ mul: (b: any) => "n" } : { mul: (b: any) => string; }

mul: (b: any) => "n"
>mul : (b: any) => string
>(b: any) => "n" : (b: any) => string
>b : any
>"n" : "n"
}
});

// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } }
var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } });
>g2 : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; }
>applySpec({ foo: { bar: { baz: (x: any) => true } } }) : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; }
>applySpec : <T>(obj: Spec<T>) => (...args: any[]) => T
>{ foo: { bar: { baz: (x: any) => true } } } : { foo: { bar: { baz: (x: any) => boolean; }; }; }
>foo : { bar: { baz: (x: any) => boolean; }; }
>{ bar: { baz: (x: any) => true } } : { bar: { baz: (x: any) => boolean; }; }
>bar : { baz: (x: any) => boolean; }
>{ baz: (x: any) => true } : { baz: (x: any) => boolean; }
>baz : (x: any) => boolean
>(x: any) => true : (x: any) => boolean
>x : any
>true : true

91 changes: 90 additions & 1 deletion tests/baselines/reference/keyofAndIndexedAccess.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,48 @@ class OtherPerson {
return getProperty(this, "parts")
}
}


// Modified repro from #12544

function path<T, K1 extends keyof T>(obj: T, key1: K1): T[K1];
function path<T, K1 extends keyof T, K2 extends keyof T[K1]>(obj: T, key1: K1, key2: K2): T[K1][K2];
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];
function path(obj: any, ...keys: (string | number)[]): any;
function path(obj: any, ...keys: (string | number)[]): any {
let result = obj;
for (let k of keys) {
result = result[k];
}
return result;
}

type Thing = {
a: { x: number, y: string },
b: boolean
};


function f1(thing: Thing) {
let x1 = path(thing, 'a'); // { x: number, y: string }
let x2 = path(thing, 'a', 'y'); // string
let x3 = path(thing, 'b'); // boolean
let x4 = path(thing, ...['a', 'x']); // any
}

// Repro from comment in #12114

const assignTo2 = <T, K1 extends keyof T, K2 extends keyof T[K1]>(object: T, key1: K1, key2: K2) =>
(value: T[K1][K2]) => object[key1][key2] = value;

// Modified repro from #12573

declare function one<T>(handler: (t: T) => void): T
var empty = one(() => {}) // inferred as {}, expected

type Handlers<T> = { [K in keyof T]: (t: T[K]) => void }
declare function on<T>(handlerHash: Handlers<T>): T
var hashOfEmpty1 = on({ test: () => {} }); // {}
var hashOfEmpty2 = on({ test: (x: boolean) => {} }); // { test: boolean }

//// [keyofAndIndexedAccess.js]
var __extends = (this && this.__extends) || function (d, b) {
Expand Down Expand Up @@ -430,6 +471,31 @@ var OtherPerson = (function () {
};
return OtherPerson;
}());
function path(obj) {
var keys = [];
for (var _i = 1; _i < arguments.length; _i++) {
keys[_i - 1] = arguments[_i];
}
var result = obj;
for (var _a = 0, keys_1 = keys; _a < keys_1.length; _a++) {
var k = keys_1[_a];
result = result[k];
}
return result;
}
function f1(thing) {
var x1 = path(thing, 'a'); // { x: number, y: string }
var x2 = path(thing, 'a', 'y'); // string
var x3 = path(thing, 'b'); // boolean
var x4 = path.apply(void 0, [thing].concat(['a', 'x'])); // any
}
// Repro from comment in #12114
var assignTo2 = function (object, key1, key2) {
return function (value) { return object[key1][key2] = value; };
};
var empty = one(function () { }); // inferred as {}, expected
var hashOfEmpty1 = on({ test: function () { } }); // {}
var hashOfEmpty2 = on({ test: function (x) { } }); // { test: boolean }


//// [keyofAndIndexedAccess.d.ts]
Expand Down Expand Up @@ -551,3 +617,26 @@ declare class OtherPerson {
constructor(parts: number);
getParts(): number;
}
declare function path<T, K1 extends keyof T>(obj: T, key1: K1): T[K1];
declare function path<T, K1 extends keyof T, K2 extends keyof T[K1]>(obj: T, key1: K1, key2: K2): T[K1][K2];
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];
declare function path(obj: any, ...keys: (string | number)[]): any;
declare type Thing = {
a: {
x: number;
y: string;
};
b: boolean;
};
declare function f1(thing: Thing): void;
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];
declare function one<T>(handler: (t: T) => void): T;
declare var empty: {};
declare type Handlers<T> = {
[K in keyof T]: (t: T[K]) => void;
};
declare function on<T>(handlerHash: Handlers<T>): T;
declare var hashOfEmpty1: {};
declare var hashOfEmpty2: {
test: boolean;
};
Loading

0 comments on commit a230cb7

Please sign in to comment.