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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b03f7efedd583d2cda9e59cd651ded78d5357c53eb06eac8980aae25db0529de
4
- data.tar.gz: c02996cc2a511f800d34cec0ce46744c6ae2bddb8974e361f994a332b898c467
3
+ metadata.gz: 1a219edf198245163ff36fbcc9e211dbfea2d5e5f41a467ee24636286967f4b9
4
+ data.tar.gz: 7a82426e9c5b0ba53a2d1ca8bab90da2a4b4eb58316af7cfb8a576dc6dc28b64
5
5
  SHA512:
6
- metadata.gz: 3aa19e8a26274f5d8c7b5b746e67f10d4e47ac281852251c737ebed4f933b5426e8b842d575f26110ee279fc48079a1dc382e78b2a184e0d4211d241dabae77a
7
- data.tar.gz: '08e6b8bf49475c874538c007b52fec615459eb0c376357e1b2429868a67885eb695d261ba02d078b7fb0835321b92e84f06a6ac5366066379229b0e78072d786'
6
+ metadata.gz: cac68a8608631ce81f145b17743ff7ec519e24f1ac2b92c90732d3f9fedd4badb26715fd5120b8a648fd574313383ac2dbd02d71cdfe98004ffafe5714e39cf3
7
+ data.tar.gz: ded1386c832dfc9784c360d3685ce3431437d7cae1c155e02dc6606e3e155789f9f2ad42d5e4285aed6c8cb1dbf8231ac8f81392bf25a0536e3fd18e882537f3
data/CHANGELOG.md CHANGED
@@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.4] - 2026-02-16
9
+
10
+ ### Added
11
+
12
+ - Method return type inference for user-defined methods ([#18](https://github.com/dak2/method-ray/pull/18))
13
+ - Parameter type propagation from call-site arguments to method parameters ([#19](https://github.com/dak2/method-ray/pull/19))
14
+ - Receiver-less method call support (ImplicitSelfCall) ([#20](https://github.com/dak2/method-ray/pull/20))
15
+ - `attr_reader`/`attr_writer`/`attr_accessor` support for type inference ([#21](https://github.com/dak2/method-ray/pull/21))
16
+ - `if`/`unless`/`case` conditional type inference ([#22](https://github.com/dak2/method-ray/pull/22))
17
+ - `ConstantReadNode`/`ConstantPathNode` support for type inference ([#23](https://github.com/dak2/method-ray/pull/23))
18
+
19
+ ### Changed
20
+
21
+ - Split install.rs and integration tests into focused modules ([#17](https://github.com/dak2/method-ray/pull/17))
22
+
23
+ ## [0.1.3] - 2025-02-08
24
+
25
+ ### Added
26
+
27
+ - Method parameter type inference support ([#3](https://github.com/dak2/method-ray/pull/3))
28
+ - Block parameter type variable resolution ([#4](https://github.com/dak2/method-ray/pull/4))
29
+ - Module scope support ([#6](https://github.com/dak2/method-ray/pull/6))
30
+ - Fully qualified name support for nested classes/modules ([#7](https://github.com/dak2/method-ray/pull/7))
31
+ - Float type support ([#8](https://github.com/dak2/method-ray/pull/8))
32
+ - Regexp type support ([#9](https://github.com/dak2/method-ray/pull/9))
33
+ - Range type support ([#10](https://github.com/dak2/method-ray/pull/10))
34
+ - Generic type inference for Range, Hash, and nested Array ([#11](https://github.com/dak2/method-ray/pull/11))
35
+
36
+ ### Fixed
37
+
38
+ - Call operator location ([#12](https://github.com/dak2/method-ray/pull/12))
39
+ - Memory leak ([#13](https://github.com/dak2/method-ray/pull/13))
40
+
41
+ ### Changed
42
+
43
+ - Extract BinaryLocator class from Commands module ([#5](https://github.com/dak2/method-ray/pull/5))
44
+ - Migrate Rust integration tests to Ruby CLI and Rust unit tests ([#14](https://github.com/dak2/method-ray/pull/14))
45
+ - Remove unnecessary test files and logs ([#1](https://github.com/dak2/method-ray/pull/1), [#15](https://github.com/dak2/method-ray/pull/15))
46
+
8
47
  ## [0.1.2] - 2025-01-19
9
48
 
10
49
  ### Added
@@ -30,6 +69,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
30
69
  - Initial release
31
70
  - `methodray check` - Static type checking for Ruby files
32
71
 
72
+ [0.1.4]: https://github.com/dak2/method-ray/releases/tag/v0.1.4
73
+ [0.1.3]: https://github.com/dak2/method-ray/releases/tag/v0.1.3
33
74
  [0.1.2]: https://github.com/dak2/method-ray/releases/tag/v0.1.2
34
75
  [0.1.1]: https://github.com/dak2/method-ray/releases/tag/v0.1.1
35
76
  [0.1.0]: https://github.com/dak2/method-ray/releases/tag/v0.1.0
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A fast static callable method checker for Ruby code.
4
4
 
5
+ No type annotations required, just check callable methods in your Ruby files.
6
+
5
7
  ## Requirements
6
8
 
7
9
  Method-Ray supports Ruby 3.4 or later.
@@ -14,7 +16,7 @@ gem install methodray
14
16
 
15
17
  ## Quick Start
16
18
 
17
- ### VSCode Extension
19
+ ### VSCode Extension (under development)
18
20
 
19
21
  1. Install the [Method-Ray VSCode extension](https://github.com/dak2/method-ray-vscode)
20
22
  2. Open a Ruby file in VSCode
@@ -30,6 +32,30 @@ bundle exec methodray check app/models/user.rb
30
32
  bundle exec methodray watch app/models/user.rb
31
33
  ```
32
34
 
35
+ #### Example
36
+
37
+ `methodray check <file>`: Performs static type checking on the specified Ruby file.
38
+
39
+
40
+ ```ruby
41
+ class User
42
+ def greeting
43
+ name = "Alice"
44
+ message = name.abs
45
+ message
46
+ end
47
+ end
48
+ ```
49
+
50
+ This will output:
51
+
52
+ ```
53
+ $ bundle exec methodray check app/models/user.rb
54
+ app/models/user.rb:4:19: error: undefined method `abs` for String
55
+ message = name.abs
56
+ ^
57
+ ```
58
+
33
59
  ## Contributing
34
60
 
35
61
  Bug reports and pull requests are welcome on GitHub at this repository!
data/ext/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "methodray"
3
- version = "0.1.0"
3
+ version = "0.1.4"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
data/ext/src/lib.rs CHANGED
@@ -6,7 +6,8 @@ use magnus::{function, method, prelude::*, Error, Ruby};
6
6
  use methodray_core::{
7
7
  analyzer::AstInstaller,
8
8
  env::{GlobalEnv, LocalEnv},
9
- parser, rbs,
9
+ parser::ParseSession,
10
+ rbs,
10
11
  };
11
12
 
12
13
  #[magnus::wrap(class = "MethodRay::Analyzer")]
@@ -27,11 +28,11 @@ impl Analyzer {
27
28
  /// Execute type inference
28
29
  fn infer_types(&self, source: String) -> Result<String, Error> {
29
30
  // Parse
30
- let parse_result =
31
- parser::parse_ruby_source(&source, "source.rb".to_string()).map_err(|e| {
32
- let ruby = unsafe { Ruby::get_unchecked() };
33
- Error::new(ruby.exception_runtime_error(), e.to_string())
34
- })?;
31
+ let session = ParseSession::new();
32
+ let parse_result = session.parse_source(&source, "source.rb").map_err(|e| {
33
+ let ruby = unsafe { Ruby::get_unchecked() };
34
+ Error::new(ruby.exception_runtime_error(), e.to_string())
35
+ })?;
35
36
 
36
37
  // Build graph
37
38
  let mut genv = GlobalEnv::new();
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MethodRay
4
+ class BinaryLocator
5
+ LIB_DIR = __dir__
6
+
7
+ def initialize
8
+ @binary_name = Gem.win_platform? ? 'methodray-cli.exe' : 'methodray-cli'
9
+ @legacy_binary_name = Gem.win_platform? ? 'methodray.exe' : 'methodray'
10
+ end
11
+
12
+ def find
13
+ candidates.find { |path| File.executable?(path) }
14
+ end
15
+
16
+ private
17
+
18
+ def candidates
19
+ [
20
+ # CLI binary built during gem install (lib/methodray directory)
21
+ File.expand_path(@binary_name, LIB_DIR),
22
+ # Development: target/release (project root)
23
+ File.expand_path("../../target/release/#{@binary_name}", LIB_DIR),
24
+ # Development: rust/target/release (legacy standalone binary)
25
+ File.expand_path("../../rust/target/release/#{@legacy_binary_name}", LIB_DIR)
26
+ ]
27
+ end
28
+ end
29
+ end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'binary_locator'
4
+
3
5
  module MethodRay
4
6
  module Commands
5
- COMMANDS_DIR = __dir__
6
-
7
7
  class << self
8
8
  def help
9
9
  puts <<~HELP
@@ -41,7 +41,7 @@ module MethodRay
41
41
  private
42
42
 
43
43
  def exec_rust_cli(command, args)
44
- binary_path = find_rust_binary
44
+ binary_path = BinaryLocator.new.find
45
45
 
46
46
  unless binary_path
47
47
  warn 'Error: CLI binary not found.'
@@ -56,23 +56,6 @@ module MethodRay
56
56
 
57
57
  exec(binary_path, command, *args)
58
58
  end
59
-
60
- def find_rust_binary
61
- # Platform-specific binary name
62
- cli_binary = Gem.win_platform? ? 'methodray-cli.exe' : 'methodray-cli'
63
- legacy_binary = Gem.win_platform? ? 'methodray.exe' : 'methodray'
64
-
65
- candidates = [
66
- # CLI binary built during gem install (lib/methodray directory)
67
- File.expand_path(cli_binary, COMMANDS_DIR),
68
- # Development: target/release (project root)
69
- File.expand_path("../../target/release/#{cli_binary}", COMMANDS_DIR),
70
- # Development: rust/target/release (legacy standalone binary)
71
- File.expand_path("../../rust/target/release/#{legacy_binary}", COMMANDS_DIR)
72
- ]
73
-
74
- candidates.find { |path| File.executable?(path) }
75
- end
76
59
  end
77
60
  end
78
61
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MethodRay
4
- VERSION = '0.1.2'
4
+ VERSION = '0.1.4'
5
5
  end
data/lib/methodray.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'rbs'
4
4
  require_relative 'methodray/version'
5
- require_relative 'methodray/methodray' # ネイティブ拡張
5
+ require_relative 'methodray/methodray'
6
6
 
7
7
  module MethodRay
8
8
  class Error < StandardError; end
data/rust/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "methodray-core"
3
- version = "0.1.0"
3
+ version = "0.1.4"
4
4
  edition = "2021"
5
5
 
6
6
  [lib]
@@ -18,6 +18,8 @@ path = "src/lsp/main.rs"
18
18
  required-features = ["lsp"]
19
19
 
20
20
  [dependencies]
21
+ bumpalo = "3.14"
22
+ smallvec = "1.13"
21
23
  rayon = "1.10"
22
24
  walkdir = "2.5"
23
25
  anyhow = "1.0"
@@ -0,0 +1,57 @@
1
+ //! Attribute accessor support - synthesize getter/setter methods for attr_reader/attr_writer/attr_accessor
2
+
3
+ use crate::env::GlobalEnv;
4
+ use crate::types::Type;
5
+
6
+ use super::dispatch::AttrKind;
7
+
8
+ /// Register synthesized getter/setter methods for attr_reader/attr_writer/attr_accessor.
9
+ ///
10
+ /// - attr_reader :name → registers User#name (getter, return = @name vertex)
11
+ /// - attr_writer :name → registers User#name= (setter, param → @name edge)
12
+ /// - attr_accessor :name → registers both getter and setter
13
+ ///
14
+ /// If @name has no VertexId yet, one is pre-allocated so that later assignments
15
+ /// (e.g., `@name = "Alice"` in initialize) propagate into the same vertex.
16
+ pub(crate) fn process_attr_declaration(
17
+ genv: &mut GlobalEnv,
18
+ kind: AttrKind,
19
+ attr_names: Vec<String>,
20
+ ) {
21
+ let Some(class_name) = genv.scope_manager.current_class_name() else {
22
+ return;
23
+ };
24
+ let recv_ty = Type::instance(&class_name);
25
+
26
+ for attr_name in attr_names {
27
+ let ivar_name = format!("@{}", attr_name);
28
+
29
+ // Get or pre-allocate VertexId for @name
30
+ let ivar_vtx = match genv.scope_manager.lookup_instance_var(&ivar_name) {
31
+ Some(vtx) => vtx,
32
+ None => {
33
+ let vtx = genv.new_vertex();
34
+ genv.scope_manager
35
+ .set_instance_var_in_class(ivar_name, vtx);
36
+ vtx
37
+ }
38
+ };
39
+
40
+ // Register getter (attr_reader / attr_accessor)
41
+ if matches!(kind, AttrKind::Reader | AttrKind::Accessor) {
42
+ genv.register_user_method(recv_ty.clone(), &attr_name, ivar_vtx, vec![]);
43
+ }
44
+
45
+ // Register setter (attr_writer / attr_accessor)
46
+ if matches!(kind, AttrKind::Writer | AttrKind::Accessor) {
47
+ let param_vtx = genv.new_vertex();
48
+ genv.add_edge(param_vtx, ivar_vtx);
49
+ genv.register_user_method(
50
+ recv_ty.clone(),
51
+ &format!("{}=", attr_name),
52
+ ivar_vtx,
53
+ vec![param_vtx],
54
+ );
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,175 @@
1
+ //! Block Handlers - Processing Ruby blocks
2
+ //!
3
+ //! This module is responsible for:
4
+ //! - Processing BlockNode (e.g., `{ |x| x.to_s }` or `do |x| x.to_s end`)
5
+ //! - Registering block parameters as local variables
6
+ //! - Managing block scope
7
+
8
+ use crate::env::{GlobalEnv, LocalEnv, ScopeKind};
9
+ use crate::graph::{ChangeSet, VertexId};
10
+
11
+ use super::parameters::{install_optional_parameter, install_required_parameter, install_rest_parameter};
12
+
13
+ /// Process block node
14
+ pub(crate) fn process_block_node(
15
+ genv: &mut GlobalEnv,
16
+ lenv: &mut LocalEnv,
17
+ changes: &mut ChangeSet,
18
+ source: &str,
19
+ block_node: &ruby_prism::BlockNode,
20
+ ) -> Option<VertexId> {
21
+ process_block_node_with_params(genv, lenv, changes, source, block_node);
22
+ None
23
+ }
24
+
25
+ /// Process block node and return block parameter vertex IDs
26
+ pub(crate) fn process_block_node_with_params(
27
+ genv: &mut GlobalEnv,
28
+ lenv: &mut LocalEnv,
29
+ changes: &mut ChangeSet,
30
+ source: &str,
31
+ block_node: &ruby_prism::BlockNode,
32
+ ) -> Vec<VertexId> {
33
+ enter_block_scope(genv);
34
+
35
+ let mut param_vtxs = Vec::new();
36
+
37
+ if let Some(params_node) = block_node.parameters() {
38
+ if let Some(block_params) = params_node.as_block_parameters_node() {
39
+ param_vtxs =
40
+ install_block_parameters_with_vtxs(genv, lenv, changes, source, &block_params);
41
+ }
42
+ }
43
+
44
+ if let Some(body) = block_node.body() {
45
+ if let Some(statements) = body.as_statements_node() {
46
+ super::install::install_statements(genv, lenv, changes, source, &statements);
47
+ } else {
48
+ super::install::install_node(genv, lenv, changes, source, &body);
49
+ }
50
+ }
51
+
52
+ exit_block_scope(genv);
53
+
54
+ param_vtxs
55
+ }
56
+
57
+ /// Install block parameters and return their vertex IDs
58
+ fn install_block_parameters_with_vtxs(
59
+ genv: &mut GlobalEnv,
60
+ lenv: &mut LocalEnv,
61
+ changes: &mut ChangeSet,
62
+ source: &str,
63
+ block_params: &ruby_prism::BlockParametersNode,
64
+ ) -> Vec<VertexId> {
65
+ let mut vtxs = Vec::new();
66
+
67
+ if let Some(params) = block_params.parameters() {
68
+ // Required parameters (most common in blocks)
69
+ for node in params.requireds().iter() {
70
+ if let Some(req_param) = node.as_required_parameter_node() {
71
+ let name = String::from_utf8_lossy(req_param.name().as_slice()).to_string();
72
+ let vtx = install_block_parameter(genv, lenv, name);
73
+ vtxs.push(vtx);
74
+ }
75
+ }
76
+
77
+ // Optional parameters: { |x = 1| ... }
78
+ for node in params.optionals().iter() {
79
+ if let Some(opt_param) = node.as_optional_parameter_node() {
80
+ let name = String::from_utf8_lossy(opt_param.name().as_slice()).to_string();
81
+ let default_value = opt_param.value();
82
+
83
+ if let Some(default_vtx) =
84
+ super::install::install_node(genv, lenv, changes, source, &default_value)
85
+ {
86
+ let vtx =
87
+ install_optional_parameter(genv, lenv, changes, name, default_vtx);
88
+ vtxs.push(vtx);
89
+ } else {
90
+ let vtx = install_block_parameter(genv, lenv, name);
91
+ vtxs.push(vtx);
92
+ }
93
+ }
94
+ }
95
+
96
+ // Rest parameter: { |*args| ... }
97
+ if let Some(rest_node) = params.rest() {
98
+ if let Some(rest_param) = rest_node.as_rest_parameter_node() {
99
+ if let Some(name_id) = rest_param.name() {
100
+ let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
101
+ let vtx = install_rest_parameter(genv, lenv, name);
102
+ vtxs.push(vtx);
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ vtxs
109
+ }
110
+
111
+ /// Enter a new block scope
112
+ fn enter_block_scope(genv: &mut GlobalEnv) {
113
+ let block_scope_id = genv.scope_manager.new_scope(ScopeKind::Block);
114
+ genv.scope_manager.enter_scope(block_scope_id);
115
+ }
116
+
117
+ /// Exit the current block scope
118
+ fn exit_block_scope(genv: &mut GlobalEnv) {
119
+ genv.scope_manager.exit_scope();
120
+ }
121
+
122
+ /// Install block parameter as a local variable (Bot type)
123
+ fn install_block_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
124
+ install_required_parameter(genv, lenv, name)
125
+ }
126
+
127
+ #[cfg(test)]
128
+ mod tests {
129
+ use super::*;
130
+
131
+ #[test]
132
+ fn test_enter_exit_block_scope() {
133
+ let mut genv = GlobalEnv::new();
134
+
135
+ let initial_scope_id = genv.scope_manager.current_scope().id;
136
+
137
+ enter_block_scope(&mut genv);
138
+ let block_scope_id = genv.scope_manager.current_scope().id;
139
+
140
+ assert_ne!(initial_scope_id, block_scope_id);
141
+
142
+ exit_block_scope(&mut genv);
143
+
144
+ assert_eq!(genv.scope_manager.current_scope().id, initial_scope_id);
145
+ }
146
+
147
+ #[test]
148
+ fn test_install_block_parameter() {
149
+ let mut genv = GlobalEnv::new();
150
+ let mut lenv = LocalEnv::new();
151
+
152
+ enter_block_scope(&mut genv);
153
+
154
+ let vtx = install_block_parameter(&mut genv, &mut lenv, "x".to_string());
155
+
156
+ assert_eq!(lenv.get_var("x"), Some(vtx));
157
+
158
+ exit_block_scope(&mut genv);
159
+ }
160
+
161
+ #[test]
162
+ fn test_block_inherits_parent_scope_vars() {
163
+ let mut genv = GlobalEnv::new();
164
+
165
+ genv.scope_manager
166
+ .current_scope_mut()
167
+ .set_local_var("outer".to_string(), VertexId(100));
168
+
169
+ enter_block_scope(&mut genv);
170
+
171
+ assert_eq!(genv.scope_manager.lookup_var("outer"), Some(VertexId(100)));
172
+
173
+ exit_block_scope(&mut genv);
174
+ }
175
+ }
@@ -14,14 +14,15 @@ pub fn install_method_call(
14
14
  genv: &mut GlobalEnv,
15
15
  recv_vtx: VertexId,
16
16
  method_name: String,
17
+ arg_vtxs: Vec<VertexId>,
17
18
  location: Option<SourceLocation>,
18
19
  ) -> VertexId {
19
20
  // Create Vertex for return value
20
21
  let ret_vtx = genv.new_vertex();
21
22
 
22
- // Create MethodCallBox with location
23
+ // Create MethodCallBox with location and argument vertices
23
24
  let box_id = genv.alloc_box_id();
24
- let call_box = MethodCallBox::new(box_id, recv_vtx, method_name, ret_vtx, location);
25
+ let call_box = MethodCallBox::new(box_id, recv_vtx, method_name, ret_vtx, arg_vtxs, location);
25
26
  genv.register_box(box_id, Box::new(call_box));
26
27
 
27
28
  ret_vtx
@@ -37,7 +38,8 @@ mod tests {
37
38
  let mut genv = GlobalEnv::new();
38
39
 
39
40
  let recv_vtx = genv.new_source(Type::string());
40
- let ret_vtx = install_method_call(&mut genv, recv_vtx, "upcase".to_string(), None);
41
+ let ret_vtx =
42
+ install_method_call(&mut genv, recv_vtx, "upcase".to_string(), vec![], None);
41
43
 
42
44
  // Return vertex should exist
43
45
  assert!(genv.get_vertex(ret_vtx).is_some());
@@ -48,7 +50,8 @@ mod tests {
48
50
  let mut genv = GlobalEnv::new();
49
51
 
50
52
  let recv_vtx = genv.new_source(Type::string());
51
- let _ret_vtx = install_method_call(&mut genv, recv_vtx, "upcase".to_string(), None);
53
+ let _ret_vtx =
54
+ install_method_call(&mut genv, recv_vtx, "upcase".to_string(), vec![], None);
52
55
 
53
56
  // Box should be added
54
57
  assert_eq!(genv.box_count(), 1);