diff --git a/examples/pattern-rules.myc b/examples/pattern-rules.myc new file mode 100644 index 0000000..4b5c5b8 --- /dev/null +++ b/examples/pattern-rules.myc @@ -0,0 +1,20 @@ +(do +(def val1 4) ; korrekt +(def val2 [4 5]) ; korrekt +(def [val3] 5) ; falsch +(def [val4] [5]) ; korrekt + +(def [[x1 [y1 y3]] z1] [[1 [2 3]] 4]) ; korrekt +(def [[x2 [y2 y4]] z2] [1 2 3 4]) ; falsch +;(def [[x [y1 y2]] z] [1] [2 3] 4) ; falsch +;(def [[x [y1 y2]] z] [1 [2 3]] 4) ; falsch + +(def f (fn [[x [y1 y2]] z] (+ x y1 y2 z))) + +(f [1 [2 3]] 4) ; korrekt +(f [1 2 3 4]) ; falsch +(f [1 2 [3] 4]) ; falsch +(f 1 2 3 4) ; falsch +(f 1 [2 [3]] 4) ; falsch +(f [[1 2] [3 4]]) ; falsch +) diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index fe6a705..3e6c7d7 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -184,6 +184,21 @@ impl TypeChecker { ) } BoundKind::Tuple { elements } => { + match specialized_ty { + StaticType::Any + | StaticType::Tuple(_) + | StaticType::Vector(_, _) + | StaticType::Matrix(_, _) + | StaticType::List(_) + | StaticType::Record(_) => {} + _ => { + return Err(format!( + "Cannot destructure type {} as a tuple/vector", + specialized_ty + )); + } + } + let mut typed_elements = Vec::new(); let mut elem_types = Vec::new(); @@ -191,6 +206,11 @@ impl TypeChecker { let sub_ty = match specialized_ty { StaticType::Tuple(t) => t.get(i).cloned().unwrap_or(StaticType::Any), StaticType::Vector(inner, _) => (**inner).clone(), + StaticType::Matrix(inner, _) => (**inner).clone(), // Matrix flat iteration or row? Wait. Matrix destructuring logic is tricky. But for now let's focus on Record. + StaticType::List(inner) => (**inner).clone(), + StaticType::Record(fields) => { + fields.get(i).map(|(_, ty)| ty.clone()).unwrap_or(StaticType::Any) + } _ => StaticType::Any, }; let t = self.check_params(el, &sub_ty, ctx)?; @@ -405,12 +425,37 @@ impl TypeChecker { BoundKind::Call { callee, args } => { let callee_typed = self.check_node(*callee, ctx)?; - let args_typed = self.check_node(*args, ctx)?; - let ret_ty = callee_typed - .ty - .resolve_call(&args_typed.ty) - .unwrap_or(StaticType::Any); + // Manually check args (Tuple) to prevent Vector/Matrix promotion + let args_typed = if let BoundKind::Tuple { elements } = args.kind { + let mut typed_elements = Vec::new(); + let mut elem_types = Vec::new(); + for e in elements { + let t = self.check_node(e, ctx)?; + elem_types.push(t.ty.clone()); + typed_elements.push(t); + } + Node { + identity: args.identity, + kind: BoundKind::Tuple { + elements: typed_elements, + }, + ty: StaticType::Tuple(elem_types), + } + } else { + // Should not happen as parser wraps args in Tuple, but fallback safely + self.check_node(*args, ctx)? + }; + + let ret_ty = match callee_typed.ty.resolve_call(&args_typed.ty) { + Some(ty) => ty, + None => { + return Err(format!( + "Invalid arguments for function call. Expected {}, got {}", + callee_typed.ty, args_typed.ty + )); + } + }; ( BoundKind::Call { @@ -422,7 +467,25 @@ impl TypeChecker { } BoundKind::Again { args } => { - let args_typed = self.check_node(*args, ctx)?; + // Manually check args (Tuple) to prevent Vector/Matrix promotion + let args_typed = if let BoundKind::Tuple { elements } = args.kind { + let mut typed_elements = Vec::new(); + let mut elem_types = Vec::new(); + for e in elements { + let t = self.check_node(e, ctx)?; + elem_types.push(t.ty.clone()); + typed_elements.push(t); + } + Node { + identity: args.identity, + kind: BoundKind::Tuple { + elements: typed_elements, + }, + ty: StaticType::Tuple(elem_types), + } + } else { + self.check_node(*args, ctx)? + }; if let Some(expected_ty) = &ctx.current_params_ty && !expected_ty.is_assignable_from(&args_typed.ty) @@ -542,6 +605,44 @@ mod tests { env.compile(source).unwrap() } + #[test] + fn test_destructuring_scalar_error() { + let env = crate::ast::environment::Environment::new(); + let result = env.compile("(def [x] 5)"); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + "Cannot destructure type int as a tuple/vector" + ); + } + + #[test] + fn test_call_argument_mismatch() { + let env = crate::ast::environment::Environment::new(); + // fn takes 1 arg, called with 0 + let result = env.compile("(do (def f (fn [x] x)) (f))"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("Invalid arguments for function call")); + } + + #[test] + fn test_destructuring_vector_args() { + let env = crate::ast::environment::Environment::new(); + // ((fn [[x y]] (+ x y)) [10 20]) -> 30 + let result = env.compile("((fn [[x y]] (+ x y)) [10 20])"); + assert!(result.is_ok()); + assert_eq!(get_ret_type(&result.unwrap()), StaticType::Int); + } + + #[test] + fn test_destructuring_record_args() { + let env = crate::ast::environment::Environment::new(); + // ((fn [[x y]] (+ x y)) {:a 5 :b 7}) -> 12 + let result = env.compile("((fn [[x y]] (+ x y)) {:a 5 :b 7})"); + assert!(result.is_ok()); + assert_eq!(get_ret_type(&result.unwrap()), StaticType::Int); + } + fn get_ret_type(node: &TypedNode) -> StaticType { if let StaticType::Function(sig) = &node.ty { sig.ret.clone() diff --git a/src/ast/rtl/core.rs b/src/ast/rtl/core.rs index 7dece9a..a3f1c0a 100644 --- a/src/ast/rtl/core.rs +++ b/src/ast/rtl/core.rs @@ -38,6 +38,11 @@ fn register_arithmetic(env: &Environment) { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime, }, + // Variadic + Signature { + params: StaticType::Any, + ret: StaticType::Any, + }, ]); env.register_native_fn("+", add_ty, Purity::Pure, |args| { if args.len() == 2 { @@ -98,6 +103,11 @@ fn register_arithmetic(env: &Environment) { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime, }, + // Variadic + Signature { + params: StaticType::Any, + ret: StaticType::Any, + }, ]); env.register_native_fn("-", sub_ty, Purity::Pure, |args| { if args.is_empty() { @@ -151,6 +161,11 @@ fn register_arithmetic(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float, }, + // Variadic + Signature { + params: StaticType::Any, + ret: StaticType::Any, + }, ]); env.register_native_fn("*", mul_ty, Purity::Pure, |args| { if args.len() == 2 { diff --git a/src/ast/types.rs b/src/ast/types.rs index f7c56db..9db81fd 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -259,6 +259,16 @@ impl StaticType { .zip(other_elements.iter()) .all(|(e, o)| e.is_assignable_from(o)) } + // Record to Tuple (Destructuring by values) + (StaticType::Tuple(elements), StaticType::Record(fields)) => { + if elements.len() != fields.len() { + return false; + } + elements + .iter() + .zip(fields.iter().map(|(_, ty)| ty)) + .all(|(e, o)| e.is_assignable_from(o)) + } // A Matrix is a Vector (of Vectors/Matrices) (StaticType::Vector(inner, len), StaticType::Matrix(m_inner, shape)) => { if shape.is_empty() || shape[0] != *len {