From 8b45d34d318ee30243e9cb178cc73fc926f34c5c Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 09:35:16 +0100 Subject: [PATCH 01/35] move some files around --- ndc_lib/src/interpreter/environment.rs | 2 +- ndc_lib/src/interpreter/mod.rs | 6 ++-- ndc_lib/src/interpreter/semantic/mod.rs | 2 ++ .../src/interpreter/{ => semantic}/resolve.rs | 33 +++++++++---------- ndc_lib/src/interpreter/semantic/types.rs | 0 5 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 ndc_lib/src/interpreter/semantic/mod.rs rename ndc_lib/src/interpreter/{ => semantic}/resolve.rs (99%) create mode 100644 ndc_lib/src/interpreter/semantic/types.rs diff --git a/ndc_lib/src/interpreter/environment.rs b/ndc_lib/src/interpreter/environment.rs index aba7e14f..f2b9fe94 100644 --- a/ndc_lib/src/interpreter/environment.rs +++ b/ndc_lib/src/interpreter/environment.rs @@ -1,7 +1,7 @@ use crate::interpreter::function::{Function, OverloadedFunction}; use crate::ast::ResolvedVar; -use crate::interpreter::resolve::LexicalIdentifier; +use crate::interpreter::semantic::resolve::LexicalIdentifier; use crate::interpreter::value::Value; use std::cell::RefCell; use std::fmt; diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index fdb5d5b0..ac1d6037 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -5,7 +5,7 @@ use crate::ast::ExpressionLocation; use crate::interpreter::environment::{Environment, InterpreterOutput}; use crate::interpreter::evaluate::{EvaluationError, evaluate_expression}; use crate::interpreter::function::FunctionCarrier; -use crate::interpreter::resolve::{LexicalData, resolve_pass}; +use crate::interpreter::semantic::resolve::{LexicalData, resolve_pass}; use crate::interpreter::value::Value; use crate::lexer::{Lexer, TokenLocation}; use miette::Diagnostic; @@ -17,7 +17,7 @@ pub(crate) mod heap; pub mod int; pub mod iterator; pub mod num; -mod resolve; +pub mod semantic; pub mod sequence; pub mod value; @@ -139,7 +139,7 @@ pub enum InterpreterError { #[diagnostic(transparent)] Resolver { #[from] - cause: resolve::ResolveError, + cause: semantic::resolve::ResolveError, }, #[error("Error while executing code")] #[diagnostic(transparent)] diff --git a/ndc_lib/src/interpreter/semantic/mod.rs b/ndc_lib/src/interpreter/semantic/mod.rs new file mode 100644 index 00000000..b74cade2 --- /dev/null +++ b/ndc_lib/src/interpreter/semantic/mod.rs @@ -0,0 +1,2 @@ +pub mod resolve; +pub mod types; diff --git a/ndc_lib/src/interpreter/resolve.rs b/ndc_lib/src/interpreter/semantic/resolve.rs similarity index 99% rename from ndc_lib/src/interpreter/resolve.rs rename to ndc_lib/src/interpreter/semantic/resolve.rs index c6afad09..96d075f2 100644 --- a/ndc_lib/src/interpreter/resolve.rs +++ b/ndc_lib/src/interpreter/semantic/resolve.rs @@ -1,23 +1,6 @@ use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; use crate::lexer::Span; -#[derive(thiserror::Error, miette::Diagnostic, Debug)] -#[error("{text}")] -pub struct ResolveError { - text: String, - #[label("related to this")] - span: Span, -} - -impl ResolveError { - fn identifier_not_previously_declared(ident: &str, span: Span) -> Self { - Self { - text: format!("Identifier {ident} has not previously been declared"), - span, - } - } -} - pub fn resolve_pass( ExpressionLocation { expression, span }: &mut ExpressionLocation, lexical_data: &mut LexicalData, @@ -551,3 +534,19 @@ impl LexicalScope { self.identifiers.len() - 1 } } +#[derive(thiserror::Error, miette::Diagnostic, Debug)] +#[error("{text}")] +pub struct ResolveError { + text: String, + #[label("related to this")] + span: Span, +} + +impl ResolveError { + fn identifier_not_previously_declared(ident: &str, span: Span) -> Self { + Self { + text: format!("Identifier {ident} has not previously been declared"), + span, + } + } +} diff --git a/ndc_lib/src/interpreter/semantic/types.rs b/ndc_lib/src/interpreter/semantic/types.rs new file mode 100644 index 00000000..e69de29b From 4d05582be19118dcd13b617e9653120d87547f10 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 12:09:06 +0100 Subject: [PATCH 02/35] WIP pre collapse types --- ndc_lib/src/ast/expression.rs | 4 +- ndc_lib/src/ast/parser.rs | 2 +- ndc_lib/src/interpreter/evaluate/mod.rs | 2 +- ndc_lib/src/interpreter/semantic/mod.rs | 1 - ndc_lib/src/interpreter/semantic/resolve.rs | 111 ++++++++++---------- ndc_lib/src/interpreter/semantic/types.rs | 36 +++++++ 6 files changed, 97 insertions(+), 59 deletions(-) diff --git a/ndc_lib/src/ast/expression.rs b/ndc_lib/src/ast/expression.rs index e9c07c6f..ca936677 100644 --- a/ndc_lib/src/ast/expression.rs +++ b/ndc_lib/src/ast/expression.rs @@ -55,7 +55,7 @@ pub enum Expression { FunctionDeclaration { name: Option, resolved_name: Option, - arguments: Box, + parameters: Box, body: Box, pure: bool, }, @@ -332,7 +332,7 @@ impl std::fmt::Debug for ExpressionLocation { .finish(), Expression::FunctionDeclaration { name, - arguments, + parameters: arguments, body, pure, resolved_name, diff --git a/ndc_lib/src/ast/parser.rs b/ndc_lib/src/ast/parser.rs index f1778591..6eaa237c 100644 --- a/ndc_lib/src/ast/parser.rs +++ b/ndc_lib/src/ast/parser.rs @@ -1155,7 +1155,7 @@ impl Parser { Ok(ExpressionLocation { expression: Expression::FunctionDeclaration { name: identifier, - arguments: Box::new(argument_list), + parameters: Box::new(argument_list), body: Box::new(body), pure: is_pure, resolved_name: None, diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index 33fed2b8..ab4ab187 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -343,7 +343,7 @@ pub(crate) fn evaluate_expression( }; } Expression::FunctionDeclaration { - arguments, + parameters: arguments, body, resolved_name, pure, diff --git a/ndc_lib/src/interpreter/semantic/mod.rs b/ndc_lib/src/interpreter/semantic/mod.rs index b74cade2..581d1dcd 100644 --- a/ndc_lib/src/interpreter/semantic/mod.rs +++ b/ndc_lib/src/interpreter/semantic/mod.rs @@ -1,2 +1 @@ pub mod resolve; -pub mod types; diff --git a/ndc_lib/src/interpreter/semantic/resolve.rs b/ndc_lib/src/interpreter/semantic/resolve.rs index 96d075f2..87e9a58c 100644 --- a/ndc_lib/src/interpreter/semantic/resolve.rs +++ b/ndc_lib/src/interpreter/semantic/resolve.rs @@ -1,5 +1,8 @@ use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; +use crate::interpreter::function::{ParamType, Parameter}; +use crate::interpreter::value::ValueType; use crate::lexer::Span; +use itertools::Itertools; pub fn resolve_pass( ExpressionLocation { expression, span }: &mut ExpressionLocation, @@ -86,27 +89,24 @@ pub fn resolve_pass( Expression::FunctionDeclaration { name, resolved_name, - arguments, + parameters, body, .. } => { if let Some(name) = name { let function_ident = LexicalIdentifier::Function { name: (*name).clone(), - arity: Some(extract_argument_arity(arguments)), + arity: Some(extract_argument_arity(parameters)), }; - *resolved_name = if let Some(binding) = - lexical_data.get_or_create_local_binding(function_ident.clone()) - { - Some(binding) - } else { - Some(lexical_data.create_local_binding(function_ident)) - } + *resolved_name = Some( + lexical_data + .get_or_create_local_binding(function_ident.clone(), ValueType::Function), + ) } lexical_data.new_scope(); - resolve_arguments_declarative(arguments, lexical_data); + resolve_parameters_declarative(parameters, lexical_data); resolve_pass(body, lexical_data)?; lexical_data.destroy_scope(); @@ -320,7 +320,7 @@ fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { values.len() } /// Resolve expressions as arguments to a function and return the function arity -fn resolve_arguments_declarative( +fn resolve_parameters_declarative( arguments: &mut ExpressionLocation, lexical_data: &mut LexicalData, ) { @@ -341,11 +341,12 @@ fn resolve_arguments_declarative( panic!("expected tuple values to be ident"); }; - *resolved = Some( - lexical_data.create_local_binding(LexicalIdentifier::Variable { + *resolved = Some(lexical_data.create_local_binding( + LexicalIdentifier::Variable { name: (*name).clone(), - }), - ); + }, + ParamType::Any, + )); } } fn resolve_lvalue_declarative( @@ -383,6 +384,14 @@ pub enum LexicalIdentifier { Function { name: String, arity: Option }, } +impl LexicalIdentifier { + pub fn name(&self) -> &str { + match self { + LexicalIdentifier::Variable { name } | LexicalIdentifier::Function { name, .. } => name, + } + } +} + impl From<&str> for LexicalIdentifier { fn from(value: &str) -> Self { Self::Variable { name: value.into() } @@ -407,7 +416,11 @@ impl LexicalData { current_scope_idx: 0, global_scope: LexicalScope { parent_idx: None, - identifiers: global_scope_map, + //TODO: maybe the line below is more of temporary solution + identifiers: global_scope_map + .into_iter() + .map(|x| (x, ValueType::Function)) + .collect_vec(), }, scopes: vec![LexicalScope::new(None)], } @@ -428,11 +441,17 @@ impl LexicalData { self.current_scope_idx = next; } - fn get_or_create_local_binding(&mut self, ident: LexicalIdentifier) -> Option { - Some(ResolvedVar::Captured { - slot: self.scopes[self.current_scope_idx].get_or_allocate(ident), + fn get_or_create_local_binding( + &mut self, + ident: LexicalIdentifier, + value_type: ValueType, + ) -> ResolvedVar { + ResolvedVar::Captured { + slot: self.scopes[self.current_scope_idx] + .get_slot(&ident) + .unwrap_or_else(|| self.scopes[self.current_scope_idx].allocate(ident, value_type)), depth: 0, - }) + } } fn get_binding_any(&mut self, ident: &str) -> Option { @@ -458,22 +477,22 @@ impl LexicalData { let mut scope_ptr = self.current_scope_idx; loop { - if let Some(slot) = self.scopes[scope_ptr].get_slot_by_identifier(name) { + if let Some(slot) = self.scopes[scope_ptr].get_slot(name) { return Some(ResolvedVar::Captured { slot, depth }); } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { depth += 1; scope_ptr = parent_idx; } else { return Some(ResolvedVar::Global { - slot: self.global_scope.get_slot_by_identifier(name)?, + slot: self.global_scope.get_slot(name)?, }); } } } - fn create_local_binding(&mut self, ident: LexicalIdentifier) -> ResolvedVar { + fn create_local_binding(&mut self, ident: LexicalIdentifier, vtype: ValueType) -> ResolvedVar { ResolvedVar::Captured { - slot: self.scopes[self.current_scope_idx].allocate(ident), + slot: self.scopes[self.current_scope_idx].allocate(ident, vtype), depth: 0, } } @@ -482,7 +501,7 @@ impl LexicalData { #[derive(Debug)] struct LexicalScope { parent_idx: Option, - identifiers: Vec, + identifiers: Vec<(LexicalIdentifier, ValueType)>, } impl LexicalScope { @@ -494,43 +513,27 @@ impl LexicalScope { } pub fn get_slot_by_name(&self, find_ident: &str) -> Option { - for (slot, ident_in_scope) in self.identifiers.iter().enumerate().rev() { - let name_in_scope = match ident_in_scope { - LexicalIdentifier::Variable { name } | LexicalIdentifier::Function { name, .. } => { - name - } - }; - - if name_in_scope == find_ident { - return Some(slot); - } - } - - None + self.identifiers + .iter() + .rposition(|(ident, _)| ident.name() == find_ident) } /// Either returns the slot in this current scope or creates a new one + #[deprecated = "use get_slot_by_ident instead"] pub fn get_or_allocate(&mut self, ident: LexicalIdentifier) -> usize { - if let Some(idx) = self.get_slot_by_identifier(&ident) { - idx - } else { - self.allocate(ident) - } + self.get_slot(&ident) + .unwrap_or_else(|| self.allocate(ident, todo!("we don't know"))) } - fn get_slot_by_identifier(&mut self, find_ident: &LexicalIdentifier) -> Option { - for (slot, ident_in_scope) in self.identifiers.iter().enumerate().rev() { - if ident_in_scope == find_ident { - return Some(slot); - } - } - - None + fn get_slot(&mut self, find_ident: &LexicalIdentifier) -> Option { + self.identifiers + .iter() + .rposition(|(ident, _type)| ident == find_ident) } - fn allocate(&mut self, name: LexicalIdentifier) -> usize { - self.identifiers.push(name); - // Slot is just the length of the list + fn allocate(&mut self, name: LexicalIdentifier, vtype: ValueType) -> usize { + self.identifiers.push((name, vtype)); + // Slot is just the length of the list minus one self.identifiers.len() - 1 } } diff --git a/ndc_lib/src/interpreter/semantic/types.rs b/ndc_lib/src/interpreter/semantic/types.rs index e69de29b..eee67047 100644 --- a/ndc_lib/src/interpreter/semantic/types.rs +++ b/ndc_lib/src/interpreter/semantic/types.rs @@ -0,0 +1,36 @@ +use crate::ast::{Expression, ExpressionLocation}; +use crate::interpreter::num::NumberType; +use crate::interpreter::value::ValueType; + +fn give_type(ExpressionLocation { expression, .. }: &ExpressionLocation) -> ValueType { + match expression { + Expression::BoolLiteral(_) => ValueType::Bool, + Expression::StringLiteral(_) => ValueType::String, + Expression::Int64Literal(_) => ValueType::Number(NumberType::Int), + Expression::Float64Literal(_) => ValueType::Number(NumberType::Float), + Expression::BigIntLiteral(_) => ValueType::Number(NumberType::Int), + Expression::ComplexLiteral(_) => ValueType::Number(NumberType::Complex), + Expression::Identifier { .. } => {} + Expression::Statement(_) => {} + Expression::Logical { .. } => {} + Expression::Grouping(_) => {} + Expression::VariableDeclaration { .. } => {} + Expression::Assignment { .. } => {} + Expression::OpAssignment { .. } => {} + Expression::FunctionDeclaration { .. } => {} + Expression::Block { .. } => {} + Expression::If { .. } => {} + Expression::While { .. } => {} + Expression::For { .. } => {} + Expression::Call { .. } => {} + Expression::Index { .. } => {} + Expression::Tuple { .. } => {} + Expression::List { .. } => {} + Expression::Map { .. } => {} + Expression::Return { .. } => {} + Expression::Break => {} + Expression::Continue => {} + Expression::RangeInclusive { .. } => {} + Expression::RangeExclusive { .. } => {} + } +} From 6dd746504068b935ee323c1e7c49d5ddc7edf9cd Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 14:05:57 +0100 Subject: [PATCH 03/35] implemented a really stupid type inference system that almost always infers all types to be any --- ndc_lib/src/interpreter/function.rs | 32 ++-- ndc_lib/src/interpreter/mod.rs | 2 +- ndc_lib/src/interpreter/semantic/resolve.rs | 161 ++++++++++++++++---- ndc_lib/src/stdlib/math.rs | 40 ++--- ndc_macros/src/convert.rs | 10 +- ndc_macros/src/function.rs | 70 ++++----- 6 files changed, 213 insertions(+), 102 deletions(-) diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 37f51613..a70f6ba2 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -125,16 +125,16 @@ impl FunctionBody { } => TypeSignature::Exact( parameter_names .iter() - .map(|name| Parameter::new(name, ParamType::Any)) + .map(|name| Parameter::new(name, StaticType::Any)) .collect(), ), Self::Memoized { cache: _, function } => function.type_signature(), Self::NumericUnaryOp { .. } => { - TypeSignature::Exact(vec![Parameter::new("num", ParamType::Number)]) + TypeSignature::Exact(vec![Parameter::new("num", StaticType::Number)]) } Self::NumericBinaryOp { .. } => TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Number), - Parameter::new("right", ParamType::Number), + Parameter::new("left", StaticType::Number), + Parameter::new("right", StaticType::Number), ]), Self::GenericFunction { type_signature, .. } => type_signature.clone(), } @@ -171,7 +171,7 @@ impl FunctionBody { Self::NumericUnaryOp { body } => match args { [Value::Number(num)] => Ok(Value::Number(body(num.clone()))), [v] => Err(FunctionCallError::ArgumentTypeError { - expected: ParamType::Number, + expected: StaticType::Number, actual: v.value_type(), } .into()), @@ -187,12 +187,12 @@ impl FunctionBody { .map_err(|err| FunctionCarrier::IntoEvaluationError(Box::new(err)))?, )), [Value::Number(_), right] => Err(FunctionCallError::ArgumentTypeError { - expected: ParamType::Number, + expected: StaticType::Number, actual: right.value_type(), } .into()), [left, _] => Err(FunctionCallError::ArgumentTypeError { - expected: ParamType::Number, + expected: StaticType::Number, actual: left.value_type(), } .into()), @@ -419,11 +419,11 @@ impl From for OverloadedFunction { #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Parameter { pub name: String, - pub type_name: ParamType, + pub type_name: StaticType, } impl Parameter { - pub fn new>(name: N, param_type: ParamType) -> Self { + pub fn new>(name: N, param_type: StaticType) -> Self { Self { name: name.into(), type_name: param_type, @@ -432,7 +432,7 @@ impl Parameter { } #[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum ParamType { +pub enum StaticType { Any, Bool, Function, @@ -457,7 +457,7 @@ pub enum ParamType { Deque, } -impl ParamType { +impl StaticType { fn distance(&self, other: &ValueType) -> Option { #[allow(clippy::match_same_arms)] match (self, other) { @@ -469,7 +469,7 @@ impl ParamType { (Self::Complex, ValueType::Number(NumberType::Complex)) => Some(0), (Self::String, ValueType::String) => Some(0), (Self::List, ValueType::List) => Some(0), - // TODO: once ParamType supports parameters we can calculate the proper distance to Tuple + // TODO: once StaticType supports parameters we can calculate the proper distance to Tuple (Self::Tuple, ValueType::Tuple(_)) => Some(0), (Self::Map, ValueType::Map) => Some(0), (Self::Iterator, ValueType::Iterator) => Some(0), @@ -519,8 +519,8 @@ impl ParamType { } } -/// Converts the concrete type of a value to the specific `ParamType` -impl From<&Value> for ParamType { +/// Converts the concrete type of a value to the specific `StaticType` +impl From<&Value> for StaticType { fn from(value: &Value) -> Self { match value { Value::Option(_) => Self::Option, @@ -542,7 +542,7 @@ impl From<&Value> for ParamType { } } -impl fmt::Display for ParamType { +impl fmt::Display for StaticType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } @@ -552,7 +552,7 @@ impl fmt::Display for ParamType { pub enum FunctionCallError { #[error("invalid argument, expected {expected} got {actual}")] ArgumentTypeError { - expected: ParamType, + expected: StaticType, actual: ValueType, }, diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index ac1d6037..063c20d0 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -72,7 +72,7 @@ impl Interpreter { } // dbg!(&expressions); - // dbg!(&self.lexical_data); + dbg!(&self.lexical_data); let final_value = self.interpret(expressions.into_iter())?; diff --git a/ndc_lib/src/interpreter/semantic/resolve.rs b/ndc_lib/src/interpreter/semantic/resolve.rs index 87e9a58c..d5d85523 100644 --- a/ndc_lib/src/interpreter/semantic/resolve.rs +++ b/ndc_lib/src/interpreter/semantic/resolve.rs @@ -1,6 +1,5 @@ use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; -use crate::interpreter::function::{ParamType, Parameter}; -use crate::interpreter::value::ValueType; +use crate::interpreter::function::StaticType; use crate::lexer::Span; use itertools::Itertools; @@ -43,7 +42,7 @@ pub fn resolve_pass( } Expression::VariableDeclaration { l_value, value } => { resolve_pass(value, lexical_data)?; - resolve_lvalue_declarative(l_value, lexical_data)?; + resolve_lvalue_declarative(l_value, resolve_type(value, lexical_data), lexical_data)?; } Expression::Assignment { l_value, r_value } => { resolve_lvalue(l_value, *span, lexical_data)?; @@ -101,7 +100,7 @@ pub fn resolve_pass( *resolved_name = Some( lexical_data - .get_or_create_local_binding(function_ident.clone(), ValueType::Function), + .get_or_create_local_binding(function_ident.clone(), StaticType::Function), ) } @@ -233,8 +232,11 @@ fn resolve_for_iterations( match iteration { ForIteration::Iteration { l_value, sequence } => { resolve_pass(sequence, lexical_data)?; + lexical_data.new_scope(); - resolve_lvalue_declarative(l_value, lexical_data)?; + + // TODO: inferring this as any is a massive cop-out + resolve_lvalue_declarative(l_value, StaticType::Any, lexical_data)?; do_destroy = true; } ForIteration::Guard(expr) => { @@ -345,12 +347,13 @@ fn resolve_parameters_declarative( LexicalIdentifier::Variable { name: (*name).clone(), }, - ParamType::Any, + StaticType::Any, )); } } fn resolve_lvalue_declarative( lvalue: &mut Lvalue, + typ: StaticType, lexical_data: &mut LexicalData, ) -> Result<(), ResolveError> { match lvalue { @@ -358,11 +361,12 @@ fn resolve_lvalue_declarative( identifier, resolved, } => { - *resolved = Some( - lexical_data.create_local_binding(LexicalIdentifier::Variable { + *resolved = Some(lexical_data.create_local_binding( + LexicalIdentifier::Variable { name: (*identifier).clone(), - }), - ); + }, + typ, + )); } Lvalue::Index { index, value } => { resolve_pass(index, lexical_data)?; @@ -370,7 +374,7 @@ fn resolve_lvalue_declarative( } Lvalue::Sequence(seq) => { for sub_lvalue in seq { - resolve_lvalue_declarative(sub_lvalue, lexical_data)? + resolve_lvalue_declarative(sub_lvalue, typ.clone(), lexical_data)? } } } @@ -378,6 +382,99 @@ fn resolve_lvalue_declarative( Ok(()) } +fn resolve_type( + ExpressionLocation { expression, .. }: &ExpressionLocation, + lex_data: &mut LexicalData, +) -> StaticType { + match expression { + Expression::BoolLiteral(_) => StaticType::Bool, + Expression::StringLiteral(_) => StaticType::String, + Expression::Int64Literal(_) => StaticType::Int, + Expression::Float64Literal(_) => StaticType::Float, + Expression::BigIntLiteral(_) => StaticType::Int, + Expression::ComplexLiteral(_) => StaticType::Complex, + Expression::Identifier { resolved, name } => { + println!("resolving ident {name}"); + lex_data.get_type( + resolved.expect( + "previously mentioned identifier was not resolved during type resolution", + ), + ) + } + Expression::Statement(_) => StaticType::Tuple, // specifically a unit tuple + Expression::Logical { .. } => StaticType::Bool, + Expression::Grouping(expr) => resolve_type(expr, lex_data), + Expression::VariableDeclaration { .. } => { + debug_assert!( + false, + "trying to get type of variable declaration, does this make sense?" + ); + StaticType::Tuple // specifically unit tuple + } + Expression::Assignment { .. } => { + // debug_assert!( + // false, + // "trying to get type of assignment, does this make sense?" + // ); + StaticType::Tuple // specifically unit tuple + } + Expression::OpAssignment { .. } => { + debug_assert!( + false, + "trying to get type of op assignment, does this make sense?" + ); + StaticType::Tuple // specifically unit tuple + } + Expression::FunctionDeclaration { .. } => StaticType::Function, + Expression::Block { statements } => statements + .iter() + .last() + .map_or(StaticType::Tuple, |last| resolve_type(last, lex_data)), + Expression::If { + on_true, on_false, .. + } => { + let on_false_type = on_false + .as_ref() + .map_or(StaticType::Tuple, |expr| resolve_type(expr, lex_data)); + + assert_eq!( + resolve_type(&*on_true, lex_data), + on_false_type, + "if branches have different types" + ); + on_false_type + } + Expression::While { .. } => StaticType::Tuple, + Expression::For { body, .. } => match &**body { + ForBody::Block(_) => StaticType::Tuple, + ForBody::List(_) => StaticType::List, + ForBody::Map { .. } => StaticType::Map, + }, + Expression::Call { + function: _, + arguments: _, + } => { + // TODO: Okay this is actually hard + StaticType::Any + } + Expression::Index { .. } => { + // TODO: this is also hard since we don't have generics + StaticType::Any + } + Expression::Tuple { .. } => { + // TODO: this is hard + StaticType::Any + } + Expression::List { .. } => StaticType::List, + Expression::Map { .. } => StaticType::Map, + Expression::Return { value: expr } => resolve_type(expr, lex_data), + Expression::Break => StaticType::Tuple, + Expression::Continue => StaticType::Any, // Maybe we need a Never type? + Expression::RangeInclusive { .. } => StaticType::Iterator, + Expression::RangeExclusive { .. } => StaticType::Iterator, + } +} + #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub enum LexicalIdentifier { Variable { name: String }, @@ -419,13 +516,32 @@ impl LexicalData { //TODO: maybe the line below is more of temporary solution identifiers: global_scope_map .into_iter() - .map(|x| (x, ValueType::Function)) + .map(|x| (x, StaticType::Function)) .collect_vec(), }, scopes: vec![LexicalScope::new(None)], } } + fn get_type(&self, res: ResolvedVar) -> StaticType { + // dbg!(self, res); + match res { + ResolvedVar::Captured { slot, depth } => { + let mut scope_idx = self.current_scope_idx; + let mut depth = depth; + while depth > 0 { + depth -= 1; + scope_idx = self.scopes[scope_idx] + .parent_idx + .expect("parent_idx was None while traversing the scope tree"); + } + self.scopes[scope_idx].identifiers[slot].1.clone() + } + // for now all globals are functions + ResolvedVar::Global { .. } => StaticType::Function, + } + } + fn new_scope(&mut self) -> &LexicalScope { let old_scope_idx = self.current_scope_idx; self.current_scope_idx = self.scopes.len(); @@ -444,12 +560,12 @@ impl LexicalData { fn get_or_create_local_binding( &mut self, ident: LexicalIdentifier, - value_type: ValueType, + typ: StaticType, ) -> ResolvedVar { ResolvedVar::Captured { slot: self.scopes[self.current_scope_idx] .get_slot(&ident) - .unwrap_or_else(|| self.scopes[self.current_scope_idx].allocate(ident, value_type)), + .unwrap_or_else(|| self.scopes[self.current_scope_idx].allocate(ident, typ)), depth: 0, } } @@ -490,9 +606,9 @@ impl LexicalData { } } - fn create_local_binding(&mut self, ident: LexicalIdentifier, vtype: ValueType) -> ResolvedVar { + fn create_local_binding(&mut self, ident: LexicalIdentifier, typ: StaticType) -> ResolvedVar { ResolvedVar::Captured { - slot: self.scopes[self.current_scope_idx].allocate(ident, vtype), + slot: self.scopes[self.current_scope_idx].allocate(ident, typ), depth: 0, } } @@ -501,7 +617,7 @@ impl LexicalData { #[derive(Debug)] struct LexicalScope { parent_idx: Option, - identifiers: Vec<(LexicalIdentifier, ValueType)>, + identifiers: Vec<(LexicalIdentifier, StaticType)>, } impl LexicalScope { @@ -518,21 +634,14 @@ impl LexicalScope { .rposition(|(ident, _)| ident.name() == find_ident) } - /// Either returns the slot in this current scope or creates a new one - #[deprecated = "use get_slot_by_ident instead"] - pub fn get_or_allocate(&mut self, ident: LexicalIdentifier) -> usize { - self.get_slot(&ident) - .unwrap_or_else(|| self.allocate(ident, todo!("we don't know"))) - } - fn get_slot(&mut self, find_ident: &LexicalIdentifier) -> Option { self.identifiers .iter() .rposition(|(ident, _type)| ident == find_ident) } - fn allocate(&mut self, name: LexicalIdentifier, vtype: ValueType) -> usize { - self.identifiers.push((name, vtype)); + fn allocate(&mut self, name: LexicalIdentifier, typ: StaticType) -> usize { + self.identifiers.push((name, typ)); // Slot is just the length of the list minus one self.identifiers.len() - 1 } diff --git a/ndc_lib/src/stdlib/math.rs b/ndc_lib/src/stdlib/math.rs index c7973b73..6212711b 100644 --- a/ndc_lib/src/stdlib/math.rs +++ b/ndc_lib/src/stdlib/math.rs @@ -210,7 +210,7 @@ mod inner { pub mod f64 { use super::{Environment, Number, ToPrimitive, f64}; use crate::interpreter::function::{ - FunctionBody, FunctionBuilder, FunctionCarrier, ParamType, Parameter, TypeSignature, + FunctionBody, FunctionBuilder, FunctionCarrier, Parameter, StaticType, TypeSignature, }; use crate::interpreter::num::BinaryOperatorError; use crate::interpreter::value::Value; @@ -256,8 +256,8 @@ pub mod f64 { .name($operator.to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => match left.partial_cmp(&right) { @@ -284,8 +284,8 @@ pub mod f64 { .name("==".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => Ok(Value::Bool(left == right)), @@ -301,8 +301,8 @@ pub mod f64 { .name("!=".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => Ok(Value::Bool(left != right)), @@ -318,8 +318,8 @@ pub mod f64 { .name("<=>".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => match left.partial_cmp(&right) { @@ -340,8 +340,8 @@ pub mod f64 { .name(">=<".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Any), - Parameter::new("right", ParamType::Any), + Parameter::new("left", StaticType::Any), + Parameter::new("right", StaticType::Any), ]), function: |values, _env| match values { [left, right] => match left.partial_cmp(&right) { @@ -364,8 +364,8 @@ pub mod f64 { .name($operator.to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Bool), - Parameter::new("right", ParamType::Bool), + Parameter::new("left", StaticType::Bool), + Parameter::new("right", StaticType::Bool), ]), function: |values, _env| match values { [Value::Bool(left), Value::Bool(right)] => Ok(Value::Bool($operation(*left, *right))), @@ -380,8 +380,8 @@ pub mod f64 { .name($operator.to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Int), - Parameter::new("right", ParamType::Int), + Parameter::new("left", StaticType::Int), + Parameter::new("right", StaticType::Int), ]), function: |values, _env| match values { // TODO: remove this clone @@ -411,7 +411,7 @@ pub mod f64 { env.declare_global_fn( FunctionBuilder::default() .body(FunctionBody::GenericFunction { - type_signature: TypeSignature::Exact(vec![Parameter::new("value", ParamType::Bool)]), + type_signature: TypeSignature::Exact(vec![Parameter::new("value", StaticType::Bool)]), function: |values, _env| match values { [Value::Bool(b)] => Ok(Value::Bool(b.not())), _ => unreachable!("the type checker should never invoke this function if the argument count does not match"), @@ -428,8 +428,8 @@ pub mod f64 { .name(">>".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Int), - Parameter::new("right", ParamType::Int), + Parameter::new("left", StaticType::Int), + Parameter::new("right", StaticType::Int), ]), function: |values, _env| match values { [Value::Number(Number::Int(left)), Value::Number(Number::Int(right))] => left.clone().checked_shr(right.clone()) @@ -447,8 +447,8 @@ pub mod f64 { .name("<<".to_string()) .body(FunctionBody::GenericFunction { type_signature: TypeSignature::Exact(vec![ - Parameter::new("left", ParamType::Int), - Parameter::new("right", ParamType::Int), + Parameter::new("left", StaticType::Int), + Parameter::new("right", StaticType::Int), ]), function: |values, _env| match values { [Value::Number(Number::Int(left)), Value::Number(Number::Int(right))] => left.clone().checked_shl(right.clone()) diff --git a/ndc_macros/src/convert.rs b/ndc_macros/src/convert.rs index c557530a..8dde49cf 100644 --- a/ndc_macros/src/convert.rs +++ b/ndc_macros/src/convert.rs @@ -33,7 +33,7 @@ impl TypeConverter for MutRefString { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::String }, + param_type: quote! { crate::interpreter::function::StaticType::String }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -60,7 +60,7 @@ impl TypeConverter for InternalMap { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: quote! { crate::interpreter::function::StaticType::Map }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -86,7 +86,7 @@ impl TypeConverter for InternalString { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::String }, + param_type: quote! { crate::interpreter::function::StaticType::String }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -113,7 +113,7 @@ impl TypeConverter for InternalList { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -141,7 +141,7 @@ impl TypeConverter for InternalTuple { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Tuple }, + param_type: quote! { crate::interpreter::function::StaticType::Tuple }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index 4887c035..f96b338b 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -223,53 +223,55 @@ fn wrap_single( fn into_param_type(ty: &syn::Type) -> TokenStream { match ty { - ty if path_ends_with(ty, "Vec") => quote! { crate::interpreter::function::ParamType::List }, + ty if path_ends_with(ty, "Vec") => { + quote! { crate::interpreter::function::StaticType::List } + } ty if path_ends_with(ty, "VecDeque") => { - quote! { crate::interpreter::function::ParamType::Deque } + quote! { crate::interpreter::function::StaticType::Deque } } ty if path_ends_with(ty, "DefaultMap") || path_ends_with(ty, "DefaultMapMut") || path_ends_with(ty, "HashMap") => { - quote! { crate::interpreter::function::ParamType::Map } + quote! { crate::interpreter::function::StaticType::Map } } ty if path_ends_with(ty, "MinHeap") => { - quote! { crate::interpreter::function::ParamType::MinHeap } + quote! { crate::interpreter::function::StaticType::MinHeap } } ty if path_ends_with(ty, "MaxHeap") => { - quote! { crate::interpreter::function::ParamType::MaxHeap } + quote! { crate::interpreter::function::StaticType::MaxHeap } } ty if path_ends_with(ty, "ListRepr") => { - quote! { crate::interpreter::function::ParamType::List } + quote! { crate::interpreter::function::StaticType::List } } ty if path_ends_with(ty, "TupleRepr") => { - quote! { crate::interpreter::function::ParamType::Tuple } + quote! { crate::interpreter::function::StaticType::Tuple } } ty if path_ends_with(ty, "MapRepr") => { - quote! { crate::interpreter::function::ParamType::Map } + quote! { crate::interpreter::function::StaticType::Map } } syn::Type::Reference(syn::TypeReference { elem, .. }) => into_param_type(elem), syn::Type::Path(syn::TypePath { path, .. }) => match path { - _ if path.is_ident("i64") => quote! { crate::interpreter::function::ParamType::Int }, - _ if path.is_ident("usize") => quote! { crate::interpreter::function::ParamType::Int }, - _ if path.is_ident("f64") => quote! { crate::interpreter::function::ParamType::Float }, - _ if path.is_ident("bool") => quote! { crate::interpreter::function::ParamType::Bool }, + _ if path.is_ident("i64") => quote! { crate::interpreter::function::StaticType::Int }, + _ if path.is_ident("usize") => quote! { crate::interpreter::function::StaticType::Int }, + _ if path.is_ident("f64") => quote! { crate::interpreter::function::StaticType::Float }, + _ if path.is_ident("bool") => quote! { crate::interpreter::function::StaticType::Bool }, _ if path.is_ident("Value") => { - quote! { crate::interpreter::function::ParamType::Any } + quote! { crate::interpreter::function::StaticType::Any } } _ if path.is_ident("Number") => { - quote! { crate::interpreter::function::ParamType::Number } + quote! { crate::interpreter::function::StaticType::Number } } _ if path.is_ident("Sequence") => { - quote! { crate::interpreter::function::ParamType::Sequence } + quote! { crate::interpreter::function::StaticType::Sequence } } _ if path.is_ident("Callable") => { - quote! { crate::interpreter::function::ParamType::Function } + quote! { crate::interpreter::function::StaticType::Function } } - _ => panic!("Don't know how to convert Path into ParamType\n\n{path:?}"), + _ => panic!("Don't know how to convert Path into StaticType\n\n{path:?}"), }, - syn::Type::ImplTrait(_) => quote! { crate::interpreter::function::ParamType::Iterator }, - x => panic!("Don't know how to convert {x:?} into ParamType"), + syn::Type::ImplTrait(_) => quote! { crate::interpreter::function::StaticType::Iterator }, + x => panic!("Don't know how to convert {x:?} into StaticType"), } } @@ -297,7 +299,7 @@ fn create_temp_variable( if path_ends_with(ty, "Callable") { let temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Function }, + param_type: quote! { crate::interpreter::function::StaticType::Function }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -316,7 +318,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: quote! { crate::interpreter::function::StaticType::Map }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -332,7 +334,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: quote! { crate::interpreter::function::StaticType::Map }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -348,7 +350,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: quote! { crate::interpreter::function::StaticType::Map }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -364,7 +366,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Map }, + param_type: quote! { crate::interpreter::function::StaticType::Map }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -381,7 +383,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -493,7 +495,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::String }, + param_type: quote! { crate::interpreter::function::StaticType::String }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -509,7 +511,7 @@ fn create_temp_variable( else if is_ref_of_bigint(ty) { let big_int = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Int }, + param_type: quote! { crate::interpreter::function::StaticType::Int }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -530,7 +532,7 @@ fn create_temp_variable( // If we need an owned Value else if path_ends_with(ty, "Value") && !is_ref(ty) { return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Any }, + param_type: quote! { crate::interpreter::function::StaticType::Any }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -543,7 +545,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -560,7 +562,7 @@ fn create_temp_variable( syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![ Argument { - param_type: quote! { crate::interpreter::function::ParamType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -571,7 +573,7 @@ fn create_temp_variable( }, }, Argument { - param_type: quote! { crate::interpreter::function::ParamType::Tuple }, + param_type: quote! { crate::interpreter::function::StaticType::Tuple }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -586,7 +588,7 @@ fn create_temp_variable( // The pattern is &BigRational else if path_ends_with(ty, "BigRational") && is_ref(ty) { return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Rational }, + param_type: quote! { crate::interpreter::function::StaticType::Rational }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -601,7 +603,7 @@ fn create_temp_variable( // The pattern is BigRational else if path_ends_with(ty, "BigRational") && !is_ref(ty) { return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Rational }, + param_type: quote! { crate::interpreter::function::StaticType::Rational }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -616,7 +618,7 @@ fn create_temp_variable( // The pattern is Complex64 else if path_ends_with(ty, "Complex64") && !is_ref(ty) { return vec![Argument { - param_type: quote! { crate::interpreter::function::ParamType::Complex }, + param_type: quote! { crate::interpreter::function::StaticType::Complex }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { From e0d47302d902f09b7c840184355a6c57ab9d69ad Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 16:13:16 +0100 Subject: [PATCH 04/35] Collapse Value and Param type into StaticType --- ndc_lib/src/interpreter/evaluate/index.rs | 6 +- ndc_lib/src/interpreter/evaluate/mod.rs | 24 +-- ndc_lib/src/interpreter/function.rs | 135 +++++++-------- ndc_lib/src/interpreter/iterator.rs | 8 +- ndc_lib/src/interpreter/mod.rs | 6 +- ndc_lib/src/interpreter/num.rs | 31 +++- ndc_lib/src/interpreter/semantic/resolve.rs | 37 ++--- ndc_lib/src/interpreter/sequence.rs | 21 +-- ndc_lib/src/interpreter/value.rs | 154 +++++------------- ndc_lib/src/stdlib/list.rs | 33 ++-- ndc_lib/src/stdlib/math.rs | 14 +- ndc_lib/src/stdlib/sequence.rs | 6 +- ndc_macros/src/convert.rs | 59 +++---- ndc_macros/src/function.rs | 25 ++- tests/programs/006_lists/019_tuple_cow.ndct | 10 +- .../programs/006_lists/039_tuple_concat.ndct | 15 +- tests/programs/006_lists/040_tuple_cow.ndct | 19 ++- 17 files changed, 272 insertions(+), 331 deletions(-) diff --git a/ndc_lib/src/interpreter/evaluate/index.rs b/ndc_lib/src/interpreter/evaluate/index.rs index 690dcd6a..c9c186fe 100644 --- a/ndc_lib/src/interpreter/evaluate/index.rs +++ b/ndc_lib/src/interpreter/evaluate/index.rs @@ -230,7 +230,7 @@ pub fn get_at_index( .clone()) } _ => Err(EvaluationError::syntax_error( - format!("cannot insert into {} at index", lhs.value_type()), + format!("cannot insert into {} at index", lhs.static_type()), span, ) .into()), @@ -294,7 +294,7 @@ pub fn set_at_index( } } else { return Err(EvaluationError::syntax_error( - format!("cannot insert {} into a string", rhs.value_type()), + format!("cannot insert {} into a string", rhs.static_type()), span, ) .into()); @@ -317,7 +317,7 @@ pub fn set_at_index( } _ => { return Err(EvaluationError::syntax_error( - format!("cannot insert into {} at index", lhs.value_type()), + format!("cannot insert into {} at index", lhs.static_type()), span, ) .into()); diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index ab4ab187..4b3bc9c1 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -1,12 +1,14 @@ use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, LogicalOperator, Lvalue}; use crate::hash_map::HashMap; use crate::interpreter::environment::Environment; -use crate::interpreter::function::{Function, FunctionBody, FunctionCarrier, OverloadedFunction}; +use crate::interpreter::function::{ + Function, FunctionBody, FunctionCarrier, OverloadedFunction, StaticType, +}; use crate::interpreter::int::Int; use crate::interpreter::iterator::mut_value_to_iterator; use crate::interpreter::num::Number; use crate::interpreter::sequence::Sequence; -use crate::interpreter::value::{Value, ValueType}; +use crate::interpreter::value::Value; use crate::lexer::Span; use index::{Offset, evaluate_as_index, get_at_index, set_at_index}; use itertools::Itertools; @@ -116,7 +118,7 @@ pub(crate) fn evaluate_expression( if operations_to_try.peek().is_none() => { let argument_string = - arguments.iter().map(Value::value_type).join(", "); + arguments.iter().map(Value::static_type).join(", "); return Err(FunctionCarrier::EvaluationError( EvaluationError::new( @@ -178,7 +180,7 @@ pub(crate) fn evaluate_expression( { let argument_string = [value_at_index.clone(), right_value.clone()] .iter() - .map(Value::value_type) + .map(Value::static_type) .join(", "); return Err(FunctionCarrier::EvaluationError( @@ -239,8 +241,8 @@ pub(crate) fn evaluate_expression( return Err(EvaluationError::new( format!( "mismatched types: expected {}, found {}", - ValueType::Bool, - ValueType::from(&value) + StaticType::Bool, + value.static_type() ), span, ) @@ -269,7 +271,7 @@ pub(crate) fn evaluate_expression( return Err(EvaluationError::new( format!( "Cannot apply logical operator to non bool value {}", - ValueType::from(&value) + value.static_type() ), span, ) @@ -322,7 +324,7 @@ pub(crate) fn evaluate_expression( match call_function(&function, &mut evaluated_args, environment, span) { Err(FunctionCarrier::FunctionNotFound) => { let argument_string = - evaluated_args.iter().map(Value::value_type).join(", "); + evaluated_args.iter().map(Value::static_type).join(", "); return Err(FunctionCarrier::EvaluationError(EvaluationError::new( format!( @@ -587,7 +589,7 @@ pub(crate) fn evaluate_expression( } value => { return Err(EvaluationError::new( - format!("cannot index into {}", value.value_type()), + format!("cannot index into {}", value.static_type()), lhs_expr.span, ) .into()); @@ -966,8 +968,8 @@ fn execute_for_iterations( return Err(EvaluationError::type_error( format!( "mismatched types: expected {}, found {}", - ValueType::Bool, - ValueType::from(&value) + StaticType::Bool, + value.static_type(), ), span, ) diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index a70f6ba2..e546b41b 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -4,9 +4,9 @@ use crate::interpreter::environment::Environment; use crate::interpreter::evaluate::{ ErrorConverter, EvaluationError, EvaluationResult, evaluate_expression, }; -use crate::interpreter::num::{BinaryOperatorError, Number, NumberType}; +use crate::interpreter::num::{BinaryOperatorError, Number}; use crate::interpreter::sequence::Sequence; -use crate::interpreter::value::{Value, ValueType}; +use crate::interpreter::value::Value; use crate::lexer::Span; use derive_builder::Builder; use itertools::Itertools; @@ -172,7 +172,7 @@ impl FunctionBody { [Value::Number(num)] => Ok(Value::Number(body(num.clone()))), [v] => Err(FunctionCallError::ArgumentTypeError { expected: StaticType::Number, - actual: v.value_type(), + actual: v.static_type(), } .into()), args => Err(FunctionCallError::ArgumentCountError { @@ -188,12 +188,12 @@ impl FunctionBody { )), [Value::Number(_), right] => Err(FunctionCallError::ArgumentTypeError { expected: StaticType::Number, - actual: right.value_type(), + actual: right.static_type(), } .into()), [left, _] => Err(FunctionCallError::ArgumentTypeError { expected: StaticType::Number, - actual: left.value_type(), + actual: left.static_type(), } .into()), args => Err(FunctionCallError::ArgumentCountError { @@ -236,14 +236,20 @@ 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`. - fn calc_type_score(&self, types: &[ValueType]) -> Option { + 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 = b.type_name.distance(a)?; + let dist = if a == &b.type_name { + 0 + } else if a.is_subtype_of(&b.type_name) { + 1 + } else { + return None; + }; acc += dist; } @@ -328,7 +334,7 @@ impl OverloadedFunction { } pub fn call(&self, args: &mut [Value], env: &Rc>) -> EvaluationResult { - let types: Vec = args.iter().map(ValueType::from).collect(); + let types: Vec = args.iter().map(Value::static_type).collect(); let mut best_function_match = None; let mut best_distance = u32::MAX; @@ -449,7 +455,7 @@ pub enum StaticType { Sequence, List, String, - Tuple, + Tuple(Vec), Map, Iterator, MinHeap, @@ -458,40 +464,62 @@ pub enum StaticType { } impl StaticType { - fn distance(&self, other: &ValueType) -> Option { - #[allow(clippy::match_same_arms)] + #[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::Bool, ValueType::Bool) => Some(0), - (Self::Option, ValueType::Option) => Some(0), - (Self::Int, ValueType::Number(NumberType::Int)) => Some(0), - (Self::Float, ValueType::Number(NumberType::Float)) => Some(0), - (Self::Rational, ValueType::Number(NumberType::Rational)) => Some(0), - (Self::Complex, ValueType::Number(NumberType::Complex)) => Some(0), - (Self::String, ValueType::String) => Some(0), - (Self::List, ValueType::List) => Some(0), - // TODO: once StaticType supports parameters we can calculate the proper distance to Tuple - (Self::Tuple, ValueType::Tuple(_)) => Some(0), - (Self::Map, ValueType::Map) => Some(0), - (Self::Iterator, ValueType::Iterator) => Some(0), - (Self::Function, ValueType::Function) => Some(0), - (Self::Deque, ValueType::Deque) => Some(0), - (Self::MinHeap, ValueType::MinHeap) => Some(0), - (Self::MaxHeap, ValueType::MaxHeap) => Some(0), - (Self::Any, _) => Some(2), - (Self::Number, ValueType::Number(_)) => Some(1), + (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, + } + } + + #[allow(clippy::match_like_matches_macro)] + pub fn is_subtype_of(&self, other: &Self) -> bool { + match (self, other) { + (_, Self::Any) => true, + (Self::Int | Self::Rational | Self::Complex | Self::Float, Self::Number) => true, ( + Self::String + | Self::List + | Self::Deque + | Self::MaxHeap + | Self::MinHeap + | Self::Tuple(_) + | Self::Iterator + | Self::Map, Self::Sequence, - ValueType::List - | ValueType::String - | ValueType::Map - // Sequence is always 1 distance to tuple - | ValueType::Tuple(_) - | ValueType::Iterator - | ValueType::MinHeap - | ValueType::MaxHeap - | ValueType::Deque, - ) => Some(1), - _ => None, + ) => true, + _ => false, } } @@ -509,7 +537,7 @@ impl StaticType { Self::Sequence => "Sequence", Self::List => "List", Self::String => "String", - Self::Tuple => "Tuple", + Self::Tuple(_) => "Tuple<...>", // TODO: what do we need alooc Self::Map => "Map", Self::Iterator => "Iterator", Self::MinHeap => "MinHeap", @@ -519,29 +547,6 @@ impl StaticType { } } -/// Converts the concrete type of a value to the specific `StaticType` -impl From<&Value> for StaticType { - fn from(value: &Value) -> Self { - match value { - Value::Option(_) => Self::Option, - Value::Number(Number::Rational(_)) => Self::Rational, - Value::Number(Number::Complex(_)) => Self::Complex, - Value::Number(Number::Int(_)) => Self::Int, - Value::Number(Number::Float(_)) => Self::Float, - Value::Bool(_) => Self::Bool, - Value::Sequence(Sequence::String(_)) => Self::String, - Value::Sequence(Sequence::List(_)) => Self::List, - Value::Sequence(Sequence::Tuple(_)) => Self::Tuple, - Value::Function(_) => Self::Function, - Value::Sequence(Sequence::Map(_, _)) => Self::Map, - Value::Sequence(Sequence::Iterator(_)) => Self::Iterator, - Value::Sequence(Sequence::MaxHeap(_)) => Self::MaxHeap, - Value::Sequence(Sequence::MinHeap(_)) => Self::MinHeap, - Value::Sequence(Sequence::Deque(_)) => Self::Deque, - } - } -} - impl fmt::Display for StaticType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) @@ -553,7 +558,7 @@ pub enum FunctionCallError { #[error("invalid argument, expected {expected} got {actual}")] ArgumentTypeError { expected: StaticType, - actual: ValueType, + actual: StaticType, }, #[error("invalid argument count, expected {expected} arguments got {actual}")] diff --git a/ndc_lib/src/interpreter/iterator.rs b/ndc_lib/src/interpreter/iterator.rs index d1a7238f..badae55d 100644 --- a/ndc_lib/src/interpreter/iterator.rs +++ b/ndc_lib/src/interpreter/iterator.rs @@ -3,13 +3,13 @@ //! The implementation of the various iterators in this module were heavily inspired by the ones in //! noulith which can be found [here](https://github.com/betaveros/noulith/blob/441d52ea433527b7ada5bc6cabd952f9ae8fb791/src/streams.rs) //! -use super::function::FunctionCarrier; +use super::function::{FunctionCarrier, StaticType}; use super::int::Int::Int64; use super::num::Number; use crate::hash_map::HashMap; use crate::interpreter::heap::{MaxHeap, MinHeap}; use crate::interpreter::sequence::Sequence; -use crate::interpreter::value::{Value, ValueType}; +use crate::interpreter::value::Value; use self_cell::self_cell; use std::cell::{Ref, RefCell}; use std::collections::VecDeque; @@ -67,7 +67,7 @@ impl Iterator for MutableValueIntoIterator<'_> { #[derive(thiserror::Error, Debug)] #[error("{} is not iterable", .value_type)] pub struct NotIterableError { - value_type: ValueType, + value_type: StaticType, } impl From for FunctionCarrier { @@ -82,7 +82,7 @@ pub fn mut_value_to_iterator( match value { Value::Sequence(sequence) => Ok(mut_seq_to_iterator(sequence)), value => Err(NotIterableError { - value_type: value.value_type(), + value_type: value.static_type(), }), } } diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index 063c20d0..6fc1e978 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -72,12 +72,12 @@ impl Interpreter { } // dbg!(&expressions); - dbg!(&self.lexical_data); + // dbg!(&self.lexical_data); let final_value = self.interpret(expressions.into_iter())?; if debug { - dbg!(&final_value, final_value.value_type()); + dbg!(&final_value, final_value.static_type()); } Ok(format!("{final_value}")) @@ -89,7 +89,7 @@ impl Interpreter { ) -> Result { let mut value = Value::unit(); for expr in expressions { - match evaluate_expression(&expr, &mut self.environment) { + match evaluate_expression(&expr, &self.environment) { Ok(val) => value = val, Err(FunctionCarrier::Return(_)) => { Err(EvaluationError::syntax_error( diff --git a/ndc_lib/src/interpreter/num.rs b/ndc_lib/src/interpreter/num.rs index 53f7abc7..03a190b7 100644 --- a/ndc_lib/src/interpreter/num.rs +++ b/ndc_lib/src/interpreter/num.rs @@ -4,9 +4,9 @@ use std::hash::{Hash, Hasher}; use std::num::TryFromIntError; use std::ops::{Add, Div, Mul, Neg, Not, Rem, Sub}; -use super::value::ValueType; use crate::ast::BinaryOperator; use crate::interpreter::evaluate::EvaluationError; +use crate::interpreter::function::StaticType; use crate::interpreter::int::Int; use crate::lexer::Span; use num::bigint::TryFromBigIntError; @@ -224,8 +224,8 @@ impl BinaryOperatorError { pub fn undefined_operation( operator: BinaryOperator, - left: &ValueType, - right: &ValueType, + left: &StaticType, + right: &StaticType, ) -> Self { Self(format!( "operator {operator} is not defined for {left} and {right}" @@ -377,7 +377,7 @@ impl Number { Self::Rational(Box::new(rat)) } - // TODO: change this to &'static str + #[deprecated = "remove this and just use static_type instead"] fn type_name(&self) -> &'static str { match self { Self::Int(_) => "int", @@ -387,6 +387,15 @@ impl Number { } } + pub fn static_type(&self) -> StaticType { + match self { + Self::Int(_) => StaticType::Int, + Self::Float(_) => StaticType::Float, + Self::Rational(_) => StaticType::Rational, + Self::Complex(_) => StaticType::Complex, + } + } + pub fn checked_rem_euclid(self, rhs: Self) -> Result { match (self, rhs) { (Self::Int(p1), Self::Int(p2)) => p1 @@ -397,8 +406,8 @@ impl Number { (Self::Float(p1), Self::Float(p2)) => Ok(Self::Float(p1.rem_euclid(p2))), (left, right) => Err(BinaryOperatorError::undefined_operation( BinaryOperator::EuclideanModulo, - &ValueType::from(left), - &ValueType::from(right), + &left.static_type(), + &right.static_type(), )), } } @@ -696,6 +705,7 @@ fn rational_to_complex(r: &BigRational) -> Complex { } #[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[deprecated = "use static type instead?"] pub enum NumberType { Int, Float, @@ -703,9 +713,14 @@ pub enum NumberType { Complex, } -impl From for ValueType { +impl From for StaticType { fn from(value: NumberType) -> Self { - Self::Number(value) + match value { + NumberType::Int => Self::Int, + NumberType::Float => Self::Float, + NumberType::Rational => Self::Rational, + NumberType::Complex => Self::Complex, + } } } diff --git a/ndc_lib/src/interpreter/semantic/resolve.rs b/ndc_lib/src/interpreter/semantic/resolve.rs index d5d85523..80eac232 100644 --- a/ndc_lib/src/interpreter/semantic/resolve.rs +++ b/ndc_lib/src/interpreter/semantic/resolve.rs @@ -389,19 +389,14 @@ fn resolve_type( match expression { Expression::BoolLiteral(_) => StaticType::Bool, Expression::StringLiteral(_) => StaticType::String, - Expression::Int64Literal(_) => StaticType::Int, + Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => StaticType::Int, Expression::Float64Literal(_) => StaticType::Float, - Expression::BigIntLiteral(_) => StaticType::Int, Expression::ComplexLiteral(_) => StaticType::Complex, - Expression::Identifier { resolved, name } => { - println!("resolving ident {name}"); - lex_data.get_type( - resolved.expect( - "previously mentioned identifier was not resolved during type resolution", - ), - ) - } - Expression::Statement(_) => StaticType::Tuple, // specifically a unit tuple + Expression::Identifier { resolved, name } => lex_data.get_type( + resolved + .expect("previously mentioned identifier was not resolved during type resolution"), + ), + Expression::Statement(_) => StaticType::unit(), Expression::Logical { .. } => StaticType::Bool, Expression::Grouping(expr) => resolve_type(expr, lex_data), Expression::VariableDeclaration { .. } => { @@ -409,44 +404,44 @@ fn resolve_type( false, "trying to get type of variable declaration, does this make sense?" ); - StaticType::Tuple // specifically unit tuple + StaticType::unit() // specifically unit tuple } Expression::Assignment { .. } => { // debug_assert!( // false, // "trying to get type of assignment, does this make sense?" // ); - StaticType::Tuple // specifically unit tuple + StaticType::unit() // specifically unit tuple } Expression::OpAssignment { .. } => { debug_assert!( false, "trying to get type of op assignment, does this make sense?" ); - StaticType::Tuple // specifically unit tuple + StaticType::unit() // specifically unit tuple } Expression::FunctionDeclaration { .. } => StaticType::Function, Expression::Block { statements } => statements .iter() .last() - .map_or(StaticType::Tuple, |last| resolve_type(last, lex_data)), + .map_or(StaticType::unit(), |last| resolve_type(last, lex_data)), Expression::If { on_true, on_false, .. } => { let on_false_type = on_false .as_ref() - .map_or(StaticType::Tuple, |expr| resolve_type(expr, lex_data)); + .map_or(StaticType::unit(), |expr| resolve_type(expr, lex_data)); assert_eq!( - resolve_type(&*on_true, lex_data), + resolve_type(on_true, lex_data), on_false_type, "if branches have different types" ); on_false_type } - Expression::While { .. } => StaticType::Tuple, + Expression::While { .. } => StaticType::unit(), Expression::For { body, .. } => match &**body { - ForBody::Block(_) => StaticType::Tuple, + ForBody::Block(_) => StaticType::unit(), ForBody::List(_) => StaticType::List, ForBody::Map { .. } => StaticType::Map, }, @@ -468,7 +463,7 @@ fn resolve_type( Expression::List { .. } => StaticType::List, Expression::Map { .. } => StaticType::Map, Expression::Return { value: expr } => resolve_type(expr, lex_data), - Expression::Break => StaticType::Tuple, + Expression::Break => StaticType::unit(), Expression::Continue => StaticType::Any, // Maybe we need a Never type? Expression::RangeInclusive { .. } => StaticType::Iterator, Expression::RangeExclusive { .. } => StaticType::Iterator, @@ -484,7 +479,7 @@ pub enum LexicalIdentifier { impl LexicalIdentifier { pub fn name(&self) -> &str { match self { - LexicalIdentifier::Variable { name } | LexicalIdentifier::Function { name, .. } => name, + Self::Variable { name } | Self::Function { name, .. } => name, } } } diff --git a/ndc_lib/src/interpreter/sequence.rs b/ndc_lib/src/interpreter/sequence.rs index a92cc30c..7d165a80 100644 --- a/ndc_lib/src/interpreter/sequence.rs +++ b/ndc_lib/src/interpreter/sequence.rs @@ -1,7 +1,8 @@ use crate::hash_map::HashMap; +use crate::interpreter::function::StaticType; use crate::interpreter::heap::{MaxHeap, MinHeap}; use crate::interpreter::iterator::ValueIterator; -use crate::interpreter::value::{Value, ValueType}; +use crate::interpreter::value::Value; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::VecDeque; @@ -88,16 +89,16 @@ impl Sequence { } } - pub fn value_type(&self) -> ValueType { + pub fn value_type(&self) -> StaticType { match self { - Self::String(_) => ValueType::String, - Self::List(_) => ValueType::List, - Self::Tuple(t) => ValueType::Tuple(t.iter().map(Value::value_type).collect()), - Self::Map(_, _) => ValueType::Map, - Self::Iterator(_) => ValueType::Iterator, - Self::MaxHeap(_) => ValueType::MaxHeap, - Self::MinHeap(_) => ValueType::MinHeap, - Self::Deque(_) => ValueType::Deque, + Self::String(_) => StaticType::String, + Self::List(_) => StaticType::List, + Self::Tuple(t) => StaticType::Tuple(t.iter().map(Value::static_type).collect()), + Self::Map(_, _) => StaticType::Map, + Self::Iterator(_) => StaticType::Iterator, + Self::MaxHeap(_) => StaticType::MaxHeap, + Self::MinHeap(_) => StaticType::MinHeap, + Self::Deque(_) => StaticType::Deque, } } diff --git a/ndc_lib/src/interpreter/value.rs b/ndc_lib/src/interpreter/value.rs index 473e0be6..85433372 100644 --- a/ndc_lib/src/interpreter/value.rs +++ b/ndc_lib/src/interpreter/value.rs @@ -10,9 +10,9 @@ use num::BigInt; use crate::compare::FallibleOrd; use crate::hash_map::DefaultHasher; -use crate::interpreter::function::OverloadedFunction; +use crate::interpreter::function::{OverloadedFunction, StaticType}; use crate::interpreter::int::Int; -use crate::interpreter::num::{Number, NumberToFloatError, NumberToUsizeError, NumberType}; +use crate::interpreter::num::{Number, NumberToFloatError, NumberToUsizeError}; use crate::interpreter::sequence::Sequence; use super::iterator::{ValueIterator, ValueRange, ValueRangeFrom, ValueRangeInclusive}; @@ -125,16 +125,23 @@ impl Value { } #[must_use] - /// Returns the `ValueType` associated with this value - /// ``` - /// # use ndc_lib::interpreter::value::Value; - /// # use ndc_lib::interpreter::value::ValueType; - /// # use ndc_lib::interpreter::num::NumberType; - /// let val = Value::from(1); - /// assert_eq!(val.value_type(), NumberType::Int.into()); - /// ``` - pub fn value_type(&self) -> ValueType { - self.into() + pub fn static_type(&self) -> StaticType { + match self { + Self::Option(_) => StaticType::Option, + Self::Number(number) => number.static_type(), + Self::Bool(_) => StaticType::Bool, + Self::Sequence(Sequence::String(_)) => StaticType::String, + Self::Sequence(Sequence::List(_)) => StaticType::List, + Self::Sequence(Sequence::Tuple(t)) => { + StaticType::Tuple(t.iter().map(Self::static_type).collect()) + } + Self::Function(_) => StaticType::Function, + Self::Sequence(Sequence::Map(_, _)) => StaticType::Map, + Self::Sequence(Sequence::Iterator(_)) => StaticType::Iterator, + Self::Sequence(Sequence::MaxHeap(_)) => StaticType::MaxHeap, + Self::Sequence(Sequence::MinHeap(_)) => StaticType::MinHeap, + Self::Sequence(Sequence::Deque(_)) => StaticType::Deque, + } } #[must_use] @@ -153,8 +160,8 @@ impl Value { #[must_use] pub fn supports_vectorization_with(&self, other: &Self) -> bool { - self.value_type() - .supports_vectorization_with(&other.value_type()) + self.static_type() + .supports_vectorization_with(&other.static_type()) } } @@ -166,8 +173,8 @@ impl FallibleOrd for Value { self.partial_cmp(other).ok_or_else(|| { anyhow::anyhow!( "{} cannot be compared to {}", - self.value_type(), - other.value_type() + self.static_type(), + other.static_type() ) }) } @@ -181,8 +188,8 @@ impl FallibleOrd for &Value { self.partial_cmp(other).ok_or_else(|| { anyhow::anyhow!( "{} cannot be compared to {}", - self.value_type(), - other.value_type() + self.static_type(), + other.static_type() ) }) } @@ -432,7 +439,7 @@ impl From> for Value { #[derive(thiserror::Error, Debug)] pub enum ConversionError { #[error("Cannot convert {0} into {1}")] - UnsupportedVariant(ValueType, &'static str), + UnsupportedVariant(StaticType, &'static str), #[error("Cannot into {0} because the length is incorrect")] IncorrectLength(&'static str), @@ -452,7 +459,7 @@ impl TryFrom for (Value, Value) { fn try_from(value: Value) -> Result { let Value::Sequence(Sequence::Tuple(tuple)) = value else { return Err(ConversionError::UnsupportedVariant( - value.value_type(), + value.static_type(), stringify!((Value, Value)), )); }; @@ -475,7 +482,7 @@ impl TryFrom for i64 { type Error = ConversionError; fn try_from(value: Value) -> Result { - let typ = value.value_type(); + let typ = value.static_type(); if let Value::Number(Number::Int(Int::Int64(i))) = value { return Ok(i); } @@ -497,7 +504,7 @@ impl TryFrom<&mut Value> for f64 { match value { Value::Number(n) => Ok((&*n).try_into()?), v => Err(Self::Error::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(f64), )), } @@ -511,7 +518,7 @@ impl TryFrom<&mut Value> for i64 { match value { Value::Number(Number::Int(Int::Int64(i))) => Ok(*i), v => Err(Self::Error::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(i64), )), } @@ -525,7 +532,7 @@ impl TryFrom<&mut Value> for bool { match value { Value::Bool(bool) => Ok(*bool), v => Err(Self::Error::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(bool), )), } @@ -539,7 +546,7 @@ impl TryFrom for usize { match value { Value::Number(n) => Ok(Self::try_from(n)?), v => Err(Self::Error::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(usize), )), } @@ -553,7 +560,7 @@ impl TryFrom for Number { match value { Value::Number(n) => Ok(n), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(Number), )), } @@ -567,7 +574,7 @@ impl TryFrom for BigInt { Value::Number(Number::Int(Int::BigInt(b))) => Ok(b), Value::Number(Number::Int(Int::Int64(i))) => Ok(Self::from(i)), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(BigInt), )), } @@ -581,7 +588,7 @@ impl TryFrom<&mut Value> for usize { match value { Value::Number(number) => Ok(Self::try_from(number.clone())?), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(usize), )), } @@ -595,7 +602,7 @@ impl<'a> TryFrom<&'a mut Value> for &'a Sequence { match value { Value::Sequence(seq) => Ok(seq), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), stringify!(&Sequence), )), } @@ -609,100 +616,13 @@ impl<'a> TryFrom<&'a mut Value> for &'a Number { match value { Value::Number(n) => Ok(n), v => Err(ConversionError::UnsupportedVariant( - v.value_type(), + v.static_type(), "&Number", )), } } } -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum ValueType { - Option, // TODO: add type param - Number(NumberType), - Bool, - String, - List, - Tuple(Vec), - Function, - Map, - Iterator, - MinHeap, - MaxHeap, - Deque, -} - -impl ValueType { - #[must_use] - pub fn supports_vectorization(&self) -> bool { - matches!(self, Self::Tuple(values) if values.iter().all(|x| matches!(x, Self::Number(_)))) - } - - #[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 - } - (Self::Tuple(_), Self::Number(_)) if self.supports_vectorization() => true, - (Self::Number(_), Self::Tuple(_)) if other.supports_vectorization() => true, - _ => false, - } - } -} - -impl From<&Value> for ValueType { - fn from(value: &Value) -> Self { - match value { - Value::Option(_) => Self::Option, - Value::Number(n) => Self::Number(n.into()), - Value::Bool(_) => Self::Bool, - Value::Sequence(Sequence::String(_)) => Self::String, - Value::Sequence(Sequence::List(_)) => Self::List, - Value::Sequence(Sequence::Tuple(t)) => Self::Tuple(t.iter().map(Into::into).collect()), - Value::Function(_) => Self::Function, - Value::Sequence(Sequence::Map(_, _)) => Self::Map, - Value::Sequence(Sequence::Iterator(_)) => Self::Iterator, - Value::Sequence(Sequence::MaxHeap(_)) => Self::MaxHeap, - Value::Sequence(Sequence::MinHeap(_)) => Self::MinHeap, - Value::Sequence(Sequence::Deque(_)) => Self::Deque, - } - } -} - -impl From for ValueType { - fn from(value: Number) -> Self { - Self::Number((&value).into()) - } -} - -impl fmt::Display for ValueType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Option => write!(f, "Option"), - Self::Number(n) => write!(f, "{n}"), - Self::Bool => write!(f, "Bool"), - Self::String => write!(f, "String"), - Self::List => write!(f, "List"), - Self::Tuple(t) => { - write!(f, "tuple<{}>", t.iter().join(", ")) - } - Self::Function => write!(f, "Function"), - Self::Map => write!(f, "Map"), - Self::Iterator => write!(f, "Iterator"), - Self::MinHeap => write!(f, "MinHeap"), - Self::MaxHeap => write!(f, "MaxHeap"), - Self::Deque => write!(f, "Deque"), - } - } -} - impl fmt::Debug for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/ndc_lib/src/stdlib/list.rs b/ndc_lib/src/stdlib/list.rs index 0d2b2894..de505e15 100644 --- a/ndc_lib/src/stdlib/list.rs +++ b/ndc_lib/src/stdlib/list.rs @@ -1,7 +1,7 @@ #[ndc_macros::export_module] mod inner { use crate::interpreter::iterator::mut_seq_to_iterator; - use crate::interpreter::sequence::{ListRepr, Sequence, TupleRepr}; + use crate::interpreter::sequence::{ListRepr, Sequence}; use crate::interpreter::value::Value; use itertools::Itertools; use std::rc::Rc; @@ -66,21 +66,22 @@ mod inner { list.append(other); } - #[function(name = "++")] - pub fn tup_concat(left: TupleRepr, mut right: TupleRepr) -> Value { - match Rc::try_unwrap(left) { - Ok(mut left) => { - left.append(Rc::make_mut(&mut right)); - Value::tuple(left) - } - Err(left) => Value::tuple( - left.iter() - .chain(right.iter()) - .cloned() - .collect::>(), - ), - } - } + // A price we have to pay for the type system + // #[function(name = "++")] + // pub fn tup_concat(left: TupleRepr, mut right: TupleRepr) -> Value { + // match Rc::try_unwrap(left) { + // Ok(mut left) => { + // left.append(Rc::make_mut(&mut right)); + // Value::tuple(left) + // } + // Err(left) => Value::tuple( + // left.iter() + // .chain(right.iter()) + // .cloned() + // .collect::>(), + // ), + // } + // } #[function(name = "++")] pub fn list_concat(left: &mut ListRepr, right: &mut ListRepr) -> Value { diff --git a/ndc_lib/src/stdlib/math.rs b/ndc_lib/src/stdlib/math.rs index 6212711b..a4e60023 100644 --- a/ndc_lib/src/stdlib/math.rs +++ b/ndc_lib/src/stdlib/math.rs @@ -21,7 +21,7 @@ where Value::Number(n) => acc.add(n), value => Err(BinaryOperatorError::new(format!( "cannot sum {} and number", - value.value_type() + value.static_type() ))), }) } @@ -41,7 +41,7 @@ where Value::Number(n) => acc.mul(n), value => Err(BinaryOperatorError::new(format!( "cannot multiply {} and number", - value.value_type() + value.static_type() ))), }) } @@ -172,7 +172,7 @@ mod inner { } value => Err(anyhow::anyhow!( "cannot convert {} to float", - value.value_type() + value.static_type() )), } } @@ -201,7 +201,7 @@ mod inner { } value => Err(anyhow::anyhow!( "cannot convert {} to int", - value.value_type() + value.static_type() )), } } @@ -263,7 +263,7 @@ pub mod f64 { [left, right] => match left.partial_cmp(&right) { Some($expected) => Ok(Value::Bool(true)), Some(_) => Ok(Value::Bool(false)), - None => Err(anyhow::anyhow!("cannot compare {} and {}",left.value_type(),right.value_type()).into()), + None => Err(anyhow::anyhow!("cannot compare {} and {}",left.static_type(),right.static_type()).into()), }, _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, @@ -326,7 +326,7 @@ pub mod f64 { Some(Ordering::Equal) => Ok(Value::from(0)), Some(Ordering::Less) => Ok(Value::from(-1)), Some(Ordering::Greater) => Ok(Value::from(1)), - None => Err(anyhow::anyhow!("cannot compare {} and {}",left.value_type(),right.value_type()).into()), + None => Err(anyhow::anyhow!("cannot compare {} and {}",left.static_type(),right.static_type()).into()), }, _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, @@ -348,7 +348,7 @@ pub mod f64 { Some(Ordering::Equal) => Ok(Value::from(0)), Some(Ordering::Less) => Ok(Value::from(1)), Some(Ordering::Greater) => Ok(Value::from(-1)), - None => Err(anyhow::anyhow!("cannot compare {} and {}",left.value_type(),right.value_type()).into()), + None => Err(anyhow::anyhow!("cannot compare {} and {}",left.static_type(),right.static_type()).into()), }, _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, diff --git a/ndc_lib/src/stdlib/sequence.rs b/ndc_lib/src/stdlib/sequence.rs index b8db0733..4d3b57d7 100644 --- a/ndc_lib/src/stdlib/sequence.rs +++ b/ndc_lib/src/stdlib/sequence.rs @@ -367,7 +367,7 @@ mod inner { v => { return Err(anyhow!(format!( "invalid return type, predicate returned {}", - v.value_type() + v.static_type() )) .into()); } @@ -385,7 +385,7 @@ mod inner { v => { return Err(anyhow!(format!( "invalid return type, predicate returned {}", - v.value_type() + v.static_type() )) .into()); } @@ -404,7 +404,7 @@ mod inner { v => { return Err(anyhow!(format!( "invalid return type, predicate returned {}", - v.value_type() + v.static_type() )) .into()); } diff --git a/ndc_macros/src/convert.rs b/ndc_macros/src/convert.rs index 8dde49cf..438bf6de 100644 --- a/ndc_macros/src/convert.rs +++ b/ndc_macros/src/convert.rs @@ -127,40 +127,41 @@ impl TypeConverter for InternalList { } } -/// Matches `Rc>>` -struct InternalTuple; -impl TypeConverter for InternalTuple { - fn matches(&self, ty: &syn::Type) -> bool { - path_ends_with(ty, "TupleRepr") - } - - fn convert( - &self, - temp_var: syn::Ident, - original_name: &str, - argument_var_name: syn::Ident, - ) -> Vec { - vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::Tuple }, - param_name: quote! { #original_name }, - argument: quote! { #argument_var_name }, - initialize_code: quote! { - let crate::interpreter::value::Value::Sequence(crate::interpreter::sequence::Sequence::Tuple(#temp_var)) = #argument_var_name else { - panic!("Value #position needed to be Sequence::Tuple but wasn't"); - }; - - // TODO: is std::mem::take appropriate here? - let #argument_var_name = std::mem::take(#temp_var); - }, - }] - } -} +// Losing tuple concatenation is a price we might have to pay +// /// Matches `Rc>>` +// struct InternalTuple; +// impl TypeConverter for InternalTuple { +// fn matches(&self, ty: &syn::Type) -> bool { +// path_ends_with(ty, "TupleRepr") +// } +// +// fn convert( +// &self, +// temp_var: syn::Ident, +// original_name: &str, +// argument_var_name: syn::Ident, +// ) -> Vec { +// vec![Argument { +// param_type: quote! { crate::interpreter::function::StaticType::Tuple }, +// param_name: quote! { #original_name }, +// argument: quote! { #argument_var_name }, +// initialize_code: quote! { +// let crate::interpreter::value::Value::Sequence(crate::interpreter::sequence::Sequence::Tuple(#temp_var)) = #argument_var_name else { +// panic!("Value #position needed to be Sequence::Tuple but wasn't"); +// }; +// +// // TODO: is std::mem::take appropriate here? +// let #argument_var_name = std::mem::take(#temp_var); +// }, +// }] +// } +// } pub fn build() -> Vec> { vec![ Box::new(InternalList), Box::new(MutRefString), - Box::new(InternalTuple), + // Box::new(InternalTuple), Box::new(InternalMap), Box::new(InternalString), ] diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index f96b338b..87d0b9e6 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -244,9 +244,6 @@ fn into_param_type(ty: &syn::Type) -> TokenStream { ty if path_ends_with(ty, "ListRepr") => { quote! { crate::interpreter::function::StaticType::List } } - ty if path_ends_with(ty, "TupleRepr") => { - quote! { crate::interpreter::function::StaticType::Tuple } - } ty if path_ends_with(ty, "MapRepr") => { quote! { crate::interpreter::function::StaticType::Map } } @@ -572,17 +569,17 @@ fn create_temp_variable( let #argument_var_name = &*#rc_temp_var.borrow(); }, }, - Argument { - param_type: quote! { crate::interpreter::function::StaticType::Tuple }, - param_name: quote! { #original_name }, - argument: quote! { #argument_var_name }, - initialize_code: quote! { - let crate::interpreter::value::Value::Sequence(crate::interpreter::sequence::Sequence::Tuple(#rc_temp_var)) = #argument_var_name else { - panic!("Value #position needed to be a Sequence::List but wasn't"); - }; - let #argument_var_name = &#rc_temp_var; - }, - }, + // Argument { + // param_type: quote! { crate::interpreter::function::StaticType::Tuple }, + // param_name: quote! { #original_name }, + // argument: quote! { #argument_var_name }, + // initialize_code: quote! { + // let crate::interpreter::value::Value::Sequence(crate::interpreter::sequence::Sequence::Tuple(#rc_temp_var)) = #argument_var_name else { + // panic!("Value #position needed to be a Sequence::List but wasn't"); + // }; + // let #argument_var_name = &#rc_temp_var; + // }, + // }, ]; } // The pattern is &BigRational diff --git a/tests/programs/006_lists/019_tuple_cow.ndct b/tests/programs/006_lists/019_tuple_cow.ndct index a38454f9..a110e587 100644 --- a/tests/programs/006_lists/019_tuple_cow.ndct +++ b/tests/programs/006_lists/019_tuple_cow.ndct @@ -1,7 +1,9 @@ --PROGRAM-- -let x = (1,2); -let y = x; -x ++= (3,4); -print(x == (1,2,3,4) and y == (1,2)) +// We had to sacrifice this feature to improve the typesystem +// let x = (1,2); +// let y = x; +// x ++= (3,4); +// print(x == (1,2,3,4) and y == (1,2)) +print(true); --EXPECT-- true \ No newline at end of file diff --git a/tests/programs/006_lists/039_tuple_concat.ndct b/tests/programs/006_lists/039_tuple_concat.ndct index c8bd0c5c..f43a08c9 100644 --- a/tests/programs/006_lists/039_tuple_concat.ndct +++ b/tests/programs/006_lists/039_tuple_concat.ndct @@ -1,11 +1,12 @@ --PROGRAM-- -let tup = (1,2,3); -let out = tup ++ (4,5); -assert_eq(out, (1,2,3,4,5)); - -out ++= (6,7); -assert_eq(out, (1,2,3,4,5,6,7)); - +// Tuple concat is no longer a feature +// let tup = (1,2,3); +// let out = tup ++ (4,5); +// assert_eq(out, (1,2,3,4,5)); +// +// out ++= (6,7); +// assert_eq(out, (1,2,3,4,5,6,7)); +// print("ok"); --EXPECT-- ok diff --git a/tests/programs/006_lists/040_tuple_cow.ndct b/tests/programs/006_lists/040_tuple_cow.ndct index 29f224cb..d0c52df6 100644 --- a/tests/programs/006_lists/040_tuple_cow.ndct +++ b/tests/programs/006_lists/040_tuple_cow.ndct @@ -1,13 +1,14 @@ --PROGRAM-- -let a = (1,2,3); -let b = a; -b ++= (4,5); - -// A remains the same -assert_eq(a, (1,2,3)); - -// B was copied and (4,5) was appended -assert_eq(b, (1,2,3,4,5)); +// No longer a feature +// let a = (1,2,3); +// let b = a; +// b ++= (4,5); +// +// // A remains the same +// assert_eq(a, (1,2,3)); +// +// // B was copied and (4,5) was appended +// assert_eq(b, (1,2,3,4,5)); print("ok"); --EXPECT-- ok From 9ee8eb4740ed817c4bd11d93605f4dfd68e5483a Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 16:20:31 +0100 Subject: [PATCH 05/35] Cleanup --- ndc_lib/src/interpreter/semantic/resolve.rs | 38 ++++++++++----------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/ndc_lib/src/interpreter/semantic/resolve.rs b/ndc_lib/src/interpreter/semantic/resolve.rs index 80eac232..a4a303cb 100644 --- a/ndc_lib/src/interpreter/semantic/resolve.rs +++ b/ndc_lib/src/interpreter/semantic/resolve.rs @@ -387,18 +387,25 @@ fn resolve_type( lex_data: &mut LexicalData, ) -> StaticType { match expression { - Expression::BoolLiteral(_) => StaticType::Bool, + Expression::BoolLiteral(_) | Expression::Logical { .. } => StaticType::Bool, Expression::StringLiteral(_) => StaticType::String, Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => StaticType::Int, Expression::Float64Literal(_) => StaticType::Float, Expression::ComplexLiteral(_) => StaticType::Complex, - Expression::Identifier { resolved, name } => lex_data.get_type( - resolved - .expect("previously mentioned identifier was not resolved during type resolution"), - ), - Expression::Statement(_) => StaticType::unit(), - Expression::Logical { .. } => StaticType::Bool, - Expression::Grouping(expr) => resolve_type(expr, lex_data), + Expression::Identifier { resolved, name } => { + lex_data.get_type(resolved.unwrap_or_else(|| { + panic!( + "previously mentioned identifier {name} was not resolved during type resolution" + ) + })) + } + Expression::Statement(_) + | Expression::While { .. } + | Expression::Break + | Expression::Assignment { .. } => StaticType::unit(), + Expression::Grouping(expr) | Expression::Return { value: expr } => { + resolve_type(expr, lex_data) + } Expression::VariableDeclaration { .. } => { debug_assert!( false, @@ -406,13 +413,6 @@ fn resolve_type( ); StaticType::unit() // specifically unit tuple } - Expression::Assignment { .. } => { - // debug_assert!( - // false, - // "trying to get type of assignment, does this make sense?" - // ); - StaticType::unit() // specifically unit tuple - } Expression::OpAssignment { .. } => { debug_assert!( false, @@ -439,7 +439,6 @@ fn resolve_type( ); on_false_type } - Expression::While { .. } => StaticType::unit(), Expression::For { body, .. } => match &**body { ForBody::Block(_) => StaticType::unit(), ForBody::List(_) => StaticType::List, @@ -462,11 +461,10 @@ fn resolve_type( } Expression::List { .. } => StaticType::List, Expression::Map { .. } => StaticType::Map, - Expression::Return { value: expr } => resolve_type(expr, lex_data), - Expression::Break => StaticType::unit(), Expression::Continue => StaticType::Any, // Maybe we need a Never type? - Expression::RangeInclusive { .. } => StaticType::Iterator, - Expression::RangeExclusive { .. } => StaticType::Iterator, + Expression::RangeInclusive { .. } | Expression::RangeExclusive { .. } => { + StaticType::Iterator + } } } From cdebf0e800aca95a32e164b56118047ed55bba96 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 16:23:39 +0100 Subject: [PATCH 06/35] Remove number type --- ndc_lib/src/interpreter/num.rs | 55 +++------------------ ndc_lib/src/interpreter/semantic/resolve.rs | 4 +- ndc_lib/src/interpreter/semantic/types.rs | 36 -------------- 3 files changed, 8 insertions(+), 87 deletions(-) delete mode 100644 ndc_lib/src/interpreter/semantic/types.rs diff --git a/ndc_lib/src/interpreter/num.rs b/ndc_lib/src/interpreter/num.rs index 03a190b7..de664006 100644 --- a/ndc_lib/src/interpreter/num.rs +++ b/ndc_lib/src/interpreter/num.rs @@ -377,16 +377,6 @@ impl Number { Self::Rational(Box::new(rat)) } - #[deprecated = "remove this and just use static_type instead"] - fn type_name(&self) -> &'static str { - match self { - Self::Int(_) => "int", - Self::Float(_) => "float", - Self::Rational(_) => "rational", - Self::Complex(_) => "complex", - } - } - pub fn static_type(&self) -> StaticType { match self { Self::Int(_) => StaticType::Int, @@ -618,7 +608,7 @@ implement_rounding!(round); #[derive(thiserror::Error, Debug)] pub enum NumberToUsizeError { #[error("cannot convert from {0} to usize")] - UnsupportedVariant(&'static str), + UnsupportedVariant(StaticType), #[error("expected non-negative integer for indexing")] FromIntError(#[from] TryFromIntError), #[error("failed to convert from bigint to number because of: '{0}'")] @@ -632,7 +622,7 @@ impl TryFrom for usize { match value { Number::Int(Int::Int64(i)) => Ok(Self::try_from(i)?), Number::Int(Int::BigInt(b)) => Ok(Self::try_from(b)?), - n => Err(NumberToUsizeError::UnsupportedVariant(n.type_name())), + n => Err(NumberToUsizeError::UnsupportedVariant(n.static_type())), } } } @@ -640,7 +630,7 @@ impl TryFrom for usize { #[derive(thiserror::Error, Debug)] pub enum NumberToFloatError { #[error("cannot convert {0} to float")] - UnsupportedType(NumberType), + UnsupportedType(StaticType), #[error("cannot convert {0} to float")] UnsupportedValue(Number), } @@ -654,7 +644,7 @@ impl TryFrom<&Number> for f64 { Number::Int(Int::Int64(i)) => i.to_f64(), Number::Float(f) => Some(*f), Number::Rational(r) => r.to_f64(), - _ => return Err(Self::Error::UnsupportedType(NumberType::from(value))), + _ => return Err(Self::Error::UnsupportedType(value.static_type())), } .ok_or_else(|| Self::Error::UnsupportedValue(value.clone())) } @@ -663,7 +653,7 @@ impl TryFrom<&Number> for f64 { #[derive(thiserror::Error, Debug)] pub enum NumberToIntError { #[error("cannot convert {0} to int")] - UnsupportedType(NumberType), + UnsupportedType(StaticType), #[error("cannot convert {0} to int")] UnsupportedValue(Number), } @@ -677,7 +667,7 @@ impl TryFrom<&Number> for i64 { .try_into() .map_err(|_err| NumberToIntError::UnsupportedValue(value.clone())), Number::Int(Int::Int64(i)) => Ok(*i), - _ => Err(Self::Error::UnsupportedType(NumberType::from(value))), + _ => Err(Self::Error::UnsupportedType(value.static_type())), } } } @@ -712,36 +702,3 @@ pub enum NumberType { Rational, Complex, } - -impl From for StaticType { - fn from(value: NumberType) -> Self { - match value { - NumberType::Int => Self::Int, - NumberType::Float => Self::Float, - NumberType::Rational => Self::Rational, - NumberType::Complex => Self::Complex, - } - } -} - -impl From<&Number> for NumberType { - fn from(value: &Number) -> Self { - match value { - Number::Int(_) => Self::Int, - Number::Float(_) => Self::Float, - Number::Rational(_) => Self::Rational, - Number::Complex(_) => Self::Complex, - } - } -} - -impl fmt::Display for NumberType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Int => write!(f, "Int"), - Self::Float => write!(f, "Float"), - Self::Rational => write!(f, "Rational"), - Self::Complex => write!(f, "Complex"), - } - } -} diff --git a/ndc_lib/src/interpreter/semantic/resolve.rs b/ndc_lib/src/interpreter/semantic/resolve.rs index a4a303cb..c8eae7af 100644 --- a/ndc_lib/src/interpreter/semantic/resolve.rs +++ b/ndc_lib/src/interpreter/semantic/resolve.rs @@ -433,8 +433,8 @@ fn resolve_type( .map_or(StaticType::unit(), |expr| resolve_type(expr, lex_data)); assert_eq!( - resolve_type(on_true, lex_data), - on_false_type, + &resolve_type(on_true, lex_data), + &on_false_type, "if branches have different types" ); on_false_type diff --git a/ndc_lib/src/interpreter/semantic/types.rs b/ndc_lib/src/interpreter/semantic/types.rs deleted file mode 100644 index eee67047..00000000 --- a/ndc_lib/src/interpreter/semantic/types.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::ast::{Expression, ExpressionLocation}; -use crate::interpreter::num::NumberType; -use crate::interpreter::value::ValueType; - -fn give_type(ExpressionLocation { expression, .. }: &ExpressionLocation) -> ValueType { - match expression { - Expression::BoolLiteral(_) => ValueType::Bool, - Expression::StringLiteral(_) => ValueType::String, - Expression::Int64Literal(_) => ValueType::Number(NumberType::Int), - Expression::Float64Literal(_) => ValueType::Number(NumberType::Float), - Expression::BigIntLiteral(_) => ValueType::Number(NumberType::Int), - Expression::ComplexLiteral(_) => ValueType::Number(NumberType::Complex), - Expression::Identifier { .. } => {} - Expression::Statement(_) => {} - Expression::Logical { .. } => {} - Expression::Grouping(_) => {} - Expression::VariableDeclaration { .. } => {} - Expression::Assignment { .. } => {} - Expression::OpAssignment { .. } => {} - Expression::FunctionDeclaration { .. } => {} - Expression::Block { .. } => {} - Expression::If { .. } => {} - Expression::While { .. } => {} - Expression::For { .. } => {} - Expression::Call { .. } => {} - Expression::Index { .. } => {} - Expression::Tuple { .. } => {} - Expression::List { .. } => {} - Expression::Map { .. } => {} - Expression::Return { .. } => {} - Expression::Break => {} - Expression::Continue => {} - Expression::RangeInclusive { .. } => {} - Expression::RangeExclusive { .. } => {} - } -} From 646d2569538304c85bdb802848fdfae2f50e3322 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 16:55:06 +0100 Subject: [PATCH 07/35] rename type checking bits --- ndc_lib/src/interpreter/environment.rs | 2 +- ndc_lib/src/interpreter/mod.rs | 10 +- ndc_lib/src/interpreter/semantic/analyser.rs | 666 +++++++++++++++++++ ndc_lib/src/interpreter/semantic/mod.rs | 2 +- ndc_lib/src/interpreter/semantic/resolve.rs | 657 ------------------ 5 files changed, 673 insertions(+), 664 deletions(-) create mode 100644 ndc_lib/src/interpreter/semantic/analyser.rs delete mode 100644 ndc_lib/src/interpreter/semantic/resolve.rs diff --git a/ndc_lib/src/interpreter/environment.rs b/ndc_lib/src/interpreter/environment.rs index f2b9fe94..51727eab 100644 --- a/ndc_lib/src/interpreter/environment.rs +++ b/ndc_lib/src/interpreter/environment.rs @@ -1,7 +1,7 @@ use crate::interpreter::function::{Function, OverloadedFunction}; use crate::ast::ResolvedVar; -use crate::interpreter::semantic::resolve::LexicalIdentifier; +use crate::interpreter::semantic::analyser::LexicalIdentifier; use crate::interpreter::value::Value; use std::cell::RefCell; use std::fmt; diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index 6fc1e978..68578f50 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -5,7 +5,7 @@ use crate::ast::ExpressionLocation; use crate::interpreter::environment::{Environment, InterpreterOutput}; use crate::interpreter::evaluate::{EvaluationError, evaluate_expression}; use crate::interpreter::function::FunctionCarrier; -use crate::interpreter::semantic::resolve::{LexicalData, resolve_pass}; +use crate::interpreter::semantic::analyser::{Analyser, ScopeTree}; use crate::interpreter::value::Value; use crate::lexer::{Lexer, TokenLocation}; use miette::Diagnostic; @@ -23,7 +23,7 @@ pub mod value; pub struct Interpreter { environment: Rc>, - lexical_data: LexicalData, + analyser: Analyser, } #[allow(clippy::dbg_macro, clippy::print_stdout, clippy::print_stderr)] @@ -38,7 +38,7 @@ impl Interpreter { Self { environment: Rc::new(RefCell::new(environment)), - lexical_data: LexicalData::from_global_scope(hash_map), + analyser: Analyser::from_scope_tree(ScopeTree::from_global_scope(hash_map)), } } @@ -68,7 +68,7 @@ impl Interpreter { } for e in &mut expressions { - resolve_pass(e, &mut self.lexical_data)? + self.analyser.analyse(e)? } // dbg!(&expressions); @@ -139,7 +139,7 @@ pub enum InterpreterError { #[diagnostic(transparent)] Resolver { #[from] - cause: semantic::resolve::ResolveError, + cause: semantic::analyser::ResolveError, }, #[error("Error while executing code")] #[diagnostic(transparent)] diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs new file mode 100644 index 00000000..932d08de --- /dev/null +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -0,0 +1,666 @@ +use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; +use crate::interpreter::function::StaticType; +use crate::lexer::Span; +use itertools::Itertools; + +pub struct Analyser { + scope_tree: ScopeTree, +} + +impl Analyser { + pub fn from_scope_tree(scope_tree: ScopeTree) -> Self { + Self { scope_tree } + } +} + +impl Analyser { + pub fn analyse( + &mut self, + ExpressionLocation { expression, span }: &mut ExpressionLocation, + ) -> Result<(), ResolveError> { + match expression { + Expression::BoolLiteral(_) + | Expression::StringLiteral(_) + | Expression::Int64Literal(_) + | Expression::Float64Literal(_) + | Expression::BigIntLiteral(_) + | Expression::ComplexLiteral(_) + | Expression::Continue + | Expression::Break => { /* nothing to do here */ } + Expression::Identifier { + name: ident, + resolved, + } => { + if ident == "None" { + // THIS IS VERY UNHINGED + return Ok(()); + } + let binding = self.scope_tree.get_binding_any(ident).ok_or_else(|| { + ResolveError::identifier_not_previously_declared(ident, *span) + })?; + + *resolved = Some(binding) + } + Expression::Statement(inner) => { + self.analyse(inner)?; + } + Expression::Logical { left, right, .. } => { + self.analyse(left)?; + self.analyse(right)?; + } + Expression::Grouping(expr) => { + self.analyse(expr)?; + } + Expression::VariableDeclaration { l_value, value } => { + self.analyse(value)?; + let typ = self.resolve_type(value); + self.resolve_lvalue_declarative(l_value, typ)?; + } + Expression::Assignment { l_value, r_value } => { + self.resolve_lvalue(l_value, *span)?; + self.analyse(r_value)?; + } + Expression::OpAssignment { + l_value, + r_value, + operation, + resolved_assign_operation, + resolved_operation, + } => { + self.resolve_lvalue(l_value, *span)?; + self.analyse(r_value)?; + + // In this future should be able to use our type system to figure out which specific + // instance of the function we need. When this is the case we can stop inserting both + // versions in the OpAssignment expression and just figure out the correct one or error + // if there is none + let op_assign_ident = LexicalIdentifier::Function { + name: format!("{operation}="), + arity: Some(2), + }; + + if let Some(binding) = self.scope_tree.get_binding(&op_assign_ident) { + *resolved_assign_operation = Some(binding); + } + + let operation_ident = LexicalIdentifier::Function { + name: (*operation).clone(), + arity: Some(2), + }; + + if let Some(binding) = self.scope_tree.get_binding(&operation_ident) { + *resolved_operation = Some(binding); + } else { + // For now, we require that the normal operation is present and the special assignment operation is optional + return Err(ResolveError::identifier_not_previously_declared( + operation, *span, + )); + } + } + Expression::FunctionDeclaration { + name, + resolved_name, + parameters, + body, + .. + } => { + if let Some(name) = name { + let function_ident = LexicalIdentifier::Function { + name: (*name).clone(), + arity: Some(extract_argument_arity(parameters)), + }; + + *resolved_name = + Some(self.scope_tree.get_or_create_local_binding( + function_ident.clone(), + StaticType::Function, + )) + } + + self.scope_tree.new_scope(); + self.resolve_parameters_declarative(parameters); + + self.analyse(body)?; + self.scope_tree.destroy_scope(); + } + Expression::Block { statements } => { + self.scope_tree.new_scope(); + for s in statements { + self.analyse(s)?; + } + self.scope_tree.destroy_scope(); + } + Expression::If { + condition, + on_true, + on_false, + } => { + self.analyse(condition)?; + self.analyse(on_true)?; + if let Some(on_false) = on_false { + self.analyse(on_false)?; + } + } + Expression::While { + expression, + loop_body, + } => { + self.analyse(expression)?; + self.analyse(loop_body)?; + } + Expression::For { iterations, body } => { + self.resolve_for_iterations(iterations, body)?; + } + Expression::Call { + function, + arguments, + } => { + self.resolve_function_ident_arity(function, arguments.len(), *span)?; + for a in arguments { + self.analyse(a)?; + } + } + Expression::Index { index, value } => { + self.analyse(index)?; + self.analyse(value)?; + } + Expression::Tuple { values } | Expression::List { values } => { + for v in values { + self.analyse(v)?; + } + } + Expression::Map { values, default } => { + for (key, value) in values { + self.analyse(key)?; + if let Some(value) = value { + self.analyse(value)?; + } + } + + if let Some(default) = default { + self.analyse(default)?; + } + } + Expression::Return { value } => { + self.analyse(value)?; + } + Expression::RangeInclusive { start, end } + | Expression::RangeExclusive { start, end } => { + if let Some(start) = start { + self.analyse(start)?; + } + if let Some(end) = end { + self.analyse(end)?; + } + } + } + + Ok(()) + } + fn resolve_function_ident_arity( + &mut self, + ident: &mut ExpressionLocation, + arity: usize, + span: Span, + ) -> Result<(), ResolveError> { + let ExpressionLocation { + expression: Expression::Identifier { name, resolved }, + .. + } = ident + else { + // It's possible that we're not trying to invoke an identifier `foo()` but instead we're + // invoking a value like `get_function()()` so in this case we just continue like normal? + return self.analyse(ident); + }; + + let binding = self + .scope_tree + .get_binding(&LexicalIdentifier::Function { + name: name.clone(), + arity: Some(arity), + }) + .or_else(|| { + self.scope_tree.get_binding(&LexicalIdentifier::Function { + name: name.clone(), + arity: None, + }) + }) + // NOTE: for now if we can't find a function that matches we just bind to a variable with that name (if possible) + // this fixes some cases until we have full type checking + .or_else(|| (self.scope_tree).get_binding_any(name)) + .ok_or_else(|| ResolveError::identifier_not_previously_declared(name, span))?; + + *resolved = Some(binding); + + Ok(()) + } + fn resolve_for_iterations( + &mut self, + iterations: &mut [ForIteration], + body: &mut ForBody, + ) -> Result<(), ResolveError> { + let Some((iteration, tail)) = iterations.split_first_mut() else { + unreachable!("because this function is never called with an empty slice"); + }; + + let mut do_destroy = false; + match iteration { + ForIteration::Iteration { l_value, sequence } => { + self.analyse(sequence)?; + + self.scope_tree.new_scope(); + + // TODO: inferring this as any is a massive cop-out + self.resolve_lvalue_declarative(l_value, StaticType::Any)?; + do_destroy = true; + } + ForIteration::Guard(expr) => { + self.analyse(expr)?; + } + } + + if !tail.is_empty() { + self.resolve_for_iterations(tail, body)? + } else { + match body { + ForBody::Block(block) => { + self.analyse(block)?; + } + ForBody::List(list) => { + self.analyse(list)?; + } + ForBody::Map { + key, + value, + default, + } => { + self.analyse(key)?; + if let Some(value) = value { + self.analyse(value)?; + } + + if let Some(default) = default { + self.analyse(default)?; + } + } + } + } + + if do_destroy { + self.scope_tree.destroy_scope(); + } + + Ok(()) + } + + fn resolve_lvalue(&mut self, lvalue: &mut Lvalue, span: Span) -> Result<(), ResolveError> { + match lvalue { + Lvalue::Identifier { + identifier, + resolved, + } => { + let Some(target) = self.scope_tree.get_binding(&identifier.clone().into()) else { + return Err(ResolveError::identifier_not_previously_declared( + identifier, span, + )); + }; + + *resolved = Some(target); + } + Lvalue::Index { index, value } => { + self.analyse(index)?; + self.analyse(value)?; + } + Lvalue::Sequence(seq) => { + for sub_lvalue in seq { + self.resolve_lvalue(sub_lvalue, span)? + } + } + } + + Ok(()) + } + + /// Resolve expressions as arguments to a function and return the function arity + fn resolve_parameters_declarative(&mut self, arguments: &mut ExpressionLocation) { + let ExpressionLocation { + expression: Expression::Tuple { values }, + .. + } = arguments + else { + panic!("expected arguments to be tuple"); + }; + + for arg in values { + let ExpressionLocation { + expression: Expression::Identifier { name, resolved }, + .. + } = arg + else { + panic!("expected tuple values to be ident"); + }; + + *resolved = Some(self.scope_tree.create_local_binding( + LexicalIdentifier::Variable { + name: (*name).clone(), + }, + StaticType::Any, + )); + } + } + fn resolve_lvalue_declarative( + &mut self, + lvalue: &mut Lvalue, + typ: StaticType, + ) -> Result<(), ResolveError> { + match lvalue { + Lvalue::Identifier { + identifier, + resolved, + } => { + *resolved = Some(self.scope_tree.create_local_binding( + LexicalIdentifier::Variable { + name: (*identifier).clone(), + }, + typ, + )); + } + Lvalue::Index { index, value } => { + self.analyse(index)?; + self.analyse(value)?; + } + Lvalue::Sequence(seq) => { + for sub_lvalue in seq { + self.resolve_lvalue_declarative(sub_lvalue, typ.clone())? + } + } + } + + Ok(()) + } + + fn resolve_type( + &mut self, + ExpressionLocation { expression, .. }: &ExpressionLocation, + ) -> StaticType { + match expression { + Expression::BoolLiteral(_) | Expression::Logical { .. } => StaticType::Bool, + Expression::StringLiteral(_) => StaticType::String, + Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => StaticType::Int, + Expression::Float64Literal(_) => StaticType::Float, + Expression::ComplexLiteral(_) => StaticType::Complex, + Expression::Identifier { resolved, name } => self + .scope_tree + .get_type(resolved.unwrap_or_else(|| { + panic!( + "previously mentioned identifier {name} was not resolved during type resolution" + ) + })), + Expression::Statement(_) + | Expression::While { .. } + | Expression::Break + | Expression::Assignment { .. } => StaticType::unit(), + Expression::Grouping(expr) | Expression::Return { value: expr } => { + self.resolve_type(expr) + } + Expression::VariableDeclaration { .. } => { + debug_assert!( + false, + "trying to get type of variable declaration, does this make sense?" + ); + StaticType::unit() // specifically unit tuple + } + Expression::OpAssignment { .. } => { + debug_assert!( + false, + "trying to get type of op assignment, does this make sense?" + ); + StaticType::unit() // specifically unit tuple + } + Expression::FunctionDeclaration { .. } => StaticType::Function, + Expression::Block { statements } => statements + .iter() + .last() + .map_or(StaticType::unit(), |last| self.resolve_type(last)), + Expression::If { + on_true, on_false, .. + } => { + let on_false_type = on_false + .as_ref() + .map_or(StaticType::unit(), |expr| self.resolve_type(expr)); + + assert_eq!( + self.resolve_type(on_true), + on_false_type, + "if branches have different types" + ); + on_false_type + } + Expression::For { body, .. } => match &**body { + ForBody::Block(_) => StaticType::unit(), + ForBody::List(_) => StaticType::List, + ForBody::Map { .. } => StaticType::Map, + }, + Expression::Call { + function: _, + arguments: _, + } => { + // TODO: Okay this is actually hard + StaticType::Any + } + Expression::Index { .. } => { + // TODO: this is also hard since we don't have generics + StaticType::Any + } + Expression::Tuple { .. } => { + // TODO: this is hard + StaticType::Any + } + Expression::List { .. } => StaticType::List, + Expression::Map { .. } => StaticType::Map, + Expression::Continue => StaticType::Any, // Maybe we need a Never type? + Expression::RangeInclusive { .. } | Expression::RangeExclusive { .. } => { + StaticType::Iterator + } + } + } +} +fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { + let ExpressionLocation { + expression: Expression::Tuple { values }, + .. + } = arguments + else { + panic!("expected arguments to be tuple"); + }; + + values.len() +} + +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub enum LexicalIdentifier { + Variable { name: String }, + Function { name: String, arity: Option }, +} + +impl LexicalIdentifier { + pub fn name(&self) -> &str { + match self { + Self::Variable { name } | Self::Function { name, .. } => name, + } + } +} + +impl From<&str> for LexicalIdentifier { + fn from(value: &str) -> Self { + Self::Variable { name: value.into() } + } +} +impl From for LexicalIdentifier { + fn from(value: String) -> Self { + Self::Variable { name: value } + } +} + +#[derive(Debug)] +pub struct ScopeTree { + current_scope_idx: usize, + global_scope: Scope, + scopes: Vec, +} + +impl ScopeTree { + pub fn from_global_scope(global_scope_map: Vec) -> Self { + Self { + current_scope_idx: 0, + global_scope: Scope { + parent_idx: None, + //TODO: maybe the line below is more of temporary solution + identifiers: global_scope_map + .into_iter() + .map(|x| (x, StaticType::Function)) + .collect_vec(), + }, + scopes: vec![Scope::new(None)], + } + } + + fn get_type(&self, res: ResolvedVar) -> StaticType { + // dbg!(self, res); + match res { + ResolvedVar::Captured { slot, depth } => { + let mut scope_idx = self.current_scope_idx; + let mut depth = depth; + while depth > 0 { + depth -= 1; + scope_idx = self.scopes[scope_idx] + .parent_idx + .expect("parent_idx was None while traversing the scope tree"); + } + self.scopes[scope_idx].identifiers[slot].1.clone() + } + // for now all globals are functions + ResolvedVar::Global { .. } => StaticType::Function, + } + } + + fn new_scope(&mut self) -> &Scope { + let old_scope_idx = self.current_scope_idx; + self.current_scope_idx = self.scopes.len(); + let new_scope = Scope::new(Some(old_scope_idx)); + self.scopes.push(new_scope); + &self.scopes[self.current_scope_idx] + } + + fn destroy_scope(&mut self) { + let next = self.scopes[self.current_scope_idx] + .parent_idx + .expect("tried to destroy scope while there were none"); + self.current_scope_idx = next; + } + + fn get_or_create_local_binding( + &mut self, + ident: LexicalIdentifier, + typ: StaticType, + ) -> ResolvedVar { + ResolvedVar::Captured { + slot: self.scopes[self.current_scope_idx] + .get_slot(&ident) + .unwrap_or_else(|| self.scopes[self.current_scope_idx].allocate(ident, typ)), + depth: 0, + } + } + + fn get_binding_any(&mut self, ident: &str) -> Option { + let mut depth = 0; + let mut scope_ptr = self.current_scope_idx; + + loop { + if let Some(slot) = self.scopes[scope_ptr].get_slot_by_name(ident) { + return Some(ResolvedVar::Captured { slot, depth }); + } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { + depth += 1; + scope_ptr = parent_idx; + } else { + return Some(ResolvedVar::Global { + slot: self.global_scope.get_slot_by_name(ident)?, + }); + } + } + } + + fn get_binding(&mut self, name: &LexicalIdentifier) -> Option { + let mut depth = 0; + let mut scope_ptr = self.current_scope_idx; + + loop { + if let Some(slot) = self.scopes[scope_ptr].get_slot(name) { + return Some(ResolvedVar::Captured { slot, depth }); + } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { + depth += 1; + scope_ptr = parent_idx; + } else { + return Some(ResolvedVar::Global { + slot: self.global_scope.get_slot(name)?, + }); + } + } + } + + fn create_local_binding(&mut self, ident: LexicalIdentifier, typ: StaticType) -> ResolvedVar { + ResolvedVar::Captured { + slot: self.scopes[self.current_scope_idx].allocate(ident, typ), + depth: 0, + } + } +} + +#[derive(Debug)] +struct Scope { + parent_idx: Option, + identifiers: Vec<(LexicalIdentifier, StaticType)>, +} + +impl Scope { + fn new(parent_idx: Option) -> Self { + Self { + parent_idx, + identifiers: Default::default(), + } + } + + pub fn get_slot_by_name(&self, find_ident: &str) -> Option { + self.identifiers + .iter() + .rposition(|(ident, _)| ident.name() == find_ident) + } + + fn get_slot(&mut self, find_ident: &LexicalIdentifier) -> Option { + self.identifiers + .iter() + .rposition(|(ident, _type)| ident == find_ident) + } + + fn allocate(&mut self, name: LexicalIdentifier, typ: StaticType) -> usize { + self.identifiers.push((name, typ)); + // Slot is just the length of the list minus one + self.identifiers.len() - 1 + } +} +#[derive(thiserror::Error, miette::Diagnostic, Debug)] +#[error("{text}")] +pub struct ResolveError { + text: String, + #[label("related to this")] + span: Span, +} + +impl ResolveError { + fn identifier_not_previously_declared(ident: &str, span: Span) -> Self { + Self { + text: format!("Identifier {ident} has not previously been declared"), + span, + } + } +} diff --git a/ndc_lib/src/interpreter/semantic/mod.rs b/ndc_lib/src/interpreter/semantic/mod.rs index 581d1dcd..32c1c0de 100644 --- a/ndc_lib/src/interpreter/semantic/mod.rs +++ b/ndc_lib/src/interpreter/semantic/mod.rs @@ -1 +1 @@ -pub mod resolve; +pub mod analyser; diff --git a/ndc_lib/src/interpreter/semantic/resolve.rs b/ndc_lib/src/interpreter/semantic/resolve.rs deleted file mode 100644 index c8eae7af..00000000 --- a/ndc_lib/src/interpreter/semantic/resolve.rs +++ /dev/null @@ -1,657 +0,0 @@ -use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; -use crate::interpreter::function::StaticType; -use crate::lexer::Span; -use itertools::Itertools; - -pub fn resolve_pass( - ExpressionLocation { expression, span }: &mut ExpressionLocation, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - match expression { - Expression::BoolLiteral(_) - | Expression::StringLiteral(_) - | Expression::Int64Literal(_) - | Expression::Float64Literal(_) - | Expression::BigIntLiteral(_) - | Expression::ComplexLiteral(_) - | Expression::Continue - | Expression::Break => { /* nothing to do here */ } - Expression::Identifier { - name: ident, - resolved, - } => { - if ident == "None" { - // THIS IS VERY UNHINGED - return Ok(()); - } - let binding = lexical_data - .get_binding_any(ident) - .ok_or_else(|| ResolveError::identifier_not_previously_declared(ident, *span))?; - - *resolved = Some(binding) - } - Expression::Statement(inner) => { - resolve_pass(inner, lexical_data)?; - } - Expression::Logical { left, right, .. } => { - resolve_pass(left, lexical_data)?; - resolve_pass(right, lexical_data)?; - } - Expression::Grouping(expr) => { - resolve_pass(expr, lexical_data)?; - } - Expression::VariableDeclaration { l_value, value } => { - resolve_pass(value, lexical_data)?; - resolve_lvalue_declarative(l_value, resolve_type(value, lexical_data), lexical_data)?; - } - Expression::Assignment { l_value, r_value } => { - resolve_lvalue(l_value, *span, lexical_data)?; - resolve_pass(r_value, lexical_data)?; - } - Expression::OpAssignment { - l_value, - r_value, - operation, - resolved_assign_operation, - resolved_operation, - } => { - resolve_lvalue(l_value, *span, lexical_data)?; - resolve_pass(r_value, lexical_data)?; - - // In this future should be able to use our type system to figure out which specific - // instance of the function we need. When this is the case we can stop inserting both - // versions in the OpAssignment expression and just figure out the correct one or error - // if there is none - let op_assign_ident = LexicalIdentifier::Function { - name: format!("{operation}="), - arity: Some(2), - }; - - if let Some(binding) = lexical_data.get_binding(&op_assign_ident) { - *resolved_assign_operation = Some(binding); - } - - let operation_ident = LexicalIdentifier::Function { - name: (*operation).clone(), - arity: Some(2), - }; - - if let Some(binding) = lexical_data.get_binding(&operation_ident) { - *resolved_operation = Some(binding); - } else { - // For now, we require that the normal operation is present and the special assignment operation is optional - return Err(ResolveError::identifier_not_previously_declared( - operation, *span, - )); - } - } - Expression::FunctionDeclaration { - name, - resolved_name, - parameters, - body, - .. - } => { - if let Some(name) = name { - let function_ident = LexicalIdentifier::Function { - name: (*name).clone(), - arity: Some(extract_argument_arity(parameters)), - }; - - *resolved_name = Some( - lexical_data - .get_or_create_local_binding(function_ident.clone(), StaticType::Function), - ) - } - - lexical_data.new_scope(); - resolve_parameters_declarative(parameters, lexical_data); - - resolve_pass(body, lexical_data)?; - lexical_data.destroy_scope(); - } - Expression::Block { statements } => { - lexical_data.new_scope(); - for s in statements { - resolve_pass(s, lexical_data)?; - } - lexical_data.destroy_scope(); - } - Expression::If { - condition, - on_true, - on_false, - } => { - resolve_pass(condition, lexical_data)?; - resolve_pass(on_true, lexical_data)?; - if let Some(on_false) = on_false { - resolve_pass(on_false, lexical_data)?; - } - } - Expression::While { - expression, - loop_body, - } => { - resolve_pass(expression, lexical_data)?; - resolve_pass(loop_body, lexical_data)?; - } - Expression::For { iterations, body } => { - resolve_for_iterations(iterations, body, lexical_data)?; - } - Expression::Call { - function, - arguments, - } => { - resolve_function_ident_arity(function, arguments.len(), *span, lexical_data)?; - for a in arguments { - resolve_pass(a, lexical_data)?; - } - } - Expression::Index { index, value } => { - resolve_pass(index, lexical_data)?; - resolve_pass(value, lexical_data)?; - } - Expression::Tuple { values } | Expression::List { values } => { - for v in values { - resolve_pass(v, lexical_data)?; - } - } - Expression::Map { values, default } => { - for (key, value) in values { - resolve_pass(key, lexical_data)?; - if let Some(value) = value { - resolve_pass(value, lexical_data)?; - } - } - - if let Some(default) = default { - resolve_pass(default, lexical_data)?; - } - } - Expression::Return { value } => { - resolve_pass(value, lexical_data)?; - } - Expression::RangeInclusive { start, end } | Expression::RangeExclusive { start, end } => { - if let Some(start) = start { - resolve_pass(start, lexical_data)?; - } - if let Some(end) = end { - resolve_pass(end, lexical_data)?; - } - } - } - - Ok(()) -} -fn resolve_function_ident_arity( - ident: &mut ExpressionLocation, - arity: usize, - span: Span, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - let ExpressionLocation { - expression: Expression::Identifier { name, resolved }, - .. - } = ident - else { - // It's possible that we're not trying to invoke an identifier `foo()` but instead we're - // invoking a value like `get_function()()` so in this case we just continue like normal? - return resolve_pass(ident, lexical_data); - }; - - let binding = lexical_data - .get_binding(&LexicalIdentifier::Function { - name: name.clone(), - arity: Some(arity), - }) - .or_else(|| { - lexical_data.get_binding(&LexicalIdentifier::Function { - name: name.clone(), - arity: None, - }) - }) - // NOTE: for now if we can't find a function that matches we just bind to a variable with that name (if possible) - // this fixes some cases until we have full type checking - .or_else(|| (lexical_data).get_binding_any(name)) - .ok_or_else(|| ResolveError::identifier_not_previously_declared(name, span))?; - - *resolved = Some(binding); - - Ok(()) -} -fn resolve_for_iterations( - iterations: &mut [ForIteration], - body: &mut ForBody, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - let Some((iteration, tail)) = iterations.split_first_mut() else { - unreachable!("because this function is never called with an empty slice"); - }; - - let mut do_destroy = false; - match iteration { - ForIteration::Iteration { l_value, sequence } => { - resolve_pass(sequence, lexical_data)?; - - lexical_data.new_scope(); - - // TODO: inferring this as any is a massive cop-out - resolve_lvalue_declarative(l_value, StaticType::Any, lexical_data)?; - do_destroy = true; - } - ForIteration::Guard(expr) => { - resolve_pass(expr, lexical_data)?; - } - } - - if !tail.is_empty() { - resolve_for_iterations(tail, body, lexical_data)? - } else { - match body { - ForBody::Block(block) => { - resolve_pass(block, lexical_data)?; - } - ForBody::List(list) => { - resolve_pass(list, lexical_data)?; - } - ForBody::Map { - key, - value, - default, - } => { - resolve_pass(key, lexical_data)?; - if let Some(value) = value { - resolve_pass(value, lexical_data)?; - } - - if let Some(default) = default { - resolve_pass(default, lexical_data)?; - } - } - } - } - - if do_destroy { - lexical_data.destroy_scope(); - } - - Ok(()) -} - -fn resolve_lvalue( - lvalue: &mut Lvalue, - span: Span, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - match lvalue { - Lvalue::Identifier { - identifier, - resolved, - } => { - let Some(target) = lexical_data.get_binding(&identifier.clone().into()) else { - return Err(ResolveError::identifier_not_previously_declared( - identifier, span, - )); - }; - - *resolved = Some(target); - } - Lvalue::Index { index, value } => { - resolve_pass(index, lexical_data)?; - resolve_pass(value, lexical_data)?; - } - Lvalue::Sequence(seq) => { - for sub_lvalue in seq { - resolve_lvalue(sub_lvalue, span, lexical_data)? - } - } - } - - Ok(()) -} - -fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { - let ExpressionLocation { - expression: Expression::Tuple { values }, - .. - } = arguments - else { - panic!("expected arguments to be tuple"); - }; - - values.len() -} -/// Resolve expressions as arguments to a function and return the function arity -fn resolve_parameters_declarative( - arguments: &mut ExpressionLocation, - lexical_data: &mut LexicalData, -) { - let ExpressionLocation { - expression: Expression::Tuple { values }, - .. - } = arguments - else { - panic!("expected arguments to be tuple"); - }; - - for arg in values { - let ExpressionLocation { - expression: Expression::Identifier { name, resolved }, - .. - } = arg - else { - panic!("expected tuple values to be ident"); - }; - - *resolved = Some(lexical_data.create_local_binding( - LexicalIdentifier::Variable { - name: (*name).clone(), - }, - StaticType::Any, - )); - } -} -fn resolve_lvalue_declarative( - lvalue: &mut Lvalue, - typ: StaticType, - lexical_data: &mut LexicalData, -) -> Result<(), ResolveError> { - match lvalue { - Lvalue::Identifier { - identifier, - resolved, - } => { - *resolved = Some(lexical_data.create_local_binding( - LexicalIdentifier::Variable { - name: (*identifier).clone(), - }, - typ, - )); - } - Lvalue::Index { index, value } => { - resolve_pass(index, lexical_data)?; - resolve_pass(value, lexical_data)?; - } - Lvalue::Sequence(seq) => { - for sub_lvalue in seq { - resolve_lvalue_declarative(sub_lvalue, typ.clone(), lexical_data)? - } - } - } - - Ok(()) -} - -fn resolve_type( - ExpressionLocation { expression, .. }: &ExpressionLocation, - lex_data: &mut LexicalData, -) -> StaticType { - match expression { - Expression::BoolLiteral(_) | Expression::Logical { .. } => StaticType::Bool, - Expression::StringLiteral(_) => StaticType::String, - Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => StaticType::Int, - Expression::Float64Literal(_) => StaticType::Float, - Expression::ComplexLiteral(_) => StaticType::Complex, - Expression::Identifier { resolved, name } => { - lex_data.get_type(resolved.unwrap_or_else(|| { - panic!( - "previously mentioned identifier {name} was not resolved during type resolution" - ) - })) - } - Expression::Statement(_) - | Expression::While { .. } - | Expression::Break - | Expression::Assignment { .. } => StaticType::unit(), - Expression::Grouping(expr) | Expression::Return { value: expr } => { - resolve_type(expr, lex_data) - } - Expression::VariableDeclaration { .. } => { - debug_assert!( - false, - "trying to get type of variable declaration, does this make sense?" - ); - StaticType::unit() // specifically unit tuple - } - Expression::OpAssignment { .. } => { - debug_assert!( - false, - "trying to get type of op assignment, does this make sense?" - ); - StaticType::unit() // specifically unit tuple - } - Expression::FunctionDeclaration { .. } => StaticType::Function, - Expression::Block { statements } => statements - .iter() - .last() - .map_or(StaticType::unit(), |last| resolve_type(last, lex_data)), - Expression::If { - on_true, on_false, .. - } => { - let on_false_type = on_false - .as_ref() - .map_or(StaticType::unit(), |expr| resolve_type(expr, lex_data)); - - assert_eq!( - &resolve_type(on_true, lex_data), - &on_false_type, - "if branches have different types" - ); - on_false_type - } - Expression::For { body, .. } => match &**body { - ForBody::Block(_) => StaticType::unit(), - ForBody::List(_) => StaticType::List, - ForBody::Map { .. } => StaticType::Map, - }, - Expression::Call { - function: _, - arguments: _, - } => { - // TODO: Okay this is actually hard - StaticType::Any - } - Expression::Index { .. } => { - // TODO: this is also hard since we don't have generics - StaticType::Any - } - Expression::Tuple { .. } => { - // TODO: this is hard - StaticType::Any - } - Expression::List { .. } => StaticType::List, - Expression::Map { .. } => StaticType::Map, - Expression::Continue => StaticType::Any, // Maybe we need a Never type? - Expression::RangeInclusive { .. } | Expression::RangeExclusive { .. } => { - StaticType::Iterator - } - } -} - -#[derive(Debug, Hash, Eq, PartialEq, Clone)] -pub enum LexicalIdentifier { - Variable { name: String }, - Function { name: String, arity: Option }, -} - -impl LexicalIdentifier { - pub fn name(&self) -> &str { - match self { - Self::Variable { name } | Self::Function { name, .. } => name, - } - } -} - -impl From<&str> for LexicalIdentifier { - fn from(value: &str) -> Self { - Self::Variable { name: value.into() } - } -} -impl From for LexicalIdentifier { - fn from(value: String) -> Self { - Self::Variable { name: value } - } -} - -#[derive(Debug)] -pub struct LexicalData { - current_scope_idx: usize, - global_scope: LexicalScope, - scopes: Vec, -} - -impl LexicalData { - pub fn from_global_scope(global_scope_map: Vec) -> Self { - Self { - current_scope_idx: 0, - global_scope: LexicalScope { - parent_idx: None, - //TODO: maybe the line below is more of temporary solution - identifiers: global_scope_map - .into_iter() - .map(|x| (x, StaticType::Function)) - .collect_vec(), - }, - scopes: vec![LexicalScope::new(None)], - } - } - - fn get_type(&self, res: ResolvedVar) -> StaticType { - // dbg!(self, res); - match res { - ResolvedVar::Captured { slot, depth } => { - let mut scope_idx = self.current_scope_idx; - let mut depth = depth; - while depth > 0 { - depth -= 1; - scope_idx = self.scopes[scope_idx] - .parent_idx - .expect("parent_idx was None while traversing the scope tree"); - } - self.scopes[scope_idx].identifiers[slot].1.clone() - } - // for now all globals are functions - ResolvedVar::Global { .. } => StaticType::Function, - } - } - - fn new_scope(&mut self) -> &LexicalScope { - let old_scope_idx = self.current_scope_idx; - self.current_scope_idx = self.scopes.len(); - let new_scope = LexicalScope::new(Some(old_scope_idx)); - self.scopes.push(new_scope); - &self.scopes[self.current_scope_idx] - } - - fn destroy_scope(&mut self) { - let next = self.scopes[self.current_scope_idx] - .parent_idx - .expect("tried to destroy scope while there were none"); - self.current_scope_idx = next; - } - - fn get_or_create_local_binding( - &mut self, - ident: LexicalIdentifier, - typ: StaticType, - ) -> ResolvedVar { - ResolvedVar::Captured { - slot: self.scopes[self.current_scope_idx] - .get_slot(&ident) - .unwrap_or_else(|| self.scopes[self.current_scope_idx].allocate(ident, typ)), - depth: 0, - } - } - - fn get_binding_any(&mut self, ident: &str) -> Option { - let mut depth = 0; - let mut scope_ptr = self.current_scope_idx; - - loop { - if let Some(slot) = self.scopes[scope_ptr].get_slot_by_name(ident) { - return Some(ResolvedVar::Captured { slot, depth }); - } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { - depth += 1; - scope_ptr = parent_idx; - } else { - return Some(ResolvedVar::Global { - slot: self.global_scope.get_slot_by_name(ident)?, - }); - } - } - } - - fn get_binding(&mut self, name: &LexicalIdentifier) -> Option { - let mut depth = 0; - let mut scope_ptr = self.current_scope_idx; - - loop { - if let Some(slot) = self.scopes[scope_ptr].get_slot(name) { - return Some(ResolvedVar::Captured { slot, depth }); - } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { - depth += 1; - scope_ptr = parent_idx; - } else { - return Some(ResolvedVar::Global { - slot: self.global_scope.get_slot(name)?, - }); - } - } - } - - fn create_local_binding(&mut self, ident: LexicalIdentifier, typ: StaticType) -> ResolvedVar { - ResolvedVar::Captured { - slot: self.scopes[self.current_scope_idx].allocate(ident, typ), - depth: 0, - } - } -} - -#[derive(Debug)] -struct LexicalScope { - parent_idx: Option, - identifiers: Vec<(LexicalIdentifier, StaticType)>, -} - -impl LexicalScope { - fn new(parent_idx: Option) -> Self { - Self { - parent_idx, - identifiers: Default::default(), - } - } - - pub fn get_slot_by_name(&self, find_ident: &str) -> Option { - self.identifiers - .iter() - .rposition(|(ident, _)| ident.name() == find_ident) - } - - fn get_slot(&mut self, find_ident: &LexicalIdentifier) -> Option { - self.identifiers - .iter() - .rposition(|(ident, _type)| ident == find_ident) - } - - fn allocate(&mut self, name: LexicalIdentifier, typ: StaticType) -> usize { - self.identifiers.push((name, typ)); - // Slot is just the length of the list minus one - self.identifiers.len() - 1 - } -} -#[derive(thiserror::Error, miette::Diagnostic, Debug)] -#[error("{text}")] -pub struct ResolveError { - text: String, - #[label("related to this")] - span: Span, -} - -impl ResolveError { - fn identifier_not_previously_declared(ident: &str, span: Span) -> Self { - Self { - text: format!("Identifier {ident} has not previously been declared"), - span, - } - } -} From 71850547b32457e8b3d91d2fbfd3402b50d0e643 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 20:16:34 +0100 Subject: [PATCH 08/35] WIP fixing bug --- ndc_lib/src/interpreter/semantic/analyser.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 932d08de..299e2335 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -389,13 +389,19 @@ impl Analyser { Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => StaticType::Int, Expression::Float64Literal(_) => StaticType::Float, Expression::ComplexLiteral(_) => StaticType::Complex, - Expression::Identifier { resolved, name } => self + Expression::Identifier { resolved, name } => { + println!( + "resolving name: {name}, {resolved:?}\n\n{}\n\n{:?}", + self.scope_tree.current_scope_idx, self.scope_tree.scopes + ); + self .scope_tree .get_type(resolved.unwrap_or_else(|| { panic!( "previously mentioned identifier {name} was not resolved during type resolution" ) - })), + })) + } Expression::Statement(_) | Expression::While { .. } | Expression::Break From 2b68dfb3440878dab47c8e47c8a4a9b5b05b56d2 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 20:49:18 +0100 Subject: [PATCH 09/35] All tests pass --- ndc_lib/src/interpreter/mod.rs | 4 +- ndc_lib/src/interpreter/semantic/analyser.rs | 309 +++++++++++-------- 2 files changed, 182 insertions(+), 131 deletions(-) diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index 68578f50..662d6ee5 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -68,7 +68,7 @@ impl Interpreter { } for e in &mut expressions { - self.analyser.analyse(e)? + self.analyser.analyse(e)?; } // dbg!(&expressions); @@ -139,7 +139,7 @@ pub enum InterpreterError { #[diagnostic(transparent)] Resolver { #[from] - cause: semantic::analyser::ResolveError, + cause: semantic::analyser::AnalysisError, }, #[error("Error while executing code")] #[diagnostic(transparent)] diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 299e2335..f66984ce 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -17,48 +17,51 @@ impl Analyser { pub fn analyse( &mut self, ExpressionLocation { expression, span }: &mut ExpressionLocation, - ) -> Result<(), ResolveError> { + ) -> Result { match expression { - Expression::BoolLiteral(_) - | Expression::StringLiteral(_) - | Expression::Int64Literal(_) - | Expression::Float64Literal(_) - | Expression::BigIntLiteral(_) - | Expression::ComplexLiteral(_) - | Expression::Continue - | Expression::Break => { /* nothing to do here */ } + Expression::BoolLiteral(_) => Ok(StaticType::Bool), + Expression::StringLiteral(_) => Ok(StaticType::String), + Expression::Int64Literal(_) => Ok(StaticType::Int), + Expression::Float64Literal(_) => Ok(StaticType::Float), + Expression::BigIntLiteral(_) => Ok(StaticType::Int), + Expression::ComplexLiteral(_) => Ok(StaticType::Complex), + Expression::Continue => Ok(StaticType::unit()), // TODO: change to never type? + Expression::Break => Ok(StaticType::unit()), Expression::Identifier { name: ident, resolved, } => { if ident == "None" { // THIS IS VERY UNHINGED - return Ok(()); + return Ok(StaticType::Option); } let binding = self.scope_tree.get_binding_any(ident).ok_or_else(|| { - ResolveError::identifier_not_previously_declared(ident, *span) + AnalysisError::identifier_not_previously_declared(ident, *span) })?; - *resolved = Some(binding) + *resolved = Some(binding); + + Ok(self.scope_tree.get_type(binding)) // are we guaranteed to even have a type } Expression::Statement(inner) => { self.analyse(inner)?; + Ok(StaticType::unit()) } Expression::Logical { left, right, .. } => { - self.analyse(left)?; - self.analyse(right)?; - } - Expression::Grouping(expr) => { - self.analyse(expr)?; + self.analyse(left)?; // TODO: throw error if type does not match bool? + self.analyse(right)?; // TODO: throw error if type does not match bool? + Ok(StaticType::Bool) } + Expression::Grouping(expr) => self.analyse(expr), Expression::VariableDeclaration { l_value, value } => { - self.analyse(value)?; - let typ = self.resolve_type(value); + let typ = self.analyse(value)?; self.resolve_lvalue_declarative(l_value, typ)?; + Ok(StaticType::unit()) // TODO: never type here? } Expression::Assignment { l_value, r_value } => { self.resolve_lvalue(l_value, *span)?; self.analyse(r_value)?; + Ok(StaticType::unit()) } Expression::OpAssignment { l_value, @@ -90,11 +93,13 @@ impl Analyser { if let Some(binding) = self.scope_tree.get_binding(&operation_ident) { *resolved_operation = Some(binding); + + Ok(StaticType::unit()) } else { // For now, we require that the normal operation is present and the special assignment operation is optional - return Err(ResolveError::identifier_not_previously_declared( + Err(AnalysisError::identifier_not_previously_declared( operation, *span, - )); + )) } } Expression::FunctionDeclaration { @@ -122,13 +127,18 @@ impl Analyser { self.analyse(body)?; self.scope_tree.destroy_scope(); + + Ok(StaticType::Function) } Expression::Block { statements } => { self.scope_tree.new_scope(); + let mut last = None; for s in statements { - self.analyse(s)?; + last = Some(self.analyse(s)?); } self.scope_tree.destroy_scope(); + + Ok(last.unwrap_or_else(StaticType::unit)) } Expression::If { condition, @@ -136,9 +146,27 @@ impl Analyser { on_false, } => { self.analyse(condition)?; - self.analyse(on_true)?; - if let Some(on_false) = on_false { - self.analyse(on_false)?; + let true_type = self.analyse(on_true)?; + let false_typ = if let Some(on_false) = on_false { + Some(self.analyse(on_false)?) + } else { + None + }; + + if false_typ.is_none() { + if true_type != StaticType::unit() { + // TODO: Emit warning for not using a semicolon in this if + } + + Ok(StaticType::unit()) + } else if let Some(false_type) = false_typ + && false_type == true_type + { + Ok(true_type) + } else { + // TODO: maybe create a warning to show the user they're doing something cursed + // TODO: figure out the nearest common ancestor for true_type and false_type + Ok(StaticType::Any) } } Expression::While { @@ -147,27 +175,51 @@ impl Analyser { } => { self.analyse(expression)?; self.analyse(loop_body)?; + Ok(StaticType::unit()) } Expression::For { iterations, body } => { self.resolve_for_iterations(iterations, body)?; + + match &**body { + // for now this is good enough? + ForBody::Block(_) => Ok(StaticType::unit()), + ForBody::List(_) => Ok(StaticType::List), + ForBody::Map { .. } => Ok(StaticType::Map), + } } Expression::Call { function, arguments, } => { - self.resolve_function_ident_arity(function, arguments.len(), *span)?; + let _the_type_of_the_function_which_doesnt_help_us_now = + self.resolve_function_ident_arity(function, arguments.len(), *span)?; for a in arguments { self.analyse(a)?; } + // Maybe we can use _the_type_of_the_function_which_doesnt_help_us_now to find the return value?!?! + // TODO: figure out the type + Ok(StaticType::Any) } Expression::Index { index, value } => { self.analyse(index)?; self.analyse(value)?; + // TODO: figure out the type here + Ok(StaticType::Any) } - Expression::Tuple { values } | Expression::List { values } => { + Expression::Tuple { values } => { + let mut types = Vec::with_capacity(values.len()); + for v in values { + types.push(self.analyse(v)?); + } + + Ok(StaticType::Tuple(types)) + } + Expression::List { values } => { for v in values { self.analyse(v)?; } + + Ok(StaticType::List) } Expression::Map { values, default } => { for (key, value) in values { @@ -180,9 +232,12 @@ impl Analyser { if let Some(default) = default { self.analyse(default)?; } + + Ok(StaticType::Map) } Expression::Return { value } => { self.analyse(value)?; + Ok(StaticType::unit()) // TODO or never? } Expression::RangeInclusive { start, end } | Expression::RangeExclusive { start, end } => { @@ -192,17 +247,17 @@ impl Analyser { if let Some(end) = end { self.analyse(end)?; } + + Ok(StaticType::Iterator) } } - - Ok(()) } fn resolve_function_ident_arity( &mut self, ident: &mut ExpressionLocation, arity: usize, span: Span, - ) -> Result<(), ResolveError> { + ) -> Result { let ExpressionLocation { expression: Expression::Identifier { name, resolved }, .. @@ -228,17 +283,17 @@ impl Analyser { // NOTE: for now if we can't find a function that matches we just bind to a variable with that name (if possible) // this fixes some cases until we have full type checking .or_else(|| (self.scope_tree).get_binding_any(name)) - .ok_or_else(|| ResolveError::identifier_not_previously_declared(name, span))?; + .ok_or_else(|| AnalysisError::identifier_not_previously_declared(name, span))?; *resolved = Some(binding); - Ok(()) + Ok(StaticType::Function) } fn resolve_for_iterations( &mut self, iterations: &mut [ForIteration], body: &mut ForBody, - ) -> Result<(), ResolveError> { + ) -> Result<(), AnalysisError> { let Some((iteration, tail)) = iterations.split_first_mut() else { unreachable!("because this function is never called with an empty slice"); }; @@ -293,14 +348,14 @@ impl Analyser { Ok(()) } - fn resolve_lvalue(&mut self, lvalue: &mut Lvalue, span: Span) -> Result<(), ResolveError> { + fn resolve_lvalue(&mut self, lvalue: &mut Lvalue, span: Span) -> Result<(), AnalysisError> { match lvalue { Lvalue::Identifier { identifier, resolved, } => { let Some(target) = self.scope_tree.get_binding(&identifier.clone().into()) else { - return Err(ResolveError::identifier_not_previously_declared( + return Err(AnalysisError::identifier_not_previously_declared( identifier, span, )); }; @@ -352,7 +407,7 @@ impl Analyser { &mut self, lvalue: &mut Lvalue, typ: StaticType, - ) -> Result<(), ResolveError> { + ) -> Result<(), AnalysisError> { match lvalue { Lvalue::Identifier { identifier, @@ -379,97 +434,93 @@ impl Analyser { Ok(()) } - fn resolve_type( - &mut self, - ExpressionLocation { expression, .. }: &ExpressionLocation, - ) -> StaticType { - match expression { - Expression::BoolLiteral(_) | Expression::Logical { .. } => StaticType::Bool, - Expression::StringLiteral(_) => StaticType::String, - Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => StaticType::Int, - Expression::Float64Literal(_) => StaticType::Float, - Expression::ComplexLiteral(_) => StaticType::Complex, - Expression::Identifier { resolved, name } => { - println!( - "resolving name: {name}, {resolved:?}\n\n{}\n\n{:?}", - self.scope_tree.current_scope_idx, self.scope_tree.scopes - ); - self - .scope_tree - .get_type(resolved.unwrap_or_else(|| { - panic!( - "previously mentioned identifier {name} was not resolved during type resolution" - ) - })) - } - Expression::Statement(_) - | Expression::While { .. } - | Expression::Break - | Expression::Assignment { .. } => StaticType::unit(), - Expression::Grouping(expr) | Expression::Return { value: expr } => { - self.resolve_type(expr) - } - Expression::VariableDeclaration { .. } => { - debug_assert!( - false, - "trying to get type of variable declaration, does this make sense?" - ); - StaticType::unit() // specifically unit tuple - } - Expression::OpAssignment { .. } => { - debug_assert!( - false, - "trying to get type of op assignment, does this make sense?" - ); - StaticType::unit() // specifically unit tuple - } - Expression::FunctionDeclaration { .. } => StaticType::Function, - Expression::Block { statements } => statements - .iter() - .last() - .map_or(StaticType::unit(), |last| self.resolve_type(last)), - Expression::If { - on_true, on_false, .. - } => { - let on_false_type = on_false - .as_ref() - .map_or(StaticType::unit(), |expr| self.resolve_type(expr)); - - assert_eq!( - self.resolve_type(on_true), - on_false_type, - "if branches have different types" - ); - on_false_type - } - Expression::For { body, .. } => match &**body { - ForBody::Block(_) => StaticType::unit(), - ForBody::List(_) => StaticType::List, - ForBody::Map { .. } => StaticType::Map, - }, - Expression::Call { - function: _, - arguments: _, - } => { - // TODO: Okay this is actually hard - StaticType::Any - } - Expression::Index { .. } => { - // TODO: this is also hard since we don't have generics - StaticType::Any - } - Expression::Tuple { .. } => { - // TODO: this is hard - StaticType::Any - } - Expression::List { .. } => StaticType::List, - Expression::Map { .. } => StaticType::Map, - Expression::Continue => StaticType::Any, // Maybe we need a Never type? - Expression::RangeInclusive { .. } | Expression::RangeExclusive { .. } => { - StaticType::Iterator - } - } - } + // fn resolve_type( + // &mut self, + // ExpressionLocation { expression, .. }: &ExpressionLocation, + // ) -> StaticType { + // match expression { + // Expression::BoolLiteral(_) | Expression::Logical { .. } => StaticType::Bool, + // Expression::StringLiteral(_) => StaticType::String, + // Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => StaticType::Int, + // Expression::Float64Literal(_) => StaticType::Float, + // Expression::ComplexLiteral(_) => StaticType::Complex, + // Expression::Identifier { resolved, name } => { + // println!( + // "resolving name: {name}, {resolved:?}\n\n{}\n\n{:?}", + // self.scope_tree.current_scope_idx, self.scope_tree.scopes + // ); + // self + // .scope_tree + // .get_type(resolved.unwrap_or_else(|| { + // panic!( + // "previously mentioned identifier {name} was not resolved during type resolution" + // ) + // })) + // } + // Expression::Statement(_) + // | Expression::While { .. } + // | Expression::Break + // | Expression::Assignment { .. } => StaticType::unit(), + // Expression::Grouping(expr) | Expression::Return { value: expr } => { + // self.resolve_type(expr) + // } + // Expression::VariableDeclaration { .. } => { + // debug_assert!( + // false, + // "trying to get type of variable declaration, does this make sense?" + // ); + // StaticType::unit() // specifically unit tuple + // } + // Expression::OpAssignment { .. } => { + // debug_assert!( + // false, + // "trying to get type of op assignment, does this make sense?" + // ); + // StaticType::unit() // specifically unit tuple + // } + // Expression::FunctionDeclaration { .. } => StaticType::Function, + // Expression::Block { statements } => statements + // .iter() + // .last() + // .map_or(StaticType::unit(), |last| self.resolve_type(last)), + // Expression::If { + // on_true, on_false, .. + // } => { + // let on_false_type = on_false + // .as_ref() + // .map_or(StaticType::unit(), |expr| self.resolve_type(expr)); + // + // assert_eq!( + // self.resolve_type(on_true), + // on_false_type, + // "if branches have different types" + // ); + // on_false_type + // } + // Expression::For { body, .. } => , + // Expression::Call { + // function: _, + // arguments: _, + // } => { + // // TODO: Okay this is actually hard + // StaticType::Any + // } + // Expression::Index { .. } => { + // // TODO: this is also hard since we don't have generics + // StaticType::Any + // } + // Expression::Tuple { .. } => { + // // TODO: this is hard + // StaticType::Any + // } + // Expression::List { .. } => StaticType::List, + // Expression::Map { .. } => StaticType::Map, + // Expression::Continue => StaticType::Any, // Maybe we need a Never type? + // Expression::RangeInclusive { .. } | Expression::RangeExclusive { .. } => { + // StaticType::Iterator + // } + // } + // } } fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { let ExpressionLocation { @@ -656,13 +707,13 @@ impl Scope { } #[derive(thiserror::Error, miette::Diagnostic, Debug)] #[error("{text}")] -pub struct ResolveError { +pub struct AnalysisError { text: String, #[label("related to this")] span: Span, } -impl ResolveError { +impl AnalysisError { fn identifier_not_previously_declared(ident: &str, span: Span) -> Self { Self { text: format!("Identifier {ident} has not previously been declared"), From 51016287566d2cc1ce8b86cc3efeb1123fc8e518 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 20:58:00 +0100 Subject: [PATCH 10/35] Cleanup StaticType printing --- ndc_bin/src/docs.rs | 6 +++- ndc_lib/src/interpreter/function.rs | 46 +++++++++++++---------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ndc_bin/src/docs.rs b/ndc_bin/src/docs.rs index 252352a7..128d3652 100644 --- a/ndc_bin/src/docs.rs +++ b/ndc_bin/src/docs.rs @@ -56,7 +56,11 @@ pub fn docs(query: Option<&str>) -> anyhow::Result<()> { write!(signature, "(")?; let mut param_iter = params.iter().peekable(); while let Some(Parameter { name, type_name }) = param_iter.next() { - write!(signature, "*{name}*: **{}**", type_name.as_str().green())?; + write!( + signature, + "*{name}*: **{}**", + format!("{}", type_name).green() + )?; if param_iter.peek().is_some() { write!(signature, ", ")?; diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index e546b41b..3ee71b55 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -522,34 +522,30 @@ impl StaticType { _ => false, } } - - pub fn as_str(&self) -> &'static str { - match self { - Self::Any => "Any", - Self::Bool => "Bool", - Self::Function => "Function", - Self::Option => "Option", - Self::Number => "Number", - Self::Float => "Float", - Self::Int => "Int", - Self::Rational => "Rational", - Self::Complex => "Complex", - Self::Sequence => "Sequence", - Self::List => "List", - Self::String => "String", - Self::Tuple(_) => "Tuple<...>", // TODO: what do we need alooc - Self::Map => "Map", - Self::Iterator => "Iterator", - Self::MinHeap => "MinHeap", - Self::MaxHeap => "MaxHeap", - Self::Deque => "Deque", - } - } } impl fmt::Display for StaticType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) + match self { + Self::Any => write!(f, "Any"), + Self::Bool => write!(f, "Bool"), + Self::Function => write!(f, "Function"), + Self::Option => write!(f, "Option"), + 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 => write!(f, "Sequence"), + Self::List => write!(f, "List"), + Self::String => write!(f, "String"), + Self::Tuple(tup) => write!(f, "tuple<{}>", tup.iter().join(", ")), + Self::Map => write!(f, "Map"), + Self::Iterator => write!(f, "Iterator"), + Self::MinHeap => write!(f, "MinHeap"), + Self::MaxHeap => write!(f, "MaxHeap"), + Self::Deque => write!(f, "Deque"), + } } } @@ -632,7 +628,7 @@ impl fmt::Display for TypeSignature { "{}", params .iter() - .map(|p| format!("{}: {}", p.name, p.type_name.as_str())) + .map(|p| format!("{}: {}", p.name, p.type_name)) .join(", ") ), } From 8d65a6c0db07873f08bcb7d0a64b69e8d206f855 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 5 Dec 2025 21:29:57 +0100 Subject: [PATCH 11/35] Very basic return types --- ndc_bin/src/docs.rs | 6 ++++-- ndc_lib/src/ast/expression.rs | 8 ++++++-- ndc_lib/src/ast/parser.rs | 1 + ndc_lib/src/interpreter/evaluate/mod.rs | 2 ++ ndc_lib/src/interpreter/function.rs | 17 +++++++++++++++++ ndc_lib/src/stdlib/file.rs | 6 +++++- ndc_lib/src/stdlib/math.rs | 10 ++++++++++ ndc_lib/src/stdlib/sequence.rs | 3 ++- ndc_macros/src/function.rs | 8 +++++++- tests/programs/603_stdlib_seq/004_zip.ndct | 5 +++++ 10 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 tests/programs/603_stdlib_seq/004_zip.ndct diff --git a/ndc_bin/src/docs.rs b/ndc_bin/src/docs.rs index 128d3652..b53589cf 100644 --- a/ndc_bin/src/docs.rs +++ b/ndc_bin/src/docs.rs @@ -67,14 +67,16 @@ pub fn docs(query: Option<&str>) -> anyhow::Result<()> { } } - writeln!(signature, ")")?; + write!(signature, ")")?; } } let name = function.name(); let documentation = function.documentation().trim(); + let return_type = function.return_type(); let markdown = format!( - "---\n\n## **{}**{signature}\n{documentation}{}", + "---\n\n## **{}**{signature} -> {}\n\n{documentation}{}", name.green(), + format!("{}", return_type).green().bold(), if documentation.is_empty() { "" } else { "\n\n" } ); diff --git a/ndc_lib/src/ast/expression.rs b/ndc_lib/src/ast/expression.rs index ca936677..2a6869d7 100644 --- a/ndc_lib/src/ast/expression.rs +++ b/ndc_lib/src/ast/expression.rs @@ -1,6 +1,7 @@ use crate::ast::operator::LogicalOperator; use crate::ast::parser::Error as ParseError; use crate::interpreter::evaluate::EvaluationError; +use crate::interpreter::function::StaticType; use crate::lexer::Span; use num::BigInt; use num::complex::Complex64; @@ -57,6 +58,7 @@ pub enum Expression { resolved_name: Option, parameters: Box, body: Box, + return_type: Option, pure: bool, }, Block { @@ -332,7 +334,8 @@ impl std::fmt::Debug for ExpressionLocation { .finish(), Expression::FunctionDeclaration { name, - parameters: arguments, + parameters, + return_type, body, pure, resolved_name, @@ -340,7 +343,8 @@ impl std::fmt::Debug for ExpressionLocation { .debug_struct("FunctionDeclaration") .field("name", name) .field("resolved_name", resolved_name) - .field("arguments", arguments) + .field("parameters", parameters) + .field("return_type", return_type) .field("body", body) .field("pure", pure) .finish(), diff --git a/ndc_lib/src/ast/parser.rs b/ndc_lib/src/ast/parser.rs index 6eaa237c..a52d6ae7 100644 --- a/ndc_lib/src/ast/parser.rs +++ b/ndc_lib/src/ast/parser.rs @@ -1157,6 +1157,7 @@ impl Parser { name: identifier, parameters: Box::new(argument_list), body: Box::new(body), + return_type: None, // At some point in the future we could use type declarations here to insert the type (return type inference is cringe anyway) pure: is_pure, resolved_name: None, }, diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index 4b3bc9c1..825d8812 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -348,12 +348,14 @@ pub(crate) fn evaluate_expression( parameters: arguments, body, resolved_name, + return_type, pure, .. } => { let mut user_function = FunctionBody::Closure { parameter_names: arguments.try_into_parameters()?, body: *body.clone(), + return_type: return_type.clone().unwrap_or_else(StaticType::unit), environment: environment.clone(), }; diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 3ee71b55..fb6c4c48 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -72,6 +72,9 @@ impl Function { pub fn type_signature(&self) -> TypeSignature { self.body.type_signature() } + pub fn return_type(&self) -> &StaticType { + self.body.return_type() + } } #[derive(Clone)] @@ -79,6 +82,7 @@ pub enum FunctionBody { Closure { parameter_names: Vec, body: ExpressionLocation, + return_type: StaticType, environment: Rc>, }, NumericUnaryOp { @@ -89,6 +93,7 @@ pub enum FunctionBody { }, GenericFunction { type_signature: TypeSignature, + return_type: StaticType, function: fn(&mut [Value], &Rc>) -> EvaluationResult, }, Memoized { @@ -111,13 +116,16 @@ impl FunctionBody { } pub fn generic( type_signature: TypeSignature, + return_type: StaticType, function: fn(&mut [Value], &Rc>) -> EvaluationResult, ) -> Self { Self::GenericFunction { type_signature, + return_type, function, } } + fn type_signature(&self) -> TypeSignature { match self { Self::Closure { @@ -140,6 +148,15 @@ impl FunctionBody { } } + pub fn return_type(&self) -> &StaticType { + match self { + FunctionBody::Closure { return_type, .. } => return_type, + FunctionBody::NumericUnaryOp { .. } => &StaticType::Number, + FunctionBody::NumericBinaryOp { .. } => &StaticType::Number, + FunctionBody::GenericFunction { return_type, .. } => return_type, + FunctionBody::Memoized { function, .. } => function.return_type(), + } + } pub fn call(&self, args: &mut [Value], env: &Rc>) -> EvaluationResult { match self { Self::Closure { diff --git a/ndc_lib/src/stdlib/file.rs b/ndc_lib/src/stdlib/file.rs index aca465d4..277b4bd7 100644 --- a/ndc_lib/src/stdlib/file.rs +++ b/ndc_lib/src/stdlib/file.rs @@ -1,5 +1,7 @@ use crate::interpreter::environment::Environment; -use crate::interpreter::function::{FunctionBody, FunctionBuilder, FunctionCarrier, TypeSignature}; +use crate::interpreter::function::{ + FunctionBody, FunctionBuilder, FunctionCarrier, StaticType, TypeSignature, +}; use crate::interpreter::value::Value; use ndc_macros::export_module; use std::fs::read_to_string; @@ -46,6 +48,7 @@ pub fn register_variadic(env: &mut Environment) { Ok(Value::unit()) }, type_signature: TypeSignature::Variadic, + return_type: StaticType::unit(), }) .build() .expect("function definition defined in code must be valid"), @@ -73,6 +76,7 @@ pub fn register_variadic(env: &mut Environment) { Ok(Value::unit()) }, type_signature: TypeSignature::Variadic, + return_type: StaticType::unit(), }) .build() .expect("function definition defined in code must be valid"), diff --git a/ndc_lib/src/stdlib/math.rs b/ndc_lib/src/stdlib/math.rs index a4e60023..40f028cf 100644 --- a/ndc_lib/src/stdlib/math.rs +++ b/ndc_lib/src/stdlib/math.rs @@ -267,6 +267,7 @@ pub mod f64 { }, _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Bool, }) .build() .expect("must succeed") @@ -291,6 +292,7 @@ pub mod f64 { [left, right] => Ok(Value::Bool(left == right)), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Bool, }) .build() .expect("must succeed") @@ -308,6 +310,7 @@ pub mod f64 { [left, right] => Ok(Value::Bool(left != right)), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Bool, }) .build() .expect("must succeed") @@ -330,6 +333,7 @@ pub mod f64 { }, _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed") @@ -352,6 +356,7 @@ pub mod f64 { }, _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed") @@ -371,6 +376,7 @@ pub mod f64 { [Value::Bool(left), Value::Bool(right)] => Ok(Value::Bool($operation(*left, *right))), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Bool, }) .build() .expect("must succeed") @@ -388,6 +394,7 @@ pub mod f64 { [Value::Number(Number::Int(left)), Value::Number(Number::Int(right))] => Ok(Value::Number(Number::Int($operation(left.clone(), right.clone())))), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed"), @@ -416,6 +423,7 @@ pub mod f64 { [Value::Bool(b)] => Ok(Value::Bool(b.not())), _ => unreachable!("the type checker should never invoke this function if the argument count does not match"), }, + return_type: StaticType::Bool, }) .name(ident.to_string()) .build() @@ -437,6 +445,7 @@ pub mod f64 { .map(|x| Value::Number(Number::Int(x))), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed") @@ -456,6 +465,7 @@ pub mod f64 { .map(|x| Value::Number(Number::Int(x))), _ => unreachable!("the type checker should never invoke this function if the argument count does not match") }, + return_type: StaticType::Int, }) .build() .expect("must succeed") diff --git a/ndc_lib/src/stdlib/sequence.rs b/ndc_lib/src/stdlib/sequence.rs index 4d3b57d7..9e4a006b 100644 --- a/ndc_lib/src/stdlib/sequence.rs +++ b/ndc_lib/src/stdlib/sequence.rs @@ -735,7 +735,7 @@ pub mod extra { use anyhow::anyhow; use itertools::izip; - use crate::interpreter::function::FunctionBuilder; + use crate::interpreter::function::{FunctionBuilder, StaticType}; use crate::interpreter::{ environment::Environment, function::FunctionBody, iterator::mut_value_to_iterator, value::Value, @@ -748,6 +748,7 @@ pub mod extra { .documentation("Combines multiple sequences (or iterables) into a single sequence of tuples, where the ith tuple contains the ith element from each input sequence.\n\nIf the input sequences are of different lengths, the resulting sequence is truncated to the length of the shortest input.".to_string()) .body(FunctionBody::generic( crate::interpreter::function::TypeSignature::Variadic, + StaticType::List, |args, _env| match args { [_] => { Err(anyhow!("zip must be called with 2 or more arguments").into()) diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index 87d0b9e6..66fdaa7d 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -53,6 +53,9 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { } } + // TODO: CONTINUE HERE + let return_type = quote! { crate::interpreter::function::StaticType::Any }; // RIP ROP RAP + // If the function has no argument then the cartesian product stuff below doesn't work if function.sig.inputs.is_empty() { return function_names @@ -63,6 +66,7 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { &original_identifier, function_name, vec![], + return_type.clone(), &docs_buf, ) }) @@ -71,7 +75,6 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { // When we call create_temp_variable we can get multiple definitions for a variable // For instance when a rust function is `fn foo(list: &[Value])` we can define two internal functions for both Tuple and List - let mut variation_id = 0usize; function_names .iter() @@ -98,6 +101,7 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { &format_ident!("{original_identifier}_{variation_id}"), function_name, args, + return_type.clone(), &docs_buf, ); variation_id += 1; @@ -116,6 +120,7 @@ fn wrap_single( identifier: &syn::Ident, register_as_function_name: &proc_macro2::Literal, input_arguments: Vec, + return_type: TokenStream, docs: &str, ) -> WrappedFunction { let inner_ident = format_ident!("{}_inner", identifier); @@ -206,6 +211,7 @@ fn wrap_single( type_signature: crate::interpreter::function::TypeSignature::Exact(vec![ #( crate::interpreter::function::Parameter::new(#param_names, #param_types,) ),* ]), + return_type: #return_type, }) .name(String::from(#register_as_function_name)) .documentation(String::from(#docs)) diff --git a/tests/programs/603_stdlib_seq/004_zip.ndct b/tests/programs/603_stdlib_seq/004_zip.ndct new file mode 100644 index 00000000..e2a07fee --- /dev/null +++ b/tests/programs/603_stdlib_seq/004_zip.ndct @@ -0,0 +1,5 @@ +--PROGRAM-- +assert_eq((1,2).zip((3,4)), [(1,3),(2,4)]); +print("ok"); +--EXPECT-- +ok \ No newline at end of file From dd3065aad97ada969f275cf20bfeebc3ccd9defb Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 05:51:11 +0100 Subject: [PATCH 12/35] Update macro crate to get return types from native rust functions (WIP) --- ndc_lib/src/interpreter/function.rs | 4 +- ndc_macros/src/convert.rs | 25 ++++++++-- ndc_macros/src/function.rs | 75 +++++++++++++++++++++++++++-- 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index fb6c4c48..0e53a0b0 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -114,6 +114,7 @@ impl FunctionBody { Self::Memoized { function, .. } => function.arity(), } } + pub fn generic( type_signature: TypeSignature, return_type: StaticType, @@ -556,7 +557,8 @@ impl fmt::Display for StaticType { Self::Sequence => write!(f, "Sequence"), Self::List => write!(f, "List"), Self::String => write!(f, "String"), - Self::Tuple(tup) => write!(f, "tuple<{}>", tup.iter().join(", ")), + Self::Tuple(tup) if tup.is_empty() => write!(f, "()"), + Self::Tuple(tup) => write!(f, "Tuple<{}>", tup.iter().join(", ")), Self::Map => write!(f, "Map"), Self::Iterator => write!(f, "Iterator"), Self::MinHeap => write!(f, "MinHeap"), diff --git a/ndc_macros/src/convert.rs b/ndc_macros/src/convert.rs index 438bf6de..b8edb990 100644 --- a/ndc_macros/src/convert.rs +++ b/ndc_macros/src/convert.rs @@ -12,6 +12,7 @@ pub struct Argument { } pub trait TypeConverter { fn matches(&self, ty: &syn::Type) -> bool; + fn static_type(&self) -> TokenStream; fn convert( &self, temp_var: syn::Ident, @@ -26,6 +27,10 @@ impl TypeConverter for MutRefString { is_ref_mut(ty) && is_string(ty) } + fn static_type(&self) -> TokenStream { + quote! { crate::interpreter::function::StaticType::String } + } + fn convert( &self, temp_var: syn::Ident, @@ -33,7 +38,7 @@ impl TypeConverter for MutRefString { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::String }, + param_type: self.static_type(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -53,6 +58,10 @@ impl TypeConverter for InternalMap { path_ends_with(ty, "MapRepr") } + fn static_type(&self) -> TokenStream { + quote! { crate::interpreter::function::StaticType::Map } + } + fn convert( &self, temp_var: syn::Ident, @@ -60,7 +69,7 @@ impl TypeConverter for InternalMap { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::Map }, + param_type: self.static_type(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -79,6 +88,10 @@ impl TypeConverter for InternalString { path_ends_with(ty, "StringRepr") } + fn static_type(&self) -> TokenStream { + quote! { crate::interpreter::function::StaticType::String } + } + fn convert( &self, temp_var: syn::Ident, @@ -86,7 +99,7 @@ impl TypeConverter for InternalString { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::String }, + param_type: self.static_type(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -106,6 +119,10 @@ impl TypeConverter for InternalList { path_ends_with(ty, "ListRepr") } + fn static_type(&self) -> TokenStream { + quote! { crate::interpreter::function::StaticType::List } + } + fn convert( &self, temp_var: syn::Ident, @@ -113,7 +130,7 @@ impl TypeConverter for InternalList { argument_var_name: syn::Ident, ) -> Vec { vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::List }, + param_type: self.static_type(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index 66fdaa7d..03a8976d 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -7,6 +7,7 @@ use itertools::Itertools; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::fmt::Write; +use syn::spanned::Spanned; pub struct WrappedFunction { pub function_declaration: TokenStream, @@ -20,7 +21,7 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { &original_identifier.to_string(), )]; - let mut docs_buf = String::new(); + let mut documentation_buffer = String::new(); for attr in &function.attrs { if attr.path().is_ident("function") { attr.parse_nested_meta(|meta| { @@ -42,7 +43,8 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { && let syn::Expr::Lit(expr) = &meta.value && let syn::Lit::Str(lit_str) = &expr.lit { - writeln!(docs_buf, "{}", lit_str.value().trim()).expect("failed to write docs"); + writeln!(documentation_buffer, "{}", lit_str.value().trim()) + .expect("failed to write docs"); } } @@ -54,7 +56,7 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { } // TODO: CONTINUE HERE - let return_type = quote! { crate::interpreter::function::StaticType::Any }; // RIP ROP RAP + let return_type = map_return_type(&function.sig.output); // If the function has no argument then the cartesian product stuff below doesn't work if function.sig.inputs.is_empty() { @@ -67,7 +69,7 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { function_name, vec![], return_type.clone(), - &docs_buf, + &documentation_buffer, ) }) .collect(); @@ -102,7 +104,7 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { function_name, args, return_type.clone(), - &docs_buf, + &documentation_buffer, ); variation_id += 1; wrapped @@ -112,6 +114,69 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { .collect() } +fn map_return_type(output: &syn::ReturnType) -> TokenStream { + let convert = build(); + match output { + syn::ReturnType::Default => { + // in case return type is not specified (for closures rust defaults to type inference which doesn't help us here) + quote! { crate::interpreter::function::StaticType::Tuple(vec![]) } + } + syn::ReturnType::Type(_, ty) => map_type(ty), + } +} + +fn map_type(ty: &syn::Type) -> TokenStream { + match ty { + syn::Type::Path(p) => map_type_path(p), + syn::Type::Reference(r) => map_type(r.elem.as_ref()), + // add more variants when needed + _ => quote::quote! { crate::interpreter::function::StaticType::Any }, + } +} + +fn map_type_path(p: &syn::TypePath) -> TokenStream { + let segment = p.path.segments.last().unwrap(); + + match segment.ident.to_string().as_str() { + // Primitive single identifiers + "i32" | "i64" | "isize" | "u32" | "u64" => { + quote::quote! { crate::interpreter::function::StaticType::Int } + } + "f32" | "f64" => { + quote::quote! { crate::interpreter::function::StaticType::Float } + } + "bool" => { + quote::quote! { crate::interpreter::function::StaticType::Bool } + } + "String" | "str" => { + quote::quote! { crate::interpreter::function::StaticType::String } + } + + // Generic wrappers like anyhow::Result, std::result::Result + // "HashMap" => + "Result" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + // Extract the first generic argument + if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() { + // Recurse on T + map_type(inner_ty) + } else { + panic!("Result without generic arguments"); + } + } + + _ => { + panic!("Result return type found without syn::PathArguments::AngleBracketed"); + } + }, + + // Fallback + _ => { + quote::quote! { crate::interpreter::function::StaticType::Any } + } + } +} + /// Wraps an original rust function `function` in an outer function with the identifier `identifier` /// It's registered with the environment as `register_as_function_name` /// The argument translations mapping is defined by `input_arguments` From e0d4b8d715cfe24e208e0de026f61c6dda236731 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 09:40:11 +0100 Subject: [PATCH 13/35] Improve return types for builtins --- ndc_lib/src/stdlib/aoc.rs | 1 + ndc_lib/src/stdlib/cmp.rs | 16 ++++----- ndc_lib/src/stdlib/deque.rs | 13 ++++---- ndc_lib/src/stdlib/hash_map.rs | 9 ++++-- ndc_lib/src/stdlib/heap.rs | 8 ++--- ndc_lib/src/stdlib/list.rs | 2 +- ndc_lib/src/stdlib/regex.rs | 4 +++ ndc_lib/src/stdlib/sequence.rs | 40 +++++++++++++++++------ ndc_macros/src/function.rs | 59 +++++++++++++++++++++++++++------- 9 files changed, 108 insertions(+), 44 deletions(-) diff --git a/ndc_lib/src/stdlib/aoc.rs b/ndc_lib/src/stdlib/aoc.rs index 394d3f70..c6577521 100644 --- a/ndc_lib/src/stdlib/aoc.rs +++ b/ndc_lib/src/stdlib/aoc.rs @@ -10,6 +10,7 @@ mod inner { use crate::interpreter::value::Value; /// Counts the occurrences of each item in a sequence and returns a map with the frequencies. + #[function(return_type = DefaultMap<'_>)] pub fn frequencies(seq: &mut Sequence) -> Value { let mut out_map = HashMap::new(); diff --git a/ndc_lib/src/stdlib/cmp.rs b/ndc_lib/src/stdlib/cmp.rs index 32d7dd88..47feef7b 100644 --- a/ndc_lib/src/stdlib/cmp.rs +++ b/ndc_lib/src/stdlib/cmp.rs @@ -7,18 +7,18 @@ mod inner { use std::cmp::Ordering; /// Produces an error if the argument is not true. - pub fn assert(value: bool) -> anyhow::Result { + pub fn assert(value: bool) -> anyhow::Result<()> { if value { - Ok(Value::unit()) + Ok(()) } else { Err(anyhow!("failed asserting that argument is true")) } } /// Produces an error if the arguments aren't equal to each other. - pub fn assert_eq(left: &Value, right: &Value) -> anyhow::Result { + pub fn assert_eq(left: &Value, right: &Value) -> anyhow::Result<()> { if left == right { - Ok(Value::unit()) + Ok(()) } else { Err(anyhow!(format!( "failed asserting that {left} equals {right}" @@ -27,21 +27,21 @@ mod inner { } /// Produces an error if the arguments are equal to each other. - pub fn assert_ne(left: &Value, right: &Value) -> anyhow::Result { + pub fn assert_ne(left: &Value, right: &Value) -> anyhow::Result<()> { if left == right { Err(anyhow!(format!( "failed asserting that {left} does not equal {right}" ))) } else { - Ok(Value::unit()) + Ok(()) } } /// Produces the error specified by the `message` parameter if the `value` argument is not true. #[function(name = "assert")] - pub fn assert_with_message(value: bool, message: &str) -> anyhow::Result { + pub fn assert_with_message(value: bool, message: &str) -> anyhow::Result<()> { if value { - Ok(Value::unit()) + Ok(()) } else { Err(anyhow!(message.to_string())) } diff --git a/ndc_lib/src/stdlib/deque.rs b/ndc_lib/src/stdlib/deque.rs index d9d165f0..02afff20 100644 --- a/ndc_lib/src/stdlib/deque.rs +++ b/ndc_lib/src/stdlib/deque.rs @@ -11,7 +11,7 @@ mod inner { /// Creates a new `Deque` type. /// /// The `Deque` type allows the user to quickly append and remove elements from both the start and the end of the list. - #[function(name = "Deque")] + #[function(name = "Deque", return_type = VecDeque)] pub fn create_deque() -> Value { Value::Sequence(Sequence::Deque(Rc::new(RefCell::new(VecDeque::new())))) } @@ -34,7 +34,7 @@ mod inner { } /// Removes and returns the first element of the `deque` as an `Option` returning `None` if the queue is empty. - #[function(name = "pop_front?")] + #[function(name = "pop_front?", return_type = Option)] pub fn maybe_pop_front(deque: &mut VecDeque) -> Value { deque.pop_front().map_or_else(Value::none, Value::some) } @@ -47,7 +47,7 @@ mod inner { } /// Removes and returns the last element of the `deque` as an `Option` returning `None` if the queue is empty. - #[function(name = "pop_back?")] + #[function(name = "pop_back?", return_type = Option)] pub fn maybe_pop_back(deque: &mut VecDeque) -> Value { deque.pop_back().map_or_else(Value::none, Value::some) } @@ -61,7 +61,7 @@ mod inner { } /// Returns (but does not remove) the first element of the `deque` as an `Option` returning `None` if the queue is empty. - #[function(name = "front?")] + #[function(name = "front?", return_type = Option)] pub fn maybe_front(deque: &VecDeque) -> Value { deque.front().cloned().map_or_else(Value::none, Value::some) } @@ -75,7 +75,7 @@ mod inner { } /// Returns (but does not remove) the last element of the `deque` as an `Option` returning `None` if the queue is empty. - #[function(name = "back?")] + #[function(name = "back?", return_type = Option)] pub fn maybe_back(deque: &VecDeque) -> Value { deque.back().cloned().map_or_else(Value::none, Value::some) } @@ -91,8 +91,7 @@ mod inner { } /// Removes all elements from the `deque` - pub fn clear(deque: &mut VecDeque) -> Value { + pub fn clear(deque: &mut VecDeque) { deque.clear(); - Value::unit() } } diff --git a/ndc_lib/src/stdlib/hash_map.rs b/ndc_lib/src/stdlib/hash_map.rs index c7afbdf8..4d448ad0 100644 --- a/ndc_lib/src/stdlib/hash_map.rs +++ b/ndc_lib/src/stdlib/hash_map.rs @@ -12,6 +12,7 @@ mod inner { /// Returns a list of all the keys in the map or set. /// /// Note that for a set this will return the values in the set. + #[function(return_type = Vec<_>)] pub fn keys(map: &mut HashMap) -> Value { Value::list(map.keys().cloned().collect::>()) } @@ -19,6 +20,7 @@ mod inner { /// Returns a list of all the values in the map. /// /// Note that for sets this will return a list of unit types, you should use keys if you want the values in the set. + #[function(return_type = Vec<_>)] pub fn values(map: &mut HashMap) -> Value { Value::list(map.values().cloned().collect::>()) } @@ -106,7 +108,7 @@ mod inner { /// Returns the union (elements that are in either `left` or `right`) of two maps or sets. /// /// This is the same as evaluating the expression `left | right` - #[function(alias = "|")] + #[function(alias = "|", return_type = DefaultMap<'_>)] pub fn union(left: DefaultMap<'_>, right: &HashMap) -> Value { Value::Sequence(Sequence::Map( Rc::new(RefCell::new(hash_map::union(left.0, right))), @@ -117,7 +119,7 @@ mod inner { /// Returns the intersection (elements that are in both `left and `right`) of two maps or sets. /// /// This is the same as evaluating the expression `left & right`. - #[function(alias = "&")] + #[function(alias = "&", return_type = DefaultMap<'_>)] pub fn intersection(left: DefaultMap<'_>, right: &HashMap) -> Value { Value::Sequence(Sequence::Map( Rc::new(RefCell::new(hash_map::intersection(left.0, right))), @@ -128,7 +130,7 @@ mod inner { /// Returns the symmetric difference (elements that are either in `left` or `right` but not both) of two maps or sets. /// /// This is the same as evaluating the expression `left ~ right`. - #[function(alias = "~")] + #[function(alias = "~", return_type = DefaultMap<'_>)] pub fn symmetric_difference(left: DefaultMap<'_>, right: &HashMap) -> Value { Value::Sequence(Sequence::Map( Rc::new(RefCell::new(hash_map::symmetric_difference(left.0, right))), @@ -137,6 +139,7 @@ mod inner { } /// Converts the given sequence to set. + #[function(return_type = DefaultMap<'_>)] pub fn set(seq: &mut Sequence) -> Value { let out: HashMap = match seq { Sequence::String(rc) => rc diff --git a/ndc_lib/src/stdlib/heap.rs b/ndc_lib/src/stdlib/heap.rs index 06c6b557..5623f235 100644 --- a/ndc_lib/src/stdlib/heap.rs +++ b/ndc_lib/src/stdlib/heap.rs @@ -8,22 +8,22 @@ mod inner { use std::cell::RefCell; use std::rc::Rc; - #[function(name = "MinHeap")] + #[function(name = "MinHeap", return_type = MinHeap)] pub fn create_min_heap() -> Value { Value::Sequence(Sequence::MinHeap(Rc::new(RefCell::new(MinHeap::new())))) } - #[function(name = "MaxHeap")] + #[function(name = "MaxHeap", return_type = MaxHeap)] pub fn create_max_heap() -> Value { Value::Sequence(Sequence::MaxHeap(Rc::new(RefCell::new(MaxHeap::new())))) } - #[function(name = "pop?")] + #[function(name = "pop?", return_type = Option)] pub fn maybe_min_pop(heap: &mut MinHeap) -> Value { heap.pop().map_or_else(Value::none, Value::some) } - #[function(name = "pop?")] + #[function(name = "pop?", return_type = Option)] pub fn maybe_max_pop(heap: &mut MaxHeap) -> Value { heap.pop().map_or_else(Value::none, Value::some) } diff --git a/ndc_lib/src/stdlib/list.rs b/ndc_lib/src/stdlib/list.rs index de505e15..bab67a6f 100644 --- a/ndc_lib/src/stdlib/list.rs +++ b/ndc_lib/src/stdlib/list.rs @@ -143,7 +143,7 @@ mod inner { list.pop().unwrap_or(Value::unit()) } - #[function(name = "pop_left?")] + #[function(name = "pop_left?", return_type = Option)] pub fn maybe_pop_left(list: &mut Vec) -> Value { if list.is_empty() { return Value::none(); diff --git a/ndc_lib/src/stdlib/regex.rs b/ndc_lib/src/stdlib/regex.rs index ad161574..5ea2aeee 100644 --- a/ndc_lib/src/stdlib/regex.rs +++ b/ndc_lib/src/stdlib/regex.rs @@ -6,6 +6,7 @@ use regex::Regex; mod inner { /// Extracts all signed integers from the given string. + #[function(return_type = Vec)] pub fn nums(haystack: &str) -> Value { static RE: Lazy = Lazy::new(|| Regex::new(r"-?\d+").unwrap()); @@ -16,6 +17,7 @@ mod inner { } /// Extracts all unsigned integers from the given string. + #[function(return_type = Vec)] pub fn unsigned_nums(haystack: &str) -> Value { static RE: Lazy = Lazy::new(|| Regex::new(r"\d+").unwrap()); @@ -32,6 +34,7 @@ mod inner { } /// Returns all capture groups from the first match of the regular expression. + #[function(return_type = Vec)] pub fn captures(haystack: &str, regex: &str) -> Result { let r = Regex::new(regex)?; @@ -51,6 +54,7 @@ mod inner { } /// Returns the first capture group from the first match of the regular expression. + #[function(return_type = Vec)] pub fn capture_once(haystack: &str, regex: &str) -> Result { let r = Regex::new(regex)?; diff --git a/ndc_lib/src/stdlib/sequence.rs b/ndc_lib/src/stdlib/sequence.rs index 9e4a006b..cfbaad89 100644 --- a/ndc_lib/src/stdlib/sequence.rs +++ b/ndc_lib/src/stdlib/sequence.rs @@ -139,24 +139,26 @@ mod inner { /// Sorts the input sequence in place. /// /// This function only works for strings and lists and will throw errors otherwise. - pub fn sort(seq: &mut Sequence) -> anyhow::Result { + pub fn sort(seq: &mut Sequence) -> anyhow::Result<()> { match seq { Sequence::String(str) => { let r = &mut *str.borrow_mut(); *r = r.chars().sorted().collect::(); - Ok(Value::unit()) } Sequence::List(list) => { let mut m = list.borrow_mut(); try_sort_by(&mut m, Value::try_cmp)?; - Ok(Value::unit()) } - Sequence::Tuple(_) => Err(anyhow!("tuple cannot be sorted in place")), - Sequence::Map(_, _) => Err(anyhow!("map cannot be sorted in place")), - Sequence::Iterator(_) => Err(anyhow!("iterator cannot be sorted in place")), - Sequence::MaxHeap(_) | Sequence::MinHeap(_) => Err(anyhow!("heap is already sorted")), - Sequence::Deque(_) => Err(anyhow!("deque cannot be sorted in place")), + Sequence::Tuple(_) => return Err(anyhow!("tuple cannot be sorted in place")), + Sequence::Map(_, _) => return Err(anyhow!("map cannot be sorted in place")), + Sequence::Iterator(_) => return Err(anyhow!("iterator cannot be sorted in place")), + Sequence::MaxHeap(_) | Sequence::MinHeap(_) => { + return Err(anyhow!("heap is already sorted")); + } + Sequence::Deque(_) => return Err(anyhow!("deque cannot be sorted in place")), } + + Ok(()) } /// Sorts the given sequence using a comparing function in place. @@ -167,6 +169,7 @@ mod inner { /// - for values equal to `0` the first argument is equal to the second argument /// /// This function only works for strings and lists and will throw errors otherwise. + #[function(return_type = ())] pub fn sort_by(list: &mut Vec, comp: &Callable<'_>) -> EvaluationResult { try_sort_by::(list, |left, right| { let ret = comp.call(&mut [left.clone(), right.clone()])?; @@ -177,6 +180,7 @@ mod inner { } /// Returns a sorted copy of the input sequence as a list. + #[function(return_type = Vec)] pub fn sorted(seq: &mut Sequence) -> anyhow::Result { let mut list = mut_seq_to_iterator(seq).collect::>(); try_sort_by(&mut list, Value::try_cmp)?; @@ -189,6 +193,7 @@ mod inner { /// - for values lower than `0` the first argument is smaller than the second argument /// - for values higher than `0` the first argument is greater than the second argument /// - for values equal to `0` the first argument is equal to the second argument + #[function(return_type = Vec)] pub fn sorted_by(seq: &mut Sequence, comp: &Callable<'_>) -> EvaluationResult { let mut list = mut_seq_to_iterator(seq).collect::>(); try_sort_by::(&mut list, |left, right| { @@ -216,6 +221,8 @@ mod inner { } /// Enumerates the given sequence returning a list of tuples where the first element of the tuple is the index of the element in the input sequence. + + #[function(return_type = Vec<(i64, Value)>)] pub fn enumerate(seq: &mut Sequence) -> Value { match seq { Sequence::String(s) => Value::list( @@ -263,11 +270,13 @@ mod inner { } /// Reduces/folds the given sequence using the given combining function and a custom initial value. + #[function(return_type = Vec<_>)] pub fn fold(seq: &mut Sequence, initial: Value, function: &Callable<'_>) -> EvaluationResult { fold_iterator(mut_seq_to_iterator(seq), initial, function) } /// Reduces/folds the given sequence using the given combining function. + #[function(return_type = Value)] pub fn reduce(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { let mut iterator = mut_seq_to_iterator(seq); let fst = iterator @@ -278,6 +287,7 @@ mod inner { } /// Filters the given sequence using the `predicate`. + #[function(return_type = Vec<_>)] pub fn filter(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); let mut out = Vec::new(); @@ -298,6 +308,7 @@ mod inner { } /// Returns the number of elements in the input sequence for which the given `predicate` returns `true`. + #[function(return_type = i64)] pub fn count(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); let mut out = 0; @@ -316,6 +327,7 @@ mod inner { } /// Returns the value of the first element for which the `predicate` is true for the given input sequence. + #[function(return_type = Value)] pub fn find(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); for element in iterator { @@ -331,6 +343,7 @@ mod inner { } /// Returns the first index of the element for which the `predicate` is true in the input sequence. + #[function(return_type = usize)] pub fn locate(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); for (idx, element) in iterator.enumerate() { @@ -346,7 +359,7 @@ mod inner { } /// Returns the first index of the element or produces an error - #[function(name = "locate")] + #[function(name = "locate", return_type = usize)] pub fn locate_element(seq: &mut Sequence, element: &Value) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); for (idx, el) in iterator.enumerate() { @@ -359,6 +372,7 @@ mod inner { } /// Returns `true` if the `predicate` is true for none of the elements in `seq`. + #[function(return_type = bool)] pub fn none(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { for item in mut_seq_to_iterator(seq) { match function.call(&mut [item])? { @@ -377,6 +391,7 @@ mod inner { Ok(Value::Bool(true)) } /// Returns `true` if the `predicate` is true for all the elements in `seq`. + #[function(return_type = bool)] pub fn all(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { for item in mut_seq_to_iterator(seq) { match function.call(&mut [item])? { @@ -396,6 +411,7 @@ mod inner { } /// Returns `true` if the `predicate` is true for any of the elements in `seq`. + #[function(return_type = bool)] pub fn any(seq: &mut Sequence, predicate: &Callable<'_>) -> EvaluationResult { for item in mut_seq_to_iterator(seq) { match predicate.call(&mut [item])? { @@ -415,6 +431,7 @@ mod inner { } /// Applies the function to each element in a sequence returning the result as a list. + #[function(return_type = Vec<_>)] pub fn map(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { let iterator = mut_seq_to_iterator(seq); let mut out = Vec::new(); @@ -427,6 +444,7 @@ mod inner { } /// Applies a function to each item in a sequence, flattens the resulting sequences, and returns a single combined sequence. + #[function(return_type = Vec<_>)] pub fn flat_map(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { // let iterator = ; let mut out = Vec::new(); @@ -469,6 +487,7 @@ mod inner { } /// Returns the `k` sized combinations of the given sequence `seq` as a list of tuples. + #[function(return_type = Vec<_>)] pub fn combinations(seq: &mut Sequence, k: usize) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -479,6 +498,7 @@ mod inner { } /// Returns the `k` sized permutations of the given sequence `seq` as a list of tuples. + #[function(return_type = Vec<_>)] pub fn permutations(seq: &mut Sequence, k: usize) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -489,6 +509,7 @@ mod inner { } /// Returns al prefixes of a sequence, each as a list. + #[function(return_type = Vec<_>)] pub fn prefixes(seq: &mut Sequence) -> Value { // Special case for string which is more efficient and doesn't produce lists of characters if let Sequence::String(string) = &seq { @@ -518,6 +539,7 @@ mod inner { } /// Returns all suffixes of a sequence, each as a list; for strings, returns all trailing substrings. + #[function(return_type = Vec<_>)] pub fn suffixes(seq: &mut Sequence) -> Value { // Special case for string which is more efficient and doesn't produce lists of characters if let Sequence::String(string) = &seq { diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index 03a8976d..b69eb97e 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -7,7 +7,6 @@ use itertools::Itertools; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::fmt::Write; -use syn::spanned::Spanned; pub struct WrappedFunction { pub function_declaration: TokenStream, @@ -21,6 +20,8 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { &original_identifier.to_string(), )]; + let mut return_type = None; + let mut documentation_buffer = String::new(); for attr in &function.attrs { if attr.path().is_ident("function") { @@ -33,6 +34,10 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { } else if meta.path.is_ident("alias") { function_names.push(meta.value()?.parse()?); Ok(()) + } else if meta.path.is_ident("return_type") { + let value: syn::Type = meta.value()?.parse()?; + return_type = Some(map_type(&value)); + Ok(()) } else { Err(meta.error("unsupported property on function")) } @@ -48,6 +53,8 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { } } + let return_type = return_type.unwrap_or_else(|| map_return_type(&function.sig.output)); + match &function.vis { syn::Visibility::Public(_) => {} syn::Visibility::Restricted(_) | syn::Visibility::Inherited => { @@ -55,9 +62,6 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { } } - // TODO: CONTINUE HERE - let return_type = map_return_type(&function.sig.output); - // If the function has no argument then the cartesian product stuff below doesn't work if function.sig.inputs.is_empty() { return function_names @@ -115,7 +119,6 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { } fn map_return_type(output: &syn::ReturnType) -> TokenStream { - let convert = build(); match output { syn::ReturnType::Default => { // in case return type is not specified (for closures rust defaults to type inference which doesn't help us here) @@ -129,8 +132,17 @@ fn map_type(ty: &syn::Type) -> TokenStream { match ty { syn::Type::Path(p) => map_type_path(p), syn::Type::Reference(r) => map_type(r.elem.as_ref()), - // add more variants when needed - _ => quote::quote! { crate::interpreter::function::StaticType::Any }, + syn::Type::Tuple(t) => { + let inner = t.elems.iter().map(map_type); + quote::quote! { + crate::interpreter::function::StaticType::Tuple(vec![ + #(#inner),* + ]) + } + } + _ => { + panic!("unmapped type: {ty:?}"); + } } } @@ -139,7 +151,7 @@ fn map_type_path(p: &syn::TypePath) -> TokenStream { match segment.ident.to_string().as_str() { // Primitive single identifiers - "i32" | "i64" | "isize" | "u32" | "u64" => { + "i32" | "i64" | "isize" | "u32" | "u64" | "usize" | "BigInt" => { quote::quote! { crate::interpreter::function::StaticType::Int } } "f32" | "f64" => { @@ -152,8 +164,29 @@ fn map_type_path(p: &syn::TypePath) -> TokenStream { quote::quote! { crate::interpreter::function::StaticType::String } } + "Vec" => { + quote::quote! { crate::interpreter::function::StaticType::List } + } + "DefaultMap" | "HashMap" => { + quote::quote! { crate::interpreter::function::StaticType::Map } + } + "Number" => { + quote::quote! { crate::interpreter::function::StaticType::Number } + } + "VecDeque" => { + quote::quote! { crate::interpreter::function::StaticType::Deque } + } + "MinHeap" => { + quote::quote! { crate::interpreter::function::StaticType::MinHeap } + } + "MaxHeap" => { + quote::quote! { crate::interpreter::function::StaticType::MaxHeap } + } + "Option" => { + // TODO: in the future add generic types + quote::quote! { crate::interpreter::function::StaticType::Option } + } // Generic wrappers like anyhow::Result, std::result::Result - // "HashMap" => "Result" => match &segment.arguments { syn::PathArguments::AngleBracketed(args) => { // Extract the first generic argument @@ -169,11 +202,13 @@ fn map_type_path(p: &syn::TypePath) -> TokenStream { panic!("Result return type found without syn::PathArguments::AngleBracketed"); } }, - - // Fallback - _ => { + "Value" | "EvaluationResult" => { quote::quote! { crate::interpreter::function::StaticType::Any } } + // Fallback + unmatched => { + panic!("Cannot map type string \"{unmatched}\" to StaticType"); + } } } From 8a4339c5495cf249525c0c55ab457b951939e26f Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 13:31:58 +0100 Subject: [PATCH 14/35] Added test for weird behavior we found --- ndc_lib/src/stdlib/sequence.rs | 1 + tests/programs/998_not_desired/weird_iterator_cloning.ndct | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 tests/programs/998_not_desired/weird_iterator_cloning.ndct diff --git a/ndc_lib/src/stdlib/sequence.rs b/ndc_lib/src/stdlib/sequence.rs index cfbaad89..4750c686 100644 --- a/ndc_lib/src/stdlib/sequence.rs +++ b/ndc_lib/src/stdlib/sequence.rs @@ -565,6 +565,7 @@ mod inner { /// Transposes a sequence of sequences, turning rows into columns, and returns the result as a list of lists. // TODO: right now transposed always produces a list, it probably should produce whatever the input type was (if possible) // TODO: this might not be the expected result for sets (since iterators over sets yield tuples) + #[function(return_type = Vec<_>)] pub fn transposed(seq: &mut Sequence) -> EvaluationResult { let mut main = mut_seq_to_iterator(seq).collect::>(); let mut iterators = Vec::new(); diff --git a/tests/programs/998_not_desired/weird_iterator_cloning.ndct b/tests/programs/998_not_desired/weird_iterator_cloning.ndct new file mode 100644 index 00000000..1faabe6b --- /dev/null +++ b/tests/programs/998_not_desired/weird_iterator_cloning.ndct @@ -0,0 +1,7 @@ +--PROGRAM-- +let a = (1..10).repeat(5).transposed; +let b = [[1,2,3,4,5],[6,7,8,9]]; +assert_eq(a, b); +print("ok"); +--EXPECT-- +ok \ No newline at end of file From 3c45cad41e3a963763701292b7aa71484481076d Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 13:55:14 +0100 Subject: [PATCH 15/35] Add return types most builtins --- ndc_bin/src/docs.rs | 2 +- ndc_lib/src/stdlib/list.rs | 8 +++++--- ndc_lib/src/stdlib/rand.rs | 1 + ndc_lib/src/stdlib/sequence.rs | 11 ++++++++++- ndc_lib/src/stdlib/serde.rs | 6 +++--- ndc_lib/src/stdlib/string.rs | 7 ++++++- ndc_lib/src/stdlib/value.rs | 12 +++++++----- ndc_macros/src/function.rs | 3 +++ 8 files changed, 36 insertions(+), 14 deletions(-) diff --git a/ndc_bin/src/docs.rs b/ndc_bin/src/docs.rs index b53589cf..f0b40423 100644 --- a/ndc_bin/src/docs.rs +++ b/ndc_bin/src/docs.rs @@ -50,7 +50,7 @@ pub fn docs(query: Option<&str>) -> anyhow::Result<()> { let mut signature = String::new(); match type_sig { TypeSignature::Variadic => { - writeln!(signature, "(*args**)")?; + write!(signature, "(*args**)")?; } TypeSignature::Exact(params) => { write!(signature, "(")?; diff --git a/ndc_lib/src/stdlib/list.rs b/ndc_lib/src/stdlib/list.rs index bab67a6f..66fc5b74 100644 --- a/ndc_lib/src/stdlib/list.rs +++ b/ndc_lib/src/stdlib/list.rs @@ -9,6 +9,7 @@ mod inner { use anyhow::anyhow; /// Converts any sequence into a list + #[function(return_type = Vec)] pub fn list(seq: &mut Sequence) -> Value { Value::list(mut_seq_to_iterator(seq).collect::>()) } @@ -34,12 +35,12 @@ mod inner { } } - pub fn insert(list: &mut Vec, index: usize, elem: Value) -> anyhow::Result { + pub fn insert(list: &mut Vec, index: usize, elem: Value) -> anyhow::Result<()> { if index > list.len() { return Err(anyhow!("index {index} is out of bounds")); } list.insert(index, elem); - Ok(Value::unit()) + Ok(()) } /// Removes and returns the element at position `index` within the list, shifting all elements after it to the left. @@ -161,7 +162,7 @@ mod inner { } /// Creates a copy of the list with its elements in reverse order - /// TODO: how to deal with tuples? + #[function(return_type = Vec)] pub fn reversed(list: &[Value]) -> Value { Value::list(list.iter().rev().cloned().collect::>()) } @@ -217,6 +218,7 @@ mod inner { list.last().cloned().map_or_else(Value::none, Value::some) } + #[function(return_type = Vec<(Value, Value)>)] pub fn cartesian_product(list_a: &[Value], list_b: &[Value]) -> Value { Value::list( list_a diff --git a/ndc_lib/src/stdlib/rand.rs b/ndc_lib/src/stdlib/rand.rs index 6b7506c5..a88f9420 100644 --- a/ndc_lib/src/stdlib/rand.rs +++ b/ndc_lib/src/stdlib/rand.rs @@ -33,6 +33,7 @@ mod inner { /// Returns a copy of the input sequence converted to a list with the elements shuffled in random order. /// /// Note: this currently does consume iterators + #[function(return_type = Vec)] pub fn shuffled(list: &mut Sequence) -> Value { Value::list( mut_seq_to_iterator(list) diff --git a/ndc_lib/src/stdlib/sequence.rs b/ndc_lib/src/stdlib/sequence.rs index 4750c686..5d6d7785 100644 --- a/ndc_lib/src/stdlib/sequence.rs +++ b/ndc_lib/src/stdlib/sequence.rs @@ -586,6 +586,7 @@ mod inner { } /// Return a list of all windows, wrapping back to the first elements when the window would otherwise exceed the length of source list, producing tuples of size 2. + #[function(return_type = Vec<(Value, Value)>)] pub fn circular_tuple_windows(seq: &mut Sequence) -> Value { // TODO: this implementation probably clones a bit more than it needs to, but it's better tol // have something than nothing @@ -600,6 +601,7 @@ mod inner { } /// Returns a list of all size-2 windows in `seq`. + #[function(return_type = Vec<(Value, Value)>)] pub fn pairwise(seq: &mut Sequence) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -612,6 +614,7 @@ mod inner { /// Applies a function to each pair of consecutive elements in a sequence and returns the results as a list. #[function(name = "pairwise")] + #[function(return_type = Vec<(Value, Value)>)] pub fn pairwise_map(seq: &mut Sequence, function: &Callable<'_>) -> EvaluationResult { let main = mut_seq_to_iterator(seq).collect::>(); @@ -624,6 +627,7 @@ mod inner { } /// Returns a list of all contiguous windows of `length` size. The windows overlap. If the `seq` is shorter than size, the iterator returns no values. + #[function(return_type = Vec>)] pub fn windows(seq: &mut Sequence, length: usize) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -638,6 +642,7 @@ mod inner { /// /// The powerset of a set contains all subsets including the empty set and the full input set. A powerset has length `2^n` where `n` is the length of the input set. /// Each list produced by this function represents a subset of the elements in the source sequence. + #[function(return_type = Vec>)] pub fn subsequences(seq: &mut Sequence) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -649,6 +654,7 @@ mod inner { /// Return a list that represents the powerset of the elements of `seq` that are exactly `length` long. #[function(name = "subsequences")] + #[function(return_type = Vec>)] pub fn subsequences_len(seq: &mut Sequence, length: usize) -> Value { Value::list( mut_seq_to_iterator(seq) @@ -685,6 +691,7 @@ mod inner { /// [3,"c",false] /// ] /// ``` + #[function(return_type = Vec>)] pub fn multi_cartesian_product(seq: &mut Sequence) -> anyhow::Result { let mut iterators = Vec::new(); @@ -704,6 +711,7 @@ mod inner { /// Split the input sequence into evenly sized chunks. If the input length of the sequence /// is not dividable by the chunk_size the last chunk will contain fewer elements. + #[function(return_type = Vec>)] pub fn chunks(seq: &mut Sequence, chunk_size: usize) -> anyhow::Result { if chunk_size == 0 { return Err(anyhow!("chunk size must be non-zero")); @@ -719,6 +727,7 @@ mod inner { )) } + #[function(return_type = Iterator)] pub fn repeat(value: Value) -> Value { Value::Sequence(Sequence::Iterator(Rc::new(RefCell::new( ValueIterator::Repeat(Repeat { @@ -729,7 +738,7 @@ mod inner { )))) } - #[function(name = "repeat")] + #[function(name = "repeat", return_type = Iterator)] pub fn repeat_times(value: Value, times: usize) -> Value { Value::Sequence(Sequence::Iterator(Rc::new(RefCell::new( ValueIterator::Repeat(Repeat { diff --git a/ndc_lib/src/stdlib/serde.rs b/ndc_lib/src/stdlib/serde.rs index 60903e27..1e318f36 100644 --- a/ndc_lib/src/stdlib/serde.rs +++ b/ndc_lib/src/stdlib/serde.rs @@ -134,9 +134,9 @@ mod inner { serde_json::from_str::(input)?.try_into() } - /// Converts the input value to json - pub fn json_encode(input: Value) -> anyhow::Result { + /// Converts the input value to JSON + pub fn json_encode(input: Value) -> anyhow::Result { let v: JsonValue = input.try_into()?; - Ok(Value::string(v.to_string())) + Ok(v.to_string()) } } diff --git a/ndc_lib/src/stdlib/string.rs b/ndc_lib/src/stdlib/string.rs index 0f2f0b06..5d46e447 100644 --- a/ndc_lib/src/stdlib/string.rs +++ b/ndc_lib/src/stdlib/string.rs @@ -112,6 +112,7 @@ mod inner { } /// Splits the string into paragraphs, using blank lines as separators. + #[function(return_type = Vec)] pub fn paragraphs(string: &str) -> Value { Value::collect_list(string.split("\n\n").map(ToString::to_string)) } @@ -122,6 +123,7 @@ mod inner { } /// Splits the string into lines, using newline characters as separators. + #[function(return_type = Vec)] pub fn lines(string: &str) -> Value { Value::collect_list(string.lines().map(ToString::to_string)) } @@ -132,6 +134,7 @@ mod inner { } /// Splits the string into words, using whitespace as the separator. + #[function(return_type = Vec)] pub fn words(string: &str) -> Value { Value::collect_list(string.split_whitespace().map(ToString::to_string)) } @@ -142,6 +145,7 @@ mod inner { } /// Splits the string by whitespace into a list of substrings. + #[function(return_type = Vec)] pub fn split(string: &str) -> Value { Value::collect_list(string.split_whitespace().map(ToString::to_string)) } @@ -157,7 +161,7 @@ mod inner { } /// Splits the string using a given pattern as the delimiter. - #[function(name = "split")] + #[function(name = "split", return_type = Vec)] pub fn split_with_pattern(string: &str, pattern: &str) -> Value { Value::list( string @@ -169,6 +173,7 @@ mod inner { } /// Splits the string at the first occurrence of `pattern`, returning a tuple-like value. + #[function(name = "split", return_type = (String, String))] pub fn split_once(string: &str, pattern: &str) -> Value { match string.split_once(pattern) { Some((fst, snd)) => Value::tuple(vec![Value::string(fst), Value::string(snd)]), diff --git a/ndc_lib/src/stdlib/value.rs b/ndc_lib/src/stdlib/value.rs index 80e5dd51..91e54011 100644 --- a/ndc_lib/src/stdlib/value.rs +++ b/ndc_lib/src/stdlib/value.rs @@ -56,29 +56,31 @@ mod inner { } /// Creates a new instance of `Some` - #[function(name = "Some")] // <-- fake type constructor + #[function(name = "Some", return_type = Option)] // <-- fake type constructor pub fn some(value: Value) -> Value { Value::Option(Some(Box::new(value))) } /// Creates a new instance of `None` + #[function(return_type = Option)] pub fn none() -> Value { Value::Option(None) } /// Returns true if the argument is Some - pub fn is_some(value: &Value) -> Value { - Value::Bool(matches!(value, Value::Option(Some(_)))) + pub fn is_some(value: &Value) -> bool { + matches!(value, Value::Option(Some(_))) } /// Returns true if the argument is None - pub fn is_none(value: &Value) -> Value { - Value::Bool(matches!(value, Value::Option(None))) + pub fn is_none(value: &Value) -> bool { + matches!(value, Value::Option(None)) } /// Extracts the value from an Option or errors if it's either None or a non-Option type /// /// Note: this function should take an Option as parameter + // TODO: the type of value should be `Option` but the macro crate probably doesn't support that yet pub fn unwrap(value: Value) -> anyhow::Result { match value { Value::Option(Some(val)) => Ok(*val), diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index b69eb97e..a278d326 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -182,6 +182,9 @@ fn map_type_path(p: &syn::TypePath) -> TokenStream { "MaxHeap" => { quote::quote! { crate::interpreter::function::StaticType::MaxHeap } } + "Iterator" => { + quote::quote! { crate::interpreter::function::StaticType::Iterator } + } "Option" => { // TODO: in the future add generic types quote::quote! { crate::interpreter::function::StaticType::Option } From 3ac72c7bf5ac49af2e11c8799edb3f7ea215ef5b Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 14:00:15 +0100 Subject: [PATCH 16/35] Clippy fixes --- ndc_lib/src/interpreter/function.rs | 10 +++++----- ndc_lib/src/interpreter/semantic/analyser.rs | 6 ++---- ndc_lib/src/stdlib/string.rs | 2 +- ndc_macros/src/function.rs | 6 +++--- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 0e53a0b0..035803b2 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -151,11 +151,11 @@ impl FunctionBody { pub fn return_type(&self) -> &StaticType { match self { - FunctionBody::Closure { return_type, .. } => return_type, - FunctionBody::NumericUnaryOp { .. } => &StaticType::Number, - FunctionBody::NumericBinaryOp { .. } => &StaticType::Number, - FunctionBody::GenericFunction { return_type, .. } => return_type, - FunctionBody::Memoized { function, .. } => function.return_type(), + Self::Closure { return_type, .. } | Self::GenericFunction { return_type, .. } => { + return_type + } + Self::NumericUnaryOp { .. } | Self::NumericBinaryOp { .. } => &StaticType::Number, + Self::Memoized { function, .. } => function.return_type(), } } pub fn call(&self, args: &mut [Value], env: &Rc>) -> EvaluationResult { diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index f66984ce..4f9ba90d 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -21,12 +21,10 @@ impl Analyser { match expression { Expression::BoolLiteral(_) => Ok(StaticType::Bool), Expression::StringLiteral(_) => Ok(StaticType::String), - Expression::Int64Literal(_) => Ok(StaticType::Int), + Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => Ok(StaticType::Int), Expression::Float64Literal(_) => Ok(StaticType::Float), - Expression::BigIntLiteral(_) => Ok(StaticType::Int), Expression::ComplexLiteral(_) => Ok(StaticType::Complex), - Expression::Continue => Ok(StaticType::unit()), // TODO: change to never type? - Expression::Break => Ok(StaticType::unit()), + Expression::Continue | Expression::Break => Ok(StaticType::unit()), // TODO: change to never type? Expression::Identifier { name: ident, resolved, diff --git a/ndc_lib/src/stdlib/string.rs b/ndc_lib/src/stdlib/string.rs index 5d46e447..9c3b4d3f 100644 --- a/ndc_lib/src/stdlib/string.rs +++ b/ndc_lib/src/stdlib/string.rs @@ -173,7 +173,7 @@ mod inner { } /// Splits the string at the first occurrence of `pattern`, returning a tuple-like value. - #[function(name = "split", return_type = (String, String))] + #[function(return_type = (String, String))] pub fn split_once(string: &str, pattern: &str) -> Value { match string.split_once(pattern) { Some((fst, snd)) => Value::tuple(vec![Value::string(fst), Value::string(snd)]), diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index a278d326..117f0809 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -72,7 +72,7 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { &original_identifier, function_name, vec![], - return_type.clone(), + &return_type, &documentation_buffer, ) }) @@ -107,7 +107,7 @@ pub fn wrap_function(function: &syn::ItemFn) -> Vec { &format_ident!("{original_identifier}_{variation_id}"), function_name, args, - return_type.clone(), + &return_type, &documentation_buffer, ); variation_id += 1; @@ -223,7 +223,7 @@ fn wrap_single( identifier: &syn::Ident, register_as_function_name: &proc_macro2::Literal, input_arguments: Vec, - return_type: TokenStream, + return_type: &TokenStream, docs: &str, ) -> WrappedFunction { let inner_ident = format_ident!("{}_inner", identifier); From c83a7780a9b3703862c8ae32ea14fa4785830b42 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 14:13:29 +0100 Subject: [PATCH 17/35] WIP: Also store global functions in a vec without merging updated LSP to show return types --- ndc_bin/src/docs.rs | 8 ++--- ndc_lib/src/interpreter/environment.rs | 19 ++++++---- ndc_lsp/src/backend.rs | 48 +++++++++++++------------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/ndc_bin/src/docs.rs b/ndc_bin/src/docs.rs index f0b40423..9b9ba97d 100644 --- a/ndc_bin/src/docs.rs +++ b/ndc_bin/src/docs.rs @@ -18,8 +18,7 @@ pub fn docs(query: Option<&str>) -> anyhow::Result<()> { let matched_functions = functions .into_iter() - .flat_map(|x| x.implementations().collect::>()) - .filter(|(_, func)| { + .filter(|func| { if let Some(query) = query { string_match(query, func.name()) } else { @@ -28,7 +27,7 @@ pub fn docs(query: Option<&str>) -> anyhow::Result<()> { }) .collect::>() .tap_mut(|list| { - list.sort_by(|(_, l), (_, r)| { + list.sort_by(|l, r| { if let Some(query) = query { normalized_damerau_levenshtein(l.name(), query) .partial_cmp(&normalized_damerau_levenshtein(r.name(), query)) @@ -46,8 +45,9 @@ pub fn docs(query: Option<&str>) -> anyhow::Result<()> { skin.headers[1].align = Alignment::Left; skin.headers[2].align = Alignment::Left; - for (type_sig, function) in matched_functions { + for function in matched_functions { let mut signature = String::new(); + let type_sig = function.type_signature(); match type_sig { TypeSignature::Variadic => { write!(signature, "(*args**)")?; diff --git a/ndc_lib/src/interpreter/environment.rs b/ndc_lib/src/interpreter/environment.rs index 51727eab..534fdb04 100644 --- a/ndc_lib/src/interpreter/environment.rs +++ b/ndc_lib/src/interpreter/environment.rs @@ -13,6 +13,7 @@ pub struct RootEnvironment { pub output: Box, // These are global values global_functions: Vec, + global_functions2: Vec, } pub struct Environment { @@ -55,6 +56,7 @@ impl Environment { let root = RootEnvironment { output: writer, global_functions: Default::default(), + global_functions2: Default::default(), }; let mut env = Self { @@ -83,8 +85,8 @@ impl Environment { } #[must_use] - pub fn get_all_functions(&self) -> Vec { - self.root.borrow().global_functions.clone() + pub fn get_all_functions(&self) -> Vec { + self.root.borrow().global_functions2.clone() } pub fn set(&mut self, var: ResolvedVar, value: Value) { @@ -99,7 +101,6 @@ impl Environment { } } else { self.values.push(value); - // self.values.insert(slot, value); } return; @@ -109,7 +110,6 @@ impl Environment { self.values[slot] = value } else { debug_assert!(slot == self.values.len()); - // self.values.insert(slot, value) // TODO: push should work here right self.values.push(value); } } @@ -139,10 +139,14 @@ impl Environment { pub fn declare_global_fn(&mut self, function: impl Into) { let new_function = function.into(); - let gb = &mut self.root.borrow_mut().global_functions; + let root: &mut RootEnvironment = &mut self.root.borrow_mut(); + + root.global_functions2.push(new_function.clone()); + + // TODO: the code below can be removed one day // Try to add it to an existing definition - for fun in gb.iter_mut() { + for fun in root.global_functions.iter_mut() { if fun.name() == Some(new_function.name()) && fun.arity() == new_function.arity() { fun.add(new_function); return; @@ -150,7 +154,8 @@ impl Environment { } // Create a new definition - gb.push(OverloadedFunction::from_multiple(vec![new_function])) + root.global_functions + .push(OverloadedFunction::from_multiple(vec![new_function])) } fn get_copy_from_slot(&self, depth: usize, slot: usize) -> Value { diff --git a/ndc_lsp/src/backend.rs b/ndc_lsp/src/backend.rs index 677bdb93..08bf3e50 100644 --- a/ndc_lsp/src/backend.rs +++ b/ndc_lsp/src/backend.rs @@ -115,30 +115,24 @@ impl LanguageServer for Backend { let env = interpreter.environment(); let functions = env.borrow().get_all_functions(); - let items = functions.iter().flat_map(|f| { - f.implementations().filter_map(|(sig, fun)| { - // Ignore operators - if fun - .name() - .chars() - .all(|c| c.is_alphanumeric() || c == '?' || c == '_') - { - Some(CompletionItem { - label: fun.name().to_string(), - label_details: Some(CompletionItemLabelDetails { - detail: Some(format!("({sig})")), - description: None, - }), - kind: Some(CompletionItemKind::FUNCTION), - documentation: Some(Documentation::MarkupContent(MarkupContent { - kind: MarkupKind::Markdown, - value: fun.documentation().to_string(), - })), - ..Default::default() - }) - } else { - None - } + let items = functions.iter().filter_map(|fun| { + // Ignore operators + if !is_normal_ident(fun.name()) { + return None; + } + + Some(CompletionItem { + label: fun.name().to_string(), + label_details: Some(CompletionItemLabelDetails { + detail: Some(format!("({})", fun.type_signature())), + description: Some(fun.return_type().to_string()), + }), + kind: Some(CompletionItemKind::FUNCTION), + documentation: Some(Documentation::MarkupContent(MarkupContent { + kind: MarkupKind::Markdown, + value: fun.documentation().to_string(), + })), + ..Default::default() }) }); @@ -220,3 +214,9 @@ fn position_from_offset(text: &str, offset: usize) -> Position { character: col, } } + +fn is_normal_ident(input: &str) -> bool { + input + .chars() + .all(|c| c.is_alphanumeric() || c == '?' || c == '_') +} From cb08296e98cfbe15fe6065bebab47e6ab1c2978b Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 15:30:04 +0100 Subject: [PATCH 18/35] WIP resolve correct function during resolution pass note: it works but the return type of a function call is not inferred correctly so we need to add it to the function type --- ndc_lib/src/interpreter/environment.rs | 32 +--- ndc_lib/src/interpreter/function.rs | 13 +- ndc_lib/src/interpreter/mod.rs | 6 +- ndc_lib/src/interpreter/semantic/analyser.rs | 183 ++++++++++++++++--- 4 files changed, 175 insertions(+), 59 deletions(-) diff --git a/ndc_lib/src/interpreter/environment.rs b/ndc_lib/src/interpreter/environment.rs index 534fdb04..c9922420 100644 --- a/ndc_lib/src/interpreter/environment.rs +++ b/ndc_lib/src/interpreter/environment.rs @@ -12,8 +12,7 @@ use std::rc::Rc; pub struct RootEnvironment { pub output: Box, // These are global values - global_functions: Vec, - global_functions2: Vec, + global_functions: Vec, } pub struct Environment { @@ -56,7 +55,6 @@ impl Environment { let root = RootEnvironment { output: writer, global_functions: Default::default(), - global_functions2: Default::default(), }; let mut env = Self { @@ -70,23 +68,21 @@ impl Environment { env } - pub fn create_global_scope_mapping(&self) -> Vec { + pub fn get_global_identifiers(&self) -> Vec { self.root .borrow() .global_functions .iter() - .filter_map(|function| { - function.name().map(|name| LexicalIdentifier::Function { - name: name.to_string(), - arity: function.arity(), - }) + .map(|function| { + debug_assert_ne!(function.name(), ""); + LexicalIdentifier::from_function(function) }) .collect::>() } #[must_use] pub fn get_all_functions(&self) -> Vec { - self.root.borrow().global_functions2.clone() + self.root.borrow().global_functions.clone() } pub fn set(&mut self, var: ResolvedVar, value: Value) { @@ -141,21 +137,7 @@ impl Environment { let root: &mut RootEnvironment = &mut self.root.borrow_mut(); - root.global_functions2.push(new_function.clone()); - - // TODO: the code below can be removed one day - - // Try to add it to an existing definition - for fun in root.global_functions.iter_mut() { - if fun.name() == Some(new_function.name()) && fun.arity() == new_function.arity() { - fun.add(new_function); - return; - } - } - - // Create a new definition - root.global_functions - .push(OverloadedFunction::from_multiple(vec![new_function])) + root.global_functions.push(new_function.clone()); } fn get_copy_from_slot(&self, depth: usize, slot: usize) -> Value { diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 035803b2..feaa6537 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -37,6 +37,17 @@ pub struct Function { body: FunctionBody, } +impl fmt::Debug for Function { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Function(name={}, sig={})", + self.name(), + self.type_signature() + ) + } +} + impl Function { pub fn arity(&self) -> Option { self.body.arity() @@ -287,7 +298,7 @@ impl TypeSignature { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct OverloadedFunction { implementations: HashMap, } diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index 662d6ee5..415ba3b5 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -34,11 +34,11 @@ impl Interpreter { T: InterpreterOutput + 'static, { let environment = Environment::new_with_stdlib(Box::new(dest)); - let hash_map = environment.create_global_scope_mapping(); + let global_identifiers = environment.get_global_identifiers(); Self { environment: Rc::new(RefCell::new(environment)), - analyser: Analyser::from_scope_tree(ScopeTree::from_global_scope(hash_map)), + analyser: Analyser::from_scope_tree(ScopeTree::from_global_scope(global_identifiers)), } } @@ -72,7 +72,7 @@ impl Interpreter { } // dbg!(&expressions); - // dbg!(&self.lexical_data); + // dbg!(&self.analyser); let final_value = self.interpret(expressions.into_iter())?; diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 4f9ba90d..a3aa6a3a 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -1,8 +1,10 @@ use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; -use crate::interpreter::function::StaticType; +use crate::interpreter::function::{Function, StaticType, TypeSignature}; use crate::lexer::Span; use itertools::Itertools; +use std::iter::repeat; +#[derive(Debug)] pub struct Analyser { scope_tree: ScopeTree, } @@ -68,8 +70,9 @@ impl Analyser { resolved_assign_operation, resolved_operation, } => { - self.resolve_lvalue(l_value, *span)?; - self.analyse(r_value)?; + let left_type = self.resolve_lvalue_as_ident(l_value, *span)?; + let right_type = self.analyse(r_value)?; + let op_assign_arg_types = Some(vec![left_type, right_type]); // In this future should be able to use our type system to figure out which specific // instance of the function we need. When this is the case we can stop inserting both @@ -77,7 +80,7 @@ impl Analyser { // if there is none let op_assign_ident = LexicalIdentifier::Function { name: format!("{operation}="), - arity: Some(2), + signature: op_assign_arg_types.clone(), // TODO: we should be able to remove this }; if let Some(binding) = self.scope_tree.get_binding(&op_assign_ident) { @@ -86,7 +89,7 @@ impl Analyser { let operation_ident = LexicalIdentifier::Function { name: (*operation).clone(), - arity: Some(2), + signature: op_assign_arg_types, }; if let Some(binding) = self.scope_tree.get_binding(&operation_ident) { @@ -110,7 +113,12 @@ impl Analyser { if let Some(name) = name { let function_ident = LexicalIdentifier::Function { name: (*name).clone(), - arity: Some(extract_argument_arity(parameters)), + // TODO: figuring out the type signature of function declarations is the rest of the owl + signature: Some( + repeat(StaticType::Any) + .take(extract_argument_arity(parameters)) + .collect(), + ), }; *resolved_name = @@ -189,13 +197,14 @@ impl Analyser { function, arguments, } => { - let _the_type_of_the_function_which_doesnt_help_us_now = - self.resolve_function_ident_arity(function, arguments.len(), *span)?; + let mut type_sig = Vec::with_capacity(arguments.len()); for a in arguments { - self.analyse(a)?; + type_sig.push(self.analyse(a)?); } - // Maybe we can use _the_type_of_the_function_which_doesnt_help_us_now to find the return value?!?! - // TODO: figure out the type + + let function = + self.resolve_function_with_argument_types(function, &type_sig, *span)?; + Ok(StaticType::Any) } Expression::Index { index, value } => { @@ -250,10 +259,10 @@ impl Analyser { } } } - fn resolve_function_ident_arity( + fn resolve_function_with_argument_types( &mut self, ident: &mut ExpressionLocation, - arity: usize, + argument_types: &[StaticType], span: Span, ) -> Result { let ExpressionLocation { @@ -269,19 +278,21 @@ impl Analyser { let binding = self .scope_tree .get_binding(&LexicalIdentifier::Function { - name: name.clone(), - arity: Some(arity), - }) - .or_else(|| { - self.scope_tree.get_binding(&LexicalIdentifier::Function { - name: name.clone(), - arity: None, - }) + name: name.clone(), // TODO: get rid of clone + signature: Some(argument_types.to_vec()), // TODO: get rid of to_vec }) + // NOTE: we don't have to look for variadic functions (I think) + // If no match was found we look for a variadic function with the given name + // .or_else(|| { + // self.scope_tree.get_binding(&LexicalIdentifier::Function { + // name: name.clone(), + // signature: None, + // }) + // }) // NOTE: for now if we can't find a function that matches we just bind to a variable with that name (if possible) // this fixes some cases until we have full type checking - .or_else(|| (self.scope_tree).get_binding_any(name)) - .ok_or_else(|| AnalysisError::identifier_not_previously_declared(name, span))?; + // .or_else(|| (self.scope_tree).get_binding_any(name)) + .ok_or_else(|| AnalysisError::function_not_found(name, &argument_types, span))?; *resolved = Some(binding); @@ -346,6 +357,30 @@ impl Analyser { Ok(()) } + fn resolve_lvalue_as_ident( + &mut self, + lvalue: &mut Lvalue, + span: Span, + ) -> Result { + let Lvalue::Identifier { + identifier, + resolved, + } = lvalue + else { + return Err(AnalysisError::lvalue_required_to_be_single_identifier(span)); + }; + + let Some(target) = self.scope_tree.get_binding(&identifier.clone().into()) else { + return Err(AnalysisError::identifier_not_previously_declared( + identifier, span, + )); + }; + + *resolved = Some(target); + + Ok(self.scope_tree.get_type(target)) + } + fn resolve_lvalue(&mut self, lvalue: &mut Lvalue, span: Span) -> Result<(), AnalysisError> { match lvalue { Lvalue::Identifier { @@ -534,11 +569,83 @@ fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { #[derive(Debug, Hash, Eq, PartialEq, Clone)] pub enum LexicalIdentifier { - Variable { name: String }, - Function { name: String, arity: Option }, + Variable { + name: String, + }, + Function { + name: String, + signature: Option>, + }, } impl LexicalIdentifier { + pub fn is_subtype_match(&self, other: &Self) -> bool { + match (self, other) { + ( + LexicalIdentifier::Variable { name: name_a }, + LexicalIdentifier::Variable { name: name_b }, + ) => name_a == name_b, + + ( + LexicalIdentifier::Function { name: name_a, .. }, + LexicalIdentifier::Variable { name: name_b }, + ) => name_a == name_b, + ( + LexicalIdentifier::Variable { name: name_a }, + LexicalIdentifier::Function { name: name_b, .. }, + ) => name_a == name_b, + ( + LexicalIdentifier::Function { + name: name_a, + signature: signature_a, + }, + LexicalIdentifier::Function { + name: name_b, + signature: signature_b, + }, + ) => { + if name_a != name_b { + return false; + } + + println!("---------------------------------------------"); + dbg!(name_a, name_b, signature_a, signature_b); + + let signature_a = signature_a + .as_deref() + .expect("the lookup side of a function match must not be variadic"); + + let Some(signature_b) = signature_b else { + return true; + }; + + if signature_a.len() != signature_b.len() { + return false; + } + + for (a, b) in signature_a.iter().zip(signature_b.iter()) { + if !a.is_subtype_of(b) { + return false; + } + } + + true + } + } + } + + pub fn from_function(function: &Function) -> Self { + debug_assert!(!function.name().is_empty()); + Self::Function { + name: function.name().to_string(), // TODO: what if name is empty? + signature: match function.type_signature() { + TypeSignature::Variadic => None, + TypeSignature::Exact(params) => { + Some(params.into_iter().map(|param| param.type_name).collect()) + } + }, + } + } pub fn name(&self) -> &str { match self { Self::Variable { name } | Self::Function { name, .. } => name, @@ -581,7 +688,6 @@ impl ScopeTree { } fn get_type(&self, res: ResolvedVar) -> StaticType { - // dbg!(self, res); match res { ResolvedVar::Captured { slot, depth } => { let mut scope_idx = self.current_scope_idx; @@ -645,19 +751,19 @@ impl ScopeTree { } } - fn get_binding(&mut self, name: &LexicalIdentifier) -> Option { + fn get_binding(&mut self, lexical_ident: &LexicalIdentifier) -> Option { let mut depth = 0; let mut scope_ptr = self.current_scope_idx; loop { - if let Some(slot) = self.scopes[scope_ptr].get_slot(name) { + if let Some(slot) = self.scopes[scope_ptr].get_slot(lexical_ident) { return Some(ResolvedVar::Captured { slot, depth }); } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { depth += 1; scope_ptr = parent_idx; } else { return Some(ResolvedVar::Global { - slot: self.global_scope.get_slot(name)?, + slot: self.global_scope.get_slot(lexical_ident)?, }); } } @@ -694,7 +800,7 @@ impl Scope { fn get_slot(&mut self, find_ident: &LexicalIdentifier) -> Option { self.identifiers .iter() - .rposition(|(ident, _type)| ident == find_ident) + .rposition(|(ident, _type)| find_ident.is_subtype_match(ident)) } fn allocate(&mut self, name: LexicalIdentifier, typ: StaticType) -> usize { @@ -712,6 +818,23 @@ pub struct AnalysisError { } impl AnalysisError { + fn lvalue_required_to_be_single_identifier(span: Span) -> AnalysisError { + Self { + text: "This lvalue is required to be a single identifier".to_string(), + span, + } + } + + fn function_not_found(ident: &str, types: &[StaticType], span: Span) -> AnalysisError { + Self { + text: format!( + "No function named '{ident}' found that matches the arguments {}", + types.iter().join(", ") + ), + span, + } + } + fn identifier_not_previously_declared(ident: &str, span: Span) -> Self { Self { text: format!("Identifier {ident} has not previously been declared"), From ab419636fd503cd12b0ab1304b8e4e1b1cf2e3ee Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 20:32:11 +0100 Subject: [PATCH 19/35] Way too static function matching --- ndc_lib/src/interpreter/environment.rs | 12 +- ndc_lib/src/interpreter/function.rs | 41 ++- ndc_lib/src/interpreter/mod.rs | 2 +- ndc_lib/src/interpreter/semantic/analyser.rs | 304 ++++++------------- ndc_lib/src/interpreter/value.rs | 9 +- ndc_macros/src/function.rs | 15 +- 6 files changed, 157 insertions(+), 226 deletions(-) diff --git a/ndc_lib/src/interpreter/environment.rs b/ndc_lib/src/interpreter/environment.rs index c9922420..a98fe20a 100644 --- a/ndc_lib/src/interpreter/environment.rs +++ b/ndc_lib/src/interpreter/environment.rs @@ -1,7 +1,6 @@ -use crate::interpreter::function::{Function, OverloadedFunction}; +use crate::interpreter::function::{Function, StaticType}; use crate::ast::ResolvedVar; -use crate::interpreter::semantic::analyser::LexicalIdentifier; use crate::interpreter::value::Value; use std::cell::RefCell; use std::fmt; @@ -68,16 +67,13 @@ impl Environment { env } - pub fn get_global_identifiers(&self) -> Vec { + pub fn get_global_identifiers(&self) -> Vec<(String, StaticType)> { self.root .borrow() .global_functions .iter() - .map(|function| { - debug_assert_ne!(function.name(), ""); - LexicalIdentifier::from_function(function) - }) - .collect::>() + .map(|function| (function.name().to_string(), function.static_type())) + .collect::>() } #[must_use] diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index feaa6537..54922808 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -86,6 +86,18 @@ impl Function { pub fn return_type(&self) -> &StaticType { self.body.return_type() } + + pub fn static_type(&self) -> StaticType { + StaticType::Function { + parameters: match self.body.type_signature() { + TypeSignature::Variadic => None, + TypeSignature::Exact(types) => { + Some(types.iter().map(|x| x.type_name.clone()).collect()) + } + }, + return_type: Box::new(self.body.return_type().clone()), + } + } } #[derive(Clone)] @@ -274,7 +286,7 @@ impl TypeSignature { for (a, b) in types.iter().zip(signature.iter()) { let dist = if a == &b.type_name { 0 - } else if a.is_subtype_of(&b.type_name) { + } else if a.is_compatible_with(&b.type_name) { 1 } else { return None; @@ -470,7 +482,10 @@ impl Parameter { pub enum StaticType { Any, Bool, - Function, + Function { + parameters: Option>, + return_type: Box, + }, Option, // Numbers @@ -532,11 +547,15 @@ impl StaticType { } } - #[allow(clippy::match_like_matches_macro)] - pub fn is_subtype_of(&self, other: &Self) -> bool { + #[allow(clippy::match_same_arms)] + pub fn is_compatible_with(&self, other: &Self) -> bool { match (self, other) { + (a, b) if a == b => true, (_, Self::Any) => true, - (Self::Int | Self::Rational | Self::Complex | Self::Float, Self::Number) => true, + ( + Self::Number | Self::Int | Self::Rational | Self::Complex | Self::Float, + Self::Number, + ) => true, ( Self::String | Self::List @@ -558,7 +577,17 @@ impl fmt::Display for StaticType { match self { Self::Any => write!(f, "Any"), Self::Bool => write!(f, "Bool"), - Self::Function => write!(f, "Function"), + Self::Function { + parameters, + return_type, + } => write!( + f, + "Function({}) -> {return_type}", + parameters + .as_deref() + .map(|p| p.iter().join(", ")) + .unwrap_or(String::from("*")) + ), Self::Option => write!(f, "Option"), Self::Number => write!(f, "Number"), Self::Float => write!(f, "Float"), diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index 415ba3b5..a3997a35 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -135,7 +135,7 @@ pub enum InterpreterError { #[from] cause: crate::ast::Error, }, - #[error("Error during resolver pass")] + #[error("Error during static analysis")] #[diagnostic(transparent)] Resolver { #[from] diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index a3aa6a3a..ef0f6865 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -1,5 +1,5 @@ use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; -use crate::interpreter::function::{Function, StaticType, TypeSignature}; +use crate::interpreter::function::StaticType; use crate::lexer::Span; use itertools::Itertools; use std::iter::repeat; @@ -41,7 +41,7 @@ impl Analyser { *resolved = Some(binding); - Ok(self.scope_tree.get_type(binding)) // are we guaranteed to even have a type + Ok(self.scope_tree.get_type(binding).clone()) } Expression::Statement(inner) => { self.analyse(inner)?; @@ -72,27 +72,19 @@ impl Analyser { } => { let left_type = self.resolve_lvalue_as_ident(l_value, *span)?; let right_type = self.analyse(r_value)?; - let op_assign_arg_types = Some(vec![left_type, right_type]); - - // In this future should be able to use our type system to figure out which specific - // instance of the function we need. When this is the case we can stop inserting both - // versions in the OpAssignment expression and just figure out the correct one or error - // if there is none - let op_assign_ident = LexicalIdentifier::Function { - name: format!("{operation}="), - signature: op_assign_arg_types.clone(), // TODO: we should be able to remove this - }; + let op_assign_arg_types = vec![left_type, right_type]; - if let Some(binding) = self.scope_tree.get_binding(&op_assign_ident) { + if let Some(binding) = self + .scope_tree + .get_function_binding(&format!("{operation}="), &op_assign_arg_types) + { *resolved_assign_operation = Some(binding); } - let operation_ident = LexicalIdentifier::Function { - name: (*operation).clone(), - signature: op_assign_arg_types, - }; - - if let Some(binding) = self.scope_tree.get_binding(&operation_ident) { + if let Some(binding) = self + .scope_tree + .get_function_binding(&operation, &op_assign_arg_types) + { *resolved_operation = Some(binding); Ok(StaticType::unit()) @@ -110,31 +102,31 @@ impl Analyser { body, .. } => { - if let Some(name) = name { - let function_ident = LexicalIdentifier::Function { - name: (*name).clone(), - // TODO: figuring out the type signature of function declarations is the rest of the owl - signature: Some( - repeat(StaticType::Any) - .take(extract_argument_arity(parameters)) - .collect(), - ), - }; - - *resolved_name = - Some(self.scope_tree.get_or_create_local_binding( - function_ident.clone(), - StaticType::Function, - )) - } + // TODO: figuring out the type signature of function declarations is the rest of the owl + let param_types: Vec = repeat(StaticType::Any) + .take(extract_argument_arity(parameters)) + .collect(); self.scope_tree.new_scope(); self.resolve_parameters_declarative(parameters); - self.analyse(body)?; + let return_type = self.analyse(body)?; self.scope_tree.destroy_scope(); - Ok(StaticType::Function) + let function_type = StaticType::Function { + parameters: Some(param_types.clone()), + return_type: Box::new(return_type), + }; + + if let Some(name) = name { + // TODO: is this correct, for now we just always create a new binding, we could also produce an error if we are generating a conflicting binding + *resolved_name = Some( + self.scope_tree + .create_local_binding(name.clone(), function_type.clone()), + ); + } + + Ok(function_type) } Expression::Block { statements } => { self.scope_tree.new_scope(); @@ -202,10 +194,15 @@ impl Analyser { type_sig.push(self.analyse(a)?); } - let function = - self.resolve_function_with_argument_types(function, &type_sig, *span)?; + let StaticType::Function { return_type, .. } = + self.resolve_function_with_argument_types(function, &type_sig, *span)? + else { + unreachable!( + "resolve_function_with_argument_types should guarantee us a function type" + ); + }; - Ok(StaticType::Any) + Ok(*return_type) } Expression::Index { index, value } => { self.analyse(index)?; @@ -277,26 +274,13 @@ impl Analyser { let binding = self .scope_tree - .get_binding(&LexicalIdentifier::Function { - name: name.clone(), // TODO: get rid of clone - signature: Some(argument_types.to_vec()), // TODO: get rid of to_vec - }) - // NOTE: we don't have to look for variadic functions (I think) - // If no match was found we look for a variadic function with the given name - // .or_else(|| { - // self.scope_tree.get_binding(&LexicalIdentifier::Function { - // name: name.clone(), - // signature: None, - // }) - // }) - // NOTE: for now if we can't find a function that matches we just bind to a variable with that name (if possible) - // this fixes some cases until we have full type checking - // .or_else(|| (self.scope_tree).get_binding_any(name)) - .ok_or_else(|| AnalysisError::function_not_found(name, &argument_types, span))?; + .get_function_binding(name, argument_types) + // TODO: instead of throwing an error we can emit a dynamic binding + .ok_or_else(|| AnalysisError::function_not_found(name, argument_types, span))?; *resolved = Some(binding); - Ok(StaticType::Function) + Ok(self.scope_tree.get_type(binding).clone()) } fn resolve_for_iterations( &mut self, @@ -370,7 +354,7 @@ impl Analyser { return Err(AnalysisError::lvalue_required_to_be_single_identifier(span)); }; - let Some(target) = self.scope_tree.get_binding(&identifier.clone().into()) else { + let Some(target) = self.scope_tree.get_binding_any(&identifier) else { return Err(AnalysisError::identifier_not_previously_declared( identifier, span, )); @@ -378,7 +362,7 @@ impl Analyser { *resolved = Some(target); - Ok(self.scope_tree.get_type(target)) + Ok(self.scope_tree.get_type(target).clone()) } fn resolve_lvalue(&mut self, lvalue: &mut Lvalue, span: Span) -> Result<(), AnalysisError> { @@ -387,7 +371,7 @@ impl Analyser { identifier, resolved, } => { - let Some(target) = self.scope_tree.get_binding(&identifier.clone().into()) else { + let Some(target) = self.scope_tree.get_binding_any(&identifier) else { return Err(AnalysisError::identifier_not_previously_declared( identifier, span, )); @@ -428,12 +412,12 @@ impl Analyser { panic!("expected tuple values to be ident"); }; - *resolved = Some(self.scope_tree.create_local_binding( - LexicalIdentifier::Variable { - name: (*name).clone(), - }, - StaticType::Any, - )); + // TODO: big challenge how do we figure out the function parameter types? + // it seems like this is something we need an HM like system for!? + *resolved = Some( + self.scope_tree + .create_local_binding(name.to_string(), StaticType::Any), + ); } } fn resolve_lvalue_declarative( @@ -446,12 +430,10 @@ impl Analyser { identifier, resolved, } => { - *resolved = Some(self.scope_tree.create_local_binding( - LexicalIdentifier::Variable { - name: (*identifier).clone(), - }, - typ, - )); + *resolved = Some( + self.scope_tree + .create_local_binding(identifier.clone(), typ), + ); } Lvalue::Index { index, value } => { self.analyse(index)?; @@ -567,103 +549,6 @@ fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { values.len() } -#[derive(Debug, Hash, Eq, PartialEq, Clone)] -pub enum LexicalIdentifier { - Variable { - name: String, - }, - Function { - name: String, - signature: Option>, - }, -} - -impl LexicalIdentifier { - pub fn is_subtype_match(&self, other: &Self) -> bool { - match (self, other) { - ( - LexicalIdentifier::Variable { name: name_a }, - LexicalIdentifier::Variable { name: name_b }, - ) => name_a == name_b, - - ( - LexicalIdentifier::Function { name: name_a, .. }, - LexicalIdentifier::Variable { name: name_b }, - ) => name_a == name_b, - ( - LexicalIdentifier::Variable { name: name_a }, - LexicalIdentifier::Function { name: name_b, .. }, - ) => name_a == name_b, - ( - LexicalIdentifier::Function { - name: name_a, - signature: signature_a, - }, - LexicalIdentifier::Function { - name: name_b, - signature: signature_b, - }, - ) => { - if name_a != name_b { - return false; - } - - println!("---------------------------------------------"); - dbg!(name_a, name_b, signature_a, signature_b); - - let signature_a = signature_a - .as_deref() - .expect("the lookup side of a function match must not be variadic"); - - let Some(signature_b) = signature_b else { - return true; - }; - - if signature_a.len() != signature_b.len() { - return false; - } - - for (a, b) in signature_a.iter().zip(signature_b.iter()) { - if !a.is_subtype_of(b) { - return false; - } - } - - true - } - } - } - - pub fn from_function(function: &Function) -> Self { - debug_assert!(!function.name().is_empty()); - Self::Function { - name: function.name().to_string(), // TODO: what if name is empty? - signature: match function.type_signature() { - TypeSignature::Variadic => None, - TypeSignature::Exact(params) => { - Some(params.into_iter().map(|param| param.type_name).collect()) - } - }, - } - } - pub fn name(&self) -> &str { - match self { - Self::Variable { name } | Self::Function { name, .. } => name, - } - } -} - -impl From<&str> for LexicalIdentifier { - fn from(value: &str) -> Self { - Self::Variable { name: value.into() } - } -} -impl From for LexicalIdentifier { - fn from(value: String) -> Self { - Self::Variable { name: value } - } -} - #[derive(Debug)] pub struct ScopeTree { current_scope_idx: usize, @@ -672,22 +557,18 @@ pub struct ScopeTree { } impl ScopeTree { - pub fn from_global_scope(global_scope_map: Vec) -> Self { + pub fn from_global_scope(global_scope_map: Vec<(String, StaticType)>) -> Self { Self { current_scope_idx: 0, global_scope: Scope { parent_idx: None, - //TODO: maybe the line below is more of temporary solution - identifiers: global_scope_map - .into_iter() - .map(|x| (x, StaticType::Function)) - .collect_vec(), + identifiers: global_scope_map, }, scopes: vec![Scope::new(None)], } } - fn get_type(&self, res: ResolvedVar) -> StaticType { + fn get_type(&self, res: ResolvedVar) -> &StaticType { match res { ResolvedVar::Captured { slot, depth } => { let mut scope_idx = self.current_scope_idx; @@ -698,10 +579,10 @@ impl ScopeTree { .parent_idx .expect("parent_idx was None while traversing the scope tree"); } - self.scopes[scope_idx].identifiers[slot].1.clone() + &self.scopes[scope_idx].identifiers[slot].1 } // for now all globals are functions - ResolvedVar::Global { .. } => StaticType::Function, + ResolvedVar::Global { slot } => &self.global_scope.identifiers[slot].1, } } @@ -720,56 +601,45 @@ impl ScopeTree { self.current_scope_idx = next; } - fn get_or_create_local_binding( - &mut self, - ident: LexicalIdentifier, - typ: StaticType, - ) -> ResolvedVar { - ResolvedVar::Captured { - slot: self.scopes[self.current_scope_idx] - .get_slot(&ident) - .unwrap_or_else(|| self.scopes[self.current_scope_idx].allocate(ident, typ)), - depth: 0, - } - } - fn get_binding_any(&mut self, ident: &str) -> Option { let mut depth = 0; let mut scope_ptr = self.current_scope_idx; loop { - if let Some(slot) = self.scopes[scope_ptr].get_slot_by_name(ident) { + if let Some(slot) = self.scopes[scope_ptr].find_slot_by_name(ident) { return Some(ResolvedVar::Captured { slot, depth }); } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { depth += 1; scope_ptr = parent_idx; } else { return Some(ResolvedVar::Global { - slot: self.global_scope.get_slot_by_name(ident)?, + slot: self.global_scope.find_slot_by_name(ident)?, }); } } } - fn get_binding(&mut self, lexical_ident: &LexicalIdentifier) -> Option { + fn get_function_binding(&mut self, ident: &str, sig: &[StaticType]) -> Option { + // println!("get function binding {ident} {sig:?}"); + let mut depth = 0; let mut scope_ptr = self.current_scope_idx; loop { - if let Some(slot) = self.scopes[scope_ptr].get_slot(lexical_ident) { + if let Some(slot) = self.scopes[scope_ptr].find_function(ident, sig) { return Some(ResolvedVar::Captured { slot, depth }); } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { depth += 1; scope_ptr = parent_idx; } else { return Some(ResolvedVar::Global { - slot: self.global_scope.get_slot(lexical_ident)?, + slot: self.global_scope.find_function(ident, sig)?, }); } } } - fn create_local_binding(&mut self, ident: LexicalIdentifier, typ: StaticType) -> ResolvedVar { + fn create_local_binding(&mut self, ident: String, typ: StaticType) -> ResolvedVar { ResolvedVar::Captured { slot: self.scopes[self.current_scope_idx].allocate(ident, typ), depth: 0, @@ -780,7 +650,7 @@ impl ScopeTree { #[derive(Debug)] struct Scope { parent_idx: Option, - identifiers: Vec<(LexicalIdentifier, StaticType)>, + identifiers: Vec<(String, StaticType)>, } impl Scope { @@ -791,19 +661,37 @@ impl Scope { } } - pub fn get_slot_by_name(&self, find_ident: &str) -> Option { + pub fn find_slot_by_name(&self, find_ident: &str) -> Option { self.identifiers .iter() - .rposition(|(ident, _)| ident.name() == find_ident) + .rposition(|(ident, _)| ident == find_ident) } - fn get_slot(&mut self, find_ident: &LexicalIdentifier) -> Option { - self.identifiers - .iter() - .rposition(|(ident, _type)| find_ident.is_subtype_match(ident)) + fn find_function(&mut self, find_ident: &str, find_typ: &[StaticType]) -> Option { + self.identifiers.iter().rposition(|(ident, typ)| { + if ident != find_ident { + return false; + } + + let StaticType::Function { parameters, .. } = typ else { + return false; + }; + + let Some(param_types) = parameters else { + // If this branch happens then the function we're matching against is variadic meaning it's always a match + return true; + }; + + ident == find_ident + && param_types.len() == find_typ.len() + && find_typ + .iter() + .zip(param_types.iter()) + .all(|(typ1, typ2)| typ1.is_compatible_with(typ2)) + }) } - fn allocate(&mut self, name: LexicalIdentifier, typ: StaticType) -> usize { + fn allocate(&mut self, name: String, typ: StaticType) -> usize { self.identifiers.push((name, typ)); // Slot is just the length of the list minus one self.identifiers.len() - 1 @@ -818,14 +706,14 @@ pub struct AnalysisError { } impl AnalysisError { - fn lvalue_required_to_be_single_identifier(span: Span) -> AnalysisError { + fn lvalue_required_to_be_single_identifier(span: Span) -> Self { Self { text: "This lvalue is required to be a single identifier".to_string(), span, } } - fn function_not_found(ident: &str, types: &[StaticType], span: Span) -> AnalysisError { + fn function_not_found(ident: &str, types: &[StaticType], span: Span) -> Self { Self { text: format!( "No function named '{ident}' found that matches the arguments {}", diff --git a/ndc_lib/src/interpreter/value.rs b/ndc_lib/src/interpreter/value.rs index 85433372..48dc51d9 100644 --- a/ndc_lib/src/interpreter/value.rs +++ b/ndc_lib/src/interpreter/value.rs @@ -135,7 +135,14 @@ impl Value { Self::Sequence(Sequence::Tuple(t)) => { StaticType::Tuple(t.iter().map(Self::static_type).collect()) } - Self::Function(_) => StaticType::Function, + // TODO: temporary until we get rid of OverloadedFunction + Self::Function(fun) => fun + .borrow() + .implementations() + .next() + .expect("TODO: implement function") + .1 + .static_type(), Self::Sequence(Sequence::Map(_, _)) => StaticType::Map, Self::Sequence(Sequence::Iterator(_)) => StaticType::Iterator, Self::Sequence(Sequence::MaxHeap(_)) => StaticType::MaxHeap, diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index 117f0809..722f584c 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -372,7 +372,12 @@ fn into_param_type(ty: &syn::Type) -> TokenStream { quote! { crate::interpreter::function::StaticType::Sequence } } _ if path.is_ident("Callable") => { - quote! { crate::interpreter::function::StaticType::Function } + quote! { + crate::interpreter::function::StaticType::Function { + parameters: None, + return_type: Box::new(crate::interpreter::function::StaticType::Any) + } + } } _ => panic!("Don't know how to convert Path into StaticType\n\n{path:?}"), }, @@ -405,7 +410,13 @@ fn create_temp_variable( if path_ends_with(ty, "Callable") { let temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::Function }, + param_type: quote! { + // TODO: how are we going to figure out the exact type of function here + crate::interpreter::function::StaticType::Function { + parameters: None, + return_type: Box::new(crate::interpreter::function::StaticType::Any) + } + }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { From 6a3bb0b200a38a77279a4b53044bb25df1bc2694 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 21:48:35 +0100 Subject: [PATCH 20/35] Get some function calling working still have work to do on op assign, which also needs to support dynamic binding now :(((( --- ndc_lib/src/ast/expression.rs | 9 +- ndc_lib/src/ast/mod.rs | 4 +- ndc_lib/src/ast/parser.rs | 16 +- ndc_lib/src/interpreter/evaluate/mod.rs | 43 +++- ndc_lib/src/interpreter/function.rs | 5 + ndc_lib/src/interpreter/semantic/analyser.rs | 201 +++++++++---------- 6 files changed, 157 insertions(+), 121 deletions(-) diff --git a/ndc_lib/src/ast/expression.rs b/ndc_lib/src/ast/expression.rs index 2a6869d7..decdeb18 100644 --- a/ndc_lib/src/ast/expression.rs +++ b/ndc_lib/src/ast/expression.rs @@ -6,6 +6,13 @@ use crate::lexer::Span; use num::BigInt; use num::complex::Complex64; +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum Binding { + None, + Resolved(ResolvedVar), + Dynamic(Vec), // figure it out at runtime +} + #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum ResolvedVar { Captured { depth: usize, slot: usize }, @@ -29,7 +36,7 @@ pub enum Expression { ComplexLiteral(Complex64), Identifier { name: String, - resolved: Option, + resolved: Binding, }, Statement(Box), Logical { diff --git a/ndc_lib/src/ast/mod.rs b/ndc_lib/src/ast/mod.rs index 85a7b176..d8dc7c71 100644 --- a/ndc_lib/src/ast/mod.rs +++ b/ndc_lib/src/ast/mod.rs @@ -2,7 +2,9 @@ mod expression; mod operator; mod parser; -pub use expression::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; +pub use expression::{ + Binding, Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar, +}; pub use operator::{BinaryOperator, LogicalOperator, UnaryOperator}; pub use parser::Error; diff --git a/ndc_lib/src/ast/parser.rs b/ndc_lib/src/ast/parser.rs index a52d6ae7..fb35bdc0 100644 --- a/ndc_lib/src/ast/parser.rs +++ b/ndc_lib/src/ast/parser.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use miette::Diagnostic; use crate::ast::Expression; -use crate::ast::expression::{ExpressionLocation, ForBody, ForIteration, Lvalue}; +use crate::ast::expression::{Binding, ExpressionLocation, ForBody, ForIteration, Lvalue}; use crate::ast::operator::{BinaryOperator, LogicalOperator, UnaryOperator}; use crate::lexer::{Span, Token, TokenLocation}; @@ -201,7 +201,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: operator_token_loc.token.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(operator_token_loc.span), ), @@ -214,7 +214,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: not_token.token.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(not_token.span), ), @@ -246,7 +246,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: operator.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(operator_span), ), @@ -483,7 +483,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: operator_token_loc.token.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(operator_span), ), @@ -610,7 +610,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: operator_token_loc.token.to_string(), - resolved: None, + resolved: Binding::None, } .to_location(token_span), ), @@ -689,7 +689,7 @@ impl Parser { function: Box::new( Expression::Identifier { name: identifier, - resolved: None, + resolved: Binding::None, } .to_location(identifier_span), ), @@ -961,7 +961,7 @@ impl Parser { Token::String(value) => Expression::StringLiteral(value), Token::Identifier(identifier) => Expression::Identifier { name: identifier, - resolved: None, + resolved: Binding::None, }, _ => { // TODO: this error might not be the best way to describe what's happening here diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index 825d8812..62ab12aa 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -1,4 +1,6 @@ -use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, LogicalOperator, Lvalue}; +use crate::ast::{ + Binding, Expression, ExpressionLocation, ForBody, ForIteration, LogicalOperator, Lvalue, +}; use crate::hash_map::HashMap; use crate::interpreter::environment::Environment; use crate::interpreter::function::{ @@ -12,7 +14,6 @@ use crate::interpreter::value::Value; use crate::lexer::Span; use index::{Offset, evaluate_as_index, get_at_index, set_at_index}; use itertools::Itertools; -use log::error; use std::cell::RefCell; use std::fmt; use std::rc::Rc; @@ -39,9 +40,12 @@ pub(crate) fn evaluate_expression( if name == "None" { return Ok(Value::none()); } - environment - .borrow() - .get(resolved.expect("identifier was not resolved before execution")) + + match resolved { + Binding::None => panic!("binding not resolved at runtime"), + Binding::Resolved(resolved) => environment.borrow().get(*resolved), + Binding::Dynamic(_) => panic!("attempted to evaluate dynamic binding"), + } } Expression::VariableDeclaration { l_value, value } => { let value = evaluate_expression(value, environment)?; @@ -318,7 +322,34 @@ pub(crate) fn evaluate_expression( // environment, or it must be some expression that evaluates to a function. // In case the expression is an identifier we get ALL the values that match the identifier // ordered by the distance is the scope-hierarchy. - let function_as_value = evaluate_expression(function, environment)?; + let function_as_value = if let ExpressionLocation { + expression: + Expression::Identifier { + resolved: Binding::Dynamic(dynamic_binding), + .. + }, + .. + } = &**function + { + dynamic_binding + .iter() + .find_map(|binding| { + let value = environment.borrow().get(*binding); + let Value::Function(..) = value else { + panic!("dynamic binding resolved to non-function type at runtime"); + }; + + Some(value) + }) + .ok_or_else(|| { + FunctionCarrier::EvaluationError(EvaluationError::new( + "dynamic binding failed to produce a useful function".to_string(), + span, + )) + })? + } else { + evaluate_expression(function, environment)? + }; return if let Value::Function(function) = function_as_value { match call_function(&function, &mut evaluated_args, environment, span) { diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 54922808..a2cdada1 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -547,6 +547,11 @@ impl StaticType { } } + // BRUH + pub fn is_incompatible_with(&self, other: &Self) -> bool { + !self.is_compatible_with(other) && !other.is_compatible_with(self) + } + #[allow(clippy::match_same_arms)] pub fn is_compatible_with(&self, other: &Self) -> bool { match (self, other) { diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index ef0f6865..cf4e8cfb 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -1,4 +1,6 @@ -use crate::ast::{Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar}; +use crate::ast::{ + Binding, Expression, ExpressionLocation, ForBody, ForIteration, Lvalue, ResolvedVar, +}; use crate::interpreter::function::StaticType; use crate::lexer::Span; use itertools::Itertools; @@ -39,7 +41,7 @@ impl Analyser { AnalysisError::identifier_not_previously_declared(ident, *span) })?; - *resolved = Some(binding); + *resolved = Binding::Resolved(binding); Ok(self.scope_tree.get_type(binding).clone()) } @@ -90,8 +92,10 @@ impl Analyser { Ok(StaticType::unit()) } else { // For now, we require that the normal operation is present and the special assignment operation is optional - Err(AnalysisError::identifier_not_previously_declared( - operation, *span, + Err(AnalysisError::function_not_found( + operation, + &op_assign_arg_types, + *span, )) } } @@ -275,12 +279,34 @@ impl Analyser { let binding = self .scope_tree .get_function_binding(name, argument_types) - // TODO: instead of throwing an error we can emit a dynamic binding + .map(|binding| Binding::Resolved(binding)) + .or_else(|| { + let loose_bindings = self + .scope_tree + .get_function_bindings_loose(name, argument_types); + + if loose_bindings.is_empty() { + return None; + } + + Some(Binding::Dynamic(loose_bindings)) + }) .ok_or_else(|| AnalysisError::function_not_found(name, argument_types, span))?; - *resolved = Some(binding); + let out_type = match &binding { + Binding::None => unreachable!("should return error before this happens"), + Binding::Resolved(res) => self.scope_tree.get_type(*res).clone(), - Ok(self.scope_tree.get_type(binding).clone()) + // TODO: are we just going to lie about the type or is this just how truthful we can be + Binding::Dynamic(_) => StaticType::Function { + parameters: None, + return_type: Box::new(StaticType::Any), + }, + }; + + *resolved = binding; + + Ok(out_type) } fn resolve_for_iterations( &mut self, @@ -371,7 +397,7 @@ impl Analyser { identifier, resolved, } => { - let Some(target) = self.scope_tree.get_binding_any(&identifier) else { + let Some(target) = self.scope_tree.get_binding_any(identifier) else { return Err(AnalysisError::identifier_not_previously_declared( identifier, span, )); @@ -414,9 +440,9 @@ impl Analyser { // TODO: big challenge how do we figure out the function parameter types? // it seems like this is something we need an HM like system for!? - *resolved = Some( + *resolved = Binding::Resolved( self.scope_tree - .create_local_binding(name.to_string(), StaticType::Any), + .create_local_binding((*name).to_string(), StaticType::Any), ); } } @@ -448,95 +474,8 @@ impl Analyser { Ok(()) } - - // fn resolve_type( - // &mut self, - // ExpressionLocation { expression, .. }: &ExpressionLocation, - // ) -> StaticType { - // match expression { - // Expression::BoolLiteral(_) | Expression::Logical { .. } => StaticType::Bool, - // Expression::StringLiteral(_) => StaticType::String, - // Expression::Int64Literal(_) | Expression::BigIntLiteral(_) => StaticType::Int, - // Expression::Float64Literal(_) => StaticType::Float, - // Expression::ComplexLiteral(_) => StaticType::Complex, - // Expression::Identifier { resolved, name } => { - // println!( - // "resolving name: {name}, {resolved:?}\n\n{}\n\n{:?}", - // self.scope_tree.current_scope_idx, self.scope_tree.scopes - // ); - // self - // .scope_tree - // .get_type(resolved.unwrap_or_else(|| { - // panic!( - // "previously mentioned identifier {name} was not resolved during type resolution" - // ) - // })) - // } - // Expression::Statement(_) - // | Expression::While { .. } - // | Expression::Break - // | Expression::Assignment { .. } => StaticType::unit(), - // Expression::Grouping(expr) | Expression::Return { value: expr } => { - // self.resolve_type(expr) - // } - // Expression::VariableDeclaration { .. } => { - // debug_assert!( - // false, - // "trying to get type of variable declaration, does this make sense?" - // ); - // StaticType::unit() // specifically unit tuple - // } - // Expression::OpAssignment { .. } => { - // debug_assert!( - // false, - // "trying to get type of op assignment, does this make sense?" - // ); - // StaticType::unit() // specifically unit tuple - // } - // Expression::FunctionDeclaration { .. } => StaticType::Function, - // Expression::Block { statements } => statements - // .iter() - // .last() - // .map_or(StaticType::unit(), |last| self.resolve_type(last)), - // Expression::If { - // on_true, on_false, .. - // } => { - // let on_false_type = on_false - // .as_ref() - // .map_or(StaticType::unit(), |expr| self.resolve_type(expr)); - // - // assert_eq!( - // self.resolve_type(on_true), - // on_false_type, - // "if branches have different types" - // ); - // on_false_type - // } - // Expression::For { body, .. } => , - // Expression::Call { - // function: _, - // arguments: _, - // } => { - // // TODO: Okay this is actually hard - // StaticType::Any - // } - // Expression::Index { .. } => { - // // TODO: this is also hard since we don't have generics - // StaticType::Any - // } - // Expression::Tuple { .. } => { - // // TODO: this is hard - // StaticType::Any - // } - // Expression::List { .. } => StaticType::List, - // Expression::Map { .. } => StaticType::Map, - // Expression::Continue => StaticType::Any, // Maybe we need a Never type? - // Expression::RangeInclusive { .. } | Expression::RangeExclusive { .. } => { - // StaticType::Iterator - // } - // } - // } } + fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { let ExpressionLocation { expression: Expression::Tuple { values }, @@ -619,9 +558,32 @@ impl ScopeTree { } } - fn get_function_binding(&mut self, ident: &str, sig: &[StaticType]) -> Option { - // println!("get function binding {ident} {sig:?}"); + fn get_function_bindings_loose(&mut self, ident: &str, sig: &[StaticType]) -> Vec { + let mut depth = 0; + let mut scope_ptr = self.current_scope_idx; + loop { + let candidates = self.scopes[scope_ptr].find_function_candidates(ident, sig); + if !candidates.is_empty() { + return candidates + .into_iter() + .map(|slot| ResolvedVar::Captured { slot, depth }) + .collect(); + } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { + depth += 1; + scope_ptr = parent_idx; + } else { + return self + .global_scope + .find_function_candidates(ident, sig) + .into_iter() + .map(|slot| ResolvedVar::Global { slot }) + .collect(); + } + } + } + + fn get_function_binding(&mut self, ident: &str, sig: &[StaticType]) -> Option { let mut depth = 0; let mut scope_ptr = self.current_scope_idx; @@ -667,12 +629,42 @@ impl Scope { .rposition(|(ident, _)| ident == find_ident) } - fn find_function(&mut self, find_ident: &str, find_typ: &[StaticType]) -> Option { + fn find_function_candidates(&self, find_ident: &str, find_types: &[StaticType]) -> Vec { + self.identifiers.iter() + .enumerate() + .rev() + .filter_map(|(slot, (ident, typ))| { + if ident != find_ident { + return None; + } + + // If the thing is not a function we're not interested + let StaticType::Function { parameters, .. } = typ else { + return None; + }; + + let Some(param_types) = parameters else { + // If this branch happens then the function we're matching against is variadic meaning it's always a match + debug_assert!(false, "we should never be calling find_function_candidates if there were variadic matches"); + // TODO: Change to unreachable? + return Some(slot); + }; + + let is_good = param_types.len() == find_types.len() + && param_types.iter().zip(find_types.iter()).all(|(typ_1, typ_2)| !typ_1.is_incompatible_with(typ_2)); + + is_good.then_some(slot) + }) + .collect() + } + fn find_function(&self, find_ident: &str, find_types: &[StaticType]) -> Option { self.identifiers.iter().rposition(|(ident, typ)| { + // If the name doesn't match we're not interested if ident != find_ident { return false; } + // If the thing is not a function we're not interested let StaticType::Function { parameters, .. } = typ else { return false; }; @@ -682,9 +674,8 @@ impl Scope { return true; }; - ident == find_ident - && param_types.len() == find_typ.len() - && find_typ + param_types.len() == find_types.len() + && find_types .iter() .zip(param_types.iter()) .all(|(typ1, typ2)| typ1.is_compatible_with(typ2)) @@ -716,7 +707,7 @@ impl AnalysisError { fn function_not_found(ident: &str, types: &[StaticType], span: Span) -> Self { Self { text: format!( - "No function named '{ident}' found that matches the arguments {}", + "No function called '{ident}' found that matches the arguments {}", types.iter().join(", ") ), span, From bce081474a42c37d46c774af3953e0b88233f3ce Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sat, 6 Dec 2025 21:52:38 +0100 Subject: [PATCH 21/35] stupid function type matching --- ndc_lib/src/interpreter/function.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index a2cdada1..bc09b0d3 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -572,6 +572,7 @@ impl StaticType { | Self::Map, Self::Sequence, ) => true, + (Self::Function { .. }, Self::Function { .. }) => true, // TODO: just saying all functions are compatible is lazy!!! _ => false, } } From 0bd9438e42d887b967e57252f168f30274de8c86 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sun, 7 Dec 2025 07:31:33 +0100 Subject: [PATCH 22/35] Messy dynamic binding for OpAssign --- ndc_lib/src/ast/expression.rs | 4 +- ndc_lib/src/ast/parser.rs | 4 +- ndc_lib/src/interpreter/evaluate/mod.rs | 133 ++++++++++++------- ndc_lib/src/interpreter/function.rs | 20 ++- ndc_lib/src/interpreter/semantic/analyser.rs | 114 +++++++--------- 5 files changed, 154 insertions(+), 121 deletions(-) diff --git a/ndc_lib/src/ast/expression.rs b/ndc_lib/src/ast/expression.rs index decdeb18..6490812a 100644 --- a/ndc_lib/src/ast/expression.rs +++ b/ndc_lib/src/ast/expression.rs @@ -57,8 +57,8 @@ pub enum Expression { l_value: Lvalue, r_value: Box, operation: String, - resolved_assign_operation: Option, - resolved_operation: Option, + resolved_assign_operation: Binding, + resolved_operation: Binding, }, FunctionDeclaration { name: Option, diff --git a/ndc_lib/src/ast/parser.rs b/ndc_lib/src/ast/parser.rs index fb35bdc0..97a0494d 100644 --- a/ndc_lib/src/ast/parser.rs +++ b/ndc_lib/src/ast/parser.rs @@ -385,8 +385,8 @@ impl Parser { .expect("guaranteed to produce an lvalue"), r_value: Box::new(expression), operation: operation_identifier, - resolved_assign_operation: None, - resolved_operation: None, + resolved_assign_operation: Binding::None, + resolved_operation: Binding::None, }; Ok(op_assign.to_location(start.merge(end))) diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index 62ab12aa..b098952d 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -1,5 +1,6 @@ use crate::ast::{ Binding, Expression, ExpressionLocation, ForBody, ForIteration, LogicalOperator, Lvalue, + ResolvedVar, }; use crate::hash_map::HashMap; use crate::interpreter::environment::Environment; @@ -85,14 +86,6 @@ pub(crate) fn evaluate_expression( resolved_operation, .. } => { - let mut operations_to_try = [ - resolved_assign_operation.map(|op| (op, true)), - resolved_operation.map(|op| (op, false)), - ] - .into_iter() - .flatten() - .peekable(); - match l_value { Lvalue::Identifier { identifier, @@ -106,11 +99,24 @@ pub(crate) fn evaluate_expression( return Err(EvaluationError::undefined_variable(identifier, span).into()); }; + let types = [lhs.static_type(), rhs.static_type()]; let mut arguments = [lhs, rhs]; - while let Some((resolved_op, modified_in_place)) = operations_to_try.next() { - let operation = environment.borrow().get(resolved_op); + let mut operations_to_try = [ + ( + resolve_dynamic_binding(resolved_assign_operation, &types, environment), + true, + ), + ( + resolve_dynamic_binding(resolved_operation, &types, environment), + false, + ), + ] + .into_iter() + .filter_map(|(value, in_place)| value.map(|value| (value, in_place))) + .peekable(); + while let Some((operation, modified_in_place)) = operations_to_try.next() { let Value::Function(func) = operation else { unreachable!( "the resolver pass should have guaranteed that the operation points to a function" @@ -162,11 +168,22 @@ pub(crate) fn evaluate_expression( let right_value = evaluate_expression(r_value, environment)?; - while let Some((resolved_operation, modified_in_place)) = - operations_to_try.next() - { - let operation_val = environment.borrow().get(resolved_operation); + let types = [value_at_index.static_type(), right_value.static_type()]; + let mut operations_to_try = [ + ( + resolve_dynamic_binding(resolved_assign_operation, &types, environment), + true, + ), + ( + resolve_dynamic_binding(resolved_operation, &types, environment), + false, + ), + ] + .into_iter() + .filter_map(|(value, in_place)| value.map(|value| (value, in_place))) + .peekable(); + while let Some((operation_val, modified_in_place)) = operations_to_try.next() { let Value::Function(func) = operation_val else { unreachable!( "the resolver pass should have guaranteed that the operation points to a function" @@ -313,43 +330,15 @@ pub(crate) fn evaluate_expression( arguments, } => { let mut evaluated_args = Vec::new(); + let mut arg_types = Vec::new(); for argument in arguments { - evaluated_args.push(evaluate_expression(argument, environment)?); + let arg = evaluate_expression(argument, environment)?; + arg_types.push(arg.static_type()); + evaluated_args.push(arg); } - // The Expression in `function` must either be an identifier in which case it will be looked up in the - // environment, or it must be some expression that evaluates to a function. - // In case the expression is an identifier we get ALL the values that match the identifier - // ordered by the distance is the scope-hierarchy. - let function_as_value = if let ExpressionLocation { - expression: - Expression::Identifier { - resolved: Binding::Dynamic(dynamic_binding), - .. - }, - .. - } = &**function - { - dynamic_binding - .iter() - .find_map(|binding| { - let value = environment.borrow().get(*binding); - let Value::Function(..) = value else { - panic!("dynamic binding resolved to non-function type at runtime"); - }; - - Some(value) - }) - .ok_or_else(|| { - FunctionCarrier::EvaluationError(EvaluationError::new( - "dynamic binding failed to produce a useful function".to_string(), - span, - )) - })? - } else { - evaluate_expression(function, environment)? - }; + let function_as_value = evaluate_as_function(function, &arg_types, environment)?; return if let Value::Function(function) = function_as_value { match call_function(&function, &mut evaluated_args, environment, span) { @@ -1013,3 +1002,53 @@ fn execute_for_iterations( Ok(Value::unit()) } + +fn evaluate_as_function( + function_expression: &ExpressionLocation, + arg_types: &[StaticType], + environment: &Rc>, +) -> EvaluationResult { + let ExpressionLocation { expression, .. } = function_expression; + + if let Expression::Identifier { resolved, .. } = expression { + resolve_dynamic_binding(resolved, &arg_types, environment).ok_or_else(|| { + FunctionCarrier::EvaluationError(EvaluationError::new( + "dynamic binding failed to produce a useful function".to_string(), + function_expression.span, + )) + }) + } else { + evaluate_expression(function_expression, environment) + } +} + +fn resolve_dynamic_binding( + binding: &Binding, + arg_types: &[StaticType], + environment: &Rc>, +) -> Option { + match binding { + Binding::None => None, + Binding::Resolved(var) => Some(environment.borrow().get(*var)), + Binding::Dynamic(dynamic_binding) => dynamic_binding + .iter() // TODO: should we consider the binding order? + .find_map(|binding| { + let value = environment.borrow().get(*binding); + + let Value::Function(fun) = &value else { + panic!("dynamic binding resolved to non-function type at runtime"); + }; + + let (_signature, fun) = fun + .borrow() + .implementations() + .next() + .expect("must have one implementation"); + + // Find the first function that matches + fun.static_type() + .is_fn_and_matches(&arg_types) + .then_some(value) + }), + } +} diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index bc09b0d3..b43bc9bd 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -277,7 +277,7 @@ 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`. - fn calc_type_score(&self, types: &[StaticType]) -> Option { + pub fn calc_type_score(&self, types: &[StaticType]) -> Option { match self { Self::Variadic => Some(0), Self::Exact(signature) => { @@ -576,6 +576,24 @@ impl StaticType { _ => false, } } + + 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(param_types) = parameters else { + // If this branch happens then the function we're matching against is variadic meaning it's always a match + return true; + }; + + param_types.len() == types.len() + && types + .iter() + .zip(param_types.iter()) + .all(|(typ1, typ2)| typ1.is_compatible_with(typ2)) + } } impl fmt::Display for StaticType { diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index cf4e8cfb..496e5e9c 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -4,7 +4,6 @@ use crate::ast::{ use crate::interpreter::function::StaticType; use crate::lexer::Span; use itertools::Itertools; -use std::iter::repeat; #[derive(Debug)] pub struct Analyser { @@ -74,30 +73,20 @@ impl Analyser { } => { let left_type = self.resolve_lvalue_as_ident(l_value, *span)?; let right_type = self.analyse(r_value)?; - let op_assign_arg_types = vec![left_type, right_type]; + let arg_types = vec![left_type, right_type]; - if let Some(binding) = self + *resolved_assign_operation = self .scope_tree - .get_function_binding(&format!("{operation}="), &op_assign_arg_types) - { - *resolved_assign_operation = Some(binding); - } - - if let Some(binding) = self - .scope_tree - .get_function_binding(&operation, &op_assign_arg_types) - { - *resolved_operation = Some(binding); + .resolve_function2(&format!("{operation}="), &arg_types); + *resolved_operation = self.scope_tree.resolve_function2(operation, &arg_types); - Ok(StaticType::unit()) - } else { - // For now, we require that the normal operation is present and the special assignment operation is optional - Err(AnalysisError::function_not_found( - operation, - &op_assign_arg_types, - *span, - )) + if let Binding::None = resolved_operation { + return Err(AnalysisError::function_not_found( + operation, &arg_types, *span, + )); } + + Ok(StaticType::unit()) } Expression::FunctionDeclaration { name, @@ -107,9 +96,9 @@ impl Analyser { .. } => { // TODO: figuring out the type signature of function declarations is the rest of the owl - let param_types: Vec = repeat(StaticType::Any) - .take(extract_argument_arity(parameters)) - .collect(); + let param_types: Vec = + std::iter::repeat_n(StaticType::Any, extract_argument_arity(parameters)) + .collect(); self.scope_tree.new_scope(); self.resolve_parameters_declarative(parameters); @@ -123,7 +112,8 @@ impl Analyser { }; if let Some(name) = name { - // TODO: is this correct, for now we just always create a new binding, we could also produce an error if we are generating a conflicting binding + // TODO: is this correct, for now we just always create a new binding, we could + // also produce an error if we are generating a conflicting binding *resolved_name = Some( self.scope_tree .create_local_binding(name.clone(), function_type.clone()), @@ -149,19 +139,19 @@ impl Analyser { } => { self.analyse(condition)?; let true_type = self.analyse(on_true)?; - let false_typ = if let Some(on_false) = on_false { + let false_type = if let Some(on_false) = on_false { Some(self.analyse(on_false)?) } else { None }; - if false_typ.is_none() { + if false_type.is_none() { if true_type != StaticType::unit() { // TODO: Emit warning for not using a semicolon in this if } Ok(StaticType::unit()) - } else if let Some(false_type) = false_typ + } else if let Some(false_type) = false_type && false_type == true_type { Ok(true_type) @@ -276,25 +266,16 @@ impl Analyser { return self.analyse(ident); }; - let binding = self - .scope_tree - .get_function_binding(name, argument_types) - .map(|binding| Binding::Resolved(binding)) - .or_else(|| { - let loose_bindings = self - .scope_tree - .get_function_bindings_loose(name, argument_types); - - if loose_bindings.is_empty() { - return None; - } - - Some(Binding::Dynamic(loose_bindings)) - }) - .ok_or_else(|| AnalysisError::function_not_found(name, argument_types, span))?; + let binding = self.scope_tree.resolve_function2(name, argument_types); let out_type = match &binding { - Binding::None => unreachable!("should return error before this happens"), + Binding::None => { + return Err(AnalysisError::function_not_found( + name, + argument_types, + span, + )); + } Binding::Resolved(res) => self.scope_tree.get_type(*res).clone(), // TODO: are we just going to lie about the type or is this just how truthful we can be @@ -558,7 +539,7 @@ impl ScopeTree { } } - fn get_function_bindings_loose(&mut self, ident: &str, sig: &[StaticType]) -> Vec { + fn resolve_function_dynamic(&mut self, ident: &str, sig: &[StaticType]) -> Vec { let mut depth = 0; let mut scope_ptr = self.current_scope_idx; @@ -583,7 +564,21 @@ impl ScopeTree { } } - fn get_function_binding(&mut self, ident: &str, sig: &[StaticType]) -> Option { + fn resolve_function2(&mut self, ident: &str, sig: &[StaticType]) -> Binding { + self.resolve_function(ident, sig) + .map(Binding::Resolved) + .or_else(|| { + let loose_bindings = self.resolve_function_dynamic(ident, sig); + + if loose_bindings.is_empty() { + return None; + } + + Some(Binding::Dynamic(loose_bindings)) + }) + .unwrap_or(Binding::None) + } + fn resolve_function(&mut self, ident: &str, sig: &[StaticType]) -> Option { let mut depth = 0; let mut scope_ptr = self.current_scope_idx; @@ -658,28 +653,9 @@ impl Scope { .collect() } fn find_function(&self, find_ident: &str, find_types: &[StaticType]) -> Option { - self.identifiers.iter().rposition(|(ident, typ)| { - // If the name doesn't match we're not interested - if ident != find_ident { - return false; - } - - // If the thing is not a function we're not interested - let StaticType::Function { parameters, .. } = typ else { - return false; - }; - - let Some(param_types) = parameters else { - // If this branch happens then the function we're matching against is variadic meaning it's always a match - return true; - }; - - param_types.len() == find_types.len() - && find_types - .iter() - .zip(param_types.iter()) - .all(|(typ1, typ2)| typ1.is_compatible_with(typ2)) - }) + self.identifiers + .iter() + .rposition(|(ident, typ)| ident == find_ident && typ.is_fn_and_matches(find_types)) } fn allocate(&mut self, name: String, typ: StaticType) -> usize { From bf93068a246c33347bb10b3f8aabc314d8cdd01e Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sun, 7 Dec 2025 07:50:47 +0100 Subject: [PATCH 23/35] It can run puzzles but it's very slow --- ndc_lib/src/interpreter/function.rs | 24 ++++++++++++++++ ndc_lib/src/interpreter/semantic/analyser.rs | 29 +++++++++++++++----- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index b43bc9bd..3f4f57cb 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -594,6 +594,30 @@ impl StaticType { .zip(param_types.iter()) .all(|(typ1, typ2)| typ1.is_compatible_with(typ2)) } + + pub fn unpack(&self) -> Option + '_>> { + match self { + // TODO: this type implementation for list is WRONG! + Self::List | Self::Any => Some(Box::new(std::iter::repeat(&Self::Any))), + Self::Tuple(types) => Some(Box::new(types.iter())), + + Self::Bool + | Self::Function { .. } + | Self::Option + | Self::Number + | Self::Float + | Self::Int + | Self::Rational + | Self::Complex + | Self::Sequence + | Self::String + | Self::Map + | Self::Iterator + | Self::MinHeap + | Self::MaxHeap + | Self::Deque => None, + } + } } impl fmt::Display for StaticType { diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 496e5e9c..2490a0ec 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -56,7 +56,7 @@ impl Analyser { Expression::Grouping(expr) => self.analyse(expr), Expression::VariableDeclaration { l_value, value } => { let typ = self.analyse(value)?; - self.resolve_lvalue_declarative(l_value, typ)?; + self.resolve_lvalue_declarative(l_value, typ, *span)?; Ok(StaticType::unit()) // TODO: never type here? } Expression::Assignment { l_value, r_value } => { @@ -170,7 +170,7 @@ impl Analyser { Ok(StaticType::unit()) } Expression::For { iterations, body } => { - self.resolve_for_iterations(iterations, body)?; + self.resolve_for_iterations(iterations, body, *span)?; match &**body { // for now this is good enough? @@ -293,6 +293,7 @@ impl Analyser { &mut self, iterations: &mut [ForIteration], body: &mut ForBody, + span: Span, ) -> Result<(), AnalysisError> { let Some((iteration, tail)) = iterations.split_first_mut() else { unreachable!("because this function is never called with an empty slice"); @@ -305,8 +306,8 @@ impl Analyser { self.scope_tree.new_scope(); - // TODO: inferring this as any is a massive cop-out - self.resolve_lvalue_declarative(l_value, StaticType::Any)?; + // TODO: when we give type parameters to all instances of sequence we can correctly infer StaticType::Any in this postition + self.resolve_lvalue_declarative(l_value, StaticType::Any, span)?; do_destroy = true; } ForIteration::Guard(expr) => { @@ -315,7 +316,7 @@ impl Analyser { } if !tail.is_empty() { - self.resolve_for_iterations(tail, body)? + self.resolve_for_iterations(tail, body, span)? } else { match body { ForBody::Block(block) => { @@ -431,6 +432,7 @@ impl Analyser { &mut self, lvalue: &mut Lvalue, typ: StaticType, + span: Span, ) -> Result<(), AnalysisError> { match lvalue { Lvalue::Identifier { @@ -447,8 +449,15 @@ impl Analyser { self.analyse(value)?; } Lvalue::Sequence(seq) => { - for sub_lvalue in seq { - self.resolve_lvalue_declarative(sub_lvalue, typ.clone())? + let sub_types = typ + .unpack() + .ok_or_else(|| AnalysisError::unable_to_unpack_type(&typ, span))?; + for (sub_lvalue, sub_lvalue_type) in seq.iter_mut().zip(sub_types) { + self.resolve_lvalue_declarative( + sub_lvalue, + sub_lvalue_type.clone(), + /* todo: figure out how to narrow this span */ span, + )? } } } @@ -673,6 +682,12 @@ pub struct AnalysisError { } impl AnalysisError { + fn unable_to_unpack_type(typ: &StaticType, span: Span) -> Self { + Self { + text: format!("Invalid unpacking of {typ}"), + span, + } + } fn lvalue_required_to_be_single_identifier(span: Span) -> Self { Self { text: "This lvalue is required to be a single identifier".to_string(), From 9da43c7af2dec94718481cbc78d4d271e46bdb93 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sun, 7 Dec 2025 08:06:51 +0100 Subject: [PATCH 24/35] Remove overloaded function --- ndc_lib/src/interpreter/environment.rs | 14 -- ndc_lib/src/interpreter/evaluate/mod.rs | 25 ++- ndc_lib/src/interpreter/function.rs | 208 ++++++------------------ ndc_lib/src/interpreter/value.rs | 25 +-- ndc_lib/src/stdlib/value.rs | 26 +-- 5 files changed, 83 insertions(+), 215 deletions(-) diff --git a/ndc_lib/src/interpreter/environment.rs b/ndc_lib/src/interpreter/environment.rs index a98fe20a..12ede514 100644 --- a/ndc_lib/src/interpreter/environment.rs +++ b/ndc_lib/src/interpreter/environment.rs @@ -84,20 +84,6 @@ impl Environment { pub fn set(&mut self, var: ResolvedVar, value: Value) { match var { ResolvedVar::Captured { depth: 0, slot } => { - // This whole mess (special handling for functions) can be removed once we check - // types during the resolver pass - if let Value::Function(value_to_insert) = &value { - if let Some(existing_value) = self.values.get(slot) { - if let Value::Function(func) = existing_value { - func.borrow_mut().merge(&mut value_to_insert.borrow_mut()) - } - } else { - self.values.push(value); - } - - return; - } - if self.values.len() > slot { self.values[slot] = value } else { diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index b098952d..e3d692b3 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -4,9 +4,7 @@ use crate::ast::{ }; use crate::hash_map::HashMap; use crate::interpreter::environment::Environment; -use crate::interpreter::function::{ - Function, FunctionBody, FunctionCarrier, OverloadedFunction, StaticType, -}; +use crate::interpreter::function::{Function, FunctionBody, FunctionCarrier, StaticType}; use crate::interpreter::int::Int; use crate::interpreter::iterator::mut_value_to_iterator; use crate::interpreter::num::Number; @@ -349,7 +347,7 @@ pub(crate) fn evaluate_expression( return Err(FunctionCarrier::EvaluationError(EvaluationError::new( format!( "no function called '{}' found matches the arguments: ({argument_string})", - function.borrow().name().unwrap_or("unnamed function") + function.borrow().name() ), span, ))); @@ -389,12 +387,13 @@ pub(crate) fn evaluate_expression( if let Some(resolved_name) = *resolved_name { environment.borrow_mut().set( resolved_name, + // TODO: put name in declaration? Value::function(Function::from_body(user_function)), ); Value::unit() } else { - Value::function(user_function) + Value::function(Function::from_body(user_function)) } } @@ -888,7 +887,7 @@ where } fn call_function( - function: &Rc>, + function: &Rc>, evaluated_args: &mut [Value], environment: &Rc>, span: Span, @@ -1039,16 +1038,12 @@ fn resolve_dynamic_binding( panic!("dynamic binding resolved to non-function type at runtime"); }; - let (_signature, fun) = fun - .borrow() - .implementations() - .next() - .expect("must have one implementation"); - // Find the first function that matches - fun.static_type() - .is_fn_and_matches(&arg_types) - .then_some(value) + if fun.borrow().static_type().is_fn_and_matches(&arg_types) { + Some(value) + } else { + None + } }), } } diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 3f4f57cb..eaa80740 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -18,7 +18,7 @@ use std::rc::Rc; /// Callable is a wrapper around a `OverloadedFunction` pointer and the environment to make it /// easy to have an executable function as a method signature in the standard library pub struct Callable<'a> { - pub function: Rc>, + pub function: Rc>, pub environment: &'a Rc>, } @@ -98,6 +98,58 @@ impl Function { return_type: Box::new(self.body.return_type().clone()), } } + + pub(crate) fn call( + &self, + args: &mut [Value], + env: &Rc>, + ) -> EvaluationResult { + self.body.call(args, env) + } + + fn call_vectorized( + &self, + args: &mut [Value], + env: &Rc>, + ) -> EvaluationResult { + let [left, right] = args else { + // Vectorized application only works in cases where there are two tuple arguments + return Err(FunctionCarrier::FunctionNotFound); + }; + + if !left.supports_vectorization_with(right) { + return Err(FunctionCarrier::FunctionNotFound); + } + + let (left, right) = match (left, right) { + // Both are tuples + (Value::Sequence(Sequence::Tuple(left)), Value::Sequence(Sequence::Tuple(right))) => { + (left, right.as_slice()) + } + // Left is a number and right is a tuple + (left @ Value::Number(_), Value::Sequence(Sequence::Tuple(right))) => ( + &mut Rc::new(vec![left.clone(); right.len()]), + right.as_slice(), + ), + // Left is a tuple and right is a number + (Value::Sequence(Sequence::Tuple(left)), right @ Value::Number(_)) => { + (left, std::slice::from_ref(right)) + } + _ => { + return Err(FunctionCarrier::FunctionNotFound); + } + }; + + let left_mut: &mut Vec = Rc::make_mut(left); + + // Zip the mutable vector with the immutable right side and perform the operations on all elements + // TODO: maybe one day figure out how to get rid of all these clones + for (l, r) in left_mut.iter_mut().zip(right.iter().cycle()) { + *l = self.call(&mut [l.clone(), r.clone()], env)?; + } + + Ok(Value::Sequence(Sequence::Tuple(left.clone()))) + } } #[derive(Clone)] @@ -309,160 +361,6 @@ impl TypeSignature { } } } - -#[derive(Clone, Debug)] -pub struct OverloadedFunction { - implementations: HashMap, -} - -impl OverloadedFunction { - pub fn from_multiple(functions: Vec) -> Self { - Self { - implementations: functions - .into_iter() - .map(|f| (f.type_signature(), f)) - .collect(), - } - } - - pub fn arity(&self) -> Option { - let arity = self - .implementations - .iter() - .next() - .map(|(sig, _)| sig.arity()) - .expect("OverloadedFunction cannot be empty"); - - debug_assert!( - self.implementations - .iter() - .all(|(sig, _)| sig.arity() == arity), - "failed asserting that arity of all functions in OverloadedFunction are equal: {}", - self.implementations - .values() - .map(|fun| fun.name()) - .join(", ") - ); - arity - } - - pub fn name(&self) -> Option<&str> { - if let Some((_, ff)) = (&self.implementations).into_iter().next() { - return ff.name.as_deref(); - } - - None - } - - pub fn add(&mut self, function: Function) { - self.implementations - .insert(function.type_signature(), function); - } - - pub fn implementations(&self) -> impl Iterator { - self.implementations.clone().into_iter() - } - - /// Ads values from the other overloaded function by draining it - pub fn merge(&mut self, other: &mut Self) { - for (key, value) in other.implementations.drain() { - debug_assert!( - !self.implementations.contains_key(&key), - "conflict while merging OverloadedFunction implementations" - ); - self.implementations.insert(key, value); - } - } - - pub fn call(&self, args: &mut [Value], env: &Rc>) -> EvaluationResult { - let types: Vec = args.iter().map(Value::static_type).collect(); - - let mut best_function_match = None; - let mut best_distance = u32::MAX; - - for (signature, function) in &self.implementations { - let Some(cur) = signature.calc_type_score(&types) else { - continue; - }; - if cur < best_distance { - best_distance = cur; - best_function_match = Some(function); - } - } - - best_function_match - .ok_or(FunctionCarrier::FunctionNotFound) - .and_then(|function| function.body().call(args, env)) - // For now if we can't find a specific function we just try to vectorize as a fallback - .or_else(|err| { - if let FunctionCarrier::FunctionNotFound = err { - self.call_vectorized(args, env) - } else { - Err(err) - } - }) - } - - fn call_vectorized( - &self, - args: &mut [Value], - env: &Rc>, - ) -> EvaluationResult { - let [left, right] = args else { - // Vectorized application only works in cases where there are two tuple arguments - return Err(FunctionCarrier::FunctionNotFound); - }; - - if !left.supports_vectorization_with(right) { - return Err(FunctionCarrier::FunctionNotFound); - } - - let (left, right) = match (left, right) { - // Both are tuples - (Value::Sequence(Sequence::Tuple(left)), Value::Sequence(Sequence::Tuple(right))) => { - (left, right.as_slice()) - } - // Left is a number and right is a tuple - (left @ Value::Number(_), Value::Sequence(Sequence::Tuple(right))) => ( - &mut Rc::new(vec![left.clone(); right.len()]), - right.as_slice(), - ), - // Left is a tuple and right is a number - (Value::Sequence(Sequence::Tuple(left)), right @ Value::Number(_)) => { - (left, std::slice::from_ref(right)) - } - _ => { - return Err(FunctionCarrier::FunctionNotFound); - } - }; - - let left_mut: &mut Vec = Rc::make_mut(left); - - // Zip the mutable vector with the immutable right side and perform the operations on all elements - // TODO: maybe one day figure out how to get rid of all these clones - for (l, r) in left_mut.iter_mut().zip(right.iter().cycle()) { - *l = self.call(&mut [l.clone(), r.clone()], env)?; - } - - Ok(Value::Sequence(Sequence::Tuple(left.clone()))) - } -} - -impl From for OverloadedFunction { - fn from(value: FunctionBody) -> Self { - Function::from_body(value).into() - } -} - -impl From for OverloadedFunction { - fn from(value: Function) -> Self { - let type_signature = value.type_signature(); - Self { - implementations: HashMap::from([(type_signature, value)]), - } - } -} - #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Parameter { pub name: String, diff --git a/ndc_lib/src/interpreter/value.rs b/ndc_lib/src/interpreter/value.rs index 48dc51d9..b753ff5f 100644 --- a/ndc_lib/src/interpreter/value.rs +++ b/ndc_lib/src/interpreter/value.rs @@ -10,7 +10,7 @@ use num::BigInt; use crate::compare::FallibleOrd; use crate::hash_map::DefaultHasher; -use crate::interpreter::function::{OverloadedFunction, StaticType}; +use crate::interpreter::function::{Function, StaticType}; use crate::interpreter::int::Int; use crate::interpreter::num::{Number, NumberToFloatError, NumberToUsizeError}; use crate::interpreter::sequence::Sequence; @@ -25,10 +25,13 @@ pub enum Value { Number(Number), Bool(bool), Sequence(Sequence), - Function(Rc>), + Function(Rc>), } impl Value { + pub(crate) fn function(function: Function) -> Self { + Self::Function(Rc::new(RefCell::new(function))) + } pub(crate) fn string>(string: S) -> Self { Self::Sequence(Sequence::String(Rc::new(RefCell::new(string.into())))) } @@ -61,10 +64,6 @@ impl Value { Self::Option(Some(Box::new(value))) } - pub(crate) fn function>(source: T) -> Self { - Self::Function(Rc::new(RefCell::new(source.into()))) - } - pub(crate) fn number>(source: T) -> Self { Self::Number(source.into()) } @@ -136,13 +135,7 @@ impl Value { StaticType::Tuple(t.iter().map(Self::static_type).collect()) } // TODO: temporary until we get rid of OverloadedFunction - Self::Function(fun) => fun - .borrow() - .implementations() - .next() - .expect("TODO: implement function") - .1 - .static_type(), + Self::Function(fun) => fun.borrow().static_type(), Self::Sequence(Sequence::Map(_, _)) => StaticType::Map, Self::Sequence(Sequence::Iterator(_)) => StaticType::Iterator, Self::Sequence(Sequence::MaxHeap(_)) => StaticType::MaxHeap, @@ -413,12 +406,6 @@ impl From for Value { } } -impl From for Value { - fn from(value: OverloadedFunction) -> Self { - Self::Function(Rc::new(RefCell::new(value))) - } -} - impl From> for Value { fn from(value: RangeInclusive) -> Self { Self::from(ValueIterator::ValueRangeInclusive(ValueRangeInclusive( diff --git a/ndc_lib/src/stdlib/value.rs b/ndc_lib/src/stdlib/value.rs index 91e54011..cd62ce22 100644 --- a/ndc_lib/src/stdlib/value.rs +++ b/ndc_lib/src/stdlib/value.rs @@ -16,18 +16,19 @@ mod inner { pub fn docs(func: &Callable<'_>) -> anyhow::Result { let mut buf = String::new(); - for (sig, fun) in func.function.borrow().implementations() { - if fun.name().is_empty() { - write!(buf, "fn({sig})")?; - } else { - write!(buf, "fn {}({sig})", fun.name())?; - } + let sig = func.function.borrow().type_signature(); + let fun = func.function.borrow(); - if !fun.short_documentation().is_empty() { - writeln!(buf, " -> {}", fun.short_documentation())?; - } else { - writeln!(buf)?; - } + if fun.name().is_empty() { + write!(buf, "fn({sig})")?; + } else { + write!(buf, "fn {}({sig})", fun.name())?; + } + + if !fun.short_documentation().is_empty() { + writeln!(buf, " -> {}", fun.short_documentation())?; + } else { + writeln!(buf)?; } buf.pop(); // Remove last newline @@ -118,7 +119,8 @@ mod inner { Value::Sequence(Sequence::Deque(deque)) => Value::Sequence(Sequence::Deque(Rc::new( RefCell::new(deque.borrow().to_owned()), ))), - Value::Function(f) => Value::from(f.borrow().to_owned()), + // TODO: for function should deepcopy have some special behavior + Value::Function(f) => Value::Function(f.clone()), } } From f68d3cba628eccf769b20e6ab531281fbb7e7cc2 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sun, 7 Dec 2025 14:09:57 +0100 Subject: [PATCH 25/35] Some WIP before generic type parameters on EVERYTHING --- ndc_lib/src/interpreter/function.rs | 25 +++++++ ndc_lib/src/interpreter/mod.rs | 2 +- ndc_lib/src/interpreter/semantic/analyser.rs | 69 ++++++++++++++------ 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index eaa80740..44808547 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -475,6 +475,31 @@ impl StaticType { } } + pub fn element_type(&self) -> Option { + match self { + StaticType::Any + | StaticType::Bool + | StaticType::Complex + | StaticType::Float + | StaticType::Function { .. } + | StaticType::Int + | StaticType::MaxHeap + | StaticType::MinHeap + | StaticType::Number + | StaticType::Option + | StaticType::Rational + | StaticType::Sequence => None, + StaticType::List => todo!("return whatever list is generic over here"), + StaticType::String => Some(StaticType::String), + StaticType::Tuple(_) => todo!( + "we either have to disallow indexing in to tuples, or we have to figure out some way to determine the index" + ), + StaticType::Map => todo!("return value type of map"), + StaticType::Iterator => todo!("return value type of iterator"), + StaticType::Deque => todo!("return type of deque"), + } + } + 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 { diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index a3997a35..f73dd17d 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -72,7 +72,7 @@ impl Interpreter { } // dbg!(&expressions); - // dbg!(&self.analyser); + dbg!(&self.analyser); let final_value = self.interpret(expressions.into_iter())?; diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 2490a0ec..56d2f8ea 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -4,8 +4,8 @@ use crate::ast::{ use crate::interpreter::function::StaticType; use crate::lexer::Span; use itertools::Itertools; +use std::fmt::{Debug, Formatter}; -#[derive(Debug)] pub struct Analyser { scope_tree: ScopeTree, } @@ -71,7 +71,7 @@ impl Analyser { resolved_assign_operation, resolved_operation, } => { - let left_type = self.resolve_lvalue_as_ident(l_value, *span)?; + let left_type = self.resolve_single_lvalue(l_value, *span)?; let right_type = self.analyse(r_value)?; let arg_types = vec![left_type, right_type]; @@ -200,9 +200,11 @@ impl Analyser { } Expression::Index { index, value } => { self.analyse(index)?; - self.analyse(value)?; - // TODO: figure out the type here - Ok(StaticType::Any) + let container_type = self.analyse(value)?; + + container_type + .element_type() + .ok_or_else(|| AnalysisError::unable_to_index_into(container_type, *span)) } Expression::Tuple { values } => { let mut types = Vec::with_capacity(values.len()); @@ -349,28 +351,38 @@ impl Analyser { Ok(()) } - fn resolve_lvalue_as_ident( + fn resolve_single_lvalue( &mut self, lvalue: &mut Lvalue, span: Span, ) -> Result { - let Lvalue::Identifier { - identifier, - resolved, - } = lvalue - else { - return Err(AnalysisError::lvalue_required_to_be_single_identifier(span)); - }; + match lvalue { + Lvalue::Identifier { + identifier, + resolved, + } => { + let Some(target) = self.scope_tree.get_binding_any(identifier) else { + return Err(AnalysisError::identifier_not_previously_declared( + identifier, span, + )); + }; - let Some(target) = self.scope_tree.get_binding_any(&identifier) else { - return Err(AnalysisError::identifier_not_previously_declared( - identifier, span, - )); - }; + *resolved = Some(target); - *resolved = Some(target); + Ok(self.scope_tree.get_type(target).clone()) + } + Lvalue::Index { index, value } => { + self.analyse(index)?; + let type_of_index_target = self.analyse(value)?; - Ok(self.scope_tree.get_type(target).clone()) + type_of_index_target + .element_type() + .ok_or_else(|| AnalysisError::unable_to_index_into(type_of_index_target, span)) + } + Lvalue::Sequence(_) => { + Err(AnalysisError::lvalue_required_to_be_single_identifier(span)) + } + } } fn resolve_lvalue(&mut self, lvalue: &mut Lvalue, span: Span) -> Result<(), AnalysisError> { @@ -682,6 +694,12 @@ pub struct AnalysisError { } impl AnalysisError { + fn unable_to_index_into(typ: StaticType, span: Span) -> Self { + Self { + text: format!("Unable to index into {typ}"), + span, + } + } fn unable_to_unpack_type(typ: &StaticType, span: Span) -> Self { Self { text: format!("Invalid unpacking of {typ}"), @@ -712,3 +730,14 @@ impl AnalysisError { } } } + +impl Debug for Analyser { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f)?; + for (id, scope) in self.scope_tree.scopes.iter().enumerate() { + writeln!(f, "{id}: {scope:?}")?; + } + + Ok(()) + } +} From 552e7a4c71f54396426d7e6fc5101e05c221f6ef Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Sun, 7 Dec 2025 15:31:41 +0100 Subject: [PATCH 26/35] Crazy WIP, parameterize all sequence types --- ndc_lib/src/interpreter/function.rs | 107 +++++++----- ndc_lib/src/interpreter/semantic/analyser.rs | 128 ++++++++++---- ndc_lib/src/interpreter/sequence.rs | 18 +- ndc_lib/src/interpreter/value.rs | 64 ++++++- ndc_lib/src/stdlib/aoc.rs | 2 +- ndc_lib/src/stdlib/heap.rs | 4 +- ndc_lib/src/stdlib/sequence.rs | 2 +- ndc_lib/src/stdlib/value.rs | 2 +- ndc_macros/src/function.rs | 161 +++++++++++++----- .../998_not_desired/big_int_ranges.ndct | 6 + 10 files changed, 356 insertions(+), 138 deletions(-) create mode 100644 tests/programs/998_not_desired/big_int_ranges.ndct diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 44808547..36a297e7 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -384,7 +384,7 @@ pub enum StaticType { parameters: Option>, return_type: Box, }, - Option, + Option(Box), // Numbers Number, @@ -394,15 +394,18 @@ pub enum StaticType { Complex, // Sequences - Sequence, - List, + Sequence(Box), + List(Box), String, Tuple(Vec), - Map, - Iterator, - MinHeap, - MaxHeap, - Deque, + Map { + key: Box, + value: Box, + }, + Iterator(Box), + MinHeap(Box), + MaxHeap(Box), + Deque(Box), } impl StaticType { @@ -459,22 +462,37 @@ impl StaticType { Self::Number | Self::Int | Self::Rational | Self::Complex | Self::Float, Self::Number, ) => true, + (Self::String, seq @ Self::Sequence(_)) => seq + .element_type() + .map(|elem| elem == StaticType::String) + .unwrap_or(false), + // Todo: This currently forces an exact match which we can relax + (map @ Self::Map { .. }, Self::Sequence(tup)) => map + .get_iteration_type() + .map(|elem| elem.is_compatible_with(tup)) + .unwrap_or(false), ( - Self::String - | Self::List - | Self::Deque - | Self::MaxHeap - | Self::MinHeap - | Self::Tuple(_) - | Self::Iterator - | Self::Map, - Self::Sequence, - ) => true, + // | Self::Tuple(_) + // | Self::Map, + Self::List(a) + | Self::Deque(a) + | Self::MaxHeap(a) + | Self::MinHeap(a) + | Self::Iterator(a), + Self::Sequence(b), + ) => a.is_compatible_with(b), (Self::Function { .. }, Self::Function { .. }) => true, // TODO: just saying all functions are compatible is lazy!!! _ => false, } } + pub fn get_iteration_type(&self) -> Option { + if let StaticType::Map { key, value } = self { + return Some(StaticType::Tuple(vec![*key.clone(), *value.clone()])); + } + + self.element_type() + } pub fn element_type(&self) -> Option { match self { StaticType::Any @@ -483,20 +501,20 @@ impl StaticType { | StaticType::Float | StaticType::Function { .. } | StaticType::Int - | StaticType::MaxHeap - | StaticType::MinHeap | StaticType::Number - | StaticType::Option - | StaticType::Rational - | StaticType::Sequence => None, - StaticType::List => todo!("return whatever list is generic over here"), + | StaticType::Rational => None, + StaticType::List(elem) + | StaticType::Deque(elem) + | StaticType::Iterator(elem) + | StaticType::MaxHeap(elem) + | StaticType::MinHeap(elem) + | StaticType::Option(elem) + | StaticType::Map { value: elem, .. } + | StaticType::Sequence(elem) => Some(*elem.clone()), StaticType::String => Some(StaticType::String), StaticType::Tuple(_) => todo!( "we either have to disallow indexing in to tuples, or we have to figure out some way to determine the index" ), - StaticType::Map => todo!("return value type of map"), - StaticType::Iterator => todo!("return value type of iterator"), - StaticType::Deque => todo!("return type of deque"), } } @@ -520,25 +538,24 @@ impl StaticType { pub fn unpack(&self) -> Option + '_>> { match self { - // TODO: this type implementation for list is WRONG! - Self::List | Self::Any => Some(Box::new(std::iter::repeat(&Self::Any))), + Self::Any => todo!("unpacking any, why what when where how"), + 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::Bool | Self::Function { .. } - | Self::Option + | Self::Option(_) | Self::Number | Self::Float | Self::Int | Self::Rational | Self::Complex - | Self::Sequence | Self::String - | Self::Map - | Self::Iterator - | Self::MinHeap - | Self::MaxHeap - | Self::Deque => None, + | Self::Map { .. } => None, } } } @@ -559,22 +576,22 @@ impl fmt::Display for StaticType { .map(|p| p.iter().join(", ")) .unwrap_or(String::from("*")) ), - Self::Option => write!(f, "Option"), + 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 => write!(f, "Sequence"), - Self::List => write!(f, "List"), + 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 => write!(f, "Map"), - Self::Iterator => write!(f, "Iterator"), - Self::MinHeap => write!(f, "MinHeap"), - Self::MaxHeap => write!(f, "MaxHeap"), - Self::Deque => write!(f, "Deque"), + 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}>"), } } } diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 56d2f8ea..9859e976 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -33,8 +33,8 @@ impl Analyser { resolved, } => { if ident == "None" { - // THIS IS VERY UNHINGED - return Ok(StaticType::Option); + // TODO: we're going to need something like HM to infer the type of option here, maybe force type annotations? + return Ok(StaticType::Option(Box::new(StaticType::Any))); } let binding = self.scope_tree.get_binding_any(ident).ok_or_else(|| { AnalysisError::identifier_not_previously_declared(ident, *span) @@ -170,14 +170,7 @@ impl Analyser { Ok(StaticType::unit()) } Expression::For { iterations, body } => { - self.resolve_for_iterations(iterations, body, *span)?; - - match &**body { - // for now this is good enough? - ForBody::Block(_) => Ok(StaticType::unit()), - ForBody::List(_) => Ok(StaticType::List), - ForBody::Map { .. } => Ok(StaticType::Map), - } + Ok(self.resolve_for_iterations(iterations, body, *span)?) } Expression::Call { function, @@ -215,17 +208,42 @@ impl Analyser { Ok(StaticType::Tuple(types)) } Expression::List { values } => { - for v in values { - self.analyse(v)?; - } + let element_type = self.analyse_multiple_expression_with_same_type(values, span)?; - Ok(StaticType::List) + // TODO: for now if we encounter an empty list expression we say the list is generic over Any but this clearly is not a good solution + Ok(StaticType::List(Box::new( + element_type.unwrap_or(StaticType::Any), + ))) } Expression::Map { values, default } => { + let mut key_type = None; + let mut value_type = None; for (key, value) in values { - self.analyse(key)?; + if let Some(key_type) = &key_type { + let next_type = self.analyse(key)?; + if &next_type != key_type { + return Err(AnalysisError::incompatible_list_element( + key_type.clone(), + next_type, + *span, + )); + } + } else { + key_type = Some(self.analyse(key)?); + } if let Some(value) = value { - self.analyse(value)?; + if let Some(value_type) = &value_type { + let next_type = self.analyse(value)?; + if &next_type != value_type { + return Err(AnalysisError::incompatible_list_element( + value_type.clone(), + next_type, + *span, + )); + } + } else { + value_type = Some(self.analyse(value)?); + } } } @@ -233,7 +251,11 @@ impl Analyser { self.analyse(default)?; } - Ok(StaticType::Map) + // TODO: defaulting to Any here is surely going to bite us later + Ok(StaticType::Map { + key: Box::new(key_type.unwrap_or(StaticType::Any)), + value: Box::new(value_type.unwrap_or_else(StaticType::unit)), + }) } Expression::Return { value } => { self.analyse(value)?; @@ -248,7 +270,7 @@ impl Analyser { self.analyse(end)?; } - Ok(StaticType::Iterator) + Ok(StaticType::Iterator(Box::new(StaticType::Int))) } } } @@ -296,7 +318,7 @@ impl Analyser { iterations: &mut [ForIteration], body: &mut ForBody, span: Span, - ) -> Result<(), AnalysisError> { + ) -> Result { let Some((iteration, tail)) = iterations.split_first_mut() else { unreachable!("because this function is never called with an empty slice"); }; @@ -310,45 +332,51 @@ impl Analyser { // TODO: when we give type parameters to all instances of sequence we can correctly infer StaticType::Any in this postition self.resolve_lvalue_declarative(l_value, StaticType::Any, span)?; - do_destroy = true; + do_destroy = true; // TODO: why is this correct } ForIteration::Guard(expr) => { self.analyse(expr)?; } } - if !tail.is_empty() { + let out_type = if !tail.is_empty() { self.resolve_for_iterations(tail, body, span)? } else { match body { ForBody::Block(block) => { self.analyse(block)?; + StaticType::unit() } - ForBody::List(list) => { - self.analyse(list)?; - } + ForBody::List(list) => StaticType::List(Box::new(self.analyse(list)?)), ForBody::Map { key, value, default, } => { - self.analyse(key)?; - if let Some(value) = value { - self.analyse(value)?; - } + let key_type = self.analyse(key)?; + let value_type = if let Some(value) = value { + self.analyse(value)? + } else { + StaticType::unit() + }; if let Some(default) = default { self.analyse(default)?; } + + StaticType::Map { + key: Box::new(key_type), + value: Box::new(value_type), + } } } - } + }; if do_destroy { self.scope_tree.destroy_scope(); } - Ok(()) + Ok(out_type) } fn resolve_single_lvalue( @@ -476,6 +504,32 @@ impl Analyser { Ok(()) } + fn analyse_multiple_expression_with_same_type( + &mut self, + expressions: &mut Vec, + span: &mut Span, + ) -> Result, AnalysisError> { + let mut element_type = None; + + for expression in expressions { + if let Some(element_type) = &element_type { + let following_type = self.analyse(expression)?; + + if &following_type != element_type { + // We don't have to allow this right now, we could just widen the type until it's Any and call it a day + return Err(AnalysisError::incompatible_list_element( + element_type.clone(), + following_type, + *span, + )); + } + } else { + element_type = Some(self.analyse(expression)?); + } + } + + Ok(element_type) + } } fn extract_argument_arity(arguments: &ExpressionLocation) -> usize { @@ -693,7 +747,21 @@ pub struct AnalysisError { span: Span, } +impl AnalysisError {} + impl AnalysisError { + fn incompatible_list_element( + list_type: StaticType, + next_element_type: StaticType, + span: Span, + ) -> AnalysisError { + Self { + text: format!( + "Invalid list expression: not allowed to mix {list_type} with {next_element_type}" + ), + span, + } + } fn unable_to_index_into(typ: StaticType, span: Span) -> Self { Self { text: format!("Unable to index into {typ}"), diff --git a/ndc_lib/src/interpreter/sequence.rs b/ndc_lib/src/interpreter/sequence.rs index 7d165a80..08e1d648 100644 --- a/ndc_lib/src/interpreter/sequence.rs +++ b/ndc_lib/src/interpreter/sequence.rs @@ -89,17 +89,13 @@ impl Sequence { } } - pub fn value_type(&self) -> StaticType { - match self { - Self::String(_) => StaticType::String, - Self::List(_) => StaticType::List, - Self::Tuple(t) => StaticType::Tuple(t.iter().map(Value::static_type).collect()), - Self::Map(_, _) => StaticType::Map, - Self::Iterator(_) => StaticType::Iterator, - Self::MaxHeap(_) => StaticType::MaxHeap, - Self::MinHeap(_) => StaticType::MinHeap, - Self::Deque(_) => StaticType::Deque, - } + /// Reflexively determine the type this value at runtime. + /// + /// Note: If a collection type is generic over a single parameter we'll attempt to peek into the + /// list and determine the type of the parameter that way, but if the list is empty we just return Any + /// Note: This function is not very performant and should be used as little as possible. + pub fn static_type(&self) -> StaticType { + match self {} } #[must_use] diff --git a/ndc_lib/src/interpreter/value.rs b/ndc_lib/src/interpreter/value.rs index b753ff5f..48d14421 100644 --- a/ndc_lib/src/interpreter/value.rs +++ b/ndc_lib/src/interpreter/value.rs @@ -126,21 +126,67 @@ impl Value { #[must_use] pub fn static_type(&self) -> StaticType { match self { - Self::Option(_) => StaticType::Option, + Self::Option(c) => StaticType::Option(Box::new( + c.as_deref() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + )), Self::Number(number) => number.static_type(), Self::Bool(_) => StaticType::Bool, + Self::Sequence(Sequence::String(_)) => StaticType::String, - Self::Sequence(Sequence::List(_)) => StaticType::List, + // I predict that defaulting to Any is going to make us very sad one day + Self::Sequence(Sequence::List(l)) => StaticType::List(Box::new( + l.borrow() + .iter() + .next() + .map(|i| i.static_type()) + .unwrap_or(StaticType::Any), + )), Self::Sequence(Sequence::Tuple(t)) => { - StaticType::Tuple(t.iter().map(Self::static_type).collect()) + StaticType::Tuple(t.iter().map(Value::static_type).collect()) + } + Self::Sequence(Sequence::Map(map, _)) => StaticType::Map { + key: Box::new( + map.borrow() + .keys() + .next() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + ), + value: Box::new( + map.borrow() + .values() + .next() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + ), + }, + Self::Sequence(Sequence::Iterator(_)) => { + todo!("we cannot determine the type of an iterator without nuking the iterator") } - // TODO: temporary until we get rid of OverloadedFunction + Self::Sequence(Sequence::MaxHeap(heap)) => StaticType::MaxHeap(Box::new( + heap.borrow() + .iter() + .max() + .map(|elem| elem.0.static_type()) + .unwrap_or(StaticType::Any), + )), + Self::Sequence(Sequence::MinHeap(heap)) => StaticType::MaxHeap(Box::new( + heap.borrow() + .iter() + .min() + .map(|elem| elem.0.0.static_type()) + .unwrap_or(StaticType::Any), + )), + Self::Sequence(Sequence::Deque(d)) => StaticType::Deque(Box::new( + d.borrow() + .iter() + .next() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + )), Self::Function(fun) => fun.borrow().static_type(), - Self::Sequence(Sequence::Map(_, _)) => StaticType::Map, - Self::Sequence(Sequence::Iterator(_)) => StaticType::Iterator, - Self::Sequence(Sequence::MaxHeap(_)) => StaticType::MaxHeap, - Self::Sequence(Sequence::MinHeap(_)) => StaticType::MinHeap, - Self::Sequence(Sequence::Deque(_)) => StaticType::Deque, } } diff --git a/ndc_lib/src/stdlib/aoc.rs b/ndc_lib/src/stdlib/aoc.rs index c6577521..2d0430ad 100644 --- a/ndc_lib/src/stdlib/aoc.rs +++ b/ndc_lib/src/stdlib/aoc.rs @@ -10,7 +10,7 @@ mod inner { use crate::interpreter::value::Value; /// Counts the occurrences of each item in a sequence and returns a map with the frequencies. - #[function(return_type = DefaultMap<'_>)] + #[function(return_type = HashMap<_, _>)] pub fn frequencies(seq: &mut Sequence) -> Value { let mut out_map = HashMap::new(); diff --git a/ndc_lib/src/stdlib/heap.rs b/ndc_lib/src/stdlib/heap.rs index 5623f235..9c6a2d5e 100644 --- a/ndc_lib/src/stdlib/heap.rs +++ b/ndc_lib/src/stdlib/heap.rs @@ -8,12 +8,12 @@ mod inner { use std::cell::RefCell; use std::rc::Rc; - #[function(name = "MinHeap", return_type = MinHeap)] + #[function(name = "MinHeap", return_type = MinHeap<_>)] pub fn create_min_heap() -> Value { Value::Sequence(Sequence::MinHeap(Rc::new(RefCell::new(MinHeap::new())))) } - #[function(name = "MaxHeap", return_type = MaxHeap)] + #[function(name = "MaxHeap", return_type = MaxHeap<_>)] pub fn create_max_heap() -> Value { Value::Sequence(Sequence::MaxHeap(Rc::new(RefCell::new(MaxHeap::new())))) } diff --git a/ndc_lib/src/stdlib/sequence.rs b/ndc_lib/src/stdlib/sequence.rs index 5d6d7785..2ab4fff5 100644 --- a/ndc_lib/src/stdlib/sequence.rs +++ b/ndc_lib/src/stdlib/sequence.rs @@ -215,7 +215,7 @@ mod inner { Some(n) => Ok(n), None => Err(anyhow!( "cannot determine the length of {}", - seq.value_type() + seq.static_type() )), } } diff --git a/ndc_lib/src/stdlib/value.rs b/ndc_lib/src/stdlib/value.rs index cd62ce22..cd925147 100644 --- a/ndc_lib/src/stdlib/value.rs +++ b/ndc_lib/src/stdlib/value.rs @@ -63,7 +63,7 @@ mod inner { } /// Creates a new instance of `None` - #[function(return_type = Option)] + #[function(return_type = Option<_>)] pub fn none() -> Value { Value::Option(None) } diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index 722f584c..cc8a7726 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -140,17 +140,29 @@ fn map_type(ty: &syn::Type) -> TokenStream { ]) } } + syn::Type::Infer(x) => { + quote::quote! { crate::interpreter::function::StaticType::Any } + } _ => { panic!("unmapped type: {ty:?}"); } } } +#[allow(clippy::single_match_else)] fn map_type_path(p: &syn::TypePath) -> TokenStream { let segment = p.path.segments.last().unwrap(); + let make_map_fallback = || { + quote::quote! { + crate::interpreter::function::StaticType::Map { + key: Box::new(crate::interpreter::function::StaticType::Any), + value: Box::new(crate::interpreter::function::StaticType::Any) + } + } + }; + match segment.ident.to_string().as_str() { - // Primitive single identifiers "i32" | "i64" | "isize" | "u32" | "u64" | "usize" | "BigInt" => { quote::quote! { crate::interpreter::function::StaticType::Int } } @@ -163,55 +175,128 @@ fn map_type_path(p: &syn::TypePath) -> TokenStream { "String" | "str" => { quote::quote! { crate::interpreter::function::StaticType::String } } - - "Vec" => { - quote::quote! { crate::interpreter::function::StaticType::List } - } - "DefaultMap" | "HashMap" => { - quote::quote! { crate::interpreter::function::StaticType::Map } - } - "Number" => { - quote::quote! { crate::interpreter::function::StaticType::Number } - } - "VecDeque" => { - quote::quote! { crate::interpreter::function::StaticType::Deque } - } - "MinHeap" => { - quote::quote! { crate::interpreter::function::StaticType::MinHeap } - } - "MaxHeap" => { - quote::quote! { crate::interpreter::function::StaticType::MaxHeap } - } - "Iterator" => { - quote::quote! { crate::interpreter::function::StaticType::Iterator } - } - "Option" => { - // TODO: in the future add generic types - quote::quote! { crate::interpreter::function::StaticType::Option } - } - // Generic wrappers like anyhow::Result, std::result::Result + "Vec" | "List" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("Vec<> requires inner type"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::List(Box::new(#mapped)) } + } else { + panic!("Vec inner not a type"); + } + } + _ => { + quote::quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) } + } + }, + "VecDeque" | "Deque" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("VecDeque<> requires inner type"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::Deque(Box::new(#mapped)) } + } else { + panic!("VecDeque inner not a type"); + } + } + _ => quote::quote! { + crate::interpreter::function::StaticType::Deque(Box::new( + crate::interpreter::function::StaticType::Any + )) + }, + }, + "DefaultMap" | "HashMap" | "Map" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let mut iter = args.args.iter(); + let Some(key) = iter.next() else { + return make_map_fallback(); + }; + let Some(val) = iter.next() else { + return make_map_fallback(); + }; + let key_ty = match key { + syn::GenericArgument::Type(t) => t, + _ => panic!("Invalid map key"), + }; + let val_ty = match val { + syn::GenericArgument::Type(t) => t, + _ => panic!("Invalid map value"), + }; + let key_mapped = map_type(key_ty); + let val_mapped = map_type(val_ty); + quote::quote! { crate::interpreter::function::StaticType::Map { key: Box::new(#key_mapped), value: Box::new(#val_mapped) } } + } + _ => make_map_fallback(), + }, + "MinHeap" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("MinHeap requires inner"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::MinHeap(Box::new(#mapped)) } + } else { + panic!("MinHeap inner invalid"); + } + } + _ => quote::quote! { + crate::interpreter::function::StaticType::MinHeap(Box::new( + crate::interpreter::function::StaticType::Any + )) + }, + }, + "MaxHeap" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("MaxHeap requires inner"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::MaxHeap(Box::new(#mapped)) } + } else { + panic!("MaxHeap inner invalid"); + } + } + _ => panic!("MaxHeap without generics"), + }, + "Iterator" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("Iterator requires inner"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::Iterator(Box::new(#mapped)) } + } else { + panic!("Iterator inner invalid"); + } + } + _ => { + quote::quote! { crate::interpreter::function::StaticType::Iterator(Box::new(crate::interpreter::function::StaticType::Any)) } + } + }, + "Option" => match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => { + let inner = args.args.first().expect("Option requires inner type"); + if let syn::GenericArgument::Type(inner_ty) = inner { + let mapped = map_type(inner_ty); + quote::quote! { crate::interpreter::function::StaticType::Option(Box::new(#mapped)) } + } else { + panic!("Option inner invalid"); + } + } + _ => panic!("Option without generics"), + }, "Result" => match &segment.arguments { syn::PathArguments::AngleBracketed(args) => { - // Extract the first generic argument if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() { - // Recurse on T map_type(inner_ty) } else { panic!("Result without generic arguments"); } } - - _ => { - panic!("Result return type found without syn::PathArguments::AngleBracketed"); - } + _ => panic!("Result without angle bracketed args"), }, + "Number" => quote::quote! { crate::interpreter::function::StaticType::Number }, "Value" | "EvaluationResult" => { quote::quote! { crate::interpreter::function::StaticType::Any } } - // Fallback - unmatched => { - panic!("Cannot map type string \"{unmatched}\" to StaticType"); - } + unmatched => panic!("Cannot map type string '{unmatched}' to StaticType"), } } diff --git a/tests/programs/998_not_desired/big_int_ranges.ndct b/tests/programs/998_not_desired/big_int_ranges.ndct new file mode 100644 index 00000000..5a8925c0 --- /dev/null +++ b/tests/programs/998_not_desired/big_int_ranges.ndct @@ -0,0 +1,6 @@ +--PROGRAM-- +// Not sure what to think of this one, but right now you cannot construct ranges with ints outside of the i64 range +// it does make type analysis a tiny bit easier though +(2^1024)..((2^1024)+1) +--EXPECT-ERROR-- +Cannot convert Int into i64 \ No newline at end of file From 0f7ab71380d7e2cdf4ae03cbdae045ce6696bb96 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Mon, 8 Dec 2025 22:15:02 +0100 Subject: [PATCH 27/35] Vibe coded type system, what could go wrong --- Cargo.lock | 1 - ndc_lib/Cargo.toml | 1 - ndc_lib/src/interpreter/evaluate/mod.rs | 5 +- ndc_lib/src/interpreter/function.rs | 406 ++++++++++++++++--- ndc_lib/src/interpreter/semantic/analyser.rs | 35 +- ndc_lib/src/interpreter/sequence.rs | 63 ++- ndc_lib/src/interpreter/value.rs | 57 +-- ndc_lib/src/stdlib/sequence.rs | 2 +- ndc_macros/src/convert.rs | 10 +- ndc_macros/src/function.rs | 63 +-- 10 files changed, 454 insertions(+), 189 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d418acda..f8ae2787 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,7 +1181,6 @@ dependencies = [ "derive_more", "factorial", "itertools 0.14.0", - "log", "md5", "miette", "ndc_macros", diff --git a/ndc_lib/Cargo.toml b/ndc_lib/Cargo.toml index 779cfb00..28b01be9 100644 --- a/ndc_lib/Cargo.toml +++ b/ndc_lib/Cargo.toml @@ -27,7 +27,6 @@ thiserror.workspace = true # Crypto md5 = { version = "0.8.0", optional = true } sha1 = { version = "0.10.6", optional = true } -log = "0.4.29" [features] default = ["ahash", "crypto"] diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index e3d692b3..ea3e23aa 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -1,6 +1,5 @@ use crate::ast::{ Binding, Expression, ExpressionLocation, ForBody, ForIteration, LogicalOperator, Lvalue, - ResolvedVar, }; use crate::hash_map::HashMap; use crate::interpreter::environment::Environment; @@ -1010,7 +1009,7 @@ fn evaluate_as_function( let ExpressionLocation { expression, .. } = function_expression; if let Expression::Identifier { resolved, .. } = expression { - resolve_dynamic_binding(resolved, &arg_types, environment).ok_or_else(|| { + resolve_dynamic_binding(resolved, arg_types, environment).ok_or_else(|| { FunctionCarrier::EvaluationError(EvaluationError::new( "dynamic binding failed to produce a useful function".to_string(), function_expression.span, @@ -1039,7 +1038,7 @@ fn resolve_dynamic_binding( }; // Find the first function that matches - if fun.borrow().static_type().is_fn_and_matches(&arg_types) { + if fun.borrow().static_type().is_fn_and_matches(arg_types) { Some(value) } else { None diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 36a297e7..c460d99f 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -338,7 +338,7 @@ impl TypeSignature { for (a, b) in types.iter().zip(signature.iter()) { let dist = if a == &b.type_name { 0 - } else if a.is_compatible_with(&b.type_name) { + } else if a.is_subtype(&b.type_name) { 1 } else { return None; @@ -407,8 +407,306 @@ pub enum StaticType { 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** + #[allow(clippy::unnested_or_patterns)] + pub fn is_subtype(&self, other: &StaticType) -> 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; + } + + 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![]) @@ -450,72 +748,15 @@ impl StaticType { // BRUH pub fn is_incompatible_with(&self, other: &Self) -> bool { - !self.is_compatible_with(other) && !other.is_compatible_with(self) + !self.is_subtype(other) && !other.is_subtype(self) } - #[allow(clippy::match_same_arms)] - pub fn is_compatible_with(&self, other: &Self) -> bool { - match (self, other) { - (a, b) if a == b => true, - (_, Self::Any) => true, - ( - Self::Number | Self::Int | Self::Rational | Self::Complex | Self::Float, - Self::Number, - ) => true, - (Self::String, seq @ Self::Sequence(_)) => seq - .element_type() - .map(|elem| elem == StaticType::String) - .unwrap_or(false), - // Todo: This currently forces an exact match which we can relax - (map @ Self::Map { .. }, Self::Sequence(tup)) => map - .get_iteration_type() - .map(|elem| elem.is_compatible_with(tup)) - .unwrap_or(false), - ( - // | Self::Tuple(_) - // | Self::Map, - Self::List(a) - | Self::Deque(a) - | Self::MaxHeap(a) - | Self::MinHeap(a) - | Self::Iterator(a), - Self::Sequence(b), - ) => a.is_compatible_with(b), - (Self::Function { .. }, Self::Function { .. }) => true, // TODO: just saying all functions are compatible is lazy!!! - _ => false, + pub fn index_element_type(&self) -> Option { + if let Self::Map { value, .. } = self { + return Some(value.as_ref().clone()); } - } - pub fn get_iteration_type(&self) -> Option { - if let StaticType::Map { key, value } = self { - return Some(StaticType::Tuple(vec![*key.clone(), *value.clone()])); - } - - self.element_type() - } - pub fn element_type(&self) -> Option { - match self { - StaticType::Any - | StaticType::Bool - | StaticType::Complex - | StaticType::Float - | StaticType::Function { .. } - | StaticType::Int - | StaticType::Number - | StaticType::Rational => None, - StaticType::List(elem) - | StaticType::Deque(elem) - | StaticType::Iterator(elem) - | StaticType::MaxHeap(elem) - | StaticType::MinHeap(elem) - | StaticType::Option(elem) - | StaticType::Map { value: elem, .. } - | StaticType::Sequence(elem) => Some(*elem.clone()), - StaticType::String => Some(StaticType::String), - StaticType::Tuple(_) => todo!( - "we either have to disallow indexing in to tuples, or we have to figure out some way to determine the index" - ), - } + self.sequence_element_type() } pub fn is_fn_and_matches(&self, types: &[Self]) -> bool { @@ -533,12 +774,13 @@ impl StaticType { && types .iter() .zip(param_types.iter()) - .all(|(typ1, typ2)| typ1.is_compatible_with(typ2)) + .all(|(typ1, typ2)| typ1.is_subtype(typ2)) } pub fn unpack(&self) -> Option + '_>> { match self { - Self::Any => todo!("unpacking any, why what when where how"), + // 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) @@ -681,3 +923,35 @@ impl fmt::Display for TypeSignature { } } } + +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 9859e976..1994e78a 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -140,26 +140,20 @@ impl Analyser { self.analyse(condition)?; let true_type = self.analyse(on_true)?; let false_type = if let Some(on_false) = on_false { - Some(self.analyse(on_false)?) + self.analyse(on_false)? } else { - None + StaticType::unit() }; - if false_type.is_none() { - if true_type != StaticType::unit() { - // TODO: Emit warning for not using a semicolon in this if - } + if true_type != StaticType::unit() { + // TODO: Emit warning for not using a semicolon in this if + } - Ok(StaticType::unit()) - } else if let Some(false_type) = false_type - && false_type == true_type - { - Ok(true_type) - } else { - // TODO: maybe create a warning to show the user they're doing something cursed - // TODO: figure out the nearest common ancestor for true_type and false_type - Ok(StaticType::Any) + if true_type != false_type { + // TODO maybe show warning? } + + Ok(true_type.lub(&false_type)) } Expression::While { expression, @@ -196,8 +190,8 @@ impl Analyser { let container_type = self.analyse(value)?; container_type - .element_type() - .ok_or_else(|| AnalysisError::unable_to_index_into(container_type, *span)) + .index_element_type() + .ok_or_else(|| AnalysisError::unable_to_index_into(&container_type, *span)) } Expression::Tuple { values } => { let mut types = Vec::with_capacity(values.len()); @@ -404,8 +398,8 @@ impl Analyser { let type_of_index_target = self.analyse(value)?; type_of_index_target - .element_type() - .ok_or_else(|| AnalysisError::unable_to_index_into(type_of_index_target, span)) + .index_element_type() + .ok_or_else(|| AnalysisError::unable_to_index_into(&type_of_index_target, span)) } Lvalue::Sequence(_) => { Err(AnalysisError::lvalue_required_to_be_single_identifier(span)) @@ -492,6 +486,7 @@ impl Analyser { let sub_types = typ .unpack() .ok_or_else(|| AnalysisError::unable_to_unpack_type(&typ, span))?; + for (sub_lvalue, sub_lvalue_type) in seq.iter_mut().zip(sub_types) { self.resolve_lvalue_declarative( sub_lvalue, @@ -762,7 +757,7 @@ impl AnalysisError { span, } } - fn unable_to_index_into(typ: StaticType, span: Span) -> Self { + fn unable_to_index_into(typ: &StaticType, span: Span) -> Self { Self { text: format!("Unable to index into {typ}"), span, diff --git a/ndc_lib/src/interpreter/sequence.rs b/ndc_lib/src/interpreter/sequence.rs index 08e1d648..45d66440 100644 --- a/ndc_lib/src/interpreter/sequence.rs +++ b/ndc_lib/src/interpreter/sequence.rs @@ -43,6 +43,60 @@ impl Sequence { } } + pub fn static_type(&self) -> StaticType { + match self { + Self::String(_) => StaticType::String, + // I predict that defaulting to Any is going to make us very sad one day + Self::List(l) => StaticType::List(Box::new( + l.borrow() + .iter() + .next() + .map(|i| i.static_type()) + .unwrap_or(StaticType::Any), + )), + Self::Tuple(t) => StaticType::Tuple(t.iter().map(Value::static_type).collect()), + Self::Map(map, _) => StaticType::Map { + key: Box::new( + map.borrow() + .keys() + .next() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + ), + value: Box::new( + map.borrow() + .values() + .next() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + ), + }, + Self::Iterator(_) => { + todo!("we cannot determine the type of an iterator without nuking the iterator") + } + Self::MaxHeap(heap) => StaticType::MaxHeap(Box::new( + heap.borrow() + .iter() + .max() + .map(|elem| elem.0.static_type()) + .unwrap_or(StaticType::Any), + )), + Self::MinHeap(heap) => StaticType::MaxHeap(Box::new( + heap.borrow() + .iter() + .min() + .map(|elem| elem.0.0.static_type()) + .unwrap_or(StaticType::Any), + )), + Self::Deque(d) => StaticType::Deque(Box::new( + d.borrow() + .iter() + .next() + .map(Value::static_type) + .unwrap_or(StaticType::Any), + )), + } + } #[must_use] pub fn deepcopy(&self) -> Self { match self { @@ -89,15 +143,6 @@ impl Sequence { } } - /// Reflexively determine the type this value at runtime. - /// - /// Note: If a collection type is generic over a single parameter we'll attempt to peek into the - /// list and determine the type of the parameter that way, but if the list is empty we just return Any - /// Note: This function is not very performant and should be used as little as possible. - pub fn static_type(&self) -> StaticType { - match self {} - } - #[must_use] pub fn contains(&self, needle: &Value) -> bool { match self { diff --git a/ndc_lib/src/interpreter/value.rs b/ndc_lib/src/interpreter/value.rs index 48d14421..6440180a 100644 --- a/ndc_lib/src/interpreter/value.rs +++ b/ndc_lib/src/interpreter/value.rs @@ -127,65 +127,12 @@ impl Value { pub fn static_type(&self) -> StaticType { match self { Self::Option(c) => StaticType::Option(Box::new( - c.as_deref() - .map(Value::static_type) - .unwrap_or(StaticType::Any), + c.as_deref().map_or(StaticType::Any, Self::static_type), )), Self::Number(number) => number.static_type(), Self::Bool(_) => StaticType::Bool, - Self::Sequence(Sequence::String(_)) => StaticType::String, - // I predict that defaulting to Any is going to make us very sad one day - Self::Sequence(Sequence::List(l)) => StaticType::List(Box::new( - l.borrow() - .iter() - .next() - .map(|i| i.static_type()) - .unwrap_or(StaticType::Any), - )), - Self::Sequence(Sequence::Tuple(t)) => { - StaticType::Tuple(t.iter().map(Value::static_type).collect()) - } - Self::Sequence(Sequence::Map(map, _)) => StaticType::Map { - key: Box::new( - map.borrow() - .keys() - .next() - .map(Value::static_type) - .unwrap_or(StaticType::Any), - ), - value: Box::new( - map.borrow() - .values() - .next() - .map(Value::static_type) - .unwrap_or(StaticType::Any), - ), - }, - Self::Sequence(Sequence::Iterator(_)) => { - todo!("we cannot determine the type of an iterator without nuking the iterator") - } - Self::Sequence(Sequence::MaxHeap(heap)) => StaticType::MaxHeap(Box::new( - heap.borrow() - .iter() - .max() - .map(|elem| elem.0.static_type()) - .unwrap_or(StaticType::Any), - )), - Self::Sequence(Sequence::MinHeap(heap)) => StaticType::MaxHeap(Box::new( - heap.borrow() - .iter() - .min() - .map(|elem| elem.0.0.static_type()) - .unwrap_or(StaticType::Any), - )), - Self::Sequence(Sequence::Deque(d)) => StaticType::Deque(Box::new( - d.borrow() - .iter() - .next() - .map(Value::static_type) - .unwrap_or(StaticType::Any), - )), + Self::Sequence(s) => s.static_type(), Self::Function(fun) => fun.borrow().static_type(), } } diff --git a/ndc_lib/src/stdlib/sequence.rs b/ndc_lib/src/stdlib/sequence.rs index 2ab4fff5..2ce692a2 100644 --- a/ndc_lib/src/stdlib/sequence.rs +++ b/ndc_lib/src/stdlib/sequence.rs @@ -780,7 +780,7 @@ pub mod extra { .documentation("Combines multiple sequences (or iterables) into a single sequence of tuples, where the ith tuple contains the ith element from each input sequence.\n\nIf the input sequences are of different lengths, the resulting sequence is truncated to the length of the shortest input.".to_string()) .body(FunctionBody::generic( crate::interpreter::function::TypeSignature::Variadic, - StaticType::List, + StaticType::List(Box::new(StaticType::Tuple(vec![StaticType::Any, StaticType::Any]))), |args, _env| match args { [_] => { Err(anyhow!("zip must be called with 2 or more arguments").into()) diff --git a/ndc_macros/src/convert.rs b/ndc_macros/src/convert.rs index b8edb990..769f76e7 100644 --- a/ndc_macros/src/convert.rs +++ b/ndc_macros/src/convert.rs @@ -1,3 +1,4 @@ +use crate::function::temp_create_map_any; use crate::r#match::{is_ref_mut, is_string, path_ends_with}; use proc_macro2::TokenStream; use quote::quote; @@ -59,7 +60,7 @@ impl TypeConverter for InternalMap { } fn static_type(&self) -> TokenStream { - quote! { crate::interpreter::function::StaticType::Map } + temp_create_map_any() } fn convert( @@ -120,7 +121,12 @@ impl TypeConverter for InternalList { } fn static_type(&self) -> TokenStream { - quote! { crate::interpreter::function::StaticType::List } + // TODO: just hardcoding Any here is lazy + quote! { + crate::interpreter::function::StaticType::List(Box::new( + crate::interpreter::function::StaticType::Any + )) + } } fn convert( diff --git a/ndc_macros/src/function.rs b/ndc_macros/src/function.rs index cc8a7726..6a3da976 100644 --- a/ndc_macros/src/function.rs +++ b/ndc_macros/src/function.rs @@ -140,7 +140,7 @@ fn map_type(ty: &syn::Type) -> TokenStream { ]) } } - syn::Type::Infer(x) => { + syn::Type::Infer(_) => { quote::quote! { crate::interpreter::function::StaticType::Any } } _ => { @@ -153,15 +153,6 @@ fn map_type(ty: &syn::Type) -> TokenStream { fn map_type_path(p: &syn::TypePath) -> TokenStream { let segment = p.path.segments.last().unwrap(); - let make_map_fallback = || { - quote::quote! { - crate::interpreter::function::StaticType::Map { - key: Box::new(crate::interpreter::function::StaticType::Any), - value: Box::new(crate::interpreter::function::StaticType::Any) - } - } - }; - match segment.ident.to_string().as_str() { "i32" | "i64" | "isize" | "u32" | "u64" | "usize" | "BigInt" => { quote::quote! { crate::interpreter::function::StaticType::Int } @@ -209,10 +200,10 @@ fn map_type_path(p: &syn::TypePath) -> TokenStream { syn::PathArguments::AngleBracketed(args) => { let mut iter = args.args.iter(); let Some(key) = iter.next() else { - return make_map_fallback(); + return temp_create_map_any(); }; let Some(val) = iter.next() else { - return make_map_fallback(); + return temp_create_map_any(); }; let key_ty = match key { syn::GenericArgument::Type(t) => t, @@ -226,7 +217,7 @@ fn map_type_path(p: &syn::TypePath) -> TokenStream { let val_mapped = map_type(val_ty); quote::quote! { crate::interpreter::function::StaticType::Map { key: Box::new(#key_mapped), value: Box::new(#val_mapped) } } } - _ => make_map_fallback(), + _ => temp_create_map_any(), }, "MinHeap" => match &segment.arguments { syn::PathArguments::AngleBracketed(args) => { @@ -418,29 +409,27 @@ fn wrap_single( fn into_param_type(ty: &syn::Type) -> TokenStream { match ty { ty if path_ends_with(ty, "Vec") => { - quote! { crate::interpreter::function::StaticType::List } + quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) } } ty if path_ends_with(ty, "VecDeque") => { - quote! { crate::interpreter::function::StaticType::Deque } + quote! { crate::interpreter::function::StaticType::Deque(Box::new(crate::interpreter::function::StaticType::Any)) } } ty if path_ends_with(ty, "DefaultMap") || path_ends_with(ty, "DefaultMapMut") || path_ends_with(ty, "HashMap") => { - quote! { crate::interpreter::function::StaticType::Map } + temp_create_map_any() } ty if path_ends_with(ty, "MinHeap") => { - quote! { crate::interpreter::function::StaticType::MinHeap } + quote! { crate::interpreter::function::StaticType::MinHeap(Box::new(crate::interpreter::function::StaticType::Any)) } } ty if path_ends_with(ty, "MaxHeap") => { - quote! { crate::interpreter::function::StaticType::MaxHeap } + quote! { crate::interpreter::function::StaticType::MaxHeap(Box::new(crate::interpreter::function::StaticType::Any)) } } ty if path_ends_with(ty, "ListRepr") => { - quote! { crate::interpreter::function::StaticType::List } - } - ty if path_ends_with(ty, "MapRepr") => { - quote! { crate::interpreter::function::StaticType::Map } + quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) } } + ty if path_ends_with(ty, "MapRepr") => temp_create_map_any(), syn::Type::Reference(syn::TypeReference { elem, .. }) => into_param_type(elem), syn::Type::Path(syn::TypePath { path, .. }) => match path { _ if path.is_ident("i64") => quote! { crate::interpreter::function::StaticType::Int }, @@ -454,7 +443,7 @@ fn into_param_type(ty: &syn::Type) -> TokenStream { quote! { crate::interpreter::function::StaticType::Number } } _ if path.is_ident("Sequence") => { - quote! { crate::interpreter::function::StaticType::Sequence } + quote! { crate::interpreter::function::StaticType::Sequence(Box::new(crate::interpreter::function::StaticType::Any)) } } _ if path.is_ident("Callable") => { quote! { @@ -466,7 +455,9 @@ fn into_param_type(ty: &syn::Type) -> TokenStream { } _ => panic!("Don't know how to convert Path into StaticType\n\n{path:?}"), }, - syn::Type::ImplTrait(_) => quote! { crate::interpreter::function::StaticType::Iterator }, + syn::Type::ImplTrait(_) => { + quote! { crate::interpreter::function::StaticType::Iterator(Box::new(crate::interpreter::function::StaticType::Any)) } + } x => panic!("Don't know how to convert {x:?} into StaticType"), } } @@ -520,7 +511,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::Map }, + param_type: temp_create_map_any(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -536,7 +527,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::Map }, + param_type: temp_create_map_any(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -552,7 +543,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::Map }, + param_type: temp_create_map_any(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -568,7 +559,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::Map }, + param_type: temp_create_map_any(), param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -585,7 +576,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -747,7 +738,7 @@ fn create_temp_variable( let rc_temp_var = syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![Argument { - param_type: quote! { crate::interpreter::function::StaticType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -764,7 +755,7 @@ fn create_temp_variable( syn::Ident::new(&format!("temp_{argument_var_name}"), identifier.span()); return vec![ Argument { - param_type: quote! { crate::interpreter::function::StaticType::List }, + param_type: quote! { crate::interpreter::function::StaticType::List(Box::new(crate::interpreter::function::StaticType::Any)) }, param_name: quote! { #original_name }, argument: quote! { #argument_var_name }, initialize_code: quote! { @@ -879,3 +870,13 @@ fn create_temp_variable( panic!("Not sure how to handle receivers"); } + +// TODO: just adding Any as type here is lazy AF but CBA fixing generics +pub fn temp_create_map_any() -> TokenStream { + quote! { + crate::interpreter::function::StaticType::Map { + key: Box::new(crate::interpreter::function::StaticType::Any), + value: Box::new(crate::interpreter::function::StaticType::Any) + } + } +} From f29ed20da0f72ed49c99692d2b33b72ed7e0ad54 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Mon, 8 Dec 2025 22:21:44 +0100 Subject: [PATCH 28/35] Fixed return type inference by making it worse --- ndc_lib/src/interpreter/semantic/analyser.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 1994e78a..3fcfc8dc 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -103,7 +103,11 @@ impl Analyser { self.scope_tree.new_scope(); self.resolve_parameters_declarative(parameters); - let return_type = self.analyse(body)?; + // TODO: instead of just hardcoding the return type of every function to StaticType::Any + // we should somehow collect all the returns that were encountered while analysing + // the body and then figuring out the LUB. + self.analyse(body)?; + let return_type = StaticType::Any; self.scope_tree.destroy_scope(); let function_type = StaticType::Function { @@ -251,10 +255,9 @@ impl Analyser { value: Box::new(value_type.unwrap_or_else(StaticType::unit)), }) } - Expression::Return { value } => { - self.analyse(value)?; - Ok(StaticType::unit()) // TODO or never? - } + // Return evaluates to the type of the expression it returns, which makes type checking easier! + // Actually it doesn't seem to make it any easier + Expression::Return { value } => self.analyse(value), Expression::RangeInclusive { start, end } | Expression::RangeExclusive { start, end } => { if let Some(start) = start { From 6e3c788714b2d19c1b21ef9fbcb3ebaeb4ab7256 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Mon, 8 Dec 2025 22:26:09 +0100 Subject: [PATCH 29/35] Slightly improved error message --- ndc_lib/src/interpreter/semantic/analyser.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index 3fcfc8dc..fa5b484f 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -651,6 +651,7 @@ impl ScopeTree { }) .unwrap_or(Binding::None) } + fn resolve_function(&mut self, ident: &str, sig: &[StaticType]) -> Option { let mut depth = 0; let mut scope_ptr = self.current_scope_idx; @@ -782,7 +783,7 @@ impl AnalysisError { fn function_not_found(ident: &str, types: &[StaticType], span: Span) -> Self { Self { text: format!( - "No function called '{ident}' found that matches the arguments {}", + "No function called '{ident}' found that matches the arguments '{}'", types.iter().join(", ") ), span, From cdddca9c65cc66f02ad71a49a982be7ed99dcb08 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Mon, 8 Dec 2025 22:35:44 +0100 Subject: [PATCH 30/35] Fallback to normal ident if no function is found (RIP) and improved error message at runtime --- ndc_lib/src/interpreter/evaluate/mod.rs | 6 ++++-- ndc_lib/src/interpreter/semantic/analyser.rs | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index ea3e23aa..ea7bdb29 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -355,8 +355,10 @@ pub(crate) fn evaluate_expression( } } else { Err(FunctionCarrier::EvaluationError(EvaluationError::new( - "Failed to invoke expression as function possibly because it's not a function" - .to_string(), + format!( + "Unable to invoke {} as a function.", + function_as_value.static_type() + ), span, ))) }; diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index fa5b484f..c90a9121 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -182,9 +182,9 @@ impl Analyser { let StaticType::Function { return_type, .. } = self.resolve_function_with_argument_types(function, &type_sig, *span)? else { - unreachable!( - "resolve_function_with_argument_types should guarantee us a function type" - ); + // If we couldn't resolve the identifier to a function we have to just assume that + // whatever identifier we did find is a function at runtime and will return Any + return Ok(StaticType::Any); }; Ok(*return_type) @@ -649,6 +649,8 @@ impl ScopeTree { Some(Binding::Dynamic(loose_bindings)) }) + // If we can't find any function in scope that could match, we just default to an identifier. + .or_else(|| self.get_binding_any(ident).map(Binding::Resolved)) .unwrap_or(Binding::None) } From 6b471ecc57ed4e3d63bfee3f67606786c0e0287f Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 11 Dec 2025 09:19:24 +0100 Subject: [PATCH 31/35] Small pre changes --- ndc_lib/src/interpreter/function.rs | 12 +++++++----- ndc_lib/src/interpreter/mod.rs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index c460d99f..12c1f3d8 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -114,12 +114,13 @@ impl Function { ) -> EvaluationResult { let [left, right] = args else { // Vectorized application only works in cases where there are two tuple arguments - return Err(FunctionCarrier::FunctionNotFound); + panic!("incorrect argument count for vectorization should have been handled by caller"); }; - if !left.supports_vectorization_with(right) { - return Err(FunctionCarrier::FunctionNotFound); - } + // TODO: let caller handle checks? + // if !left.supports_vectorization_with(right) { + // return Err(FunctionCarrier::FunctionNotFound); + // } let (left, right) = match (left, right) { // Both are tuples @@ -136,7 +137,7 @@ impl Function { (left, std::slice::from_ref(right)) } _ => { - return Err(FunctionCarrier::FunctionNotFound); + panic!("caller should handle all checks before vectorizing") } }; @@ -924,6 +925,7 @@ impl fmt::Display for TypeSignature { } } +#[allow(unused_imports)] mod test { use super::*; diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index f73dd17d..a3997a35 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -72,7 +72,7 @@ impl Interpreter { } // dbg!(&expressions); - dbg!(&self.analyser); + // dbg!(&self.analyser); let final_value = self.interpret(expressions.into_iter())?; From 4365a05128b6e92fc3926176ca7e8b4a648a6b44 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 11 Dec 2025 11:11:30 +0100 Subject: [PATCH 32/35] Fixed some booges --- ndc_lib/src/interpreter/evaluate/mod.rs | 91 ++++++------------- ndc_lib/src/interpreter/function.rs | 31 +++++-- ndc_lib/src/interpreter/semantic/analyser.rs | 68 +++++--------- ndc_lib/src/interpreter/sequence.rs | 8 +- ndc_lib/src/interpreter/value.rs | 25 +++++ .../030_augmented_assign_to_pattern.ndct | 2 +- 6 files changed, 106 insertions(+), 119 deletions(-) diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index ea7bdb29..2cd29618 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -119,9 +119,9 @@ pub(crate) fn evaluate_expression( "the resolver pass should have guaranteed that the operation points to a function" ); }; - - let result = match call_function(&func, &mut arguments, environment, span) { - Err(FunctionCarrier::FunctionNotFound) + // (&func, &mut arguments, environment, span) + let result = match func.borrow().call_checked(&mut arguments, environment) { + Err(FunctionCarrier::FunctionTypeMismatch) if operations_to_try.peek().is_none() => { let argument_string = @@ -136,7 +136,10 @@ pub(crate) fn evaluate_expression( ), )); } - Err(FunctionCarrier::FunctionNotFound) => continue, + Err(FunctionCarrier::FunctionTypeMismatch) => continue, + Err(carrier @ FunctionCarrier::IntoEvaluationError(_)) => { + return Err(carrier.lift_if(span)); + } eval_result => eval_result?, }; @@ -187,30 +190,28 @@ pub(crate) fn evaluate_expression( ); }; - let result = match call_function( - &func, + let result = match func.borrow().call_checked( &mut [value_at_index.clone(), right_value.clone()], environment, - span, ) { - Err(FunctionCarrier::FunctionNotFound) + Err(FunctionCarrier::FunctionTypeMismatch) if operations_to_try.peek().is_none() => { - let argument_string = [value_at_index.clone(), right_value.clone()] - .iter() - .map(Value::static_type) - .join(", "); - return Err(FunctionCarrier::EvaluationError( EvaluationError::new( format!( - "no function called 'TODO FIGURE OUT NAME' found matches the arguments: ({argument_string})" + "no function called 'TODO FIGURE OUT NAME' found matches the arguments: ({}, {})", + value_at_index.static_type(), + right_value.static_type() ), span, ), )); } - Err(FunctionCarrier::FunctionNotFound) => continue, + Err(FunctionCarrier::FunctionTypeMismatch) => continue, + Err(carrier @ FunctionCarrier::IntoEvaluationError(_)) => { + return Err(carrier.lift_if(span)); + } eval_result => eval_result?, }; @@ -338,21 +339,8 @@ pub(crate) fn evaluate_expression( let function_as_value = evaluate_as_function(function, &arg_types, environment)?; return if let Value::Function(function) = function_as_value { - match call_function(&function, &mut evaluated_args, environment, span) { - Err(FunctionCarrier::FunctionNotFound) => { - let argument_string = - evaluated_args.iter().map(Value::static_type).join(", "); - - return Err(FunctionCarrier::EvaluationError(EvaluationError::new( - format!( - "no function called '{}' found matches the arguments: ({argument_string})", - function.borrow().name() - ), - span, - ))); - } - result => result, - } + // Here we should be able to call without checking types + function.borrow().call(&mut evaluated_args, environment) } else { Err(FunctionCarrier::EvaluationError(EvaluationError::new( format!( @@ -680,8 +668,8 @@ fn produce_default_value( ) -> EvaluationResult { match default { Value::Function(function) => { - match call_function(function, &mut [], environment, span) { - Err(FunctionCarrier::FunctionNotFound) => { + match function.borrow().call_checked(&mut [], environment) { + Err(FunctionCarrier::FunctionTypeMismatch) => { Err(FunctionCarrier::EvaluationError(EvaluationError::new( "default function is not callable without arguments".to_string(), span, @@ -711,20 +699,14 @@ fn declare_or_assign_variable( .set(resolved.expect("must be resolved"), value.clone()); } Lvalue::Sequence(l_values) => { - let mut remaining = l_values.len(); - let mut iter = l_values.iter().zip(value.try_into_iter().ok_or_else(|| { + let r_values = value.try_into_vec().ok_or_else(|| { FunctionCarrier::EvaluationError(EvaluationError::syntax_error( "failed to unpack non iterable value into pattern".to_string(), span, )) - })?); - - for (l_value, value) in iter.by_ref() { - remaining -= 1; - declare_or_assign_variable(l_value, value, environment, span)?; - } + })?; - if remaining > 0 || iter.next().is_some() { + if l_values.len() != r_values.len() { return Err(EvaluationError::syntax_error( "failed to unpack value into pattern because the lengths do not match" .to_string(), @@ -732,6 +714,11 @@ fn declare_or_assign_variable( ) .into()); } + let mut iter = l_values.iter().zip(r_values); + + for (l_value, value) in iter.by_ref() { + declare_or_assign_variable(l_value, value, environment, span)?; + } } Lvalue::Index { value: lhs_expr, @@ -887,28 +874,6 @@ where } } -fn call_function( - function: &Rc>, - evaluated_args: &mut [Value], - environment: &Rc>, - span: Span, -) -> EvaluationResult { - let result = function.borrow().call(evaluated_args, environment); - - match result { - Err(FunctionCarrier::Return(value)) | Ok(value) => Ok(value), - - e @ Err( - FunctionCarrier::FunctionNotFound - | FunctionCarrier::EvaluationError(_) - // TODO: for now we just pass the break from inside the function to outside the function. This would allow some pretty funky code and might introduce weird bugs? - | FunctionCarrier::Break(_) - | FunctionCarrier::Continue - ) => e, - Err(carrier @ FunctionCarrier::IntoEvaluationError(_)) => Err(carrier.lift_if(span)), - } -} - fn execute_body( body: &ForBody, environment: &Rc>, diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 12c1f3d8..b504de2e 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -99,12 +99,27 @@ impl Function { } } - pub(crate) fn call( + pub fn call(&self, args: &mut [Value], env: &Rc>) -> EvaluationResult { + let result = self.body.call(args, env); + + match result { + Err(FunctionCarrier::Return(value)) | Ok(value) => Ok(value), + e => e, + } + } + + pub fn call_checked( &self, args: &mut [Value], env: &Rc>, ) -> EvaluationResult { - self.body.call(args, env) + let arg_types = args.iter().map(|arg| arg.static_type()).collect::>(); + + if self.static_type().is_fn_and_matches(&arg_types) { + self.call(args, env) + } else { + Err(FunctionCarrier::FunctionTypeMismatch) + } } fn call_vectorized( @@ -394,7 +409,7 @@ pub enum StaticType { Rational, Complex, - // Sequences + // Sequences List -> List Sequence(Box), List(Box), String, @@ -420,8 +435,7 @@ impl StaticType { /// - `Sequence` > sequence types with element type T /// - Generic types are covariant in their type parameters /// - Function parameters are **contravariant**, returns are **covariant** - #[allow(clippy::unnested_or_patterns)] - pub fn is_subtype(&self, other: &StaticType) -> bool { + pub fn is_subtype(&self, other: &Self) -> bool { // Any is the universal supertype if matches!(other, Self::Any) { return true; @@ -432,6 +446,8 @@ impl StaticType { 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, @@ -519,6 +535,7 @@ impl StaticType { } } + // /// Computes the Least Upper Bound (join) of two types. /// /// The LUB is the most specific type that is a supertype of both inputs. @@ -789,6 +806,7 @@ impl StaticType { | 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(_) @@ -797,7 +815,6 @@ impl StaticType { | Self::Int | Self::Rational | Self::Complex - | Self::String | Self::Map { .. } => None, } } @@ -872,7 +889,7 @@ pub enum FunctionCarrier { #[error("evaluation error {0}")] EvaluationError(#[from] EvaluationError), #[error("function does not exist")] - FunctionNotFound, // This error has specific handling behavior and needs its own variant + FunctionTypeMismatch, // This error has specific handling behavior and needs its own variant #[error("unconverted evaluation error")] IntoEvaluationError(Box), } diff --git a/ndc_lib/src/interpreter/semantic/analyser.rs b/ndc_lib/src/interpreter/semantic/analyser.rs index c90a9121..b9a9b164 100644 --- a/ndc_lib/src/interpreter/semantic/analyser.rs +++ b/ndc_lib/src/interpreter/semantic/analyser.rs @@ -206,7 +206,7 @@ impl Analyser { Ok(StaticType::Tuple(types)) } Expression::List { values } => { - let element_type = self.analyse_multiple_expression_with_same_type(values, span)?; + let element_type = self.analyse_multiple_expression_with_same_type(values)?; // TODO: for now if we encounter an empty list expression we say the list is generic over Any but this clearly is not a good solution Ok(StaticType::List(Box::new( @@ -214,30 +214,24 @@ impl Analyser { ))) } Expression::Map { values, default } => { - let mut key_type = None; - let mut value_type = None; + let mut key_type: Option = None; + let mut value_type: Option = None; for (key, value) in values { - if let Some(key_type) = &key_type { + // let map = %{ + // "key": 1, + // 10: 1, + // } + if let Some(key_type) = &mut key_type { let next_type = self.analyse(key)?; - if &next_type != key_type { - return Err(AnalysisError::incompatible_list_element( - key_type.clone(), - next_type, - *span, - )); - } + *key_type = key_type.lub(&next_type); } else { key_type = Some(self.analyse(key)?); } if let Some(value) = value { - if let Some(value_type) = &value_type { + if let Some(value_type) = &mut value_type { let next_type = self.analyse(value)?; if &next_type != value_type { - return Err(AnalysisError::incompatible_list_element( - value_type.clone(), - next_type, - *span, - )); + *value_type = value_type.lub(&next_type); } } else { value_type = Some(self.analyse(value)?); @@ -287,6 +281,8 @@ impl Analyser { return self.analyse(ident); }; + // println!("resolve fn {name} {}", argument_types.iter().join(", ")); + let binding = self.scope_tree.resolve_function2(name, argument_types); let out_type = match &binding { @@ -505,22 +501,14 @@ impl Analyser { fn analyse_multiple_expression_with_same_type( &mut self, expressions: &mut Vec, - span: &mut Span, ) -> Result, AnalysisError> { - let mut element_type = None; + let mut element_type: Option = None; for expression in expressions { - if let Some(element_type) = &element_type { + if let Some(element_type) = &mut element_type { let following_type = self.analyse(expression)?; - if &following_type != element_type { - // We don't have to allow this right now, we could just widen the type until it's Any and call it a day - return Err(AnalysisError::incompatible_list_element( - element_type.clone(), - following_type, - *span, - )); - } + *element_type = element_type.lub(&following_type); } else { element_type = Some(self.analyse(expression)?); } @@ -650,23 +638,27 @@ impl ScopeTree { Some(Binding::Dynamic(loose_bindings)) }) // If we can't find any function in scope that could match, we just default to an identifier. - .or_else(|| self.get_binding_any(ident).map(Binding::Resolved)) + // TODO: no this fucks everything + .or_else(|| { + self.get_binding_any(ident) + .map(|resolved| Binding::Dynamic(vec![resolved])) + }) .unwrap_or(Binding::None) } - fn resolve_function(&mut self, ident: &str, sig: &[StaticType]) -> Option { + fn resolve_function(&mut self, ident: &str, arg_types: &[StaticType]) -> Option { let mut depth = 0; let mut scope_ptr = self.current_scope_idx; loop { - if let Some(slot) = self.scopes[scope_ptr].find_function(ident, sig) { + if let Some(slot) = self.scopes[scope_ptr].find_function(ident, arg_types) { return Some(ResolvedVar::Captured { slot, depth }); } else if let Some(parent_idx) = self.scopes[scope_ptr].parent_idx { depth += 1; scope_ptr = parent_idx; } else { return Some(ResolvedVar::Global { - slot: self.global_scope.find_function(ident, sig)?, + slot: self.global_scope.find_function(ident, arg_types)?, }); } } @@ -751,18 +743,6 @@ pub struct AnalysisError { impl AnalysisError {} impl AnalysisError { - fn incompatible_list_element( - list_type: StaticType, - next_element_type: StaticType, - span: Span, - ) -> AnalysisError { - Self { - text: format!( - "Invalid list expression: not allowed to mix {list_type} with {next_element_type}" - ), - span, - } - } fn unable_to_index_into(typ: &StaticType, span: Span) -> Self { Self { text: format!("Unable to index into {typ}"), diff --git a/ndc_lib/src/interpreter/sequence.rs b/ndc_lib/src/interpreter/sequence.rs index 45d66440..83882d53 100644 --- a/ndc_lib/src/interpreter/sequence.rs +++ b/ndc_lib/src/interpreter/sequence.rs @@ -71,9 +71,9 @@ impl Sequence { .unwrap_or(StaticType::Any), ), }, - Self::Iterator(_) => { - todo!("we cannot determine the type of an iterator without nuking the iterator") - } + // TODO: we can't infer the type of iterators at runtime, unless we implement peek (CNA WE?) + Self::Iterator(_) => StaticType::Iterator(Box::new(StaticType::Any)), + Self::MaxHeap(heap) => StaticType::MaxHeap(Box::new( heap.borrow() .iter() @@ -81,7 +81,7 @@ impl Sequence { .map(|elem| elem.0.static_type()) .unwrap_or(StaticType::Any), )), - Self::MinHeap(heap) => StaticType::MaxHeap(Box::new( + Self::MinHeap(heap) => StaticType::MinHeap(Box::new( heap.borrow() .iter() .min() diff --git a/ndc_lib/src/interpreter/value.rs b/ndc_lib/src/interpreter/value.rs index 6440180a..02d15b03 100644 --- a/ndc_lib/src/interpreter/value.rs +++ b/ndc_lib/src/interpreter/value.rs @@ -84,6 +84,7 @@ impl Value { // Note: this method is called `try_into_iter` but it doesn't always create an iterator over // the original value. In most cases you get an iterator over a copy of the data. #[must_use] + #[deprecated = "use try_into_vec"] pub fn try_into_iter(self) -> Option> { match self { Self::Sequence(Sequence::List(list)) => match Rc::try_unwrap(list) { @@ -123,6 +124,30 @@ impl Value { } } + pub fn try_into_vec(self) -> Option> { + match self { + Self::Sequence(Sequence::List(list)) => match Rc::try_unwrap(list) { + // This short circuit is almost certainly wrong because take will panic if list is borrowed + Ok(list) => Some(list.into_inner()), + Err(list) => Some(Vec::clone(&*list.borrow())), + }, + Self::Sequence(Sequence::Tuple(list)) => match Rc::try_unwrap(list) { + Ok(list) => Some(list), + Err(list) => Some(Vec::clone(&list)), + }, + Self::Sequence(Sequence::Map(map, _)) => { + Some(map.borrow().keys().cloned().collect_vec()) + } + Self::Sequence(Sequence::String(string)) => match Rc::try_unwrap(string) { + // This implementation is peak retard, we don't want collect_vec here + // ^-- WTF: is this comment, we collect_vec here anyways? + Ok(string) => Some(string.into_inner().chars().map(Self::from).collect_vec()), + Err(string) => Some(string.borrow().chars().map(Self::from).collect_vec()), + }, + _ => None, + } + } + #[must_use] pub fn static_type(&self) -> StaticType { match self { diff --git a/tests/programs/006_lists/030_augmented_assign_to_pattern.ndct b/tests/programs/006_lists/030_augmented_assign_to_pattern.ndct index ffff43ac..a43dfce1 100644 --- a/tests/programs/006_lists/030_augmented_assign_to_pattern.ndct +++ b/tests/programs/006_lists/030_augmented_assign_to_pattern.ndct @@ -2,4 +2,4 @@ let x, y = 0, 0; x, y += 1; // ERROR --EXPECT-ERROR-- -cannot use augmented assignment in combination with destructuring +This lvalue is required to be a single identifier From ae5f20db9adf9f8ba220dde9811d2f6564916125 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 11 Dec 2025 11:29:21 +0100 Subject: [PATCH 33/35] Added some add_span logic --- ndc_lib/src/interpreter/evaluate/mod.rs | 15 ++++++++++++++- ndc_lib/src/interpreter/mod.rs | 2 +- tests/programs/011_heap/007_max_heap_min .ndct | 6 +++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index 2cd29618..a799843e 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -340,7 +340,10 @@ pub(crate) fn evaluate_expression( return if let Value::Function(function) = function_as_value { // Here we should be able to call without checking types - function.borrow().call(&mut evaluated_args, environment) + function + .borrow() + .call(&mut evaluated_args, environment) + .add_span(span) } else { Err(FunctionCarrier::EvaluationError(EvaluationError::new( format!( @@ -859,6 +862,16 @@ where } } +pub trait LiftEvaluationResult { + fn add_span(self, span: Span) -> Result; +} + +impl LiftEvaluationResult for Result { + fn add_span(self, span: Span) -> Self { + self.map_err(|err| err.lift_if(span)) + } +} + // NOTE: this is called `IntoEvaluationResult` but it actually only takes care of the error part of an evaluation result. // `EvaluationResult` always wants the `Ok` type to be `Value` but this converter doesn't care. pub trait IntoEvaluationResult { diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index a3997a35..264868fa 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -110,7 +110,7 @@ impl Interpreter { ))?; } Err(FunctionCarrier::EvaluationError(e)) => return Err(InterpreterError::from(e)), - _ => { + r => { panic!( "internal error: unhandled function carrier variant returned from evaluate_expression" ); diff --git a/tests/programs/011_heap/007_max_heap_min .ndct b/tests/programs/011_heap/007_max_heap_min .ndct index 887e07ce..e2aa6976 100644 --- a/tests/programs/011_heap/007_max_heap_min .ndct +++ b/tests/programs/011_heap/007_max_heap_min .ndct @@ -1,9 +1,9 @@ --PROGRAM-- -let heap = MinHeap(); +let heap = MaxHeap(); heap.push(5); heap.push(4); heap.push(6); assert_eq(heap.min, 4); print("ok"); ---EXPECT-- -ok \ No newline at end of file +--EXPECT-ERROR-- +not supported for MaxHeap From e1129500738b7cebd9305114e63bff5ba21d9c4f Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 11 Dec 2025 13:25:34 +0100 Subject: [PATCH 34/35] Jan Itor --- ndc_lib/src/interpreter/evaluate/mod.rs | 5 ++++- ndc_lib/src/interpreter/function.rs | 13 +++++++------ ndc_lib/src/interpreter/mod.rs | 4 ++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index a799843e..0742c59d 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -991,7 +991,10 @@ fn evaluate_as_function( if let Expression::Identifier { resolved, .. } = expression { resolve_dynamic_binding(resolved, arg_types, environment).ok_or_else(|| { FunctionCarrier::EvaluationError(EvaluationError::new( - "dynamic binding failed to produce a useful function".to_string(), + format!( + "Failed to find a function that can handle the arguments ({}) at runtime", + arg_types.iter().join(", ") + ), function_expression.span, )) }) diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index b504de2e..121b1ba5 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -435,6 +435,8 @@ impl StaticType { /// - `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) { @@ -783,16 +785,15 @@ impl StaticType { return false; }; - let Some(param_types) = parameters else { + 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; }; - param_types.len() == types.len() - && types - .iter() - .zip(param_types.iter()) - .all(|(typ1, typ2)| typ1.is_subtype(typ2)) + self.is_subtype(&Self::Function { + parameters: Some(types.to_vec()), + return_type: Box::new(Self::Any), + }) } pub fn unpack(&self) -> Option + '_>> { diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index 264868fa..5e1c9360 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -71,8 +71,8 @@ impl Interpreter { self.analyser.analyse(e)?; } - // dbg!(&expressions); - // dbg!(&self.analyser); + dbg!(&expressions); + dbg!(&self.analyser); let final_value = self.interpret(expressions.into_iter())?; From 3f87cd40accef8222c26731b4a6f774bb6a9dd24 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 11 Dec 2025 16:16:52 +0100 Subject: [PATCH 35/35] WIP: adding back vectorization for the 12th time --- ndc_lib/src/interpreter/evaluate/mod.rs | 115 ++++++++++++++++-------- ndc_lib/src/interpreter/function.rs | 4 +- ndc_lib/src/interpreter/value.rs | 6 +- ndc_lib/src/stdlib/value.rs | 4 +- 4 files changed, 87 insertions(+), 42 deletions(-) diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index 0742c59d..51a6224d 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -120,7 +120,7 @@ pub(crate) fn evaluate_expression( ); }; // (&func, &mut arguments, environment, span) - let result = match func.borrow().call_checked(&mut arguments, environment) { + let result = match func.call_checked(&mut arguments, environment) { Err(FunctionCarrier::FunctionTypeMismatch) if operations_to_try.peek().is_none() => { @@ -190,7 +190,7 @@ pub(crate) fn evaluate_expression( ); }; - let result = match func.borrow().call_checked( + let result = match func.call_checked( &mut [value_at_index.clone(), right_value.clone()], environment, ) { @@ -328,31 +328,13 @@ pub(crate) fn evaluate_expression( arguments, } => { let mut evaluated_args = Vec::new(); - let mut arg_types = Vec::new(); for argument in arguments { let arg = evaluate_expression(argument, environment)?; - arg_types.push(arg.static_type()); evaluated_args.push(arg); } - let function_as_value = evaluate_as_function(function, &arg_types, environment)?; - - return if let Value::Function(function) = function_as_value { - // Here we should be able to call without checking types - function - .borrow() - .call(&mut evaluated_args, environment) - .add_span(span) - } else { - Err(FunctionCarrier::EvaluationError(EvaluationError::new( - format!( - "Unable to invoke {} as a function.", - function_as_value.static_type() - ), - span, - ))) - }; + let function_as_value = resolve_and_call(function, evaluated_args, environment, span)?; } Expression::FunctionDeclaration { parameters: arguments, @@ -671,7 +653,7 @@ fn produce_default_value( ) -> EvaluationResult { match default { Value::Function(function) => { - match function.borrow().call_checked(&mut [], environment) { + match function.call_checked(&mut [], environment) { Err(FunctionCarrier::FunctionTypeMismatch) => { Err(FunctionCarrier::EvaluationError(EvaluationError::new( "default function is not callable without arguments".to_string(), @@ -887,7 +869,7 @@ where } } -fn execute_body( +fn execute_for_body( body: &ForBody, environment: &Rc>, result: &mut Vec, @@ -946,7 +928,7 @@ fn execute_for_iterations( declare_or_assign_variable(l_value, r_value, &scope, span)?; if tail.is_empty() { - match execute_body(body, &scope, out_values) { + match execute_for_body(body, &scope, out_values) { Err(FunctionCarrier::Continue) => {} Err(error) => return Err(error), Ok(_value) => {} @@ -958,7 +940,7 @@ fn execute_for_iterations( } ForIteration::Guard(guard) => match evaluate_expression(guard, environment)? { Value::Bool(true) if tail.is_empty() => { - execute_body(body, environment, out_values)?; + execute_for_body(body, environment, out_values)?; } Value::Bool(true) => { execute_for_iterations(tail, body, out_values, environment, span)?; @@ -981,15 +963,61 @@ fn execute_for_iterations( Ok(Value::unit()) } -fn evaluate_as_function( +// fn evaluate_as_function( +// function_expression: &ExpressionLocation, +// arg_types: &[StaticType], +// environment: &Rc>, +// ) -> EvaluationResult { +// let ExpressionLocation { expression, .. } = function_expression; +// +// if let Expression::Identifier { resolved, .. } = expression { +// resolve_dynamic_binding(resolved, arg_types, environment).ok_or_else(|| { +// FunctionCarrier::EvaluationError(EvaluationError::new( +// format!( +// "Failed to find a function that can handle the arguments ({}) at runtime", +// arg_types.iter().join(", ") +// ), +// function_expression.span, +// )) +// }) +// } else { +// evaluate_expression(function_expression, environment) +// } +// } + +fn resolve_and_call( function_expression: &ExpressionLocation, - arg_types: &[StaticType], + mut args: Vec, environment: &Rc>, + span: Span, ) -> EvaluationResult { + //////////////////////////// let ExpressionLocation { expression, .. } = function_expression; - if let Expression::Identifier { resolved, .. } = expression { - resolve_dynamic_binding(resolved, arg_types, environment).ok_or_else(|| { + let function_as_value = if let Expression::Identifier { resolved, .. } = expression { + let arg_types = args.iter().map(|arg| arg.static_type()).collect::>(); + + let opt = match resolved { + Binding::None => None, + Binding::Resolved(var) => Some(environment.borrow().get(*var)), + Binding::Dynamic(dynamic_binding) => dynamic_binding + .iter() // TODO: should we consider the binding order? + .find_map(|binding| { + let value = environment.borrow().get(*binding); + + let Value::Function(fun) = &value else { + panic!("dynamic binding resolved to non-function type at runtime"); + }; + + // Find the first function that matches + if fun.static_type().is_fn_and_matches(&arg_types) { + return Some(value); + } + + None + }), + }; + opt.ok_or_else(|| { FunctionCarrier::EvaluationError(EvaluationError::new( format!( "Failed to find a function that can handle the arguments ({}) at runtime", @@ -997,9 +1025,26 @@ fn evaluate_as_function( ), function_expression.span, )) - }) + })? + } else { + evaluate_expression(function_expression, environment)? + }; + + //////////////////////////// + //////////////////////////// + //////////////////////////// + + if let Value::Function(function) = function_as_value { + // Here we should be able to call without checking types + function.call(&mut args, environment).add_span(span) } else { - evaluate_expression(function_expression, environment) + Err(FunctionCarrier::EvaluationError(EvaluationError::new( + format!( + "Unable to invoke {} as a function.", + function_as_value.static_type() + ), + span, + ))) } } @@ -1021,11 +1066,11 @@ fn resolve_dynamic_binding( }; // Find the first function that matches - if fun.borrow().static_type().is_fn_and_matches(arg_types) { - Some(value) - } else { - None + if fun.static_type().is_fn_and_matches(arg_types) { + return Some(value); } + + None }), } } diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 121b1ba5..4c46a3a0 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -18,13 +18,13 @@ use std::rc::Rc; /// Callable is a wrapper around a `OverloadedFunction` pointer and the environment to make it /// easy to have an executable function as a method signature in the standard library pub struct Callable<'a> { - pub function: Rc>, + pub function: Rc, pub environment: &'a Rc>, } impl Callable<'_> { pub fn call(&self, args: &mut [Value]) -> EvaluationResult { - self.function.borrow().call(args, self.environment) + self.function.call(args, self.environment) } } diff --git a/ndc_lib/src/interpreter/value.rs b/ndc_lib/src/interpreter/value.rs index 02d15b03..a2a170ca 100644 --- a/ndc_lib/src/interpreter/value.rs +++ b/ndc_lib/src/interpreter/value.rs @@ -25,12 +25,12 @@ pub enum Value { Number(Number), Bool(bool), Sequence(Sequence), - Function(Rc>), + Function(Rc), } impl Value { pub(crate) fn function(function: Function) -> Self { - Self::Function(Rc::new(RefCell::new(function))) + Self::Function(Rc::new(function)) } pub(crate) fn string>(string: S) -> Self { Self::Sequence(Sequence::String(Rc::new(RefCell::new(string.into())))) @@ -158,7 +158,7 @@ impl Value { Self::Bool(_) => StaticType::Bool, Self::Sequence(s) => s.static_type(), - Self::Function(fun) => fun.borrow().static_type(), + Self::Function(fun) => fun.static_type(), } } diff --git a/ndc_lib/src/stdlib/value.rs b/ndc_lib/src/stdlib/value.rs index cd925147..b8f8c80a 100644 --- a/ndc_lib/src/stdlib/value.rs +++ b/ndc_lib/src/stdlib/value.rs @@ -16,8 +16,8 @@ mod inner { pub fn docs(func: &Callable<'_>) -> anyhow::Result { let mut buf = String::new(); - let sig = func.function.borrow().type_signature(); - let fun = func.function.borrow(); + let sig = func.function.type_signature(); + let fun = &func.function; if fun.name().is_empty() { write!(buf, "fn({sig})")?;