Typescript: Don't mention constraints in errors when type parameters don't explicitly specify them

Created on 14 Sep 2019  路  14Comments  路  Source: microsoft/TypeScript

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'.
Experience Enhancement Suggestion

Most helpful comment

i have no idea what the error message means

All 14 comments

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

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

i鈥檓 seeing this in many places in many different projects

i have no idea what the error message means

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.

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

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.

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

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

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

@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

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

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.

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

siddjain picture siddjain  路  3Comments

dlaberge picture dlaberge  路  3Comments

Zlatkovsky picture Zlatkovsky  路  3Comments

uber5001 picture uber5001  路  3Comments

bgrieder picture bgrieder  路  3Comments