diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fcb93c45dc1..aa78c82bb02 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45976,14 +45976,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { /** * Narrowable type parameters are type parameters that: - * (1) have a union type constraint; - * (2) are used as the type of a single parameter in the function, and nothing else + * (1) have a narrowable constraint; + * (2) are syntactically used as the type of a single parameter in the function, and nothing else */ function getNarrowableTypeParameters(candidates: TypeParameter[]): [TypeParameter, Symbol, Identifier][] { const narrowableParams: [TypeParameter, Symbol, Identifier][] = []; for (const typeParam of candidates) { const constraint = getConstraintOfTypeParameter(typeParam); - if (!constraint || !(constraint.flags & TypeFlags.Union)) continue; + if (!constraint || !isNarrowableTypeParameterConstraint(constraint)) 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; @@ -46050,6 +46050,24 @@ 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) diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 4513ccb75cf..01843e25149 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -22,6 +22,8 @@ dependentReturnType1.ts(206,28): error TS2322: Type 'number' is not assignable t dependentReturnType1.ts(243,9): error TS2322: Type '""' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. dependentReturnType1.ts(245,9): error TS2322: Type 'true' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. dependentReturnType1.ts(247,5): error TS2322: Type '3' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. +dependentReturnType1.ts(258,9): error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : T extends { b: number; } ? string : never'. +dependentReturnType1.ts(261,5): error TS2322: Type 'number' is not assignable to type 'T extends { a: string; } ? number : T extends { b: number; } ? string : never'. dependentReturnType1.ts(275,9): error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. dependentReturnType1.ts(278,9): error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. dependentReturnType1.ts(280,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. @@ -44,7 +46,7 @@ dependentReturnType1.ts(488,9): error TS2322: Type 'R' is not assignable to type dependentReturnType1.ts(514,5): error TS2322: Type '1' is not assignable to type 'never'. -==== dependentReturnType1.ts (39 errors) ==== +==== dependentReturnType1.ts (41 errors) ==== interface A { 1: number; 2: string; @@ -346,9 +348,13 @@ dependentReturnType1.ts(514,5): error TS2322: Type '1' is not assignable to type if (q(x)) { x.b; return ""; + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : T extends { b: number; } ? string : never'. } x.a; return 1; + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'T extends { a: string; } ? number : T extends { b: number; } ? string : never'. } let y = { a: "", b: 1 }