Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue a custom error message when trying to assign constraint type to generic type parameter #29049

Closed
RyanCavanaugh opened this issue Dec 15, 2018 · 26 comments · Fixed by #30394
Labels
Domain: Error Messages The issue relates to error messaging Experience Enhancement Noncontroversial enhancements Good First Issue Well scoped, documented and has the green light Help Wanted You can do this Suggestion An idea for TypeScript
Milestone

Comments

@RyanCavanaugh
Copy link
Member

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

@jack-williams
Copy link
Collaborator

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".

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Dec 15, 2018

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.

@RyanCavanaugh
Copy link
Member Author

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}`.

@jack-williams
Copy link
Collaborator

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'.

@weswigham weswigham added Domain: Error Messages The issue relates to error messaging Experience Enhancement Noncontroversial enhancements labels Dec 17, 2018
@RyanCavanaugh
Copy link
Member Author

'false' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'boolean'.

❤️ !

@DanielRosenwasser DanielRosenwasser added Help Wanted You can do this Good First Issue Well scoped, documented and has the green light labels Dec 18, 2018
@DanielRosenwasser DanielRosenwasser added this to the Community milestone Dec 18, 2018
@a-tarasyuk
Copy link
Contributor

a-tarasyuk commented Dec 18, 2018

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

@DanielRosenwasser
Copy link
Member

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.

@jack-williams
Copy link
Collaborator

@a-tarasyuk I think structuredTypeRelatedTois probably where you want to look.

@kunlongxu
Copy link

@RyanCavanaugh function fn1<T extends string>(x: T): T { return "hello world!"; } How to resolve this error? Change to return "hello world" as T?

JoshuaKGoldberg pushed a commit to JoshuaKGoldberg/TypeScript that referenced this issue Jan 15, 2019
…eneric type parameter

Fixes microsoft#29049.

This also adds the new message in chained error messages. `typeParameterDiamond4.errors.txt` shows it appearing twice in the "diamond" scenario. I can't tell if this severely increased amount of nested messages is good or bad...?
@JoshuaKGoldberg
Copy link
Contributor

@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. 😊

@a-tarasyuk
Copy link
Contributor

a-tarasyuk commented Jan 16, 2019

@JoshuaKGoldberg Never mind 😃. 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 👍 .

@JoshuaKGoldberg
Copy link
Contributor

@a-tarasyuk no-no, this one's all you! I'll wait a week before looking into it.

@moccaplusplus
Copy link

Current message: [ts] Type 'false' is not assignable to type 'T'. [2322]
is perfectly OK.
Making it too verbose does not make it better. ;)

@jack-williams
Copy link
Collaborator

@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.

@RyanCavanaugh RyanCavanaugh added the Suggestion An idea for TypeScript label Mar 7, 2019
@RyanCavanaugh RyanCavanaugh modified the milestones: Community, Backlog Mar 7, 2019
@RyanCavanaugh
Copy link
Member Author

@JoshuaKGoldberg your commit there is looking pretty nice; is there an associated PR?

@JoshuaKGoldberg
Copy link
Contributor

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?

RyanCavanaugh pushed a commit that referenced this issue Apr 30, 2019
…eneric type parameter (#30394)

* Added custom error message when trying to assign constraint type to generic type parameter

Fixes #29049.

This also adds the new message in chained error messages. `typeParameterDiamond4.errors.txt` shows it appearing twice in the "diamond" scenario. I can't tell if this severely increased amount of nested messages is good or bad...?

* Updated diagnostic message per suggestion

* Align formatting with local custom
@lucasbordeau
Copy link

lucasbordeau commented Aug 15, 2019

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 ?

@dagda1
Copy link

dagda1 commented Sep 14, 2019

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.

@DanielRosenwasser
Copy link
Member

@dagda feel free to help us iterate with that on #33436

@Dema
Copy link

Dema commented Oct 16, 2019

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

@derakhshanfar
Copy link

any workaround?

@KostyaTretyak
Copy link

@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 #30394? I highly doubt it.
Please describe this case in the documentation. At the moment you can find a sea of questions about this.

@akopchinskiy
Copy link

akopchinskiy commented Mar 30, 2020

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

@mikew
Copy link

mikew commented Jun 8, 2020

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

@tcodes0
Copy link

tcodes0 commented Jun 9, 2020

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.

noahbrenner added a commit to noahbrenner/instrument-catalog-frontend that referenced this issue Feb 27, 2021
Apparently, TypeScript can't deal with default values for parameters
with inferred types:
microsoft/TypeScript#29049
@wbt
Copy link

wbt commented Sep 8, 2022

@tcodes0 I agree, there seems to be a TypeScript penalty prohibiting use of default values on parameters used to infer generic types (that e.g. then are used to determine a return type, or which are passed through as the generic type parameter to a called function).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: Error Messages The issue relates to error messaging Experience Enhancement Noncontroversial enhancements Good First Issue Well scoped, documented and has the green light Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet