From a3e7ea503def31ec21e6216f55ad00a3b171510d Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Sat, 1 Feb 2025 12:34:04 -0800 Subject: [PATCH] relax restriction on extends type of narrowable conditional type --- src/compiler/checker.ts | 4 +- .../reference/dependentReturnType10.symbols | 61 +++++++++++++++ .../reference/dependentReturnType10.types | 78 +++++++++++++++++++ tests/cases/compiler/dependentReturnType10.ts | 22 ++++++ 4 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType10.symbols create mode 100644 tests/baselines/reference/dependentReturnType10.types create mode 100644 tests/cases/compiler/dependentReturnType10.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aa78c82bb02..3b74a8ec4a0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -46100,7 +46100,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` and `FalseBranch` must be valid, recursively. // In particular, the false-most branch of the conditional type must be `never`. function isNarrowableConditionalTypeWorker(type: ConditionalType): boolean { @@ -46129,7 +46129,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; diff --git a/tests/baselines/reference/dependentReturnType10.symbols b/tests/baselines/reference/dependentReturnType10.symbols new file mode 100644 index 00000000000..d8a400718ae --- /dev/null +++ b/tests/baselines/reference/dependentReturnType10.symbols @@ -0,0 +1,61 @@ +//// [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 = +>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(animal: T): GreetRet { +>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)) +} diff --git a/tests/baselines/reference/dependentReturnType10.types b/tests/baselines/reference/dependentReturnType10.types new file mode 100644 index 00000000000..572d21c41bf --- /dev/null +++ b/tests/baselines/reference/dependentReturnType10.types @@ -0,0 +1,78 @@ +//// [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 = +>GreetRet : GreetRet +> : ^^^^^^^^^^^ + + T extends string ? string : + T extends { name: string } ? { greeting: string, breed: string } : +>name : string +> : ^^^^^^ +>greeting : string +> : ^^^^^^ +>breed : string +> : ^^^^^^ + + never; + +function greet(animal: T): GreetRet { +>greet : (animal: T) => GreetRet +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>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 +> : ^^^^^^ +} diff --git a/tests/cases/compiler/dependentReturnType10.ts b/tests/cases/compiler/dependentReturnType10.ts new file mode 100644 index 00000000000..df2ee64d825 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType10.ts @@ -0,0 +1,22 @@ +// @noEmit: true + +interface Animal { + name: string; + species: string; +} + +interface Dog extends Animal { + breed: string; +} + +type GreetRet = + T extends string ? string : + T extends { name: string } ? { greeting: string, breed: string } : + never; + +function greet(animal: T): GreetRet { + if (typeof animal === "string") { + return `hello, ${animal}` + } + return { greeting: `woof, ${animal.name}`, breed: animal.breed } +} \ No newline at end of file