Skip to content

Commit 37c0ba8

Browse files
committed
Unified: Fix locations of various tokens
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).
1 parent c878e73 commit 37c0ba8

5 files changed

Lines changed: 80 additions & 26 deletions

File tree

‎unified/extractor/src/languages/swift/swift.rs‎

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ fn and_chain(
100100
conds
101101
.into_iter()
102102
.reduce(|acc, elem| {
103-
tree!((binary_expr operator: (infix_operator "&&") left: {acc} right: {elem}))
103+
let operator_range = ctx.empty_source_range_between(acc, elem);
104+
let operator = ctx.literal_with_source_range("infix_operator", "&&", operator_range);
105+
tree!((binary_expr operator: {operator} left: {acc} right: {elem}))
104106
})
105107
.expect("control-flow statement must have at least one condition")
106108
}
@@ -126,21 +128,15 @@ fn member_chain(
126128
ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>,
127129
parts: Vec<yeast::Id>,
128130
) -> yeast::Id {
129-
// `member_chain` builds the imported expression inside the larger import
130-
// declaration rule. The imported expression should span the import path,
131-
// not the whole declaration including the `import` keyword.
132-
let source_range = ctx.source_range.take();
133131
let mut iter = parts.into_iter();
134132
let first = iter
135133
.next()
136134
.expect("identifier with `part:` must have at least one part");
137135
let init = tree!((identifier #{first}));
138-
let result = iter.fold(
136+
iter.fold(
139137
init,
140138
|acc, elem| tree!((member_access_expr base: {acc} member_name_node: (identifier #{elem}))),
141-
);
142-
ctx.source_range = source_range;
143-
result
139+
)
144140
}
145141

146142
/// Compound-assignment operator spellings (`+=`, `<<=`, ...). Used to tell a
@@ -865,6 +861,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
865861
// form is matched first.
866862
rule!(
867863
(optionalBindingCondition
864+
bindingSpecifier: @@spec
868865
pattern: (identifierPattern identifier: @name)
869866
initializer: (initializerClause value: @val))
870867
=>
@@ -873,18 +870,20 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
873870
pattern: (call_expr
874871
callee: (member_access_expr base: (identifier "Optional") member_name_node: (identifier "some"))
875872
argument: (argument value: (expr_pattern
876-
modifier: (modifier "let")
873+
modifier: (modifier #{spec})
877874
expr: (identifier #{name})))))
878875
),
879876
rule!(
880-
(optionalBindingCondition pattern: (identifierPattern identifier: @name))
877+
(optionalBindingCondition
878+
bindingSpecifier: @@spec
879+
pattern: (identifierPattern identifier: @name))
881880
=>
882881
(pattern_guard_expr
883882
value: (identifier #{name})
884883
pattern: (call_expr
885884
callee: (member_access_expr base: (identifier "Optional") member_name_node: (identifier "some"))
886885
argument: (argument value: (expr_pattern
887-
modifier: (modifier "let")
886+
modifier: (modifier #{spec})
888887
expr: (identifier #{name})))))
889888
),
890889
// A single condition in an `if`/`while`/`guard` condition list unwraps to
@@ -968,11 +967,18 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
968967
}),
969968
// try/try?/try! expr → unary_expr with operator "try", "try?" or "try!"
970969
rule!(
971-
(tryExpr questionOrExclamationMark: _? @@m expression: @e)
970+
(tryExpr
971+
tryKeyword: @@keyword
972+
questionOrExclamationMark: _? @@m
973+
expression: @e)
972974
=>
973975
expr {
974976
let op = format!("try{}", m.map(|m| ctx.source_text(m)).unwrap_or_default());
975-
tree!((unary_expr operator: (prefix_operator #{op}) operand: {e}))
977+
let operator_range =
978+
ctx.source_range_of_all(std::iter::once(keyword).chain(m));
979+
let operator =
980+
ctx.literal_with_source_range("prefix_operator", &op, operator_range);
981+
tree!((unary_expr operator: {operator} operand: {e}))
976982
}
977983
),
978984
// Do-catch → try_expr
@@ -1006,17 +1012,27 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
10061012
// Catch block without error binding
10071013
rule!((catchClause body: @body) => (catch_clause body: {body})),
10081014
// As expression (type cast) — as?, as!
1009-
rule!((asExpr expression: @val questionOrExclamationMark: _? @@mark type: @ty) => type_cast_expr {
1015+
rule!((asExpr expression: @val asKeyword: @@keyword questionOrExclamationMark: _? @@mark type: @ty) => type_cast_expr {
10101016
let op = format!("as{}", mark.map(|m| ctx.source_text(m)).unwrap_or_default());
1011-
tree!((type_cast_expr expr: {val} operator: (infix_operator #{op}) type: {ty}))
1017+
let operator_range =
1018+
ctx.source_range_of_all(std::iter::once(keyword).chain(mark));
1019+
let operator = ctx.literal_with_source_range("infix_operator", &op, operator_range);
1020+
tree!((type_cast_expr expr: {val} operator: {operator} type: {ty}))
10121021
}),
10131022
// Check expression (`x is T`) → type_test_expr
1014-
rule!((isExpr expression: @val type: @ty) => (type_test_expr expr: {val} operator: (infix_operator "is") type: {ty})),
1023+
rule!((isExpr expression: @val isKeyword: @@keyword type: @ty) => (type_test_expr
1024+
expr: {val}
1025+
operator: {ctx.literal_with_source_range_of("infix_operator", "is", keyword)}
1026+
type: {ty})),
10151027
// Await expression → unary_expr with operator "await"
1016-
rule!((awaitExpr expression: @val) => (unary_expr operator: (prefix_operator "await") operand: {val})),
1028+
rule!((awaitExpr awaitKeyword: @@keyword expression: @val) => (unary_expr
1029+
operator: {ctx.literal_with_source_range_of("prefix_operator", "await", keyword)}
1030+
operand: {val})),
10171031
// Force-unwrap (`x!`) → postfix unary_expr, via swift-syntax's dedicated
10181032
// `forceUnwrapExpr` node.
1019-
rule!((forceUnwrapExpr expression: @e) => (unary_expr operator: (postfix_operator "!") operand: {e})),
1033+
rule!((forceUnwrapExpr expression: @e exclamationMark: @@mark) => (unary_expr
1034+
operator: {ctx.literal_with_source_range_of("postfix_operator", "!", mark)}
1035+
operand: {e})),
10201036
// ---- Imports ----
10211037
// An import declaration. The dotted path (a list of
10221038
// `importPathComponent`s) becomes a `name_node`/`member_access_expr`
@@ -1036,9 +1052,14 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
10361052
import_declaration {
10371053
let last = *parts.last().ok_or("import has no path")?;
10381054
let pattern = match kind {
1039-
None => tree!((named_pattern
1040-
name_node: (identifier #{last})
1041-
sub_pattern: (bulk_importing_pattern))),
1055+
None => {
1056+
let bulk = ctx.with_node_source_range(ctx.matched_source_range, |ctx| {
1057+
tree!(ctx, (bulk_importing_pattern))
1058+
});
1059+
tree!((named_pattern
1060+
name_node: (identifier #{last})
1061+
sub_pattern: {bulk}))
1062+
}
10421063
Some(_) => tree!((identifier #{last})),
10431064
};
10441065
tree!((import_declaration

‎unified/extractor/tests/corpus/swift/desugar/import-with-deeply-nested-path-three-parts.output‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ top_level
3838
pattern:
3939
named_pattern
4040
name_node: identifier "URLSession"
41-
sub_pattern: bulk_importing_pattern
41+
sub_pattern: bulk_importing_pattern "import Foundation.Networking.URLSession"

‎unified/extractor/tests/corpus/swift/desugar/import-with-dotted-path-two-parts.output‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ top_level
3232
pattern:
3333
named_pattern
3434
name_node: identifier "Networking"
35-
sub_pattern: bulk_importing_pattern
35+
sub_pattern: bulk_importing_pattern "import Foundation.Networking"

‎unified/extractor/tests/corpus/swift/desugar/simple-import-with-single-name.output‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ top_level
2626
pattern:
2727
named_pattern
2828
name_node: identifier "Foundation"
29-
sub_pattern: bulk_importing_pattern
29+
sub_pattern: bulk_importing_pattern "import Foundation"

‎unified/extractor/tests/location_tests.rs‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,46 @@ fn generic_type_children_have_local_ranges() {
7171
assert_has_span(&ast, source, "identifier", Some("Foo"), "Foo");
7272
}
7373

74+
#[test]
75+
fn synthesized_condition_and_switch_nodes_use_child_ranges() {
76+
let source = "if a, b { c }\nswitch x { case a, b: c }";
77+
let ast = desugar(source);
78+
79+
assert_has_span(&ast, source, "binary_expr", None, "a, b");
80+
assert_has_empty_span(
81+
&ast,
82+
"infix_operator",
83+
Some("&&"),
84+
source.find(',').unwrap(),
85+
);
86+
assert_has_span(&ast, source, "or_pattern", None, "a, b");
87+
assert_has_span(&ast, source, "block", None, "c");
88+
}
89+
7490
#[test]
7591
fn declaration_and_operator_tokens_keep_precise_ranges() {
76-
let source = "func f() { return x }";
92+
let source = "func f() { return x }\nlet y = try? await value! as? T\nlet z = value is T";
7793
let ast = desugar(source);
7894

7995
assert_has_span(&ast, source, "block", None, "{ return x }");
8096
assert_has_span(&ast, source, "return_expr", None, "return x");
97+
assert_has_span(&ast, source, "prefix_operator", Some("try?"), "try?");
98+
assert_has_span(&ast, source, "prefix_operator", Some("await"), "await");
99+
assert_has_span(&ast, source, "postfix_operator", Some("!"), "!");
100+
assert_has_span(&ast, source, "infix_operator", Some("as?"), "as?");
101+
assert_has_span(&ast, source, "infix_operator", Some("is"), "is");
102+
}
103+
104+
#[test]
105+
fn synthetic_optional_binding_nodes_anchor_to_binding_keyword() {
106+
let source = "if let value = optional {}";
107+
let ast = desugar(source);
108+
let binding_start = source.find("let").unwrap();
109+
110+
assert_has_empty_span(&ast, "member_access_expr", None, binding_start);
111+
assert_has_empty_span(&ast, "identifier", Some("Optional"), binding_start);
112+
assert_has_empty_span(&ast, "identifier", Some("some"), binding_start);
113+
assert_has_span(&ast, source, "modifier", Some("let"), "let");
81114
}
82115

83116
#[test]

0 commit comments

Comments
 (0)