Typescript: Issue a custom error message when trying to assign constraint type to generic type parameter

Created on 15 Dec 2018  路  25Comments  路  Source: microsoft/TypeScript

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

Error Messages Experience Enhancement Suggestion good first issue help wanted

Most helpful comment

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.

All 25 comments

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 structuredTypeRelatedTois 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?

playground link

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jbondc picture jbondc  路  3Comments

CyrusNajmabadi picture CyrusNajmabadi  路  3Comments

zhuravlikjb picture zhuravlikjb  路  3Comments

manekinekko picture manekinekko  路  3Comments

dlaberge picture dlaberge  路  3Comments