ES6 should be valid TypeScript #2606

Closed
ChristianKohler opened this Issue Apr 3, 2015 · 13 comments

Comments

Projects
None yet
7 participants
@ChristianKohler

Since TypeScript is a superset of JavaScript it should be possible to compile ES6 code without errors.

This valid ES6 snippets throws the following error:

Error:(3, 14) TS2339: Property 'weight' does not exist on type 'Car'.
class Car {
    constructor(weight) {
        this.weight = weight;
    }
}

Is this a bug or a 'feature'?

@danielearwicker

This comment has been minimized.

Show comment
Hide comment
@danielearwicker

danielearwicker Apr 3, 2015

You'll find with your example that although the compiler says the code has a type error, it will still emit valid ES, e.g. as ES5:

var Car = (function () {
    function Car(weight) {
        this.weight = weight;
    }
    return Car;
})();

TS has three levels of correctness. If the input code is syntactically correct (prior to type checking) then it can generate ES output, and it is "valid" TS. At this first level, TS is a superset of ES, in that the set of valid TS programs is larger than the set of valid ES programs (because it includes all the valid ES programs plus those with type annotations).

The second level is type-correctness, which is what your error is complaining about. At this level, TS can act as a subset of ES: some valid ES programs, such as your example, are not type-correct TS programs.

So if you want your program to jump both these hurdles, then is TS is not a simple superset of ES. If it was, then there would be no value at all in having a static type system, as it would accept every ES program without errors, and so add no value over ordinary ES. So this is most definitely a feature, not a 'bug'.

Note that you can effectively eradicate level 1 by using the --noEmitOnError flag.

An alternative approach that a compiler could adopt would be to infer from your constructor code that the class Car must have a property called weight (although it would have to give it the type any).

But TS takes the approach of requiring you to declare the shape of your classes and modules. This is important because it means that you will know when you are making a change that will affect callers, i.e. when you are changing the interfaces between separated concerns in your code:

class Car {
    weight: number;

    constructor(weight: number) {
        this.weight = weight;
    }
}

Note I've also added an explicit statement of the type of the parameter to the constructor. This satisfies the third level of correctness: noImplicitAny, in which the compiler is not allowed to automatically give the type any to variables for which it cannot deduce a more specific type (with one exception: free functions have a this of type any, but hopefully that will be fixed soon!)

You'll find with your example that although the compiler says the code has a type error, it will still emit valid ES, e.g. as ES5:

var Car = (function () {
    function Car(weight) {
        this.weight = weight;
    }
    return Car;
})();

TS has three levels of correctness. If the input code is syntactically correct (prior to type checking) then it can generate ES output, and it is "valid" TS. At this first level, TS is a superset of ES, in that the set of valid TS programs is larger than the set of valid ES programs (because it includes all the valid ES programs plus those with type annotations).

The second level is type-correctness, which is what your error is complaining about. At this level, TS can act as a subset of ES: some valid ES programs, such as your example, are not type-correct TS programs.

So if you want your program to jump both these hurdles, then is TS is not a simple superset of ES. If it was, then there would be no value at all in having a static type system, as it would accept every ES program without errors, and so add no value over ordinary ES. So this is most definitely a feature, not a 'bug'.

Note that you can effectively eradicate level 1 by using the --noEmitOnError flag.

An alternative approach that a compiler could adopt would be to infer from your constructor code that the class Car must have a property called weight (although it would have to give it the type any).

But TS takes the approach of requiring you to declare the shape of your classes and modules. This is important because it means that you will know when you are making a change that will affect callers, i.e. when you are changing the interfaces between separated concerns in your code:

class Car {
    weight: number;

    constructor(weight: number) {
        this.weight = weight;
    }
}

Note I've also added an explicit statement of the type of the parameter to the constructor. This satisfies the third level of correctness: noImplicitAny, in which the compiler is not allowed to automatically give the type any to variables for which it cannot deduce a more specific type (with one exception: free functions have a this of type any, but hopefully that will be fixed soon!)

@mhegazy mhegazy added the Question label Apr 3, 2015

@danquirk

This comment has been minimized.

Show comment
Hide comment
@danquirk

danquirk Apr 3, 2015

Member

I'll note that we have had requests for better support for this particular pattern. I can't find the related issues right now (that's happening more frequently these days...)

Member

danquirk commented Apr 3, 2015

I'll note that we have had requests for better support for this particular pattern. I can't find the related issues right now (that's happening more frequently these days...)

@nycdotnet

This comment has been minimized.

Show comment
Hide comment
@nycdotnet

nycdotnet Apr 3, 2015

#766
#2393

;-)

Note for the original poster, this will work as you expect:

class Car {
    constructor(public weight) {
    }
}

emits as:

var Car = (function () {
    function Car(weight) {
        this.weight = weight;
    }
    return Car;
})();

#766
#2393

;-)

Note for the original poster, this will work as you expect:

class Car {
    constructor(public weight) {
    }
}

emits as:

var Car = (function () {
    function Car(weight) {
        this.weight = weight;
    }
    return Car;
})();
@basarat

This comment has been minimized.

Show comment
Hide comment
@basarat

basarat Apr 4, 2015

Contributor

Perhaps #766 should reopened and #2606 and #2393 closed as duplicates. Or even create a new clean issue, if you don't like existing proposals and mark all these as duplicates ❤️

Contributor

basarat commented Apr 4, 2015

Perhaps #766 should reopened and #2606 and #2393 closed as duplicates. Or even create a new clean issue, if you don't like existing proposals and mark all these as duplicates ❤️

@DanielRosenwasser

This comment has been minimized.

Show comment
Hide comment
@DanielRosenwasser

DanielRosenwasser Apr 4, 2015

Member

I just jumped between marking this as a duplicate and turning this into a meta-issue, but I don't think we can turn something this broad into a meta issue.

Member

DanielRosenwasser commented Apr 4, 2015

I just jumped between marking this as a duplicate and turning this into a meta-issue, but I don't think we can turn something this broad into a meta issue.

@DanielRosenwasser

This comment has been minimized.

Show comment
Hide comment
@DanielRosenwasser

DanielRosenwasser Apr 4, 2015

Member

I think that in general, the cost of the solution to this on the user end (declaring a member) is not high enough to add complexity to the type system, which itself is incurred on the user as well.

Member

DanielRosenwasser commented Apr 4, 2015

I think that in general, the cost of the solution to this on the user end (declaring a member) is not high enough to add complexity to the type system, which itself is incurred on the user as well.

@basarat

This comment has been minimized.

Show comment
Hide comment
@basarat

basarat Apr 4, 2015

Contributor

Duplicate of #766

Contributor

basarat commented Apr 4, 2015

Duplicate of #766

@ChristianKohler

This comment has been minimized.

Show comment
Hide comment
@ChristianKohler

ChristianKohler Apr 4, 2015

Thank you all for the comprehensive clarification. That was quick :-)

I agree with @sccolbert (#2393):

I was coming at this question from the angle of a new user gradually moving some existing ES6 project to TypeScript. When TypeScript claims (aims) to be a "superset of ES6", a new user would expect valid ES6 code to compile without error, just like this ES5 code compiles without error..

I think that a big part of the success of less/scss is due to the full compatibility with css. Just start with css syntax and gradually learn/use more and more superset features. The same applies to Babel as well. I can just add Babel to my build pipeline without rewriting my exiting code. And I think the same should apply to TypeScript.

I really like how TypeScript becomes more compatible with ES6 with 1.5. But with issues like this, the ES6 circle (from the ngConf slide) will never be completely inside the TypeScript circle and therefore TypeScript wouldn't be a superset of JavaScript but a own language. (Sorry, sounds a little bit harsh)

@DanielRosenwasser: I think, it is not just about the cost of declaring a member in this case but the ES6 compatibility as a whole.

@basarat: I don't understand how #766 is related with ES6 compatibility. Isn't this issue rather a duplicate of #2393?

I am interested on what we should do with issues like these. What about tagging issues which are related to ES6 incompatibly? Or creating a ES6 incompatibility list?

Thank you all for the comprehensive clarification. That was quick :-)

I agree with @sccolbert (#2393):

I was coming at this question from the angle of a new user gradually moving some existing ES6 project to TypeScript. When TypeScript claims (aims) to be a "superset of ES6", a new user would expect valid ES6 code to compile without error, just like this ES5 code compiles without error..

I think that a big part of the success of less/scss is due to the full compatibility with css. Just start with css syntax and gradually learn/use more and more superset features. The same applies to Babel as well. I can just add Babel to my build pipeline without rewriting my exiting code. And I think the same should apply to TypeScript.

I really like how TypeScript becomes more compatible with ES6 with 1.5. But with issues like this, the ES6 circle (from the ngConf slide) will never be completely inside the TypeScript circle and therefore TypeScript wouldn't be a superset of JavaScript but a own language. (Sorry, sounds a little bit harsh)

@DanielRosenwasser: I think, it is not just about the cost of declaring a member in this case but the ES6 compatibility as a whole.

@basarat: I don't understand how #766 is related with ES6 compatibility. Isn't this issue rather a duplicate of #2393?

I am interested on what we should do with issues like these. What about tagging issues which are related to ES6 incompatibly? Or creating a ES6 incompatibility list?

@danielearwicker

This comment has been minimized.

Show comment
Hide comment
@danielearwicker

danielearwicker Apr 4, 2015

I think, it is not just about the cost of declaring a member in this case but the ES6 compatibility as a whole.

That's what I assumed you were asking - my comment above is an attempt to answer that wider question.

Slides like the ngConf one you mentioned (concentric circles) are not accurate depictions of compatibility. They are depictions of feature sets. TS is only a compatibility-superset of ES if you ignore static type checking.

I think, it is not just about the cost of declaring a member in this case but the ES6 compatibility as a whole.

That's what I assumed you were asking - my comment above is an attempt to answer that wider question.

Slides like the ngConf one you mentioned (concentric circles) are not accurate depictions of compatibility. They are depictions of feature sets. TS is only a compatibility-superset of ES if you ignore static type checking.

@basarat

This comment has been minimized.

Show comment
Hide comment
@basarat

basarat Apr 17, 2015

Contributor

This can be eased with tooling though: https://github.com/TypeStrong/atom-typescript#quick-fix

addclassmember

Contributor

basarat commented Apr 17, 2015

This can be eased with tooling though: https://github.com/TypeStrong/atom-typescript#quick-fix

addclassmember

@mhegazy

This comment has been minimized.

Show comment
Hide comment
@mhegazy

mhegazy Apr 17, 2015

Contributor

@basarat this is awesome 🏆 💯

Contributor

mhegazy commented Apr 17, 2015

@basarat this is awesome 🏆 💯

@basarat

This comment has been minimized.

Show comment
Hide comment
@basarat

basarat Apr 18, 2015

Contributor

@mhegazy your language service is awesome! 🌹

Contributor

basarat commented Apr 18, 2015

@mhegazy your language service is awesome! 🌹

@DanielRosenwasser

This comment has been minimized.

Show comment
Hide comment
@DanielRosenwasser

DanielRosenwasser Apr 18, 2015

Member

💯

Very cool @basarat ❗️

Member

DanielRosenwasser commented Apr 18, 2015

💯

Very cool @basarat ❗️

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.