Description
Bug Report
π Search Terms
conditional types, generics
π Version & Regression Information
This is the behavior in every version I tried, and I reviewed the FAQ for entries about 4.6.4 (also beta channel)
β― Playground Link
Conditional types in constructor parameters cannot infer type from generics
π» Code
I want to create an abstract class that will store some common logic and type will be inferred based on the generic passed to this abstract class.
Simple example
type StringOrNuber = string | number;
type ConditionalType<T> = T extends StringOrNuber ? { value: T } : T;
interface HandleBaseClass<T> {
value: T
}
abstract class BaseClass<T> implements HandleBaseClass<T> {
test: ConditionalType<T>;
constructor(value: ConditionalType<T>) {
this.test = value;
}
abstract get value(): T;
abstract set value(newValue: T);
}
class ClassA<T extends StringOrNuber> extends BaseClass<T> {
constructor(initialValue: T) {
super({value: initialValue});
}
get value() {
return this.test.value;
}
set value(newValue: T) {
this.test.value = newValue;
}
}
class ClassB<T extends Record<PropertyKey, unknown>> extends BaseClass<T> {
constructor(initialValue: T) {
super(initialValue);
}
get value() {
return this.test;
}
set value(newValue: T) {
this.test = newValue;
}
}
Playground Link: Provided
In the above example I have a ConditionalType<T>
that can be an object with value
key only if we pass a number
or string
value, for all other cases the type should be the same as given type. Let's consider abstract class BaseClass<T>
that takes one parameter in the constructor, which will be a type of { value: T }
or T
. We have created two more specific class ClassA
and ClassB
.
ClassA
ClassA
extends BaseClass
and adds restriction to its generic that it can be only sth that extends StringOrNuber
type (string | number
) so the BaseClass
knows that T
can be only a string
or number
so type of constructor parameter and test
field should be { value: StringOrNumber }
. This partly works. If I try to access this.test
from ClassA
then TS says the type of this.test
is { value: number; } | { value: string; }
what is good, but if I try to pass value that can be only a string
or number
wrapped in an object I am getting an error. What should not happen, I think (correct me if I am wrong)
abstract class BaseClass<T> implements HandleBaseClass<T> {
test: ConditionalType<T>;
constructor(value: ConditionalType<T>) {
this.test = value;
}
// ...
}
class ClassA<T extends StringOrNuber> extends BaseClass<T> {
constructor(initialValue: T) {
super({value: initialValue});
}
// ...
}
Argument of type '{ value: StringOrNuber; }' is not assignable to parameter of type 'ConditionalType'
ClassB
ClassB
extends BaseClass
and adds restriction to its generic that it can be only sth that extends Record<PropertyKey, unknown>
type (basically any object) so the BaseClass
knows that T
can NOT be a string
or number
so type of constructor parameter and test
field should be T
. This partly works. If I try to access this.test
from ClassB
then TS says the type of this.test
is T
what is good, but if I try to pass value that can be only an object I am getting an error. What should not happen, I think (correct me if I am wrong).
abstract class BaseClass<T> implements HandleBaseClass<T> {
test: ConditionalType<T>;
constructor(value: ConditionalType<T>) {
this.test = value;
}
// ...
}
class ClassB<T extends Record<PropertyKey, unknown>> extends BaseClass<T> {
constructor(initialValue: T) {
super(initialValue);
}
// ...
}
Argument of type 'T' is not assignable to parameter of type 'ConditionalType'.
Type 'Record<PropertyKey, unknown>' is not assignable to type 'ConditionalType'.
If we use this ConditionalType<T>
like that:
type ConditionalType<T> = T extends StringOrNuber ? { value: T } : T;
const x: ConditionalType<number> = {
value: 12
}
const y: ConditionalType<Person> = {
firstName: 'Jan',
lastName: 'Kowlaski',
age: 21
}
Then there is no any issues and types are inferred as expected.
To solve the above issue, I can use Union type and initialize test
separately in ClassA
and ClassB
, but I would like to do it only once and do not repeat this operation in every class.
π Actual behavior
Class constructor parameters do not infer generic type to use it in ConditionalType
π Expected behavior
Class constructor parameters should infer generic type and use it in ConditionalType
Activity
whzx5byb commentedon Aug 6, 2022
The constraint provided for
ClassA
is not applied to the conditional type inBaseClass
, even they are literally the same. See discussions in #31096 and #32591.kaczor6418 commentedon Aug 6, 2022
@whzx5byb So, this is impossible right now because of TypeScript limitations and this can be more like feature request than bug ?
whzx5byb commentedon Aug 6, 2022
@kaczor6418 I think so. At the moment you can use type assertion
super({value: initialValue} as ConditionalType<T>)
as a workaround.typescript-bot commentedon Aug 11, 2022
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.