method-ray 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +27 -1
- data/ext/Cargo.toml +1 -1
- data/ext/src/lib.rs +7 -6
- data/lib/methodray/binary_locator.rb +29 -0
- data/lib/methodray/commands.rb +3 -20
- data/lib/methodray/version.rb +1 -1
- data/lib/methodray.rb +1 -1
- data/rust/Cargo.toml +3 -1
- data/rust/src/analyzer/attributes.rs +57 -0
- data/rust/src/analyzer/blocks.rs +175 -0
- data/rust/src/analyzer/calls.rs +7 -4
- data/rust/src/analyzer/conditionals.rs +466 -0
- data/rust/src/analyzer/definitions.rs +280 -13
- data/rust/src/analyzer/dispatch.rs +754 -11
- data/rust/src/analyzer/install.rs +58 -176
- data/rust/src/analyzer/literals.rs +201 -37
- data/rust/src/analyzer/mod.rs +4 -3
- data/rust/src/analyzer/parameters.rs +218 -0
- data/rust/src/analyzer/variables.rs +16 -8
- data/rust/src/cache/rbs_cache.rs +11 -4
- data/rust/src/checker.rs +20 -8
- data/rust/src/env/global_env.rs +42 -2
- data/rust/src/env/method_registry.rs +86 -4
- data/rust/src/env/mod.rs +1 -0
- data/rust/src/env/scope.rs +291 -25
- data/rust/src/graph/box.rs +478 -4
- data/rust/src/graph/change_set.rs +14 -0
- data/rust/src/graph/mod.rs +1 -1
- data/rust/src/lib.rs +2 -1
- data/rust/src/parser.rs +99 -39
- data/rust/src/rbs/converter.rs +16 -11
- data/rust/src/rbs/loader.rs +35 -5
- data/rust/src/rbs/mod.rs +4 -3
- data/rust/src/types.rs +344 -9
- metadata +6 -3
- data/rust/src/analyzer/tests/integration_test.rs +0 -136
- data/rust/src/analyzer/tests/mod.rs +0 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
//! Parameter Handlers - Processing Ruby method/block parameters
|
|
2
|
+
//!
|
|
3
|
+
//! This module is responsible for:
|
|
4
|
+
//! - Extracting parameter names from DefNode
|
|
5
|
+
//! - Creating vertices for parameters
|
|
6
|
+
//! - Registering parameters as local variables in method scope
|
|
7
|
+
|
|
8
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
9
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
10
|
+
use crate::types::Type;
|
|
11
|
+
|
|
12
|
+
/// Install a required parameter as a local variable
|
|
13
|
+
///
|
|
14
|
+
/// Required parameters start with Bot (untyped) type since we don't know
|
|
15
|
+
/// what type will be passed at call sites.
|
|
16
|
+
///
|
|
17
|
+
/// # Example
|
|
18
|
+
/// ```ruby
|
|
19
|
+
/// def greet(name) # 'name' is a required parameter
|
|
20
|
+
/// name.upcase
|
|
21
|
+
/// end
|
|
22
|
+
/// ```
|
|
23
|
+
pub fn install_required_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
|
|
24
|
+
// Create a vertex for the parameter (starts as Bot/untyped)
|
|
25
|
+
let param_vtx = genv.new_vertex();
|
|
26
|
+
|
|
27
|
+
// Register in LocalEnv for variable lookup
|
|
28
|
+
lenv.new_var(name, param_vtx);
|
|
29
|
+
|
|
30
|
+
param_vtx
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Install an optional parameter with a default value
|
|
34
|
+
///
|
|
35
|
+
/// The parameter's type is inferred from the default value expression.
|
|
36
|
+
///
|
|
37
|
+
/// # Example
|
|
38
|
+
/// ```ruby
|
|
39
|
+
/// def greet(name = "World") # 'name' has type String from default
|
|
40
|
+
/// name.upcase
|
|
41
|
+
/// end
|
|
42
|
+
/// ```
|
|
43
|
+
pub fn install_optional_parameter(
|
|
44
|
+
genv: &mut GlobalEnv,
|
|
45
|
+
lenv: &mut LocalEnv,
|
|
46
|
+
_changes: &mut ChangeSet,
|
|
47
|
+
name: String,
|
|
48
|
+
default_value_vtx: VertexId,
|
|
49
|
+
) -> VertexId {
|
|
50
|
+
// Create a vertex for the parameter
|
|
51
|
+
let param_vtx = genv.new_vertex();
|
|
52
|
+
|
|
53
|
+
// Connect default value to parameter vertex for type inference
|
|
54
|
+
// Use genv.add_edge directly so the type is immediately propagated
|
|
55
|
+
// before the method body is processed
|
|
56
|
+
genv.add_edge(default_value_vtx, param_vtx);
|
|
57
|
+
|
|
58
|
+
// Register in LocalEnv for variable lookup
|
|
59
|
+
lenv.new_var(name, param_vtx);
|
|
60
|
+
|
|
61
|
+
param_vtx
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Install a rest parameter (*args) as a local variable with Array type
|
|
65
|
+
///
|
|
66
|
+
/// Rest parameters collect all remaining arguments into an Array.
|
|
67
|
+
///
|
|
68
|
+
/// # Example
|
|
69
|
+
/// ```ruby
|
|
70
|
+
/// def collect(*items) # 'items' has type Array
|
|
71
|
+
/// items.first
|
|
72
|
+
/// end
|
|
73
|
+
/// ```
|
|
74
|
+
pub fn install_rest_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
|
|
75
|
+
// Create a vertex for the parameter
|
|
76
|
+
let param_vtx = genv.new_vertex();
|
|
77
|
+
|
|
78
|
+
// Rest parameters are always Arrays
|
|
79
|
+
let array_src = genv.new_source(Type::array());
|
|
80
|
+
genv.add_edge(array_src, param_vtx);
|
|
81
|
+
|
|
82
|
+
// Register in LocalEnv for variable lookup
|
|
83
|
+
lenv.new_var(name, param_vtx);
|
|
84
|
+
|
|
85
|
+
param_vtx
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Install a keyword rest parameter (**kwargs) as a local variable with Hash type
|
|
89
|
+
///
|
|
90
|
+
/// Keyword rest parameters collect all remaining keyword arguments into a Hash.
|
|
91
|
+
///
|
|
92
|
+
/// # Example
|
|
93
|
+
/// ```ruby
|
|
94
|
+
/// def configure(**options) # 'options' has type Hash
|
|
95
|
+
/// options[:debug]
|
|
96
|
+
/// end
|
|
97
|
+
/// ```
|
|
98
|
+
pub fn install_keyword_rest_parameter(
|
|
99
|
+
genv: &mut GlobalEnv,
|
|
100
|
+
lenv: &mut LocalEnv,
|
|
101
|
+
name: String,
|
|
102
|
+
) -> VertexId {
|
|
103
|
+
// Create a vertex for the parameter
|
|
104
|
+
let param_vtx = genv.new_vertex();
|
|
105
|
+
|
|
106
|
+
// Keyword rest parameters are always Hashes
|
|
107
|
+
let hash_src = genv.new_source(Type::hash());
|
|
108
|
+
genv.add_edge(hash_src, param_vtx);
|
|
109
|
+
|
|
110
|
+
// Register in LocalEnv for variable lookup
|
|
111
|
+
lenv.new_var(name, param_vtx);
|
|
112
|
+
|
|
113
|
+
param_vtx
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Install method parameters as local variables
|
|
117
|
+
///
|
|
118
|
+
/// Returns a Vec of VertexId for required and optional parameters (positional),
|
|
119
|
+
/// which can be used for argument-to-parameter type propagation.
|
|
120
|
+
pub(crate) fn install_parameters(
|
|
121
|
+
genv: &mut GlobalEnv,
|
|
122
|
+
lenv: &mut LocalEnv,
|
|
123
|
+
changes: &mut ChangeSet,
|
|
124
|
+
source: &str,
|
|
125
|
+
params_node: &ruby_prism::ParametersNode,
|
|
126
|
+
) -> Vec<VertexId> {
|
|
127
|
+
let mut param_vtxs = Vec::new();
|
|
128
|
+
|
|
129
|
+
// Required parameters: def foo(a, b)
|
|
130
|
+
for node in params_node.requireds().iter() {
|
|
131
|
+
if let Some(req_param) = node.as_required_parameter_node() {
|
|
132
|
+
let name = String::from_utf8_lossy(req_param.name().as_slice()).to_string();
|
|
133
|
+
let vtx = install_required_parameter(genv, lenv, name);
|
|
134
|
+
param_vtxs.push(vtx);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Optional parameters: def foo(a = 1, b = "hello")
|
|
139
|
+
for node in params_node.optionals().iter() {
|
|
140
|
+
if let Some(opt_param) = node.as_optional_parameter_node() {
|
|
141
|
+
let name = String::from_utf8_lossy(opt_param.name().as_slice()).to_string();
|
|
142
|
+
let default_value = opt_param.value();
|
|
143
|
+
|
|
144
|
+
let vtx = if let Some(default_vtx) =
|
|
145
|
+
super::install::install_node(genv, lenv, changes, source, &default_value)
|
|
146
|
+
{
|
|
147
|
+
install_optional_parameter(genv, lenv, changes, name, default_vtx)
|
|
148
|
+
} else {
|
|
149
|
+
install_required_parameter(genv, lenv, name)
|
|
150
|
+
};
|
|
151
|
+
param_vtxs.push(vtx);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Rest parameter: def foo(*args)
|
|
156
|
+
// Not included in param_vtxs (variadic args need special handling)
|
|
157
|
+
if let Some(rest_node) = params_node.rest() {
|
|
158
|
+
if let Some(rest_param) = rest_node.as_rest_parameter_node() {
|
|
159
|
+
if let Some(name_id) = rest_param.name() {
|
|
160
|
+
let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
|
|
161
|
+
install_rest_parameter(genv, lenv, name);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Keyword rest parameter: def foo(**kwargs)
|
|
167
|
+
// Not included in param_vtxs (keyword args need special handling)
|
|
168
|
+
if let Some(kwrest_node) = params_node.keyword_rest() {
|
|
169
|
+
if let Some(kwrest_param) = kwrest_node.as_keyword_rest_parameter_node() {
|
|
170
|
+
if let Some(name_id) = kwrest_param.name() {
|
|
171
|
+
let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
|
|
172
|
+
install_keyword_rest_parameter(genv, lenv, name);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
param_vtxs
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[cfg(test)]
|
|
181
|
+
mod tests {
|
|
182
|
+
use super::*;
|
|
183
|
+
|
|
184
|
+
#[test]
|
|
185
|
+
fn test_install_required_parameter() {
|
|
186
|
+
let mut genv = GlobalEnv::new();
|
|
187
|
+
let mut lenv = LocalEnv::new();
|
|
188
|
+
|
|
189
|
+
let vtx = install_required_parameter(&mut genv, &mut lenv, "name".to_string());
|
|
190
|
+
|
|
191
|
+
// Parameter should be registered in LocalEnv
|
|
192
|
+
assert_eq!(lenv.get_var("name"), Some(vtx));
|
|
193
|
+
|
|
194
|
+
// Vertex should exist in GlobalEnv (as untyped)
|
|
195
|
+
let vertex = genv.get_vertex(vtx);
|
|
196
|
+
assert!(vertex.is_some());
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[test]
|
|
200
|
+
fn test_install_multiple_parameters() {
|
|
201
|
+
let mut genv = GlobalEnv::new();
|
|
202
|
+
let mut lenv = LocalEnv::new();
|
|
203
|
+
|
|
204
|
+
let vtx_a = install_required_parameter(&mut genv, &mut lenv, "a".to_string());
|
|
205
|
+
let vtx_b = install_required_parameter(&mut genv, &mut lenv, "b".to_string());
|
|
206
|
+
let vtx_c = install_required_parameter(&mut genv, &mut lenv, "c".to_string());
|
|
207
|
+
|
|
208
|
+
// All parameters should be registered
|
|
209
|
+
assert_eq!(lenv.get_var("a"), Some(vtx_a));
|
|
210
|
+
assert_eq!(lenv.get_var("b"), Some(vtx_b));
|
|
211
|
+
assert_eq!(lenv.get_var("c"), Some(vtx_c));
|
|
212
|
+
|
|
213
|
+
// All vertices should be different
|
|
214
|
+
assert_ne!(vtx_a, vtx_b);
|
|
215
|
+
assert_ne!(vtx_b, vtx_c);
|
|
216
|
+
assert_ne!(vtx_a, vtx_c);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -29,14 +29,23 @@ pub fn install_local_var_read(lenv: &LocalEnv, var_name: &str) -> Option<VertexI
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/// Install instance variable write: @name = value
|
|
32
|
+
///
|
|
33
|
+
/// If @name already has a pre-allocated VertexId (e.g., from attr_reader),
|
|
34
|
+
/// an edge is added from value_vtx to the existing vertex so types propagate.
|
|
35
|
+
/// Otherwise, value_vtx is registered directly as the ivar's VertexId.
|
|
32
36
|
pub fn install_ivar_write(
|
|
33
37
|
genv: &mut GlobalEnv,
|
|
34
38
|
ivar_name: String,
|
|
35
39
|
value_vtx: VertexId,
|
|
36
40
|
) -> VertexId {
|
|
37
|
-
genv.scope_manager
|
|
38
|
-
.
|
|
39
|
-
|
|
41
|
+
if let Some(existing_vtx) = genv.scope_manager.lookup_instance_var(&ivar_name) {
|
|
42
|
+
genv.add_edge(value_vtx, existing_vtx);
|
|
43
|
+
existing_vtx
|
|
44
|
+
} else {
|
|
45
|
+
genv.scope_manager
|
|
46
|
+
.set_instance_var_in_class(ivar_name, value_vtx);
|
|
47
|
+
value_vtx
|
|
48
|
+
}
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
/// Install instance variable read: @name
|
|
@@ -45,13 +54,12 @@ pub fn install_ivar_read(genv: &GlobalEnv, ivar_name: &str) -> Option<VertexId>
|
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
/// Install self node
|
|
57
|
+
/// Uses the fully qualified name if available (e.g., Api::V1::User instead of just User)
|
|
48
58
|
pub fn install_self(genv: &mut GlobalEnv) -> VertexId {
|
|
49
|
-
if let Some(
|
|
50
|
-
genv.new_source(Type::
|
|
59
|
+
if let Some(qualified_name) = genv.scope_manager.current_qualified_name() {
|
|
60
|
+
genv.new_source(Type::instance(&qualified_name))
|
|
51
61
|
} else {
|
|
52
|
-
genv.new_source(Type::
|
|
53
|
-
class_name: "Object".to_string(),
|
|
54
|
-
})
|
|
62
|
+
genv.new_source(Type::instance("Object"))
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
|
data/rust/src/cache/rbs_cache.rs
CHANGED
|
@@ -26,14 +26,14 @@ pub struct SerializableMethodInfo {
|
|
|
26
26
|
pub receiver_class: String,
|
|
27
27
|
pub method_name: String,
|
|
28
28
|
pub return_type_str: String, // Simplified: store as string
|
|
29
|
+
#[serde(default)]
|
|
30
|
+
pub block_param_types: Option<Vec<String>>,
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
impl SerializableMethodInfo {
|
|
32
34
|
/// Parse return type string into Type (simple parser for cached data)
|
|
33
35
|
pub fn return_type(&self) -> crate::types::Type {
|
|
34
|
-
crate::types::Type::
|
|
35
|
-
class_name: self.return_type_str.clone(),
|
|
36
|
-
}
|
|
36
|
+
crate::types::Type::instance(&self.return_type_str)
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -79,7 +79,6 @@ impl RbsCache {
|
|
|
79
79
|
if let Some(bundled_path) = Self::bundled_cache_path() {
|
|
80
80
|
if let Ok(bytes) = fs::read(&bundled_path) {
|
|
81
81
|
if let Ok(cache) = bincode::deserialize::<Self>(&bytes) {
|
|
82
|
-
eprintln!("Loaded bundled cache from {}", bundled_path.display());
|
|
83
82
|
return Ok(cache);
|
|
84
83
|
}
|
|
85
84
|
}
|
|
@@ -123,6 +122,7 @@ impl RbsCache {
|
|
|
123
122
|
receiver_class: m.receiver_class.clone(),
|
|
124
123
|
method_name: m.method_name.clone(),
|
|
125
124
|
return_type: crate::rbs::converter::RbsTypeConverter::parse(&m.return_type_str),
|
|
125
|
+
block_param_types: m.block_param_types.clone(),
|
|
126
126
|
})
|
|
127
127
|
.collect()
|
|
128
128
|
}
|
|
@@ -140,6 +140,7 @@ impl RbsCache {
|
|
|
140
140
|
receiver_class: m.receiver_class,
|
|
141
141
|
method_name: m.method_name,
|
|
142
142
|
return_type_str: m.return_type.show(),
|
|
143
|
+
block_param_types: m.block_param_types,
|
|
143
144
|
})
|
|
144
145
|
.collect();
|
|
145
146
|
|
|
@@ -166,6 +167,7 @@ mod tests {
|
|
|
166
167
|
receiver_class: "String".to_string(),
|
|
167
168
|
method_name: "upcase".to_string(),
|
|
168
169
|
return_type_str: "String".to_string(),
|
|
170
|
+
block_param_types: None,
|
|
169
171
|
}],
|
|
170
172
|
timestamp: SystemTime::now(),
|
|
171
173
|
};
|
|
@@ -197,6 +199,7 @@ mod tests {
|
|
|
197
199
|
receiver_class: "String".to_string(),
|
|
198
200
|
method_name: "upcase".to_string(),
|
|
199
201
|
return_type_str: "String".to_string(),
|
|
202
|
+
block_param_types: None,
|
|
200
203
|
};
|
|
201
204
|
|
|
202
205
|
let return_type = method_info.return_type();
|
|
@@ -213,11 +216,13 @@ mod tests {
|
|
|
213
216
|
receiver_class: "String".to_string(),
|
|
214
217
|
method_name: "upcase".to_string(),
|
|
215
218
|
return_type_str: "String".to_string(),
|
|
219
|
+
block_param_types: None,
|
|
216
220
|
},
|
|
217
221
|
SerializableMethodInfo {
|
|
218
222
|
receiver_class: "Integer".to_string(),
|
|
219
223
|
method_name: "to_s".to_string(),
|
|
220
224
|
return_type_str: "String".to_string(),
|
|
225
|
+
block_param_types: None,
|
|
221
226
|
},
|
|
222
227
|
],
|
|
223
228
|
timestamp: SystemTime::now(),
|
|
@@ -244,11 +249,13 @@ mod tests {
|
|
|
244
249
|
receiver_class: "String".to_string(),
|
|
245
250
|
method_name: "upcase".to_string(),
|
|
246
251
|
return_type_str: "String".to_string(),
|
|
252
|
+
block_param_types: None,
|
|
247
253
|
},
|
|
248
254
|
SerializableMethodInfo {
|
|
249
255
|
receiver_class: "Array".to_string(),
|
|
250
256
|
method_name: "first".to_string(),
|
|
251
257
|
return_type_str: "Object".to_string(),
|
|
258
|
+
block_param_types: None,
|
|
252
259
|
},
|
|
253
260
|
],
|
|
254
261
|
timestamp: SystemTime::now(),
|
data/rust/src/checker.rs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use crate::analyzer::AstInstaller;
|
|
2
2
|
use crate::diagnostics::Diagnostic;
|
|
3
3
|
use crate::env::{GlobalEnv, LocalEnv};
|
|
4
|
-
use crate::parser;
|
|
4
|
+
use crate::parser::ParseSession;
|
|
5
5
|
use anyhow::{Context, Result};
|
|
6
6
|
use std::path::Path;
|
|
7
7
|
|
|
@@ -30,8 +30,12 @@ impl FileChecker {
|
|
|
30
30
|
let source = std::fs::read_to_string(file_path)
|
|
31
31
|
.with_context(|| format!("Failed to read {}", file_path.display()))?;
|
|
32
32
|
|
|
33
|
+
// Create ParseSession (arena allocator) for this check
|
|
34
|
+
let session = ParseSession::new();
|
|
35
|
+
|
|
33
36
|
// Parse file
|
|
34
|
-
let parse_result =
|
|
37
|
+
let parse_result = session
|
|
38
|
+
.parse_source(&source, &file_path.to_string_lossy())
|
|
35
39
|
.with_context(|| format!("Failed to parse {}", file_path.display()))?;
|
|
36
40
|
|
|
37
41
|
// Create fresh GlobalEnv for this analysis
|
|
@@ -56,12 +60,13 @@ impl FileChecker {
|
|
|
56
60
|
let diagnostics = collect_diagnostics(&genv, file_path);
|
|
57
61
|
|
|
58
62
|
Ok(diagnostics)
|
|
59
|
-
}
|
|
63
|
+
} // session drops here, freeing arena memory
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
/// Load RBS methods from cache (CLI mode without Ruby runtime)
|
|
63
67
|
fn load_rbs_from_cache(genv: &mut GlobalEnv) -> Result<()> {
|
|
64
68
|
use crate::cache::RbsCache;
|
|
69
|
+
use crate::rbs::converter::RbsTypeConverter;
|
|
65
70
|
use crate::types::Type;
|
|
66
71
|
|
|
67
72
|
let cache = RbsCache::load().context(
|
|
@@ -70,16 +75,23 @@ fn load_rbs_from_cache(genv: &mut GlobalEnv) -> Result<()> {
|
|
|
70
75
|
)?;
|
|
71
76
|
|
|
72
77
|
let methods = cache.methods();
|
|
73
|
-
eprintln!("Loaded {} methods from cache", methods.len());
|
|
74
78
|
|
|
75
79
|
for method_info in methods {
|
|
76
|
-
let receiver_type = Type::
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
let receiver_type = Type::instance(&method_info.receiver_class);
|
|
81
|
+
|
|
82
|
+
// Convert block param type strings to Type enums
|
|
83
|
+
let block_param_types = method_info.block_param_types.as_ref().map(|types| {
|
|
84
|
+
types
|
|
85
|
+
.iter()
|
|
86
|
+
.map(|s| RbsTypeConverter::parse(s))
|
|
87
|
+
.collect()
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
genv.register_builtin_method_with_block(
|
|
80
91
|
receiver_type,
|
|
81
92
|
&method_info.method_name,
|
|
82
93
|
method_info.return_type(),
|
|
94
|
+
block_param_types,
|
|
83
95
|
);
|
|
84
96
|
}
|
|
85
97
|
|
data/rust/src/env/global_env.rs
CHANGED
|
@@ -115,6 +115,11 @@ impl GlobalEnv {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
// Reschedule boxes that need to run again
|
|
120
|
+
for box_id in changes.take_reschedule_boxes() {
|
|
121
|
+
self.box_manager.add_run(box_id);
|
|
122
|
+
}
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
/// Execute all Boxes
|
|
@@ -145,6 +150,30 @@ impl GlobalEnv {
|
|
|
145
150
|
self.method_registry.register(recv_ty, method_name, ret_ty);
|
|
146
151
|
}
|
|
147
152
|
|
|
153
|
+
/// Register built-in method with block parameter types
|
|
154
|
+
pub fn register_builtin_method_with_block(
|
|
155
|
+
&mut self,
|
|
156
|
+
recv_ty: Type,
|
|
157
|
+
method_name: &str,
|
|
158
|
+
ret_ty: Type,
|
|
159
|
+
block_param_types: Option<Vec<Type>>,
|
|
160
|
+
) {
|
|
161
|
+
self.method_registry
|
|
162
|
+
.register_with_block(recv_ty, method_name, ret_ty, block_param_types);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Register a user-defined method (return type resolved via graph)
|
|
166
|
+
pub fn register_user_method(
|
|
167
|
+
&mut self,
|
|
168
|
+
recv_ty: Type,
|
|
169
|
+
method_name: &str,
|
|
170
|
+
return_vertex: VertexId,
|
|
171
|
+
param_vertices: Vec<VertexId>,
|
|
172
|
+
) {
|
|
173
|
+
self.method_registry
|
|
174
|
+
.register_user_method(recv_ty, method_name, return_vertex, param_vertices);
|
|
175
|
+
}
|
|
176
|
+
|
|
148
177
|
// ===== Type Errors =====
|
|
149
178
|
|
|
150
179
|
/// Record a type error (undefined method)
|
|
@@ -170,12 +199,23 @@ impl GlobalEnv {
|
|
|
170
199
|
scope_id
|
|
171
200
|
}
|
|
172
201
|
|
|
202
|
+
/// Enter a module scope
|
|
203
|
+
pub fn enter_module(&mut self, name: String) -> ScopeId {
|
|
204
|
+
let scope_id = self.scope_manager.new_scope(ScopeKind::Module { name });
|
|
205
|
+
self.scope_manager.enter_scope(scope_id);
|
|
206
|
+
scope_id
|
|
207
|
+
}
|
|
208
|
+
|
|
173
209
|
/// Enter a method scope
|
|
174
210
|
pub fn enter_method(&mut self, name: String) -> ScopeId {
|
|
175
|
-
|
|
211
|
+
// Look for class or module context
|
|
212
|
+
let receiver_type = self
|
|
213
|
+
.scope_manager
|
|
214
|
+
.current_class_name()
|
|
215
|
+
.or_else(|| self.scope_manager.current_module_name());
|
|
176
216
|
let scope_id = self.scope_manager.new_scope(ScopeKind::Method {
|
|
177
217
|
name,
|
|
178
|
-
receiver_type
|
|
218
|
+
receiver_type,
|
|
179
219
|
});
|
|
180
220
|
self.scope_manager.enter_scope(scope_id);
|
|
181
221
|
scope_id
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//! Method registration and resolution
|
|
2
2
|
|
|
3
|
+
use crate::graph::VertexId;
|
|
3
4
|
use crate::types::Type;
|
|
4
5
|
use std::collections::HashMap;
|
|
5
6
|
|
|
@@ -7,6 +8,9 @@ use std::collections::HashMap;
|
|
|
7
8
|
#[derive(Debug, Clone)]
|
|
8
9
|
pub struct MethodInfo {
|
|
9
10
|
pub return_type: Type,
|
|
11
|
+
pub block_param_types: Option<Vec<Type>>,
|
|
12
|
+
pub return_vertex: Option<VertexId>,
|
|
13
|
+
pub param_vertices: Option<Vec<VertexId>>,
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
/// Registry for method definitions
|
|
@@ -25,18 +29,67 @@ impl MethodRegistry {
|
|
|
25
29
|
|
|
26
30
|
/// Register a method for a receiver type
|
|
27
31
|
pub fn register(&mut self, recv_ty: Type, method_name: &str, ret_ty: Type) {
|
|
32
|
+
self.register_with_block(recv_ty, method_name, ret_ty, None);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Register a method with block parameter types
|
|
36
|
+
pub fn register_with_block(
|
|
37
|
+
&mut self,
|
|
38
|
+
recv_ty: Type,
|
|
39
|
+
method_name: &str,
|
|
40
|
+
ret_ty: Type,
|
|
41
|
+
block_param_types: Option<Vec<Type>>,
|
|
42
|
+
) {
|
|
28
43
|
self.methods.insert(
|
|
29
44
|
(recv_ty, method_name.to_string()),
|
|
30
45
|
MethodInfo {
|
|
31
46
|
return_type: ret_ty,
|
|
47
|
+
block_param_types,
|
|
48
|
+
return_vertex: None,
|
|
49
|
+
param_vertices: None,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Register a user-defined method (return type resolved via graph)
|
|
55
|
+
pub fn register_user_method(
|
|
56
|
+
&mut self,
|
|
57
|
+
recv_ty: Type,
|
|
58
|
+
method_name: &str,
|
|
59
|
+
return_vertex: VertexId,
|
|
60
|
+
param_vertices: Vec<VertexId>,
|
|
61
|
+
) {
|
|
62
|
+
self.methods.insert(
|
|
63
|
+
(recv_ty, method_name.to_string()),
|
|
64
|
+
MethodInfo {
|
|
65
|
+
return_type: Type::Bot,
|
|
66
|
+
block_param_types: None,
|
|
67
|
+
return_vertex: Some(return_vertex),
|
|
68
|
+
param_vertices: Some(param_vertices),
|
|
32
69
|
},
|
|
33
70
|
);
|
|
34
71
|
}
|
|
35
72
|
|
|
36
73
|
/// Resolve a method for a receiver type
|
|
74
|
+
///
|
|
75
|
+
/// For generic types like `Array[Integer]`, first tries exact match,
|
|
76
|
+
/// then falls back to base class match (`Array`).
|
|
37
77
|
pub fn resolve(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
|
|
38
|
-
|
|
78
|
+
// First, try exact match
|
|
79
|
+
if let Some(info) = self
|
|
80
|
+
.methods
|
|
39
81
|
.get(&(recv_ty.clone(), method_name.to_string()))
|
|
82
|
+
{
|
|
83
|
+
return Some(info);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// For generic types, fall back to base class
|
|
87
|
+
if let Type::Generic { name, .. } = recv_ty {
|
|
88
|
+
let base_type = Type::Instance { name: name.clone() };
|
|
89
|
+
return self.methods.get(&(base_type, method_name.to_string()));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
None
|
|
40
93
|
}
|
|
41
94
|
}
|
|
42
95
|
|
|
@@ -50,9 +103,7 @@ mod tests {
|
|
|
50
103
|
registry.register(Type::string(), "length", Type::integer());
|
|
51
104
|
|
|
52
105
|
let info = registry.resolve(&Type::string(), "length").unwrap();
|
|
53
|
-
|
|
54
|
-
matches!(info.return_type, Type::Instance { ref class_name, .. } if class_name == "Integer")
|
|
55
|
-
);
|
|
106
|
+
assert_eq!(info.return_type.base_class_name(), Some("Integer"));
|
|
56
107
|
}
|
|
57
108
|
|
|
58
109
|
#[test]
|
|
@@ -60,4 +111,35 @@ mod tests {
|
|
|
60
111
|
let registry = MethodRegistry::new();
|
|
61
112
|
assert!(registry.resolve(&Type::string(), "unknown").is_none());
|
|
62
113
|
}
|
|
114
|
+
|
|
115
|
+
#[test]
|
|
116
|
+
fn test_register_user_method_and_resolve() {
|
|
117
|
+
let mut registry = MethodRegistry::new();
|
|
118
|
+
let return_vtx = VertexId(42);
|
|
119
|
+
registry.register_user_method(Type::instance("User"), "name", return_vtx, vec![]);
|
|
120
|
+
|
|
121
|
+
let info = registry.resolve(&Type::instance("User"), "name").unwrap();
|
|
122
|
+
assert_eq!(info.return_vertex, Some(VertexId(42)));
|
|
123
|
+
assert_eq!(info.return_type, Type::Bot);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#[test]
|
|
127
|
+
fn test_register_user_method_with_param_vertices() {
|
|
128
|
+
let mut registry = MethodRegistry::new();
|
|
129
|
+
let return_vtx = VertexId(10);
|
|
130
|
+
let param_vtxs = vec![VertexId(20), VertexId(21)];
|
|
131
|
+
registry.register_user_method(
|
|
132
|
+
Type::instance("Calc"),
|
|
133
|
+
"add",
|
|
134
|
+
return_vtx,
|
|
135
|
+
param_vtxs,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
let info = registry.resolve(&Type::instance("Calc"), "add").unwrap();
|
|
139
|
+
assert_eq!(info.return_vertex, Some(VertexId(10)));
|
|
140
|
+
let pvs = info.param_vertices.as_ref().unwrap();
|
|
141
|
+
assert_eq!(pvs.len(), 2);
|
|
142
|
+
assert_eq!(pvs[0], VertexId(20));
|
|
143
|
+
assert_eq!(pvs[1], VertexId(21));
|
|
144
|
+
}
|
|
63
145
|
}
|
data/rust/src/env/mod.rs
CHANGED