From a7c683903d34519e84e2f614a81d94db5cd626fb Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 5 Mar 2025 10:41:51 -0800 Subject: [PATCH] rework non-primitive restriction to be based on conditional type branches --- src/compiler/checker.ts | 57 ++- .../dependentReturnType13.errors.txt | 145 +++++++ .../reference/dependentReturnType13.symbols | 287 ++++++++++++++ .../reference/dependentReturnType13.types | 361 ++++++++++++++++++ tests/cases/compiler/dependentReturnType13.ts | 119 ++++++ 5 files changed, 939 insertions(+), 30 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType13.errors.txt create mode 100644 tests/baselines/reference/dependentReturnType13.symbols create mode 100644 tests/baselines/reference/dependentReturnType13.types create mode 100644 tests/cases/compiler/dependentReturnType13.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aac5bc9f6b6..6a2e8bde1f2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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` const typeArg = typeArgsReferencingT[0]; if (!(typeArg.kind & SyntaxKind.TypeReference)) return false; // e.g. `Foo, ...>` - 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` and `FalseBranch` must be valid, recursively. + // (4) At most one extends type has a non-primitive type. + // (5) `TrueBranch` and `FalseBranch` 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; } diff --git a/tests/baselines/reference/dependentReturnType13.errors.txt b/tests/baselines/reference/dependentReturnType13.errors.txt new file mode 100644 index 00000000000..3636362679b --- /dev/null +++ b/tests/baselines/reference/dependentReturnType13.errors.txt @@ -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(param: T): + T extends string ? 1 : + T extends string[] ? 2 : + never { + if (Array.isArray(param)) { + return 2; + } + return 1; + } + + function f2(param: T): + T extends string ? 1 : + T extends string[] | number[] ? 2 : + never { + if (Array.isArray(param)) { + return 2; + } + return 1; + } + + function f3(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(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(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(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(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; + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType13.symbols b/tests/baselines/reference/dependentReturnType13.symbols new file mode 100644 index 00000000000..6c488868ba3 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType13.symbols @@ -0,0 +1,287 @@ +//// [tests/cases/compiler/dependentReturnType13.ts] //// + +=== dependentReturnType13.ts === +// Restrictions on what kind of union types can be narrowed. + +function f1(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(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(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(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(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(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(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; +} diff --git a/tests/baselines/reference/dependentReturnType13.types b/tests/baselines/reference/dependentReturnType13.types new file mode 100644 index 00000000000..f7842900878 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType13.types @@ -0,0 +1,361 @@ +//// [tests/cases/compiler/dependentReturnType13.ts] //// + +=== dependentReturnType13.ts === +// Restrictions on what kind of union types can be narrowed. + +function f1(param: T): +>f1 : (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(param: T): +>f2 : (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(param: T): // Bad. +>f3 : (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(param: T): // Bad. +>f4 : (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(param: T): +>f5 : (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(param: T, other: U): +>f6 : (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(param: T, other: U): +>f7 : (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 +> : ^ +} diff --git a/tests/cases/compiler/dependentReturnType13.ts b/tests/cases/compiler/dependentReturnType13.ts new file mode 100644 index 00000000000..ab28adf3e2a --- /dev/null +++ b/tests/cases/compiler/dependentReturnType13.ts @@ -0,0 +1,119 @@ +// @noEmit: true +// @strict: true + + +// Restrictions on what kind of union types can be narrowed. + +function f1(param: T): + T extends string ? 1 : + T extends string[] ? 2 : + never { + if (Array.isArray(param)) { + return 2; + } + return 1; +} + +function f2(param: T): + T extends string ? 1 : + T extends string[] | number[] ? 2 : + never { + if (Array.isArray(param)) { + return 2; + } + return 1; +} + +function f3(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(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(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(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(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; +} \ No newline at end of file