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

Get name of declaration uniformly, even for JS-style assignment declarations. #15594

Merged
merged 12 commits into from
May 10, 2017

Conversation

sandersn
Copy link
Member

@sandersn sandersn commented May 4, 2017

JS-style assignment declarations have a name, but it's not stored on the name property of the BinaryExpression. This commit adds getNameOfDeclaration to uniformly get the name of a declaration.

It also reworks the declaration of Declaration so that accessing name is an error unless the type does not include BinaryExpression.

The implementation is crude, especially the reworking of the type definitions. Ideas for improvement are welcome.

Improves the fix for #15586

JS-style assignment declarations have a name, but it's not stored on the
name property of the BinaryExpression. This commit adds
`getNameOfDeclaration` to uniformly get the name of a declaration.

It also reworks the declaration of `Declaration` so that accessing
`name` is an error unless the type does *not* include
BinaryExpression.
return;
}

let errorNode: Node;
if (propDeclaration && propDeclaration.kind === SyntaxKind.BinaryExpression) {
Copy link
Contributor

Choose a reason for hiding this comment

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

i would say it is safe to assume that if it is a BinarayExression, that it is a SpecialPropertyAssignment

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, good point. It's unlikely that any other declaration (even a special ModuleExport) would get in here by mistake.

@@ -603,12 +603,15 @@ namespace ts {

export type DeclarationName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | BindingPattern;

export interface Declaration extends Node {
export interface RealDeclaration extends Node {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we find a better name for this...

Copy link
Member

Choose a reason for hiding this comment

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

I'm not certain I'm a big fan of renaming this at all.

@mhegazy mhegazy requested a review from rbuckton May 4, 2017 23:38
@@ -603,12 +603,15 @@ namespace ts {

export type DeclarationName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | BindingPattern;

export interface Declaration extends Node {
export interface RealDeclaration extends Node {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not certain I'm a big fan of renaming this at all.

@@ -11,6 +11,36 @@ namespace ts {
isTypeReferenceDirective?: boolean;
}

export function getNameOfDeclaration(declaration: Declaration): DeclarationName {
Copy link
Member

Choose a reason for hiding this comment

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

Can't we just use the type Declaration | BinaryExpression here? If not I would rather we have a DeclarationLike alias for that rather than renaming Declaration to RealDeclaration.

@@ -1994,7 +2024,7 @@ namespace ts {
* Symbol.
*/
export function hasDynamicName(declaration: Declaration): boolean {
return declaration.name && isDynamicName(declaration.name);
return getNameOfDeclaration(declaration) && isDynamicName(getNameOfDeclaration(declaration));
Copy link
Member

Choose a reason for hiding this comment

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

Inefficient to look this up twice.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

_declarationBrand: any;
name?: DeclarationName;
}

export interface DeclarationStatement extends Declaration, Statement {
// Binary expressions can be declarations if they are 'exports.foo = bar' expressions in JS files
export type Declaration = RealDeclaration | BinaryExpression;
Copy link
Member

Choose a reason for hiding this comment

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

Why not name this DeclarationLike?

@@ -18106,17 +18110,18 @@ namespace ts {

forEach(overloads, o => {
const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags;
const name = getNameOfDeclaration(o);
Copy link
Member

Choose a reason for hiding this comment

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

Despite the repetition, calling getNameOfDeclaration(o) everywhere we had o.name is more efficient since we don't look up the name if there are no deviations.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks! Fixed.

export type Declaration =
| ArrowFunction
// Binary expressions can be declarations if they are 'exports.foo = bar' expressions in JS files
| BinaryExpression
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about:

    export interface JsExportsIdentifier extends Identifier {
        text: "exports";
    }

    export interface JsExportsPropertyAccess extends PropertyAccessExpression {
        expression: JsExportsIdentifier;
    }

    export interface JsExportsElementAccess extends ElementAccessExpression {
        expression: JsExportsIdentifier;
    }

    export interface JsExportExpression extends AssignmentExpression<Token<SyntaxKind.EqualsToken>>, Declaration {
        left: JsExportsPropertyAccess | JsExportsElementAccess;
    }

Then we have a type that represents a specific shape for a BinaryExpression that represents a declaration, rather than a large discriminated union?

Copy link
Member Author

Choose a reason for hiding this comment

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

This approach would require separate types for this.x = 12 and Foo.x = 12 and Foo.prototype.x assignments, wouldn't it?

For the last two, there's no way to change the inherited type of text, since it can vary, though I think JsPropertyPropertyAccess could just have expression: Identifier.

Copy link
Member

Choose a reason for hiding this comment

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

So this is for more than just 'exports.foo = bar' as per the comment? If that's the case, then disregard. I generally haven't been a fan of us tagging BinaryExpression, PropertyAccessExpression, and CallExpression and NewExpression as Declaration, but that ship has sailed.

export type Declaration = RealDeclaration | BinaryExpression;

export interface DeclarationStatement extends RealDeclaration, Statement {
export type Declaration =
Copy link
Member

Choose a reason for hiding this comment

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

How does this large discriminated union affect the compile time of the compiler itself?

Copy link
Member Author

Choose a reason for hiding this comment

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

Based on a couple of before/after runs just timed using time, the time goes from 46.1 to 47.7 seconds, or 3.3% slower. I think that's OK — @mhegazy do you have an opinion?

@@ -1780,12 +1863,30 @@ namespace ts {
kind: SyntaxKind.ClassExpression;
}

export interface ClassElement extends RealDeclaration {
export interface ClassElement extends DeclarationBase {
Copy link
Member

Choose a reason for hiding this comment

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

I have wanted to make ClassElement and TypeElement discriminated unions (and replace ObjectLiteralElement with ObjectLiteralElementLike), but I've held off due to performance-related issues with respect to discriminated unions.

Copy link
Member Author

Choose a reason for hiding this comment

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

I will try it and see.

Copy link
Member Author

Choose a reason for hiding this comment

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

Another nearly-3% slowdown for a combined 106.4% slowdown. On my machine compilation went from 46.1 → 47.7 → 49.0 seconds

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 we should steer clear from large discriminated unions, including this one for Declaration. Is there another way we can structure it? Something like:

interface Declaration extends Node {
  _declarationBrand: any;  
}
interface DeclarationWithName extends Declaration {
  name?: DeclarationName;
}

Have the expressions inherit from Declaration and have other named declarations inherit from DeclarationWithName?

| SyntaxKind.GetAccessor
| SyntaxKind.SetAccessor
| SyntaxKind.IndexSignature
| SyntaxKind.MissingDeclaration;
_classElementBrand: any;
Copy link
Member

Choose a reason for hiding this comment

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

Could we feasibly remove the brand with this change?

| SyntaxKind.IndexSignature
| SyntaxKind.MissingDeclaration
| SyntaxKind.JSDocPropertyTag
| SyntaxKind.JSDocRecordMember;
_typeElementBrand: any;
Copy link
Member

Choose a reason for hiding this comment

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

Could we feasibly remove the brand with this change?

@sandersn
Copy link
Member Author

sandersn commented May 8, 2017

Switching to a union for Declaration slows the compiler's build time down 3%, and switching all of Declaration, ClassElement and TypeElement slows build time down 6%.

For example, on my machine the build went from 46.1 → 47.7 → 49.0 seconds.

I'm not sure whether either of those steps are worthwhile. Let's discuss with @mhegazy in person today.

Slows down compilation another 3%, so I'm not sure we'll want to take
this change.
@sandersn
Copy link
Member Author

sandersn commented May 8, 2017

Sorry @rbuckton I didn't see your earlier reply in the comment thread. Your inheritance-based suggestion seems like a good one.

@sandersn
Copy link
Member Author

sandersn commented May 8, 2017

OK, the discriminated union is gone in favour of a deeper interface hierarchy.

@@ -1417,7 +1419,7 @@ namespace ts {
export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression | ParenthesizedExpression;
export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression;

export interface PropertyAccessExpression extends MemberExpression, Declaration {
export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this a NamedDeclaration?

Copy link
Member Author

Choose a reason for hiding this comment

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

This one actually does have a name property, so I'll leave it alone.

@@ -1449,7 +1451,7 @@ namespace ts {
| SuperElementAccessExpression
;

export interface CallExpression extends LeftHandSideExpression, Declaration {
export interface CallExpression extends LeftHandSideExpression, NamedDeclaration {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this a NamedDeclaration?

@@ -1468,7 +1470,7 @@ namespace ts {
typeArguments?: NodeArray<TypeNode>;
}

export interface NewExpression extends PrimaryExpression, Declaration {
export interface NewExpression extends PrimaryExpression, NamedDeclaration {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this a NamedDeclaration?

@@ -1403,7 +1405,7 @@ namespace ts {
* JSXAttribute or JSXSpreadAttribute. ObjectLiteralExpression, on the other hand, can only have properties of type
* ObjectLiteralElement (e.g. PropertyAssignment, ShorthandPropertyAssignment etc.)
*/
export interface ObjectLiteralExpressionBase<T extends ObjectLiteralElement> extends PrimaryExpression, Declaration {
export interface ObjectLiteralExpressionBase<T extends ObjectLiteralElement> extends PrimaryExpression, NamedDeclaration {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this a NamedDeclaration?

Copy link
Member Author

Choose a reason for hiding this comment

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

No reason! I'll change all the types without a name property to extend Declaration instead.

@sandersn
Copy link
Member Author

sandersn commented May 9, 2017

Node types with no name now correctly extend Declaration directly instead of claiming that they might have a node.

Note that getNameOfDeclaration doesn't handle these node kinds differently. It just returns node.name which will be undefined for them.

@mhegazy
Copy link
Contributor

mhegazy commented May 10, 2017

We should port this to release-2.3 as well.

@sandersn sandersn merged commit 3768dae into master May 10, 2017
@sandersn sandersn deleted the get-name-of-declaration-wrapper branch May 10, 2017 15:08
@sandersn
Copy link
Member Author

This is now on release-2.3 as well.

@ghost
Copy link

ghost commented May 19, 2017


The public Declaration.name was removed and replaced with an internal getNameOfDeclaration.
This is a breaking change and there's no clear path forward for users.

@mhegazy
Copy link
Contributor

mhegazy commented May 19, 2017

@sandersn can we make getNameOfDeclaration part of the public utilities.

@sandersn
Copy link
Member Author

Will do. In the meantime, people can still cast Declaration down to NamedDeclaration to get the name property.

@mhegazy mhegazy added Breaking Change Would introduce errors in existing code API Relates to the public API for TypeScript labels May 19, 2017
@mhegazy
Copy link
Contributor

mhegazy commented May 19, 2017

@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
API Relates to the public API for TypeScript Breaking Change Would introduce errors in existing code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants