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
56 changes: 52 additions & 4 deletions rust/src/analyzer/definitions.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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();
}
Expand All @@ -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::*;
Expand All @@ -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();
Expand All @@ -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);
}
}
87 changes: 86 additions & 1 deletion rust/src/analyzer/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -197,6 +205,21 @@ impl<'a> AstInstaller<'a> {
None
}

/// Install module definition
fn install_module_node(&mut self, module_node: &ruby_prism::ModuleNode) -> Option<VertexId> {
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<VertexId> {
let method_name = String::from_utf8_lossy(def_node.name().as_slice()).to_string();
Expand Down Expand Up @@ -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);
}
}
15 changes: 13 additions & 2 deletions rust/src/env/global_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading