Skip to content

Commit

Permalink
Merge pull request #31141 from Microsoft/fixInferenceToIndexedAccessW…
Browse files Browse the repository at this point in the history
…ithSubstitution

Fix inference to indexed access type containing substitution type
  • Loading branch information
ahejlsberg committed May 1, 2019
2 parents d1646c7 + 1818218 commit 9509a54
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 5 deletions.
18 changes: 13 additions & 5 deletions src/compiler/checker.ts
Expand Up @@ -10349,8 +10349,16 @@ namespace ts {
return links.resolvedType;
}

function getActualTypeVariable(type: Type) {
return type.flags & TypeFlags.Substitution ? (<SubstitutionType>type).typeVariable : type;
function getActualTypeVariable(type: Type): Type {
if (type.flags & TypeFlags.Substitution) {
return (<SubstitutionType>type).typeVariable;
}
if (type.flags & TypeFlags.IndexedAccess && (
(<IndexedAccessType>type).objectType.flags & TypeFlags.Substitution ||
(<IndexedAccessType>type).indexType.flags & TypeFlags.Substitution)) {
return getIndexedAccessType(getActualTypeVariable((<IndexedAccessType>type).objectType), getActualTypeVariable((<IndexedAccessType>type).indexType));
}
return type;
}

/**
Expand Down Expand Up @@ -15030,6 +15038,9 @@ namespace ts {
target = removeTypesFromUnionOrIntersection(<UnionOrIntersectionType>target, matchingTypes);
}
}
else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
target = getActualTypeVariable(target);
}
if (target.flags & TypeFlags.TypeVariable) {
// If target is a type parameter, make an inference, unless the source type contains
// the anyFunctionType (the wildcard type that's used to avoid contextually typing functions).
Expand Down Expand Up @@ -15091,9 +15102,6 @@ namespace ts {
}
}
}
else if (target.flags & TypeFlags.Substitution) {
inferFromTypes(source, (target as SubstitutionType).typeVariable);
}
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
// If source and target are references to the same generic type, infer from type arguments
const sourceTypes = (<TypeReference>source).typeArguments || emptyArray;
Expand Down
30 changes: 30 additions & 0 deletions tests/baselines/reference/substitutionTypesInIndexedAccessTypes.js
@@ -0,0 +1,30 @@
//// [substitutionTypesInIndexedAccessTypes.ts]
// Repro from #31086

type UserArgs = {
select?: boolean
};

type Subset<T, U> = { [key in keyof T]: key extends keyof U ? T[key] : never };

declare function withBoundary<T extends UserArgs>(args?: Subset<T, UserArgs>): T;
declare function withoutBoundary<T extends UserArgs>(args?: T): T;

const boundaryResult = withBoundary({
select: true,
});

const withoutBoundaryResult = withoutBoundary({
select: true,
});


//// [substitutionTypesInIndexedAccessTypes.js]
"use strict";
// Repro from #31086
var boundaryResult = withBoundary({
select: true
});
var withoutBoundaryResult = withoutBoundary({
select: true
});
@@ -0,0 +1,58 @@
=== tests/cases/compiler/substitutionTypesInIndexedAccessTypes.ts ===
// Repro from #31086

type UserArgs = {
>UserArgs : Symbol(UserArgs, Decl(substitutionTypesInIndexedAccessTypes.ts, 0, 0))

select?: boolean
>select : Symbol(select, Decl(substitutionTypesInIndexedAccessTypes.ts, 2, 17))

};

type Subset<T, U> = { [key in keyof T]: key extends keyof U ? T[key] : never };
>Subset : Symbol(Subset, Decl(substitutionTypesInIndexedAccessTypes.ts, 4, 2))
>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 12))
>U : Symbol(U, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 14))
>key : Symbol(key, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 23))
>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 12))
>key : Symbol(key, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 23))
>U : Symbol(U, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 14))
>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 12))
>key : Symbol(key, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 23))

declare function withBoundary<T extends UserArgs>(args?: Subset<T, UserArgs>): T;
>withBoundary : Symbol(withBoundary, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 79))
>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 30))
>UserArgs : Symbol(UserArgs, Decl(substitutionTypesInIndexedAccessTypes.ts, 0, 0))
>args : Symbol(args, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 50))
>Subset : Symbol(Subset, Decl(substitutionTypesInIndexedAccessTypes.ts, 4, 2))
>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 30))
>UserArgs : Symbol(UserArgs, Decl(substitutionTypesInIndexedAccessTypes.ts, 0, 0))
>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 30))

declare function withoutBoundary<T extends UserArgs>(args?: T): T;
>withoutBoundary : Symbol(withoutBoundary, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 81))
>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 9, 33))
>UserArgs : Symbol(UserArgs, Decl(substitutionTypesInIndexedAccessTypes.ts, 0, 0))
>args : Symbol(args, Decl(substitutionTypesInIndexedAccessTypes.ts, 9, 53))
>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 9, 33))
>T : Symbol(T, Decl(substitutionTypesInIndexedAccessTypes.ts, 9, 33))

const boundaryResult = withBoundary({
>boundaryResult : Symbol(boundaryResult, Decl(substitutionTypesInIndexedAccessTypes.ts, 11, 5))
>withBoundary : Symbol(withBoundary, Decl(substitutionTypesInIndexedAccessTypes.ts, 6, 79))

select: true,
>select : Symbol(select, Decl(substitutionTypesInIndexedAccessTypes.ts, 11, 37))

});

const withoutBoundaryResult = withoutBoundary({
>withoutBoundaryResult : Symbol(withoutBoundaryResult, Decl(substitutionTypesInIndexedAccessTypes.ts, 15, 5))
>withoutBoundary : Symbol(withoutBoundary, Decl(substitutionTypesInIndexedAccessTypes.ts, 8, 81))

select: true,
>select : Symbol(select, Decl(substitutionTypesInIndexedAccessTypes.ts, 15, 47))

});

@@ -0,0 +1,46 @@
=== tests/cases/compiler/substitutionTypesInIndexedAccessTypes.ts ===
// Repro from #31086

type UserArgs = {
>UserArgs : UserArgs

select?: boolean
>select : boolean | undefined

};

type Subset<T, U> = { [key in keyof T]: key extends keyof U ? T[key] : never };
>Subset : Subset<T, U>

declare function withBoundary<T extends UserArgs>(args?: Subset<T, UserArgs>): T;
>withBoundary : <T extends UserArgs>(args?: Subset<T, UserArgs> | undefined) => T
>args : Subset<T, UserArgs> | undefined

declare function withoutBoundary<T extends UserArgs>(args?: T): T;
>withoutBoundary : <T extends UserArgs>(args?: T | undefined) => T
>args : T | undefined

const boundaryResult = withBoundary({
>boundaryResult : { select: true; }
>withBoundary({ select: true,}) : { select: true; }
>withBoundary : <T extends UserArgs>(args?: Subset<T, UserArgs> | undefined) => T
>{ select: true,} : { select: true; }

select: true,
>select : true
>true : true

});

const withoutBoundaryResult = withoutBoundary({
>withoutBoundaryResult : { select: true; }
>withoutBoundary({ select: true,}) : { select: true; }
>withoutBoundary : <T extends UserArgs>(args?: T | undefined) => T
>{ select: true,} : { select: true; }

select: true,
>select : true
>true : true

});

20 changes: 20 additions & 0 deletions tests/cases/compiler/substitutionTypesInIndexedAccessTypes.ts
@@ -0,0 +1,20 @@
// @strict: true

// Repro from #31086

type UserArgs = {
select?: boolean
};

type Subset<T, U> = { [key in keyof T]: key extends keyof U ? T[key] : never };

declare function withBoundary<T extends UserArgs>(args?: Subset<T, UserArgs>): T;
declare function withoutBoundary<T extends UserArgs>(args?: T): T;

const boundaryResult = withBoundary({
select: true,
});

const withoutBoundaryResult = withoutBoundary({
select: true,
});

0 comments on commit 9509a54

Please sign in to comment.