diff --git a/rust/src/analyzer/definitions.rs b/rust/src/analyzer/definitions.rs index a4162ad..7398dc8 100644 --- a/rust/src/analyzer/definitions.rs +++ b/rust/src/analyzer/definitions.rs @@ -1,9 +1,10 @@ -//! Definition Handlers - Processing Ruby class/method definitions +//! Definition Handlers - Processing Ruby class/method/module definitions //! //! This module is responsible for: //! - Class definition scope management (class Foo ... end) -//! - Method definition scope management (def bar ... end) -//! - Extracting class names from AST nodes +//! - Module definition scope management (module Bar ... end) +//! - Method definition scope management (def baz ... end) +//! - Extracting class/module names from AST nodes use crate::env::GlobalEnv; @@ -12,12 +13,17 @@ pub fn install_class(genv: &mut GlobalEnv, class_name: String) { genv.enter_class(class_name); } +/// Install module definition +pub fn install_module(genv: &mut GlobalEnv, module_name: String) { + genv.enter_module(module_name); +} + /// Install method definition pub fn install_method(genv: &mut GlobalEnv, method_name: String) { genv.enter_method(method_name); } -/// Exit current scope (class or method) +/// Exit current scope (class, module, or method) pub fn exit_scope(genv: &mut GlobalEnv) { genv.exit_scope(); } @@ -31,6 +37,15 @@ pub fn extract_class_name(class_node: &ruby_prism::ClassNode) -> String { } } +/// Extract module name from ModuleNode +pub fn extract_module_name(module_node: &ruby_prism::ModuleNode) -> String { + if let Some(constant_read) = module_node.constant_path().as_constant_read_node() { + String::from_utf8_lossy(constant_read.name().as_slice()).to_string() + } else { + "UnknownModule".to_string() + } +} + #[cfg(test)] mod tests { use super::*; @@ -49,6 +64,20 @@ mod tests { assert_eq!(genv.scope_manager.current_class_name(), None); } + #[test] + fn test_enter_exit_module_scope() { + let mut genv = GlobalEnv::new(); + + install_module(&mut genv, "Utils".to_string()); + assert_eq!( + genv.scope_manager.current_module_name(), + Some("Utils".to_string()) + ); + + exit_scope(&mut genv); + assert_eq!(genv.scope_manager.current_module_name(), None); + } + #[test] fn test_nested_method_scope() { let mut genv = GlobalEnv::new(); @@ -67,4 +96,23 @@ mod tests { assert_eq!(genv.scope_manager.current_class_name(), None); } + + #[test] + fn test_method_in_module() { + let mut genv = GlobalEnv::new(); + + install_module(&mut genv, "Helpers".to_string()); + install_method(&mut genv, "format".to_string()); + + // Should find module context from within method + assert_eq!( + genv.scope_manager.current_module_name(), + Some("Helpers".to_string()) + ); + + exit_scope(&mut genv); // exit method + exit_scope(&mut genv); // exit module + + assert_eq!(genv.scope_manager.current_module_name(), None); + } } diff --git a/rust/src/analyzer/install.rs b/rust/src/analyzer/install.rs index d1bed24..2412607 100644 --- a/rust/src/analyzer/install.rs +++ b/rust/src/analyzer/install.rs @@ -9,7 +9,10 @@ use crate::graph::{BlockParameterTypeBox, ChangeSet, VertexId}; use ruby_prism::Node; use super::blocks::{enter_block_scope, exit_block_scope, install_block_parameter}; -use super::definitions::{exit_scope, extract_class_name, install_class, install_method}; +use super::definitions::{ + exit_scope, extract_class_name, extract_module_name, install_class, install_method, + install_module, +}; use super::dispatch::{ dispatch_needs_child, dispatch_simple, finish_ivar_write, finish_local_var_write, finish_method_call, DispatchResult, NeedsChildKind, @@ -45,6 +48,11 @@ impl<'a> AstInstaller<'a> { return self.install_class_node(&class_node); } + // Module definition + if let Some(module_node) = node.as_module_node() { + return self.install_module_node(&module_node); + } + // Method definition if let Some(def_node) = node.as_def_node() { return self.install_def_node(&def_node); @@ -197,6 +205,21 @@ impl<'a> AstInstaller<'a> { None } + /// Install module definition + fn install_module_node(&mut self, module_node: &ruby_prism::ModuleNode) -> Option { + let module_name = extract_module_name(module_node); + install_module(self.genv, module_name); + + if let Some(body) = module_node.body() { + if let Some(statements) = body.as_statements_node() { + self.install_statements(&statements); + } + } + + exit_scope(self.genv); + None + } + /// Install method definition fn install_def_node(&mut self, def_node: &ruby_prism::DefNode) -> Option { let method_name = String::from_utf8_lossy(def_node.name().as_slice()).to_string(); @@ -487,4 +510,66 @@ y = x.upcase assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "String"); assert_eq!(genv.get_vertex(y_vtx).unwrap().show(), "String"); } + + #[test] + fn test_install_module_with_method() { + let source = r#" +module Utils + def helper + x = "test" + end +end +"#; + let parse_result = parse_ruby_source(source, "test.rb".to_string()).unwrap(); + + let mut genv = GlobalEnv::new(); + let mut lenv = LocalEnv::new(); + let mut installer = AstInstaller::new(&mut genv, &mut lenv, source); + + let root = parse_result.node(); + if let Some(program_node) = root.as_program_node() { + let statements = program_node.statements(); + for stmt in &statements.body() { + installer.install_node(&stmt); + } + } + + installer.finish(); + + // After processing, we should be back at top-level scope + assert_eq!(genv.scope_manager.current_module_name(), None); + assert_eq!(genv.scope_manager.current_class_name(), None); + } + + #[test] + fn test_install_nested_module_class() { + let source = r#" +module Api + class User + def greet + name = "hello" + end + end +end +"#; + let parse_result = parse_ruby_source(source, "test.rb".to_string()).unwrap(); + + let mut genv = GlobalEnv::new(); + let mut lenv = LocalEnv::new(); + let mut installer = AstInstaller::new(&mut genv, &mut lenv, source); + + let root = parse_result.node(); + if let Some(program_node) = root.as_program_node() { + let statements = program_node.statements(); + for stmt in &statements.body() { + installer.install_node(&stmt); + } + } + + installer.finish(); + + // After processing, we should be back at top-level scope + assert_eq!(genv.scope_manager.current_module_name(), None); + assert_eq!(genv.scope_manager.current_class_name(), None); + } } diff --git a/rust/src/env/global_env.rs b/rust/src/env/global_env.rs index d397fde..d7e0908 100644 --- a/rust/src/env/global_env.rs +++ b/rust/src/env/global_env.rs @@ -187,12 +187,23 @@ impl GlobalEnv { scope_id } + /// Enter a module scope + pub fn enter_module(&mut self, name: String) -> ScopeId { + let scope_id = self.scope_manager.new_scope(ScopeKind::Module { name }); + self.scope_manager.enter_scope(scope_id); + scope_id + } + /// Enter a method scope pub fn enter_method(&mut self, name: String) -> ScopeId { - let class_name = self.scope_manager.current_class_name(); + // Look for class or module context + let receiver_type = self + .scope_manager + .current_class_name() + .or_else(|| self.scope_manager.current_module_name()); let scope_id = self.scope_manager.new_scope(ScopeKind::Method { name, - receiver_type: class_name, + receiver_type, }); self.scope_manager.enter_scope(scope_id); scope_id diff --git a/rust/src/env/scope.rs b/rust/src/env/scope.rs index 599eaea..94aad46 100644 --- a/rust/src/env/scope.rs +++ b/rust/src/env/scope.rs @@ -1,11 +1,11 @@ use crate::graph::VertexId; use std::collections::HashMap; -/// スコープID +/// Scope ID #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ScopeId(pub usize); -/// スコープの種類 +/// Scope kind #[derive(Debug, Clone)] #[allow(dead_code)] pub enum ScopeKind { @@ -19,12 +19,12 @@ pub enum ScopeKind { }, Method { name: String, - receiver_type: Option, // レシーバーのクラス名 + receiver_type: Option, // Receiver class/module name }, Block, } -/// スコープ情報 +/// Scope information #[derive(Debug, Clone)] #[allow(dead_code)] pub struct Scope { @@ -32,13 +32,13 @@ pub struct Scope { pub kind: ScopeKind, pub parent: Option, - /// ローカル変数 + /// Local variables pub local_vars: HashMap, - /// インスタンス変数(クラス/メソッドスコープのみ) + /// Instance variables (class/module scope only) pub instance_vars: HashMap, - /// クラス変数(クラススコープのみ) + /// Class variables (class scope only) pub class_vars: HashMap, } @@ -55,28 +55,28 @@ impl Scope { } } - /// ローカル変数を追加 + /// Add local variable pub fn set_local_var(&mut self, name: String, vtx: VertexId) { self.local_vars.insert(name, vtx); } - /// ローカル変数を取得 + /// Get local variable pub fn get_local_var(&self, name: &str) -> Option { self.local_vars.get(name).copied() } - /// インスタンス変数を追加 + /// Add instance variable pub fn set_instance_var(&mut self, name: String, vtx: VertexId) { self.instance_vars.insert(name, vtx); } - /// インスタンス変数を取得 + /// Get instance variable pub fn get_instance_var(&self, name: &str) -> Option { self.instance_vars.get(name).copied() } } -/// スコープマネージャー +/// Scope manager #[derive(Debug)] pub struct ScopeManager { scopes: HashMap, @@ -99,7 +99,7 @@ impl ScopeManager { } } - /// 新しいスコープを作成 + /// Create a new scope pub fn new_scope(&mut self, kind: ScopeKind) -> ScopeId { let id = ScopeId(self.next_id); self.next_id += 1; @@ -110,12 +110,12 @@ impl ScopeManager { id } - /// スコープに入る + /// Enter a scope pub fn enter_scope(&mut self, scope_id: ScopeId) { self.current_scope = scope_id; } - /// スコープから出る + /// Exit current scope pub fn exit_scope(&mut self) { if let Some(scope) = self.scopes.get(&self.current_scope) { if let Some(parent) = scope.parent { @@ -124,27 +124,27 @@ impl ScopeManager { } } - /// 現在のスコープを取得 + /// Get current scope pub fn current_scope(&self) -> &Scope { self.scopes.get(&self.current_scope).unwrap() } - /// 現在のスコープを可変で取得 + /// Get current scope mutably pub fn current_scope_mut(&mut self) -> &mut Scope { self.scopes.get_mut(&self.current_scope).unwrap() } - /// スコープを取得 + /// Get scope by ID pub fn get_scope(&self, id: ScopeId) -> Option<&Scope> { self.scopes.get(&id) } - /// スコープを可変で取得 + /// Get scope by ID mutably pub fn get_scope_mut(&mut self, id: ScopeId) -> Option<&mut Scope> { self.scopes.get_mut(&id) } - /// 変数を現在のスコープまたは親スコープから検索 + /// Lookup variable in current scope or parent scopes pub fn lookup_var(&self, name: &str) -> Option { let mut current = Some(self.current_scope); @@ -162,13 +162,13 @@ impl ScopeManager { None } - /// インスタンス変数を現在のクラススコープから検索 + /// Lookup instance variable in enclosing class scope pub fn lookup_instance_var(&self, name: &str) -> Option { let mut current = Some(self.current_scope); while let Some(scope_id) = current { if let Some(scope) = self.scopes.get(&scope_id) { - // クラススコープまで遡る + // Walk up to class scope match &scope.kind { ScopeKind::Class { .. } => { return scope.get_instance_var(name); @@ -185,13 +185,13 @@ impl ScopeManager { None } - /// インスタンス変数を現在のクラススコープに設定 + /// Set instance variable in enclosing class scope pub fn set_instance_var_in_class(&mut self, name: String, vtx: VertexId) { let mut current = Some(self.current_scope); while let Some(scope_id) = current { if let Some(scope) = self.scopes.get(&scope_id) { - // クラススコープを見つけたら設定 + // Find class scope and set variable match &scope.kind { ScopeKind::Class { .. } => { if let Some(class_scope) = self.scopes.get_mut(&scope_id) { @@ -209,7 +209,7 @@ impl ScopeManager { } } - /// 現在のクラス名を取得 + /// Get current class name pub fn current_class_name(&self) -> Option { let mut current = Some(self.current_scope); @@ -226,6 +226,69 @@ impl ScopeManager { None } + + /// Get current module name + pub fn current_module_name(&self) -> Option { + let mut current = Some(self.current_scope); + + while let Some(scope_id) = current { + if let Some(scope) = self.scopes.get(&scope_id) { + if let ScopeKind::Module { name } = &scope.kind { + return Some(name.clone()); + } + current = scope.parent; + } else { + break; + } + } + + None + } + + /// Lookup instance variable in enclosing module scope + pub fn lookup_instance_var_in_module(&self, name: &str) -> Option { + let mut current = Some(self.current_scope); + + while let Some(scope_id) = current { + if let Some(scope) = self.scopes.get(&scope_id) { + match &scope.kind { + ScopeKind::Module { .. } => { + return scope.get_instance_var(name); + } + _ => { + current = scope.parent; + } + } + } else { + break; + } + } + + None + } + + /// Set instance variable in enclosing module scope + pub fn set_instance_var_in_module(&mut self, name: String, vtx: VertexId) { + let mut current = Some(self.current_scope); + + while let Some(scope_id) = current { + if let Some(scope) = self.scopes.get(&scope_id) { + match &scope.kind { + ScopeKind::Module { .. } => { + if let Some(module_scope) = self.scopes.get_mut(&scope_id) { + module_scope.set_instance_var(name, vtx); + } + return; + } + _ => { + current = scope.parent; + } + } + } else { + break; + } + } + } } #[cfg(test)] @@ -327,4 +390,59 @@ mod tests { // Should still find parent class name assert_eq!(sm.current_class_name(), Some("User".to_string())); } + + #[test] + fn test_scope_manager_module_scope() { + let mut sm = ScopeManager::new(); + + assert_eq!(sm.current_module_name(), None); + + let module_id = sm.new_scope(ScopeKind::Module { + name: "Utils".to_string(), + }); + sm.enter_scope(module_id); + + assert_eq!(sm.current_module_name(), Some("Utils".to_string())); + + // Enter method within module + let method_id = sm.new_scope(ScopeKind::Method { + name: "helper".to_string(), + receiver_type: Some("Utils".to_string()), + }); + sm.enter_scope(method_id); + + // Should still find parent module name + assert_eq!(sm.current_module_name(), Some("Utils".to_string())); + + sm.exit_scope(); // exit method + sm.exit_scope(); // exit module + + assert_eq!(sm.current_module_name(), None); + } + + #[test] + fn test_scope_manager_module_instance_var() { + let mut sm = ScopeManager::new(); + + let module_id = sm.new_scope(ScopeKind::Module { + name: "Config".to_string(), + }); + sm.enter_scope(module_id); + + // Set instance variable in module + sm.set_instance_var_in_module("@setting".to_string(), VertexId(100)); + + // Enter method within module + let method_id = sm.new_scope(ScopeKind::Method { + name: "get_setting".to_string(), + receiver_type: Some("Config".to_string()), + }); + sm.enter_scope(method_id); + + // Should find instance variable from module scope + assert_eq!( + sm.lookup_instance_var_in_module("@setting"), + Some(VertexId(100)) + ); + } }