Merge pull request #22707 from Microsoft/fixIndexedAccessInConditionalType

Fix indexed access in conditional type
This commit is contained in:
Anders Hejlsberg 2018-03-21 15:18:39 -07:00 committed by GitHub
commit e5f6ed0b60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 638 additions and 390 deletions

View File

@ -3040,7 +3040,7 @@ namespace ts {
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
}
if (type.flags & TypeFlags.Substitution) {
return typeToTypeNodeHelper((<SubstitutionType>type).typeParameter, context);
return typeToTypeNodeHelper((<SubstitutionType>type).typeVariable, context);
}
Debug.fail("Should be unreachable.");
@ -7305,7 +7305,7 @@ namespace ts {
const res = tryGetDeclaredTypeOfSymbol(symbol);
if (res) {
return checkNoTypeArguments(node, symbol) ?
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeParameter(<TypeParameter>res, node) : res :
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable(<TypeParameter>res, node) : res :
unknownType;
}
@ -7344,25 +7344,36 @@ namespace ts {
}
}
function getSubstitutionType(typeParameter: TypeParameter, substitute: Type) {
function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) {
const result = <SubstitutionType>createType(TypeFlags.Substitution);
result.typeParameter = typeParameter;
result.typeVariable = typeVariable;
result.substitute = substitute;
return result;
}
function getConstrainedTypeParameter(typeParameter: TypeParameter, node: Node) {
function isUnaryTupleTypeNode(node: TypeNode) {
return node.kind === SyntaxKind.TupleType && (<TupleTypeNode>node).elementTypes.length === 1;
}
function getImpliedConstraint(typeVariable: TypeVariable, checkNode: TypeNode, extendsNode: TypeNode): Type {
return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(typeVariable, (<TupleTypeNode>checkNode).elementTypes[0], (<TupleTypeNode>extendsNode).elementTypes[0]) :
getActualTypeVariable(getTypeFromTypeNode(checkNode)) === typeVariable ? getTypeFromTypeNode(extendsNode) :
undefined;
}
function getConstrainedTypeVariable(typeVariable: TypeVariable, node: Node) {
let constraints: Type[];
while (isPartOfTypeNode(node)) {
const parent = node.parent;
if (parent.kind === SyntaxKind.ConditionalType && node === (<ConditionalTypeNode>parent).trueType) {
if (getTypeFromTypeNode((<ConditionalTypeNode>parent).checkType) === typeParameter) {
constraints = append(constraints, getTypeFromTypeNode((<ConditionalTypeNode>parent).extendsType));
const constraint = getImpliedConstraint(typeVariable, (<ConditionalTypeNode>parent).checkType, (<ConditionalTypeNode>parent).extendsType);
if (constraint) {
constraints = append(constraints, constraint);
}
}
node = parent;
}
return constraints ? getSubstitutionType(typeParameter, getIntersectionType(append(constraints, typeParameter))) : typeParameter;
return constraints ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable))) : typeVariable;
}
function isJSDocTypeReference(node: TypeReferenceType): node is TypeReferenceNode {
@ -8258,7 +8269,13 @@ namespace ts {
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getIndexedAccessType(getTypeFromTypeNode(node.objectType), getTypeFromTypeNode(node.indexType), node);
const objectType = getTypeFromTypeNode(node.objectType);
const indexType = getTypeFromTypeNode(node.indexType);
const resolved = getIndexedAccessType(objectType, indexType, node);
links.resolvedType = resolved.flags & TypeFlags.IndexedAccess &&
(<IndexedAccessType>resolved).objectType === objectType &&
(<IndexedAccessType>resolved).indexType === indexType ?
getConstrainedTypeVariable(<IndexedAccessType>resolved, node) : resolved;
}
return links.resolvedType;
}
@ -8278,8 +8295,8 @@ namespace ts {
return links.resolvedType;
}
function getActualTypeParameter(type: Type) {
return type.flags & TypeFlags.Substitution ? (<SubstitutionType>type).typeParameter : type;
function getActualTypeVariable(type: Type) {
return type.flags & TypeFlags.Substitution ? (<SubstitutionType>type).typeVariable : type;
}
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper): Type {
@ -8323,7 +8340,7 @@ namespace ts {
}
}
// Return a deferred type for a check that is neither definitely true nor definitely false
const erasedCheckType = getActualTypeParameter(checkType);
const erasedCheckType = getActualTypeVariable(checkType);
const result = <ConditionalType>createType(TypeFlags.Conditional);
result.root = root;
result.checkType = erasedCheckType;
@ -9043,7 +9060,7 @@ namespace ts {
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
}
if (type.flags & TypeFlags.Substitution) {
return mapper((<SubstitutionType>type).typeParameter);
return instantiateType((<SubstitutionType>type).typeVariable, mapper);
}
}
return type;
@ -9650,10 +9667,10 @@ namespace ts {
target = (<LiteralType>target).regularType;
}
if (source.flags & TypeFlags.Substitution) {
source = relation === definitelyAssignableRelation ? (<SubstitutionType>source).typeParameter : (<SubstitutionType>source).substitute;
source = relation === definitelyAssignableRelation ? (<SubstitutionType>source).typeVariable : (<SubstitutionType>source).substitute;
}
if (target.flags & TypeFlags.Substitution) {
target = (<SubstitutionType>target).typeParameter;
target = (<SubstitutionType>target).typeVariable;
}
// both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases

View File

@ -3845,6 +3845,8 @@ namespace ts {
constraint?: Type;
}
export type TypeVariable = TypeParameter | IndexedAccessType;
// keyof T types (TypeFlags.Index)
export interface IndexType extends InstantiableType {
type: InstantiableType | UnionOrIntersectionType;
@ -3876,14 +3878,14 @@ namespace ts {
}
// Type parameter substitution (TypeFlags.Substitution)
// Substitution types are created for type parameter references that occur in the true branch
// of a conditional type. For example, in 'T extends string ? Foo<T> : Bar<T>', the reference to
// T in Foo<T> is resolved as a substitution type that substitutes 'string & T' for T. Thus, if
// Foo has a 'string' constraint on its type parameter, T will satisfy it. Substitution types
// disappear upon instantiation (just like type parameters).
// Substitution types are created for type parameters or indexed access types that occur in the
// true branch of a conditional type. For example, in 'T extends string ? Foo<T> : Bar<T>', the
// reference to T in Foo<T> is resolved as a substitution type that substitutes 'string & T' for T.
// Thus, if Foo has a 'string' constraint on its type parameter, T will satisfy it. Substitution
// types disappear upon instantiation (just like type parameters).
export interface SubstitutionType extends InstantiableType {
typeParameter: TypeParameter; // Target type parameter
substitute: Type; // Type to substitute for type parameter
typeVariable: TypeVariable; // Target type variable
substitute: Type; // Type to substitute for type parameter
}
export const enum SignatureKind {

View File

@ -2192,6 +2192,7 @@ declare namespace ts {
indexType: Type;
constraint?: Type;
}
type TypeVariable = TypeParameter | IndexedAccessType;
interface IndexType extends InstantiableType {
type: InstantiableType | UnionOrIntersectionType;
}
@ -2216,7 +2217,7 @@ declare namespace ts {
resolvedFalseType?: Type;
}
interface SubstitutionType extends InstantiableType {
typeParameter: TypeParameter;
typeVariable: TypeVariable;
substitute: Type;
}
enum SignatureKind {

View File

@ -2192,6 +2192,7 @@ declare namespace ts {
indexType: Type;
constraint?: Type;
}
type TypeVariable = TypeParameter | IndexedAccessType;
interface IndexType extends InstantiableType {
type: InstantiableType | UnionOrIntersectionType;
}
@ -2216,7 +2217,7 @@ declare namespace ts {
resolvedFalseType?: Type;
}
interface SubstitutionType extends InstantiableType {
typeParameter: TypeParameter;
typeVariable: TypeVariable;
substitute: Type;
}
enum SignatureKind {

View File

@ -64,8 +64,8 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(159,5): error TS2
tests/cases/conformance/types/conditional/conditionalTypes1.ts(160,5): error TS2322: Type 'T' is not assignable to type 'ZeroOf<T>'.
Type 'string | number' is not assignable to type 'ZeroOf<T>'.
Type 'string' is not assignable to type 'ZeroOf<T>'.
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>'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(275,43): error TS2322: Type 'T95<U>' is not assignable to type 'T94<U>'.
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>'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(280,43): error TS2322: Type 'T95<U>' is not assignable to type 'T94<U>'.
Type 'boolean' is not assignable to type 'true'.
@ -318,6 +318,11 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(275,43): error TS
!!! error TS2322: Type 'string' is not assignable to type 'ZeroOf<T>'.
}
type T35<T extends { a: string, b: number }> = T[];
type T36<T> = T extends { a: string } ? T extends { b: number } ? T35<T> : never : never;
type T37<T> = T extends { b: number } ? T extends { a: string } ? T35<T> : never : never;
type T38<T> = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35<T> : never : never;
type Extends<T, U> = T extends U ? true : false;
type If<C extends boolean, T, F> = C extends true ? T : F;
type Not<C extends boolean> = If<C, false, true>;
@ -477,4 +482,15 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(275,43): error TS
type Test1 = NonFooKeys1<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
type Test2 = NonFooKeys2<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
// Repro from #21729
interface Foo2 { foo: string; }
interface Bar2 { bar: string; }
type FooBar = Foo2 | Bar2;
declare interface ExtractFooBar<FB extends FooBar> { }
type Extracted<Struct> = {
[K in keyof Struct]: Struct[K] extends FooBar ? ExtractFooBar<Struct[K]> : Struct[K];
}

View File

@ -161,6 +161,11 @@ function f21<T extends number | string>(x: T, y: ZeroOf<T>) {
y = x; // Error
}
type T35<T extends { a: string, b: number }> = T[];
type T36<T> = T extends { a: string } ? T extends { b: number } ? T35<T> : never : never;
type T37<T> = T extends { b: number } ? T extends { a: string } ? T35<T> : never : never;
type T38<T> = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35<T> : never : never;
type Extends<T, U> = T extends U ? true : false;
type If<C extends boolean, T, F> = C extends true ? T : F;
type Not<C extends boolean> = If<C, false, true>;
@ -315,6 +320,17 @@ type NonFooKeys2<T extends object> = Exclude<keyof T, 'foo'>;
type Test1 = NonFooKeys1<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
type Test2 = NonFooKeys2<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
// Repro from #21729
interface Foo2 { foo: string; }
interface Bar2 { bar: string; }
type FooBar = Foo2 | Bar2;
declare interface ExtractFooBar<FB extends FooBar> { }
type Extracted<Struct> = {
[K in keyof Struct]: Struct[K] extends FooBar ? ExtractFooBar<Struct[K]> : Struct[K];
}
//// [conditionalTypes1.js]
@ -517,6 +533,25 @@ declare type ZeroOf<T extends number | string | boolean> = T extends number ? 0
declare function zeroOf<T extends number | string | boolean>(value: T): ZeroOf<T>;
declare function f20<T extends string>(n: number, b: boolean, x: number | boolean, y: T): void;
declare function f21<T extends number | string>(x: T, y: ZeroOf<T>): void;
declare type T35<T extends {
a: string;
b: number;
}> = T[];
declare type T36<T> = T extends {
a: string;
} ? T extends {
b: number;
} ? T35<T> : never : never;
declare type T37<T> = T extends {
b: number;
} ? T extends {
a: string;
} ? T35<T> : never : never;
declare type T38<T> = [T] extends [{
a: string;
}] ? [T] extends [{
b: number;
}] ? T35<T> : never : never;
declare type Extends<T, U> = T extends U ? true : false;
declare type If<C extends boolean, T, F> = C extends true ? T : F;
declare type Not<C extends boolean> = If<C, false, true>;
@ -624,3 +659,15 @@ declare type Test2 = NonFooKeys2<{
bar: 2;
baz: 3;
}>;
interface Foo2 {
foo: string;
}
interface Bar2 {
bar: string;
}
declare type FooBar = Foo2 | Bar2;
declare interface ExtractFooBar<FB extends FooBar> {
}
declare type Extracted<Struct> = {
[K in keyof Struct]: Struct[K] extends FooBar ? ExtractFooBar<Struct[K]> : Struct[K];
};

File diff suppressed because it is too large Load Diff

View File

@ -678,6 +678,43 @@ function f21<T extends number | string>(x: T, y: ZeroOf<T>) {
>x : T
}
type T35<T extends { a: string, b: number }> = T[];
>T35 : T[]
>T : T
>a : string
>b : number
>T : T
type T36<T> = T extends { a: string } ? T extends { b: number } ? T35<T> : never : never;
>T36 : T36<T>
>T : T
>T : T
>a : string
>T : T
>b : number
>T35 : T[]
>T : T
type T37<T> = T extends { b: number } ? T extends { a: string } ? T35<T> : never : never;
>T37 : T37<T>
>T : T
>T : T
>b : number
>T : T
>a : string
>T35 : T[]
>T : T
type T38<T> = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35<T> : never : never;
>T38 : T38<T>
>T : T
>T : T
>a : string
>T : T
>b : number
>T35 : T[]
>T : T
type Extends<T, U> = T extends U ? true : false;
>Extends : Extends<T, U>
>T : T
@ -1370,3 +1407,40 @@ type Test2 = NonFooKeys2<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
>bar : 2
>baz : 3
// Repro from #21729
interface Foo2 { foo: string; }
>Foo2 : Foo2
>foo : string
interface Bar2 { bar: string; }
>Bar2 : Bar2
>bar : string
type FooBar = Foo2 | Bar2;
>FooBar : FooBar
>Foo2 : Foo2
>Bar2 : Bar2
declare interface ExtractFooBar<FB extends FooBar> { }
>ExtractFooBar : ExtractFooBar<FB>
>FB : FB
>FooBar : FooBar
type Extracted<Struct> = {
>Extracted : Extracted<Struct>
>Struct : Struct
[K in keyof Struct]: Struct[K] extends FooBar ? ExtractFooBar<Struct[K]> : Struct[K];
>K : K
>Struct : Struct
>Struct : Struct
>K : K
>FooBar : FooBar
>ExtractFooBar : ExtractFooBar<FB>
>Struct : Struct
>K : K
>Struct : Struct
>K : K
}

View File

@ -163,6 +163,11 @@ function f21<T extends number | string>(x: T, y: ZeroOf<T>) {
y = x; // Error
}
type T35<T extends { a: string, b: number }> = T[];
type T36<T> = T extends { a: string } ? T extends { b: number } ? T35<T> : never : never;
type T37<T> = T extends { b: number } ? T extends { a: string } ? T35<T> : never : never;
type T38<T> = [T] extends [{ a: string }] ? [T] extends [{ b: number }] ? T35<T> : never : never;
type Extends<T, U> = T extends U ? true : false;
type If<C extends boolean, T, F> = C extends true ? T : F;
type Not<C extends boolean> = If<C, false, true>;
@ -317,3 +322,14 @@ type NonFooKeys2<T extends object> = Exclude<keyof T, 'foo'>;
type Test1 = NonFooKeys1<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
type Test2 = NonFooKeys2<{foo: 1, bar: 2, baz: 3}>; // "bar" | "baz"
// Repro from #21729
interface Foo2 { foo: string; }
interface Bar2 { bar: string; }
type FooBar = Foo2 | Bar2;
declare interface ExtractFooBar<FB extends FooBar> { }
type Extracted<Struct> = {
[K in keyof Struct]: Struct[K] extends FooBar ? ExtractFooBar<Struct[K]> : Struct[K];
}