Skip to content

Commit 5e6b1af

Browse files
author
Your Name
committed
docs: add rust support research and superskills plan
1 parent 7d2a2f8 commit 5e6b1af

3 files changed

Lines changed: 1716 additions & 3 deletions

File tree

.ci-bin/x86_64-unknown-linux-musl/ci

Lines changed: 0 additions & 3 deletions
This file was deleted.

docs/rust-support-research.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
---
2+
title: "Nghiên cứu: Hỗ trợ Rust top-tier cho ci"
3+
date: 2026-07-03
4+
status: RESEARCH — đề xuất, chưa phải quyết định (ADR sẽ chốt sau khi review)
5+
related: ADR-0002, ADR-0004, docs/comparison.md
6+
---
7+
8+
# Rust support cho `ci` — nghiên cứu có kiểm chứng thực nghiệm
9+
10+
> **TL;DR**: Kết luận "không có đường rẻ cho Rust — mọi hướng đều cần compiler thật + code
11+
> build được" là **sai một nửa quan trọng**. Thực nghiệm trên máy thật (2026-07-03) chứng minh
12+
> `rust-analyzer scip` (chế độ batch, không phải LSP server) chạy tốt trên **workspace không
13+
> compile được** (lỗi type + lỗi syntax), tốn 4–22s và 1–2GB RAM cho repo nhỏ-vừa, output chứa
14+
> đầy đủ resolution mà không heuristic nào với tới (method call trên receiver không annotation,
15+
> dyn dispatch, cross-crate moniker). Điều nó cần không phải "build thành công" mà là
16+
> "`cargo metadata` load được" — một điều kiện yếu hơn rất nhiều.
17+
>
18+
> Lời giải đề xuất: **2 tầng** — (1) nâng cấp tầng syntactic Rust-native (sửa 1 bug thật +
19+
> 4 nâng cấp, zero dependency mới, luôn chạy, robust tuyệt đối) và (2) SCIP overlay từ
20+
> `rust-analyzer scip` như tầng Formal opt-in/batch/additive-only, đúng khung ADR-0004 nhưng
21+
> **rẻ và đơn giản hơn phương án live-LSP** mà ADR-0004 phác thảo.
22+
23+
---
24+
25+
## 1. Phương pháp
26+
27+
Khác với các vòng desk-research trước, vòng này **chạy thí nghiệm thật** trên máy dev:
28+
29+
- Tạo workspace 2-crate cố tình hỏng (lỗi type trong `core`, lỗi syntax trong `app`,
30+
trait + dyn dispatch, cross-crate call, method call không annotation).
31+
- Chạy `rust-analyzer scip` (v1.96.0, 2026-05-25) trên: workspace hỏng đó, chính repo `ci`,
32+
và ripgrep vừa clone (cold).
33+
- Decode SCIP protobuf bằng `scip` CLI v0.9.0, soi từng occurrence.
34+
- Chạy `ci index` (binary release hiện tại) trên cùng workspace hỏng, dump SQLite,
35+
so từng edge với SCIP — đo **baseline gap thật** thay vì suy đoán.
36+
37+
## 2. Kết quả thực nghiệm
38+
39+
### 2.1 `rust-analyzer scip` KHÔNG cần code build được
40+
41+
Workspace 2-crate, `cargo check` **fail** (E0308 + syntax error):
42+
43+
| Kiểm tra | Kết quả |
44+
|---|---|
45+
| Exit code `rust-analyzer scip .` | **0**, index sinh ra trong **4.0s** |
46+
| Symbol trong file có lỗi type (`type_error_fn`) | ✅ có DEF |
47+
| Symbol là chính hàm lỗi syntax (`broken_syntax`) | ✅ vẫn có DEF |
48+
| `let e = Engine::new(); e.start()` — receiver **không annotation** | ✅ ref → `engine/impl#[Engine]start()` |
49+
| `r.run()` với `r: &dyn Runner` | ✅ ref → `Runner#run()` (trait method — đúng ngữ nghĩa) |
50+
| Cross-crate: `main.rs` gọi `demo-core` | ✅ moniker đầy đủ `demo-core 0.1.0 engine/Engine#` |
51+
| `impl Runner for FastRunner` | ✅ moniker `impl#[FastRunner][Runner]run()` — quan hệ Type–Trait mã hoá ngay trong symbol string |
52+
| Toán tử `+`, `println!` | ✅ resolve về `core`/`std` (lọc bỏ được dễ dàng qua moniker prefix) |
53+
54+
Điều kiện thật sự: **`cargo metadata` phải chạy được** (mọi `Cargo.toml` well-formed, deps
55+
resolve được — lần đầu cần network hoặc cache `~/.cargo` sẵn; repo đang được dev active thì
56+
gần như luôn sẵn). Nó cũng chạy build script/proc-macro qua `cargo check` phía dưới (tạo
57+
`target/`) — tắt được qua `--config-path` nếu cần chế độ zero-side-effect, đổi lấy độ phủ
58+
proc-macro thấp hơn.
59+
60+
### 2.2 Chi phí đo được
61+
62+
| Corpus | Wall | RAM peak | Index |
63+
|---|---|---|---|
64+
| Workspace 2-crate hỏng | 4.0s | không đáng kể | 5.6KB |
65+
| **Chính repo `ci`** (3 crates + deps nặng: stack-graphs, grammars, tokio) | **21.5s** | 1.9GB | 4.3MB — 58 docs, 44,141 occurrences (6,685 defs / 37,456 refs) |
66+
| ripgrep, fresh clone (cold, fetch deps trong lúc chạy) | 20.1s | 1.1GB | 7.7MB |
67+
| `cargo metadata --no-deps` trên `ci` | **44ms** || crate-name → src-root map |
68+
69+
So sánh đúng bản chất: đây **không phải** chi phí kiểu "LSP cold-start 30s + hang 75s mỗi
70+
session" trong ADR-0004 — đây là **batch job chạy nền một lần, cache được theo (RA version,
71+
Cargo.lock hash, dirty-files hash)**, không giữ process, không JSON-RPC, không lifecycle.
72+
73+
### 2.3 Baseline gap của `ci` hiện tại trên Rust (đo trên cùng workspace)
74+
75+
`ci index` hôm nay (9 symbols, 4 call edges) so với ground truth:
76+
77+
1. **BUG — `pub use` vô hình hoàn toàn**: `parse_rust_import` làm
78+
`text.strip_prefix("use ")` (`imports.rs:156`) → `"pub use engine::Engine"` trả `None`.
79+
Re-export façade (`lib.rs` pattern phổ biến bậc nhất Rust) biến mất khỏi graph.
80+
2. **Cross-crate import chết**: `use demo_core::Engine``import_edges.to_path = NULL`.
81+
Không có mapping crate-name (`demo-core``demo_core`) → thư mục crate. Đồng thời
82+
`resolve_module_to_path` strip `crate/`/`super/`/`self/` rồi thử `src/…` từ **repo root**
83+
(`pipeline.rs:559-575`) — sai với mọi workspace nhiều crate, kể cả chính `ci`.
84+
3. **Method call chỉ đạt `textual` khi thiếu annotation**: `let e = Engine::new(); e.start()`
85+
→ textual (đúng target nhờ may mắn tên `start` duy nhất). Tier-2 hiện chỉ đọc
86+
`let x: Foo` và typed params — bỏ qua quy ước constructor `Foo::new()`/`Foo::default()`.
87+
4. **Trait method declaration không là symbol**: `Runner::run` không tồn tại trong DB;
88+
field `trait` của `impl_item` bị vứt → không trả lời được "ai implement `Runner`".
89+
Edge `call_dynamic → FastRunner::run` được gắn `resolved` **do trùng tên may mắn**
90+
(bare-name `run` nằm trong `file_symbols`) — semantics thật là dispatch qua trait.
91+
5. **Không index**: `mod` declarations (→ không có module tree), `enum_item`, `const/static`,
92+
`type_item`, `macro_definition`, `macro_invocation`.
93+
94+
## 3. Đối chiếu với báo cáo desk-research trước
95+
96+
| Kết luận cũ | Kiểm chứng |
97+
|---|---|
98+
| Scope-graphs/stack-graphs không mô hình được trait resolution (constraint solving) | ✅ Đúng, giữ nguyên. Không viết `.tsg` cho Rust — lý thuyết capped + upstream archived 09/2025. |
99+
| Meta Glean dùng rust-analyzer SCIP mode | ✅ Đúng — và thêm: Sourcegraph `scip-rust` (v0.0.6, 05/2026) cũng chỉ là **wrapper quanh `rust-analyzer scip`**. Hội tụ industry tuyệt đối. |
100+
| `cargo-call-stack` cần LLVM-IR + nightly + bó tay dyn | ✅ Đúng, loại. |
101+
| "Cả LSP subprocess lẫn embed `ra_ap_*` đều không rẻ và an toàn" | ⚠️ Đúng cho **2 hướng đã xét** — nhưng bỏ sót hướng thứ ba: **batch `rust-analyzer scip`**, không phải LSP (không process sống, không cold-start mỗi session), không phải embed (không đụng API unstable, không phình binary). |
102+
| "Cần compiler thật + code build được" |**Bác bỏ bằng thực nghiệm** — cần `cargo metadata` load được, không cần build thành công. Lỗi type/syntax trong workspace không chặn index. |
103+
| Sợi dây `hir_def`-only cần spike thật | ✅ Spike đã chạy — và câu trả lời hay hơn kỳ vọng: không cần lát cắt `hir_def` riêng vì (a) phần "module-tree/import resolution không cần type info" **tự implement được ~vài trăm dòng** trên nền tree-sitter sẵn có (mục 4, Tầng 0), (b) phần cần type info thì batch scip đã cho với chi phí chấp nhận được. Đóng sợi dây này. |
104+
| rustdoc JSON (chưa xét trong báo cáo) | Loại: vẫn nightly-only 2026, và không chứa reference/call-site data — chỉ có ích cho API surface, thứ tree-sitter đã làm. |
105+
106+
## 4. Lời giải đề xuất — 2 tầng, đúng triết lý "sống trong môi trường"
107+
108+
Nguyên tắc phân vai (theo yêu cầu thiết kế của `ci`): không cạnh tranh với những gì môi
109+
trường agent đã làm tốt — diagnostics là việc của `cargo check`/IDE; go-to-def một-lần-một
110+
là việc của LSP trong IDE. Niche của `ci`**whole-repo call graph + risk metrics +
111+
token-efficient context, robust trên code hỏng, chạy được cả headless CLI**. Mọi đề xuất
112+
dưới đây chỉ phục vụ việc làm graph đó **đúng hơn**, không biến `ci` thành language server.
113+
114+
### Tầng 0 — Rust-native syntactic upgrade (luôn chạy, zero dependency mới)
115+
116+
Rust là ngôn ngữ hiếm hoi mà **import/module resolution là thuần cú pháp + quy ước**
117+
không dynamic như Python/JS. Phần này heuristic làm được gần-hoàn-hảo, không cần compiler:
118+
119+
- **R0.1 — Fix bug `pub use`** (gap #1): parse `use_declaration` bằng cấu trúc node tree-sitter
120+
(`visibility_modifier`, `scoped_use_list`, `use_as_clause`, `use_wildcard`) thay vì string
121+
split — sửa luôn nested groups `use a::{b::{c,d}, e}` mà string-split hiện bóp méo.
122+
- **R0.2 — Workspace crate map** (gap #2): đọc `cargo metadata --no-deps` (44ms) khi có cargo;
123+
fallback parse TOML thủ công khi không có (giữ nguyên zero-dependency install). Map
124+
`package.name` (chuẩn hoá `-``_`) → src root (`lib.rs`/`main.rs`, `[lib] path`).
125+
- **R0.3 — Module tree thật** (gap #5): index `mod foo;` + `#[path]` + quy ước
126+
`foo.rs`/`foo/mod.rs` → thay thế đường strip-prefix sai trong `resolve_module_to_path`
127+
bằng resolution `crate::`/`super::`/`self::` đúng theo vị trí file trong module tree.
128+
Đây chính là phần "DefCollector không cần type info" — fixed-point chỉ cần cho glob
129+
re-export, có thể bound hoặc bỏ qua glob ở v1.
130+
- **R0.4 — Re-export chain**: sau R0.1+R0.3, follow `pub use` chains (non-glob) khi resolve
131+
import target — `use mylib::Engine` tìm thấy `engine::Engine` qua façade.
132+
- **R0.5 — Trait surface** (gap #4): trait method declarations thành symbols; lưu quan hệ
133+
`(impl_type, trait)` từ 2 field sẵn có của `impl_item`. Mở khoá: "ai implement X",
134+
candidate edges cho dyn call (đúng pattern `MAX_CALLEE_CANDIDATES` sẵn có), và chấm dứt
135+
chuyện dyn-dispatch được gắn `resolved` nhờ trùng tên.
136+
- **R0.6 — Constructor inference** (gap #3): `let x = Foo::new(...)`/`Foo::default()`/
137+
`Foo { .. }``type_map[x] = Foo` (tier-`inferred`, đúng contract hiện hành).
138+
- **R0.7 (tuỳ chọn, sau)**: `enum_item`/`const`/`type_item`/`macro_definition` thành symbols;
139+
`macro_invocation` → edge tới `macro_rules!` cùng crate.
140+
141+
Trần của Tầng 0 (chấp nhận, đo và dán nhãn confidence thay vì giấu): receiver qua biểu thức
142+
tuỳ ý / method chaining, generic bounds, code sinh bởi proc-macro.
143+
144+
### Tầng 1 — SCIP overlay từ `rust-analyzer scip` (Formal tier, opt-in, batch, additive-only)
145+
146+
Đúng 6 nguyên tắc ADR-0004 (opt-in qua config, detect-once fail-silent, additive-only,
147+
chạy sau `ready`, tái dùng rank `Formal`, per-language decision) — nhưng thay transport:
148+
**batch subprocess sinh file SCIP, không phải LSP client sống**. Đơn giản hơn phương án
149+
gopls-pilot của ADR-0004 ở mọi trục vận hành (không process lifecycle, không request
150+
budget per-call, không leak).
151+
152+
- **Detect**: `rust-analyzer` trên PATH → rustup component → binary bundle trong VS Code
153+
extension (`~/.vscode/extensions/rust-lang.rust-analyzer-*/server/`). Máy Rust dev gần như
154+
luôn có ≥1 đường (máy dev này có cả 3).
155+
- **Chạy**: nền, sau `indexing_phase=ready`, timeout cứng (đề xuất 120s — đo được 22s cho
156+
`ci`), nice/ionice thấp. Cache theo (RA version, `Cargo.lock` hash, set file dirty).
157+
- **Ingest**: đối chiếu SCIP occurrence `(file, line, moniker)` với call site
158+
`(from_path, call_line, callee_name)` sẵn có → nâng edge lên `Formal`. Def moniker →
159+
`(path, range)``qualified_name`. Moniker `impl#[Type][Trait]method()` cho quan hệ
160+
trait-impl formal. **Không bao giờ** tạo/xoá/hạ edge — đúng ADR-0004 §3.
161+
- **Bonus kiến trúc**: module ingest này là **SCIP chung**, không Rust-riêng — cùng code
162+
đường sau nhận `scip-typescript`/`scip-java`/`scip-clang`, và cho phép mô hình Glean-style:
163+
CI pipeline build index artifact tập trung, agent tải về — máy yếu không phải trả 2GB RAM.
164+
- **Rủi ro & đối phó**: (a) help text ghi rõ subcommand "no stability guarantees" → golden
165+
test nhỏ chạy khi detect version mới, lệch thì tắt overlay cho session, log một lần;
166+
(b) staleness giữa 2 lần chạy → edge mới sinh ra giữa chừng chỉ có confidence syntactic
167+
cho tới lần chạy sau — chấp nhận được vì additive-only nghĩa là không bao giờ *tệ hơn*
168+
baseline; (c) repo không phải Cargo (Bazel/Buck) → RA hỗ trợ `rust-project.json`, còn
169+
không có thì Tầng 0 vẫn nguyên vẹn.
170+
171+
### Tầng 2 — (chỉ khi đo thấy cần) live-LSP theo đúng pilot plan ADR-0004
172+
173+
Nếu sau khi ship Tầng 1, staleness thành vấn đề thật (đo bằng tỉ lệ query chạm edge chưa
174+
được xác nhận), lúc đó mới xét rust-analyzer LSP sống / `ra-multiplex`. Phát hiện batch-SCIP
175+
này nên được ghi vào ADR-0004 như một cập nhật: với Rust (và mọi ngôn ngữ có SCIP indexer
176+
trưởng thành), **batch SCIP đi trước live LSP** trong thứ tự cân nhắc.
177+
178+
## 5. Benchmark khép vòng — SCIP làm oracle
179+
180+
Tác dụng phụ giá trị nhất của Tầng 1: SCIP output là **ground truth để đo Tầng 0**.
181+
Chạy trên chính `ci` (dogfood) + 2-3 repo Rust thật (vd ripgrep): precision/recall của
182+
call edges syntactic so với SCIP, trước/sau mỗi hạng mục R0.x — biến "hỗ trợ Rust tốt
183+
chưa" thành con số theo dõi được qua `benchmarks/` harness sẵn có.
184+
185+
## 6. Những hướng đã xét và loại (kèm lý do một dòng)
186+
187+
- **Viết `.tsg` stack-graphs cho Rust**: lý thuyết capped (trait solving = constraint
188+
satisfaction, không phải name resolution), upstream archived, effort khổng lồ.
189+
- **Embed `ra_ap_*`**: API không cam kết ổn định, phình binary, gánh maintenance — batch
190+
subprocess đạt cùng chất lượng với zero coupling.
191+
- **`cargo-call-stack`/LLVM-IR**: cần codegen thật + nightly flags, chết với dyn dispatch.
192+
- **rustdoc JSON**: nightly-only (xác nhận 2026), không có reference data.
193+
- **Lát cắt `hir_def`-only tự trích**: mục đích của nó (resolution không cần build) đã đạt
194+
bằng đường rẻ hơn nhiều (Tầng 0 tự viết + batch scip). Đóng.
195+
196+
## 7. Effort ước lượng
197+
198+
| Hạng mục | Effort | Phụ thuộc |
199+
|---|---|---|
200+
| R0.1–R0.6 (syntactic) | ~2–4 ngày, thuần Rust trên codebase sẵn | không |
201+
| Fixture workspace Rust + parity tests | ~0.5 ngày | không |
202+
| Tầng 1 (SCIP ingest + runner + cache) | ~3–5 ngày | `scip` protobuf parse (crate `scip` hoặc prost tự sinh — vet license/size trước) |
203+
| Benchmark oracle hoá | ~1 ngày | Tầng 1 |
204+
205+
## Phụ lục — lệnh tái lập thí nghiệm
206+
207+
```bash
208+
# workspace hỏng: xem scratchpad session 2026-07-03; cấu trúc 2 crate,
209+
# core/src/lib.rs chứa lỗi type E0308, app/src/main.rs chứa lỗi syntax
210+
cargo check # → FAIL (2 lỗi)
211+
rust-analyzer scip . --output broken.scip # → exit 0, ~4s
212+
scip print --json broken.scip # → đầy đủ defs/refs kể cả file lỗi
213+
214+
/usr/bin/time -v rust-analyzer scip /path/to/ci --output ci.scip
215+
# → 21.5s wall, 1.9GB peak, 44,141 occurrences
216+
217+
cargo metadata --no-deps --format-version 1 # → 44ms, crate→src-root map
218+
```
219+
220+
Nguồn ngoài: [rust-analyzer SCIP CLI](https://rust-lang.github.io/rust-analyzer/rust_analyzer/cli/scip/index.html) ·
221+
[sourcegraph/scip-rust — wrapper quanh `rust-analyzer scip`](https://github.com/sourcegraph/scip-rust) ·
222+
[Glean lsif-rust](https://glean.software/docs/indexer/lsif-rust/) ·
223+
[RA persistent cache #4712 (vẫn mở)](https://github.com/rust-lang/rust-analyzer/issues/4712) ·
224+
[Port RA sang salsa 3.0](https://hackmd.io/@salsa/B19OUlA71l) ·
225+
[rustdoc JSON RFC 2963](https://rust-lang.github.io/rfcs/2963-rustdoc-json.html) ·
226+
[Rust project goals 04/2026](https://blog.rust-lang.org/2026/05/18/project-goals-2026-04/) ·
227+
[Trait solving — rustc-dev-guide](https://rustc-dev-guide.rust-lang.org/traits/resolution.html) ·
228+
[ra-multiplex](https://github.com/pr2502/ra-multiplex)

0 commit comments

Comments
 (0)