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
data/rust/src/parser.rs
CHANGED
|
@@ -1,46 +1,81 @@
|
|
|
1
1
|
use anyhow::{Context, Result};
|
|
2
|
+
use bumpalo::Bump;
|
|
2
3
|
use ruby_prism::{parse, ParseResult};
|
|
3
4
|
use std::fs;
|
|
4
5
|
use std::path::Path;
|
|
5
6
|
|
|
6
|
-
/// Parse
|
|
7
|
+
/// Parse session - manages source bytes for multiple files using arena allocation
|
|
7
8
|
///
|
|
8
|
-
///
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
parse_ruby_source(&source, file_path.to_string_lossy().to_string())
|
|
9
|
+
/// Uses an arena allocator to efficiently manage source bytes during parsing.
|
|
10
|
+
/// When the session is dropped, all memory is released at once.
|
|
11
|
+
pub struct ParseSession {
|
|
12
|
+
arena: Bump,
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
pub fn
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
15
|
+
impl ParseSession {
|
|
16
|
+
pub fn new() -> Self {
|
|
17
|
+
Self { arena: Bump::new() }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Create with pre-allocated capacity (recommended for batch file processing)
|
|
21
|
+
pub fn with_capacity(capacity: usize) -> Self {
|
|
22
|
+
Self {
|
|
23
|
+
arena: Bump::with_capacity(capacity),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Allocate source in arena and parse
|
|
28
|
+
pub fn parse_source<'a>(&'a self, source: &str, file_name: &str) -> Result<ParseResult<'a>> {
|
|
29
|
+
// Copy bytes to arena
|
|
30
|
+
let source_bytes = self.arena.alloc_slice_copy(source.as_bytes());
|
|
31
|
+
let parse_result = parse(source_bytes);
|
|
32
|
+
|
|
33
|
+
// Check for parse errors
|
|
34
|
+
let error_messages: Vec<String> = parse_result
|
|
35
|
+
.errors()
|
|
36
|
+
.map(|e| {
|
|
37
|
+
format!(
|
|
38
|
+
"Parse error at offset {}: {}",
|
|
39
|
+
e.location().start_offset(),
|
|
40
|
+
e.message()
|
|
41
|
+
)
|
|
42
|
+
})
|
|
43
|
+
.collect();
|
|
44
|
+
|
|
45
|
+
if !error_messages.is_empty() {
|
|
46
|
+
anyhow::bail!(
|
|
47
|
+
"Failed to parse Ruby source in {}:\n{}",
|
|
48
|
+
file_name,
|
|
49
|
+
error_messages.join("\n")
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Ok(parse_result)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Read file and parse
|
|
57
|
+
pub fn parse_file<'a>(&'a self, file_path: &Path) -> Result<ParseResult<'a>> {
|
|
58
|
+
let source = fs::read_to_string(file_path)
|
|
59
|
+
.with_context(|| format!("Failed to read file: {}", file_path.display()))?;
|
|
60
|
+
|
|
61
|
+
self.parse_source(&source, &file_path.to_string_lossy())
|
|
41
62
|
}
|
|
42
63
|
|
|
43
|
-
|
|
64
|
+
/// Get allocated memory size (for debugging)
|
|
65
|
+
pub fn allocated_bytes(&self) -> usize {
|
|
66
|
+
self.arena.allocated_bytes()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Reset arena (for memory control during batch file processing)
|
|
70
|
+
pub fn reset(&mut self) {
|
|
71
|
+
self.arena.reset();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
impl Default for ParseSession {
|
|
76
|
+
fn default() -> Self {
|
|
77
|
+
Self::new()
|
|
78
|
+
}
|
|
44
79
|
}
|
|
45
80
|
|
|
46
81
|
#[cfg(test)]
|
|
@@ -51,21 +86,24 @@ mod tests {
|
|
|
51
86
|
fn test_parse_simple_ruby() {
|
|
52
87
|
let source = r#"x = 1
|
|
53
88
|
puts x"#;
|
|
54
|
-
let
|
|
89
|
+
let session = ParseSession::new();
|
|
90
|
+
let result = session.parse_source(source, "test.rb");
|
|
55
91
|
assert!(result.is_ok());
|
|
56
92
|
}
|
|
57
93
|
|
|
58
94
|
#[test]
|
|
59
95
|
fn test_parse_string_literal() {
|
|
60
96
|
let source = r#""hello".upcase"#;
|
|
61
|
-
let
|
|
97
|
+
let session = ParseSession::new();
|
|
98
|
+
let result = session.parse_source(source, "test.rb");
|
|
62
99
|
assert!(result.is_ok());
|
|
63
100
|
}
|
|
64
101
|
|
|
65
102
|
#[test]
|
|
66
103
|
fn test_parse_array_literal() {
|
|
67
104
|
let source = r#"[1, 2, 3].map { |x| x * 2 }"#;
|
|
68
|
-
let
|
|
105
|
+
let session = ParseSession::new();
|
|
106
|
+
let result = session.parse_source(source, "test.rb");
|
|
69
107
|
assert!(result.is_ok());
|
|
70
108
|
}
|
|
71
109
|
|
|
@@ -75,14 +113,16 @@ puts x"#;
|
|
|
75
113
|
x = "hello"
|
|
76
114
|
x.upcase
|
|
77
115
|
end"#;
|
|
78
|
-
let
|
|
116
|
+
let session = ParseSession::new();
|
|
117
|
+
let result = session.parse_source(source, "test.rb");
|
|
79
118
|
assert!(result.is_ok());
|
|
80
119
|
}
|
|
81
120
|
|
|
82
121
|
#[test]
|
|
83
122
|
fn test_parse_invalid_ruby() {
|
|
84
123
|
let source = "def\nend end";
|
|
85
|
-
let
|
|
124
|
+
let session = ParseSession::new();
|
|
125
|
+
let result = session.parse_source(source, "test.rb");
|
|
86
126
|
assert!(result.is_err());
|
|
87
127
|
}
|
|
88
128
|
|
|
@@ -90,7 +130,27 @@ end"#;
|
|
|
90
130
|
fn test_parse_method_call() {
|
|
91
131
|
let source = r#"user = User.new
|
|
92
132
|
user.save"#;
|
|
93
|
-
let
|
|
133
|
+
let session = ParseSession::new();
|
|
134
|
+
let result = session.parse_source(source, "test.rb");
|
|
94
135
|
assert!(result.is_ok());
|
|
95
136
|
}
|
|
137
|
+
|
|
138
|
+
#[test]
|
|
139
|
+
fn test_parse_session_memory_tracking() {
|
|
140
|
+
let session = ParseSession::new();
|
|
141
|
+
let source = "x = 1";
|
|
142
|
+
let _ = session.parse_source(source, "test.rb").unwrap();
|
|
143
|
+
assert!(session.allocated_bytes() > 0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#[test]
|
|
147
|
+
fn test_parse_session_reset() {
|
|
148
|
+
let mut session = ParseSession::new();
|
|
149
|
+
let source = "x = 1";
|
|
150
|
+
let _ = session.parse_source(source, "test.rb").unwrap();
|
|
151
|
+
let before_reset = session.allocated_bytes();
|
|
152
|
+
session.reset();
|
|
153
|
+
// After reset, allocated_bytes may still report used chunks but internal data is cleared
|
|
154
|
+
assert!(before_reset > 0);
|
|
155
|
+
}
|
|
96
156
|
}
|
data/rust/src/rbs/converter.rs
CHANGED
|
@@ -20,18 +20,12 @@ impl RbsTypeConverter {
|
|
|
20
20
|
|
|
21
21
|
match type_name {
|
|
22
22
|
"bool" => Type::Union(vec![
|
|
23
|
-
Type::
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
Type::Instance {
|
|
27
|
-
class_name: "FalseClass".to_string(),
|
|
28
|
-
},
|
|
23
|
+
Type::instance("TrueClass"),
|
|
24
|
+
Type::instance("FalseClass"),
|
|
29
25
|
]),
|
|
30
26
|
"void" | "nil" => Type::Nil,
|
|
31
27
|
"untyped" | "top" => Type::Bot,
|
|
32
|
-
_ => Type::
|
|
33
|
-
class_name: type_name.to_string(),
|
|
34
|
-
},
|
|
28
|
+
_ => Type::instance(type_name),
|
|
35
29
|
}
|
|
36
30
|
}
|
|
37
31
|
}
|
|
@@ -43,12 +37,23 @@ mod tests {
|
|
|
43
37
|
#[test]
|
|
44
38
|
fn test_parse_simple_types() {
|
|
45
39
|
match RbsTypeConverter::parse("::String") {
|
|
46
|
-
Type::Instance {
|
|
40
|
+
Type::Instance { name } => assert_eq!(name.full_name(), "String"),
|
|
47
41
|
_ => panic!("Expected Instance type"),
|
|
48
42
|
}
|
|
49
43
|
|
|
50
44
|
match RbsTypeConverter::parse("Integer") {
|
|
51
|
-
Type::Instance {
|
|
45
|
+
Type::Instance { name } => assert_eq!(name.full_name(), "Integer"),
|
|
46
|
+
_ => panic!("Expected Instance type"),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[test]
|
|
51
|
+
fn test_parse_qualified_types() {
|
|
52
|
+
match RbsTypeConverter::parse("::Api::User") {
|
|
53
|
+
Type::Instance { name } => {
|
|
54
|
+
assert_eq!(name.full_name(), "Api::User");
|
|
55
|
+
assert_eq!(name.name(), "User");
|
|
56
|
+
}
|
|
52
57
|
_ => panic!("Expected Instance type"),
|
|
53
58
|
}
|
|
54
59
|
}
|
data/rust/src/rbs/loader.rs
CHANGED
|
@@ -2,6 +2,7 @@ use crate::env::GlobalEnv;
|
|
|
2
2
|
use crate::rbs::converter::RbsTypeConverter;
|
|
3
3
|
use crate::rbs::error::RbsError;
|
|
4
4
|
use crate::types::Type;
|
|
5
|
+
use magnus::value::ReprValue;
|
|
5
6
|
use magnus::{Error, RArray, RHash, Ruby, TryConvert, Value};
|
|
6
7
|
|
|
7
8
|
/// Method information loaded from RBS
|
|
@@ -10,6 +11,7 @@ pub struct RbsMethodInfo {
|
|
|
10
11
|
pub receiver_class: String,
|
|
11
12
|
pub method_name: String,
|
|
12
13
|
pub return_type: Type,
|
|
14
|
+
pub block_param_types: Option<Vec<String>>,
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/// Loader that calls RBS API via magnus to load method information
|
|
@@ -100,10 +102,33 @@ impl<'a> RbsLoader<'a> {
|
|
|
100
102
|
// Convert RBS type string to internal Type enum
|
|
101
103
|
let return_type = RbsTypeConverter::parse(&return_type_str);
|
|
102
104
|
|
|
105
|
+
// Parse block_param_types (optional)
|
|
106
|
+
let block_param_types: Option<Vec<String>> =
|
|
107
|
+
if let Some(bpt_value) = hash.get(self.ruby.to_symbol("block_param_types")) {
|
|
108
|
+
if bpt_value.is_nil() {
|
|
109
|
+
None
|
|
110
|
+
} else if let Ok(bpt_array) = RArray::try_convert(bpt_value) {
|
|
111
|
+
let types: Vec<String> = bpt_array
|
|
112
|
+
.into_iter()
|
|
113
|
+
.filter_map(|v| String::try_convert(v).ok())
|
|
114
|
+
.collect();
|
|
115
|
+
if types.is_empty() {
|
|
116
|
+
None
|
|
117
|
+
} else {
|
|
118
|
+
Some(types)
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
None
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
None
|
|
125
|
+
};
|
|
126
|
+
|
|
103
127
|
method_infos.push(RbsMethodInfo {
|
|
104
128
|
receiver_class,
|
|
105
129
|
method_name,
|
|
106
130
|
return_type,
|
|
131
|
+
block_param_types,
|
|
107
132
|
});
|
|
108
133
|
}
|
|
109
134
|
|
|
@@ -128,7 +153,6 @@ pub fn register_rbs_methods(genv: &mut GlobalEnv, ruby: &Ruby) -> Result<usize,
|
|
|
128
153
|
// Try to load from cache
|
|
129
154
|
let methods = if let Ok(cache) = RbsCache::load() {
|
|
130
155
|
if cache.is_valid(methodray_version, &rbs_version) {
|
|
131
|
-
eprintln!("Loaded {} methods from cache", cache.methods.len());
|
|
132
156
|
cache.to_method_infos()
|
|
133
157
|
} else {
|
|
134
158
|
eprintln!("Cache invalid, reloading from RBS...");
|
|
@@ -142,13 +166,19 @@ pub fn register_rbs_methods(genv: &mut GlobalEnv, ruby: &Ruby) -> Result<usize,
|
|
|
142
166
|
|
|
143
167
|
let count = methods.len();
|
|
144
168
|
for method_info in methods {
|
|
145
|
-
let receiver_type = Type::
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
169
|
+
let receiver_type = Type::instance(&method_info.receiver_class);
|
|
170
|
+
// Convert block param type strings to Type enums
|
|
171
|
+
let block_param_types = method_info.block_param_types.map(|types| {
|
|
172
|
+
types
|
|
173
|
+
.iter()
|
|
174
|
+
.map(|s| RbsTypeConverter::parse(s))
|
|
175
|
+
.collect()
|
|
176
|
+
});
|
|
177
|
+
genv.register_builtin_method_with_block(
|
|
149
178
|
receiver_type,
|
|
150
179
|
&method_info.method_name,
|
|
151
180
|
method_info.return_type,
|
|
181
|
+
block_param_types,
|
|
152
182
|
);
|
|
153
183
|
}
|
|
154
184
|
|
data/rust/src/rbs/mod.rs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
//! RBS type loading and conversion
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Converter is always available (no Ruby FFI dependency)
|
|
4
4
|
pub mod converter;
|
|
5
|
+
pub use converter::RbsTypeConverter;
|
|
6
|
+
|
|
7
|
+
// These require Ruby FFI for RBS loading
|
|
5
8
|
#[cfg(feature = "ruby-ffi")]
|
|
6
9
|
pub mod error;
|
|
7
10
|
#[cfg(feature = "ruby-ffi")]
|
|
8
11
|
pub mod loader;
|
|
9
12
|
|
|
10
|
-
#[cfg(feature = "ruby-ffi")]
|
|
11
|
-
pub use converter::RbsTypeConverter;
|
|
12
13
|
#[cfg(feature = "ruby-ffi")]
|
|
13
14
|
pub use error::RbsError;
|
|
14
15
|
#[cfg(feature = "ruby-ffi")]
|