Skip to content
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

Add support for TypeScript type annotations #4

Merged
merged 16 commits into from
Sep 24, 2017
Merged

Conversation

slotik
Copy link
Contributor

@slotik slotik commented Aug 20, 2017

The changes are adaptations of TypeScript's AST nodes. The most notable differences are:

  • treatment of modifiers (public, protected, private, readonly, static, abstract) is somewhat stricter
  • parameter declarations in getters and setters are restricted as in ES2017 (the TypeScript AST in principle allows for an arbitrary parameter list)
  • MethodDefinition is a NamedObjectProperty but not a ClassElement as that would require multiple inheritance; composition is used instead
  • IndexSignatureDeclaration is treated as a special case since in TypeScript it is the only TypeElement that is also a ClassElement - perhaps this should be handled differently
  • some seemingly internal TypeScript interfaces are not included; an example of this is MissingDeclaration

Martin Slota added 7 commits August 19, 2017 01:04
src/term-spec.js Outdated
@@ -32,7 +32,7 @@ declare export class NamedObjectProperty extends ObjectProperty {
name: PropertyName;
}
declare export class MethodDefinition extends NamedObjectProperty {
body: FunctionBody;
body?: FunctionBody; // undefined only on abstract class methods
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ideal. There are certainly other options but I'm not sure which one is best, so I went with this "minimum impact" approach for now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to split it out into MethodDefinition with non-optional body and AbstractMethodDefinition with no body.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this and while at it I found two bugs which I tried to fix. Note that I also had to add AbstractMethod, AbstractGetter and AbstractSetter which mirror the non-abstract counterparts.

src/term-spec.js Outdated
declare export class ImplementsClause extends HeritageClause {}

declare export class IndexSignatureDeclaration extends Term {
parent?: ClassExpression | ClassDeclaration | InterfaceDeclaration | TypeLiteralNode;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These parent? members are more for documentation than anything else. They could be turned into comments or deleted altogether.

src/term-spec.js Outdated
elements: ClassElement[];
typeParameters?: TypeParameterDeclaration[];
heritageClauses?: HeritageClause[];
elements: (ClassElement | IndexSignatureDeclaration)[];
Copy link
Contributor Author

@slotik slotik Aug 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IndexSignatureDeclaration is handled as a special case since

  1. It cannot be static nor have an access modifier;
  2. It can be present on interfaces and type literals as well.

There might be a better way of dealing with it.

src/term-spec.js Outdated
accessModifier?: any; // 'public' | 'protected' | 'private'
hasReadonlyModifier: any; // boolean
parameter: ParameterDeclaration;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up with this special way of representing constructor arguments to avoid having access/readonly modifiers on all ParameterDeclarations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@disnet Now that I'm looking at it again, it might be nicer to have ConstructorParameterDeclaration inherit from ParameterDeclaration.

src/term-spec.js Outdated

// enum

declare export class EnumDeclaration extends Statement {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum support is not strictly related to type annotations, so they could be dropped from here.

// example:
// `SomeClass[number]`
// where
// `class SomeClass { [index: number]: string }`
Copy link
Contributor Author

@slotik slotik Aug 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment above fails to mention that SomeClass[number] would then essentially resolve to the string type.

src/term-spec.js Outdated
parameter: ParameterDeclaration;
}

declare export class SemicolonClassElement extends ClassElement {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the point of representing semicolons in class bodies in the AST?

Copy link
Contributor Author

@slotik slotik Aug 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sneaked in from the TypeScript source. The comment in there says the following:

For when we encounter a semicolon in a class declaration. ES6 allows these as class elements.

So it's perhaps only used for recording and later emitting these empty semicolon statments as can be seen here.

When I added it, I probably thought it had some deep significance. Shall I remove it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah lets remove it. We of course have to parse them but as long as TypeScript doesn't give any semantic meaning to the extra semis there no reason to preserve them in the AST.

…f setter parameter. Made a separate AbstractMethodDefinition term.
}

declare export class DataProperty extends NamedObjectProperty {
hasQuestionToken: any; // boolean
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I may have overdone it with the question tokens. I think I added them only where the TypeScript AST permits them but some of them (like this one, but also the one on FunctionDeclaration) don't seem to make too much sense...

@disnet
Copy link
Member

disnet commented Aug 22, 2017

As discussed in sweet-js/sweet-core#742 we are going to probably need to use a reducer at the end of expansion to transform our shift-like AST into a babel AST to do codegen. Our type nodes don't need to be identical with what babel does as long as transforming ours to theirs is straightforward. That said one thing I'd definitely like to copy from babel is splitting the type nodes in Ts* and Flow* variants (https://github.com/babel/babylon/blob/master/src/types.js#L796).

@slotik
Copy link
Contributor Author

slotik commented Aug 26, 2017

@disnet I can certainly make an attempt to split TypeNode into TsTypeNode and FlowTypeNode (there might be a bit more to this but that's the most important change I guess).

The Babel AST seems rather undetailed when it comes to Flow type annotations - it just says type FlowTypeAnnotation = Node and that's that. There's a bunch of other Flow-related nodes but I struggle with figuring out how they are related to one another.

I don't use Flow very much, so I'm quite ignorant. But I'm thinking I could either follow Flow documentation and create nodes for everything I see there, or I could follow Flow AST. The former is easier but may lead to imprecision and/or omissions. The latter sounds like fun since I don't know OCaml at all - the only issue may be that it will take a while until I'm done with it.

Another question: Is any sort of structural sharing across TsTypeNode and FlowTypeNode subclasses desirable? I'm thinking things might be simpler if I don't try to share anything but then there will clearly be some "duplication" since they ultimately have a lot in common.

Martin Slota added 2 commits August 26, 2017 23:03
@disnet
Copy link
Member

disnet commented Aug 29, 2017

Ah yeah, the babylon spec is missing all the flow detail. We could probably figure it out from reading the plugin that generates the nodes though.

Is any sort of structural sharing across TsTypeNode and FlowTypeNode subclasses desirable?

I think no. Yeah we will probably have some duplication in the spec but that's fine I think; having them separate makes it easier if flow or ts evolves and forces a change to their respective AST.

btw, as long as we split the nodes (prefix ts specific nodes with Ts* and flow with Flow*) we probably can and should split the work into two PRs. No reason to block the ts nodes on figuring out the flow nodes.

@slotik
Copy link
Contributor Author

slotik commented Sep 10, 2017

I'm sorry for the long delay.

I added the Ts prefix where it seemed like a good idea. I didn't for example fork interface statements as such, instead I forked TypeElement. Let me know if anything needs to be adjusted.

I'm not aware of any outstanding issues in this PR - I'll continue working on it if/when I get more feedback.

As for Flow types, I'll take a look at the babel plugin and I can start adding them to a separate fork (which builds on the work here) within the following week.

@disnet
Copy link
Member

disnet commented Sep 10, 2017

I'm sorry for the long delay.

No worries, this is open source. It takes how long it takes 😄

I'll try and do a review soon and then we can get it merged and published under a pre-release tag. I suspect we'll find tweaks we want to make as we go about updating the parser.

src/term-spec.js Outdated
body: FunctionBody;
}

declare export class AbstractMethodDefinition extends Term {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be TsAbstractMethodDefinition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes a lot of sense. The change is already in.

src/term-spec.js Outdated
name: PropertyName;
binding?: ObjectAssignmentTarget | ArrayAssignmentTarget | AssignmentTargetIdentifier | MemberAssignmentTarget | AssignmentTargetWithDefault;
}



// class

declare export class ClassExpression extends Expression {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love renaming super to heritageClauses. How about we split the TS stuff out into ClassExpression and TsClassExpression (with shared attributes in ClassExpressionBase)?

Alternatively we could type it as super: Expression | HeritageClause[] but in a given AST it will only ever be one or the other so it's nice to keep that clear with two different nodes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the split of both ClassExpression and ClassDeclaration. Once that was done, it also seemed natural to split ClassElement, so I did that, too. The nice thing about the result is that it is backwards-compatible with the previous version of classes.

I also added ClassExpressionBase, ClassDeclarationBase and ClassElementBase - they only hold one field each but are perhaps still more preferable than duplicating those fields (and later triplicating for Flow* nodes).

@slotik slotik changed the title Add support for type annotations Add support for TypeScript type annotations Sep 17, 2017
Copy link
Member

@disnet disnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! I'm going to merge but hold off on publishing until we get experience with it while implementing the changes in core.

@disnet disnet merged commit b2b6bea into sweet-js:master Sep 24, 2017
@cmcaine
Copy link

cmcaine commented Oct 2, 2017

What does the merge of this pull request mean for sweet.js?

Will I soon be able to run sjs example.ts.sjs > example.ts and have any macros in the first file expanded and the rest (including types) passed through unchanged?

Thanks!

@disnet
Copy link
Member

disnet commented Oct 3, 2017

That's the plan!

@cmcaine
Copy link

cmcaine commented Oct 3, 2017 via email

@disnet
Copy link
Member

disnet commented Oct 3, 2017 via email

@alexshpilkin
Copy link

@disnet Can you explain what's missing? (I know how frontends and Scheme macros work in general, but not any particulars about Sweet). I understand this PR added the AST definitions; what remains aside from a parser and an emitter in sweet-core?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants