-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Interfaces and classes considered equal #8168
Comments
TypeScript is a structural typing system, not a nominal one. 😄 Therefore anything that shares the same structure is considered type equality. |
I understand that, but a class has a constructor and a prototype which I would expect to be present in the structure of a class but not in the structure of a object literal |
You can add a private or protected property to a class if you don't want this to happen accidentally. |
@RyanCavanaugh Doesn't that mean that in effect I can never rely on
|
Some history, initially instanceOf checks were nominal. i.e. in side the true branch, the type is narrowed to the class, but outside it nothing happens, as we can not be sure that it is not of the type A but not created by the constructor A. We then got feedback about users thinking of So, though i agree that instanceof is a nominal check, it seems that most users do not think of the difference between nominal and structural checks when testing for types that much. As for your last example, I had a discussion about this with @ahejlsberg yesterday. We think the correct behavior is to narrow to |
For us the problem is twofold. Take the CompletionItem provider we ask extension writers to implement. The interface states that you should
Now the problem is that an extension writer doesn't get a compile error when returning a structure instead of a new'ed object but we still ignore his value since it doesn't comply to our checked. Working around this by not using |
I would say the check in |
Yes, wouldn't something like this solve the problem: class CompletionList {
private __class = 'CompletionList';
/*...*/
} Now no duck can quack like |
Well, it just makes very ugly API if I have to put these markers in every class of our system. We use classes because we see additional benefit from that (like ctor logic, etc) but it seems TypeScript is not fit for that. |
@mhegazy That's the whole point. The interface declaration says return CompletionItem or CompletionList which both are defined as classes, not interfaces. |
But when consuming There is good reason why TypeScript is structurally typed, because of the permissiveness of the language it is a superset of. Many people have tried to project concepts of other languages into JavaScript because of familiarity only to find out that it doesn't work that way. ES6 Classes are nothing more than a constructor functions with an assigned prototype and a bit of logic which throws if you don't use a magical keyword |
We also allow people to write extensions in Javascript (in which you don't get type checking) and I want to be save in case where a client returns something which is neither a CompletionList nor a CompletionItem array but something more or less random |
I think then what you are saying is that from an interface perspective, you would be completely fine with someone providing you an object that was structurally typed like |
yes, well said. maybe TypeScript should emit |
Not that this addresses the design time/runtime behaviour of I realise there maybe a difference when you are trying to execute others code and need to try to do a level of guarding against people being malicious, but considering a custom type guard to weed out random "junk" maybe sufficient (because if the TypeScript team agree about the behaviour, it will be a while before it would become available). |
We have got feedback previously about users thinking of classes nominally and interfaces structurally. but i have to say there is value in making these two interchangeable, specially for ambients, the way the definition author decided to write the declarations should not dictate your runtime semantics. I do not think we can change that now anyways, or that would be a huge breaking change. An option would be what is tracked by #202, possibly adding a tag on the classes marking them as nominal. |
TypeScript Version:
1.8.0
Code
Expected behavior:
The second to
doIt
should raise a type check error.Actual behavior:
It does not. I assume it was a conscious decision and I wonder why? This break assumptions the
instanceof
operator makes and has led to subtle bugs on our side.The text was updated successfully, but these errors were encountered: