-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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 warning when generic type inference produces {} #360
Comments
👍 |
Yes yes yes! |
Great :D |
This is really great! @RyanCavanaugh should this apply when there are zero input types? class List<T> {
items: T[]= [];
add(item: T) {
this.items.push(item);
}
}
var t = new List();
t.add(1);
t.add("1"); |
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 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 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. |
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. |
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 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 function getThing(name: string): {} {
/* ... */
}
var x = <Animal>getThing('cat'); The only difference here is that 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 |
@RyanCavanaugh I would group this with "Stricter" TypeScript #274, and not with #360 |
👍
👍 |
@RyanCavanaugh, being required to declare members in an interface in order for the type inference to work properly is a big inconvenience, what if there actually are members in an interface but I want to hide them intentionally in order to preserve the integrity? You may want to consider this as an example. I wish the members of List and Node were hidden because the caller has no business to see or use them directly. Does it mean that doing such sort of tricks (having an empty generic interface) is a crime in TypeScript? Is there a better idiomatic way of doing it without giving up on using generics? |
I don't think that's a common scenario. It seems very rare to have a generic type you want to traffic around on an object which doesn't use that type at all. If you really wanted to emulate that, you could do something like this: interface Node<T> {
'__element type'?: T;
}
interface NodeManager {
create<T>(x: T): Node<T>;
readValue<T>(x: Node<T>): T;
}
var mg: NodeManager;
var x = mg.create('foo');
var y = mg.readValue(x); // y: string |
@RyanCavanaugh, speaking of common scenarios, here is another one for having poor-man units of measure for your consideration |
There is a way to have a truly private member on an interface. It takes advantage of the fact that interfaces can extend classes: class NodePrivate<T> {
private value: T;
}
interface Node<T> extends NodePrivate<T> { } It's not pretty, but it would work if you really want an interface whose sole purpose is to wrap a type and not allow any interaction. |
Scenarios like these are very common in functional programming. The motivation there is to have clear separation of data and logic contrary to what you see in OOP when a class is a mix of data and methods. Such separation has a number of advantages, one of them is the serialization and desalinization which are straight forward and simple compared to ones with classes involved. It's seems unfortunate that OOP was chosen as the main paradigm for TypeScript namely that encapsulation is only possible using private fields of a class. A class is a much heavier structure than an object literal yet according to the current vision and design it is a main building block. |
Private fields of a class actually aren't private at runtime. Function closures are the only way to actually make something private and pass it around. OOP is most definitely not the main paradigm, the compiler sources themselves are fairly functional in style, primarily using object literals/interfaces and functions. |
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. |
Pull request #951 makes the error for type argument inference failure more informative. |
We have since fixed this |
Motivating example (one of many):
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:
Open Questions
From @KamyarNazeri -- should this apply when there are zero input types? e.g.:
That seems desirable, but has some collateral damage:
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?
The text was updated successfully, but these errors were encountered: