Skip to content

Conversation

@rbuckton
Copy link
Contributor

@rbuckton rbuckton commented Aug 4, 2023

This fixes our emit for references to a class name inside of a class body when down-level emit moves the member containing the reference outside of the class body. This is necessary to preserve the "double bind" semantics of class names, where references to a class name inside of a class body are not affected when the reference to the class outside of the class body is changed.

We generally emit members outside of a class body when downleveling static fields, class static blocks, and private methods/accessors.

Fixes #54607

@rbuckton rbuckton requested review from jakebailey and sandersn August 4, 2023 01:35
@typescript-bot typescript-bot added Author: Team For Milestone Bug PRs that fix a bug with a specific milestone labels Aug 4, 2023
Comment on lines -1073 to +1072
YieldExpression,
YieldExpression
Copy link
Member

Choose a reason for hiding this comment

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

Do you know what you ran which did this? Was it organize imports? Something else? Hoping to get to the bottom of these so we can be consistent (or, I guess formatting will prevent that).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably organize imports.

Copy link
Member

Choose a reason for hiding this comment

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

Hm, just tested running organize on checker.ts and this didn't happen, even when reordering things, really odd.

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 it was a result of running Organize Imports to remove an unused import. I just checked locally and that does remove the trailing ,.

@jakebailey
Copy link
Member

(Abusing this PR slightly to make sure RWC is working; this PR touches emit so it should produce a diff)

@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 4, 2023

Heya @jakebailey, I've started to run the extended test suite on this PR at 9868cdb. You can monitor the build here.

WideningContext,
WithStatement,
YieldExpression,
YieldExpression
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
YieldExpression
YieldExpression,

Well, in any case, probably best to leave it until we can figure out what's happening. This is of course a nit (still looking at the PR)

Copy link
Member

@jakebailey jakebailey left a comment

Choose a reason for hiding this comment

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

Seems correct to me though it's a little hard for me to be sure staring at the emit output 😄

Comment on lines -6025 to -6026
ClassWithBodyScopedClassBinding = 1 << 16, // Decorated class that contains a binding to itself inside of the class body.
BodyScopedClassBinding = 1 << 17, // Binding to a decorated class inside of the class's body.
Copy link
Member

Choose a reason for hiding this comment

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

Should we shift the other bits down or is it no big deal to leave them empty until later?

Copy link
Member

Choose a reason for hiding this comment

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

this is a driveby cleanup, right? I don't see any removal of BodyScopedClassBinding in the rest of the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All references to BodyScopedClassBinding were removed in a prior PR, but I neglected to remove the flag. I can fix up the bits used as well.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

Seems good, although I have a few questions. And the emit changes Testing123_1 -> Testing123 are unexpected to me, so worth double-checking.

Comment on lines -6025 to -6026
ClassWithBodyScopedClassBinding = 1 << 16, // Decorated class that contains a binding to itself inside of the class body.
BodyScopedClassBinding = 1 << 17, // Binding to a decorated class inside of the class's body.
Copy link
Member

Choose a reason for hiding this comment

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

this is a driveby cleanup, right? I don't see any removal of BodyScopedClassBinding in the rest of the PR.

break;
}

while (container.kind !== SyntaxKind.SourceFile && container.parent !== declaration) {
Copy link
Member

Choose a reason for hiding this comment

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

as I read it, this while loop is semantically equivalent to the old one. Am I missing anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, they are equivalent, but I feel this is a bit easier to read and reason over.

Copy link
Member

Choose a reason for hiding this comment

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

Agree, the new one is much easier to read.


let declaration = localOrExportSymbol.valueDeclaration;
if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) {
// Due to the emit for class decorators, any reference to the class from inside of the class body
Copy link
Member

Choose a reason for hiding this comment

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

To check my understanding: before, the _a reference was only generated for ClassExpressions and decorated classes. Now it happens for any class declaration/expression that references itself in its body. Is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We won't always generate the reference, but now we will always set the flag. It's just that the cases where we need the flag set were no longer restricted to the cases mentioned in the comment.

Testing123.prop1 = Testing123_1.prop0;
exports.Testing123 = Testing123 = Testing123_1 = __decorate([
Something({ v: () => Testing123_1 })
Something({ v: () => Testing123 })
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 understand how this diff happened. I assume it's because of the first block in the checker getting deleted, but I thought the edit to the second block would cause the reference to still get generated as Testing123_1.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Testing123_1 represents the class binding inside the class body, however a class decorator is evaluated outside of the class body, so it was pointing to the wrong reference prior to this change.

@rbuckton rbuckton merged commit b1c4dc4 into main Aug 5, 2023
@rbuckton rbuckton deleted the fix-class-name-references branch August 5, 2023 00:36
@microsoft microsoft locked as resolved and limited conversation to collaborators Oct 22, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Author: Team For Milestone Bug PRs that fix a bug with a specific milestone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect transpilation of class name references in static contexts

5 participants