method-ray 0.1.10 → 0.2.0
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 +22 -0
- data/core/Cargo.toml +1 -1
- data/core/src/analyzer/blocks.rs +1 -1
- data/core/src/analyzer/compound_assignments.rs +194 -0
- data/core/src/analyzer/conditionals.rs +268 -2
- data/core/src/analyzer/dispatch.rs +234 -22
- data/core/src/analyzer/install.rs +36 -1
- data/core/src/analyzer/lambdas.rs +153 -0
- data/core/src/analyzer/literals.rs +11 -0
- data/core/src/analyzer/mod.rs +3 -0
- data/core/src/analyzer/variables.rs +224 -0
- data/core/src/analyzer/yields.rs +24 -0
- data/core/src/env/global_env.rs +50 -0
- data/core/src/env/scope.rs +91 -7
- data/core/src/graph/box.rs +11 -0
- data/core/src/types.rs +34 -0
- data/ext/Cargo.toml +1 -1
- data/lib/methodray/version.rb +1 -1
- metadata +4 -1
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
//! This module is responsible for:
|
|
4
4
|
//! - Local variable read/write (x, x = value)
|
|
5
5
|
//! - Instance variable read/write (@name, @name = value)
|
|
6
|
+
//! - Class variable read/write (@@name, @@name = value)
|
|
7
|
+
//! - Global variable read/write ($var, $var = value)
|
|
8
|
+
//! - Constant read/write (CONST = value, CONST)
|
|
6
9
|
//! - self node handling
|
|
7
10
|
|
|
8
11
|
use crate::env::{GlobalEnv, LocalEnv};
|
|
@@ -62,3 +65,224 @@ pub(crate) fn install_self(genv: &mut GlobalEnv) -> VertexId {
|
|
|
62
65
|
genv.new_source(Type::instance("Object"))
|
|
63
66
|
}
|
|
64
67
|
}
|
|
68
|
+
|
|
69
|
+
/// Install class variable write: @@name = value
|
|
70
|
+
///
|
|
71
|
+
/// If @@name already has a VertexId (e.g., from a previous assignment),
|
|
72
|
+
/// an edge is added from value_vtx to the existing vertex so types propagate.
|
|
73
|
+
/// Otherwise, value_vtx is registered directly as the cvar's VertexId.
|
|
74
|
+
pub(crate) fn install_class_var_write(
|
|
75
|
+
genv: &mut GlobalEnv,
|
|
76
|
+
cvar_name: String,
|
|
77
|
+
value_vtx: VertexId,
|
|
78
|
+
) -> VertexId {
|
|
79
|
+
if let Some(existing_vtx) = genv.scope_manager.lookup_class_var(&cvar_name) {
|
|
80
|
+
genv.add_edge(value_vtx, existing_vtx);
|
|
81
|
+
existing_vtx
|
|
82
|
+
} else {
|
|
83
|
+
genv.scope_manager.set_class_var_in_class(cvar_name, value_vtx);
|
|
84
|
+
value_vtx
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Install class variable read: @@name
|
|
89
|
+
pub(crate) fn install_class_var_read(genv: &GlobalEnv, cvar_name: &str) -> Option<VertexId> {
|
|
90
|
+
genv.scope_manager.lookup_class_var(cvar_name)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Install global variable write: $var = value
|
|
94
|
+
///
|
|
95
|
+
/// Delegates to [`GlobalEnv::set_global_var`] for edge behavior details.
|
|
96
|
+
pub(crate) fn install_global_var_write(
|
|
97
|
+
genv: &mut GlobalEnv,
|
|
98
|
+
gvar_name: String,
|
|
99
|
+
value_vtx: VertexId,
|
|
100
|
+
) -> VertexId {
|
|
101
|
+
genv.set_global_var(gvar_name, value_vtx)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Install global variable read: $var
|
|
105
|
+
pub(crate) fn install_global_var_read(genv: &GlobalEnv, gvar_name: &str) -> Option<VertexId> {
|
|
106
|
+
genv.get_global_var(gvar_name)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub(crate) fn install_constant_write(
|
|
110
|
+
genv: &mut GlobalEnv,
|
|
111
|
+
const_name: String,
|
|
112
|
+
value_vtx: VertexId,
|
|
113
|
+
) -> VertexId {
|
|
114
|
+
let key = match genv.scope_manager.current_qualified_name() {
|
|
115
|
+
Some(ns) => format!("{}::{}", ns, const_name),
|
|
116
|
+
None => const_name,
|
|
117
|
+
};
|
|
118
|
+
genv.set_constant(key, value_vtx)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pub(crate) fn install_constant_read(
|
|
122
|
+
genv: &GlobalEnv,
|
|
123
|
+
const_name: &str,
|
|
124
|
+
) -> Option<VertexId> {
|
|
125
|
+
if let Some(ns) = genv.scope_manager.current_qualified_name() {
|
|
126
|
+
let mut current_ns = ns.as_str();
|
|
127
|
+
loop {
|
|
128
|
+
let key = format!("{}::{}", current_ns, const_name);
|
|
129
|
+
if let Some(vtx) = genv.get_constant(&key) {
|
|
130
|
+
return Some(vtx);
|
|
131
|
+
}
|
|
132
|
+
match current_ns.rfind("::") {
|
|
133
|
+
Some(pos) => current_ns = ¤t_ns[..pos],
|
|
134
|
+
None => break,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
genv.get_constant(const_name)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[cfg(test)]
|
|
142
|
+
mod tests {
|
|
143
|
+
use super::*;
|
|
144
|
+
use crate::types::Type;
|
|
145
|
+
|
|
146
|
+
fn setup_class_scope(genv: &mut GlobalEnv) {
|
|
147
|
+
genv.enter_class("TestClass".to_string(), None);
|
|
148
|
+
genv.enter_method("test_method".to_string());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[test]
|
|
152
|
+
fn test_install_class_var_write_new() {
|
|
153
|
+
let mut genv = GlobalEnv::new();
|
|
154
|
+
setup_class_scope(&mut genv);
|
|
155
|
+
|
|
156
|
+
let value_vtx = genv.new_source(Type::integer());
|
|
157
|
+
let result_vtx = install_class_var_write(&mut genv, "@@count".to_string(), value_vtx);
|
|
158
|
+
|
|
159
|
+
assert_eq!(result_vtx, value_vtx);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#[test]
|
|
163
|
+
fn test_install_class_var_read_after_write() {
|
|
164
|
+
let mut genv = GlobalEnv::new();
|
|
165
|
+
setup_class_scope(&mut genv);
|
|
166
|
+
|
|
167
|
+
let value_vtx = genv.new_source(Type::integer());
|
|
168
|
+
install_class_var_write(&mut genv, "@@count".to_string(), value_vtx);
|
|
169
|
+
|
|
170
|
+
let read_vtx = install_class_var_read(&genv, "@@count");
|
|
171
|
+
assert_eq!(read_vtx, Some(value_vtx));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[test]
|
|
175
|
+
fn test_install_class_var_read_undefined() {
|
|
176
|
+
let genv = GlobalEnv::new();
|
|
177
|
+
assert_eq!(install_class_var_read(&genv, "@@undefined"), None);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[test]
|
|
181
|
+
fn test_install_class_var_write_twice_merges() {
|
|
182
|
+
let mut genv = GlobalEnv::new();
|
|
183
|
+
setup_class_scope(&mut genv);
|
|
184
|
+
|
|
185
|
+
let str_vtx = genv.new_source(Type::string());
|
|
186
|
+
let vtx1 = install_class_var_write(&mut genv, "@@var".to_string(), str_vtx);
|
|
187
|
+
|
|
188
|
+
let int_vtx = genv.new_source(Type::integer());
|
|
189
|
+
let vtx2 = install_class_var_write(&mut genv, "@@var".to_string(), int_vtx);
|
|
190
|
+
|
|
191
|
+
assert_eq!(vtx1, vtx2);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#[test]
|
|
195
|
+
fn test_global_var_write_and_read() {
|
|
196
|
+
let mut genv = GlobalEnv::new();
|
|
197
|
+
let value_vtx = genv.new_source(Type::instance("String"));
|
|
198
|
+
let result_vtx = install_global_var_write(&mut genv, "$config".to_string(), value_vtx);
|
|
199
|
+
assert_eq!(result_vtx, value_vtx);
|
|
200
|
+
|
|
201
|
+
let read_vtx = install_global_var_read(&genv, "$config");
|
|
202
|
+
assert_eq!(read_vtx, Some(value_vtx));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
#[test]
|
|
206
|
+
fn test_global_var_read_unregistered() {
|
|
207
|
+
let genv = GlobalEnv::new();
|
|
208
|
+
let read_vtx = install_global_var_read(&genv, "$unknown");
|
|
209
|
+
assert_eq!(read_vtx, None);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[test]
|
|
213
|
+
fn test_global_var_write_twice_returns_same_vertex() {
|
|
214
|
+
let mut genv = GlobalEnv::new();
|
|
215
|
+
let vtx1 = genv.new_source(Type::instance("String"));
|
|
216
|
+
let first = install_global_var_write(&mut genv, "$data".to_string(), vtx1);
|
|
217
|
+
|
|
218
|
+
let vtx2 = genv.new_source(Type::instance("Integer"));
|
|
219
|
+
let second = install_global_var_write(&mut genv, "$data".to_string(), vtx2);
|
|
220
|
+
|
|
221
|
+
assert_eq!(first, second);
|
|
222
|
+
assert_eq!(install_global_var_read(&genv, "$data"), Some(first));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#[test]
|
|
226
|
+
fn test_global_var_write_twice_propagates_via_vertex() {
|
|
227
|
+
let mut genv = GlobalEnv::new();
|
|
228
|
+
let var_vtx = genv.new_vertex();
|
|
229
|
+
install_global_var_write(&mut genv, "$data".to_string(), var_vtx);
|
|
230
|
+
|
|
231
|
+
let str_src = genv.new_source(Type::instance("String"));
|
|
232
|
+
install_global_var_write(&mut genv, "$data".to_string(), str_src);
|
|
233
|
+
|
|
234
|
+
let types = genv.get_receiver_types(var_vtx).unwrap();
|
|
235
|
+
assert!(types.contains(&Type::instance("String")));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#[test]
|
|
239
|
+
fn test_constant_write_twice_merges() {
|
|
240
|
+
let mut genv = GlobalEnv::new();
|
|
241
|
+
let str_vtx = genv.new_source(Type::string());
|
|
242
|
+
let vtx1 = install_constant_write(&mut genv, "VAL".to_string(), str_vtx);
|
|
243
|
+
|
|
244
|
+
let int_vtx = genv.new_source(Type::integer());
|
|
245
|
+
let vtx2 = install_constant_write(&mut genv, "VAL".to_string(), int_vtx);
|
|
246
|
+
|
|
247
|
+
assert_eq!(vtx1, vtx2);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[test]
|
|
251
|
+
fn test_constant_read_undefined() {
|
|
252
|
+
let genv = GlobalEnv::new();
|
|
253
|
+
assert_eq!(install_constant_read(&genv, "UNDEFINED"), None);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#[test]
|
|
257
|
+
fn test_constant_read_nested_namespace_walk() {
|
|
258
|
+
let mut genv = GlobalEnv::new();
|
|
259
|
+
|
|
260
|
+
genv.enter_class("Api".to_string(), None);
|
|
261
|
+
let api_vtx = genv.new_source(Type::string());
|
|
262
|
+
install_constant_write(&mut genv, "VERSION".to_string(), api_vtx);
|
|
263
|
+
|
|
264
|
+
genv.enter_class("V1".to_string(), None);
|
|
265
|
+
genv.enter_class("Users".to_string(), None);
|
|
266
|
+
genv.enter_method("index".to_string());
|
|
267
|
+
|
|
268
|
+
let read = install_constant_read(&genv, "VERSION");
|
|
269
|
+
assert_eq!(read, Some(api_vtx));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#[test]
|
|
273
|
+
fn test_constant_read_prefers_inner_namespace() {
|
|
274
|
+
let mut genv = GlobalEnv::new();
|
|
275
|
+
|
|
276
|
+
let top_vtx = genv.new_source(Type::string());
|
|
277
|
+
install_constant_write(&mut genv, "NAME".to_string(), top_vtx);
|
|
278
|
+
|
|
279
|
+
genv.enter_class("Config".to_string(), None);
|
|
280
|
+
let class_vtx = genv.new_source(Type::integer());
|
|
281
|
+
install_constant_write(&mut genv, "NAME".to_string(), class_vtx);
|
|
282
|
+
|
|
283
|
+
genv.enter_method("get_name".to_string());
|
|
284
|
+
|
|
285
|
+
let read = install_constant_read(&genv, "NAME");
|
|
286
|
+
assert_eq!(read, Some(class_vtx));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//! Yield statement handling
|
|
2
|
+
|
|
3
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
4
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
5
|
+
|
|
6
|
+
use super::install::install_node;
|
|
7
|
+
|
|
8
|
+
/// Process YieldNode: evaluate arguments for type checking, return unresolved vertex
|
|
9
|
+
pub(crate) fn process_yield_node(
|
|
10
|
+
genv: &mut GlobalEnv,
|
|
11
|
+
lenv: &mut LocalEnv,
|
|
12
|
+
changes: &mut ChangeSet,
|
|
13
|
+
source: &str,
|
|
14
|
+
yield_node: &ruby_prism::YieldNode,
|
|
15
|
+
) -> Option<VertexId> {
|
|
16
|
+
// TODO: Connect argument vertices to block parameter types
|
|
17
|
+
if let Some(args) = yield_node.arguments() {
|
|
18
|
+
for arg in args.arguments().iter() {
|
|
19
|
+
install_node(genv, lenv, changes, source, &arg);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Some(genv.new_vertex())
|
|
24
|
+
}
|
data/core/src/env/global_env.rs
CHANGED
|
@@ -46,6 +46,11 @@ pub struct GlobalEnv {
|
|
|
46
46
|
|
|
47
47
|
/// Module extensions: class_name → Vec<module_name> (in extend order)
|
|
48
48
|
module_extensions: HashMap<String, Vec<String>>,
|
|
49
|
+
|
|
50
|
+
/// Global variables: $var_name → VertexId
|
|
51
|
+
global_variables: HashMap<String, VertexId>,
|
|
52
|
+
|
|
53
|
+
constants: HashMap<String, VertexId>,
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
impl GlobalEnv {
|
|
@@ -59,6 +64,8 @@ impl GlobalEnv {
|
|
|
59
64
|
module_inclusions: HashMap::new(),
|
|
60
65
|
superclass_map: HashMap::new(),
|
|
61
66
|
module_extensions: HashMap::new(),
|
|
67
|
+
global_variables: HashMap::new(),
|
|
68
|
+
constants: HashMap::new(),
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
|
|
@@ -234,6 +241,25 @@ impl GlobalEnv {
|
|
|
234
241
|
);
|
|
235
242
|
}
|
|
236
243
|
|
|
244
|
+
// ===== Global Variables =====
|
|
245
|
+
|
|
246
|
+
/// Set a global variable. If it already exists, add an edge from value_vtx
|
|
247
|
+
/// to the existing vertex so types propagate. Otherwise, register value_vtx directly.
|
|
248
|
+
pub fn set_global_var(&mut self, name: String, value_vtx: VertexId) -> VertexId {
|
|
249
|
+
if let Some(&existing_vtx) = self.global_variables.get(&name) {
|
|
250
|
+
self.add_edge(value_vtx, existing_vtx);
|
|
251
|
+
existing_vtx
|
|
252
|
+
} else {
|
|
253
|
+
self.global_variables.insert(name, value_vtx);
|
|
254
|
+
value_vtx
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// Get the vertex for a global variable, if it has been registered.
|
|
259
|
+
pub fn get_global_var(&self, name: &str) -> Option<VertexId> {
|
|
260
|
+
self.global_variables.get(name).copied()
|
|
261
|
+
}
|
|
262
|
+
|
|
237
263
|
// ===== Type Errors =====
|
|
238
264
|
|
|
239
265
|
/// Record a type error (undefined method)
|
|
@@ -247,6 +273,20 @@ impl GlobalEnv {
|
|
|
247
273
|
.push(TypeError::new(receiver_type, method_name, location));
|
|
248
274
|
}
|
|
249
275
|
|
|
276
|
+
pub fn set_constant(&mut self, key: String, value_vtx: VertexId) -> VertexId {
|
|
277
|
+
if let Some(&existing_vtx) = self.constants.get(&key) {
|
|
278
|
+
self.add_edge(value_vtx, existing_vtx);
|
|
279
|
+
existing_vtx
|
|
280
|
+
} else {
|
|
281
|
+
self.constants.insert(key, value_vtx);
|
|
282
|
+
value_vtx
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
pub fn get_constant(&self, key: &str) -> Option<VertexId> {
|
|
287
|
+
self.constants.get(key).copied()
|
|
288
|
+
}
|
|
289
|
+
|
|
250
290
|
// ===== Scope Management =====
|
|
251
291
|
|
|
252
292
|
/// Register a constant (simple name → qualified name) in the parent scope
|
|
@@ -320,6 +360,16 @@ impl GlobalEnv {
|
|
|
320
360
|
scope_id
|
|
321
361
|
}
|
|
322
362
|
|
|
363
|
+
/// Enter a lambda scope
|
|
364
|
+
pub fn enter_lambda(&mut self) -> (ScopeId, VertexId) {
|
|
365
|
+
let merge_vtx = self.new_vertex();
|
|
366
|
+
let scope_id = self.scope_manager.new_scope(ScopeKind::Lambda {
|
|
367
|
+
return_vertex: Some(merge_vtx),
|
|
368
|
+
});
|
|
369
|
+
self.scope_manager.enter_scope(scope_id);
|
|
370
|
+
(scope_id, merge_vtx)
|
|
371
|
+
}
|
|
372
|
+
|
|
323
373
|
/// Exit current scope
|
|
324
374
|
pub fn exit_scope(&mut self) {
|
|
325
375
|
self.scope_manager.exit_scope();
|
data/core/src/env/scope.rs
CHANGED
|
@@ -22,6 +22,9 @@ pub enum ScopeKind {
|
|
|
22
22
|
return_vertex: Option<VertexId>, // Merge vertex for return statements
|
|
23
23
|
},
|
|
24
24
|
Block,
|
|
25
|
+
Lambda {
|
|
26
|
+
return_vertex: Option<VertexId>,
|
|
27
|
+
},
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/// Scope information
|
|
@@ -76,6 +79,16 @@ impl Scope {
|
|
|
76
79
|
pub fn get_instance_var(&self, name: &str) -> Option<VertexId> {
|
|
77
80
|
self.instance_vars.get(name).copied()
|
|
78
81
|
}
|
|
82
|
+
|
|
83
|
+
/// Add class variable
|
|
84
|
+
pub fn set_class_var(&mut self, name: String, vtx: VertexId) {
|
|
85
|
+
self.class_vars.insert(name, vtx);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Get class variable
|
|
89
|
+
pub fn get_class_var(&self, name: &str) -> Option<VertexId> {
|
|
90
|
+
self.class_vars.get(name).copied()
|
|
91
|
+
}
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
/// Scope manager
|
|
@@ -267,14 +280,12 @@ impl ScopeManager {
|
|
|
267
280
|
})
|
|
268
281
|
}
|
|
269
282
|
|
|
270
|
-
/// Get return_vertex from the nearest enclosing method scope
|
|
283
|
+
/// Get return_vertex from the nearest enclosing method or lambda scope
|
|
271
284
|
pub fn current_method_return_vertex(&self) -> Option<VertexId> {
|
|
272
|
-
self.walk_scopes().find_map(|scope| {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
None
|
|
277
|
-
}
|
|
285
|
+
self.walk_scopes().find_map(|scope| match &scope.kind {
|
|
286
|
+
ScopeKind::Method { return_vertex, .. } => *return_vertex,
|
|
287
|
+
ScopeKind::Lambda { return_vertex } => *return_vertex,
|
|
288
|
+
_ => None,
|
|
278
289
|
})
|
|
279
290
|
}
|
|
280
291
|
|
|
@@ -296,4 +307,77 @@ impl ScopeManager {
|
|
|
296
307
|
}
|
|
297
308
|
}
|
|
298
309
|
}
|
|
310
|
+
|
|
311
|
+
/// Lookup class variable in enclosing class scope.
|
|
312
|
+
///
|
|
313
|
+
/// Note: Only searches `ScopeKind::Class` scopes. Module-scoped @@var and
|
|
314
|
+
/// inheritance-chain traversal are not supported in v0.2.0.
|
|
315
|
+
pub fn lookup_class_var(&self, name: &str) -> Option<VertexId> {
|
|
316
|
+
self.walk_scopes()
|
|
317
|
+
.find(|scope| matches!(&scope.kind, ScopeKind::Class { .. }))
|
|
318
|
+
.and_then(|scope| scope.get_class_var(name))
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// Set class variable in enclosing class scope.
|
|
322
|
+
///
|
|
323
|
+
/// No-op if there is no enclosing `ScopeKind::Class` scope (e.g., top-level or module scope).
|
|
324
|
+
/// Module-scoped @@var support is planned for a future version.
|
|
325
|
+
pub fn set_class_var_in_class(&mut self, name: String, vtx: VertexId) {
|
|
326
|
+
let class_scope_id = self.walk_scopes()
|
|
327
|
+
.find(|scope| matches!(&scope.kind, ScopeKind::Class { .. }))
|
|
328
|
+
.map(|scope| scope.id);
|
|
329
|
+
if let Some(scope_id) = class_scope_id {
|
|
330
|
+
if let Some(scope) = self.scopes.get_mut(&scope_id) {
|
|
331
|
+
scope.set_class_var(name, vtx);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
#[cfg(test)]
|
|
338
|
+
mod tests {
|
|
339
|
+
use super::*;
|
|
340
|
+
|
|
341
|
+
#[test]
|
|
342
|
+
fn test_class_var_set_and_get() {
|
|
343
|
+
let mut scope = Scope::new(ScopeId(0), ScopeKind::TopLevel, None);
|
|
344
|
+
let vtx = VertexId(1);
|
|
345
|
+
scope.set_class_var("@@count".to_string(), vtx);
|
|
346
|
+
assert_eq!(scope.get_class_var("@@count"), Some(vtx));
|
|
347
|
+
assert_eq!(scope.get_class_var("@@missing"), None);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#[test]
|
|
351
|
+
fn test_lookup_class_var_from_method_scope() {
|
|
352
|
+
let mut manager = ScopeManager::new();
|
|
353
|
+
|
|
354
|
+
// Class scope
|
|
355
|
+
let class_id = manager.new_scope(ScopeKind::Class {
|
|
356
|
+
name: "Counter".to_string(),
|
|
357
|
+
superclass: None,
|
|
358
|
+
});
|
|
359
|
+
manager.enter_scope(class_id);
|
|
360
|
+
|
|
361
|
+
let vtx = VertexId(42);
|
|
362
|
+
manager.set_class_var_in_class("@@count".to_string(), vtx);
|
|
363
|
+
|
|
364
|
+
// Method scope (inside the class)
|
|
365
|
+
let method_id = manager.new_scope(ScopeKind::Method {
|
|
366
|
+
name: "increment".to_string(),
|
|
367
|
+
receiver_type: Some("Counter".to_string()),
|
|
368
|
+
return_vertex: None,
|
|
369
|
+
});
|
|
370
|
+
manager.enter_scope(method_id);
|
|
371
|
+
|
|
372
|
+
// Should find @@count through the class scope
|
|
373
|
+
assert_eq!(manager.lookup_class_var("@@count"), Some(vtx));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#[test]
|
|
377
|
+
fn test_set_class_var_noop_without_class_scope() {
|
|
378
|
+
let mut manager = ScopeManager::new();
|
|
379
|
+
// At top-level, no class scope exists
|
|
380
|
+
manager.set_class_var_in_class("@@var".to_string(), VertexId(1));
|
|
381
|
+
assert_eq!(manager.lookup_class_var("@@var"), None);
|
|
382
|
+
}
|
|
299
383
|
}
|
data/core/src/graph/box.rs
CHANGED
|
@@ -112,6 +112,17 @@ impl MethodCallBox {
|
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
if let Type::Proc { return_vertex, param_vertices, .. } = recv_ty {
|
|
116
|
+
if self.method_name == "call" {
|
|
117
|
+
if let Some(merge_vtx) = return_vertex {
|
|
118
|
+
changes.add_edge(*merge_vtx, self.ret);
|
|
119
|
+
}
|
|
120
|
+
propagate_arguments(&self.arg_vtxs, Some(param_vertices), changes);
|
|
121
|
+
}
|
|
122
|
+
// TODO: Proc#arity, Proc#curry etc. not yet resolved via RBS
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
115
126
|
if let Some(method_info) = genv.resolve_method(recv_ty, &self.method_name) {
|
|
116
127
|
if let Some(return_vtx) = method_info.return_vertex {
|
|
117
128
|
// User-defined method: connect body's return vertex to call site
|
data/core/src/types.rs
CHANGED
|
@@ -141,6 +141,10 @@ pub enum Type {
|
|
|
141
141
|
Union(Vec<Type>),
|
|
142
142
|
/// Bottom type: no type information
|
|
143
143
|
Bot,
|
|
144
|
+
Proc {
|
|
145
|
+
return_vertex: Option<crate::graph::VertexId>,
|
|
146
|
+
param_vertices: Vec<crate::graph::VertexId>,
|
|
147
|
+
},
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
impl Type {
|
|
@@ -159,6 +163,10 @@ impl Type {
|
|
|
159
163
|
names.join(" | ")
|
|
160
164
|
}
|
|
161
165
|
Type::Bot => "untyped".to_string(),
|
|
166
|
+
Type::Proc { param_vertices, .. } => {
|
|
167
|
+
let params = vec!["untyped"; param_vertices.len()];
|
|
168
|
+
format!("Proc[({})->untyped]", params.join(", "))
|
|
169
|
+
}
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
172
|
|
|
@@ -277,6 +285,16 @@ impl Type {
|
|
|
277
285
|
}
|
|
278
286
|
}
|
|
279
287
|
|
|
288
|
+
pub fn proc_type_with_vertex(
|
|
289
|
+
return_vertex: crate::graph::VertexId,
|
|
290
|
+
param_vertices: Vec<crate::graph::VertexId>,
|
|
291
|
+
) -> Self {
|
|
292
|
+
Type::Proc {
|
|
293
|
+
return_vertex: Some(return_vertex),
|
|
294
|
+
param_vertices,
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
280
298
|
/// Collapse a Vec<Type> into a single Type or Union.
|
|
281
299
|
/// Returns the single element if len==1, or Union if len>1.
|
|
282
300
|
/// Panics if the vec is empty.
|
|
@@ -417,4 +435,20 @@ mod tests {
|
|
|
417
435
|
assert_eq!(singleton.show(), "singleton(Api::User)");
|
|
418
436
|
assert_eq!(singleton.base_class_name(), Some("Api::User"));
|
|
419
437
|
}
|
|
438
|
+
|
|
439
|
+
#[test]
|
|
440
|
+
fn test_proc_type_show() {
|
|
441
|
+
let proc_ty = Type::Proc {
|
|
442
|
+
return_vertex: None,
|
|
443
|
+
param_vertices: vec![crate::graph::VertexId(99)],
|
|
444
|
+
};
|
|
445
|
+
assert_eq!(proc_ty.show(), "Proc[(untyped)->untyped]");
|
|
446
|
+
|
|
447
|
+
let proc_no_params = Type::Proc {
|
|
448
|
+
return_vertex: None,
|
|
449
|
+
param_vertices: vec![],
|
|
450
|
+
};
|
|
451
|
+
assert_eq!(proc_no_params.show(), "Proc[()->untyped]");
|
|
452
|
+
}
|
|
453
|
+
|
|
420
454
|
}
|
data/ext/Cargo.toml
CHANGED
data/lib/methodray/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: method-ray
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- dak2
|
|
@@ -43,11 +43,13 @@ files:
|
|
|
43
43
|
- core/src/analyzer/attributes.rs
|
|
44
44
|
- core/src/analyzer/blocks.rs
|
|
45
45
|
- core/src/analyzer/calls.rs
|
|
46
|
+
- core/src/analyzer/compound_assignments.rs
|
|
46
47
|
- core/src/analyzer/conditionals.rs
|
|
47
48
|
- core/src/analyzer/definitions.rs
|
|
48
49
|
- core/src/analyzer/dispatch.rs
|
|
49
50
|
- core/src/analyzer/exceptions.rs
|
|
50
51
|
- core/src/analyzer/install.rs
|
|
52
|
+
- core/src/analyzer/lambdas.rs
|
|
51
53
|
- core/src/analyzer/literals.rs
|
|
52
54
|
- core/src/analyzer/loops.rs
|
|
53
55
|
- core/src/analyzer/mod.rs
|
|
@@ -57,6 +59,7 @@ files:
|
|
|
57
59
|
- core/src/analyzer/returns.rs
|
|
58
60
|
- core/src/analyzer/super_calls.rs
|
|
59
61
|
- core/src/analyzer/variables.rs
|
|
62
|
+
- core/src/analyzer/yields.rs
|
|
60
63
|
- core/src/cache/mod.rs
|
|
61
64
|
- core/src/cache/rbs_cache.rs
|
|
62
65
|
- core/src/checker.rs
|