method-ray 0.1.8 → 0.1.10
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 +38 -0
- data/README.md +9 -11
- data/{rust → core}/Cargo.toml +1 -1
- data/core/src/analyzer/assignments.rs +219 -0
- data/{rust → core}/src/analyzer/blocks.rs +0 -50
- data/{rust → core}/src/analyzer/calls.rs +3 -32
- data/core/src/analyzer/conditionals.rs +190 -0
- data/core/src/analyzer/definitions.rs +205 -0
- data/core/src/analyzer/dispatch.rs +455 -0
- data/core/src/analyzer/exceptions.rs +168 -0
- data/{rust → core}/src/analyzer/install.rs +16 -1
- data/{rust → core}/src/analyzer/literals.rs +3 -71
- data/core/src/analyzer/loops.rs +94 -0
- data/{rust → core}/src/analyzer/mod.rs +1 -15
- data/core/src/analyzer/operators.rs +79 -0
- data/{rust → core}/src/analyzer/parameters.rs +4 -67
- data/core/src/analyzer/parentheses.rs +25 -0
- data/core/src/analyzer/returns.rs +39 -0
- data/core/src/analyzer/super_calls.rs +74 -0
- data/{rust → core}/src/analyzer/variables.rs +5 -25
- data/{rust → core}/src/checker.rs +0 -13
- data/{rust → core}/src/diagnostics/diagnostic.rs +0 -41
- data/{rust → core}/src/diagnostics/formatter.rs +0 -38
- data/{rust → core}/src/env/box_manager.rs +0 -30
- data/{rust → core}/src/env/global_env.rs +67 -80
- data/core/src/env/local_env.rs +42 -0
- data/core/src/env/method_registry.rs +173 -0
- data/core/src/env/scope.rs +299 -0
- data/{rust → core}/src/env/vertex_manager.rs +0 -73
- data/core/src/graph/box.rs +347 -0
- data/{rust → core}/src/graph/change_set.rs +0 -65
- data/{rust → core}/src/graph/vertex.rs +0 -69
- data/{rust → core}/src/parser.rs +0 -77
- data/{rust → core}/src/types.rs +11 -0
- data/ext/Cargo.toml +2 -2
- data/lib/methodray/binary_locator.rb +2 -2
- data/lib/methodray/commands.rb +1 -1
- data/lib/methodray/version.rb +1 -1
- metadata +58 -56
- data/rust/src/analyzer/assignments.rs +0 -152
- data/rust/src/analyzer/conditionals.rs +0 -538
- data/rust/src/analyzer/definitions.rs +0 -719
- data/rust/src/analyzer/dispatch.rs +0 -1137
- data/rust/src/analyzer/exceptions.rs +0 -521
- data/rust/src/analyzer/loops.rs +0 -176
- data/rust/src/analyzer/operators.rs +0 -284
- data/rust/src/analyzer/parentheses.rs +0 -113
- data/rust/src/analyzer/returns.rs +0 -191
- data/rust/src/env/local_env.rs +0 -92
- data/rust/src/env/method_registry.rs +0 -268
- data/rust/src/env/scope.rs +0 -596
- data/rust/src/graph/box.rs +0 -766
- /data/{rust → core}/src/analyzer/attributes.rs +0 -0
- /data/{rust → core}/src/cache/mod.rs +0 -0
- /data/{rust → core}/src/cache/rbs_cache.rs +0 -0
- /data/{rust → core}/src/cli/args.rs +0 -0
- /data/{rust → core}/src/cli/commands.rs +0 -0
- /data/{rust → core}/src/cli/mod.rs +0 -0
- /data/{rust → core}/src/diagnostics/mod.rs +0 -0
- /data/{rust → core}/src/env/mod.rs +0 -0
- /data/{rust → core}/src/env/type_error.rs +0 -0
- /data/{rust → core}/src/graph/mod.rs +0 -0
- /data/{rust → core}/src/lib.rs +0 -0
- /data/{rust → core}/src/lsp/diagnostics.rs +0 -0
- /data/{rust → core}/src/lsp/main.rs +0 -0
- /data/{rust → core}/src/lsp/mod.rs +0 -0
- /data/{rust → core}/src/lsp/server.rs +0 -0
- /data/{rust → core}/src/main.rs +0 -0
- /data/{rust → core}/src/rbs/converter.rs +0 -0
- /data/{rust → core}/src/rbs/error.rs +0 -0
- /data/{rust → core}/src/rbs/loader.rs +0 -0
- /data/{rust → core}/src/rbs/mod.rs +0 -0
- /data/{rust → core}/src/source_map.rs +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//! Super call handling: `super` and `super(args)`
|
|
2
|
+
//!
|
|
3
|
+
//! Ruby's `super` calls the same-named method on the parent class.
|
|
4
|
+
//! - `super(args)` → SuperNode: explicit arguments
|
|
5
|
+
//! - `super` (bare) → ForwardingSuperNode: implicit argument forwarding
|
|
6
|
+
//!
|
|
7
|
+
//! Note: ForwardingSuperNode (bare `super`) is treated as a zero-argument
|
|
8
|
+
//! call. In Ruby, bare `super` forwards all arguments from the enclosing
|
|
9
|
+
//! method, but replicating this requires parameter-vertex forwarding that
|
|
10
|
+
//! is not yet implemented. Return type inference is unaffected.
|
|
11
|
+
|
|
12
|
+
use ruby_prism::{ForwardingSuperNode, SuperNode};
|
|
13
|
+
|
|
14
|
+
use crate::env::{GlobalEnv, LocalEnv};
|
|
15
|
+
use crate::graph::{ChangeSet, VertexId};
|
|
16
|
+
use crate::source_map::SourceLocation as SL;
|
|
17
|
+
use crate::types::Type;
|
|
18
|
+
|
|
19
|
+
/// Process SuperNode: `super(args)` — explicit arguments
|
|
20
|
+
pub(crate) fn process_super_node(
|
|
21
|
+
genv: &mut GlobalEnv,
|
|
22
|
+
lenv: &mut LocalEnv,
|
|
23
|
+
changes: &mut ChangeSet,
|
|
24
|
+
source: &str,
|
|
25
|
+
super_node: &SuperNode,
|
|
26
|
+
) -> Option<VertexId> {
|
|
27
|
+
let location = SL::from_prism_location_with_source(&super_node.location(), source);
|
|
28
|
+
process_super_call(genv, lenv, changes, source, super_node.arguments(), location)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Process ForwardingSuperNode: `super` — implicit argument forwarding
|
|
32
|
+
pub(crate) fn process_forwarding_super_node(
|
|
33
|
+
genv: &mut GlobalEnv,
|
|
34
|
+
lenv: &mut LocalEnv,
|
|
35
|
+
changes: &mut ChangeSet,
|
|
36
|
+
source: &str,
|
|
37
|
+
node: &ForwardingSuperNode,
|
|
38
|
+
) -> Option<VertexId> {
|
|
39
|
+
let location = SL::from_prism_location_with_source(&node.location(), source);
|
|
40
|
+
process_super_call(genv, lenv, changes, source, None, location)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Resolve a super call by looking up the same-named method on the superclass.
|
|
44
|
+
///
|
|
45
|
+
/// Returns `None` if there is no enclosing method scope (super outside a method)
|
|
46
|
+
/// or no explicit superclass declared on the enclosing class.
|
|
47
|
+
fn process_super_call(
|
|
48
|
+
genv: &mut GlobalEnv,
|
|
49
|
+
lenv: &mut LocalEnv,
|
|
50
|
+
changes: &mut ChangeSet,
|
|
51
|
+
source: &str,
|
|
52
|
+
arguments: Option<ruby_prism::ArgumentsNode>,
|
|
53
|
+
location: SL,
|
|
54
|
+
) -> Option<VertexId> {
|
|
55
|
+
let method_name = genv.scope_manager.current_method_name()?;
|
|
56
|
+
let superclass_name = genv.scope_manager.current_superclass()?;
|
|
57
|
+
let recv_vtx = genv.new_source(Type::instance(&superclass_name));
|
|
58
|
+
|
|
59
|
+
let (arg_vtxs, kw) = if let Some(args) = arguments {
|
|
60
|
+
super::dispatch::collect_arguments(genv, lenv, changes, source, args.arguments().iter())
|
|
61
|
+
} else {
|
|
62
|
+
(vec![], None)
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
Some(super::calls::install_method_call(
|
|
66
|
+
genv,
|
|
67
|
+
recv_vtx,
|
|
68
|
+
method_name,
|
|
69
|
+
arg_vtxs,
|
|
70
|
+
kw,
|
|
71
|
+
Some(location),
|
|
72
|
+
false, // super calls cannot use safe navigation
|
|
73
|
+
))
|
|
74
|
+
}
|
|
@@ -10,7 +10,7 @@ use crate::graph::{ChangeSet, VertexId};
|
|
|
10
10
|
use crate::types::Type;
|
|
11
11
|
|
|
12
12
|
/// Install local variable write: x = value
|
|
13
|
-
pub fn install_local_var_write(
|
|
13
|
+
pub(crate) fn install_local_var_write(
|
|
14
14
|
genv: &mut GlobalEnv,
|
|
15
15
|
lenv: &mut LocalEnv,
|
|
16
16
|
changes: &mut ChangeSet,
|
|
@@ -24,7 +24,7 @@ pub fn install_local_var_write(
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/// Install local variable read: x
|
|
27
|
-
pub fn install_local_var_read(lenv: &LocalEnv, var_name: &str) -> Option<VertexId> {
|
|
27
|
+
pub(crate) fn install_local_var_read(lenv: &LocalEnv, var_name: &str) -> Option<VertexId> {
|
|
28
28
|
lenv.get_var(var_name)
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -33,7 +33,7 @@ pub fn install_local_var_read(lenv: &LocalEnv, var_name: &str) -> Option<VertexI
|
|
|
33
33
|
/// If @name already has a pre-allocated VertexId (e.g., from attr_reader),
|
|
34
34
|
/// an edge is added from value_vtx to the existing vertex so types propagate.
|
|
35
35
|
/// Otherwise, value_vtx is registered directly as the ivar's VertexId.
|
|
36
|
-
pub fn install_ivar_write(
|
|
36
|
+
pub(crate) fn install_ivar_write(
|
|
37
37
|
genv: &mut GlobalEnv,
|
|
38
38
|
ivar_name: String,
|
|
39
39
|
value_vtx: VertexId,
|
|
@@ -49,36 +49,16 @@ pub fn install_ivar_write(
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/// Install instance variable read: @name
|
|
52
|
-
pub fn install_ivar_read(genv: &GlobalEnv, ivar_name: &str) -> Option<VertexId> {
|
|
52
|
+
pub(crate) fn install_ivar_read(genv: &GlobalEnv, ivar_name: &str) -> Option<VertexId> {
|
|
53
53
|
genv.scope_manager.lookup_instance_var(ivar_name)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/// Install self node
|
|
57
57
|
/// Uses the fully qualified name if available (e.g., Api::V1::User instead of just User)
|
|
58
|
-
pub fn install_self(genv: &mut GlobalEnv) -> VertexId {
|
|
58
|
+
pub(crate) fn install_self(genv: &mut GlobalEnv) -> VertexId {
|
|
59
59
|
if let Some(qualified_name) = genv.scope_manager.current_qualified_name() {
|
|
60
60
|
genv.new_source(Type::instance(&qualified_name))
|
|
61
61
|
} else {
|
|
62
62
|
genv.new_source(Type::instance("Object"))
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
#[cfg(test)]
|
|
67
|
-
mod tests {
|
|
68
|
-
use super::*;
|
|
69
|
-
|
|
70
|
-
#[test]
|
|
71
|
-
fn test_install_self_at_top_level() {
|
|
72
|
-
let mut genv = GlobalEnv::new();
|
|
73
|
-
|
|
74
|
-
let vtx = install_self(&mut genv);
|
|
75
|
-
assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Object");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
#[test]
|
|
79
|
-
fn test_local_var_read_not_found() {
|
|
80
|
-
let lenv = LocalEnv::new();
|
|
81
|
-
|
|
82
|
-
assert_eq!(install_local_var_read(&lenv, "unknown"), None);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -134,16 +134,3 @@ fn collect_diagnostics(genv: &GlobalEnv, file_path: &Path) -> Vec<Diagnostic> {
|
|
|
134
134
|
|
|
135
135
|
diagnostics
|
|
136
136
|
}
|
|
137
|
-
|
|
138
|
-
#[cfg(test)]
|
|
139
|
-
mod tests {
|
|
140
|
-
use super::*;
|
|
141
|
-
|
|
142
|
-
#[test]
|
|
143
|
-
fn test_file_checker_creation() {
|
|
144
|
-
// This test will fail if RBS cache doesn't exist
|
|
145
|
-
// That's expected - cache should be generated from Ruby side first
|
|
146
|
-
let result = FileChecker::new();
|
|
147
|
-
assert!(result.is_ok() || result.is_err()); // Just check it doesn't panic
|
|
148
|
-
}
|
|
149
|
-
}
|
|
@@ -79,44 +79,3 @@ impl Diagnostic {
|
|
|
79
79
|
Self::warning(location, message)
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
|
|
83
|
-
#[cfg(test)]
|
|
84
|
-
mod tests {
|
|
85
|
-
use super::*;
|
|
86
|
-
|
|
87
|
-
#[test]
|
|
88
|
-
fn test_diagnostic_creation() {
|
|
89
|
-
let loc = Location {
|
|
90
|
-
file: PathBuf::from("test.rb"),
|
|
91
|
-
line: 10,
|
|
92
|
-
column: 5,
|
|
93
|
-
length: None,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
let diag = Diagnostic::undefined_method(loc.clone(), "Integer", "upcase");
|
|
97
|
-
assert_eq!(diag.level, DiagnosticLevel::Error);
|
|
98
|
-
assert_eq!(diag.message, "undefined method `upcase` for Integer");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#[test]
|
|
102
|
-
fn test_union_partial_error() {
|
|
103
|
-
let loc = Location {
|
|
104
|
-
file: PathBuf::from("test.rb"),
|
|
105
|
-
line: 15,
|
|
106
|
-
column: 3,
|
|
107
|
-
length: None,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
let diag = Diagnostic::union_partial_error(
|
|
111
|
-
loc,
|
|
112
|
-
vec!["String".to_string()],
|
|
113
|
-
vec!["Integer".to_string()],
|
|
114
|
-
"upcase",
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
assert_eq!(diag.level, DiagnosticLevel::Warning);
|
|
118
|
-
assert!(diag
|
|
119
|
-
.message
|
|
120
|
-
.contains("method `upcase` is defined for String but not for Integer"));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
@@ -78,41 +78,3 @@ pub fn format_diagnostics_with_file(diagnostics: &[Diagnostic], file_path: &Path
|
|
|
78
78
|
Err(_) => format_diagnostics(diagnostics), // Fallback to simple format
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
#[cfg(test)]
|
|
83
|
-
mod tests {
|
|
84
|
-
use super::*;
|
|
85
|
-
use crate::diagnostics::diagnostic::{Diagnostic, Location};
|
|
86
|
-
use std::path::PathBuf;
|
|
87
|
-
|
|
88
|
-
#[test]
|
|
89
|
-
fn test_format_diagnostics() {
|
|
90
|
-
let diagnostics = vec![
|
|
91
|
-
Diagnostic::undefined_method(
|
|
92
|
-
Location {
|
|
93
|
-
file: PathBuf::from("test.rb"),
|
|
94
|
-
line: 10,
|
|
95
|
-
column: 5,
|
|
96
|
-
length: None,
|
|
97
|
-
},
|
|
98
|
-
"Integer",
|
|
99
|
-
"upcase",
|
|
100
|
-
),
|
|
101
|
-
Diagnostic::union_partial_error(
|
|
102
|
-
Location {
|
|
103
|
-
file: PathBuf::from("test.rb"),
|
|
104
|
-
line: 15,
|
|
105
|
-
column: 3,
|
|
106
|
-
length: None,
|
|
107
|
-
},
|
|
108
|
-
vec!["String".to_string()],
|
|
109
|
-
vec!["Integer".to_string()],
|
|
110
|
-
"upcase",
|
|
111
|
-
),
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
let output = format_diagnostics(&diagnostics);
|
|
115
|
-
assert!(output.contains("test.rb:10:5: error:"));
|
|
116
|
-
assert!(output.contains("test.rb:15:3: warning:"));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
@@ -87,33 +87,3 @@ impl BoxManager {
|
|
|
87
87
|
self.boxes.is_empty()
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
#[cfg(test)]
|
|
92
|
-
mod tests {
|
|
93
|
-
use super::*;
|
|
94
|
-
|
|
95
|
-
#[test]
|
|
96
|
-
fn test_add_run_prevents_duplicates() {
|
|
97
|
-
let mut manager = BoxManager::new();
|
|
98
|
-
|
|
99
|
-
let id = BoxId(0);
|
|
100
|
-
manager.add_run(id);
|
|
101
|
-
manager.add_run(id); // Should be ignored
|
|
102
|
-
|
|
103
|
-
assert_eq!(manager.run_queue.len(), 1);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
#[test]
|
|
107
|
-
fn test_pop_run() {
|
|
108
|
-
let mut manager = BoxManager::new();
|
|
109
|
-
|
|
110
|
-
let id1 = BoxId(0);
|
|
111
|
-
let id2 = BoxId(1);
|
|
112
|
-
manager.add_run(id1);
|
|
113
|
-
manager.add_run(id2);
|
|
114
|
-
|
|
115
|
-
assert_eq!(manager.pop_run(), Some(id1));
|
|
116
|
-
assert_eq!(manager.pop_run(), Some(id2));
|
|
117
|
-
assert_eq!(manager.pop_run(), None);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
use std::collections::HashMap;
|
|
7
7
|
|
|
8
8
|
use crate::env::box_manager::BoxManager;
|
|
9
|
-
use crate::env::method_registry::{MethodInfo, MethodRegistry};
|
|
9
|
+
use crate::env::method_registry::{MethodInfo, MethodRegistry, ResolutionContext};
|
|
10
10
|
use crate::env::scope::{Scope, ScopeId, ScopeKind, ScopeManager};
|
|
11
11
|
use crate::env::type_error::TypeError;
|
|
12
12
|
use crate::env::vertex_manager::VertexManager;
|
|
@@ -37,6 +37,15 @@ pub struct GlobalEnv {
|
|
|
37
37
|
|
|
38
38
|
/// Scope management
|
|
39
39
|
pub scope_manager: ScopeManager,
|
|
40
|
+
|
|
41
|
+
/// Module inclusions: class_name → Vec<module_name> (in include order)
|
|
42
|
+
module_inclusions: HashMap<String, Vec<String>>,
|
|
43
|
+
|
|
44
|
+
/// Superclass map: child_class → parent_class
|
|
45
|
+
superclass_map: HashMap<String, String>,
|
|
46
|
+
|
|
47
|
+
/// Module extensions: class_name → Vec<module_name> (in extend order)
|
|
48
|
+
module_extensions: HashMap<String, Vec<String>>,
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
impl GlobalEnv {
|
|
@@ -47,6 +56,9 @@ impl GlobalEnv {
|
|
|
47
56
|
method_registry: MethodRegistry::new(),
|
|
48
57
|
type_errors: Vec::new(),
|
|
49
58
|
scope_manager: ScopeManager::new(),
|
|
59
|
+
module_inclusions: HashMap::new(),
|
|
60
|
+
superclass_map: HashMap::new(),
|
|
61
|
+
module_extensions: HashMap::new(),
|
|
50
62
|
}
|
|
51
63
|
}
|
|
52
64
|
|
|
@@ -158,7 +170,33 @@ impl GlobalEnv {
|
|
|
158
170
|
|
|
159
171
|
/// Resolve method
|
|
160
172
|
pub fn resolve_method(&self, recv_ty: &Type, method_name: &str) -> Option<&MethodInfo> {
|
|
161
|
-
|
|
173
|
+
let ctx = ResolutionContext {
|
|
174
|
+
inclusions: &self.module_inclusions,
|
|
175
|
+
superclass_map: &self.superclass_map,
|
|
176
|
+
extensions: &self.module_extensions,
|
|
177
|
+
};
|
|
178
|
+
self.method_registry
|
|
179
|
+
.resolve(recv_ty, method_name, &ctx)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Record that a class includes a module
|
|
183
|
+
pub fn record_include(&mut self, class_name: &str, module_name: &str) {
|
|
184
|
+
Self::record_mixin(&mut self.module_inclusions, class_name, module_name);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// Record that a class extends a module
|
|
188
|
+
pub fn record_extend(&mut self, class_name: &str, module_name: &str) {
|
|
189
|
+
Self::record_mixin(&mut self.module_extensions, class_name, module_name);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fn record_mixin(
|
|
193
|
+
map: &mut HashMap<String, Vec<String>>,
|
|
194
|
+
class_name: &str,
|
|
195
|
+
module_name: &str,
|
|
196
|
+
) {
|
|
197
|
+
map.entry(class_name.to_string())
|
|
198
|
+
.or_default()
|
|
199
|
+
.push(module_name.to_string());
|
|
162
200
|
}
|
|
163
201
|
|
|
164
202
|
/// Register built-in method
|
|
@@ -225,14 +263,38 @@ impl GlobalEnv {
|
|
|
225
263
|
}
|
|
226
264
|
}
|
|
227
265
|
|
|
228
|
-
/// Enter a class scope
|
|
229
|
-
pub fn enter_class(&mut self, name: String) -> ScopeId {
|
|
266
|
+
/// Enter a class scope with optional superclass
|
|
267
|
+
pub fn enter_class(&mut self, name: String, superclass: Option<&str>) -> ScopeId {
|
|
230
268
|
let scope_id = self.scope_manager.new_scope(ScopeKind::Class {
|
|
231
269
|
name: name.clone(),
|
|
232
|
-
superclass:
|
|
270
|
+
superclass: superclass.map(|s| s.to_string()),
|
|
233
271
|
});
|
|
234
272
|
self.scope_manager.enter_scope(scope_id);
|
|
235
273
|
self.register_constant_in_parent(scope_id, &name);
|
|
274
|
+
|
|
275
|
+
// Record superclass relationship in superclass_map
|
|
276
|
+
if let Some(parent) = superclass {
|
|
277
|
+
let child_name = self.scope_manager.current_qualified_name()
|
|
278
|
+
.unwrap_or_else(|| name.clone());
|
|
279
|
+
// NOTE: lookup_constant may fail for cross-namespace inheritance
|
|
280
|
+
// (e.g., `class Dog < Animal` inside `module Service` where Animal is `Api::Animal`).
|
|
281
|
+
// In that case, the raw name is used. This is a known limitation (see design doc Q2).
|
|
282
|
+
let parent_name = self.scope_manager.lookup_constant(parent)
|
|
283
|
+
.unwrap_or_else(|| parent.to_string());
|
|
284
|
+
|
|
285
|
+
// Detect superclass mismatch (Ruby raises TypeError for this at runtime)
|
|
286
|
+
if let Some(existing) = self.superclass_map.get(&child_name) {
|
|
287
|
+
if *existing != parent_name {
|
|
288
|
+
eprintln!(
|
|
289
|
+
"[methodray] warning: superclass mismatch for {}: previously {}, now {}",
|
|
290
|
+
child_name, existing, parent_name
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
self.superclass_map.insert(child_name, parent_name);
|
|
296
|
+
}
|
|
297
|
+
|
|
236
298
|
scope_id
|
|
237
299
|
}
|
|
238
300
|
|
|
@@ -279,78 +341,3 @@ impl Default for GlobalEnv {
|
|
|
279
341
|
Self::new()
|
|
280
342
|
}
|
|
281
343
|
}
|
|
282
|
-
|
|
283
|
-
#[cfg(test)]
|
|
284
|
-
mod tests {
|
|
285
|
-
use super::*;
|
|
286
|
-
|
|
287
|
-
#[test]
|
|
288
|
-
fn test_global_env_new_vertex() {
|
|
289
|
-
let mut genv = GlobalEnv::new();
|
|
290
|
-
|
|
291
|
-
let v1 = genv.new_vertex();
|
|
292
|
-
let v2 = genv.new_vertex();
|
|
293
|
-
|
|
294
|
-
assert_eq!(v1.0, 0);
|
|
295
|
-
assert_eq!(v2.0, 1);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
#[test]
|
|
299
|
-
fn test_global_env_new_source() {
|
|
300
|
-
let mut genv = GlobalEnv::new();
|
|
301
|
-
|
|
302
|
-
let s1 = genv.new_source(Type::string());
|
|
303
|
-
let s2 = genv.new_source(Type::integer());
|
|
304
|
-
|
|
305
|
-
assert_eq!(genv.get_source(s1).unwrap().ty.show(), "String");
|
|
306
|
-
assert_eq!(genv.get_source(s2).unwrap().ty.show(), "Integer");
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
#[test]
|
|
310
|
-
fn test_global_env_edge_propagation() {
|
|
311
|
-
let mut genv = GlobalEnv::new();
|
|
312
|
-
|
|
313
|
-
// Source<String> -> Vertex
|
|
314
|
-
let src = genv.new_source(Type::string());
|
|
315
|
-
let vtx = genv.new_vertex();
|
|
316
|
-
|
|
317
|
-
genv.add_edge(src, vtx);
|
|
318
|
-
|
|
319
|
-
// Verify type propagated to Vertex
|
|
320
|
-
assert_eq!(genv.get_vertex(vtx).unwrap().show(), "String");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
#[test]
|
|
324
|
-
fn test_global_env_chain_propagation() {
|
|
325
|
-
let mut genv = GlobalEnv::new();
|
|
326
|
-
|
|
327
|
-
// Source<String> -> Vertex1 -> Vertex2
|
|
328
|
-
let src = genv.new_source(Type::string());
|
|
329
|
-
let v1 = genv.new_vertex();
|
|
330
|
-
let v2 = genv.new_vertex();
|
|
331
|
-
|
|
332
|
-
genv.add_edge(src, v1);
|
|
333
|
-
genv.add_edge(v1, v2);
|
|
334
|
-
|
|
335
|
-
// Verify type propagated to v2
|
|
336
|
-
assert_eq!(genv.get_vertex(v1).unwrap().show(), "String");
|
|
337
|
-
assert_eq!(genv.get_vertex(v2).unwrap().show(), "String");
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
#[test]
|
|
341
|
-
fn test_global_env_union_propagation() {
|
|
342
|
-
let mut genv = GlobalEnv::new();
|
|
343
|
-
|
|
344
|
-
// Source<String> -> Vertex
|
|
345
|
-
// Source<Integer> -> Vertex
|
|
346
|
-
let src1 = genv.new_source(Type::string());
|
|
347
|
-
let src2 = genv.new_source(Type::integer());
|
|
348
|
-
let vtx = genv.new_vertex();
|
|
349
|
-
|
|
350
|
-
genv.add_edge(src1, vtx);
|
|
351
|
-
genv.add_edge(src2, vtx);
|
|
352
|
-
|
|
353
|
-
// Verify it became Union type
|
|
354
|
-
assert_eq!(genv.get_vertex(vtx).unwrap().show(), "(Integer | String)");
|
|
355
|
-
}
|
|
356
|
-
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
use crate::graph::VertexId;
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
|
|
4
|
+
/// Local environment: mapping of local variable names to VertexIDs
|
|
5
|
+
pub struct LocalEnv {
|
|
6
|
+
locals: HashMap<String, VertexId>,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl Default for LocalEnv {
|
|
10
|
+
fn default() -> Self {
|
|
11
|
+
Self::new()
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl LocalEnv {
|
|
16
|
+
pub fn new() -> Self {
|
|
17
|
+
Self {
|
|
18
|
+
locals: HashMap::new(),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Register variable
|
|
23
|
+
pub fn new_var(&mut self, name: String, vtx_id: VertexId) {
|
|
24
|
+
self.locals.insert(name, vtx_id);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Get variable
|
|
28
|
+
pub fn get_var(&self, name: &str) -> Option<VertexId> {
|
|
29
|
+
self.locals.get(name).copied()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Remove a variable from the local environment.
|
|
33
|
+
/// Used for scoped variables like rescue's `=> e` binding.
|
|
34
|
+
pub fn remove_var(&mut self, name: &str) {
|
|
35
|
+
self.locals.remove(name);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Get all variables
|
|
39
|
+
pub fn all_vars(&self) -> impl Iterator<Item = (&String, &VertexId)> {
|
|
40
|
+
self.locals.iter()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
//! Method registration and resolution
|
|
2
|
+
|
|
3
|
+
use std::collections::{HashMap, HashSet};
|
|
4
|
+
|
|
5
|
+
use crate::graph::VertexId;
|
|
6
|
+
use crate::types::Type;
|
|
7
|
+
use smallvec::SmallVec;
|
|
8
|
+
|
|
9
|
+
const OBJECT_CLASS: &str = "Object";
|
|
10
|
+
const KERNEL_MODULE: &str = "Kernel";
|
|
11
|
+
|
|
12
|
+
/// Aggregated context for method resolution (inclusions, superclass chain, extensions)
|
|
13
|
+
pub struct ResolutionContext<'a> {
|
|
14
|
+
pub inclusions: &'a HashMap<String, Vec<String>>,
|
|
15
|
+
pub superclass_map: &'a HashMap<String, String>,
|
|
16
|
+
pub extensions: &'a HashMap<String, Vec<String>>,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Method information
|
|
20
|
+
#[derive(Debug, Clone)]
|
|
21
|
+
pub struct MethodInfo {
|
|
22
|
+
pub return_type: Type,
|
|
23
|
+
pub block_param_types: Option<Vec<Type>>,
|
|
24
|
+
pub return_vertex: Option<VertexId>,
|
|
25
|
+
pub param_vertices: Option<Vec<VertexId>>,
|
|
26
|
+
pub keyword_param_vertices: Option<HashMap<String, VertexId>>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Registry for method definitions
|
|
30
|
+
#[derive(Debug, Default)]
|
|
31
|
+
pub struct MethodRegistry {
|
|
32
|
+
methods: HashMap<(Type, String), MethodInfo>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl MethodRegistry {
|
|
36
|
+
/// Create a new empty registry
|
|
37
|
+
pub fn new() -> Self {
|
|
38
|
+
Self {
|
|
39
|
+
methods: HashMap::new(),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Register a method for a receiver type
|
|
44
|
+
pub fn register(&mut self, recv_ty: Type, method_name: &str, ret_ty: Type) {
|
|
45
|
+
self.register_with_block(recv_ty, method_name, ret_ty, None);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Register a method with block parameter types
|
|
49
|
+
pub fn register_with_block(
|
|
50
|
+
&mut self,
|
|
51
|
+
recv_ty: Type,
|
|
52
|
+
method_name: &str,
|
|
53
|
+
ret_ty: Type,
|
|
54
|
+
block_param_types: Option<Vec<Type>>,
|
|
55
|
+
) {
|
|
56
|
+
self.methods.insert(
|
|
57
|
+
(recv_ty, method_name.to_string()),
|
|
58
|
+
MethodInfo {
|
|
59
|
+
return_type: ret_ty,
|
|
60
|
+
block_param_types,
|
|
61
|
+
return_vertex: None,
|
|
62
|
+
param_vertices: None,
|
|
63
|
+
keyword_param_vertices: None,
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Register a user-defined method (return type resolved via graph)
|
|
69
|
+
pub fn register_user_method(
|
|
70
|
+
&mut self,
|
|
71
|
+
recv_ty: Type,
|
|
72
|
+
method_name: &str,
|
|
73
|
+
return_vertex: VertexId,
|
|
74
|
+
param_vertices: Vec<VertexId>,
|
|
75
|
+
keyword_param_vertices: Option<HashMap<String, VertexId>>,
|
|
76
|
+
) {
|
|
77
|
+
self.methods.insert(
|
|
78
|
+
(recv_ty, method_name.to_string()),
|
|
79
|
+
MethodInfo {
|
|
80
|
+
return_type: Type::Bot,
|
|
81
|
+
block_param_types: None,
|
|
82
|
+
return_vertex: Some(return_vertex),
|
|
83
|
+
param_vertices: Some(param_vertices),
|
|
84
|
+
keyword_param_vertices,
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Add included modules for a given class name to the chain.
|
|
90
|
+
fn add_included_modules(
|
|
91
|
+
chain: &mut SmallVec<[Type; 8]>,
|
|
92
|
+
class_name: &str,
|
|
93
|
+
inclusions: &HashMap<String, Vec<String>>,
|
|
94
|
+
) {
|
|
95
|
+
if let Some(modules) = inclusions.get(class_name) {
|
|
96
|
+
for module_name in modules.iter().rev() {
|
|
97
|
+
chain.push(Type::instance(module_name));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Build the method resolution order (MRO) fallback chain for a receiver type.
|
|
103
|
+
///
|
|
104
|
+
/// Returns a list of types to search in order:
|
|
105
|
+
/// 1. Exact receiver type
|
|
106
|
+
/// 2. Generic → base class (e.g., Array[Integer] → Array)
|
|
107
|
+
/// 3. Included modules of self (last included first, matching Ruby MRO)
|
|
108
|
+
/// 4. Superclass chain: for each parent, add parent type + its included modules
|
|
109
|
+
/// 5. Object (for Instance/Generic types only)
|
|
110
|
+
/// 6. Kernel (for Instance/Generic types only)
|
|
111
|
+
/// 7. Extended modules (for Singleton types only, last extended has highest priority)
|
|
112
|
+
fn fallback_chain(
|
|
113
|
+
recv_ty: &Type,
|
|
114
|
+
ctx: &ResolutionContext,
|
|
115
|
+
) -> SmallVec<[Type; 8]> {
|
|
116
|
+
let mut chain = SmallVec::new();
|
|
117
|
+
chain.push(recv_ty.clone());
|
|
118
|
+
|
|
119
|
+
if let Type::Generic { name, .. } = recv_ty {
|
|
120
|
+
chain.push(Type::Instance { name: name.clone() });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// MRO for Instance/Generic: included modules → superclass chain → Object → Kernel
|
|
124
|
+
if matches!(recv_ty, Type::Instance { .. } | Type::Generic { .. }) {
|
|
125
|
+
// Included modules of self (reverse order = last included has highest priority)
|
|
126
|
+
if let Some(class_name) = recv_ty.base_class_name() {
|
|
127
|
+
Self::add_included_modules(&mut chain, class_name, ctx.inclusions);
|
|
128
|
+
|
|
129
|
+
// Walk superclass chain
|
|
130
|
+
let mut visited = HashSet::new();
|
|
131
|
+
visited.insert(class_name.to_string());
|
|
132
|
+
let mut current = class_name.to_string();
|
|
133
|
+
while let Some(parent) = ctx.superclass_map.get(¤t) {
|
|
134
|
+
if !visited.insert(parent.clone()) {
|
|
135
|
+
// Cycle detected, stop walking
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
chain.push(Type::instance(parent));
|
|
139
|
+
Self::add_included_modules(&mut chain, parent, ctx.inclusions);
|
|
140
|
+
current = parent.clone();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
chain.push(Type::instance(OBJECT_CLASS));
|
|
145
|
+
chain.push(Type::instance(KERNEL_MODULE));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Singleton type: search extended modules (extend makes module methods available as class methods)
|
|
149
|
+
if let Type::Singleton { name } = recv_ty {
|
|
150
|
+
Self::add_included_modules(&mut chain, name.full_name(), ctx.extensions);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
chain
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// Resolve a method for a receiver type
|
|
157
|
+
///
|
|
158
|
+
/// Searches the MRO fallback chain: exact type → base class (for generics)
|
|
159
|
+
/// → included modules → superclass chain → Object → Kernel.
|
|
160
|
+
/// For Singleton types, also searches extended modules after exact match.
|
|
161
|
+
/// For other non-instance types (Nil, Union, Bot), only exact match is attempted.
|
|
162
|
+
pub fn resolve(
|
|
163
|
+
&self,
|
|
164
|
+
recv_ty: &Type,
|
|
165
|
+
method_name: &str,
|
|
166
|
+
ctx: &ResolutionContext,
|
|
167
|
+
) -> Option<&MethodInfo> {
|
|
168
|
+
let method_key = method_name.to_string();
|
|
169
|
+
Self::fallback_chain(recv_ty, ctx)
|
|
170
|
+
.into_iter()
|
|
171
|
+
.find_map(|ty| self.methods.get(&(ty, method_key.clone())))
|
|
172
|
+
}
|
|
173
|
+
}
|