Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Cargo.lock

# Ruby
lib/methodray/methodray.*
lib/methodray/methodray-cli*
lib/methodray/*.{so,bundle,dll}
*.gem
.bundle/
Expand Down
2 changes: 1 addition & 1 deletion lib/methodray.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require 'rbs'
require_relative 'methodray/version'
require_relative 'methodray/methodray' # ネイティブ拡張
require_relative 'methodray/methodray'

module MethodRay
class Error < StandardError; end
Expand Down
Binary file removed lib/methodray/methodray-cli
Binary file not shown.
94 changes: 94 additions & 0 deletions rust/src/analyzer/blocks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! Block Handlers - Processing Ruby blocks
//!
//! This module is responsible for:
//! - Processing BlockNode (e.g., `{ |x| x.to_s }` or `do |x| x.to_s end`)
//! - Registering block parameters as local variables
//! - Managing block scope

use crate::env::{GlobalEnv, LocalEnv, ScopeKind};
use crate::graph::VertexId;

use super::parameters::install_required_parameter;

/// Enter a new block scope
///
/// Creates a new scope for the block and enters it.
/// Block scopes inherit variables from parent scopes.
pub fn enter_block_scope(genv: &mut GlobalEnv) {
let block_scope_id = genv.scope_manager.new_scope(ScopeKind::Block);
genv.scope_manager.enter_scope(block_scope_id);
}

/// Exit the current block scope
pub fn exit_block_scope(genv: &mut GlobalEnv) {
genv.scope_manager.exit_scope();
}

/// Install block parameters as local variables
///
/// Block parameters are registered as Bot (untyped) type since we don't
/// know what type will be passed from the iterator method.
///
/// # Example
/// ```ruby
/// [1, 2, 3].each { |x| x.to_s } # 'x' is a block parameter
/// ```
pub fn install_block_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
// Reuse required parameter logic (Bot type)
install_required_parameter(genv, lenv, name)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_enter_exit_block_scope() {
let mut genv = GlobalEnv::new();

let initial_scope_id = genv.scope_manager.current_scope().id;

enter_block_scope(&mut genv);
let block_scope_id = genv.scope_manager.current_scope().id;

// Should be in a new scope
assert_ne!(initial_scope_id, block_scope_id);

exit_block_scope(&mut genv);

// Should be back to initial scope
assert_eq!(genv.scope_manager.current_scope().id, initial_scope_id);
}

#[test]
fn test_install_block_parameter() {
let mut genv = GlobalEnv::new();
let mut lenv = LocalEnv::new();

enter_block_scope(&mut genv);

let vtx = install_block_parameter(&mut genv, &mut lenv, "x".to_string());

// Parameter should be registered in LocalEnv
assert_eq!(lenv.get_var("x"), Some(vtx));

exit_block_scope(&mut genv);
}

#[test]
fn test_block_inherits_parent_scope_vars() {
let mut genv = GlobalEnv::new();

// Set variable in top-level scope
genv.scope_manager
.current_scope_mut()
.set_local_var("outer".to_string(), VertexId(100));

enter_block_scope(&mut genv);

// Block should be able to lookup parent scope variables
assert_eq!(genv.scope_manager.lookup_var("outer"), Some(VertexId(100)));

exit_block_scope(&mut genv);
}
}
18 changes: 11 additions & 7 deletions rust/src/analyzer/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use crate::source_map::SourceLocation;
use ruby_prism::Node;

use super::calls::install_method_call;
use super::literals::install_literal;
use super::variables::{
install_ivar_read, install_ivar_write, install_local_var_read, install_local_var_write,
install_self,
Expand All @@ -34,10 +33,15 @@ pub enum NeedsChildKind<'a> {
receiver: Node<'a>,
method_name: String,
location: SourceLocation,
/// Optional block attached to the method call
block: Option<Node<'a>>,
},
}

/// First pass: check if node can be handled immediately without child processing
///
/// Note: Literals (including Array) are handled in install.rs via install_literal
/// because Array literals need child processing for element type inference.
pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -> DispatchResult {
// Instance variable read: @name
if let Some(ivar_read) = node.as_instance_variable_read_node() {
Expand All @@ -62,11 +66,6 @@ pub fn dispatch_simple(genv: &mut GlobalEnv, lenv: &mut LocalEnv, node: &Node) -
};
}

// Literals (String, Integer, Array, Hash, nil, true, false, Symbol)
if let Some(vtx) = install_literal(genv, node) {
return DispatchResult::Vertex(vtx);
}

DispatchResult::NotHandled
}

Expand All @@ -90,16 +89,21 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
});
}

// Method call: x.upcase
// Method call: x.upcase or x.each { |i| ... }
if let Some(call_node) = node.as_call_node() {
if let Some(receiver) = call_node.receiver() {
let method_name = String::from_utf8_lossy(call_node.name().as_slice()).to_string();
let location =
SourceLocation::from_prism_location_with_source(&node.location(), source);

// Get block if present (e.g., `x.each { |i| ... }`)
let block = call_node.block();

return Some(NeedsChildKind::MethodCall {
receiver,
method_name,
location,
block,
});
}
}
Expand Down
Loading