Conversation
tausbn
force-pushed
the
tausbn/unified-improve-node-locations
branch
7 times, most recently
from
September 22, 2026 11:30
f662105 to
1a9354d
Compare
NB: This temporarily changes the test output for bulk imports, blocks, and things like synthesised Array and Option literals. Later commits will fix these up again.
The location 0..0 was being treated as "no useable location", which at the beginning of a file could cause problems. We now represent this as None instead, making Some(0..0) a valid empty location.
Adds two new macros, both variants of the existing `tree!` macro. First, `tree_at!` takes an extra argument, and sets the location of the root of the constructed tree to be that of the argument in question. Secondly, `tree_spanning!` does the same construction, but accepts an iterable of nodes instead. It then makes it so that the location of the root node of the constructed tree is the smallest span that contains all of the locations given by the iterable.
For such nodes, we assign them the zero-length location at the beginning of the matched input node. Note: This does mean some `expr_pattern`s now appear to have a fixed `let ` prefix in their source range (when really that `let` should belong to the modifier). This will be fixed in a later commit.
Blocks are slightly awkward, since we destructure them (as codeBlock) in the query, and then reconstruct them (as `block`) in the rule body, meaning the location of the block is (by default) assigned to the wrong place by the heuristic. To get around this, I added a helper function that updates the location appropriately, including handling cases where we only optionally match a block. (Also, in some cases we can fix this by just not destructuring `codeBlock`s in the first place -- there's already a rule that maps `codeBlock` to `block`.)
Handles things like `try!` (which is represented as two separate tokens -- we explicitly union their ranges) and "let" binding modifiers (where we reuse the bindingSpecifier, getting its location and string value for free).
Anchor synthesized generic type expressions for Swift array syntax to the original array node. This keeps the closing bracket in the type range without leaking it into surrounding calls or member accesses. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Trailing commas appear in a lot of places in the swift-syntax AST, and are all attached to the individual values separated by said commas, not the parent node. This means that the default heuristics thinks a call like `foo(1, 2)` has arguments `1,` and `2`, which is undesirable. Since this applies uniformly for a lot of different comma-separated lists, I decided that adding a generic mechanism for ignoring certain fields was the least invasive change. Thus, we now add `trailingComma` to that list, and this means it's automatically excluded from the location calculations, without need a bunch of specific changes to the existing rules.
In some cases (I'm looking at you, access_declaration) we link up things like the modifier of an accessor to a node that lives _outside_ of the declaration itself. Previously this was treated as an error (and the "fix" was to extend the source ranges), but this lead to some weird source skeletons in the output. Instead, we now allow the source of a node to be anywhere (within reason -- it has to be inside the file, and properly UTF-8 aligned), and simply add a trailing ` (external)` to the source annotation. Thus, the fields containing these values do not appear in the source skeleton for the node in question.
Keep inherited property metadata at its original source while limiting each accessor declaration to its own keyword and optional body. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tausbn
force-pushed
the
tausbn/unified-improve-node-locations
branch
from
September 22, 2026 11:42
1a9354d to
985155a
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tausbn
force-pushed
the
tausbn/unified-improve-node-locations
branch
from
September 22, 2026 13:18
985155a to
364d59c
Compare
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Boundary exclusion can create invalid ranges, and bodyless Swift initializers lose their name node.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Improves Yeast source-range propagation and applies the new location semantics to Swift extraction.
Changes:
- Adds child-derived ranges, explicit location macros, boundary exclusions, and external-child diagnostics.
- Updates Swift desugaring to produce more precise node locations.
- Adds focused tests and regenerates Swift corpus and QL expectations.
| File | Description |
|---|---|
unified/ql/test/library-tests/dataflow/test.expected |
Updates data-flow locations. |
unified/ql/test/library-tests/controlflow/cfg.swift |
Updates inline CFG assertions. |
unified/ql/test/library-tests/controlflow/cfg.expected |
Updates CFG expectations. |
unified/ql/test/library-tests/controlflow/basicblock-slices.expected |
Updates block-range expectations. |
unified/ql/test/library-tests/BasicTest/test.expected |
Updates identifier locations. |
unified/extractor/tests/location_tests.rs |
Adds focused location tests. |
unified/extractor/tests/corpus/swift/variables/tuple-destructuring-binding.output |
Regenerates tuple-binding output. |
unified/extractor/tests/corpus/swift/variables/property-with-willset-and-didset-observers.output |
Regenerates observer output. |
unified/extractor/tests/corpus/swift/variables/multiple-bindings-on-one-line.output |
Regenerates binding locations. |
unified/extractor/tests/corpus/swift/variables/binding-modifier-does-not-leak-into-initializer.output |
Regenerates switch locations. |
unified/extractor/tests/corpus/swift/types/static-function.output |
Regenerates function locations. |
unified/extractor/tests/corpus/swift/types/protocol-with-read-only-and-read-write-property-requirements.output |
Regenerates accessor locations. |
unified/extractor/tests/corpus/swift/types/protocol-declaration.output |
Regenerates protocol output. |
unified/extractor/tests/corpus/swift/types/property-with-getter-and-setter.output |
Regenerates property output. |
unified/extractor/tests/corpus/swift/types/noncopyable-type.output |
Regenerates base-type locations. |
unified/extractor/tests/corpus/swift/types/generic-class-parameters-and-constraints.output |
Regenerates generic locations. |
unified/extractor/tests/corpus/swift/types/extension.output |
Regenerates extension output. |
unified/extractor/tests/corpus/swift/types/enum-with-comma-separated-cases-chained-declaration.output |
Regenerates enum-case output. |
unified/extractor/tests/corpus/swift/types/enum-with-cases.output |
Regenerates enum locations. |
unified/extractor/tests/corpus/swift/types/enum-with-associated-values.output |
Regenerates enum constructors. |
unified/extractor/tests/corpus/swift/types/constructor-with-parameters.output |
Regenerates constructor locations. |
unified/extractor/tests/corpus/swift/types/computed-property.output |
Regenerates computed-property output. |
unified/extractor/tests/corpus/swift/types/class-with-multiple-base-types.output |
Regenerates base-type ranges. |
unified/extractor/tests/corpus/swift/types/class-with-method.output |
Regenerates method locations. |
unified/extractor/tests/corpus/swift/types/class-with-initializer.output |
Regenerates initializer locations. |
unified/extractor/tests/corpus/swift/types/class-inheritance.output |
Regenerates inheritance output. |
unified/extractor/tests/corpus/swift/types/class-function.output |
Regenerates class-function output. |
unified/extractor/tests/corpus/swift/types/binding-modifier-does-not-leak-into-accessor-body.output |
Regenerates accessor-body output. |
unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression.output |
Regenerates try? locations. |
unified/extractor/tests/corpus/swift/optionals-and-errors/try-expression-2.output |
Regenerates try! locations. |
unified/extractor/tests/corpus/swift/optionals-and-errors/throwing-function.output |
Regenerates throwing-function output. |
unified/extractor/tests/corpus/swift/optionals-and-errors/optional-type-annotation.output |
Regenerates optional-type output. |
unified/extractor/tests/corpus/swift/optionals-and-errors/optional-enum-case-binding.output |
Regenerates optional-pattern output. |
unified/extractor/tests/corpus/swift/optionals-and-errors/force-unwrap.output |
Regenerates unwrap locations. |
unified/extractor/tests/corpus/swift/optionals-and-errors/do-catch.output |
Regenerates do-catch output. |
unified/extractor/tests/corpus/swift/optionals-and-errors/catch-where-clauses.output |
Regenerates catch-pattern locations. |
unified/extractor/tests/corpus/swift/operators/unresolved-operator-sequence.output |
Regenerates operator output. |
unified/extractor/tests/corpus/swift/operators/unresolved-operator-sequence-with-ternary.output |
Regenerates ternary-sequence output. |
unified/extractor/tests/corpus/swift/operators/unresolved-operator-sequence-with-casts.output |
Regenerates cast-sequence output. |
unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output |
Regenerates interpolation locations. |
unified/extractor/tests/corpus/swift/functions/variadic-function.output |
Regenerates variadic-function output. |
unified/extractor/tests/corpus/swift/functions/nested-function-type.output |
Regenerates nested-function-type output. |
unified/extractor/tests/corpus/swift/functions/generic-type-alias.output |
Regenerates type-alias locations. |
unified/extractor/tests/corpus/swift/functions/generic-function.output |
Regenerates generic-function output. |
unified/extractor/tests/corpus/swift/functions/function-with-parameters-and-return-type.output |
Regenerates function signature output. |
unified/extractor/tests/corpus/swift/functions/function-with-no-parameters.output |
Regenerates function-body locations. |
unified/extractor/tests/corpus/swift/functions/function-with-named-parameters.output |
Regenerates named-parameter output. |
unified/extractor/tests/corpus/swift/functions/function-with-inout-parameter.output |
Regenerates inout-function output. |
unified/extractor/tests/corpus/swift/functions/function-with-default-parameter-value.output |
Regenerates default-parameter output. |
unified/extractor/tests/corpus/swift/functions/function-call.output |
Regenerates call locations. |
unified/extractor/tests/corpus/swift/functions/call-with-inout-argument.output |
Regenerates inout-call output. |
unified/extractor/tests/corpus/swift/expressions/unsafe-expression.output |
Regenerates unsafe-expression output. |
unified/extractor/tests/corpus/swift/expressions/super-expression.output |
Regenerates superclass locations. |
unified/extractor/tests/corpus/swift/expressions/array-type-metatype.output |
Regenerates array metatype output. |
unified/extractor/tests/corpus/swift/expressions/array-type-constructor.output |
Regenerates array-constructor output. |
unified/extractor/tests/corpus/swift/control-flow/switch-with-labeled-case-pattern-arguments.output |
Regenerates switch-pattern output. |
unified/extractor/tests/corpus/swift/control-flow/switch-with-binding-pattern.output |
Regenerates binding-pattern output. |
unified/extractor/tests/corpus/swift/control-flow/switch-statement.output |
Regenerates switch locations. |
unified/extractor/tests/corpus/swift/control-flow/switch-expression-pattern.output |
Regenerates expression-pattern output. |
unified/extractor/tests/corpus/swift/control-flow/switch-case-item-where-clauses.output |
Regenerates conditional-pattern output. |
unified/extractor/tests/corpus/swift/control-flow/nested-enum-case-pattern.output |
Regenerates nested-pattern output. |
unified/extractor/tests/corpus/swift/control-flow/if-let-optional-binding.output |
Regenerates optional-binding output. |
unified/extractor/tests/corpus/swift/control-flow/guard-let.output |
Regenerates guard-binding output. |
unified/extractor/tests/corpus/swift/control-flow/fallthrough.output |
Regenerates fallthrough output. |
unified/extractor/tests/corpus/swift/control-flow/discard-statement.output |
Regenerates discard-statement output. |
unified/extractor/tests/corpus/swift/control-flow/defer-statement.output |
Regenerates defer-statement output. |
unified/extractor/tests/corpus/swift/control-flow/binding-modifier-does-not-leak-to-sibling.output |
Regenerates sibling-binding output. |
unified/extractor/tests/corpus/swift/collections/tuple-literal.output |
Regenerates tuple locations. |
unified/extractor/tests/corpus/swift/collections/empty-array-literal-with-type.output |
Regenerates array-type output. |
unified/extractor/tests/corpus/swift/closures/trailing-closure.output |
Regenerates trailing-closure output. |
unified/extractor/src/languages/swift/swift.rs |
Applies precise Swift location construction. |
unified/extractor/BUILD.bazel |
Registers location tests. |
shared/yeast/tests/test.rs |
Tests new range semantics. |
shared/yeast/src/range.rs |
Adds range operations and exclusions. |
shared/yeast/src/lib.rs |
Adds location APIs and configuration. |
shared/yeast/src/dump.rs |
Marks external child locations. |
shared/yeast/src/build.rs |
Tracks and finalizes constructed nodes. |
shared/yeast/doc/yeast.md |
Documents location behavior. |
shared/yeast-macros/src/parse.rs |
Finalizes rule results. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Contributor
Author
tausbn
marked this pull request as ready for review
September 22, 2026 14:47
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Changes the location-assigning heuristics to implement roughly the following principles:
middlein(outer foo: (middle bar: {baz}))), then it gets a location that spans all of its children (that is, all of the captures that have already been translated, that are assigned as descendants of this node).outerabove) gets the location of the entire node that matched that rule (unless that location has been assigned already, e.g. by recursive translation or explicit assignment).(identifier #{name}), wherenameis some captured node ID, then theidentifiernode gets the location of that capture, whereas the string value is what is actually interpolated.One consequence of the above rules is that if you have something like
then the location of the node emitted by the right hand side is not that of
foo, but rather whatever is assigned tobazwhen it is recursively translated.In cases where this heuristic is insufficient, newly added
tree_at!andtree_spanning!macros may be employed. These take a second argument that specifies the node (or iterable of nodes) from which to take the location of the tree being constructed.Should be reviewed commit-by-commit. I checked a bunch of the location changes in the last commit manually (and instructed Copilot to check the rest). In all cases, the locations are an improvement on what was there before (e.g. a call inside a tuple accidentally including the following
,in its range). This does produce a fair amount of churn, but I think it's somewhat unavoidable.