rework non-primitive restriction to be based on conditional type branches

This commit is contained in:
Gabriela Araujo Britto 2025-03-05 10:41:51 -08:00
parent ab0d43c8e5
commit a7c683903d
5 changed files with 939 additions and 30 deletions

View File

@ -20423,7 +20423,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const checkType = root.checkType;
let distributionType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined;
let narrowingBaseType: Type | undefined;
const forNarrowing = distributionType && isNarrowingSubstitutionType(distributionType) && isNarrowableConditionalType(type, mapper);
const forNarrowing = distributionType &&
isNarrowingSubstitutionType(distributionType) &&
isNarrowableConditionalType(type, /*hadNonPrimitiveExtendsType*/ [], mapper);
if (forNarrowing) {
narrowingBaseType = (distributionType as SubstitutionType).baseType;
distributionType = getReducedType((distributionType as SubstitutionType).constraint);
@ -45985,14 +45987,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
type NarrowableReference = Identifier | ElementAccessExpression | PropertyAccessExpression;
/**
* Narrowable type parameters are type parameters that:
* (1) have a narrowable constraint;
* (1) have a union constraint;
* (2) are syntactically used as the type of a single parameter in the function, and nothing else
*/
function getNarrowableTypeParameters(candidates: TypeParameter[]): [TypeParameter, Symbol, NarrowableReference][] {
const narrowableParams: [TypeParameter, Symbol, NarrowableReference][] = [];
for (const typeParam of candidates) {
const constraint = getConstraintOfTypeParameter(typeParam);
if (!constraint || !isNarrowableTypeParameterConstraint(constraint)) continue;
if (!constraint || !(constraint.flags & TypeFlags.Union)) continue;
if (typeParam.symbol && typeParam.symbol.declarations && typeParam.symbol.declarations.length === 1) {
const declaration = typeParam.symbol.declarations[0];
const container = isJSDocTemplateTag(declaration.parent) ? getJSDocHost(declaration.parent) : declaration.parent;
@ -46068,11 +46070,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const typeArgs = (typeNode as TypeReferenceNode).typeArguments;
// Type arguments that reference `T`
const typeArgsReferencingT = typeArgs?.filter(node => isTypeParameterReferenced(typeParam, node));
if (!typeArgsReferencingT || typeArgsReferencingT.length == 0) return true; // Type reference unrelated to `T`
if (!typeArgsReferencingT || typeArgsReferencingT.length === 0) return true; // Type reference unrelated to `T`
if (typeArgsReferencingT && typeArgsReferencingT.length > 1) return false; // e.g. `Foo<T, T, ...>`
const typeArg = typeArgsReferencingT[0];
if (!(typeArg.kind & SyntaxKind.TypeReference)) return false; // e.g. `Foo<Wrapper<T>, ...>`
if (!type.symbol || !type.symbol.declarations || type.symbol.declarations.length != 1) return false;
if (!type.symbol || !type.symbol.declarations || type.symbol.declarations.length !== 1) return false;
const typeDeclaration = type.symbol.declarations[0];
let aliasDeclaration;
if (isTypeLiteralNode(typeDeclaration)) {
@ -46243,30 +46245,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
/**
* Determines if the type parameter constraint allows for narrowing of that type parameter.
* This is true if:
* (1) the constraint is a union type;
* (2) there's at most one non-primitive type in the union.
*/
function isNarrowableTypeParameterConstraint(constraint: Type): boolean {
if (!(constraint.flags & TypeFlags.Union)) return false;
let nonPrimitives = 0;
for (const type of (constraint as UnionType).types) {
if (!(type.flags & TypeFlags.Primitive)) {
nonPrimitives += 1;
}
}
return nonPrimitives <= 1;
}
function isNarrowableReturnType(returnType: IndexedAccessType | ConditionalType): boolean {
return isConditionalType(returnType)
? isNarrowableConditionalType(returnType)
? isNarrowableConditionalType(returnType, /*hadNonPrimitiveExtendsType*/ [])
: !!(returnType.indexType.flags & TypeFlags.TypeParameter);
}
function isNarrowableConditionalType(type: ConditionalType, mapper?: TypeMapper): boolean {
function isNarrowableConditionalType(type: ConditionalType, hadNonPrimitiveExtendsType: TypeParameter[], mapper?: TypeMapper): boolean {
const typeArguments = mapper && map(type.root.outerTypeParameters, t => {
const mapped = getMappedType(t, mapper);
if (isNarrowingSubstitutionType(mapped)) {
@ -46274,14 +46259,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return mapped;
});
const id = `${type.id}:${getTypeListId(typeArguments)}`;
const id = `${type.id}:${getTypeListId(typeArguments)}:${getTypeListId(hadNonPrimitiveExtendsType)}`;
let result = narrowableReturnTypeCache.get(id);
if (result === undefined) {
const nonNarrowingMapper = type.root.outerTypeParameters
&& typeArguments
&& createTypeMapper(type.root.outerTypeParameters, typeArguments);
const instantiatedType = instantiateType(type, nonNarrowingMapper);
result = isConditionalType(instantiatedType) && isNarrowableConditionalTypeWorker(instantiatedType);
result = isConditionalType(instantiatedType) &&
isNarrowableConditionalTypeWorker(instantiatedType, hadNonPrimitiveExtendsType);
narrowableReturnTypeCache.set(id, result);
}
return result;
@ -46293,9 +46279,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// (1) The conditional type has no `infer` type parameters;
// (2) The conditional type's check type is a narrowable type parameter (i.e. a type parameter with a union constraint);
// (3) The extends type `A` is a type or a union of types that are supertypes of the union constraint of the type parameter;
// (4) `TrueBranch<T>` and `FalseBranch<T>` must be valid, recursively.
// (4) At most one extends type has a non-primitive type.
// (5) `TrueBranch<T>` and `FalseBranch<T>` must be valid, recursively.
// In particular, the false-most branch of the conditional type must be `never`.
function isNarrowableConditionalTypeWorker(type: ConditionalType): boolean {
function isNarrowableConditionalTypeWorker(type: ConditionalType, hadNonPrimitiveExtendsType: TypeParameter[]): boolean {
// (0)
if (!type.root.isDistributive) {
return false;
@ -46328,14 +46315,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// (4)
const hasNonPrimitive = someType(type.extendsType, type => (type.flags & TypeFlags.Primitive) === 0);
if (hasNonPrimitive && hadNonPrimitiveExtendsType.includes(type.checkType)) {
return false;
}
if (hasNonPrimitive) {
hadNonPrimitiveExtendsType = hadNonPrimitiveExtendsType.slice();
hadNonPrimitiveExtendsType.push(type.checkType);
}
// (5)
const trueType = getTrueTypeFromConditionalType(type);
const isValidTrueType = isConditionalType(trueType)
? isNarrowableConditionalType(trueType)
? isNarrowableConditionalType(trueType, hadNonPrimitiveExtendsType)
: true;
if (!isValidTrueType) return false;
const falseType = getFalseTypeFromConditionalType(type);
const isValidFalseType = isConditionalType(falseType)
? isNarrowableConditionalType(falseType)
? isNarrowableConditionalType(falseType, hadNonPrimitiveExtendsType)
: falseType === neverType;
return isValidFalseType;
}

View File

@ -0,0 +1,145 @@
dependentReturnType13.ts(29,9): error TS2322: Type '2' is not assignable to type 'T extends string ? 1 : T extends string[] ? 2 : T extends number[] ? 3 : never'.
dependentReturnType13.ts(31,5): error TS2322: Type '1' is not assignable to type 'T extends string ? 1 : T extends string[] ? 2 : T extends number[] ? 3 : never'.
dependentReturnType13.ts(49,9): error TS2322: Type '2' is not assignable to type 'T extends Cat ? 1 : T extends Dog ? 2 : never'.
dependentReturnType13.ts(52,5): error TS2322: Type '1' is not assignable to type 'T extends Cat ? 1 : T extends Dog ? 2 : never'.
dependentReturnType13.ts(79,9): error TS2322: Type '2' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
dependentReturnType13.ts(83,13): error TS2322: Type '3' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
dependentReturnType13.ts(85,9): error TS2322: Type '1' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
dependentReturnType13.ts(88,9): error TS2322: Type '4' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
dependentReturnType13.ts(90,5): error TS2322: Type '5' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
==== dependentReturnType13.ts (9 errors) ====
// Restrictions on what kind of union types can be narrowed.
function f1<T extends string | string[]>(param: T):
T extends string ? 1 :
T extends string[] ? 2 :
never {
if (Array.isArray(param)) {
return 2;
}
return 1;
}
function f2<T extends string | string[] | number[]>(param: T):
T extends string ? 1 :
T extends string[] | number[] ? 2 :
never {
if (Array.isArray(param)) {
return 2;
}
return 1;
}
function f3<T extends string | string[] | number[]>(param: T): // Bad.
T extends string ? 1 :
T extends string[] ? 2 :
T extends number[] ? 3 :
never {
if (Array.isArray(param)) {
return 2;
~~~~~~
!!! error TS2322: Type '2' is not assignable to type 'T extends string ? 1 : T extends string[] ? 2 : T extends number[] ? 3 : never'.
}
return 1;
~~~~~~
!!! error TS2322: Type '1' is not assignable to type 'T extends string ? 1 : T extends string[] ? 2 : T extends number[] ? 3 : never'.
}
class Dog {
bark(): void {}
}
class Cat {
meow(): void {}
}
function f4<T extends Cat | Dog>(param: T): // Bad.
T extends Cat ? 1 :
T extends Dog ? 2 :
never {
if ('bark' in param) {
const _: Dog = param;
return 2;
~~~~~~
!!! error TS2322: Type '2' is not assignable to type 'T extends Cat ? 1 : T extends Dog ? 2 : never'.
}
const _: Cat = param;
return 1;
~~~~~~
!!! error TS2322: Type '1' is not assignable to type 'T extends Cat ? 1 : T extends Dog ? 2 : never'.
}
function f5<T extends string | number | string[]>(param: T):
T extends string ? 1 :
T extends number | string[] ? 2 :
never {
if (Array.isArray(param) || typeof param === "number") {
const _: string[] | number = param;
return 2;
}
const _: string = param;
return 1;
}
function f6<T extends string | number[] | string[], U extends boolean>(param: T, other: U):
T extends number[] ? 2 :
U extends true ?
T extends string[] ? 3 :
T extends string ? 1 :
never :
U extends false ?
T extends string[] ? 4 :
T extends string ? 5 :
never :
never {
if (isNumberArray(param)) {
return 2;
~~~~~~
!!! error TS2322: Type '2' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
}
if (other) {
if (Array.isArray(param)) {
return 3;
~~~~~~
!!! error TS2322: Type '3' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
}
return 1;
~~~~~~
!!! error TS2322: Type '1' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
}
if (Array.isArray(param)) {
return 4;
~~~~~~
!!! error TS2322: Type '4' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
}
return 5;
~~~~~~
!!! error TS2322: Type '5' is not assignable to type 'T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never'.
}
declare function isNumberArray(x: unknown): x is number[];
function f7<T extends string | string[], U extends number | number[]>(param: T, other: U):
U extends number ?
T extends string[] ? 2 :
T extends string ? 1 :
never :
U extends number[] ?
T extends string[] ? 4 :
T extends string ? 3 :
never :
never {
if (Array.isArray(other)) {
if (Array.isArray(param)) {
return 4;
}
return 3;
}
if (Array.isArray(param)) {
return 2;
}
return 1;
}

View File

@ -0,0 +1,287 @@
//// [tests/cases/compiler/dependentReturnType13.ts] ////
=== dependentReturnType13.ts ===
// Restrictions on what kind of union types can be narrowed.
function f1<T extends string | string[]>(param: T):
>f1 : Symbol(f1, Decl(dependentReturnType13.ts, 0, 0))
>T : Symbol(T, Decl(dependentReturnType13.ts, 2, 12))
>param : Symbol(param, Decl(dependentReturnType13.ts, 2, 41))
>T : Symbol(T, Decl(dependentReturnType13.ts, 2, 12))
T extends string ? 1 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 2, 12))
T extends string[] ? 2 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 2, 12))
never {
if (Array.isArray(param)) {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>param : Symbol(param, Decl(dependentReturnType13.ts, 2, 41))
return 2;
}
return 1;
}
function f2<T extends string | string[] | number[]>(param: T):
>f2 : Symbol(f2, Decl(dependentReturnType13.ts, 10, 1))
>T : Symbol(T, Decl(dependentReturnType13.ts, 12, 12))
>param : Symbol(param, Decl(dependentReturnType13.ts, 12, 52))
>T : Symbol(T, Decl(dependentReturnType13.ts, 12, 12))
T extends string ? 1 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 12, 12))
T extends string[] | number[] ? 2 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 12, 12))
never {
if (Array.isArray(param)) {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>param : Symbol(param, Decl(dependentReturnType13.ts, 12, 52))
return 2;
}
return 1;
}
function f3<T extends string | string[] | number[]>(param: T): // Bad.
>f3 : Symbol(f3, Decl(dependentReturnType13.ts, 20, 1))
>T : Symbol(T, Decl(dependentReturnType13.ts, 22, 12))
>param : Symbol(param, Decl(dependentReturnType13.ts, 22, 52))
>T : Symbol(T, Decl(dependentReturnType13.ts, 22, 12))
T extends string ? 1 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 22, 12))
T extends string[] ? 2 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 22, 12))
T extends number[] ? 3 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 22, 12))
never {
if (Array.isArray(param)) {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>param : Symbol(param, Decl(dependentReturnType13.ts, 22, 52))
return 2;
}
return 1;
}
class Dog {
>Dog : Symbol(Dog, Decl(dependentReturnType13.ts, 31, 1))
bark(): void {}
>bark : Symbol(Dog.bark, Decl(dependentReturnType13.ts, 33, 11))
}
class Cat {
>Cat : Symbol(Cat, Decl(dependentReturnType13.ts, 35, 1))
meow(): void {}
>meow : Symbol(Cat.meow, Decl(dependentReturnType13.ts, 37, 11))
}
function f4<T extends Cat | Dog>(param: T): // Bad.
>f4 : Symbol(f4, Decl(dependentReturnType13.ts, 40, 1))
>T : Symbol(T, Decl(dependentReturnType13.ts, 42, 12))
>Cat : Symbol(Cat, Decl(dependentReturnType13.ts, 35, 1))
>Dog : Symbol(Dog, Decl(dependentReturnType13.ts, 31, 1))
>param : Symbol(param, Decl(dependentReturnType13.ts, 42, 33))
>T : Symbol(T, Decl(dependentReturnType13.ts, 42, 12))
T extends Cat ? 1 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 42, 12))
>Cat : Symbol(Cat, Decl(dependentReturnType13.ts, 35, 1))
T extends Dog ? 2 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 42, 12))
>Dog : Symbol(Dog, Decl(dependentReturnType13.ts, 31, 1))
never {
if ('bark' in param) {
>param : Symbol(param, Decl(dependentReturnType13.ts, 42, 33))
const _: Dog = param;
>_ : Symbol(_, Decl(dependentReturnType13.ts, 47, 13))
>Dog : Symbol(Dog, Decl(dependentReturnType13.ts, 31, 1))
>param : Symbol(param, Decl(dependentReturnType13.ts, 42, 33))
return 2;
}
const _: Cat = param;
>_ : Symbol(_, Decl(dependentReturnType13.ts, 50, 9))
>Cat : Symbol(Cat, Decl(dependentReturnType13.ts, 35, 1))
>param : Symbol(param, Decl(dependentReturnType13.ts, 42, 33))
return 1;
}
function f5<T extends string | number | string[]>(param: T):
>f5 : Symbol(f5, Decl(dependentReturnType13.ts, 52, 1))
>T : Symbol(T, Decl(dependentReturnType13.ts, 54, 12))
>param : Symbol(param, Decl(dependentReturnType13.ts, 54, 50))
>T : Symbol(T, Decl(dependentReturnType13.ts, 54, 12))
T extends string ? 1 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 54, 12))
T extends number | string[] ? 2 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 54, 12))
never {
if (Array.isArray(param) || typeof param === "number") {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>param : Symbol(param, Decl(dependentReturnType13.ts, 54, 50))
>param : Symbol(param, Decl(dependentReturnType13.ts, 54, 50))
const _: string[] | number = param;
>_ : Symbol(_, Decl(dependentReturnType13.ts, 59, 13))
>param : Symbol(param, Decl(dependentReturnType13.ts, 54, 50))
return 2;
}
const _: string = param;
>_ : Symbol(_, Decl(dependentReturnType13.ts, 62, 9))
>param : Symbol(param, Decl(dependentReturnType13.ts, 54, 50))
return 1;
}
function f6<T extends string | number[] | string[], U extends boolean>(param: T, other: U):
>f6 : Symbol(f6, Decl(dependentReturnType13.ts, 64, 1))
>T : Symbol(T, Decl(dependentReturnType13.ts, 66, 12))
>U : Symbol(U, Decl(dependentReturnType13.ts, 66, 51))
>param : Symbol(param, Decl(dependentReturnType13.ts, 66, 71))
>T : Symbol(T, Decl(dependentReturnType13.ts, 66, 12))
>other : Symbol(other, Decl(dependentReturnType13.ts, 66, 80))
>U : Symbol(U, Decl(dependentReturnType13.ts, 66, 51))
T extends number[] ? 2 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 66, 12))
U extends true ?
>U : Symbol(U, Decl(dependentReturnType13.ts, 66, 51))
T extends string[] ? 3 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 66, 12))
T extends string ? 1 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 66, 12))
never :
U extends false ?
>U : Symbol(U, Decl(dependentReturnType13.ts, 66, 51))
T extends string[] ? 4 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 66, 12))
T extends string ? 5 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 66, 12))
never :
never {
if (isNumberArray(param)) {
>isNumberArray : Symbol(isNumberArray, Decl(dependentReturnType13.ts, 90, 1))
>param : Symbol(param, Decl(dependentReturnType13.ts, 66, 71))
return 2;
}
if (other) {
>other : Symbol(other, Decl(dependentReturnType13.ts, 66, 80))
if (Array.isArray(param)) {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>param : Symbol(param, Decl(dependentReturnType13.ts, 66, 71))
return 3;
}
return 1;
}
if (Array.isArray(param)) {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>param : Symbol(param, Decl(dependentReturnType13.ts, 66, 71))
return 4;
}
return 5;
}
declare function isNumberArray(x: unknown): x is number[];
>isNumberArray : Symbol(isNumberArray, Decl(dependentReturnType13.ts, 90, 1))
>x : Symbol(x, Decl(dependentReturnType13.ts, 92, 31))
>x : Symbol(x, Decl(dependentReturnType13.ts, 92, 31))
function f7<T extends string | string[], U extends number | number[]>(param: T, other: U):
>f7 : Symbol(f7, Decl(dependentReturnType13.ts, 92, 58))
>T : Symbol(T, Decl(dependentReturnType13.ts, 94, 12))
>U : Symbol(U, Decl(dependentReturnType13.ts, 94, 40))
>param : Symbol(param, Decl(dependentReturnType13.ts, 94, 70))
>T : Symbol(T, Decl(dependentReturnType13.ts, 94, 12))
>other : Symbol(other, Decl(dependentReturnType13.ts, 94, 79))
>U : Symbol(U, Decl(dependentReturnType13.ts, 94, 40))
U extends number ?
>U : Symbol(U, Decl(dependentReturnType13.ts, 94, 40))
T extends string[] ? 2 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 94, 12))
T extends string ? 1 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 94, 12))
never :
U extends number[] ?
>U : Symbol(U, Decl(dependentReturnType13.ts, 94, 40))
T extends string[] ? 4 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 94, 12))
T extends string ? 3 :
>T : Symbol(T, Decl(dependentReturnType13.ts, 94, 12))
never :
never {
if (Array.isArray(other)) {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>other : Symbol(other, Decl(dependentReturnType13.ts, 94, 79))
if (Array.isArray(param)) {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>param : Symbol(param, Decl(dependentReturnType13.ts, 94, 70))
return 4;
}
return 3;
}
if (Array.isArray(param)) {
>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --))
>param : Symbol(param, Decl(dependentReturnType13.ts, 94, 70))
return 2;
}
return 1;
}

View File

@ -0,0 +1,361 @@
//// [tests/cases/compiler/dependentReturnType13.ts] ////
=== dependentReturnType13.ts ===
// Restrictions on what kind of union types can be narrowed.
function f1<T extends string | string[]>(param: T):
>f1 : <T extends string | string[]>(param: T) => T extends string ? 1 : T extends string[] ? 2 : never
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>param : T
> : ^
T extends string ? 1 :
T extends string[] ? 2 :
never {
if (Array.isArray(param)) {
>Array.isArray(param) : boolean
> : ^^^^^^^
>Array.isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>Array : ArrayConstructor
> : ^^^^^^^^^^^^^^^^
>isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>param : string | string[]
> : ^^^^^^^^^^^^^^^^^
return 2;
>2 : 2
> : ^
}
return 1;
>1 : 1
> : ^
}
function f2<T extends string | string[] | number[]>(param: T):
>f2 : <T extends string | string[] | number[]>(param: T) => T extends string ? 1 : T extends string[] | number[] ? 2 : never
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>param : T
> : ^
T extends string ? 1 :
T extends string[] | number[] ? 2 :
never {
if (Array.isArray(param)) {
>Array.isArray(param) : boolean
> : ^^^^^^^
>Array.isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>Array : ArrayConstructor
> : ^^^^^^^^^^^^^^^^
>isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>param : string | string[] | number[]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return 2;
>2 : 2
> : ^
}
return 1;
>1 : 1
> : ^
}
function f3<T extends string | string[] | number[]>(param: T): // Bad.
>f3 : <T extends string | string[] | number[]>(param: T) => T extends string ? 1 : T extends string[] ? 2 : T extends number[] ? 3 : never
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>param : T
> : ^
T extends string ? 1 :
T extends string[] ? 2 :
T extends number[] ? 3 :
never {
if (Array.isArray(param)) {
>Array.isArray(param) : boolean
> : ^^^^^^^
>Array.isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>Array : ArrayConstructor
> : ^^^^^^^^^^^^^^^^
>isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>param : string | string[] | number[]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return 2;
>2 : 2
> : ^
}
return 1;
>1 : 1
> : ^
}
class Dog {
>Dog : Dog
> : ^^^
bark(): void {}
>bark : () => void
> : ^^^^^^
}
class Cat {
>Cat : Cat
> : ^^^
meow(): void {}
>meow : () => void
> : ^^^^^^
}
function f4<T extends Cat | Dog>(param: T): // Bad.
>f4 : <T extends Cat | Dog>(param: T) => T extends Cat ? 1 : T extends Dog ? 2 : never
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>param : T
> : ^
T extends Cat ? 1 :
T extends Dog ? 2 :
never {
if ('bark' in param) {
>'bark' in param : boolean
> : ^^^^^^^
>'bark' : "bark"
> : ^^^^^^
>param : T
> : ^
const _: Dog = param;
>_ : Dog
> : ^^^
>param : Dog
> : ^^^
return 2;
>2 : 2
> : ^
}
const _: Cat = param;
>_ : Cat
> : ^^^
>param : Cat
> : ^^^
return 1;
>1 : 1
> : ^
}
function f5<T extends string | number | string[]>(param: T):
>f5 : <T extends string | number | string[]>(param: T) => T extends string ? 1 : T extends number | string[] ? 2 : never
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>param : T
> : ^
T extends string ? 1 :
T extends number | string[] ? 2 :
never {
if (Array.isArray(param) || typeof param === "number") {
>Array.isArray(param) || typeof param === "number" : boolean
> : ^^^^^^^
>Array.isArray(param) : boolean
> : ^^^^^^^
>Array.isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>Array : ArrayConstructor
> : ^^^^^^^^^^^^^^^^
>isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>param : string | number | string[]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
>typeof param === "number" : boolean
> : ^^^^^^^
>typeof param : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>param : T
> : ^
>"number" : "number"
> : ^^^^^^^^
const _: string[] | number = param;
>_ : number | string[]
> : ^^^^^^^^^^^^^^^^^
>param : number | string[]
> : ^^^^^^^^^^^^^^^^^
return 2;
>2 : 2
> : ^
}
const _: string = param;
>_ : string
> : ^^^^^^
>param : string
> : ^^^^^^
return 1;
>1 : 1
> : ^
}
function f6<T extends string | number[] | string[], U extends boolean>(param: T, other: U):
>f6 : <T extends string | number[] | string[], U extends boolean>(param: T, other: U) => T extends number[] ? 2 : U extends true ? T extends string[] ? 3 : T extends string ? 1 : never : U extends false ? T extends string[] ? 4 : T extends string ? 5 : never : never
> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^
>param : T
> : ^
>other : U
> : ^
T extends number[] ? 2 :
U extends true ?
>true : true
> : ^^^^
T extends string[] ? 3 :
T extends string ? 1 :
never :
U extends false ?
>false : false
> : ^^^^^
T extends string[] ? 4 :
T extends string ? 5 :
never :
never {
if (isNumberArray(param)) {
>isNumberArray(param) : boolean
> : ^^^^^^^
>isNumberArray : (x: unknown) => x is number[]
> : ^ ^^ ^^^^^
>param : string | string[] | number[]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return 2;
>2 : 2
> : ^
}
if (other) {
>other : U
> : ^
if (Array.isArray(param)) {
>Array.isArray(param) : boolean
> : ^^^^^^^
>Array.isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>Array : ArrayConstructor
> : ^^^^^^^^^^^^^^^^
>isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>param : string | string[]
> : ^^^^^^^^^^^^^^^^^
return 3;
>3 : 3
> : ^
}
return 1;
>1 : 1
> : ^
}
if (Array.isArray(param)) {
>Array.isArray(param) : boolean
> : ^^^^^^^
>Array.isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>Array : ArrayConstructor
> : ^^^^^^^^^^^^^^^^
>isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>param : string | string[]
> : ^^^^^^^^^^^^^^^^^
return 4;
>4 : 4
> : ^
}
return 5;
>5 : 5
> : ^
}
declare function isNumberArray(x: unknown): x is number[];
>isNumberArray : (x: unknown) => x is number[]
> : ^ ^^ ^^^^^
>x : unknown
> : ^^^^^^^
function f7<T extends string | string[], U extends number | number[]>(param: T, other: U):
>f7 : <T extends string | string[], U extends number | number[]>(param: T, other: U) => U extends number ? T extends string[] ? 2 : T extends string ? 1 : never : U extends number[] ? T extends string[] ? 4 : T extends string ? 3 : never : never
> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^
>param : T
> : ^
>other : U
> : ^
U extends number ?
T extends string[] ? 2 :
T extends string ? 1 :
never :
U extends number[] ?
T extends string[] ? 4 :
T extends string ? 3 :
never :
never {
if (Array.isArray(other)) {
>Array.isArray(other) : boolean
> : ^^^^^^^
>Array.isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>Array : ArrayConstructor
> : ^^^^^^^^^^^^^^^^
>isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>other : number | number[]
> : ^^^^^^^^^^^^^^^^^
if (Array.isArray(param)) {
>Array.isArray(param) : boolean
> : ^^^^^^^
>Array.isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>Array : ArrayConstructor
> : ^^^^^^^^^^^^^^^^
>isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>param : string | string[]
> : ^^^^^^^^^^^^^^^^^
return 4;
>4 : 4
> : ^
}
return 3;
>3 : 3
> : ^
}
if (Array.isArray(param)) {
>Array.isArray(param) : boolean
> : ^^^^^^^
>Array.isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>Array : ArrayConstructor
> : ^^^^^^^^^^^^^^^^
>isArray : (arg: any) => arg is any[]
> : ^ ^^ ^^^^^
>param : string | string[]
> : ^^^^^^^^^^^^^^^^^
return 2;
>2 : 2
> : ^
}
return 1;
>1 : 1
> : ^
}

View File

@ -0,0 +1,119 @@
// @noEmit: true
// @strict: true
// Restrictions on what kind of union types can be narrowed.
function f1<T extends string | string[]>(param: T):
T extends string ? 1 :
T extends string[] ? 2 :
never {
if (Array.isArray(param)) {
return 2;
}
return 1;
}
function f2<T extends string | string[] | number[]>(param: T):
T extends string ? 1 :
T extends string[] | number[] ? 2 :
never {
if (Array.isArray(param)) {
return 2;
}
return 1;
}
function f3<T extends string | string[] | number[]>(param: T): // Bad.
T extends string ? 1 :
T extends string[] ? 2 :
T extends number[] ? 3 :
never {
if (Array.isArray(param)) {
return 2;
}
return 1;
}
class Dog {
bark(): void {}
}
class Cat {
meow(): void {}
}
function f4<T extends Cat | Dog>(param: T): // Bad.
T extends Cat ? 1 :
T extends Dog ? 2 :
never {
if ('bark' in param) {
const _: Dog = param;
return 2;
}
const _: Cat = param;
return 1;
}
function f5<T extends string | number | string[]>(param: T):
T extends string ? 1 :
T extends number | string[] ? 2 :
never {
if (Array.isArray(param) || typeof param === "number") {
const _: string[] | number = param;
return 2;
}
const _: string = param;
return 1;
}
function f6<T extends string | number[] | string[], U extends boolean>(param: T, other: U):
T extends number[] ? 2 :
U extends true ?
T extends string[] ? 3 :
T extends string ? 1 :
never :
U extends false ?
T extends string[] ? 4 :
T extends string ? 5 :
never :
never {
if (isNumberArray(param)) {
return 2;
}
if (other) {
if (Array.isArray(param)) {
return 3;
}
return 1;
}
if (Array.isArray(param)) {
return 4;
}
return 5;
}
declare function isNumberArray(x: unknown): x is number[];
function f7<T extends string | string[], U extends number | number[]>(param: T, other: U):
U extends number ?
T extends string[] ? 2 :
T extends string ? 1 :
never :
U extends number[] ?
T extends string[] ? 4 :
T extends string ? 3 :
never :
never {
if (Array.isArray(other)) {
if (Array.isArray(param)) {
return 4;
}
return 3;
}
if (Array.isArray(param)) {
return 2;
}
return 1;
}