GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Allow deriving from object and intersection types by ahejlsberg · Pull Request #13604 · microsoft/TypeScript

Conversation

@ahejlsberg

@ahejlsberg Anders Hejlsberg (ahejlsberg) commented Jan 20, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

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:

  • An interface is permitted to extend any object-like type.
  • A class is permitted to extend an expression of a constructor type with one or more construct signatures that return an object-like type.
  • A class can implements any object-like type.

Some examples:

type T1 = { a: number };
type T2 = T1 & { b: string };
type T3 = () => void;
type T4 = [string, number];

interface I1 extends T1 { x: string }  // Extend object literal
interface I2 extends T2 { x: string }  // Extend intersection
interface I3 extends T3 { x: string }  // Extend function type
interface I4 extends T4 { x: string }  // Extend tuple type

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:

type Named<T> = T & { name: string };

interface N1 extends Named<T1> { x: string } // { a: number, name: string, x: string }
interface N2 extends Named<T2> { x: string } // { a: number, b: string, name: string, x: string }

interface P1 extends Partial<T1> { x: string } // { a?: number | undefined, x: string }

The this type of an intersection is now the intersection itself:

interface Thing1 {
    a: number;
    self(): this;
}

interface Thing2 {
    b: number;
    me(): this;
}

function f1(t: Thing1 & Thing2) {
    t = t.self();  // Thing1 & Thing2
    t = t.me().self().me();  // Thing1 & Thing2
}

All of the above can be combined in lightweight mixin patterns like the following:

interface Component {
    extend<T>(props: T): this & T;
}

interface Label extends Component {
    title: string;
}

function test(label: Label) {
    const extended = label.extend({ id: 67 }).extend({ tag: "hello" });
    extended.id;  // Ok
    extended.tag;  // Ok
}

Also, mixin classes can be modeled, provided the base classes have constructors with a uniform shape:

type Constructor<T> = new () => T;

function Identifiable<T>(superClass: Constructor<T>) {
    class Class extends (superClass as Constructor<{}>) {
        id: string;
        getId() {
            return this.id;
        }
    }
    return Class as Constructor<T & Class>;
}

class Component {
    name: string;
}

const IdentifiableComponent = Identifiable(Component);

class Box extends IdentifiableComponent {
    width: number;
    height: number;
}

const box = new Box();
box.name;
box.id;
box.width;
box.height;

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.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

tony19, zpdDG4gta8XKpMCd, rictic, alitaheri, HerringtonDarkholme, ktsn, normalser, blakeembrey, steve8708, ethanresnick, and 14 more reacted with thumbs up emoji rictic, tinganho, HerringtonDarkholme, ktsn, steve8708, Igorbek, sanex3339, opensrcken, and janriemer reacted with hooray emoji rictic, alitaheri, HerringtonDarkholme, ktsn, majodev, tomachristian, mnn, steve8708, Igorbek, sanex3339, and 2 more reacted with heart emoji
All reactions
  • 👍 24 reactions
  • 🎉 9 reactions
  • ❤️ 12 reactions
Anders Hejlsberg (ahejlsberg) added 6 commits January 19, 2017 13:58
@ahejlsberg
Allow object intersection types as class/interface base types
a6c5306
@ahejlsberg
Improve efficiency of union/intersection resolved property caching
1c2f7f8
@ahejlsberg
Allow object intersection types in class implements clauses
c51e286
@ahejlsberg
Accept new baselines
f9a65e4
@ahejlsberg
Intersections as their own 'this' type
a9af10b
@ahejlsberg
Allow base constructor types to be intersections
c16c7d5
@msftclas Microsoft Contribution License Agreements (msftclas) added the cla-not-required label Jan 20, 2017
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Jan 20, 2017
Allow class to extend from a generic type parameter #4890
Closed
RyanCavanaugh
Ryan Cavanaugh (RyanCavanaugh) reviewed Jan 20, 2017
View reviewed changes
Comment thread src/compiler/checker.ts

// A valid base type is any non-generic object type or intersection of non-generic
// object types.
function isValidBaseType(type: Type): boolean {

@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) Jan 20, 2017

Copy link
Copy Markdown
Member

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 ?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ahejlsberg Anders Hejlsberg (ahejlsberg) Jan 21, 2017

Copy link
Copy Markdown
Member Author

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.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@jwbay

jwbay commented Jan 20, 2017

Copy link
Copy Markdown
Contributor

In the second block of examples, are N1 and N2 missing x: string in the commented result?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ahejlsberg

Anders Hejlsberg (ahejlsberg) commented Jan 20, 2017

Copy link
Copy Markdown
Member Author

jwbay Yes, thanks. Now fixed.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mhegazy
Mohamed Hegazy (mhegazy) approved these changes Jan 21, 2017
View reviewed changes

@mhegazy Mohamed Hegazy (mhegazy) left a comment

Copy link
Copy Markdown
Contributor

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.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Anders Hejlsberg (ahejlsberg) added 3 commits January 21, 2017 13:06
@ahejlsberg
Fix error reporting bug
ad71da0
@ahejlsberg
Add tests
615784a
@ahejlsberg
Accept new baselines
3a34cb3
@ahejlsberg
Anders Hejlsberg (ahejlsberg) merged commit 5b9004e into master Jan 21, 2017
@ahejlsberg
Anders Hejlsberg (ahejlsberg) deleted the intersectionBaseTypes branch January 21, 2017 21:38
@rotemdan

Rotem Dan (rotemdan) commented Jan 22, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Anders Hejlsberg (@ahejlsberg)

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:

// Error: Property 'someValue' does not exist on type 'this["props"]'.'

The problem might be related to the fact that this["props"] has the type:

{
    children?: React.ReactNode;
} & {
    someValue: number;
}

I've tried to work around this by using typeof this.props instead but that didn't seem to help:

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 React.Component looks like:

    // 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;
        // ...
    }
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ahejlsberg

Anders Hejlsberg (ahejlsberg) commented Jan 22, 2017

Copy link
Copy Markdown
Member Author

Rotem Dan (@rotemdan) Yes, there was a minor typo that's causing this issue. I will have a fix shortly.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Jan 22, 2017
Fix intersection apparent type #13623
Merged
@rotemdan

Rotem Dan (rotemdan) commented Jan 22, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Thanks, I temporarily worked around these errors by creating a type alias for this[props], which was relatively easy. I'm intentionally using the latest nightly builds with a large code base to try to help to quickly identify issues of this kind. If I update every day it makes it easy to speculate what could have been the cause by looking at the commits of the previous day.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@shlomiassaf

Shlomi Assaf (shlomiassaf) commented Jan 23, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Anders Hejlsberg (@ahejlsberg) this is just CRAZY!

I can now build a decorator, export it as const and export a type with the same name.
The type is a Record of a name and the decorators expected signature... I now have a dynamic typed decorator, perfect for lifescycle hooks as decorators.

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!

atrauzzi reacted with laugh emoji
All reactions
  • 😄 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@basarat Basarat Ali Syed (basarat) mentioned this pull request Jan 23, 2017
Mixin basarat/typescript-book#56
Open
@matt-gadd Matthew Gadd (matt-gadd) mentioned this pull request Jan 24, 2017
TypeScript 2.2 Object and Intersection Types dojo/compose#118
Closed
@stanhuff

stanhuff commented Jan 24, 2017

Copy link
Copy Markdown

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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@HerringtonDarkholme Herrington Darkholme (HerringtonDarkholme) mentioned this pull request Jan 25, 2017
Mixin Error HerringtonDarkholme/av-ts#16
Closed
@atrauzzi

Alexander Trauzzi (atrauzzi) commented Jan 25, 2017

Copy link
Copy Markdown

Forgive me if it's a silly question, but would this change help at all with what I'm looking for here?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@shlomiassaf Shlomi Assaf (shlomiassaf) mentioned this pull request Jan 26, 2017
Pull up and export lambdas in Annotations so that AoT Compiler can reference it. angular/angular#13138
Closed
1 task
@trusktr

Joe Pea (trusktr) commented Jan 26, 2017

Copy link
Copy Markdown
Contributor

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
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mhegazy

Mohamed Hegazy (mhegazy) commented Jan 26, 2017

Copy link
Copy Markdown
Contributor

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 new. a function does not have that.

if this is your function, i would suggest switching it to a class, and switching this.prop assignments in it to property declarations.

if this is a function you got from a different module, then consider declaring it as a calss, e.g. declare class Foo {}.

you can always cast the type to a constructable type, e.g. const Foo: { new (): FooInstance } = <any>function Foo() { }

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@shlomiassaf Shlomi Assaf (shlomiassaf) mentioned this pull request Jan 26, 2017
Proposal: AOT Compiler instructions angular/angular#14128
Closed
@trusktr

Joe Pea (trusktr) commented Jan 27, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

if this is your function, i would suggest switching it to a class
if this is a function you got from a different module, then consider declaring it as a calss

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?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@trusktr

Joe Pea (trusktr) commented Jan 27, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Oh, another thing. I know how to declare a type, for example, so that I can extend from a Backbone.View, by declaring the class definition.

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() {}
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mhegazy

Mohamed Hegazy (mhegazy) commented Jan 27, 2017

Copy link
Copy Markdown
Contributor

Joe Pea (@trusktr) you need #4890, this should land in the next few days.

trusktr reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@tamayika tamayika mentioned this pull request Jan 28, 2017
Deriving from object and intersection types problem #13732
Closed
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Jan 30, 2017
Mixin classes #13743
Merged
@trusktr

Joe Pea (trusktr) commented Jan 30, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Mohamed Hegazy (@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) {}
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mhegazy

Mohamed Hegazy (mhegazy) commented Jan 30, 2017

Copy link
Copy Markdown
Contributor

Please see #13743

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ktsn Katashin (ktsn) mentioned this pull request Feb 6, 2017
Would love to help you out but... how? ktsn/vue-comm#1
Open
@felixfbecker

Felix Becker (felixfbecker) commented Jun 11, 2017

Copy link
Copy Markdown
Contributor

Does this also allow extending from typeof expressions?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@olegdunkan olegdunkan mentioned this pull request Jul 19, 2017
Spec inconsistency for type aliases #17287
Closed
@luke-john Luke John (luke-john) mentioned this pull request Aug 27, 2017
typescript: autocomplete missing on some things stackblitz/core#42
Closed
@HerringtonDarkholme Herrington Darkholme (HerringtonDarkholme) mentioned this pull request Feb 8, 2018
Proposal: Introduce a new way to define "inherited" interfaces: likes #18762
Closed
@microsoft Microsoft (microsoft) locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) RyanCavanaugh left review comments
+1 more reviewer
@mhegazy Mohamed Hegazy (mhegazy) mhegazy approved these changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

11 participants

@ahejlsberg @jwbay @rotemdan @shlomiassaf @stanhuff @atrauzzi @trusktr @mhegazy @felixfbecker @RyanCavanaugh @msftclas
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.