0.9.5 Default Generic Type Inference

Topics: General
Nov 25, 2013 at 6:40 PM
It looks like generic type inference changed in 0.9.5 beta. Before the update I was doing something like this:
class Observable<T> {
    value: T;
    constructor(value?: T) {
        this.value = value !== undefined ? value : null;
    }
}

var o: Observable<string>;
o = new Observable();
but now that fails with the error types Observable<{}> and Observable<string> are incompatible. I understand the change is to reduce implicit any errors which makes sense, but in this case it feels like it's just going to cause extra typing. Because the LHS of the assignment is typed and the RHS constructor is compatible I think this should compile without error. Does that make sense?
Developer
Nov 26, 2013 at 1:05 AM
The RHS is not compatible though. The only thing the compiler knows about new Observable() is that T is {}, you've given no other information that can be used to infer the type (ie no arguments that can be used to infer possible types for T). This is adding real safety, not just extra typing. Consider what happens once you try to do anything with o's value:
var o: Observable<string>;
o = new Observable();
console.log(o.value.charAt(0)); // runtime exception
Nov 26, 2013 at 1:50 AM
The only thing the compiler knows about new Observable() is that T is {},
Well it doesn't know that, it guessed. Ignoring the LHS it's arguably as good a guess as any (get it?). I think using the LHS to make a better inference is fair game though.

I don't think adding an explicit generic type in your example adds any safety. This doesn't look more or less obviously broken to me.
var o: Observable<string>;
o = new Observable<string>();
console.log(o.value.charAt(0)); // still a runtime exception
Coordinator
Nov 26, 2013 at 4:08 PM
That's true.

I think the point here is that regardless of whether or not we infer the shape of T from the LHS, having an optional initializer is not going to become any safer by using the default 'any' we had before. The fact that you'd have to specify a type here should be a red flag that perhaps you should instead be inferring it from the initial value you pass to the constructor. We try to help give you that red flag by giving the default type '{}', which should error if you actually try to use the value, but ultimately the type system can only do so much to let the user know that not enough information was given.
Nov 27, 2013 at 8:55 PM
If I'm understanding you correctly then you're saying that the optional initializer is dangerous? Making it mandatory would give us this:
class Observable<T> {
    value: T;
    constructor(value: T) {
        this.value = value;
    }
}

var o: Observable<string>;
o = new Observable(null);
Well it compiles but I'm not convinced it's safer. It's a matter of personal opinion on whether the default behavior is confusing but empirically it hasn't been a source of defects for us.
Developer
Nov 27, 2013 at 9:55 PM
Currently the compiler only makes inferences for type arguments based on argument values. Therefore if you have a generic call or constructor that doesn't use a type parameter type in any parameter the compiler will not be able to infer anything for that type parameter other than {}. Likewise if you did have such a parameter but it is optional (as above) and you pass no argument then no inferences can be made. We're considering also making inferences from generic constraints but that isn't really relevant to this case.

You are correct that your newer example is not safer, in fact it is less safe because the 'null' value is widened to 'any' and then T is inferred to be 'any' rather than {}. It's just another way where you're making a generic call without providing any useful values with which to make inferences.

One additional argument for the current behavior is that the type annotation implies a certain level of safety which if the original assignment were allowed would not actually be the case. The following is allowed and I would argue more appropriately captures the level of safety here (ie "I am claiming this is an Observable<string> even though it cannot be statically verified"):
var o = <Observable<string>>new Observable();
Alternatively you can explicitly pass a type argument list as you did in your second post.
Nov 28, 2013 at 3:34 AM
I totally get what you're saying about this being the best behaviour given the current capabilities of type inference. I'll standardize our codebase on this style for the next release:
var o: Observable<string>;
// code
o = new Observable<string>();
var o2 = new Observable<string>();
I'd still like to see the LHS of an assignment used in type inference in a future release. I think it could make the coding experience a little more easy going without making it easier to write bugs. Maybe gave it some thought.

Cheers.