Skip to content

Issue a warning when generic type inference produces {} #360

Closed
@RyanCavanaugh

Description

@RyanCavanaugh

Motivating example (one of many):

function compare<T>(lhs: T, rhs: T): boolean {
  return lhs === rhs;
}

if(compare('1', 1)) { // Expected: Error -- I made 'compare' generic for a reason!
  /* ... */
}

Proposal

When generic type inference performs its Best Common Type operation (4.12.2), it should be an error if this returns {} when {} was not one of the input types.

This is entirely consistent with other behavior already in the compiler:

var foo = bar ? '1' : 1; // Error, no BCT between '1' and 1
function fn() { // Error, no BCT between '1' and 1
  if(foo) {
    return '1';
  } else {
    return 1;
  }
}

Open Questions

From @KamyarNazeri -- should this apply when there are zero input types? e.g.:

class List<T> {
    items: T[];
}

var x = new List(); // Probably want an error here

That seems desirable, but has some collateral damage:

function foo<T>(x: number, a?: T, b?: T) { }
foo(0); // Error, zero input types provided for inference

This would come up slightly more often than naively expected because many .d.ts authors (mistakenly) create generic types which don't actually consume their type parameters. Perhaps this warning would effectively discourage them from doing so?

Activity

basarat

basarat commented on Aug 5, 2014

@basarat
Contributor

👍

johnnyreilly

johnnyreilly commented on Aug 5, 2014

@johnnyreilly

Yes yes yes!

saschanaz

saschanaz commented on Aug 5, 2014

@saschanaz
Contributor

Great :D

knazeri

knazeri commented on Aug 5, 2014

@knazeri

This is really great!
I hope this also includes creating instances from a generic class without the type parameters in the constructor.

@RyanCavanaugh should this apply when there are zero input types?
I think it should, given the example above, type inference fails to correctly address generic instance methods:

class List<T> {
    items: T[]= [];
    add(item: T) {
        this.items.push(item);
    }
}

var t = new List();
t.add(1);
t.add("1");
DanielRosenwasser

DanielRosenwasser commented on Aug 6, 2014

@DanielRosenwasser
Member

So @JsonFreeman, @vladima, and I discussed this offline a bit yesterday.

One outstanding question is whether or not we want to error in the case that there are no available candidates to determine the best common type for a type parameter. A good example of this was seen in @KamyarNazeri's example:

class List<T> {
    items: T[]
}
var xs = new List();

Here, our new call expression lacks any arguments for which to infer T.

If we do want to error on such a case, then we feel it is worth asking whether we also want to error if a type variable is entirely unused within a type signature.

For instance:

function f<T1,T2>(): void {
}

f();

In this example, there will never be candidates for which to instantiate T1 or T2's types. Using the simple approach, we would always complain at the call-site.

However, we could complain at the definition site instead.

Our feeling is that a type variable that is unused in the signature buys nothing in terms of type safety and utility. There is very little someone can do with a type parameter appearing within a function body if it does not appear within the signature.

JsonFreeman

JsonFreeman commented on Aug 6, 2014

@JsonFreeman
Contributor

I agree with Daniel's comment, although I want to clarify that we don't error on signatures that don't use their own type parameters. So this would be a new error.

Another thing: I don't believe we should error in the absence of inference candidates if the type parameter has a constraint. Instead, we should fall back to the constraint, just like we do for inference results that are not assignable to the constraint.

RyanCavanaugh

RyanCavanaugh commented on Aug 6, 2014

@RyanCavanaugh
MemberAuthor

I'm thinking unconsumed generic type parameters in general to be an error under this rule. They're land mines for type inference -- people think that they can just write interface List<T> { } and have that work (see the current underscore.d.ts file for good examples), then log bugs when type inference doesn't work because there are no members to infer from.

Pre-documenting:

How do I fix "Type argument 'T' is not used in function 'f'" ?

Example:

function getThing<T>(name: string): T {
  /* ... */
}
var x = getThing<Animal>('cat');

This is a TypeScript anti-pattern because it implies that getThing is the function providing the type safety, when in reality it's the caller who's in control of the type. The better way to write this code is:

function getThing(name: string): {} {
  /* ... */
}
var x = <Animal>getThing('cat');

The only difference here is that <Animal> moved to the left of getThing rather than the right. One important note is that we've made getThing return {} instead of any. This avoids "implicit" any types caused by failing to convert the return value:

function getThingAny(name: string): any { /* ... */ }
function getThingEmpty(name: string): any { /* ... */ }

var x = getThingAny('cat'); // Forgot to type-assert, x: any
x.mov(); // No error
var y = getThingAny('cat'); // y: {}
y.mov(); // Error
mhegazy

mhegazy commented on Aug 6, 2014

@mhegazy
Contributor

@RyanCavanaugh I would group this with "Stricter" TypeScript #274, and not with #360

basarat

basarat commented on Aug 7, 2014

@basarat
Contributor

should this apply when there are zero input types?

👍

do we also want to error if a type variable is entirely unused within a type signature

👍

8 remaining items

danquirk

danquirk commented on Oct 27, 2014

@danquirk
Member

Status on this issue is that per #868 we do now give an error in the original case (multiple candidates leading to {}). The current implementation does not error on the additional suggests in this thread, namely generic inference with 0 candidates and unconsumed generic parameters.

JsonFreeman

JsonFreeman commented on Oct 27, 2014

@JsonFreeman
Contributor

Pull request #951 makes the error for type argument inference failure more informative.

added
DeclinedThe issue was declined as something which matches the TypeScript vision
and removed
Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
on Dec 2, 2015
RyanCavanaugh

RyanCavanaugh commented on Dec 2, 2015

@RyanCavanaugh
MemberAuthor

We have since fixed this

locked and limited conversation to collaborators on Jun 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @basarat@zpdDG4gta8XKpMCd@DanielRosenwasser@johnnyreilly@knazeri

        Issue actions

          Issue a warning when generic type inference produces {} · Issue #360 · microsoft/TypeScript