diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ffc8e88652..2686467453 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4278,7 +4278,7 @@ namespace ts { return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); } if (type.flags & TypeFlags.Substitution) { - return typeToTypeNodeHelper((type).typeVariable, context); + return typeToTypeNodeHelper((type).baseType, context); } return Debug.fail("Should be unreachable."); @@ -11327,9 +11327,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(res, node) : getRegularTypeOfLiteralType(res) : - errorType; + return checkNoTypeArguments(node, symbol) ? getRegularTypeOfLiteralType(res) : errorType; } if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { const jsdocType = getTypeFromJSDocValueReference(node, symbol); @@ -11377,17 +11375,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 = createType(TypeFlags.Substitution); - result.typeVariable = typeVariable; + result.baseType = baseType; result.substitute = substitute; substitutionTypes.set(id, result); return result; @@ -11397,25 +11395,25 @@ namespace ts { return node.kind === SyntaxKind.TupleType && (node).elementTypes.length === 1; } - function getImpliedConstraint(typeVariable: TypeVariable, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { - return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(typeVariable, (checkNode).elementTypes[0], (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, (checkNode).elementTypes[0], (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 === (parent).trueType) { - const constraint = getImpliedConstraint(typeVariable, (parent).checkType, (parent).extendsType); + const constraint = getImpliedConstraint(type, (parent).checkType, (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 { @@ -12858,7 +12856,7 @@ namespace ts { links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && (resolved).objectType === objectType && (resolved).indexType === indexType ? - getConstrainedTypeVariable(resolved, node) : resolved; + getConditionalFlowTypeOfType(resolved, node) : resolved; } return links.resolvedType; } @@ -12880,7 +12878,7 @@ namespace ts { function getActualTypeVariable(type: Type): Type { if (type.flags & TypeFlags.Substitution) { - return (type).typeVariable; + return (type).baseType; } if (type.flags & TypeFlags.IndexedAccess && ( (type).objectType.flags & TypeFlags.Substitution || @@ -13428,6 +13426,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: @@ -13775,7 +13777,7 @@ namespace ts { return !!tp.isThisType; case SyntaxKind.Identifier: return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) && - getTypeFromTypeNode(node) === tp; + getTypeFromTypeNodeWorker(node) === tp; // use worker because we're looking for === equality case SyntaxKind.TypeQuery: return true; } @@ -13976,7 +13978,7 @@ namespace ts { return getConditionalTypeInstantiation(type, combineTypeMappers((type).mapper, mapper)); } if (flags & TypeFlags.Substitution) { - const maybeVariable = instantiateType((type).typeVariable, mapper); + const maybeVariable = instantiateType((type).baseType, mapper); if (maybeVariable.flags & TypeFlags.TypeVariable) { return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((type).substitute, mapper)); } @@ -14985,7 +14987,7 @@ namespace ts { const t = isFreshLiteralType(type) ? (type).regularType : getObjectFlags(type) & ObjectFlags.Reference && (type).node ? createTypeReference((type).target, getTypeArguments(type)) : type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) : - type.flags & TypeFlags.Substitution ? writing ? (type).typeVariable : (type).substitute : + type.flags & TypeFlags.Substitution ? writing ? (type).baseType : (type).substitute : type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : type; if (t === type) break; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9ae1777851..6543d8904f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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 */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 12e0ed5f0d..39ba1ef93d 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2523,7 +2523,7 @@ declare namespace ts { resolvedFalseType: Type; } export interface SubstitutionType extends InstantiableType { - typeVariable: TypeVariable; + baseType: Type; substitute: Type; } export enum SignatureKind { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index afd99ad98f..84f57b074d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2523,7 +2523,7 @@ declare namespace ts { resolvedFalseType: Type; } export interface SubstitutionType extends InstantiableType { - typeVariable: TypeVariable; + baseType: Type; substitute: Type; } export enum SignatureKind { diff --git a/tests/baselines/reference/inlineConditionalHasSimilarAssignability.js b/tests/baselines/reference/inlineConditionalHasSimilarAssignability.js new file mode 100644 index 0000000000..a3a4fe0f34 --- /dev/null +++ b/tests/baselines/reference/inlineConditionalHasSimilarAssignability.js @@ -0,0 +1,29 @@ +//// [inlineConditionalHasSimilarAssignability.ts] +type MyExtract = T extends U ? T : never + +function foo(a: T) { + const b: Extract = 0 as any; + a = b; // ok + + const c: (any[] extends T ? any[] : never) = 0 as any; + a = c; + + const d: MyExtract = 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; +} diff --git a/tests/baselines/reference/inlineConditionalHasSimilarAssignability.symbols b/tests/baselines/reference/inlineConditionalHasSimilarAssignability.symbols new file mode 100644 index 0000000000..f7adae74a6 --- /dev/null +++ b/tests/baselines/reference/inlineConditionalHasSimilarAssignability.symbols @@ -0,0 +1,53 @@ +=== tests/cases/compiler/inlineConditionalHasSimilarAssignability.ts === +type MyExtract = 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(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 = 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 = 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)) +} diff --git a/tests/baselines/reference/inlineConditionalHasSimilarAssignability.types b/tests/baselines/reference/inlineConditionalHasSimilarAssignability.types new file mode 100644 index 0000000000..7c176da0fe --- /dev/null +++ b/tests/baselines/reference/inlineConditionalHasSimilarAssignability.types @@ -0,0 +1,51 @@ +=== tests/cases/compiler/inlineConditionalHasSimilarAssignability.ts === +type MyExtract = T extends U ? T : never +>MyExtract : MyExtract + +function foo(a: T) { +>foo : (a: T) => void +>a : T + + const b: Extract = 0 as any; +>b : Extract +>0 as any : any +>0 : 0 + + a = b; // ok +>a = b : Extract +>a : T +>b : Extract + + 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 = 0 as any; +>d : MyExtract +>0 as any : any +>0 : 0 + + a = d; // ok +>a = d : MyExtract +>a : T +>d : MyExtract + + 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 +} diff --git a/tests/cases/compiler/inlineConditionalHasSimilarAssignability.ts b/tests/cases/compiler/inlineConditionalHasSimilarAssignability.ts new file mode 100644 index 0000000000..9a22b9a2da --- /dev/null +++ b/tests/cases/compiler/inlineConditionalHasSimilarAssignability.ts @@ -0,0 +1,16 @@ +type MyExtract = T extends U ? T : never + +function foo(a: T) { + const b: Extract = 0 as any; + a = b; // ok + + const c: (any[] extends T ? any[] : never) = 0 as any; + a = c; + + const d: MyExtract = 0 as any; + a = d; // ok + + type CustomType = any[] extends T ? any[] : never; + const e: CustomType = 0 as any; + a = e; +} \ No newline at end of file