-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Type inference for generics not considering interfaces #8273
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
Comments
The problem, I believe, is that interface IFooable<T> { }
class NumberFooable implements IFooable<number> { }
class StringFooable implements IFooable<string> { }
let x: NumberFooable = new StringFooable(); // No error
let y: StringFooable = new NumberFooable(); // No error So here: foo.doStuff(new NumberFooable(), ""); // was expecting a compilation error here The type argument inference logic goes to the second argument and infers The reason for this is that Interestingly, even if the generic argument was applied to the class itself, it would still behave this way: class Box<T> {}
let x: Box<number> = new Box<string>(); // No error
let y: Box<string> = new Box<number>(); // No error I have spent an excessive amount of time analyzing and demonstrating more significant aspects of this problem and have even proposed a solution (see the examples I've given there, I believe they better illustrate the possible consequences of this phenomenon). The team basically stamped it as "by design", which is sometimes their 'friendly' way of saying: "we know about this but we don't care, moreover, we consider it offensive to expose flaws in our original design: note, this forum is for bug reports, suggestions, and input that Microsoft likes and finds valuable, not a place to question our prior decisions and please stop wasting our precious time, we have better things to do" (and so do I..). If that's what you intended when you asked whether this is by 'by design'? then based on their responses, I believe that would probably capture that well. That doesn't mean it's good design though, unless you're one of those people who sees it perfectly fine to have a |
Ok, I think I got it. Coming from C# this is quite confusing, but I can understand why in a dynamic language it makes more sense. I am still trying to wrap my head around the fact that types are structural and not nominal, but old habits continue to kick in. Hopefully they will introduce them someday. Going to close this issue for now, as by design. |
Just wanted to mention the Javascript run-time has a nominal identification system for prototypes: function A() { this.x = 1234 }
function B() { this.x = 1234 }
let a = new A();
let b = new B();
console.log(a instanceof A); // true
console.log(b instanceof B); // true
console.log(a instanceof B); // false, despite the compatible structure
console.log(b instanceof A); // false, despite the compatible structure (same for ES6 classes) So nominal typing is not necessarily a stranger to Javascript, at least when it comes to classes. To the contrary: TypeScript's type system does not actually successfully "capture" correct prototypical relations as it sees all classes as purely structural: class A { x: number; }
class B { x: number; }
function giveMeA(arg: A) {
if (!(arg instanceof A))
throw new TypeError("That is not an A!");
}
giveMeA(new B()); // Runtime error, but no compilation error. |
@malibuzios That's true. However, the interfaces and type parameters have no runtime representation in JavaScript. Furthermore, var MyClass = (function () {
function MyClass(value) {
this.value = value;
};
return MyClass;
}());
function assertIsInstanceOf(instance, ctor) {
if (!(instance instanceof ctor))
throw new TypeError("instance is not an instance of " + ctor);
}
var myInstance = new MyClass();
assertIsInstanceOf(myInstance, MyClass); // OK
myInstance.__proto__ = window.Date;
assertIsInstanceOf(myInstance, MyClass); // Runtime error This is not really nominal typing, it is an equality check on the prototype chain, based on comparison of properties of the objects themselves, what value their prototype refers to, which is pretty structural if you ask me. EDIT: clarified example, remarks |
I was not referring to interfaces but particularly to classes which are strictly the case I was referring to on the other issues (in that sense I was digressing a bit). The way TypeScript models ES6 classes today is only valid for unsafe Javascript code that does not actually apply run-time checks using I was told that in a very early incarnation of the language, TypeScript did have nominal typing for classes but a structural one for interfaces, but the feedback they received was not very positive so they changed classes to become structural. They could have considered maintaining that functionality as an option, and I believe they may be reconsidering this now due to the community's feedback. The case for allowing meaningless generic annotations when the generic variable is not referenced is a different one: class Box<T> {}
let x: Box<number> = new Box<string>(); // No error
let y: Box<string> = new Box<number>(); // No error It is unlikely, that anyone, say, in the history of the universe would actually find any remotely reasonable usage for this. Essentially the annotation here acts to purely confuse the programmer. I find it grossly disturbing from a design point of view, as it imprints a specialization on the type that is completely meaningless, but the only current way for the programmer to find this out is through gaining an understanding of this as an anomaly resulting from the particular way generics were implemented on top of structural typing at this language. It is as if the language designers are saying: "yes we have a bug in our language but the only way for you to avoid it is to ask us or read about it in a forum" (this of course assumes you actually did notice it at all!). And then they say "oh, and you can have this checked through a linter", which is sometimes what they use as a "garbage bag" for things that may be important but they don't actually care about. |
@malibuzios it doesn't change the reality that instanceof checks are brittle and that JavaScript does not have nominal typing. Even if you use some tool to enforce instanceof checks throughout your codebase, the language itself will not enforce it and a third party is free to create an object which masquerades as an instance of your class. |
Javascript doesn't have typing at all, and would rarely enforce it, even for primitives like |
TypeScript Version:
1.8.10
Code
Expected behavior:
Apparently when I create a class that implements an interface the system doesn't type check against the generic by using the interfaces, unless I declare it explicitly with the interface type.
Not sure if this is by design, but it looks quite odd to me.
The text was updated successfully, but these errors were encountered: