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

Don't mention constraints in errors when type parameters don't explicitly specify them #33436

Closed
DanielRosenwasser opened this issue Sep 14, 2019 · 14 comments · Fixed by #35225
Closed
Labels
Experience Enhancement Noncontroversial enhancements Suggestion An idea for TypeScript
Milestone

Comments

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Sep 14, 2019

function f<T>(x: T) {
    x = {};
}

Current

Type '{}' is not assignable to type 'T'.
  '{}' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.

Type '10' is not assignable to type 'T'.
  '10' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.

Nobody ever mentioned a constraint or {} in this code, so why is it being displayed? As a user, this is telling me more than I needed or wanted to know.

Proposed

Type '{}' is not assignable to type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to '{}'.


Type '10' is not assignable to type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to '10'.
@dagda1
Copy link

dagda1 commented Sep 14, 2019

I see the same error in this playground. I am at a loss to what the error is and where {} is coming from

@jack-williams
Copy link
Collaborator

Looks like the default constraint is coming through as {}, but shouldn't it be unknown now?

@dagda1
Copy link

dagda1 commented Sep 14, 2019

i’m seeing this in many places in many different projects

@dagda1
Copy link

dagda1 commented Sep 14, 2019

i have no idea what the error message means

@fatcerberus
Copy link

Looks like the default constraint is coming through as {}, but shouldn't it be unknown now?

I had the same question. It was changed to unknown in TS 3.5 but still shown as {} in (some? all?) relevant error messages.

@jack-williams
Copy link
Collaborator

jack-williams commented Sep 16, 2019

I debugged this and the summary is that noConstraintType is defined as an empty object type.

const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);

Changing this type to be defined as unknown changes the error message accordingly.

PR: #33445

@jack-williams
Copy link
Collaborator

On the topic of error messages: A proposal:

  • 'T' could be instantiated with a type that is unrelated to '10'.,
  • or, just specify no additional elaboration.

@jack-williams
Copy link
Collaborator

jack-williams commented Sep 16, 2019

@dagda1 Sorry, to answer you questions.

Assigning a concrete type to a generic type parameter is incorrect because the type parameter can always be instantiated to some arbitrary type:

function f<T>(x: T) {
    x = {};
}
f(3);
f(true);
f("something else");

In the body of the function you have no control over the instantiation by the calling context, so we have to treat things of type T as opaque boxes and say you can't assign to it.

A common mistake or misunderstanding was to constraint a type parameter and then assign to its constraint, for example:

function f<T extends boolean>(x: T) {
    x = true;
}
f<false>(false);

This is still incorrect because the constraint only restricts the upper bound, it does not tell you how precise T may really be.

To try and address this misunderstanding the additional error elaboration was provided, but in the most degenerate case, an unconstrained parameter, the elaboration is really just noise.

The {} you see is the implicit constraint on the type paramater.

@RyanCavanaugh RyanCavanaugh added Experience Enhancement Noncontroversial enhancements Suggestion An idea for TypeScript labels Sep 16, 2019
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Sep 16, 2019
@dagda1
Copy link

dagda1 commented Sep 22, 2019

@jack-williams thank you for the explanation although I see this error a crazy amount of times now and the implicit {} constrain is what I always see.

The example in this playground on line 34 has nothing to do with assigning to a constraint and I would really appreciate an explanation of what the error message is trying to tell me here.

I have so many other examples.

@jack-williams
Copy link
Collaborator

Line 34 is trying to assign A | B | null | undefined to B | null | undefined which fails because A is not related to any union constituent in B | null | undefined. Type parameter A does not have an explicit constraint which is why you see the additional spurious message; the error message is using the implicit constraint when it should not be and that should be getting fixed.

The error message is correct though, and symptomatic of the fact that nullableFunctor does not satisfy the functor laws. For the correct typing you need negation types and to constraint A to not (null | undefined).

@theseyi
Copy link

theseyi commented Oct 1, 2019

@jack-williams I semi understand why this change was made, but it's not clear how to resolve it. Most of the documentation in this repo seem to say the same thing, but the error does not give a suggestion on how to resolve this.
I have a simple playground with comments on the issues I'm facing with this, appreciate your help here

@jack-williams
Copy link
Collaborator

@theseyi It's not clear to me why A.methodA needs to be a generic method. The error is correct though:

Every B should be a subtype of A, so I should always be able to assign a B to an A, but the type of B.method is weaker. For example:

const a: A = new B();
const three: 3 = a.methodA(3);

Because A.methodA is generic I think I'm getting 3, but I'm really calling B's method, which returns 5.

It's hard for me to help you fix this because I'm not sure what you're trying to achieve, but having a parent method return a generic type, and a child method return a non-generic type, is usually wrong.

@theseyi
Copy link

theseyi commented Oct 2, 2019

Thanks for responding, I agree in attempting to get to the salient parts of the issue, I may have fuzzied the example and concrete values make it unclear.
Hopefully, this is a more vivid example, TS does not complain, but is this the correct approach here?

In other words, I have a class that is generic over a base type (because as you mention inherited and base methods have to be generic), and a subclass that is generic over a more specialized extension of that base type.

@jack-williams
Copy link
Collaborator

@DanielRosenwasser I have a fix (#33445) that removes the useless error message. Is the proposed message final and OK to go ahead and add?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Experience Enhancement Noncontroversial enhancements Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants