Skip to content

Make substitution types even if the substitution base isnt a type variable #37348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 22 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4275,7 +4275,7 @@ namespace ts {
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
}
if (type.flags & TypeFlags.Substitution) {
return typeToTypeNodeHelper((<SubstitutionType>type).typeVariable, context);
return typeToTypeNodeHelper((<SubstitutionType>type).baseType, context);
}

return Debug.fail("Should be unreachable.");
Expand Down Expand Up @@ -11323,9 +11323,7 @@ namespace ts {
// Get type from reference to named type that cannot be generic (enum or type parameter)
const res = tryGetDeclaredTypeOfSymbol(symbol);
if (res) {
return checkNoTypeArguments(node, symbol) ?
res.flags & TypeFlags.TypeParameter ? getConstrainedTypeVariable(<TypeParameter>res, node) : getRegularTypeOfLiteralType(res) :
errorType;
return checkNoTypeArguments(node, symbol) ? getRegularTypeOfLiteralType(res) : errorType;
}
if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) {
const jsdocType = getTypeFromJSDocValueReference(node, symbol);
Expand Down Expand Up @@ -11373,17 +11371,17 @@ namespace ts {
return links.resolvedJSDocType;
}

function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) {
if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === typeVariable) {
return typeVariable;
function getSubstitutionType(baseType: Type, substitute: Type) {
if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === baseType) {
return baseType;
}
const id = `${getTypeId(typeVariable)}>${getTypeId(substitute)}`;
const id = `${getTypeId(baseType)}>${getTypeId(substitute)}`;
const cached = substitutionTypes.get(id);
if (cached) {
return cached;
}
const result = <SubstitutionType>createType(TypeFlags.Substitution);
result.typeVariable = typeVariable;
result.baseType = baseType;
result.substitute = substitute;
substitutionTypes.set(id, result);
return result;
Expand All @@ -11393,25 +11391,25 @@ namespace ts {
return node.kind === SyntaxKind.TupleType && (<TupleTypeNode>node).elementTypes.length === 1;
}

function getImpliedConstraint(typeVariable: TypeVariable, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined {
return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(typeVariable, (<TupleTypeNode>checkNode).elementTypes[0], (<TupleTypeNode>extendsNode).elementTypes[0]) :
getActualTypeVariable(getTypeFromTypeNode(checkNode)) === typeVariable ? getTypeFromTypeNode(extendsNode) :
function getImpliedConstraint(type: Type, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined {
return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(type, (<TupleTypeNode>checkNode).elementTypes[0], (<TupleTypeNode>extendsNode).elementTypes[0]) :
getActualTypeVariable(getTypeFromTypeNode(checkNode)) === type ? getTypeFromTypeNode(extendsNode) :
undefined;
}

function getConstrainedTypeVariable(typeVariable: TypeVariable, node: Node) {
function getConditionalFlowTypeOfType(type: Type, node: Node) {
let constraints: Type[] | undefined;
while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) {
const parent = node.parent;
if (parent.kind === SyntaxKind.ConditionalType && node === (<ConditionalTypeNode>parent).trueType) {
const constraint = getImpliedConstraint(typeVariable, (<ConditionalTypeNode>parent).checkType, (<ConditionalTypeNode>parent).extendsType);
const constraint = getImpliedConstraint(type, (<ConditionalTypeNode>parent).checkType, (<ConditionalTypeNode>parent).extendsType);
if (constraint) {
constraints = append(constraints, constraint);
}
}
node = parent;
}
return constraints ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable))) : typeVariable;
return constraints ? getSubstitutionType(type, getIntersectionType(append(constraints, type))) : type;
}

function isJSDocTypeReference(node: Node): node is TypeReferenceNode {
Expand Down Expand Up @@ -12854,7 +12852,7 @@ namespace ts {
links.resolvedType = resolved.flags & TypeFlags.IndexedAccess &&
(<IndexedAccessType>resolved).objectType === objectType &&
(<IndexedAccessType>resolved).indexType === indexType ?
getConstrainedTypeVariable(<IndexedAccessType>resolved, node) : resolved;
getConditionalFlowTypeOfType(resolved, node) : resolved;
}
return links.resolvedType;
}
Expand All @@ -12876,7 +12874,7 @@ namespace ts {

function getActualTypeVariable(type: Type): Type {
if (type.flags & TypeFlags.Substitution) {
return (<SubstitutionType>type).typeVariable;
return (<SubstitutionType>type).baseType;
}
if (type.flags & TypeFlags.IndexedAccess && (
(<IndexedAccessType>type).objectType.flags & TypeFlags.Substitution ||
Expand Down Expand Up @@ -13424,6 +13422,10 @@ namespace ts {
}

function getTypeFromTypeNode(node: TypeNode): Type {
return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node);
}

function getTypeFromTypeNodeWorker(node: TypeNode): Type {
switch (node.kind) {
case SyntaxKind.AnyKeyword:
case SyntaxKind.JSDocAllType:
Expand Down Expand Up @@ -13758,7 +13760,7 @@ namespace ts {
return !!tp.isThisType;
case SyntaxKind.Identifier:
return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) &&
getTypeFromTypeNode(<TypeNode>node) === tp;
getTypeFromTypeNodeWorker(<TypeNode>node) === tp; // use worker because we're looking for === equality
case SyntaxKind.TypeQuery:
return true;
}
Expand Down Expand Up @@ -13959,7 +13961,7 @@ namespace ts {
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
}
if (flags & TypeFlags.Substitution) {
const maybeVariable = instantiateType((<SubstitutionType>type).typeVariable, mapper);
const maybeVariable = instantiateType((<SubstitutionType>type).baseType, mapper);
if (maybeVariable.flags & TypeFlags.TypeVariable) {
return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((<SubstitutionType>type).substitute, mapper));
}
Expand Down Expand Up @@ -14968,7 +14970,7 @@ namespace ts {
const t = isFreshLiteralType(type) ? (<FreshableType>type).regularType :
getObjectFlags(type) & ObjectFlags.Reference && (<TypeReference>type).node ? createTypeReference((<TypeReference>type).target, getTypeArguments(<TypeReference>type)) :
type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) :
type.flags & TypeFlags.Substitution ? writing ? (<SubstitutionType>type).typeVariable : (<SubstitutionType>type).substitute :
type.flags & TypeFlags.Substitution ? writing ? (<SubstitutionType>type).baseType : (<SubstitutionType>type).substitute :
type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) :
type;
if (t === type) break;
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4808,8 +4808,8 @@ namespace ts {
// 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 {
typeVariable: TypeVariable; // Target type variable
substitute: Type; // Type to substitute for type parameter
baseType: Type; // Target type
substitute: Type; // Type to substitute for type parameter
}

/* @internal */
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2523,7 +2523,7 @@ declare namespace ts {
resolvedFalseType: Type;
}
export interface SubstitutionType extends InstantiableType {
typeVariable: TypeVariable;
baseType: Type;
substitute: Type;
}
export enum SignatureKind {
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2523,7 +2523,7 @@ declare namespace ts {
resolvedFalseType: Type;
}
export interface SubstitutionType extends InstantiableType {
typeVariable: TypeVariable;
baseType: Type;
substitute: Type;
}
export enum SignatureKind {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// [inlineConditionalHasSimilarAssignability.ts]
type MyExtract<T, U> = T extends U ? T : never

function foo<T>(a: T) {
const b: Extract<any[], T> = 0 as any;
a = b; // ok

const c: (any[] extends T ? any[] : never) = 0 as any;
a = c;

const d: MyExtract<any[], T> = 0 as any;
a = d; // ok

type CustomType = any[] extends T ? any[] : never;
const e: CustomType = 0 as any;
a = e;
}

//// [inlineConditionalHasSimilarAssignability.js]
function foo(a) {
var b = 0;
a = b; // ok
var c = 0;
a = c;
var d = 0;
a = d; // ok
var e = 0;
a = e;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
=== tests/cases/compiler/inlineConditionalHasSimilarAssignability.ts ===
type MyExtract<T, U> = T extends U ? T : never
>MyExtract : Symbol(MyExtract, Decl(inlineConditionalHasSimilarAssignability.ts, 0, 0))
>T : Symbol(T, Decl(inlineConditionalHasSimilarAssignability.ts, 0, 15))
>U : Symbol(U, Decl(inlineConditionalHasSimilarAssignability.ts, 0, 17))
>T : Symbol(T, Decl(inlineConditionalHasSimilarAssignability.ts, 0, 15))
>U : Symbol(U, Decl(inlineConditionalHasSimilarAssignability.ts, 0, 17))
>T : Symbol(T, Decl(inlineConditionalHasSimilarAssignability.ts, 0, 15))

function foo<T>(a: T) {
>foo : Symbol(foo, Decl(inlineConditionalHasSimilarAssignability.ts, 0, 46))
>T : Symbol(T, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 13))
>a : Symbol(a, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 16))
>T : Symbol(T, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 13))

const b: Extract<any[], T> = 0 as any;
>b : Symbol(b, Decl(inlineConditionalHasSimilarAssignability.ts, 3, 7))
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 13))

a = b; // ok
>a : Symbol(a, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 16))
>b : Symbol(b, Decl(inlineConditionalHasSimilarAssignability.ts, 3, 7))

const c: (any[] extends T ? any[] : never) = 0 as any;
>c : Symbol(c, Decl(inlineConditionalHasSimilarAssignability.ts, 6, 7))
>T : Symbol(T, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 13))

a = c;
>a : Symbol(a, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 16))
>c : Symbol(c, Decl(inlineConditionalHasSimilarAssignability.ts, 6, 7))

const d: MyExtract<any[], T> = 0 as any;
>d : Symbol(d, Decl(inlineConditionalHasSimilarAssignability.ts, 9, 7))
>MyExtract : Symbol(MyExtract, Decl(inlineConditionalHasSimilarAssignability.ts, 0, 0))
>T : Symbol(T, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 13))

a = d; // ok
>a : Symbol(a, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 16))
>d : Symbol(d, Decl(inlineConditionalHasSimilarAssignability.ts, 9, 7))

type CustomType = any[] extends T ? any[] : never;
>CustomType : Symbol(CustomType, Decl(inlineConditionalHasSimilarAssignability.ts, 10, 8))
>T : Symbol(T, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 13))

const e: CustomType = 0 as any;
>e : Symbol(e, Decl(inlineConditionalHasSimilarAssignability.ts, 13, 7))
>CustomType : Symbol(CustomType, Decl(inlineConditionalHasSimilarAssignability.ts, 10, 8))

a = e;
>a : Symbol(a, Decl(inlineConditionalHasSimilarAssignability.ts, 2, 16))
>e : Symbol(e, Decl(inlineConditionalHasSimilarAssignability.ts, 13, 7))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
=== tests/cases/compiler/inlineConditionalHasSimilarAssignability.ts ===
type MyExtract<T, U> = T extends U ? T : never
>MyExtract : MyExtract<T, U>

function foo<T>(a: T) {
>foo : <T>(a: T) => void
>a : T

const b: Extract<any[], T> = 0 as any;
>b : Extract<any[], T>
>0 as any : any
>0 : 0

a = b; // ok
>a = b : Extract<any[], T>
>a : T
>b : Extract<any[], T>

const c: (any[] extends T ? any[] : never) = 0 as any;
>c : any[] extends T ? any[] : never
>0 as any : any
>0 : 0

a = c;
>a = c : any[] extends T ? any[] : never
>a : T
>c : any[] extends T ? any[] : never

const d: MyExtract<any[], T> = 0 as any;
>d : MyExtract<any[], T>
>0 as any : any
>0 : 0

a = d; // ok
>a = d : MyExtract<any[], T>
>a : T
>d : MyExtract<any[], T>

type CustomType = any[] extends T ? any[] : never;
>CustomType : any[] extends T ? any[] : never

const e: CustomType = 0 as any;
>e : any[] extends T ? any[] : never
>0 as any : any
>0 : 0

a = e;
>a = e : any[] extends T ? any[] : never
>a : T
>e : any[] extends T ? any[] : never
}
16 changes: 16 additions & 0 deletions tests/cases/compiler/inlineConditionalHasSimilarAssignability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
type MyExtract<T, U> = T extends U ? T : never

function foo<T>(a: T) {
const b: Extract<any[], T> = 0 as any;
a = b; // ok

const c: (any[] extends T ? any[] : never) = 0 as any;
a = c;

const d: MyExtract<any[], T> = 0 as any;
a = d; // ok

type CustomType = any[] extends T ? any[] : never;
const e: CustomType = 0 as any;
a = e;
}