Skip to content

Commit 97b5a9d

Browse files
tausbnCopilot
andcommitted
unified: Stop using the tree-sitter-swift grammar
With the Swift front-end fully switched to swift-syntax, nothing links the tree-sitter Swift grammar any more. Remove every reference to it; the vendored crate itself is deleted in the following commit, so that this one shows only what actually changed. - Drop `unified/extractor/tree-sitter-swift` as a workspace member, path dependency and BUILD.bazel dependency. - Drop the now-unused `tree-sitter` and `tree-sitter-embedded-template` direct dependencies from the extractor (neither is referenced any longer; the tree-sitter runtime is still pulled in transitively where the shared extractor needs it). - Rewrite the "Swift Parser" section of `AGENTS.md`, which still pointed at `grammar.js` and `node-types.yml`, to describe `swift-syntax-parse` and the hand-maintained `swift_node_types.yml`, and note that the tests need the parser binary. The mapping's comments also explained many rules by how the tree-sitter path had behaved. That is now of historical interest only, so each is restated in terms of swift-syntax and the target AST alone — no rule changes, and the corpus is unaffected. Two were more than stylistic: - The `subscriptCallExpr` rule and its corpus case said the parser reports `xs[0]` and `xs(0)` identically. swift-syntax distinguishes them, so the collapse to `call_expr` is now purely ours, and a dedicated `subscript_expr` would need only a schema addition and a remap. - `discardAssignmentExpr` mapped to `name_expr` "because tree-sitter treated `_` as a name". The standing reason is that the target AST has no expression-level discard — only `ignore_pattern`, which is a pattern. References to tree-sitter's *node model* are kept: yeast is built on it, so `adapter.rs` still explains named/anonymous nodes, `extra` tokens and byte-offset conventions in those terms. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 80c4b44 commit 97b5a9d

10 files changed

Lines changed: 51 additions & 360 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 307 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ members = [
99
"shared/yeast-schema",
1010
"ruby/extractor",
1111
"unified/extractor",
12-
"unified/extractor/tree-sitter-swift",
1312
"unified/swift-syntax-rs",
1413
"rust/extractor",
1514
"rust/extractor/macros",

unified/AGENTS.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
# Agent instructions
22

3-
This is a CodeQL extractor based on tree-sitter.
3+
This is a CodeQL extractor that maps a language's parse tree onto a shared AST
4+
using the `yeast` desugaring engine. Swift, the only language so far, is parsed
5+
by Apple's swift-syntax rather than by tree-sitter.
46

57
## Building
68
- To build the extractor, run `scripts/create-extractor-pack.sh`
79

810
## Swift Parser
9-
- The Swift parser is defined by `extractor/tree-sitter-swift/grammar.js` and can be edited if needed.
11+
- Swift source is parsed by `swift-syntax-parse`, a small Swift/Rust binary in
12+
`swift-syntax-rs` that wraps Apple's swift-syntax and emits the parse tree as
13+
JSON. There is no grammar in this repository to edit.
1014

11-
- After editing the grammar, always run `scripts/regenerate-grammar.sh`.
15+
- `extractor/src/languages/swift/adapter.rs` converts that JSON into a yeast AST.
1216

13-
- The raw parse tree is described by `extractor/tree-sitter-swift/node-types.yml` and should be reviewed after grammar changes.
17+
- The raw parse tree's shape is described by `extractor/swift_node_types.yml`,
18+
which is maintained by hand.
1419

1520
## AST Mapping
1621
- The target AST shape is described by `extractor/ast_types.yml`.
1722

1823
- The mapping from the parse tree to the target AST is found in `extractor/src/languages/swift/swift.rs`
1924

20-
- To run tests for the parser and mapping, run `cargo test` in the `extractor` directory.
25+
- To run tests for the parser and mapping, run `cargo test` in the `extractor`
26+
directory. The tests need the `swift-syntax-parse` binary: point
27+
`CODEQL_EXTRACTOR_UNIFIED_SWIFT_SYNTAX_PARSE` at it, or put it on `PATH`.
28+
Corpus tests skip themselves when it cannot be found, so check for skips
29+
before concluding a change is clean.
2130

2231
- Extractor test cases are located at `extractor/tests/corpus/swift/*/*.swift`.
2332

unified/extractor/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,5 @@ codeql_rust_binary(
2020
) + [
2121
"//shared/tree-sitter-extractor",
2222
"//shared/yeast",
23-
"//unified/extractor/tree-sitter-swift",
2423
],
2524
)

unified/extractor/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ edition = "2024"
77

88
# When updating these dependencies, run `misc/bazel/3rdparty/update_cargo_deps.sh`
99
[dependencies]
10-
tree-sitter = ">= 0.23.0"
11-
tree-sitter-embedded-template = "0.25.0"
12-
tree-sitter-swift = { path = "tree-sitter-swift" }
1310
clap = { version = "4.5", features = ["derive"] }
1411
tracing = "0.1"
1512
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }

unified/extractor/ast_types.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ supertypes:
5656
# A statement is anything that can appear in a block.
5757
# This type contains all of 'expr' and has partial overlap with 'member'.
5858
# For example, type_alias_declaration can appear either as a stmt or member.
59-
# constructor_declaration and destructor_declaration appear here because
60-
# tree-sitter-swift's error recovery for #if/#endif in class bodies can place
61-
# init/deinit declarations at the wrong (statement) level.
6259
stmt:
6360
- expr
6461
- variable_declaration

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
//! * Collection nodes are already elided to JSON arrays upstream, so a
1818
//! list-valued field maps directly to that field holding several children.
1919
//!
20-
//! Note: this preserves swift-syntax's own kind/field names. Aligning those
21-
//! names with the tree-sitter-swift schema (so the rewrite rules in
22-
//! [`super::swift`] fire) is done incrementally in the rules.
20+
//! Note: this preserves swift-syntax's own kind/field names; the rewrite rules
21+
//! in [`super::swift`] match those names directly.
2322
2423
use std::collections::BTreeMap;
2524

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

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ fn chained_modifier(ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>) -> Optio
7777

7878
/// Combine a list of boolean sub-conditions into a single expression by
7979
/// left-folding with the infix `&&` operator. Used by control-flow
80-
/// rules (`if`, `guard`, `while`, `repeat-while`) whose tree-sitter
81-
/// nodes carry one or more comma-separated conditions that the target
80+
/// rules (`if`, `guard`, `while`, `repeat-while`), which carry one or
81+
/// more comma-separated conditions that the target
8282
/// AST represents as a single `condition:` field. Panics on an empty
8383
/// input because every caller's grammar guarantees at least one
8484
/// condition.
@@ -144,7 +144,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
144144
// ---- Literals ----
145145
// swift-syntax does not distinguish the lexical integer/string forms
146146
// (hex/binary/octal, single- vs multi-line, raw): each is a single
147-
// `*LiteralExpr` kind, so the tree-sitter variants collapse to one rule.
147+
// `*LiteralExpr` kind, so one rule per literal type suffices.
148148
rule!((integerLiteralExpr) => (int_literal)),
149149
rule!((floatLiteralExpr) => (float_literal)),
150150
rule!((booleanLiteralExpr) => (boolean_literal)),
@@ -169,8 +169,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
169169
rule!((declReferenceExpr baseName: @name) => (name_expr identifier: (identifier #{name}))),
170170
// A discard `_` used as an expression — e.g. the target of a discarding
171171
// assignment `_ = x`. swift-syntax models it as a `discardAssignmentExpr`;
172-
// the tree-sitter path treated the bare `_` as a name, so map it to a
173-
// `name_expr` too.
172+
// the target AST has no expression-level discard (only `ignore_pattern`,
173+
// which is a pattern), so it becomes a `name_expr` over the `_` token.
174174
rule!((discardAssignmentExpr wildcard: @@w) => (name_expr identifier: (identifier #{w}))),
175175
// ---- Operators ----
176176
// The parser front-end folds operator chains into nested
@@ -244,10 +244,9 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
244244
accessor_kind: (accessor_kind "get")
245245
body: (block stmt: {body}))
246246
),
247-
// A property with an explicit accessor block. The two shapes differ only
248-
// by the presence of an initializer (tree-sitter split them into distinct
249-
// `willset_didset_block` vs computed-accessor node types; swift-syntax
250-
// makes both plain `accessorDecl`s):
247+
// A property with an explicit accessor block. swift-syntax makes both
248+
// shapes plain `accessorDecl`s, so they are told apart by the presence
249+
// of an initializer:
251250
//
252251
// * With an initializer (`var x: T = e { willSet {…} didSet {…} }`) it is
253252
// a *stored* property with observers: emit the backing
@@ -398,8 +397,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
398397
// payload parameters; an element with a raw value (`case a = 1`) or a
399398
// plain element (`case north`) becomes a `variable_declaration`. All
400399
// carry the shared case modifiers / chained tag from `ctx` (set by the
401-
// `enumCaseDecl` rule below) and are tagged `enum_case` (after any
402-
// `chained_declaration` tag, matching the tree-sitter modifier order).
400+
// `enumCaseDecl` rule below) and are tagged `enum_case`, after any
401+
// `chained_declaration` tag.
403402
rule!(
404403
(enumCaseElement name: @name parameterClause: (enumCaseParameterClause parameters: _* @params))
405404
=>
@@ -432,8 +431,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
432431
// Enum cases. A single `case` declaration may carry modifiers
433432
// (e.g. `indirect`) and list several comma-separated elements; each
434433
// becomes its own declaration carrying those shared modifiers, and
435-
// non-first ones are tagged `chained_declaration` (mirroring the
436-
// tree-sitter `enum_entry` rule). The modifiers are published into `ctx`
434+
// non-first ones are tagged `chained_declaration`. The modifiers are
435+
// published into `ctx`
437436
// for the element rules above, which build the actual declaration.
438437
rule!(
439438
(enumCaseDecl modifiers: _* @mods elements: _* @@cases)
@@ -530,7 +529,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
530529
// A function declaration (parameters/return type/body optional). The
531530
// parameters and return type nest under `signature`; the body is a
532531
// `codeBlock`. A bodyless function (a protocol requirement) still emits
533-
// an empty `block`, matching the tree-sitter path.
532+
// an empty `block`.
534533
rule!(
535534
(functionDecl
536535
name: @name
@@ -723,8 +722,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
723722
condition: {and_chain(&mut ctx, cond)}
724723
else: {else_stmts})
725724
),
726-
// Ternary (`c ? a : b`) desugars to an `if_expr`, as in the tree-sitter
727-
// path.
725+
// Ternary (`c ? a : b`) desugars to an `if_expr`.
728726
rule!(
729727
(ternaryExpr condition: @cond thenExpression: @then_val elseExpression: @else_val)
730728
=>
@@ -769,8 +767,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
769767
(pattern_guard_expr pattern: {pat} value: {val})
770768
),
771769
// Optional binding (`if let x = foo`, or shorthand `if let x`) desugars
772-
// to a `pattern_guard_expr` matching `Optional.some(x)`, exactly as the
773-
// tree-sitter path does. The initialized form is matched first.
770+
// to a `pattern_guard_expr` matching `Optional.some(x)`. The initialized
771+
// form is matched first.
774772
rule!(
775773
(optionalBindingCondition
776774
pattern: (identifierPattern identifier: @name)
@@ -845,10 +843,12 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
845843
),
846844
rule!((arrayElement expression: @e) => expr { e }),
847845
// A dictionary literal (`["a": 1]`) is kept as an opaque `map_literal`
848-
// leaf (its source span), matching the tree-sitter path.
846+
// leaf (its source span).
849847
rule!((dictionaryExpr) => (map_literal)),
850-
// A subscript access (`xs[0]`) is modelled as a call, exactly as the
851-
// tree-sitter grammar does (it parses `xs[0]` like `xs(0)`).
848+
// A subscript access (`xs[0]`) is modelled as a call. swift-syntax does
849+
// report a distinct `subscriptCallExpr`, so giving
850+
// subscripts their own shape needs only a `subscript_expr` node in
851+
// ast_types.yml and a remap here.
852852
rule!(
853853
(subscriptCallExpr calledExpression: @callee arguments: _* @args)
854854
=>
@@ -898,9 +898,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
898898
rule!((isExpr expression: @val type: @ty) => (type_test_expr expr: {val} operator: (infix_operator "is") type: {ty})),
899899
// Await expression → unary_expr with operator "await"
900900
rule!((awaitExpr expression: @val) => (unary_expr operator: (prefix_operator "await") operand: {val})),
901-
// Force-unwrap (`x!`) → postfix unary_expr. swift-syntax has a dedicated
902-
// `forceUnwrapExpr` node (the tree-sitter path used the generic postfix
903-
// operator rule instead).
901+
// Force-unwrap (`x!`) → postfix unary_expr, via swift-syntax's dedicated
902+
// `forceUnwrapExpr` node.
904903
rule!((forceUnwrapExpr expression: @e) => (unary_expr operator: (postfix_operator "!") operand: {e})),
905904
// ---- Imports ----
906905
// An import declaration. The dotted path (a list of
@@ -961,8 +960,8 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
961960
// A named type (`Int`). `identifierType.name` is the type-name token.
962961
rule!((identifierType name: @@n) => (named_type_expr name: (identifier #{n}))),
963962
// A qualified type (`Outer.Inner`, `NSString.CompareOptions`). swift-syntax
964-
// nests these as `memberType` nodes; like the old tree-sitter `user_type`
965-
// rule, we keep the whole dotted path as the opaque `named_type_expr` name.
963+
// nests these as `memberType` nodes; we keep the whole dotted path as the
964+
// opaque `named_type_expr` name.
966965
rule!((memberType) @ty => (named_type_expr name: (identifier #{ty}))),
967966
// Sugared types desugar to `generic_type_expr`: `T?` -> Optional<T>,
968967
// `[T]` -> Array<T>, `[K: V]` -> Dictionary<K, V>.
@@ -1029,9 +1028,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
10291028
// expansions uniformly as a `macroExpansionExpr`).
10301029
rule!((macroExpansionExpr) => (unsupported_node)),
10311030
// A nominal type's `inheritanceClause` (`: Base, Proto`) becomes a list
1032-
// of `base_type`s, one per inherited type. The tree-sitter path dropped
1033-
// it (no corpus target had a `base_type`) and the mapping matched that
1034-
// for parity; swift-syntax exposes it cleanly. Each declaration keyword
1031+
// of `base_type`s, one per inherited type. Each declaration keyword
10351032
// gets its own rule; the bodies are identical but for the keyword.
10361033
// Class declaration with body containing members
10371034
rule!(
@@ -1100,8 +1097,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
11001097
// An `extension Foo { … }` is likewise a `class_like_declaration`, named
11011098
// by the extended type. The extended type is captured opaquely (as its
11021099
// source text) so that qualified names (`extension String.Interpolation`,
1103-
// a `memberType`) name the declaration just like simple ones, matching the
1104-
// old tree-sitter `user_type` behaviour.
1100+
// a `memberType`) name the declaration just like simple ones.
11051101
rule!(
11061102
(extensionDecl
11071103
extensionKeyword: @kind

unified/extractor/tests/corpus/swift/collections/subscript-access.output

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// TODO: tree-sitter-swift parses `xs[0]` as a call_expression (same shape
2-
// as `xs(0)`), so the mapping currently produces a call_expr. Update the
3-
// parser / add a separate subscript_expr node and remap when fixed.
1+
// TODO: `xs[0]` is mapped to a call_expr, even though swift-syntax reports a
2+
// distinct subscriptCallExpr. Giving subscripts their own shape needs only a
3+
// subscript_expr node in ast_types.yml and a remap.
44
let first = xs[0]
55

66
---
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// TODO: tree-sitter-swift parses `xs[0]` as a call_expression (same shape
2-
// as `xs(0)`), so the mapping currently produces a call_expr. Update the
3-
// parser / add a separate subscript_expr node and remap when fixed.
1+
// TODO: `xs[0]` is mapped to a call_expr, even though swift-syntax reports a
2+
// distinct subscriptCallExpr. Giving subscripts their own shape needs only a
3+
// subscript_expr node in ast_types.yml and a remap.
44
let first = xs[0]

0 commit comments

Comments
 (0)