TypeScript Version: master
Search Terms: generic constrained type parameter error
Code
People are regularly confused why code like this doesn't work:
function fn1<T extends string>(x: T): T {
return "hello world!";
}
function fn2<T extends HTMLElement>(obj: T): T[] {
return [obj, new HTMLDialogElement()];
}
function fn3<T extends boolean>(obj: T = false) {
}
Actual behavior: Unelaborated error like "Can't assign false
to T
"
Expected behavior:
If the source type is assignable to the constraint of a type parameter, we should issue a more specific error that attempts to explain the situation:
馃毑 馃彔
Type "false" is not assignable to type "T" "false" is assignable to the constraint type "boolean", but "T" could be a different more-specific type during a call
Playground Link: Link
Very much agree with the suggestion. To throw my unicycle into the arena:
Type "false" is not assignable to type "T"
"false" is assignable to the constraint of type "T", but not all possible instantiations of type "T".
Agree with @jack-williams since this can happen in a class too. I think the following combines the best of both:
'{0}' is assignable to the constraint of type '{1}', but '{1}' could be instantiated with a different type.
Also, when not assignable, or when there's an implicit constraint, but the RHS is not instantiable, I could see an elaboraron like
Concrete types are never assignable to uninstantiated types.
Or something like that.
I think the problem with could be instantiated with a __different__ type is that the user reaction would probably be "You mean the constraint doesn't do anything?".
What about:
'{0}' is assignable to the constraint of type '{1}', but '{0}' could be instantiated with a different subtype of `{1}`.
That's a good point @RyanCavanaugh. I'm not sure if I've substituted in the placeholders correctly; is this right?
'false' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of 'boolean'.
In that instance I would probably make it explicit that boolean is the constraint, as I feel it pops out of nowhere abit. So:
'false' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'boolean'.
'false' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'boolean'.
鉂わ笍 !
@DanielRosenwasser Seems, that the error needs to add in the method isRelatedTo(
, after the condition else if (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) {
. Is there any method to determinate generic
definition? I've found isGenericObjectType(target)
- Is it right direction for this?
Since you're just specializing the error, target.flags & TypeFlags.TypeVariable
is probably a good first approximation. I haven't been deep in the codebase in a while, but others reviewing might be able to help you iterate on that.
@a-tarasyuk I think structuredTypeRelatedTo
is probably where you want to look.
@RyanCavanaugh function fn1<T extends string>(x: T): T {
return "hello world!";
}
How to resolve this error? Change to return "hello world" as T
?
@a-tarasyuk are you still interested in this? I'm interested in it going in, but don't want to snatch it out from under you. 馃槉
@JoshuaKGoldberg Never mind :smiley:. I wanted to make PR at the end of this week, however, I see you have started work and ready to send PR. I'll take another issue :+1: .
@a-tarasyuk no-no, this one's all you! I'll wait a week before looking into it.
Current message: [ts] Type 'false' is not assignable to type 'T'. [2322]
is perfectly OK.
Making it too verbose does not make it better. ;)
@moccaplusplus I agree that there is a fine balance between being informative and concise. IMO the extra information is helpful; as you can see I've been collating issues that trigger this error, so it's something people definitely come across.
Generics are a hard subject, subtyping is a hard subject, and variance is a hard subject---this error is a combination of all three. I think spelling out the problem in a more concrete way is a definite improvement.
@JoshuaKGoldberg your commit there is looking pretty nice; is there an associated PR?
Oh hey I forgot about this thread! Thanks for the reminder. Will send now and fix up merge conflicts soon.
Maybe "Although " should be prepended to the front of the message to make it more clear it's useful?
Is it relevant to include some sort of "you should consider doing X, Y or Z to solve this problem" ?
It could speed up the process of finding a solution for new comers, as it is not really easy to understand, and there are not many available resources online,
I managed to found this : https://stackoverflow.com/a/56701587/6790372
Could you at least modify your first post in this issue, to explain why each case does not work, and what could be done instead ?
I find this error message undecipherable. I have lots of this type of error message:
'A' is assignable to the constraint of type 'B', but 'B' could be instantiated with a different subtype of constraint '{}'.
The last placeholder is always, the {}
. I have no idea what this means or what the fix is.
@dagda feel free to help us iterate with that on https://github.com/microsoft/TypeScript/issues/33436
I have a question related to this error. I'm trying to implement "Slaying UI antipattern in...", but I'd like to have a separate state for pending but when we already have some data. It gives an error because I use two unrelated generic parameters. Could you help me a bit with how can I constrain Data param to be the same on both success and pendingWithData?
any workaround?
@RyanCavanaugh says:
People are regularly confused why code like this doesn't work:
function fn1<T extends string>(x: T): T { return "hello world!"; } function fn2<T extends HTMLElement>(obj: T): T[] { return [obj, new HTMLDialogElement()]; } function fn3<T extends boolean>(obj: T = false) { }
So, did confused reduce after https://github.com/microsoft/TypeScript/pull/30394? I highly doubt it.
Please describe this case in the documentation. At the moment you can find a sea of questions about this.
{1} is assignable to the constraint of type {2}, but {2} could be instantiated with a different subtype of constraint 'any'
Oh, really? Can you give me an example of how's that possible?
I'm bumping into this a lot recently, and I'm not quite sure how to solve it. Currently trying to do something on top of RESTDataSource.didReceiveResponse
from apollo-server
:
class SomethingOutsideOfOurControl {
didReceiveResponse<TResult = any>(): Promise<TResult> {
return Promise.resolve({}) as Promise<TResult>
}
}
class Implementation extends SomethingOutsideOfOurControl {
// Property 'didReceiveResponse' in type 'Implementation' is not assignable to the same property in base type 'SomethingOutsideOfOurControl'.
// Type '<T>() => Promise<{ data: any; somethingElse: boolean; }>' is not assignable to type '<TResult = any>() => Promise<TResult>'.
// Type 'Promise<{ data: any; somethingElse: boolean; }>' is not assignable to type 'Promise<TResult>'.
// Type '{ data: any; somethingElse: boolean; }' is not assignable to type 'TResult'.
// 'TResult' could be instantiated with an arbitrary type which could be unrelated to '{ data: any; somethingElse: boolean; }'.(2416)
async didReceiveResponse() {
const data = await super.didReceiveResponse()
return {
data,
somethingElse: true,
}
}
}
What would be the correct way of going about things? Seems like I have to throw in a return ... as any
in Implementation.didReceiveResponse
My 2cents on this matter is that TS seems to punish you for using default params. Makes sense to me for this to be a warning instead.
Most helpful comment
I find this error message undecipherable. I have lots of this type of error message:
The last placeholder is always, the
{}
. I have no idea what this means or what the fix is.