-
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
Allow deriving from object and intersection types #13604
Conversation
@@ -3951,15 +3956,22 @@ namespace ts { | |||
return true; | |||
} | |||
|
|||
// A valid base type is any non-generic object type or intersection of non-generic | |||
// object types. | |||
function isValidBaseType(type: Type): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return type could be : type is BaseType
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, BaseType
is the most restrictive type we can express for a valid base type, but there are still BaseType
instances that aren't valid base types (e.g. intersections containing type parameters). So, wouldn't be correct in the negative sense.
In the second block of examples, are N1 and N2 missing |
@jwbay Yes, thanks. Now fixed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add some tests for the changes in the apparent type for this in intersection types.
I'm not 100% sure but it seems like this change breaks a common pattern I use (and probably others, at least in the future) with React. This worked in yesterday's nightly build: export class MyComponent extends React.Component<{ someValue: number }, {}> {
shouldComponentUpdate(nextProps: this["props"], nextState: this["state"]) {
if (nextProps.someValue > 0) {
// ...
}
}
} But now errors:
The problem might be related to the fact that {
children?: React.ReactNode;
} & {
someValue: number;
} I've tried to work around this by using export class MyComponent extends React.Component<{ someValue: number }, {}> {
shouldComponentUpdate(nextProps: typeof this.props, nextState: typeof this.state) {
if (nextProps.someValue > 0) {
// ...
}
}
}
// Error on 'typeof this.props': Identifier expected The relevant ambient declaration for // Base component for plain JS classes
class Component<P, S> implements ComponentLifecycle<P, S> {
constructor(props?: P, context?: any);
constructor(...args: any[]);
// ...
props: { children?: ReactNode } & P;
state: S;
// ...
} |
@rotemdan Yes, there was a minor typo that's causing this issue. I will have a fix shortly. |
Thanks, I temporarily worked around these errors by creating a type alias for |
@ahejlsberg this is just CRAZY! I can now build a decorator, export it as const and export a type with the same name. export class Class implements MyDecorator<'myMethod'> {
@MyDecorator()
myMethod() { // myMethod's signature is fixed, if changed -> type error! if name changes -> type error
}
} This is getting to be the best type system ever, I am really amazed! thank you guys! |
It would be very nice if a class decorator could "replace" the original class type with respect to the type system so that we could use such a decorator to fully implement a mixin, both the implementation at runtime and the extended type for compile time. |
Forgive me if it's a silly question, but would this change help at all with what I'm looking for here? |
Brand new to TS here. Is there a simple way to make this work? function Foo() {}
class Bar extends Foo {
constructor() {
super()
console.log('Bar!')
}
}
new Bar |
A class needs to inherit from something that has a "construct signature"; i.e. a declaration of what the shape of the object it returns if called with if this is your function, i would suggest switching it to a class, and switching if this is a function you got from a different module, then consider declaring it as a calss, e.g. you can always cast the type to a constructable type, e.g. |
In reality it is a dynamically generated class as in the following (working) cases: class Foo extends multiple(One, Two, Three) {} class Foo extends MixinOne(MixinTwo(Three)) {} I am relying on those features to do interesting things. These type of things are possible in JS. Can I use interfaces somehow? |
Oh, another thing. I know how to declare a type, for example, so that I can extend from a But how do I declare that some function returns a specific class? F.e., // Third party
function get(){
return Backbone.View
}
// My file
Class Foo extends get() {} |
@mhegazy Once released, how would I write that last example? Is the following correct? interface Constructable<T> {
new (...args): T;
}
// Third party
function get(): Constructable<Backbone.View> {
return Backbone.View
}
// My file
Class Foo extends get() {} What about the following? function multiple(...classes: whatGoesHere?): Constructable<whatGoesHere?> {
// uses Proxy
}
class Foo extends multiple(One, Two, Three) {} |
Please see #13743 |
Does this also allow extending from |
With this PR we permit classes and interfaces to derive from object types and intersections of object types. Furthermore, in intersections of object types we now instantiate the
this
type using the intersection itself. Collectively these changes enable several interesting "mixin" patterns.In the following, a type is said to be object-like if it is a named type that denotes an object type or an intersection of object types. Object-like types include named object literal types, function types, constructor types, array types, tuple types, mapped types, and intersections of any of those.
Interfaces and classes may now extend and implement types as follows:
extend
any object-like type.extend
an expression of a constructor type with one or more construct signatures that return an object-like type.implements
any object-like type.Some examples:
An interface or class cannot extend a naked type parameter because it is not possible to consistently verify there are no member name conflicts in instantiations of the type. However, an interface or class can now extend an instantiation of a generic type alias, and such a type alias can intersect naked type parameters. For example:
The
this
type of an intersection is now the intersection itself:All of the above can be combined in lightweight mixin patterns like the following:
Also, mixin classes can be modeled, provided the base classes have constructors with a uniform shape:
We're still contemplating type system extensions that would allow the last example to be written without type assertions and in a manner that would work for arbitrary constructor types. For example, see #4890.
EDIT: Mixin classes are now implemented by #13743.
Fixes #10591.
Fixes #12986.