Skip to content

Set parent pointers as nodes are ready in parser, rather than all at once as parse is complete #1279

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

Merged
merged 12 commits into from
Jun 26, 2025

Conversation

weswigham
Copy link
Member

@weswigham weswigham commented Jun 24, 2025

This is, imo, an improvement on #1251. It's almost very straightforward to just set parents in finishNode (which would be ideal as it's when all the relevant pointers are in cache)... except for all the cases where we reparse a node and need to forcibly overwrite parent pointers we already set (which can't be the default behavior, as last-in-wins parent setting by default breaks the jsdoc reparser which currently only ends up setting parents on new nodes, and not copied or moved ones).

This also exposed an interesting issue with the @satisfies jsdoc transform and ended up reducing the diffs for it - by overriding the .Parent explicitly on the transformed result, as is needed to ensure one is always set in this manner (in current main, the parent of the synthetic satisfies expression/type only gets set if it hasn't been set by the main file walk, which is to say basically never), we get correct context lookup behavior in the checker.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR changes parent-pointer assignment to occur immediately as nodes are parsed or reparsed, improving context lookup for JSDoc transforms (especially @satisfies). Key updates include:

  • Introducing finishReparsedNode and a currentParent mechanism to incrementally set .Parent via setParent.
  • Extensive calls to finishReparsedNode across reparser.go and updates to finishNodeWithEnd in parser.go to reset parent pointers on the fly.
  • Updated test baselines for JSDoc @satisfies error output to reflect the new transformer behavior.

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

File Description
internal/parser/reparser.go Adds finishReparsedNode and calls it after reparsing each JSDoc node to set parent pointers immediately.
internal/parser/parser.go Introduces currentParent, updates finishNodeWithEnd, and adds unparseExpressionWithTypeArguments to reset parent pointers post-parse.
internal/checker/checker.go Expands export-assignment check to include KindJSExportAssignment.
testdata/baselines/reference/... Updated expected error messages for various checkJsdocSatisfiesTag tests to match new behavior.

@jakebailey
Copy link
Member

This is an improvement I was hoping we'd "eventually" send, so I'm happy to see that "eventually" could be "right now" 😄

The JSX case was the one case I knew we did something funky with. Hopefully there aren't any more? I know @andrewbranch mentioned that we may not know the right parents for the reparsed nodes early enough.

Is there a way we could in testing verify that the parents are set properly, like potentially in the harness's GetSourceFile function ForEachChild like SetParentsInChildren and check that the parent is the caller?

@weswigham
Copy link
Member Author

Is there a way we could in testing verify that the parents are set properly, like potentially in the harness's GetSourceFile function ForEachChild like SetParentsInChildren and check that the parent is the caller?

Probably. I think we used to have tests that did this in Strada to check that .parent got fixed up correctly after incremental reparsing, actually. We should probably just resurrect those assertions.

- ~
-!!! error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T1'.
- const t3 = /** @satisfies {T1} */ ({});
+/a.js(22,38): error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'.
Copy link
Member

Choose a reason for hiding this comment

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

Nice, this is actually a desired change IIRC, some new error stack collapsing

-!!! error TS2353: Object literal may only specify known properties, and 'x' does not exist in type 'Partial<Record<Keys, unknown>>'.
+!!! error TS2353: Object literal may only specify known properties, and 'x' does not exist in type 'Partial<Record<"a" | "b" | "c" | "d", unknown>>'.
Copy link
Member

Choose a reason for hiding this comment

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

Weird, where did a b c d come from?

Copy link
Member Author

Choose a reason for hiding this comment

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

Expansion of Keys - presumably a difference in type node printback behavior to do with not currently having most input node reuse logic in place right now.

@jakebailey
Copy link
Member

FWIW I could have sworn the intention was that any reparsed nodes were copies and their parents set in a "normal" way, so I'd be interested in fixing that stuff too at some point. The lest special cases the better...

@weswigham
Copy link
Member Author

The lest special cases the better...

Yeah.... the await reparser and js reparser also behave differently when it comes to updating parent nodes - the js reparser mutates nodes in-place, while the await reparser returns a new parent node. Both have differing tradeoffs when it comes to setting parent pointers (the await reparser has to mutate parents on all the retained old nodes, the js reparser has to ensure parents are fixed up on nodes moved via mutation), but I'd appreciate it if we were at least consistent, so we could use a consistent style of parent-(re)setting.

@weswigham
Copy link
Member Author

weswigham commented Jun 24, 2025

like potentially in the harness's GetSourceFile function ForEachChild like SetParentsInChildren and check that the parent is the caller?

I think we could assert non-nil parents right now (or rather, we definitely should), but the js reparser's laissez faire attitude with moving nodes around and keeping the same node accessible in multiple places (?!¿) makes me think we can't currently rely totally on that matching visitor-ordered traversal parents. Not in files where it's been invoked, anyway.

@jakebailey
Copy link
Member

the js reparser's laissez faire attitude with moving nodes around and keeping the same node accessible in multiple places

If this is still happening, I'm pretty sure it's a bug. I know I had found one place in the reparser that was missing a clone, but perhaps there are more.

@jakebailey
Copy link
Member

When I test main vs this branch+main, the perf is a lot worse:

goos: linux
goarch: amd64
pkg: github.com/microsoft/typescript-go/internal/parser
cpu: Intel(R) Core(TM) i9-10900K CPU @ 3.70GHz
                                                             │   old.txt   │               new.txt               │
                                                             │   sec/op    │   sec/op     vs base                │
Parse/empty.ts/tsc-20                                          495.3n ± 1%   522.9n ± 1%   +5.58% (p=0.000 n=10)
Parse/empty.ts/server-20                                       495.1n ± 1%   525.1n ± 1%   +6.07% (p=0.000 n=10)
Parse/checker.ts/tsc-20                                        44.96m ± 2%   53.98m ± 2%  +20.06% (p=0.000 n=10)
Parse/checker.ts/server-20                                     46.18m ± 1%   55.07m ± 2%  +19.23% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-20                                15.07m ± 2%   18.54m ± 1%  +23.07% (p=0.000 n=10)
Parse/dom.generated.d.ts/server-20                             21.94m ± 1%   25.63m ± 1%  +16.85% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-20                                    826.6µ ± 1%   945.9µ ± 0%  +14.43% (p=0.000 n=10)
Parse/Herebyfile.mjs/server-20                                 830.2µ ± 1%   945.8µ ± 1%  +13.93% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-20      251.0µ ± 2%   297.5µ ± 1%  +18.51% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-20   382.5µ ± 1%   434.5µ ± 2%  +13.58% (p=0.000 n=10)
geomean                                                        637.5µ        733.1µ       +15.00%
                                                             │   old.txt    │                new.txt                │
                                                             │     B/op     │     B/op       vs base                │
Parse/empty.ts/tsc-20                                            881.0 ± 0%      912.0 ± 0%   +3.52% (p=0.000 n=10)
Parse/empty.ts/server-20                                         881.0 ± 0%      912.0 ± 0%   +3.52% (p=0.000 n=10)
Parse/checker.ts/tsc-20                                        25.69Mi ± 0%    30.22Mi ± 0%  +17.63% (p=0.000 n=10)
Parse/checker.ts/server-20                                     25.87Mi ± 0%    30.40Mi ± 0%  +17.53% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-20                                9.245Mi ± 0%   10.789Mi ± 0%  +16.70% (p=0.000 n=10)
Parse/dom.generated.d.ts/server-20                             11.31Mi ± 0%    13.04Mi ± 0%  +15.28% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-20                                    450.7Ki ± 0%    510.0Ki ± 0%  +13.16% (p=0.000 n=10)
Parse/Herebyfile.mjs/server-20                                 450.7Ki ± 0%    510.0Ki ± 0%  +13.16% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-20      164.9Ki ± 0%    187.6Ki ± 0%  +13.75% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-20   244.8Ki ± 0%    272.7Ki ± 0%  +11.41% (p=0.000 n=10)
geomean                                                        464.1Ki         521.9Ki       +12.45%
                                                             │   old.txt   │                new.txt                 │
                                                             │  allocs/op  │  allocs/op    vs base                  │
Parse/empty.ts/tsc-20                                           4.000 ± 0%     6.000 ± 0%    +50.00% (p=0.000 n=10)
Parse/empty.ts/server-20                                        4.000 ± 0%     6.000 ± 0%    +50.00% (p=0.000 n=10)
Parse/checker.ts/tsc-20                                        18.09k ± 0%   314.28k ± 0%  +1637.05% (p=0.000 n=10)
Parse/checker.ts/server-20                                     18.41k ± 0%   315.45k ± 0%  +1613.36% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-20                                11.49k ± 0%   112.69k ± 0%   +881.13% (p=0.000 n=10)
Parse/dom.generated.d.ts/server-20                             13.50k ± 0%   126.93k ± 0%   +840.41% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-20                                    1.825k ± 0%    5.634k ± 0%   +208.71% (p=0.000 n=10)
Parse/Herebyfile.mjs/server-20                                 1.825k ± 0%    5.634k ± 0%   +208.71% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-20       278.0 ± 0%    1734.0 ± 0%   +523.74% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-20    404.0 ± 0%    2200.0 ± 0%   +444.55% (p=0.000 n=10)
geomean                                                         889.2         4.776k        +437.16%

This seems to be due to the closure thing again:

image

@weswigham
Copy link
Member Author

sigh always the method closure overhead. I'm willing to bet if I write an actual small inline closure instead of a method reference it'll actually get inlined.

In other news, though, the jsdoc reparser reparses

/** @type {string} */
module.exports = 0;

into a JS export declaration with a .Type set to the string type node, but also reparses the same type node to set a .Type on the original binary expression - this is a big no-no - the same node in two traversable parts of the AST, but with different logical parent nodes. It's impossible to satisfy a traversal-order-parenting assertion with this logic in place (and I'm sure there's other downstream invariants that get broken by it).

The jsdoc reparser only gets away with the mutations it does normally because the .jsDoc member the source nodes come from isn't part of the child preorder traversal set. It really should be deep cloning any nodes it moves, for each destination location, for exactly this reason.

@@ -6,8 +6,10 @@ import (
)

func (p *Parser) finishReparsedNode(node *ast.Node) {
p.currentParent = node
node.ForEachChild(p.setParent)
node.ForEachChild(func(n *ast.Node) bool {
Copy link
Member

Choose a reason for hiding this comment

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

ForEachChild is an indirect call, so is not inlinable and escapes its argument; this is why I had to do the goofy parent setting thing in SetParentsInChildren. I would suspect that this wouldn't work.

@weswigham
Copy link
Member Author

I know I had found one place in the reparser that was missing a clone, but perhaps there are more.

You sure someone didn't walk this back? The text "Clone" doesn't even appear inside the reparser.

@jakebailey
Copy link
Member

I found it, but it was never committed as we just undid the parent code in the binder instead. #1187 (comment) is actually one such case.

@andrewbranch
Copy link
Member

this is a big no-no

That’s what I said too, but apparently that’s the design that @ahejlsberg and @sandersn discussed. The invariant that was intended to hold is weaker: that real nodes have real parents. So a real node is allowed to appear inside a reparsed node, but its parent should point into the real AST. In other words, if you visit the children of a reparsed subtree, you may not be able to get back up to the same place by parent pointer traversal. I don’t particularly like this, but that was the intended design, and many things broke when I tried adding consistent cloning.

@weswigham
Copy link
Member Author

weswigham commented Jun 24, 2025

that real nodes have real parents

That's true, but reparsed nodes are, for all intents, real nodes. Like an await reparsed source file or the handful of places where we parse out an ExpressionWithTypeArguments but then discard it and pull the members of it into a different kinded node (all of which get called out in this PR as places which need exceptions to the normal parent setting behavior of finishNode). The js reparser is not the first time we "reparse" things. They need real parents - and more importantly, they can't be shared into multiple (traversable) places in the AST - that's the real problem. Anything about the fake-ness of these nodes the reparser makes should be about their positions (since, ofc, reparsed nodes with cloned positions violate the formatter's "all nodes contain the spans of all child nodes" invariant, so the formatter needs to know not to include them in its' formatting logic, but that's a bug for another day), not the data objects themselves. We can't go making our abstract tree into an abstract graph - some of the algorithms don't hold up, and running those down is a lot harder than ensuring we keep to the tree abstraction in the first place.

and many things broke when I tried adding consistent cloning.

Same here where swapping parents between multiple "correct" options causes changes! And looking through those breaks, they're places where "alternate but correct" parent types don't seem to be handled, like JSExportDeclarations, since which parent is set today between, eg, binary expression and js export declaration, only comes down to traversal order in the parent setting walk.

@sandersn
Copy link
Member

To me, it sounds like we need to revisit the no-clone real-parent design again in a realtime discussion. I can't make the design meeting today but maybe on Friday.

The current design really doesn't turn the tree into a graph, though. It adds out-of-band secondary parents that are only checked in a few places. (Which are then treated inconsistently on commonjs exports to avoid adding a secondary parent field to BinaryExpression, as you noticed.) What algorithms are broken by it, if they skip nodes flagged as reparsed?

@weswigham
Copy link
Member Author

It adds out-of-band secondary parents that are only checked in a few place

It does that, too, but it also, in a roundabout way, sets the .Parent in reparsed nodes simply by making reparsed nodes children of "real" nodes (as they then get hit by the parent node setting traversal in main); because they're in the tree twice in some cases, which position's .Parent wins is ??? (either the first or last topographically, depending on how the setter is written and when it's invoked) - this is the core logical problem that causes the .Parent setting data races we've been contending with, I imagine, as it prevents us from setting parents on partial trees and having a singular canonical output.

@jakebailey
Copy link
Member

jakebailey commented Jun 24, 2025

I think we're likely to merge #1241 soon, so hopefully that isn't too hard to merge into this PR.

@weswigham
Copy link
Member Author

@jakebailey I had to except the cases where the reparser reuses nodes, but otherwise the tests show the assertion holding now. There's some baseline diffs as in some of those cases, a different parent is selected now; but the baselines show mostly improvements (or at least reduced diffs) from the current post-hoc parent selection method. I'm pretty disappointed on the manual indirections needed to iterate node children and set parent pointers efficiently though. :(

@jakebailey
Copy link
Member

Here's the current perf diff. Memory is unchanged, but runtime gets slower:

                                                             │   old.txt   │               new.txt               │
                                                             │   sec/op    │   sec/op     vs base                │
Parse/empty.ts/tsc-20                                          493.9n ± 1%   510.5n ± 1%   +3.35% (p=0.000 n=10)
Parse/empty.ts/server-20                                       493.9n ± 1%   511.0n ± 1%   +3.45% (p=0.000 n=10)
Parse/checker.ts/tsc-20                                        45.59m ± 3%   50.64m ± 1%  +11.07% (p=0.000 n=10)
Parse/checker.ts/server-20                                     46.75m ± 1%   51.99m ± 2%  +11.21% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-20                                15.16m ± 2%   17.38m ± 1%  +14.67% (p=0.000 n=10)
Parse/dom.generated.d.ts/server-20                             22.29m ± 2%   24.30m ± 1%   +9.02% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-20                                    834.9µ ± 1%   898.8µ ± 1%   +7.66% (p=0.000 n=10)
Parse/Herebyfile.mjs/server-20                                 838.0µ ± 1%   896.5µ ± 1%   +6.97% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-20      254.6µ ± 2%   280.6µ ± 1%  +10.19% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-20   386.4µ ± 1%   414.4µ ± 1%   +7.25% (p=0.000 n=10)
geomean                                                        643.1µ        697.3µ        +8.43%

The profile is a little unclear, but it does imply that 10% of the time is spent in OverrideParentInImmediateChildren, whereas the old recursive setting was only 5% of the parse time.

Half of that is just messing with the pool; my guess is that while the old code would only get something out of the pool once per AST, now we're doing it for every node on finish.

image

@jakebailey
Copy link
Member

Probably this just means we should be sticking this on the parser as a cached function pointer instead of a global?

@jakebailey
Copy link
Member

jakebailey commented Jun 25, 2025

With the suggestion above, versus main:

                                                             │   old.txt   │              new.txt               │
                                                             │   sec/op    │   sec/op     vs base               │
Parse/empty.ts/tsc-20                                          495.7n ± 1%   475.2n ± 1%  -4.14% (p=0.000 n=10)
Parse/empty.ts/server-20                                       494.8n ± 0%   477.5n ± 1%  -3.49% (p=0.000 n=10)
Parse/checker.ts/tsc-20                                        44.85m ± 3%   43.94m ± 2%  -2.03% (p=0.000 n=10)
Parse/checker.ts/server-20                                     46.12m ± 1%   45.15m ± 1%  -2.12% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-20                                15.11m ± 4%   15.05m ± 1%       ~ (p=0.684 n=10)
Parse/dom.generated.d.ts/server-20                             22.14m ± 1%   21.75m ± 1%  -1.73% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-20                                    843.9µ ± 1%   831.4µ ± 2%  -1.48% (p=0.004 n=10)
Parse/Herebyfile.mjs/server-20                                 847.0µ ± 1%   828.8µ ± 1%  -2.15% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-20      255.5µ ± 1%   249.6µ ± 1%  -2.32% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-20   390.4µ ± 1%   377.7µ ± 2%  -3.24% (p=0.000 n=10)
geomean                                                        643.1µ        628.2µ       -2.31%

@jakebailey
Copy link
Member

Main without parent setting in the parser, vs main vs this PR:

                                                             │ oldold.txt  │              old.txt               │              new.txt               │
                                                             │   sec/op    │   sec/op     vs base               │   sec/op     vs base               │
Parse/empty.ts/tsc-20                                          464.6n ± 1%   495.7n ± 1%  +6.71% (p=0.001 n=10)   475.2n ± 1%  +2.29% (p=0.001 n=10)
Parse/empty.ts/server-20                                       468.6n ± 2%   494.8n ± 0%  +5.59% (p=0.000 n=10)   477.5n ± 1%  +1.91% (p=0.005 n=10)
Parse/checker.ts/tsc-20                                        42.32m ± 2%   44.85m ± 3%  +5.96% (p=0.000 n=10)   43.94m ± 2%  +3.82% (p=0.004 n=10)
Parse/checker.ts/server-20                                     43.20m ± 3%   46.12m ± 1%  +6.76% (p=0.000 n=10)   45.15m ± 1%  +4.50% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-20                                14.13m ± 1%   15.11m ± 4%  +6.91% (p=0.000 n=10)   15.05m ± 1%  +6.48% (p=0.000 n=10)
Parse/dom.generated.d.ts/server-20                             20.74m ± 2%   22.14m ± 1%  +6.73% (p=0.000 n=10)   21.75m ± 1%  +4.88% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-20                                    807.1µ ± 2%   843.9µ ± 1%  +4.56% (p=0.000 n=10)   831.4µ ± 2%  +3.01% (p=0.000 n=10)
Parse/Herebyfile.mjs/server-20                                 787.9µ ± 1%   847.0µ ± 1%  +7.50% (p=0.000 n=10)   828.8µ ± 1%  +5.19% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-20      236.1µ ± 2%   255.5µ ± 1%  +8.22% (p=0.000 n=10)   249.6µ ± 1%  +5.71% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-20   363.8µ ± 1%   390.4µ ± 1%  +7.31% (p=0.000 n=10)   377.7µ ± 2%  +3.82% (p=0.000 n=10)
geomean                                                        603.2µ        643.1µ       +6.62%                  628.2µ       +4.15%

Not getting back as much as I thought...

@jakebailey
Copy link
Member

However, I think the numbers do correspond to the time removed from the binder in #1251, so that may all be expected.

@weswigham
Copy link
Member Author

Yeah, as I mentioned offline, I think the only way we could get more efficient than this would be if we built setting parent pointers into the node factory functions to avoid the virtual dispatch on ForEachChild (which, as you said, would need to be toggleable functionality so the transformers don't override parse tree parents on children of transformed nodes), which'd need a fair amount of toil to add that functionality to the constructors (or a reasonably capable codegen - still wish most of ast.go was codegen!), then additional review to ensure anything mutating nodes after construction (eck) manually sets up parent pointers.

In any case, this gets us back to where we were-but-racing but without-a-race, so seems like a good place to stop for now.

Comment on lines +19 to +20
+!!! related TS2728 /a.js:3:23: 'a' is declared here.
Copy link
Member

Choose a reason for hiding this comment

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

Not for this PR but seems like we're not setting the range of the satisfies expression properly? That or we need to do some special handling to choose the JSDoc node to report the satisfies error on?

Copy link
Member Author

Choose a reason for hiding this comment

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

The location on the synthetic satisfies expression is set to the satisfies tag, but binder.GetErrorRangeForNode isn't set up to handle reparsed nodes for this node kind, so this error span is accidentally close, at best. It's "the token range at the end of the trivia after the expression position", which for the @satisfies tag is the semicolon trivia we see underlined here.

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.

Baseline improvements all seem good too.

Comment on lines -36 to -37
+ ~
+!!! error TS2304: Cannot find name 'T'.
Copy link
Member

Choose a reason for hiding this comment

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

Nice to fix this one.

Copy link
Member Author

Choose a reason for hiding this comment

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

Entirely accidental upside to going through the reparser and adding calls to manually set parents. Node just gets a .Parent set to a node in the real expression tree now, where it did not before (it got a jsdoc parent).

@weswigham weswigham enabled auto-merge June 25, 2025 22:54
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.

I don't think the baselines are an unqualified improvement, but it's most likely that the bad diffs I'm seeing are from problems that were hidden before when the parents were wrong.

@@ -30197,7 +30197,7 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast
return c.getSymbolOfNode(name.Parent)
}

if name.Parent.Kind == ast.KindExportAssignment && ast.IsEntityNameExpression(name) {
if (name.Parent.Kind == ast.KindExportAssignment || name.Parent.Kind == ast.KindJSExportAssignment) && ast.IsEntityNameExpression(name) {
Copy link
Member

Choose a reason for hiding this comment

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

if this is code ported since the introduction of JSExportAssignment, I think that means I'll need to make another pass through the source looking for places where all the synthetic kinds need to be handled alongside their non-synthetic peers.

@@ -977,6 +978,9 @@ func (p *Parser) parseTypedefTag(start int, tagName *ast.IdentifierNode, indent

typedefTag := p.factory.NewJSDocTypedefTag(tagName, typeExpression, fullName, comment)
p.finishNodeWithEnd(typedefTag, start, end)
if typeExpression != nil {
Copy link
Member

Choose a reason for hiding this comment

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

nested @typedef pushes the whole syntax from tragedy to loop back around to comedy.

@@ -75,7 +75,7 @@
+>module.exports : { justExport: number; bothBefore: number; bothAfter: number; }
+>module : { "export=": { justExport: number; bothBefore: number; bothAfter: number; }; }
+>exports : { justExport: number; bothBefore: number; bothAfter: number; }
+>bothBefore : number
+>bothBefore : "string"
Copy link
Member

Choose a reason for hiding this comment

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

this was number | "string" before, and switched types, so I think that's an effect of our stricter rules combined with different visit order. Or something like that?

+>exports : typeof import("./mod1")
>"b" : "b"
+>"b" : { x: string; }
Copy link
Member

Choose a reason for hiding this comment

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

this looks like a change in how the baseliner is interpreting "b", from a string literal to a property name. The latter is defensible but definitely different from before.

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, for sure a difference in the symbol and therefore type associated with the node.

@jakebailey jakebailey disabled auto-merge June 26, 2025 20:45
@jakebailey
Copy link
Member

Turning off auto merge for now, given we were wanting to talk about this tomorrow and any approvals here will make it go in (it would have been merged just now if there weren't a merge conflict)

@sandersn
Copy link
Member

thanks, didn't notice that

@jakebailey
Copy link
Member

That being said, I'm pro-this-change because yay perf, just wanted to make sure we understood the model we're going for with this new reparsed AST.

@weswigham
Copy link
Member Author

weswigham commented Jun 26, 2025

That being said, I'm pro-this-change because yay perf, just wanted to make sure we understood the model we're going for with this new reparsed AST.

Haven't changed anything here about that, this just tosses exceptions into the test you asked for in the cases that highlight the issues with the current model - we do need to talk about it, but that's followup work. I think we can take this as-is (sans merge conflict).

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.

In which case, seems fine, and I'm glad to have at test to make sure we don't break it.

@weswigham weswigham enabled auto-merge June 26, 2025 21:17
@weswigham weswigham added this pull request to the merge queue Jun 26, 2025
Merged via the queue into microsoft:main with commit ff49e72 Jun 26, 2025
22 checks passed
@weswigham weswigham deleted the set-parents-upfront branch June 26, 2025 21:36
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