Take the below for instance. I'm not quite sure what the error message means, but it seems that logically, the signature is pletely valid. Is this just not supported by TS?
function _createNominalCollection<isOutputOrdered_T extends boolean>(
input: nominal_T,
processingFunc: (count: number) => number,
orderedOutput: isOutputOrdered_T = true,
)
^^^
Type 'boolean' is not assignable to type 'isOutputOrdered_T'.
'boolean' is assignable to the constraint of type 'isOutputOrdered_T', but 'isOutputOrdered_T' could be instantiated with a different subtype of constraint 'boolean'.ts(2322)
Take the below for instance. I'm not quite sure what the error message means, but it seems that logically, the signature is pletely valid. Is this just not supported by TS?
function _createNominalCollection<isOutputOrdered_T extends boolean>(
input: nominal_T,
processingFunc: (count: number) => number,
orderedOutput: isOutputOrdered_T = true,
)
^^^
Type 'boolean' is not assignable to type 'isOutputOrdered_T'.
'boolean' is assignable to the constraint of type 'isOutputOrdered_T', but 'isOutputOrdered_T' could be instantiated with a different subtype of constraint 'boolean'.ts(2322)
Share
Improve this question
asked Mar 12, 2021 at 17:30
user1543574user1543574
8631 gold badge7 silver badges13 bronze badges
4
-
3
What if somebody calls
_createNominalCollection<false>()
? Also, why does this really need to be a generic? – VLAZ Commented Mar 12, 2021 at 17:34 -
1
it's not clear to me what
orderedOutput: isOutputOrdered_T = true,
is supposed to be, this looks like assignment, but can't do assignment to a type – Ski Commented Mar 12, 2021 at 17:35 -
1
The output changes depending on if
orderedOutput_T
is set. So something likeisOutputOrdered_T extends true ? Ta : Tb
. The= true
is me trying to assign a default value to make the parameter optional (i.e. the output defaults to being ordered) – user1543574 Commented Mar 12, 2021 at 17:37 -
@Ski it's a default value. However, since it's TS, it also includes the type. A simplified version would be
(x: number = 41) => x + 1
-x
is a parameter which is a number and if not passed in, it's going to get the value41
. – VLAZ Commented Mar 12, 2021 at 18:08
4 Answers
Reset to default 9As @VLAZ pointed out _createNominalCollection<false>()
is problematic. Let's look at this error again:
'boolean' is assignable to the constraint of type 'isOutputOrdered_T', but 'isOutputOrdered_T' could be instantiated with a different subtype of constraint 'boolean'.ts(2322)
What that means is that you pass an explicit <false>
type as the generic parameter, isOutputOrdered_T
is now constrained to false
but then the default argument is true
, which would violate that.
Or to put it another way, true
and false
are subtypes of boolean
, and your function allows the boolean to be constrained to one of those subtypes, but doesn't guarantee assignments to that variable will all be of that same subtype.
Let me propose an alternative.
When you have a function that returns different types based on different arguments, you should always consider if function overloads are better suited to model that instead of generics. They allow you to specifically map argument patterns to specific return type in a simple way without any generics at all.
For example:
// sorted version
function myFn(orderedOutput?: true): 'sorted'
// unsorted version
function myFn(orderedOutput: false): 'unsorted'
// Implementation
function myFn(orderedOutput: boolean = true): 'sorted' | 'unsorted' {
return orderedOutput ? 'sorted' : 'unsorted'
}
// Testing
const a = myFn(true) // sorted
const b = myFn(false) // unsorted
const c = myFn() // sorted
Here you create two signatures for your function. The first "sorted" version accepts no argument or true
. The second "unsorted" version accepts false
. Then you have an implementation that handle both cases.
Playground
The reason you get this error, is because isOutputOrdered_T could be a boolean subtype like type truthy = true
or type falsy = false
instead of being a plete boolean like type bool = true | false
. For example:
function _createNominalCollection<T extends boolean>(
orderedOutput: T
) {}
type booleanSubtype = true;
_createNominalCollection<booleanSubtype>(false);
In this example the piler would plain about passing false, because booleanSubtype only allows true as input:
Argument of type 'false' is not assignable to parameter of type 'true'
In case of a truthy boolean subtype, like in the example, your default value wouldn't be a valid input and thats why the piler is warning you. Your example could work without piler warning if you typecast your default value like this:
function _createNominalCollection<T extends boolean>(
orderedOutput: T = false as T
) {}
But as shown before, this wouldn't be actually correct with a truthy subtype, just getting rid of the error.
You have option to not assign default to false - leave it as undefined.
function f<T extends boolean=false>(a?: T) {
return a ? 'isTrue' : 'isNotTrue'
}
Thought I'd question why do you need this generic in first place. Does return type change depending on this flag? If it does change why does it need to be single function, would it not make more sense to have 2 distinctly named functions?
You get the error because you assign true
to orderedOutput
as a default parameter.
If you would call _createNominalCollection(..,..,false)
the constrainted type parameter isOutputOrdered_T
would be inferred to false
. Of course it is not allowed to assign the value true
to a parameter of type false
.