1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#![cfg_attr(coverage_nightly, coverage(off))]
//! Rust-specific variable extraction from AST nodes
use super::{Variable, VariableInspector};
use tree_sitter::{Node, Tree};
impl VariableInspector {
/// Extract variables from Rust AST
pub(super) fn extract_variables_rust(
&self,
tree: &Tree,
source: &str,
target_line: usize,
) -> Result<Vec<Variable>, String> {
let root_node = tree.root_node();
let bytes = source.as_bytes();
// Convert line number to byte position (lines are 0-indexed internally)
let target_line_idx = if target_line > 0 { target_line - 1 } else { 0 };
// Validate line number is within bounds
let max_line = root_node.end_position().row;
if target_line_idx > max_line {
return Err(format!(
"Line {} is out of bounds (file has {} lines)",
target_line,
max_line + 1
));
}
// Find the scope containing the target line
let scope_node = self.find_scope_at_line(root_node, target_line_idx);
if scope_node.is_none() {
return Ok(Vec::new());
}
let scope = scope_node.expect("internal error");
let mut variables = Vec::new();
// Find the parent function to get the scope we should search from
let search_scope = if let Some(func_node) = self.find_parent_function(scope) {
// Extract function parameters
self.extract_rust_function_params(func_node, bytes, &mut variables);
func_node
} else {
scope
};
// Extract let bindings from the function or scope
self.extract_rust_let_bindings(search_scope, bytes, &mut variables, target_line_idx);
// Deduplicate variables by name (keep last occurrence for shadowing)
Ok(self.deduplicate_variables(variables))
}
/// Extract Rust let bindings
pub(super) fn extract_rust_let_bindings(
&self,
node: Node,
bytes: &[u8],
variables: &mut Vec<Variable>,
target_line: usize,
) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
// Only include bindings before or at target line
if child.start_position().row > target_line {
continue;
}
if child.kind() == "let_declaration" {
// Find the identifier node (variable name)
let mut decl_cursor = child.walk();
let mut var_name = String::new();
let mut value_node = None;
for decl_child in child.children(&mut decl_cursor) {
match decl_child.kind() {
"identifier" if var_name.is_empty() => {
var_name = decl_child.utf8_text(bytes).unwrap_or("").to_string();
}
"integer_literal" | "float_literal" | "string_literal"
| "boolean_literal" | "true" | "false" => {
value_node = Some(decl_child);
}
_ => {}
}
}
if !var_name.is_empty() {
let type_info = if let Some(val_node) = value_node {
self.infer_rust_type(val_node, bytes)
} else {
"unknown".to_string()
};
variables.push(Variable {
name: var_name,
value: String::new(),
type_info,
variables_reference: None,
});
}
}
// Recurse into child scopes
self.extract_rust_let_bindings(child, bytes, variables, target_line);
}
}
/// Extract Rust function parameters
pub(super) fn extract_rust_function_params(
&self,
func_node: Node,
bytes: &[u8],
variables: &mut Vec<Variable>,
) {
if let Some(params_node) = func_node.child_by_field_name("parameters") {
let mut cursor = params_node.walk();
for param in params_node.children(&mut cursor) {
if param.kind() == "parameter" {
// Extract identifier and type from parameter children
let mut param_cursor = param.walk();
let mut name = String::new();
let mut type_info = String::from("unknown");
for param_child in param.children(&mut param_cursor) {
match param_child.kind() {
"identifier" if name.is_empty() => {
name = param_child.utf8_text(bytes).unwrap_or("").to_string();
}
"primitive_type" | "type_identifier" => {
type_info = param_child
.utf8_text(bytes)
.unwrap_or("unknown")
.to_string();
}
_ => {}
}
}
if !name.is_empty() {
variables.push(Variable {
name,
value: String::new(),
type_info,
variables_reference: None,
});
}
}
}
}
}
/// Infer Rust type from literal
pub(super) fn infer_rust_type(&self, node: Node, _bytes: &[u8]) -> String {
match node.kind() {
"integer_literal" => "i32".to_string(),
"float_literal" => "f64".to_string(),
"string_literal" => "&str".to_string(),
"boolean_literal" | "true" | "false" => "bool".to_string(),
_ => "unknown".to_string(),
}
}
}