diff --git a/rust/src/analyzer/install.rs b/rust/src/analyzer/install.rs index 2412607..fa37d7e 100644 --- a/rust/src/analyzer/install.rs +++ b/rust/src/analyzer/install.rs @@ -6,6 +6,7 @@ use crate::env::{GlobalEnv, LocalEnv}; use crate::graph::{BlockParameterTypeBox, ChangeSet, VertexId}; +use crate::types::Type; use ruby_prism::Node; use super::blocks::{enter_block_scope, exit_block_scope, install_block_parameter}; @@ -84,7 +85,7 @@ impl<'a> AstInstaller<'a> { /// Install literal node /// - /// Handles all literals including Array with element type inference + /// Handles all literals including Array and Hash with element type inference fn install_literal_node(&mut self, node: &Node) -> Option { // Array literals need special handling for element type inference if node.as_array_node().is_some() { @@ -92,7 +93,18 @@ impl<'a> AstInstaller<'a> { return self.install_array_literal_elements(elements); } - // Other literals (String, Integer, Hash, nil, true, false, Symbol) + // Hash literals need special handling for key/value type inference + if node.as_hash_node().is_some() { + let elements: Vec = node.as_hash_node().unwrap().elements().iter().collect(); + return self.install_hash_literal_elements(elements); + } + + // Range literals need special handling for element type inference + if let Some(range_node) = node.as_range_node() { + return self.install_range_literal(&range_node); + } + + // Other literals (String, Integer, nil, true, false, Symbol) install_literal(self.genv, node) } @@ -133,6 +145,105 @@ impl<'a> AstInstaller<'a> { Some(self.genv.new_source(array_type)) } + /// Install hash literal with element type inference + fn install_hash_literal_elements(&mut self, elements: Vec) -> Option { + use crate::types::Type; + use std::collections::HashSet; + + if elements.is_empty() { + return Some(self.genv.new_source(Type::hash())); + } + + let mut key_types: HashSet = HashSet::new(); + let mut value_types: HashSet = HashSet::new(); + + for element in &elements { + if let Some(assoc_node) = element.as_assoc_node() { + // Infer key type + if let Some(key_vtx) = self.install_node(&assoc_node.key()) { + if let Some(source) = self.genv.get_source(key_vtx) { + key_types.insert(source.ty.clone()); + } else if let Some(vertex) = self.genv.get_vertex(key_vtx) { + for ty in vertex.types.keys() { + key_types.insert(ty.clone()); + } + } + } + + // Infer value type + if let Some(value_vtx) = self.install_node(&assoc_node.value()) { + if let Some(source) = self.genv.get_source(value_vtx) { + value_types.insert(source.ty.clone()); + } else if let Some(vertex) = self.genv.get_vertex(value_vtx) { + for ty in vertex.types.keys() { + value_types.insert(ty.clone()); + } + } + } + } + } + + let hash_type = if key_types.is_empty() || value_types.is_empty() { + Type::hash() + } else { + // Build key type (single type or union) + let key_type = if key_types.len() == 1 { + key_types.into_iter().next().unwrap() + } else { + let types_vec: Vec = key_types.into_iter().collect(); + Type::Union(types_vec) + }; + + // Build value type (single type or union) + let value_type = if value_types.len() == 1 { + value_types.into_iter().next().unwrap() + } else { + let types_vec: Vec = value_types.into_iter().collect(); + Type::Union(types_vec) + }; + + Type::hash_of(key_type, value_type) + }; + + Some(self.genv.new_source(hash_type)) + } + + /// Install range literal with element type inference + fn install_range_literal(&mut self, range_node: &ruby_prism::RangeNode) -> Option { + // Try to infer element type from left or right endpoint + let element_type = if let Some(left) = range_node.left() { + self.infer_range_element_type(&left) + } else if let Some(right) = range_node.right() { + self.infer_range_element_type(&right) + } else { + None + }; + + let range_type = match element_type { + Some(ty) => Type::range_of(ty), + None => Type::range(), + }; + + Some(self.genv.new_source(range_type)) + } + + /// Infer element type from a range endpoint node + fn infer_range_element_type(&mut self, node: &Node) -> Option { + // Install the node and get its type + if let Some(vtx) = self.install_node(node) { + if let Some(source) = self.genv.get_source(vtx) { + return Some(source.ty.clone()); + } + if let Some(vertex) = self.genv.get_vertex(vtx) { + // Get first type from vertex (simplified) + if let Some(ty) = vertex.types.keys().next() { + return Some(ty.clone()); + } + } + } + None + } + /// Process nodes that need child evaluation first fn process_needs_child(&mut self, kind: NeedsChildKind) -> Option { match kind { diff --git a/rust/src/analyzer/literals.rs b/rust/src/analyzer/literals.rs index 60e0d89..0451d4a 100644 --- a/rust/src/analyzer/literals.rs +++ b/rust/src/analyzer/literals.rs @@ -1,11 +1,11 @@ //! Literal Handlers - Processing Ruby literal values //! //! This module is responsible for: -//! - String, Integer, Float, Hash, Regexp, Range literals +//! - String, Integer, Float, Regexp literals //! - nil, true, false, Symbol literals //! - Creating Source vertices with fixed types //! -//! Note: Array literals are handled in install.rs for element type inference +//! Note: Array, Hash, and Range literals are handled in install.rs for element type inference use crate::env::GlobalEnv; use crate::graph::VertexId; @@ -14,7 +14,7 @@ use ruby_prism::Node; /// Install literal nodes and return their VertexId /// -/// Note: Array literals are NOT handled here because they require +/// Note: Array and Hash literals are NOT handled here because they require /// child processing for element type inference. See install.rs. pub fn install_literal(genv: &mut GlobalEnv, node: &Node) -> Option { // "hello" @@ -32,11 +32,6 @@ pub fn install_literal(genv: &mut GlobalEnv, node: &Node) -> Option { return Some(genv.new_source(Type::float())); } - // {a: 1} - if node.as_hash_node().is_some() { - return Some(genv.new_source(Type::hash())); - } - // nil if node.as_nil_node().is_some() { return Some(genv.new_source(Type::Nil)); @@ -62,11 +57,6 @@ pub fn install_literal(genv: &mut GlobalEnv, node: &Node) -> Option { return Some(genv.new_source(Type::regexp())); } - // 1..5, "a".."z" (Range literal) - if node.as_range_node().is_some() { - return Some(genv.new_source(Type::range())); - } - None } @@ -107,12 +97,4 @@ mod tests { let vtx = genv.new_source(Type::regexp()); assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Regexp"); } - - #[test] - fn test_install_range_literal() { - let mut genv = GlobalEnv::new(); - - let vtx = genv.new_source(Type::range()); - assert_eq!(genv.get_source(vtx).unwrap().ty.show(), "Range"); - } } diff --git a/rust/src/analyzer/tests/integration_test.rs b/rust/src/analyzer/tests/integration_test.rs index a30f310..f31c5ab 100644 --- a/rust/src/analyzer/tests/integration_test.rs +++ b/rust/src/analyzer/tests/integration_test.rs @@ -546,7 +546,7 @@ fn test_range_literal_basic() { let (genv, lenv) = analyze(source); let x_vtx = lenv.get_var("x").unwrap(); - assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range"); + assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range[Integer]"); } #[test] @@ -556,7 +556,7 @@ fn test_range_literal_exclusive() { let (genv, lenv) = analyze(source); let x_vtx = lenv.get_var("x").unwrap(); - assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range"); + assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range[Integer]"); } #[test] @@ -566,7 +566,64 @@ fn test_range_literal_string() { let (genv, lenv) = analyze(source); let x_vtx = lenv.get_var("x").unwrap(); - assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range"); + assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range[String]"); +} + +#[test] +fn test_range_generic_float() { + let source = r#"x = 1.0..5.0"#; + + let (genv, lenv) = analyze(source); + + let x_vtx = lenv.get_var("x").unwrap(); + assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Range[Float]"); +} + +// ============================================ +// Nested Generic Array Tests +// ============================================ + +#[test] +fn test_nested_array_integer() { + let source = r#"x = [[1, 2], [3]]"#; + + let (genv, lenv) = analyze(source); + + let x_vtx = lenv.get_var("x").unwrap(); + assert_eq!( + genv.get_vertex(x_vtx).unwrap().show(), + "Array[Array[Integer]]" + ); +} + +#[test] +fn test_deeply_nested_array() { + let source = r#"x = [[[1]]]"#; + + let (genv, lenv) = analyze(source); + + let x_vtx = lenv.get_var("x").unwrap(); + assert_eq!( + genv.get_vertex(x_vtx).unwrap().show(), + "Array[Array[Array[Integer]]]" + ); +} + +#[test] +fn test_nested_array_mixed() { + let source = r#"x = [[1], ["a"]]"#; + + let (genv, lenv) = analyze(source); + + let x_vtx = lenv.get_var("x").unwrap(); + // Union type order may vary, so check for either order + let result = genv.get_vertex(x_vtx).unwrap().show(); + assert!( + result == "Array[Array[Integer] | Array[String]]" + || result == "Array[Array[String] | Array[Integer]]", + "Expected nested array with union, got: {}", + result + ); } #[test] @@ -604,3 +661,73 @@ b = x.size let b_vtx = lenv.get_var("b").unwrap(); assert_eq!(genv.get_vertex(b_vtx).unwrap().show(), "Integer"); } + +// ============================================ +// Hash Generic Type Tests +// ============================================ + +#[test] +fn test_hash_symbol_integer() { + let source = r#"x = { a: 1, b: 2 }"#; + + let (genv, lenv) = analyze(source); + + let x_vtx = lenv.get_var("x").unwrap(); + assert_eq!( + genv.get_vertex(x_vtx).unwrap().show(), + "Hash[Symbol, Integer]" + ); +} + +#[test] +fn test_hash_string_string() { + let source = r#"x = { "k" => "v" }"#; + + let (genv, lenv) = analyze(source); + + let x_vtx = lenv.get_var("x").unwrap(); + assert_eq!( + genv.get_vertex(x_vtx).unwrap().show(), + "Hash[String, String]" + ); +} + +#[test] +fn test_hash_mixed_values() { + let source = r#"x = { a: 1, b: "x" }"#; + + let (genv, lenv) = analyze(source); + + let x_vtx = lenv.get_var("x").unwrap(); + let result = genv.get_vertex(x_vtx).unwrap().show(); + // Union type order may vary + assert!( + result == "Hash[Symbol, Integer | String]" + || result == "Hash[Symbol, String | Integer]", + "Expected Hash with union value type, got: {}", + result + ); +} + +#[test] +fn test_hash_empty() { + let source = r#"x = {}"#; + + let (genv, lenv) = analyze(source); + + let x_vtx = lenv.get_var("x").unwrap(); + assert_eq!(genv.get_vertex(x_vtx).unwrap().show(), "Hash"); +} + +#[test] +fn test_hash_nested() { + let source = r#"x = { a: [1] }"#; + + let (genv, lenv) = analyze(source); + + let x_vtx = lenv.get_var("x").unwrap(); + assert_eq!( + genv.get_vertex(x_vtx).unwrap().show(), + "Hash[Symbol, Array[Integer]]" + ); +} diff --git a/rust/src/types.rs b/rust/src/types.rs index 081592b..4e6658f 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -269,6 +269,14 @@ impl Type { type_args: vec![key_type, value_type], } } + + /// Create a generic Range type: Range[element_type] + pub fn range_of(element_type: Type) -> Self { + Type::Generic { + name: QualifiedName::simple("Range"), + type_args: vec![element_type], + } + } } #[cfg(test)]