allow extends type to be supertype of constraint types

This commit is contained in:
Gabriela Araujo Britto
2025-02-04 13:22:26 -08:00
parent c50b5e718a
commit a668b6b19b
6 changed files with 309 additions and 9 deletions

View File

@@ -46082,7 +46082,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// (0) The conditional type is distributive;
// (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 belonging to the union constraint of the type parameter;
// (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.
// In particular, the false-most branch of the conditional type must be `never`.
function isNarrowableConditionalTypeWorker(type: ConditionalType): boolean {
@@ -46111,7 +46111,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
!everyType(type.extendsType, extendsType =>
some(
(constraintType as UnionType).types,
constraintType => isTypeIdenticalTo(constraintType, extendsType),
constraintType => isTypeAssignableTo(constraintType, extendsType),
))
) {
return false;

View File

@@ -26,8 +26,6 @@ dependentReturnType1.ts(275,9): error TS2322: Type '1' is not assignable to type
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>'.
dependentReturnType1.ts(302,9): error TS2322: Type 'string' is not assignable to type 'string[]'.
dependentReturnType1.ts(311,9): error TS2322: Type 'undefined' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'.
dependentReturnType1.ts(313,5): error TS2322: Type 'number' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'.
dependentReturnType1.ts(334,9): error TS2322: Type '1' is not assignable to type '4'.
dependentReturnType1.ts(367,13): error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'.
dependentReturnType1.ts(369,9): error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'.
@@ -44,7 +42,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 (37 errors) ====
interface A {
1: number;
2: string;
@@ -407,12 +405,8 @@ dependentReturnType1.ts(514,5): error TS2322: Type '1' is not assignable to type
): T extends {} ? void : T extends undefined ? number : never {
if (x) {
return;
~~~~~~
!!! error TS2322: Type 'undefined' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'.
}
return 1;
~~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'.
}
// Multiple type parameters at once

View File

@@ -0,0 +1,41 @@
dependentReturnType10.ts(29,9): error TS2322: Type 'number' is not assignable to type 'void'.
==== dependentReturnType10.ts (1 errors) ====
interface Animal {
name: string;
species: string;
}
interface Dog extends Animal {
breed: string;
}
type GreetRet<T> =
T extends string ? string :
T extends { name: string } ? { greeting: string, breed: string } :
never;
function greet<T extends string | Dog>(animal: T): GreetRet<T> {
if (typeof animal === "string") {
return `hello, ${animal}`
}
return { greeting: `woof, ${animal.name}`, breed: animal.breed }
}
type BadRet<T> =
T extends {} ? void :
T extends string ? number :
never;
function badFun<T extends { a: string } | string>(x: T): BadRet<T> {
if (typeof x === "string") {
return 1
~~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'void'.
}
return;
}
declare let arg2: { a: string } | string;
const badRet = badFun(arg2);

View File

@@ -0,0 +1,100 @@
//// [tests/cases/compiler/dependentReturnType10.ts] ////
=== dependentReturnType10.ts ===
interface Animal {
>Animal : Symbol(Animal, Decl(dependentReturnType10.ts, 0, 0))
name: string;
>name : Symbol(Animal.name, Decl(dependentReturnType10.ts, 0, 18))
species: string;
>species : Symbol(Animal.species, Decl(dependentReturnType10.ts, 1, 17))
}
interface Dog extends Animal {
>Dog : Symbol(Dog, Decl(dependentReturnType10.ts, 3, 1))
>Animal : Symbol(Animal, Decl(dependentReturnType10.ts, 0, 0))
breed: string;
>breed : Symbol(Dog.breed, Decl(dependentReturnType10.ts, 5, 30))
}
type GreetRet<T> =
>GreetRet : Symbol(GreetRet, Decl(dependentReturnType10.ts, 7, 1))
>T : Symbol(T, Decl(dependentReturnType10.ts, 9, 14))
T extends string ? string :
>T : Symbol(T, Decl(dependentReturnType10.ts, 9, 14))
T extends { name: string } ? { greeting: string, breed: string } :
>T : Symbol(T, Decl(dependentReturnType10.ts, 9, 14))
>name : Symbol(name, Decl(dependentReturnType10.ts, 11, 15))
>greeting : Symbol(greeting, Decl(dependentReturnType10.ts, 11, 34))
>breed : Symbol(breed, Decl(dependentReturnType10.ts, 11, 52))
never;
function greet<T extends string | Dog>(animal: T): GreetRet<T> {
>greet : Symbol(greet, Decl(dependentReturnType10.ts, 12, 10))
>T : Symbol(T, Decl(dependentReturnType10.ts, 14, 15))
>Dog : Symbol(Dog, Decl(dependentReturnType10.ts, 3, 1))
>animal : Symbol(animal, Decl(dependentReturnType10.ts, 14, 39))
>T : Symbol(T, Decl(dependentReturnType10.ts, 14, 15))
>GreetRet : Symbol(GreetRet, Decl(dependentReturnType10.ts, 7, 1))
>T : Symbol(T, Decl(dependentReturnType10.ts, 14, 15))
if (typeof animal === "string") {
>animal : Symbol(animal, Decl(dependentReturnType10.ts, 14, 39))
return `hello, ${animal}`
>animal : Symbol(animal, Decl(dependentReturnType10.ts, 14, 39))
}
return { greeting: `woof, ${animal.name}`, breed: animal.breed }
>greeting : Symbol(greeting, Decl(dependentReturnType10.ts, 18, 12))
>animal.name : Symbol(Animal.name, Decl(dependentReturnType10.ts, 0, 18))
>animal : Symbol(animal, Decl(dependentReturnType10.ts, 14, 39))
>name : Symbol(Animal.name, Decl(dependentReturnType10.ts, 0, 18))
>breed : Symbol(breed, Decl(dependentReturnType10.ts, 18, 46))
>animal.breed : Symbol(Dog.breed, Decl(dependentReturnType10.ts, 5, 30))
>animal : Symbol(animal, Decl(dependentReturnType10.ts, 14, 39))
>breed : Symbol(Dog.breed, Decl(dependentReturnType10.ts, 5, 30))
}
type BadRet<T> =
>BadRet : Symbol(BadRet, Decl(dependentReturnType10.ts, 19, 1))
>T : Symbol(T, Decl(dependentReturnType10.ts, 21, 12))
T extends {} ? void :
>T : Symbol(T, Decl(dependentReturnType10.ts, 21, 12))
T extends string ? number :
>T : Symbol(T, Decl(dependentReturnType10.ts, 21, 12))
never;
function badFun<T extends { a: string } | string>(x: T): BadRet<T> {
>badFun : Symbol(badFun, Decl(dependentReturnType10.ts, 24, 10))
>T : Symbol(T, Decl(dependentReturnType10.ts, 26, 16))
>a : Symbol(a, Decl(dependentReturnType10.ts, 26, 27))
>x : Symbol(x, Decl(dependentReturnType10.ts, 26, 50))
>T : Symbol(T, Decl(dependentReturnType10.ts, 26, 16))
>BadRet : Symbol(BadRet, Decl(dependentReturnType10.ts, 19, 1))
>T : Symbol(T, Decl(dependentReturnType10.ts, 26, 16))
if (typeof x === "string") {
>x : Symbol(x, Decl(dependentReturnType10.ts, 26, 50))
return 1
}
return;
}
declare let arg2: { a: string } | string;
>arg2 : Symbol(arg2, Decl(dependentReturnType10.ts, 33, 11))
>a : Symbol(a, Decl(dependentReturnType10.ts, 33, 19))
const badRet = badFun(arg2);
>badRet : Symbol(badRet, Decl(dependentReturnType10.ts, 34, 5))
>badFun : Symbol(badFun, Decl(dependentReturnType10.ts, 24, 10))
>arg2 : Symbol(arg2, Decl(dependentReturnType10.ts, 33, 11))

View File

@@ -0,0 +1,128 @@
//// [tests/cases/compiler/dependentReturnType10.ts] ////
=== dependentReturnType10.ts ===
interface Animal {
name: string;
>name : string
> : ^^^^^^
species: string;
>species : string
> : ^^^^^^
}
interface Dog extends Animal {
breed: string;
>breed : string
> : ^^^^^^
}
type GreetRet<T> =
>GreetRet : GreetRet<T>
> : ^^^^^^^^^^^
T extends string ? string :
T extends { name: string } ? { greeting: string, breed: string } :
>name : string
> : ^^^^^^
>greeting : string
> : ^^^^^^
>breed : string
> : ^^^^^^
never;
function greet<T extends string | Dog>(animal: T): GreetRet<T> {
>greet : <T extends string | Dog>(animal: T) => GreetRet<T>
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>animal : T
> : ^
if (typeof animal === "string") {
>typeof animal === "string" : boolean
> : ^^^^^^^
>typeof animal : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>animal : T
> : ^
>"string" : "string"
> : ^^^^^^^^
return `hello, ${animal}`
>`hello, ${animal}` : string
> : ^^^^^^
>animal : T & string
> : ^^^^^^^^^^
}
return { greeting: `woof, ${animal.name}`, breed: animal.breed }
>{ greeting: `woof, ${animal.name}`, breed: animal.breed } : { greeting: string; breed: string; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>greeting : string
> : ^^^^^^
>`woof, ${animal.name}` : string
> : ^^^^^^
>animal.name : string
> : ^^^^^^
>animal : Dog
> : ^^^
>name : string
> : ^^^^^^
>breed : string
> : ^^^^^^
>animal.breed : string
> : ^^^^^^
>animal : Dog
> : ^^^
>breed : string
> : ^^^^^^
}
type BadRet<T> =
>BadRet : BadRet<T>
> : ^^^^^^^^^
T extends {} ? void :
T extends string ? number :
never;
function badFun<T extends { a: string } | string>(x: T): BadRet<T> {
>badFun : <T extends { a: string; } | string>(x: T) => BadRet<T>
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>a : string
> : ^^^^^^
>x : T
> : ^
if (typeof x === "string") {
>typeof x === "string" : boolean
> : ^^^^^^^
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>x : T
> : ^
>"string" : "string"
> : ^^^^^^^^
return 1
>1 : 1
> : ^
}
return;
}
declare let arg2: { a: string } | string;
>arg2 : string | { a: string; }
> : ^^^^^^^^^^^^^^ ^^^
>a : string
> : ^^^^^^
const badRet = badFun(arg2);
>badRet : void
> : ^^^^
>badFun(arg2) : void
> : ^^^^
>badFun : <T extends { a: string; } | string>(x: T) => BadRet<T>
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>arg2 : string | { a: string; }
> : ^^^^^^^^^^^^^^ ^^^

View File

@@ -0,0 +1,37 @@
// @noEmit: true
interface Animal {
name: string;
species: string;
}
interface Dog extends Animal {
breed: string;
}
type GreetRet<T> =
T extends string ? string :
T extends { name: string } ? { greeting: string, breed: string } :
never;
function greet<T extends string | Dog>(animal: T): GreetRet<T> {
if (typeof animal === "string") {
return `hello, ${animal}`
}
return { greeting: `woof, ${animal.name}`, breed: animal.breed }
}
type BadRet<T> =
T extends {} ? void :
T extends string ? number :
never;
function badFun<T extends { a: string } | string>(x: T): BadRet<T> {
if (typeof x === "string") {
return 1
}
return;
}
declare let arg2: { a: string } | string;
const badRet = badFun(arg2);