As below codes showing, according to the error message, seems that type A is equivalent to an interface, but type A2 is a strict type kind type
type A<T> = {
[P in keyof { foo: any }]: number
}
interface B<T> extends A<T> { }
type A2<T> = {
[P in keyof T]: number
}
interface B2<T> extends A2<T> {} // An interface may only extend a class or another interface.
@zheeeng I suggest reading this SO answer: https://stackoverflow.com/a/37233777/516210
The error message is out of date. In essence, the rule is that a class or interface may only extend a type _with a known set of properties_, since the compiler needs to check whether the types of properties declared in the class or interface are compatible with the types of the corresponding properties (if any) of the base type. A<T> has a known set of properties (i.e., just foo) but A2<T> doesn't. See #13604 for more information. Want to repurpose this issue to have the error message updated?
@mattmccutchen yes, expect a more accurate error message.
@zheeeng Then please at a minimum update the issue subject, otherwise the TypeScript team is liable to close it and put the onus on you to file another. They are not big enough to be as helpful with everything as one might hope.
(@zheeng Sorry for the misdirected mention.)
Since these types don't just have properties, "members" might be a better word for this.
An interface can only extend an object or intersection type with statically known members.
A class can only implement an object or intersection type with statically known members.
I almost didn't want to include "object or intersection", but then this message doesn't work for primitives.
To be pedantic, string & {x: string} is an intersection type with statically known members, so we should probably say "... an object type or intersection of object types with statically known members". And we also need to update the "Base constructor return type '{0}' is not a class or interface type" message.
I'll be happy to write a PR once the TypeScript team shows signs of having time to deal with more PRs. (TypeScript team, is there any way I can help you move faster? In theory, I can always start maintaining a fork, but realistically no one will use it because no one will trust me to provide competent long-term maintenance, and indeed I might not be willing to. You have won the community's trust with your good work so far (modulo the level of controversy that is to be expected in a project like this), so now you have become the bottleneck.)
@mattmccutchen I'd like to start contributing to open source, and this seems like a good starting point for typescript. Could you perhaps guide me how I could solve this issue?
@fabioloreggian Sure. You picked a nice and easy issue to start with. Just find the three relevant diagnostic templates in src/compiler/diagnosticMessages.json:
Base constructor return type '{0}' is not a class or interface type.A class may only implement another class or interface.An interface may only extend a class or another interface.update the wording along the lines discussed above, and update the references from src/compiler/checker.ts. Each diagnostic template defined in src/compiler/diagnosticMessages.json becomes a property of the Diagnostics object, where the property name is determined by mangling the English text of the diagnostic into a valid identifier according to certain rules. For instance, the first diagnostic listed above becomes Diagnostics.Base_constructor_return_type_0_is_not_a_class_or_interface_type and is referenced that way from src/compiler/checker.ts. You'll need to change the reference to match the new text of the diagnostic.
While you're at it, you can delete the A class may only extend another class. diagnostic (which has been obsolete and unused for several years) from src/compiler/diagnosticMessages.json.
Seems like no one is working on this. I will take this up as my first contribution to TypeScript.
I create a PR with the changes but I am not able to figure out where should I make the changes for tests to pass. Can anyone guide me in this?