Get name of declaration uniformly, even for JS-style assignment declarations.#15594
Get name of declaration uniformly, even for JS-style assignment declarations.#15594
Conversation
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.
src/compiler/checker.ts
Outdated
| } | ||
|
|
||
| let errorNode: Node; | ||
| if (propDeclaration && propDeclaration.kind === SyntaxKind.BinaryExpression) { |
There was a problem hiding this comment.
i would say it is safe to assume that if it is a BinarayExression, that it is a SpecialPropertyAssignment
There was a problem hiding this comment.
yeah, good point. It's unlikely that any other declaration (even a special ModuleExport) would get in here by mistake.
src/compiler/types.ts
Outdated
| export type DeclarationName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | BindingPattern; | ||
|
|
||
| export interface Declaration extends Node { | ||
| export interface RealDeclaration extends Node { |
There was a problem hiding this comment.
can we find a better name for this...
There was a problem hiding this comment.
I'm not certain I'm a big fan of renaming this at all.
src/compiler/types.ts
Outdated
| export type DeclarationName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | BindingPattern; | ||
|
|
||
| export interface Declaration extends Node { | ||
| export interface RealDeclaration extends Node { |
There was a problem hiding this comment.
I'm not certain I'm a big fan of renaming this at all.
src/compiler/utilities.ts
Outdated
| isTypeReferenceDirective?: boolean; | ||
| } | ||
|
|
||
| export function getNameOfDeclaration(declaration: Declaration): DeclarationName { |
There was a problem hiding this comment.
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.
src/compiler/utilities.ts
Outdated
| */ | ||
| export function hasDynamicName(declaration: Declaration): boolean { | ||
| return declaration.name && isDynamicName(declaration.name); | ||
| return getNameOfDeclaration(declaration) && isDynamicName(getNameOfDeclaration(declaration)); |
There was a problem hiding this comment.
Inefficient to look this up twice.
src/compiler/types.ts
Outdated
|
|
||
| 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; |
There was a problem hiding this comment.
Why not name this DeclarationLike?
src/compiler/checker.ts
Outdated
|
|
||
| forEach(overloads, o => { | ||
| const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; | ||
| const name = getNameOfDeclaration(o); |
There was a problem hiding this comment.
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.
src/compiler/types.ts
Outdated
| export type Declaration = | ||
| | ArrowFunction | ||
| // Binary expressions can be declarations if they are 'exports.foo = bar' expressions in JS files | ||
| | BinaryExpression |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/compiler/types.ts
Outdated
| export type Declaration = RealDeclaration | BinaryExpression; | ||
|
|
||
| export interface DeclarationStatement extends RealDeclaration, Statement { | ||
| export type Declaration = |
There was a problem hiding this comment.
How does this large discriminated union affect the compile time of the compiler itself?
There was a problem hiding this comment.
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?
src/compiler/types.ts
Outdated
| } | ||
|
|
||
| export interface ClassElement extends RealDeclaration { | ||
| export interface ClassElement extends DeclarationBase { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Another nearly-3% slowdown for a combined 106.4% slowdown. On my machine compilation went from 46.1 → 47.7 → 49.0 seconds
There was a problem hiding this comment.
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.SetAccessor | ||
| | SyntaxKind.IndexSignature | ||
| | SyntaxKind.MissingDeclaration; | ||
| _classElementBrand: any; |
There was a problem hiding this comment.
Could we feasibly remove the brand with this change?
| | SyntaxKind.MissingDeclaration | ||
| | SyntaxKind.JSDocPropertyTag | ||
| | SyntaxKind.JSDocRecordMember; | ||
| _typeElementBrand: any; |
There was a problem hiding this comment.
Could we feasibly remove the brand with this change?
|
Switching to a union for 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.
|
Sorry @rbuckton I didn't see your earlier reply in the comment thread. Your inheritance-based suggestion seems like a good one. |
This PR included a fix that I already checked in this morning.
|
OK, the discriminated union is gone in favour of a deeper interface hierarchy. |
| export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression; | ||
|
|
||
| export interface PropertyAccessExpression extends MemberExpression, Declaration { | ||
| export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { |
There was a problem hiding this comment.
Why is this a NamedDeclaration?
There was a problem hiding this comment.
This one actually does have a name property, so I'll leave it alone.
src/compiler/types.ts
Outdated
| ; | ||
|
|
||
| export interface CallExpression extends LeftHandSideExpression, Declaration { | ||
| export interface CallExpression extends LeftHandSideExpression, NamedDeclaration { |
There was a problem hiding this comment.
Why is this a NamedDeclaration?
src/compiler/types.ts
Outdated
| } | ||
|
|
||
| export interface NewExpression extends PrimaryExpression, Declaration { | ||
| export interface NewExpression extends PrimaryExpression, NamedDeclaration { |
There was a problem hiding this comment.
Why is this a NamedDeclaration?
src/compiler/types.ts
Outdated
| * ObjectLiteralElement (e.g. PropertyAssignment, ShorthandPropertyAssignment etc.) | ||
| */ | ||
| export interface ObjectLiteralExpressionBase<T extends ObjectLiteralElement> extends PrimaryExpression, Declaration { | ||
| export interface ObjectLiteralExpressionBase<T extends ObjectLiteralElement> extends PrimaryExpression, NamedDeclaration { |
There was a problem hiding this comment.
Why is this a NamedDeclaration?
There was a problem hiding this comment.
No reason! I'll change all the types without a name property to extend Declaration instead.
|
Node types with no name now correctly extend Note that |
|
We should port this to release-2.3 as well. |
|
This is now on release-2.3 as well. |
|
❗ |
|
@sandersn can we make getNameOfDeclaration part of the public utilities. |
|
Will do. In the meantime, people can still cast |
|
Added note for the breaking change in https://github.com/Microsoft/TypeScript/wiki/API-Breaking-Changes#typescript-23 |
JS-style assignment declarations have a name, but it's not stored on the name property of the BinaryExpression. This commit adds
getNameOfDeclarationto uniformly get the name of a declaration.It also reworks the declaration of
Declarationso that accessingnameis 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