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
127 changes: 104 additions & 23 deletions rust/src/analyzer/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,121 @@
//! - Managing block scope

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

use super::parameters::{install_optional_parameter, install_required_parameter, install_rest_parameter};

/// Process block node
pub(crate) fn process_block_node(
genv: &mut GlobalEnv,
lenv: &mut LocalEnv,
changes: &mut ChangeSet,
source: &str,
block_node: &ruby_prism::BlockNode,
) -> Option<VertexId> {
process_block_node_with_params(genv, lenv, changes, source, block_node);
None
}

/// Process block node and return block parameter vertex IDs
pub(crate) fn process_block_node_with_params(
genv: &mut GlobalEnv,
lenv: &mut LocalEnv,
changes: &mut ChangeSet,
source: &str,
block_node: &ruby_prism::BlockNode,
) -> Vec<VertexId> {
enter_block_scope(genv);

let mut param_vtxs = Vec::new();

if let Some(params_node) = block_node.parameters() {
if let Some(block_params) = params_node.as_block_parameters_node() {
param_vtxs =
install_block_parameters_with_vtxs(genv, lenv, changes, source, &block_params);
}
}

if let Some(body) = block_node.body() {
if let Some(statements) = body.as_statements_node() {
super::install::install_statements(genv, lenv, changes, source, &statements);
} else {
super::install::install_node(genv, lenv, changes, source, &body);
}
}

exit_block_scope(genv);

use super::parameters::install_required_parameter;
param_vtxs
}

/// Install block parameters and return their vertex IDs
fn install_block_parameters_with_vtxs(
genv: &mut GlobalEnv,
lenv: &mut LocalEnv,
changes: &mut ChangeSet,
source: &str,
block_params: &ruby_prism::BlockParametersNode,
) -> Vec<VertexId> {
let mut vtxs = Vec::new();

if let Some(params) = block_params.parameters() {
// Required parameters (most common in blocks)
for node in params.requireds().iter() {
if let Some(req_param) = node.as_required_parameter_node() {
let name = String::from_utf8_lossy(req_param.name().as_slice()).to_string();
let vtx = install_block_parameter(genv, lenv, name);
vtxs.push(vtx);
}
}

// Optional parameters: { |x = 1| ... }
for node in params.optionals().iter() {
if let Some(opt_param) = node.as_optional_parameter_node() {
let name = String::from_utf8_lossy(opt_param.name().as_slice()).to_string();
let default_value = opt_param.value();

if let Some(default_vtx) =
super::install::install_node(genv, lenv, changes, source, &default_value)
{
let vtx =
install_optional_parameter(genv, lenv, changes, name, default_vtx);
vtxs.push(vtx);
} else {
let vtx = install_block_parameter(genv, lenv, name);
vtxs.push(vtx);
}
}
}

// Rest parameter: { |*args| ... }
if let Some(rest_node) = params.rest() {
if let Some(rest_param) = rest_node.as_rest_parameter_node() {
if let Some(name_id) = rest_param.name() {
let name = String::from_utf8_lossy(name_id.as_slice()).to_string();
let vtx = install_rest_parameter(genv, lenv, name);
vtxs.push(vtx);
}
}
}
}

vtxs
}

/// 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) {
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) {
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 block parameter as a local variable (Bot type)
fn install_block_parameter(genv: &mut GlobalEnv, lenv: &mut LocalEnv, name: String) -> VertexId {
install_required_parameter(genv, lenv, name)
}

Expand All @@ -51,12 +137,10 @@ mod tests {
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);
}

Expand All @@ -69,7 +153,6 @@ mod tests {

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);
Expand All @@ -79,14 +162,12 @@ mod tests {
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);
Expand Down
86 changes: 79 additions & 7 deletions rust/src/analyzer/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,110 @@
//! - Method definition scope management (def baz ... end)
//! - Extracting class/module names from AST nodes (including qualified names like Api::User)

use crate::env::GlobalEnv;
use crate::env::{GlobalEnv, LocalEnv};
use crate::graph::{ChangeSet, VertexId};
use ruby_prism::Node;

use super::install::install_statements;
use super::parameters::install_parameters;

/// Process class definition node
pub(crate) fn process_class_node(
genv: &mut GlobalEnv,
lenv: &mut LocalEnv,
changes: &mut ChangeSet,
source: &str,
class_node: &ruby_prism::ClassNode,
) -> Option<VertexId> {
let class_name = extract_class_name(class_node);
install_class(genv, class_name);

if let Some(body) = class_node.body() {
if let Some(statements) = body.as_statements_node() {
install_statements(genv, lenv, changes, source, &statements);
}
}

exit_scope(genv);
None
}

/// Process module definition node
pub(crate) fn process_module_node(
genv: &mut GlobalEnv,
lenv: &mut LocalEnv,
changes: &mut ChangeSet,
source: &str,
module_node: &ruby_prism::ModuleNode,
) -> Option<VertexId> {
let module_name = extract_module_name(module_node);
install_module(genv, module_name);

if let Some(body) = module_node.body() {
if let Some(statements) = body.as_statements_node() {
install_statements(genv, lenv, changes, source, &statements);
}
}

exit_scope(genv);
None
}

/// Process method definition node
pub(crate) fn process_def_node(
genv: &mut GlobalEnv,
lenv: &mut LocalEnv,
changes: &mut ChangeSet,
source: &str,
def_node: &ruby_prism::DefNode,
) -> Option<VertexId> {
let method_name = String::from_utf8_lossy(def_node.name().as_slice()).to_string();
install_method(genv, method_name);

// Process parameters BEFORE processing body
if let Some(params_node) = def_node.parameters() {
install_parameters(genv, lenv, changes, source, &params_node);
}

if let Some(body) = def_node.body() {
if let Some(statements) = body.as_statements_node() {
install_statements(genv, lenv, changes, source, &statements);
}
}

exit_scope(genv);
None
}

/// Install class definition
pub fn install_class(genv: &mut GlobalEnv, class_name: String) {
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) {
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) {
fn install_method(genv: &mut GlobalEnv, method_name: String) {
genv.enter_method(method_name);
}

/// Exit current scope (class, module, or method)
pub fn exit_scope(genv: &mut GlobalEnv) {
fn exit_scope(genv: &mut GlobalEnv) {
genv.exit_scope();
}

/// Extract class name from ClassNode
/// Supports both simple names (User) and qualified names (Api::V1::User)
pub fn extract_class_name(class_node: &ruby_prism::ClassNode) -> String {
fn extract_class_name(class_node: &ruby_prism::ClassNode) -> String {
extract_constant_path(&class_node.constant_path()).unwrap_or_else(|| "UnknownClass".to_string())
}

/// Extract module name from ModuleNode
/// Supports both simple names (Utils) and qualified names (Api::V1::Utils)
pub fn extract_module_name(module_node: &ruby_prism::ModuleNode) -> String {
fn extract_module_name(module_node: &ruby_prism::ModuleNode) -> String {
extract_constant_path(&module_node.constant_path())
.unwrap_or_else(|| "UnknownModule".to_string())
}
Expand Down
62 changes: 58 additions & 4 deletions rust/src/analyzer/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! and dispatches them to specialized handlers.

use crate::env::{GlobalEnv, LocalEnv};
use crate::graph::{ChangeSet, VertexId};
use crate::graph::{BlockParameterTypeBox, ChangeSet, VertexId};
use crate::source_map::SourceLocation;
use ruby_prism::Node;

Expand Down Expand Up @@ -115,13 +115,67 @@ pub fn dispatch_needs_child<'a>(node: &Node<'a>, source: &str) -> Option<NeedsCh
None
}

/// Process a node that needs child processing
///
/// This function handles the second phase of two-phase dispatch:
/// 1. `dispatch_needs_child` identifies the node kind and extracts data
/// 2. `process_needs_child` processes child nodes and completes the operation
pub(crate) fn process_needs_child(
genv: &mut GlobalEnv,
lenv: &mut LocalEnv,
changes: &mut ChangeSet,
source: &str,
kind: NeedsChildKind,
) -> Option<VertexId> {
match kind {
NeedsChildKind::IvarWrite { ivar_name, value } => {
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
Some(finish_ivar_write(genv, ivar_name, value_vtx))
}
NeedsChildKind::LocalVarWrite { var_name, value } => {
let value_vtx = super::install::install_node(genv, lenv, changes, source, &value)?;
Some(finish_local_var_write(genv, lenv, changes, var_name, value_vtx))
}
NeedsChildKind::MethodCall {
receiver,
method_name,
location,
block,
} => {
let recv_vtx = super::install::install_node(genv, lenv, changes, source, &receiver)?;

// Handle block if present (e.g., `x.each { |i| ... }`)
if let Some(block_node) = block {
if let Some(block) = block_node.as_block_node() {
let param_vtxs = super::blocks::process_block_node_with_params(
genv, lenv, changes, source, &block,
);

if !param_vtxs.is_empty() {
let box_id = genv.alloc_box_id();
let block_box = BlockParameterTypeBox::new(
box_id,
recv_vtx,
method_name.clone(),
param_vtxs,
);
genv.register_box(box_id, Box::new(block_box));
}
}
}

Some(finish_method_call(genv, recv_vtx, method_name, location))
}
}
}

/// Finish instance variable write after child is processed
pub fn finish_ivar_write(genv: &mut GlobalEnv, ivar_name: String, value_vtx: VertexId) -> VertexId {
fn finish_ivar_write(genv: &mut GlobalEnv, ivar_name: String, value_vtx: VertexId) -> VertexId {
install_ivar_write(genv, ivar_name, value_vtx)
}

/// Finish local variable write after child is processed
pub fn finish_local_var_write(
fn finish_local_var_write(
genv: &mut GlobalEnv,
lenv: &mut LocalEnv,
changes: &mut ChangeSet,
Expand All @@ -132,7 +186,7 @@ pub fn finish_local_var_write(
}

/// Finish method call after receiver is processed
pub fn finish_method_call(
fn finish_method_call(
genv: &mut GlobalEnv,
recv_vtx: VertexId,
method_name: String,
Expand Down
Loading