-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
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
return; | ||
} | ||
|
||
let errorNode: Node; | ||
if (propDeclaration && propDeclaration.kind === SyntaxKind.BinaryExpression) { |
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.
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.
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.
src/compiler/types.ts
Outdated
@@ -603,12 +603,15 @@ namespace ts { | |||
|
|||
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.
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...
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.
I'm not certain I'm a big fan of renaming this at all.
src/compiler/types.ts
Outdated
@@ -603,12 +603,15 @@ namespace ts { | |||
|
|||
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.
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.
src/compiler/utilities.ts
Outdated
@@ -11,6 +11,36 @@ namespace ts { | |||
isTypeReferenceDirective?: boolean; | |||
} | |||
|
|||
export function getNameOfDeclaration(declaration: Declaration): DeclarationName { |
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.
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
@@ -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)); |
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.
Inefficient to look this up twice.
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.
Fixed.
src/compiler/types.ts
Outdated
_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; |
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.
Why not name this DeclarationLike
?
src/compiler/checker.ts
Outdated
@@ -18106,17 +18110,18 @@ namespace ts { | |||
|
|||
forEach(overloads, o => { | |||
const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; | |||
const name = getNameOfDeclaration(o); |
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.
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.
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.
Thanks! Fixed.
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.
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?
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.
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.
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.
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.
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?
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.
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
@@ -1780,12 +1863,30 @@ namespace ts { | |||
kind: SyntaxKind.ClassExpression; | |||
} | |||
|
|||
export interface ClassElement extends RealDeclaration { | |||
export interface ClassElement extends DeclarationBase { |
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.
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.
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.
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.
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.
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
?
src/compiler/types.ts
Outdated
| SyntaxKind.GetAccessor | ||
| SyntaxKind.SetAccessor | ||
| SyntaxKind.IndexSignature | ||
| SyntaxKind.MissingDeclaration; | ||
_classElementBrand: any; |
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.
Could we feasibly remove the brand with this change?
src/compiler/types.ts
Outdated
| SyntaxKind.IndexSignature | ||
| SyntaxKind.MissingDeclaration | ||
| SyntaxKind.JSDocPropertyTag | ||
| SyntaxKind.JSDocRecordMember; | ||
_typeElementBrand: any; |
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.
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. |
@@ -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 { |
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.
Why is this a NamedDeclaration
?
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.
This one actually does have a name
property, so I'll leave it alone.
src/compiler/types.ts
Outdated
@@ -1449,7 +1451,7 @@ namespace ts { | |||
| SuperElementAccessExpression | |||
; | |||
|
|||
export interface CallExpression extends LeftHandSideExpression, Declaration { | |||
export interface CallExpression extends LeftHandSideExpression, NamedDeclaration { |
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.
Why is this a NamedDeclaration
?
src/compiler/types.ts
Outdated
@@ -1468,7 +1470,7 @@ namespace ts { | |||
typeArguments?: NodeArray<TypeNode>; | |||
} | |||
|
|||
export interface NewExpression extends PrimaryExpression, Declaration { | |||
export interface NewExpression extends PrimaryExpression, NamedDeclaration { |
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.
Why is this a NamedDeclaration
?
src/compiler/types.ts
Outdated
@@ -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 { |
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.
Why is this a NamedDeclaration
?
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.
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
getNameOfDeclaration
to uniformly get the name of a declaration.It also reworks the declaration of
Declaration
so that accessingname
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