diff --git a/Cargo.lock b/Cargo.lock index ca9db30b..00d3ee42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1280,6 +1280,7 @@ dependencies = [ "md5", "ndc_lexer", "ndc_macros", + "ndc_parser", "num", "once_cell", "ordered-float", @@ -1313,6 +1314,17 @@ dependencies = [ "syn", ] +[[package]] +name = "ndc_parser" +version = "0.2.1" +dependencies = [ + "derive_more", + "itertools 0.14.0", + "ndc_lexer", + "num", + "thiserror", +] + [[package]] name = "nibble_vec" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 01df384f..3d9b5934 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["ndc_macros", "ndc_bin", "ndc_lib", "ndc_lsp", "ndc_lexer", "benches", "tests"] +members = ["ndc_macros", "ndc_bin", "ndc_lib", "ndc_lsp", "ndc_lexer", "ndc_parser", "benches", "tests"] [workspace.package] edition = "2024" @@ -19,6 +19,7 @@ factorial = "0.4.0" itertools = "0.14.0" ndc_lexer = { path = "ndc_lexer" } ndc_lib = { path = "ndc_lib" } +ndc_parser = { path = "ndc_parser" } ndc_lsp = { path = "ndc_lsp" } ndc_macros = { path = "ndc_macros" } num = "0.4.3" diff --git a/ndc_bin/src/diagnostic.rs b/ndc_bin/src/diagnostic.rs index c6e79f1a..5144dc89 100644 --- a/ndc_bin/src/diagnostic.rs +++ b/ndc_bin/src/diagnostic.rs @@ -1,6 +1,6 @@ use miette::{Diagnostic, LabeledSpan, SourceSpan}; -use ndc_lib::interpreter::InterpreterError; use ndc_lexer::Span; +use ndc_lib::interpreter::InterpreterError; use std::fmt; fn span_to_source_span(span: Span) -> SourceSpan { diff --git a/ndc_lib/Cargo.toml b/ndc_lib/Cargo.toml index 624c53e8..9fbe449b 100644 --- a/ndc_lib/Cargo.toml +++ b/ndc_lib/Cargo.toml @@ -13,6 +13,7 @@ factorial.workspace = true itertools.workspace = true ndc_lexer.workspace = true ndc_macros.workspace = true +ndc_parser.workspace = true num.workspace = true once_cell.workspace = true ordered-float.workspace = true diff --git a/ndc_lib/src/interpreter/evaluate/index.rs b/ndc_lib/src/interpreter/evaluate/index.rs index bff61c19..728824ee 100644 --- a/ndc_lib/src/interpreter/evaluate/index.rs +++ b/ndc_lib/src/interpreter/evaluate/index.rs @@ -15,8 +15,8 @@ use crate::{ ast::{Expression, ExpressionLocation}, interpreter::{function::FunctionCarrier, sequence::Sequence, value::Value}, }; -use ndc_lexer::Span; use itertools::Itertools; +use ndc_lexer::Span; use std::cell::RefCell; use std::cmp::min; use std::ops::IndexMut; @@ -223,13 +223,19 @@ pub fn get_at_index( } }; - let value = map.try_borrow().into_evaluation_result(span)?.get(&key).cloned(); + let value = map + .try_borrow() + .into_evaluation_result(span)? + .get(&key) + .cloned(); if let Some(value) = value { Ok(value) } else if let Some(default) = default { let default_value = produce_default_value(default, environment, span)?; - map.try_borrow_mut().into_evaluation_result(span)?.insert(key, default_value.clone()); + map.try_borrow_mut() + .into_evaluation_result(span)? + .insert(key, default_value.clone()); Ok(default_value) } else { Err(EvaluationError::key_not_found(&key, span).into()) @@ -249,17 +255,15 @@ pub(super) fn produce_default_value( span: Span, ) -> EvaluationResult { match default { - Value::Function(function) => { - match function.call_checked(&mut [], environment) { - Err(FunctionCarrier::FunctionTypeMismatch) => { - Err(FunctionCarrier::EvaluationError(EvaluationError::new( - "default function is not callable without arguments".to_string(), - span, - ))) - } - a => a, + Value::Function(function) => match function.call_checked(&mut [], environment) { + Err(FunctionCarrier::FunctionTypeMismatch) => { + Err(FunctionCarrier::EvaluationError(EvaluationError::new( + "default function is not callable without arguments".to_string(), + span, + ))) } - } + a => a, + }, value => Ok(value.clone()), } } diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index 3d7b1c50..7d84933c 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -9,9 +9,9 @@ use crate::interpreter::iterator::mut_value_to_iterator; use crate::interpreter::num::Number; use crate::interpreter::sequence::Sequence; use crate::interpreter::value::Value; -use ndc_lexer::Span; use index::{Offset, evaluate_as_index, get_at_index, set_at_index}; use itertools::Itertools; +use ndc_lexer::Span; use std::cell::RefCell; use std::fmt; use std::rc::Rc; @@ -165,7 +165,8 @@ pub(crate) fn evaluate_expression( } => { let mut lhs_value = evaluate_expression(lhs_expression, environment)?; let index = evaluate_as_index(index_expression, environment)?; - let value_at_index = get_at_index(&lhs_value, index.clone(), span, environment)?; + let value_at_index = + get_at_index(&lhs_value, index.clone(), span, environment)?; let right_value = evaluate_expression(r_value, environment)?; @@ -338,7 +339,7 @@ pub(crate) fn evaluate_expression( resolve_and_call(function, evaluated_args, environment, span)? } Expression::FunctionDeclaration { - parameters: arguments, + parameters, body, resolved_name, return_type, @@ -346,7 +347,11 @@ pub(crate) fn evaluate_expression( .. } => { let mut user_function = FunctionBody::Closure { - parameter_names: arguments.try_into_parameters()?, + parameter_names: parameters + .as_parameters() + .into_iter() + .map(|x| x.to_string()) + .collect(), body: *body.clone(), return_type: return_type.clone().unwrap_or_else(StaticType::unit), environment: environment.clone(), @@ -647,7 +652,6 @@ pub(crate) fn evaluate_expression( Ok(literal) } - fn declare_or_assign_variable( l_value: &Lvalue, value: Value, diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index d9a489e2..e68d6885 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -7,9 +7,9 @@ use crate::interpreter::evaluate::{ use crate::interpreter::num::{BinaryOperatorError, Number}; use crate::interpreter::sequence::Sequence; use crate::interpreter::value::Value; -use ndc_lexer::Span; use derive_builder::Builder; -use itertools::Itertools; +use ndc_lexer::Span; +pub use ndc_parser::{Parameter, StaticType, TypeSignature}; use std::cell::{BorrowError, BorrowMutError, RefCell}; use std::fmt; use std::hash::{Hash, Hasher}; @@ -322,528 +322,6 @@ impl FunctionBody { } } -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum TypeSignature { - Variadic, - Exact(Vec), -} - -impl TypeSignature { - /// Matches a list of `ValueTypes` to a type signature. It can return `None` if there is no match or - /// `Some(num)` where num is the sum of the distances of the types. The type `Int`, is distance 1 - /// away from `Number`, and `Number` is 1 distance from `Any`, then `Int` is distance 2 from `Any`. - pub fn calc_type_score(&self, types: &[StaticType]) -> Option { - match self { - Self::Variadic => Some(0), - Self::Exact(signature) => { - if types.len() == signature.len() { - let mut acc = 0; - for (a, b) in types.iter().zip(signature.iter()) { - let dist = if a == &b.type_name { - 0 - } else if a.is_subtype(&b.type_name) { - 1 - } else { - return None; - }; - acc += dist; - } - - return Some(acc); - } - - None - } - } - } - - fn arity(&self) -> Option { - match self { - Self::Variadic => None, - Self::Exact(args) => Some(args.len()), - } - } -} -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct Parameter { - pub name: String, - pub type_name: StaticType, -} - -impl Parameter { - pub fn new>(name: N, param_type: StaticType) -> Self { - Self { - name: name.into(), - type_name: param_type, - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum StaticType { - Any, - Bool, - Function { - parameters: Option>, - return_type: Box, - }, - Option(Box), - - // Numbers - Number, - Float, - Int, - Rational, - Complex, - - // Sequences List -> List - Sequence(Box), - List(Box), - String, - Tuple(Vec), - Map { - key: Box, - value: Box, - }, - Iterator(Box), - MinHeap(Box), - MaxHeap(Box), - Deque(Box), -} -impl StaticType { - /// Checks if `self` is a subtype of `other`. - /// - /// A type S is a subtype of T (S <: T) if a value of type S can be safely - /// used wherever a value of type T is expected. - /// - /// # Key Rules - /// - `Any` is the top type (supertype of all types) - /// - `Number` > `{Float, Int, Rational, Complex}` - /// - `Sequence` > sequence types with element type T - /// - Generic types are covariant in their type parameters - /// - Function parameters are **contravariant**, returns are **covariant** - /// - /// Note: this function probably doesn't handle variadic functions correctly - pub fn is_subtype(&self, other: &Self) -> bool { - // Any is the universal supertype - if matches!(other, Self::Any) { - return true; - } - - // Only Any satisfies the above; all other types fail here - if matches!(self, Self::Any) { - return false; - } - - #[allow(clippy::match_same_arms)] - #[allow(clippy::unnested_or_patterns)] - match (self, other) { - // Reflexivity: every type is a subtype of itself - _ if self == other => true, - - // Number hierarchy: all numeric types are subtypes of Number - (Self::Float | Self::Int | Self::Rational | Self::Complex, Self::Number) => true, - - // Sequence hierarchy: specific sequences are subtypes of Sequence - // where T is their element type (covariant) - (Self::List(s), Self::Sequence(t)) - | (Self::Iterator(s), Self::Sequence(t)) - | (Self::MinHeap(s), Self::Sequence(t)) - | (Self::MaxHeap(s), Self::Sequence(t)) - | (Self::Deque(s), Self::Sequence(t)) => s.is_subtype(t), - - // String is Sequence - (Self::String, Self::Sequence(t)) => Self::String.is_subtype(t.as_ref()), - - // Tuple is Sequence - (Self::Tuple(elems), Self::Sequence(t)) => { - Self::compute_tuple_element_type(elems).is_subtype(t) - } - - // Map is Sequence> - (Self::Map { key, value }, Self::Sequence(t)) => { - Self::Tuple(vec![key.as_ref().clone(), value.as_ref().clone()]) - .is_subtype(t.as_ref()) - } - - // Generic types are covariant: Container <: Container if S <: T - (Self::Option(s), Self::Option(t)) - | (Self::List(s), Self::List(t)) - | (Self::Iterator(s), Self::Iterator(t)) - | (Self::MinHeap(s), Self::MinHeap(t)) - | (Self::MaxHeap(s), Self::MaxHeap(t)) - | (Self::Deque(s), Self::Deque(t)) - | (Self::Sequence(s), Self::Sequence(t)) => s.is_subtype(t), - - // Tuples are covariant pointwise and must have the same arity - (Self::Tuple(s_elems), Self::Tuple(t_elems)) => { - s_elems.len() == t_elems.len() - && s_elems.iter().zip(t_elems).all(|(s, t)| s.is_subtype(t)) - } - - // Maps are covariant in both key and value type parameters - (Self::Map { key: k1, value: v1 }, Self::Map { key: k2, value: v2 }) => { - k1.is_subtype(k2) && v1.is_subtype(v2) - } - - // Functions: contravariant in parameters, covariant in return type - // F1 <: F2 iff params(F2) <: params(F1) AND return(F1) <: return(F2) - ( - Self::Function { - parameters: p1, - return_type: r1, - }, - Self::Function { - parameters: p2, - return_type: r2, - }, - ) => { - // Return type is covariant: must satisfy r1 <: r2 - let return_ok = r1.is_subtype(r2); - - // Parameters are contravariant: must satisfy p2 <: p1 (reversed!) - let params_ok = match (p1, p2) { - (None, None) => true, - // A function with specific params is a subtype of one with generic params - (Some(_), None) => true, - // Cannot substitute generic params with specific ones - (None, Some(_)) => false, - (Some(ps1), Some(ps2)) => { - ps1.len() == ps2.len() && - // Note the reversal: p2 must be subtype of p1! - ps1.iter() - .zip(ps2) - .all(|(p1, p2)| p2.is_subtype(p1)) - } - }; - - return_ok && params_ok - } - - _ => false, - } - } - - // - /// Computes the Least Upper Bound (join) of two types. - /// - /// The LUB is the most specific type that is a supertype of both inputs. - /// - /// # Examples - /// - `lub(Int, Float) = Number` - /// - `lub(List, List) = List` - /// - `lub(List, Iterator) = Sequence` - /// - `lub(Int, String) = Any` - pub fn lub(&self, other: &Self) -> Self { - // Any is the top type - if matches!(self, Self::Any) || matches!(other, Self::Any) { - return Self::Any; - } - - // Reflexivity: lub(T, T) = T - if self == other { - return self.clone(); - } - - // If one is a subtype of the other, return the supertype - if self.is_subtype(other) { - return other.clone(); - } - if other.is_subtype(self) { - return self.clone(); - } - - match (self, other) { - // Number type lattice: all numeric types join to Number - (Self::Float, Self::Int | Self::Rational | Self::Complex) - | (Self::Int, Self::Float | Self::Rational | Self::Complex) - | (Self::Rational, Self::Float | Self::Int | Self::Complex) - | (Self::Complex, Self::Float | Self::Int | Self::Rational) => Self::Number, - - // Covariant generic types: compute LUB pointwise - (Self::Option(s), Self::Option(t)) => Self::Option(Box::new(s.lub(t))), - (Self::List(s), Self::List(t)) => Self::List(Box::new(s.lub(t))), - (Self::Iterator(s), Self::Iterator(t)) => Self::Iterator(Box::new(s.lub(t))), - (Self::MinHeap(s), Self::MinHeap(t)) => Self::MinHeap(Box::new(s.lub(t))), - (Self::MaxHeap(s), Self::MaxHeap(t)) => Self::MaxHeap(Box::new(s.lub(t))), - (Self::Deque(s), Self::Deque(t)) => Self::Deque(Box::new(s.lub(t))), - (Self::Sequence(s), Self::Sequence(t)) => Self::Sequence(Box::new(s.lub(t))), - - // Maps are covariant in both parameters - (Self::Map { key: k1, value: v1 }, Self::Map { key: k2, value: v2 }) => Self::Map { - key: Box::new(k1.lub(k2)), - value: Box::new(v1.lub(v2)), - }, - - // Tuples of same arity: compute LUB pointwise - (Self::Tuple(e1), Self::Tuple(e2)) if e1.len() == e2.len() => { - Self::Tuple(e1.iter().zip(e2).map(|(a, b)| a.lub(b)).collect()) - } - - // Different sequence types: generalize to Sequence - _ if Self::is_sequence(self) && Self::is_sequence(other) => { - let elem1 = self.sequence_element_type().expect("must be seq"); - let elem2 = other.sequence_element_type().expect("must be seq"); - Self::Sequence(Box::new(elem1.lub(&elem2))) - } - - // Functions: covariant in return, contravariant in parameters - ( - Self::Function { - parameters: p1, - return_type: r1, - }, - Self::Function { - parameters: p2, - return_type: r2, - }, - ) => { - // Return type: covariant, so take LUB - let return_type = Box::new(r1.lub(r2)); - - // Parameters: contravariant, so we need GLB (greatest lower bound) - let parameters = match (p1, p2) { - (None, None) => None, - (Some(ps1), Some(ps2)) if ps1.len() == ps2.len() => { - Some(ps1.iter().zip(ps2).map(|(a, b)| a.glb(b)).collect()) - } - // Incompatible parameter lists - _ => None, - }; - - Self::Function { - parameters, - return_type, - } - } - - // No common supertype found: default to Any - _ => Self::Any, - } - } - - /// Computes the Greatest Lower Bound (meet) of two types. - /// - /// The GLB is the most general type that is a subtype of both inputs. - /// This is required for contravariant positions (e.g., function parameters). - /// - /// Note: Not all type pairs have a representable GLB in our type system. - /// In such cases, we return a conservative approximation. - fn glb(&self, other: &Self) -> Self { - // Any is the top type: glb(Any, T) = T - match (self, other) { - (Self::Any, t) | (t, Self::Any) => t.clone(), - _ if self == other => self.clone(), - _ if self.is_subtype(other) => self.clone(), - _ if other.is_subtype(self) => other.clone(), - - // Covariant types: compute GLB pointwise - (Self::Option(s), Self::Option(t)) => Self::Option(Box::new(s.glb(t))), - (Self::List(s), Self::List(t)) => Self::List(Box::new(s.glb(t))), - (Self::Iterator(s), Self::Iterator(t)) => Self::Iterator(Box::new(s.glb(t))), - (Self::MinHeap(s), Self::MinHeap(t)) => Self::MinHeap(Box::new(s.glb(t))), - (Self::MaxHeap(s), Self::MaxHeap(t)) => Self::MaxHeap(Box::new(s.glb(t))), - (Self::Deque(s), Self::Deque(t)) => Self::Deque(Box::new(s.glb(t))), - (Self::Sequence(s), Self::Sequence(t)) => Self::Sequence(Box::new(s.glb(t))), - - (Self::Map { key: k1, value: v1 }, Self::Map { key: k2, value: v2 }) => Self::Map { - key: Box::new(k1.glb(k2)), - value: Box::new(v1.glb(v2)), - }, - - (Self::Tuple(e1), Self::Tuple(e2)) if e1.len() == e2.len() => { - Self::Tuple(e1.iter().zip(e2).map(|(a, b)| a.glb(b)).collect()) - } - - // No representable GLB: conservatively return first type - // A complete type system would have a Bottom type (⊥) here - _ => self.clone(), - } - } - - /// Computes the element type of a tuple when viewed as a sequence. - /// Returns the LUB of all tuple elements. - fn compute_tuple_element_type(elems: &[Self]) -> Self { - if elems.is_empty() { - return Self::Any; - } - - elems - .iter() - .skip(1) - .fold(elems[0].clone(), |acc, elem| acc.lub(elem)) - } - - /// Checks if a type is a sequence-like type. - fn is_sequence(ty: &Self) -> bool { - matches!( - ty, - Self::Sequence(_) - | Self::List(_) - | Self::String - | Self::Tuple(_) - | Self::Map { .. } - | Self::Iterator(_) - | Self::MinHeap(_) - | Self::MaxHeap(_) - | Self::Deque(_) - ) - } - - /// Gets the element type when treating a type as a sequence. - /// - /// - `List`, `Iterator`, etc. → `T` - /// - `String` → `String` - /// - `Tuple` → `lub(T1, ..., Tn)` - /// - `Map` → `Tuple` - pub fn sequence_element_type(&self) -> Option { - match self { - Self::Sequence(t) - | Self::List(t) - | Self::Iterator(t) - | Self::MinHeap(t) - | Self::MaxHeap(t) - | Self::Deque(t) => Some(t.as_ref().clone()), - Self::String => Some(Self::String), - Self::Tuple(elems) => Some(Self::compute_tuple_element_type(elems)), - Self::Map { key, value } => Some(Self::Tuple(vec![ - key.as_ref().clone(), - value.as_ref().clone(), - ])), - Self::Any => Some(Self::Any), - _ => None, - } - } - #[must_use] - pub fn unit() -> Self { - Self::Tuple(vec![]) - } - - #[must_use] - pub fn supports_vectorization(&self) -> bool { - match self { - Self::Tuple(values) => values.iter().all(|v| v.is_number()), - _ => false, - } - } - - pub fn is_number(&self) -> bool { - matches!( - self, - Self::Number | Self::Float | Self::Int | Self::Rational | Self::Complex - ) - } - - #[must_use] - pub fn supports_vectorization_with(&self, other: &Self) -> bool { - match (self, other) { - (Self::Tuple(l), Self::Tuple(r)) - if { - l.len() == r.len() - && self.supports_vectorization() - && other.supports_vectorization() - } => - { - true - } - (tup @ Self::Tuple(_), maybe_num) | (maybe_num, tup @ Self::Tuple(_)) => { - tup.supports_vectorization() && maybe_num.is_number() - } - _ => false, - } - } - - // BRUH - pub fn is_incompatible_with(&self, other: &Self) -> bool { - !self.is_subtype(other) && !other.is_subtype(self) - } - - pub fn index_element_type(&self) -> Option { - if let Self::Map { value, .. } = self { - return Some(value.as_ref().clone()); - } - - self.sequence_element_type() - } - - pub fn is_fn_and_matches(&self, types: &[Self]) -> bool { - // If the thing is not a function we're not interested - let Self::Function { parameters, .. } = self else { - return false; - }; - - let Some(_) = parameters else { - // If this branch happens then the function we're matching against is variadic meaning it's always a match - return true; - }; - - self.is_subtype(&Self::Function { - parameters: Some(types.to_vec()), - return_type: Box::new(Self::Any), - }) - } - - pub fn unpack(&self) -> Option + '_>> { - match self { - // Any just unpacks to an infinite list of Any - Self::Any => Some(Box::new(std::iter::repeat(&Self::Any))), - Self::List(elem) - | Self::Sequence(elem) - | Self::Iterator(elem) - | Self::MinHeap(elem) - | Self::MaxHeap(elem) - | Self::Deque(elem) => Some(Box::new(std::iter::repeat(&**elem))), - Self::Tuple(types) => Some(Box::new(types.iter())), - Self::String => Some(Box::new(std::iter::repeat(&Self::String))), - Self::Bool - | Self::Function { .. } - | Self::Option(_) - | Self::Number - | Self::Float - | Self::Int - | Self::Rational - | Self::Complex - | Self::Map { .. } => None, - } - } -} - -impl fmt::Display for StaticType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Any => write!(f, "Any"), - Self::Bool => write!(f, "Bool"), - Self::Function { - parameters, - return_type, - } => write!( - f, - "Function({}) -> {return_type}", - parameters - .as_deref() - .map(|p| p.iter().join(", ")) - .unwrap_or(String::from("*")) - ), - Self::Option(elem) => write!(f, "Option<{elem}>"), - Self::Number => write!(f, "Number"), - Self::Float => write!(f, "Float"), - Self::Int => write!(f, "Int"), - Self::Rational => write!(f, "Rational"), - Self::Complex => write!(f, "Complex"), - Self::Sequence(elem) => write!(f, "Sequence<{elem}>"), - Self::List(elem) => write!(f, "List<{elem}>"), - Self::String => write!(f, "String"), - Self::Tuple(tup) if tup.is_empty() => write!(f, "()"), - Self::Tuple(tup) => write!(f, "Tuple<{}>", tup.iter().join(", ")), - Self::Map { key, value } => write!(f, "Map<{key}, {value}>"), - Self::Iterator(elem) => write!(f, "Iterator<{elem}>"), - Self::MinHeap(elem) => write!(f, "MinHeap<{elem}>"), - Self::MaxHeap(elem) => write!(f, "MaxHeap<{elem}>"), - Self::Deque(elem) => write!(f, "Deque<{elem}>"), - } - } -} - #[derive(thiserror::Error, Debug)] pub enum FunctionCallError { #[error("invalid argument, expected {expected} got {actual}")] @@ -913,52 +391,3 @@ impl From for FunctionCarrier { Self::IntoEvaluationError(Box::new(value)) } } - -impl fmt::Display for TypeSignature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Variadic => write!(f, "*args"), - Self::Exact(params) => write!( - f, - "{}", - params - .iter() - .map(|p| format!("{}: {}", p.name, p.type_name)) - .join(", ") - ), - } - } -} - -#[allow(unused_imports)] -mod test { - use super::*; - - #[test] - fn test_list_of_type_compatibility() { - let list_of_two_tuple_int = StaticType::List(Box::new(StaticType::Tuple(vec![ - StaticType::Int, - StaticType::Int, - ]))); - let list_of_any = StaticType::List(Box::new(StaticType::Any)); - assert!(list_of_two_tuple_int.is_subtype(&list_of_any)); - } - - #[test] - fn test_function_compatibility() { - let fun = StaticType::Function { - parameters: Some(vec![ - StaticType::List(Box::new(StaticType::Any)), - StaticType::Int, - ]), - return_type: Box::new(StaticType::Any), - }; - - let list_of_two_tuple_int = StaticType::List(Box::new(StaticType::Tuple(vec![ - StaticType::Int, - StaticType::Int, - ]))); - - assert!(fun.is_fn_and_matches(&[list_of_two_tuple_int, StaticType::Int])); - } -} diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 38c19328..eeee8b58 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -2,8 +2,8 @@ use crate::ast::{ Binding, Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar, }; use crate::interpreter::function::StaticType; -use ndc_lexer::Span; use itertools::Itertools; +use ndc_lexer::Span; use std::fmt::{Debug, Formatter}; pub struct Analyser { diff --git a/ndc_lib/src/lib.rs b/ndc_lib/src/lib.rs index da4362fb..0722e44a 100644 --- a/ndc_lib/src/lib.rs +++ b/ndc_lib/src/lib.rs @@ -1,4 +1,4 @@ -pub mod ast; +pub use ndc_parser as ast; mod compare; mod hash_map; pub mod interpreter; diff --git a/ndc_lsp/src/backend.rs b/ndc_lsp/src/backend.rs index db769b82..046f560d 100644 --- a/ndc_lsp/src/backend.rs +++ b/ndc_lsp/src/backend.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; +use ndc_lexer::{Lexer, Span, TokenLocation}; use ndc_lib::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue}; use ndc_lib::interpreter::Interpreter; -use ndc_lexer::{Lexer, Span, TokenLocation}; use tokio::sync::Mutex; use tower_lsp::jsonrpc::Result as JsonRPCResult; use tower_lsp::lsp_types::{ diff --git a/ndc_parser/Cargo.toml b/ndc_parser/Cargo.toml new file mode 100644 index 00000000..17829a14 --- /dev/null +++ b/ndc_parser/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ndc_parser" +edition.workspace = true +version.workspace = true + +[dependencies] +ndc_lexer.workspace = true +num.workspace = true +derive_more = { workspace = true } +thiserror.workspace = true +itertools.workspace = true diff --git a/ndc_lib/src/ast/expression.rs b/ndc_parser/src/expression.rs similarity index 91% rename from ndc_lib/src/ast/expression.rs rename to ndc_parser/src/expression.rs index 574e302a..261d646f 100644 --- a/ndc_lib/src/ast/expression.rs +++ b/ndc_parser/src/expression.rs @@ -1,7 +1,6 @@ -use crate::ast::operator::LogicalOperator; -use crate::ast::parser::Error as ParseError; -use crate::interpreter::evaluate::EvaluationError; -use crate::interpreter::function::StaticType; +use crate::operator::LogicalOperator; +use crate::parser::Error as ParseError; +use crate::static_type::StaticType; use ndc_lexer::Span; use num::BigInt; use num::complex::Complex64; @@ -63,6 +62,7 @@ pub enum Expression { FunctionDeclaration { name: Option, resolved_name: Option, + // TODO: Instead of an ExpressionLocation with a Tuple the parser should just give us something we can actually work with parameters: Box, body: Box, return_type: Option, @@ -177,32 +177,19 @@ impl ExpressionLocation { } } - /// # Errors - /// If this expression cannot be converted into an identifier an `EvaluationError::InvalidExpression` will be returned - pub fn try_into_identifier(&self) -> Result<&str, EvaluationError> { + pub fn as_identifier(&self) -> &str { match &self.expression { - Expression::Identifier { name, resolved: _ } => Ok(name), - _ => Err(EvaluationError::syntax_error( - "expected identifier".to_string(), - self.span, - )), + Expression::Identifier { name, resolved: _ } => name, + _ => panic!("the parser should have guaranteed us the right type of expression"), } } - /// # Errors - /// If this expression cannot be converted into a tuple (or possibly another type that can be a valid parameter list) an `EvaluationError::InvalidExpression` will be returned - pub fn try_into_parameters(&self) -> Result, EvaluationError> { + pub fn as_parameters(&self) -> Vec<&str> { match &self.expression { Expression::Tuple { values: tuple_values, - } => tuple_values - .iter() - .map(|it| it.try_into_identifier().map(ToString::to_string)) - .collect::, EvaluationError>>(), - _ => Err(EvaluationError::syntax_error( - "expected a parameter list".to_string(), - self.span, - )), + } => tuple_values.iter().map(|it| it.as_identifier()).collect(), + _ => panic!("the parser should have guaranteed us the right type of expression"), } } diff --git a/ndc_lib/src/ast/mod.rs b/ndc_parser/src/lib.rs similarity index 77% rename from ndc_lib/src/ast/mod.rs rename to ndc_parser/src/lib.rs index d8dc7c71..f6f5e900 100644 --- a/ndc_lib/src/ast/mod.rs +++ b/ndc_parser/src/lib.rs @@ -1,11 +1,12 @@ mod expression; mod operator; mod parser; +mod static_type; pub use expression::{ Binding, Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar, }; pub use operator::{BinaryOperator, LogicalOperator, UnaryOperator}; - pub use parser::Error; pub use parser::Parser; +pub use static_type::{Parameter, StaticType, TypeSignature}; diff --git a/ndc_lib/src/ast/operator.rs b/ndc_parser/src/operator.rs similarity index 99% rename from ndc_lib/src/ast/operator.rs rename to ndc_parser/src/operator.rs index d976b731..17b9bbcc 100644 --- a/ndc_lib/src/ast/operator.rs +++ b/ndc_parser/src/operator.rs @@ -1,4 +1,4 @@ -use crate::ast::Error as ParseError; +use crate::parser::Error as ParseError; use ndc_lexer::{Token, TokenLocation}; use std::fmt; use std::fmt::Formatter; diff --git a/ndc_lib/src/ast/parser.rs b/ndc_parser/src/parser.rs similarity index 99% rename from ndc_lib/src/ast/parser.rs rename to ndc_parser/src/parser.rs index af09d1ec..733566fb 100644 --- a/ndc_lib/src/ast/parser.rs +++ b/ndc_parser/src/parser.rs @@ -1,8 +1,8 @@ use std::fmt::Write; -use crate::ast::Expression; -use crate::ast::expression::{Binding, ExpressionLocation, ForBody, ForIteration, Lvalue}; -use crate::ast::operator::{BinaryOperator, LogicalOperator, UnaryOperator}; +use crate::expression::Expression; +use crate::expression::{Binding, ExpressionLocation, ForBody, ForIteration, Lvalue}; +use crate::operator::{BinaryOperator, LogicalOperator, UnaryOperator}; use ndc_lexer::{Span, Token, TokenLocation}; pub struct Parser { diff --git a/ndc_parser/src/static_type.rs b/ndc_parser/src/static_type.rs new file mode 100644 index 00000000..732b0202 --- /dev/null +++ b/ndc_parser/src/static_type.rs @@ -0,0 +1,576 @@ +use itertools::Itertools; +use std::fmt; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum TypeSignature { + Variadic, + Exact(Vec), +} + +impl TypeSignature { + /// Matches a list of `ValueTypes` to a type signature. It can return `None` if there is no match or + /// `Some(num)` where num is the sum of the distances of the types. The type `Int`, is distance 1 + /// away from `Number`, and `Number` is 1 distance from `Any`, then `Int` is distance 2 from `Any`. + pub fn calc_type_score(&self, types: &[StaticType]) -> Option { + match self { + Self::Variadic => Some(0), + Self::Exact(signature) => { + if types.len() == signature.len() { + let mut acc = 0; + for (a, b) in types.iter().zip(signature.iter()) { + let dist = if a == &b.type_name { + 0 + } else if a.is_subtype(&b.type_name) { + 1 + } else { + return None; + }; + acc += dist; + } + + return Some(acc); + } + + None + } + } + } + + pub fn arity(&self) -> Option { + match self { + Self::Variadic => None, + Self::Exact(args) => Some(args.len()), + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Parameter { + pub name: String, + pub type_name: StaticType, +} + +impl Parameter { + pub fn new>(name: N, param_type: StaticType) -> Self { + Self { + name: name.into(), + type_name: param_type, + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum StaticType { + Any, + Bool, + Function { + parameters: Option>, + return_type: Box, + }, + Option(Box), + + // Numbers + Number, + Float, + Int, + Rational, + Complex, + + // Sequences List -> List + Sequence(Box), + List(Box), + String, + Tuple(Vec), + Map { + key: Box, + value: Box, + }, + Iterator(Box), + MinHeap(Box), + MaxHeap(Box), + Deque(Box), +} + +impl StaticType { + /// Checks if `self` is a subtype of `other`. + /// + /// A type S is a subtype of T (S <: T) if a value of type S can be safely + /// used wherever a value of type T is expected. + /// + /// # Key Rules + /// - `Any` is the top type (supertype of all types) + /// - `Number` > `{Float, Int, Rational, Complex}` + /// - `Sequence` > sequence types with element type T + /// - Generic types are covariant in their type parameters + /// - Function parameters are **contravariant**, returns are **covariant** + /// + /// Note: this function probably doesn't handle variadic functions correctly + pub fn is_subtype(&self, other: &Self) -> bool { + // Any is the universal supertype + if matches!(other, Self::Any) { + return true; + } + + // Only Any satisfies the above; all other types fail here + if matches!(self, Self::Any) { + return false; + } + + #[allow(clippy::match_same_arms)] + #[allow(clippy::unnested_or_patterns)] + match (self, other) { + // Reflexivity: every type is a subtype of itself + _ if self == other => true, + + // Number hierarchy: all numeric types are subtypes of Number + (Self::Float | Self::Int | Self::Rational | Self::Complex, Self::Number) => true, + + // Sequence hierarchy: specific sequences are subtypes of Sequence + // where T is their element type (covariant) + (Self::List(s), Self::Sequence(t)) + | (Self::Iterator(s), Self::Sequence(t)) + | (Self::MinHeap(s), Self::Sequence(t)) + | (Self::MaxHeap(s), Self::Sequence(t)) + | (Self::Deque(s), Self::Sequence(t)) => s.is_subtype(t), + + // String is Sequence + (Self::String, Self::Sequence(t)) => Self::String.is_subtype(t.as_ref()), + + // Tuple is Sequence + (Self::Tuple(elems), Self::Sequence(t)) => { + Self::compute_tuple_element_type(elems).is_subtype(t) + } + + // Map is Sequence> + (Self::Map { key, value }, Self::Sequence(t)) => { + Self::Tuple(vec![key.as_ref().clone(), value.as_ref().clone()]) + .is_subtype(t.as_ref()) + } + + // Generic types are covariant: Container <: Container if S <: T + (Self::Option(s), Self::Option(t)) + | (Self::List(s), Self::List(t)) + | (Self::Iterator(s), Self::Iterator(t)) + | (Self::MinHeap(s), Self::MinHeap(t)) + | (Self::MaxHeap(s), Self::MaxHeap(t)) + | (Self::Deque(s), Self::Deque(t)) + | (Self::Sequence(s), Self::Sequence(t)) => s.is_subtype(t), + + // Tuples are covariant pointwise and must have the same arity + (Self::Tuple(s_elems), Self::Tuple(t_elems)) => { + s_elems.len() == t_elems.len() + && s_elems.iter().zip(t_elems).all(|(s, t)| s.is_subtype(t)) + } + + // Maps are covariant in both key and value type parameters + (Self::Map { key: k1, value: v1 }, Self::Map { key: k2, value: v2 }) => { + k1.is_subtype(k2) && v1.is_subtype(v2) + } + + // Functions: contravariant in parameters, covariant in return type + // F1 <: F2 iff params(F2) <: params(F1) AND return(F1) <: return(F2) + ( + Self::Function { + parameters: p1, + return_type: r1, + }, + Self::Function { + parameters: p2, + return_type: r2, + }, + ) => { + // Return type is covariant: must satisfy r1 <: r2 + let return_ok = r1.is_subtype(r2); + + // Parameters are contravariant: must satisfy p2 <: p1 (reversed!) + let params_ok = match (p1, p2) { + (None, None) => true, + // A function with specific params is a subtype of one with generic params + (Some(_), None) => true, + // Cannot substitute generic params with specific ones + (None, Some(_)) => false, + (Some(ps1), Some(ps2)) => { + ps1.len() == ps2.len() && + // Note the reversal: p2 must be subtype of p1! + ps1.iter() + .zip(ps2) + .all(|(p1, p2)| p2.is_subtype(p1)) + } + }; + + return_ok && params_ok + } + + _ => false, + } + } + + // + /// Computes the Least Upper Bound (join) of two types. + /// + /// The LUB is the most specific type that is a supertype of both inputs. + /// + /// # Examples + /// - `lub(Int, Float) = Number` + /// - `lub(List, List) = List` + /// - `lub(List, Iterator) = Sequence` + /// - `lub(Int, String) = Any` + pub fn lub(&self, other: &Self) -> Self { + // Any is the top type + if matches!(self, Self::Any) || matches!(other, Self::Any) { + return Self::Any; + } + + // Reflexivity: lub(T, T) = T + if self == other { + return self.clone(); + } + + // If one is a subtype of the other, return the supertype + if self.is_subtype(other) { + return other.clone(); + } + if other.is_subtype(self) { + return self.clone(); + } + + match (self, other) { + // Number type lattice: all numeric types join to Number + (Self::Float, Self::Int | Self::Rational | Self::Complex) + | (Self::Int, Self::Float | Self::Rational | Self::Complex) + | (Self::Rational, Self::Float | Self::Int | Self::Complex) + | (Self::Complex, Self::Float | Self::Int | Self::Rational) => Self::Number, + + // Covariant generic types: compute LUB pointwise + (Self::Option(s), Self::Option(t)) => Self::Option(Box::new(s.lub(t))), + (Self::List(s), Self::List(t)) => Self::List(Box::new(s.lub(t))), + (Self::Iterator(s), Self::Iterator(t)) => Self::Iterator(Box::new(s.lub(t))), + (Self::MinHeap(s), Self::MinHeap(t)) => Self::MinHeap(Box::new(s.lub(t))), + (Self::MaxHeap(s), Self::MaxHeap(t)) => Self::MaxHeap(Box::new(s.lub(t))), + (Self::Deque(s), Self::Deque(t)) => Self::Deque(Box::new(s.lub(t))), + (Self::Sequence(s), Self::Sequence(t)) => Self::Sequence(Box::new(s.lub(t))), + + // Maps are covariant in both parameters + (Self::Map { key: k1, value: v1 }, Self::Map { key: k2, value: v2 }) => Self::Map { + key: Box::new(k1.lub(k2)), + value: Box::new(v1.lub(v2)), + }, + + // Tuples of same arity: compute LUB pointwise + (Self::Tuple(e1), Self::Tuple(e2)) if e1.len() == e2.len() => { + Self::Tuple(e1.iter().zip(e2).map(|(a, b)| a.lub(b)).collect()) + } + + // Different sequence types: generalize to Sequence + _ if Self::is_sequence(self) && Self::is_sequence(other) => { + let elem1 = self.sequence_element_type().expect("must be seq"); + let elem2 = other.sequence_element_type().expect("must be seq"); + Self::Sequence(Box::new(elem1.lub(&elem2))) + } + + // Functions: covariant in return, contravariant in parameters + ( + Self::Function { + parameters: p1, + return_type: r1, + }, + Self::Function { + parameters: p2, + return_type: r2, + }, + ) => { + // Return type: covariant, so take LUB + let return_type = Box::new(r1.lub(r2)); + + // Parameters: contravariant, so we need GLB (greatest lower bound) + let parameters = match (p1, p2) { + (None, None) => None, + (Some(ps1), Some(ps2)) if ps1.len() == ps2.len() => { + Some(ps1.iter().zip(ps2).map(|(a, b)| a.glb(b)).collect()) + } + // Incompatible parameter lists + _ => None, + }; + + Self::Function { + parameters, + return_type, + } + } + + // No common supertype found: default to Any + _ => Self::Any, + } + } + + /// Computes the Greatest Lower Bound (meet) of two types. + /// + /// The GLB is the most general type that is a subtype of both inputs. + /// This is required for contravariant positions (e.g., function parameters). + /// + /// Note: Not all type pairs have a representable GLB in our type system. + /// In such cases, we return a conservative approximation. + fn glb(&self, other: &Self) -> Self { + // Any is the top type: glb(Any, T) = T + match (self, other) { + (Self::Any, t) | (t, Self::Any) => t.clone(), + _ if self == other => self.clone(), + _ if self.is_subtype(other) => self.clone(), + _ if other.is_subtype(self) => other.clone(), + + // Covariant types: compute GLB pointwise + (Self::Option(s), Self::Option(t)) => Self::Option(Box::new(s.glb(t))), + (Self::List(s), Self::List(t)) => Self::List(Box::new(s.glb(t))), + (Self::Iterator(s), Self::Iterator(t)) => Self::Iterator(Box::new(s.glb(t))), + (Self::MinHeap(s), Self::MinHeap(t)) => Self::MinHeap(Box::new(s.glb(t))), + (Self::MaxHeap(s), Self::MaxHeap(t)) => Self::MaxHeap(Box::new(s.glb(t))), + (Self::Deque(s), Self::Deque(t)) => Self::Deque(Box::new(s.glb(t))), + (Self::Sequence(s), Self::Sequence(t)) => Self::Sequence(Box::new(s.glb(t))), + + (Self::Map { key: k1, value: v1 }, Self::Map { key: k2, value: v2 }) => Self::Map { + key: Box::new(k1.glb(k2)), + value: Box::new(v1.glb(v2)), + }, + + (Self::Tuple(e1), Self::Tuple(e2)) if e1.len() == e2.len() => { + Self::Tuple(e1.iter().zip(e2).map(|(a, b)| a.glb(b)).collect()) + } + + // No representable GLB: conservatively return first type + // A complete type system would have a Bottom type (⊥) here + _ => self.clone(), + } + } + + /// Computes the element type of a tuple when viewed as a sequence. + /// Returns the LUB of all tuple elements. + fn compute_tuple_element_type(elems: &[Self]) -> Self { + if elems.is_empty() { + return Self::Any; + } + + elems + .iter() + .skip(1) + .fold(elems[0].clone(), |acc, elem| acc.lub(elem)) + } + + /// Checks if a type is a sequence-like type. + fn is_sequence(ty: &Self) -> bool { + matches!( + ty, + Self::Sequence(_) + | Self::List(_) + | Self::String + | Self::Tuple(_) + | Self::Map { .. } + | Self::Iterator(_) + | Self::MinHeap(_) + | Self::MaxHeap(_) + | Self::Deque(_) + ) + } + + /// Gets the element type when treating a type as a sequence. + /// + /// - `List`, `Iterator`, etc. → `T` + /// - `String` → `String` + /// - `Tuple` → `lub(T1, ..., Tn)` + /// - `Map` → `Tuple` + pub fn sequence_element_type(&self) -> Option { + match self { + Self::Sequence(t) + | Self::List(t) + | Self::Iterator(t) + | Self::MinHeap(t) + | Self::MaxHeap(t) + | Self::Deque(t) => Some(t.as_ref().clone()), + Self::String => Some(Self::String), + Self::Tuple(elems) => Some(Self::compute_tuple_element_type(elems)), + Self::Map { key, value } => Some(Self::Tuple(vec![ + key.as_ref().clone(), + value.as_ref().clone(), + ])), + Self::Any => Some(Self::Any), + _ => None, + } + } + + #[must_use] + pub fn unit() -> Self { + Self::Tuple(vec![]) + } + + #[must_use] + pub fn supports_vectorization(&self) -> bool { + match self { + Self::Tuple(values) => values.iter().all(|v| v.is_number()), + _ => false, + } + } + + pub fn is_number(&self) -> bool { + matches!( + self, + Self::Number | Self::Float | Self::Int | Self::Rational | Self::Complex + ) + } + + #[must_use] + pub fn supports_vectorization_with(&self, other: &Self) -> bool { + match (self, other) { + (Self::Tuple(l), Self::Tuple(r)) + if { + l.len() == r.len() + && self.supports_vectorization() + && other.supports_vectorization() + } => + { + true + } + (tup @ Self::Tuple(_), maybe_num) | (maybe_num, tup @ Self::Tuple(_)) => { + tup.supports_vectorization() && maybe_num.is_number() + } + _ => false, + } + } + + // BRUH + pub fn is_incompatible_with(&self, other: &Self) -> bool { + !self.is_subtype(other) && !other.is_subtype(self) + } + + pub fn index_element_type(&self) -> Option { + if let Self::Map { value, .. } = self { + return Some(value.as_ref().clone()); + } + + self.sequence_element_type() + } + + pub fn is_fn_and_matches(&self, types: &[Self]) -> bool { + // If the thing is not a function we're not interested + let Self::Function { parameters, .. } = self else { + return false; + }; + + let Some(_) = parameters else { + // If this branch happens then the function we're matching against is variadic meaning it's always a match + return true; + }; + + self.is_subtype(&Self::Function { + parameters: Some(types.to_vec()), + return_type: Box::new(Self::Any), + }) + } + + pub fn unpack(&self) -> Option + '_>> { + match self { + // Any just unpacks to an infinite list of Any + Self::Any => Some(Box::new(std::iter::repeat(&Self::Any))), + Self::List(elem) + | Self::Sequence(elem) + | Self::Iterator(elem) + | Self::MinHeap(elem) + | Self::MaxHeap(elem) + | Self::Deque(elem) => Some(Box::new(std::iter::repeat(&**elem))), + Self::Tuple(types) => Some(Box::new(types.iter())), + Self::String => Some(Box::new(std::iter::repeat(&Self::String))), + Self::Bool + | Self::Function { .. } + | Self::Option(_) + | Self::Number + | Self::Float + | Self::Int + | Self::Rational + | Self::Complex + | Self::Map { .. } => None, + } + } +} + +impl fmt::Display for StaticType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Any => write!(f, "Any"), + Self::Bool => write!(f, "Bool"), + Self::Function { + parameters, + return_type, + } => write!( + f, + "Function({}) -> {return_type}", + parameters + .as_deref() + .map(|p| p.iter().join(", ")) + .unwrap_or(String::from("*")) + ), + Self::Option(elem) => write!(f, "Option<{elem}>"), + Self::Number => write!(f, "Number"), + Self::Float => write!(f, "Float"), + Self::Int => write!(f, "Int"), + Self::Rational => write!(f, "Rational"), + Self::Complex => write!(f, "Complex"), + Self::Sequence(elem) => write!(f, "Sequence<{elem}>"), + Self::List(elem) => write!(f, "List<{elem}>"), + Self::String => write!(f, "String"), + Self::Tuple(tup) if tup.is_empty() => write!(f, "()"), + Self::Tuple(tup) => write!(f, "Tuple<{}>", tup.iter().join(", ")), + Self::Map { key, value } => write!(f, "Map<{key}, {value}>"), + Self::Iterator(elem) => write!(f, "Iterator<{elem}>"), + Self::MinHeap(elem) => write!(f, "MinHeap<{elem}>"), + Self::MaxHeap(elem) => write!(f, "MaxHeap<{elem}>"), + Self::Deque(elem) => write!(f, "Deque<{elem}>"), + } + } +} + +impl fmt::Display for TypeSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Variadic => write!(f, "*args"), + Self::Exact(params) => write!( + f, + "{}", + params + .iter() + .map(|p| format!("{}: {}", p.name, p.type_name)) + .join(", ") + ), + } + } +} + +#[allow(unused_imports)] +mod test { + use super::*; + + #[test] + fn test_list_of_type_compatibility() { + let list_of_two_tuple_int = StaticType::List(Box::new(StaticType::Tuple(vec![ + StaticType::Int, + StaticType::Int, + ]))); + let list_of_any = StaticType::List(Box::new(StaticType::Any)); + assert!(list_of_two_tuple_int.is_subtype(&list_of_any)); + } + + #[test] + fn test_function_compatibility() { + let fun = StaticType::Function { + parameters: Some(vec![ + StaticType::List(Box::new(StaticType::Any)), + StaticType::Int, + ]), + return_type: Box::new(StaticType::Any), + }; + + let list_of_two_tuple_int = StaticType::List(Box::new(StaticType::Tuple(vec![ + StaticType::Int, + StaticType::Int, + ]))); + + assert!(fun.is_fn_and_matches(&[list_of_two_tuple_int, StaticType::Int])); + } +}