diff --git a/crates/rue-compiler/src/compiler.rs b/crates/rue-compiler/src/compiler.rs index 4502ef8..f0e8072 100644 --- a/crates/rue-compiler/src/compiler.rs +++ b/crates/rue-compiler/src/compiler.rs @@ -11,7 +11,7 @@ use crate::{ database::{Database, HirId, ScopeId, SymbolId, TypeId}, hir::Hir, scope::Scope, - ty::{FunctionType, PairType, Rest, Type, Value}, + ty::{PairType, Type, Value}, ErrorKind, }; @@ -97,36 +97,6 @@ impl<'a> Compiler<'a> { result } - fn expected_param_type( - &self, - function_type: FunctionType, - index: usize, - spread: bool, - ) -> Option { - let param_types = function_type.param_types; - let len = param_types.len(); - - if index + 1 < len { - return Some(param_types[index]); - } - - if function_type.rest == Rest::Nil { - if index + 1 == len { - return Some(param_types[index]); - } - return None; - } - - if spread { - return Some(param_types[len - 1]); - } - - match self.db.ty(param_types[len - 1]) { - Type::List(list_type) => Some(*list_type), - _ => None, - } - } - fn type_reference(&mut self, referenced_type_id: TypeId) { if let Some(symbol_id) = self.symbol_stack.last() { self.sym @@ -237,6 +207,10 @@ impl<'a> Compiler<'a> { let inner = self.type_name_visitor(*ty, stack); format!("{inner}?") } + Type::PossiblyUndefined(ty) => { + let inner = self.type_name_visitor(*ty, stack); + format!("possibly undefined {inner}") + } }; stack.pop().unwrap(); diff --git a/crates/rue-compiler/src/compiler/expr/field_access_expr.rs b/crates/rue-compiler/src/compiler/expr/field_access_expr.rs index f6c9aa6..2b40489 100644 --- a/crates/rue-compiler/src/compiler/expr/field_access_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/field_access_expr.rs @@ -3,7 +3,7 @@ use rue_parser::FieldAccessExpr; use crate::{ compiler::Compiler, hir::Hir, - ty::{PairType, Type, Value}, + ty::{Guard, PairType, Type, Value}, ErrorKind, }; @@ -51,6 +51,19 @@ impl Compiler<'_> { self.builtins.int, ); } + Type::PossiblyUndefined(inner) if field_name.text() == "exists" => { + let maybe_nil_reference = self.db.alloc_hir(Hir::CheckExists(value.hir_id)); + let exists = self.db.alloc_hir(Hir::IsCons(maybe_nil_reference)); + let mut new_value = Value::new(exists, self.builtins.bool); + + if let Hir::Reference(symbol_id) = self.db.hir(value.hir_id).clone() { + new_value + .guards + .insert(symbol_id, Guard::new(inner, value.type_id)); + } + + return new_value; + } _ => {} } diff --git a/crates/rue-compiler/src/compiler/expr/function_call_expr.rs b/crates/rue-compiler/src/compiler/expr/function_call_expr.rs index 098596c..6739adf 100644 --- a/crates/rue-compiler/src/compiler/expr/function_call_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/function_call_expr.rs @@ -5,159 +5,196 @@ use rue_parser::{AstNode, FunctionCallExpr}; use crate::{ compiler::Compiler, hir::Hir, - ty::{Rest, Type, Value}, - ErrorKind, + ty::{FunctionType, Rest, Type, Value}, + ErrorKind, TypeId, }; impl Compiler<'_> { pub fn compile_function_call_expr(&mut self, call: &FunctionCallExpr) -> Value { - let Some(callee) = call.callee() else { - return self.unknown(); - }; - + // Compile the callee expression. + // We mark this expression as a callee to allow inline function references. self.is_callee = true; - let callee = self.compile_expr(&callee, None); - - let expected = if let Type::Function(fun) = self.db.ty(callee.type_id) { - Some(fun.clone()) - } else if let Type::Unknown = self.db.ty(callee.type_id) { - None - } else { - self.db.error( - ErrorKind::UncallableType(self.type_name(callee.type_id)), - call.callee().unwrap().syntax().text_range(), - ); - None - }; + let callee = call.callee().map(|callee| self.compile_expr(&callee, None)); + + // Get the function type of the callee. + let function_type = + callee + .as_ref() + .and_then(|callee| match self.db.ty(callee.type_id).clone() { + Type::Function(function_type) => Some(function_type), + _ => None, + }); + + // Make sure the callee is callable, if present. + if let Some(callee) = callee.as_ref() { + if function_type.is_none() { + self.db.error( + ErrorKind::UncallableType(self.type_name(callee.type_id)), + call.callee().unwrap().syntax().text_range(), + ); + } + } + // Push a generic type context for the function, and allow inference. self.generic_type_stack.push(HashMap::new()); self.allow_generic_inference_stack.push(true); + // Compile the arguments naively, and defer type checking until later. let mut args = Vec::new(); - let mut arg_types = Vec::new(); let mut spread = false; - let arg_len = call.args().len(); + let call_args = call.args(); + let len = call_args.len(); - for (i, arg) in call.args().into_iter().enumerate().rev() { - let expected_type = expected.as_ref().and_then(|expected| { - self.expected_param_type( - expected.clone(), - i, - i + 1 == arg_len && arg.spread().is_some(), - ) + for (i, arg) in call_args.into_iter().enumerate() { + // Determine the expected type. + let expected_type = function_type.as_ref().and_then(|ty| { + if i < ty.param_types.len() { + Some(ty.param_types[i]) + } else if ty.rest == Rest::Spread { + self.db.unwrap_list(*ty.param_types.last().unwrap()) + } else { + None + } }); - let value = arg + // Compile the argument expression, if present. + // Otherwise, it's a parser error + let expr = arg .expr() .map(|expr| self.compile_expr(&expr, expected_type)) .unwrap_or_else(|| self.unknown()); - arg_types.push(value.type_id); + // Add the argument to the list. + args.push(expr); + // Check if it's a spread argument. if arg.spread().is_some() { - if i + 1 == arg_len { + if i == len - 1 { spread = true; } else { self.db - .error(ErrorKind::NonFinalSpread, arg.syntax().text_range()); + .error(ErrorKind::InvalidSpreadArgument, arg.syntax().text_range()); } } + } - args.push(value.hir_id); + // Check that the arguments match the parameters. + if let Some(function_type) = function_type.as_ref() { + let arg_types = args.iter().map(|arg| arg.type_id).collect::>(); + self.check_arguments(call, function_type, &arg_types, spread); } - args.reverse(); - arg_types.reverse(); + // The generic type context is no longer needed. + let generic_types = self.generic_type_stack.pop().unwrap(); + self.allow_generic_inference_stack.pop().unwrap(); - if let Some(expected) = expected.as_ref() { - let param_len = expected.param_types.len(); + // Calculate the return type. + let mut type_id = + function_type.map_or(self.builtins.unknown, |expected| expected.return_type); - let too_few_args = arg_types.len() < param_len - && !(expected.rest == Rest::Parameter && arg_types.len() == param_len - 1); - let too_many_args = arg_types.len() > param_len && expected.rest == Rest::Nil; + if !generic_types.is_empty() { + type_id = self.db.substitute_type(type_id, &generic_types); + } - if too_few_args && expected.rest == Rest::Parameter { - self.db.error( - ErrorKind::TooFewArgumentsWithVarargs { - expected: param_len - 1, - found: arg_types.len(), - }, - call.syntax().text_range(), - ); - } else if too_few_args || too_many_args { - self.db.error( - ErrorKind::ArgumentMismatch { - expected: param_len, - found: arg_types.len(), - }, - call.syntax().text_range(), - ); - } + // Build the HIR for the function call. - for (i, arg) in arg_types.into_iter().enumerate() { - if i + 1 == arg_len && spread && expected.rest == Rest::Nil { + let hir_id = self.db.alloc_hir(Hir::FunctionCall { + callee: callee.map_or(self.builtins.unknown_hir, |callee| callee.hir_id), + args: args.iter().map(|arg| arg.hir_id).collect(), + varargs: spread, + }); + + Value::new(hir_id, type_id) + } + + fn check_arguments( + &mut self, + ast: &FunctionCallExpr, + function: &FunctionType, + args: &[TypeId], + spread: bool, + ) { + match function.rest { + Rest::Nil => { + if args.len() != function.param_types.len() { self.db.error( - ErrorKind::NonVarargSpread, - call.args()[i].syntax().text_range(), + ErrorKind::ArgumentMismatch { + expected: function.param_types.len(), + found: args.len(), + }, + ast.syntax().text_range(), ); - continue; } - - if i + 1 >= param_len - && (i + 1 < arg_len || !spread) - && expected.rest == Rest::Parameter + } + Rest::Optional => { + if args.len() != function.param_types.len() + && args.len() != function.param_types.len() - 1 { - match self.db.ty(expected.param_types.last().copied().unwrap()) { - Type::List(list_type) => { - self.type_check(arg, *list_type, call.args()[i].syntax().text_range()); - } - _ => { - self.db.error( - ErrorKind::NonListVararg, - call.args()[i].syntax().text_range(), - ); - } - } - continue; + self.db.error( + ErrorKind::ArgumentMismatchOptional { + expected: function.param_types.len(), + found: args.len(), + }, + ast.syntax().text_range(), + ); } - - if i + 1 == arg_len && spread && expected.rest == Rest::Parameter { - self.type_check( - arg, - expected.param_types[param_len - 1], - call.args()[i].syntax().text_range(), + } + Rest::Spread => { + if self + .db + .unwrap_list(*function.param_types.last().unwrap()) + .is_some() + { + if args.len() < function.param_types.len() - 1 { + self.db.error( + ErrorKind::ArgumentMismatchSpread { + expected: function.param_types.len(), + found: args.len(), + }, + ast.syntax().text_range(), + ); + } + } else if args.len() != function.param_types.len() { + self.db.error( + ErrorKind::ArgumentMismatch { + expected: function.param_types.len(), + found: args.len(), + }, + ast.syntax().text_range(), ); - continue; } - - self.type_check( - arg, - expected - .param_types - .get(i) - .copied() - .unwrap_or(self.builtins.unknown), - call.args()[i].syntax().text_range(), - ); } } - let hir_id = self.db.alloc_hir(Hir::FunctionCall { - callee: callee.hir_id, - args, - varargs: spread, - }); + let ast_args = ast.args(); - let mut type_id = expected.map_or(self.builtins.unknown, |expected| expected.return_type); + for (i, &arg) in args.iter().enumerate() { + let last = i == args.len() - 1; - self.allow_generic_inference_stack.pop().unwrap(); - let generic_types = self.generic_type_stack.pop().unwrap(); - - if !generic_types.is_empty() { - type_id = self.db.substitute_type(type_id, &generic_types); + if last && spread { + if function.rest != Rest::Spread { + self.db.error( + ErrorKind::DisallowedSpread, + ast_args[i].syntax().text_range(), + ); + } else if i >= function.param_types.len() - 1 { + let expected_type = *function.param_types.last().unwrap(); + self.type_check(arg, expected_type, ast_args[i].syntax().text_range()); + } + } else if function.rest == Rest::Spread && i >= function.param_types.len() - 1 { + if let Some(inner_list_type) = + self.db.unwrap_list(*function.param_types.last().unwrap()) + { + self.type_check(arg, inner_list_type, ast_args[i].syntax().text_range()); + } else if i == function.param_types.len() - 1 && !spread { + self.db + .error(ErrorKind::RequiredSpread, ast_args[i].syntax().text_range()); + } + } else if i < function.param_types.len() { + let param_type = function.param_types[i]; + self.type_check(arg, param_type, ast_args[i].syntax().text_range()); + } } - - Value::new(hir_id, type_id) } } diff --git a/crates/rue-compiler/src/compiler/expr/lambda_expr.rs b/crates/rue-compiler/src/compiler/expr/lambda_expr.rs index 7abeafa..fa6405b 100644 --- a/crates/rue-compiler/src/compiler/expr/lambda_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/lambda_expr.rs @@ -10,6 +10,7 @@ use crate::{ }; impl Compiler<'_> { + // TODO: Update and cleanup lambdas with the new features. pub fn compile_lambda_expr( &mut self, lambda_expr: &LambdaExpr, @@ -45,10 +46,12 @@ impl Compiler<'_> { if param.spread().is_some() { if i + 1 == len { - rest = Rest::Parameter; + rest = Rest::Spread; } else { - self.db - .error(ErrorKind::NonFinalSpread, param.syntax().text_range()); + self.db.error( + ErrorKind::InvalidSpreadParameter, + param.syntax().text_range(), + ); } } } diff --git a/crates/rue-compiler/src/compiler/expr/list_expr.rs b/crates/rue-compiler/src/compiler/expr/list_expr.rs index ee02cb2..c11eba0 100644 --- a/crates/rue-compiler/src/compiler/expr/list_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/list_expr.rs @@ -62,7 +62,7 @@ impl Compiler<'_> { nil_terminated = false; } else { self.db - .error(ErrorKind::NonFinalSpread, spread.text_range()); + .error(ErrorKind::InvalidSpreadItem, spread.text_range()); } } diff --git a/crates/rue-compiler/src/compiler/expr/path_expr.rs b/crates/rue-compiler/src/compiler/expr/path_expr.rs index de2c106..2495eb4 100644 --- a/crates/rue-compiler/src/compiler/expr/path_expr.rs +++ b/crates/rue-compiler/src/compiler/expr/path_expr.rs @@ -59,8 +59,7 @@ impl Compiler<'_> { return self.unknown(); } - Value::new( - self.db.alloc_hir(Hir::Reference(symbol_id)), + let type_id = self.symbol_type(symbol_id) .unwrap_or_else(|| match self.db.symbol(symbol_id) { Symbol::Unknown | Symbol::Module(..) => unreachable!(), @@ -72,7 +71,8 @@ impl Compiler<'_> { | Symbol::Let(Let { type_id, .. }) | Symbol::Const(Const { type_id, .. }) | Symbol::InlineConst(Const { type_id, .. }) => *type_id, - }), - ) + }); + + Value::new(self.db.alloc_hir(Hir::Reference(symbol_id)), type_id) } } diff --git a/crates/rue-compiler/src/compiler/item/function_item.rs b/crates/rue-compiler/src/compiler/item/function_item.rs index dd72cc5..7bea56b 100644 --- a/crates/rue-compiler/src/compiler/item/function_item.rs +++ b/crates/rue-compiler/src/compiler/item/function_item.rs @@ -10,27 +10,27 @@ use crate::{ }; impl Compiler<'_> { - /// Define a function in the current scope. - /// This does not compile the function body, but it creates a new scope for it. - /// Parameter symbols are defined now in the inner function scope. - /// The function body is compiled later to allow for forward references. pub fn declare_function_item(&mut self, function_item: &FunctionItem) -> SymbolId { - // Add the symbol to the stack early so you can track type references. + // Add the symbol to the stack so you can track type references. let symbol_id = self.db.alloc_symbol(Symbol::Unknown); self.symbol_stack.push(symbol_id); + // Add the scope so you can track generic types. let scope_id = self.db.alloc_scope(Scope::default()); self.scope_stack.push(scope_id); let mut generic_types = Vec::new(); + // Add the generic types to the scope. for generic_type in function_item .generic_types() .map(|generics| generics.idents()) .unwrap_or_default() { + // Create the generic type id. let type_id = self.db.alloc_type(Type::Generic); + // Check for duplicate generic types. if self.scope().ty(generic_type.text()).is_some() { self.db.error( ErrorKind::DuplicateType(generic_type.text().to_string()), @@ -38,57 +38,101 @@ impl Compiler<'_> { ); } + // Add the generic type to the scope and define the token for the generic type. self.scope_mut() .define_type(generic_type.to_string(), type_id); self.db.insert_type_token(type_id, generic_type); + // Add the generic type to the list so it can be added to the function type. generic_types.push(type_id); } let mut param_types = Vec::new(); let mut rest = Rest::Nil; - let len = function_item.params().len(); + let params = function_item.params(); + let len = params.len(); - for (i, param) in function_item.params().into_iter().enumerate() { - // Add the symbol to the stack early so you can track type references. + // Add the parameters to the scope and collect the parameter types. + // Also keep track of and check the spread or optional parameter. + for (i, param) in params.into_iter().enumerate() { + // Add the symbol to the stack so you can track type references. let symbol_id = self.db.alloc_symbol(Symbol::Unknown); self.symbol_stack.push(symbol_id); + // Compile the parameter's type, if present. + // Otherwise, it's a parser error. let type_id = param .ty() .map_or(self.builtins.unknown, |ty| self.compile_type(ty)); + // Add the parameter type to the list and update the parameter symbol. param_types.push(type_id); - *self.db.symbol_mut(symbol_id) = Symbol::Parameter(type_id); + *self.db.symbol_mut(symbol_id) = Symbol::Parameter(if param.optional().is_some() { + // If the parameter is optional, wrap the type in a possibly undefined type. + // This prevents referencing the parameter until it's checked for undefined. + self.db.alloc_type(Type::PossiblyUndefined(type_id)) + } else { + // Otherwise, just use the type. + type_id + }); + // Add the parameter to the scope and define the token for the parameter. if let Some(name) = param.name() { + if self.scope().symbol(name.text()).is_some() { + self.db.error( + ErrorKind::DuplicateSymbol(name.text().to_string()), + name.text_range(), + ); + } + self.scope_mut().define_symbol(name.to_string(), symbol_id); self.db.insert_symbol_token(symbol_id, name); } - if param.spread().is_some() { - if i + 1 == len { - rest = Rest::Parameter; - } else { - self.db - .error(ErrorKind::NonFinalSpread, param.syntax().text_range()); - } + // Check if it's a spread or optional parameter. + let last = i + 1 == len; + let spread = param.spread().is_some(); + let optional = param.optional().is_some(); + + if spread && optional { + self.db.error( + ErrorKind::OptionalParameterSpread, + param.syntax().text_range(), + ); + } else if spread && !last { + self.db.error( + ErrorKind::InvalidSpreadParameter, + param.syntax().text_range(), + ); + } else if optional && !last { + self.db.error( + ErrorKind::InvalidOptionalParameter, + param.syntax().text_range(), + ); + } else if spread { + rest = Rest::Spread; + } else if optional { + rest = Rest::Optional; } self.symbol_stack.pop().unwrap(); } + // Compile the return type, if present. + // Otherwise, it's a parser error. let return_type = function_item .return_type() .map_or(self.builtins.unknown, |ty| self.compile_type(ty)); self.scope_stack.pop().unwrap(); + // We don't know the body yet, so we just allocate a placeholder HIR node. let hir_id = self.db.alloc_hir(Hir::Unknown); + // Create the function's type. let ty = FunctionType { generic_types, param_types, @@ -96,6 +140,7 @@ impl Compiler<'_> { return_type, }; + // Update the symbol with the function. if function_item.inline().is_some() { *self.db.symbol_mut(symbol_id) = Symbol::InlineFunction(Function { scope_id, @@ -110,6 +155,7 @@ impl Compiler<'_> { }); } + // Add the function to the scope and define the token for the function and scope. if let Some(name) = function_item.name() { self.scope_mut().define_symbol(name.to_string(), symbol_id); self.db.insert_scope_token(scope_id, name.clone()); @@ -119,12 +165,14 @@ impl Compiler<'_> { self.symbol_stack.pop().unwrap() } - /// Compiles the body of a function within the function's scope. pub fn compile_function_item(&mut self, function: &FunctionItem, symbol_id: SymbolId) { + // The body is already unknown, so we don't need to do anything if it's not present. + // This is a parser error, of course. let Some(body) = function.body() else { return; }; + // Get the function's scope and type. let (Symbol::Function(Function { scope_id, ty, .. }) | Symbol::InlineFunction(Function { scope_id, ty, .. })) = self.db.symbol(symbol_id).clone() @@ -146,8 +194,7 @@ impl Compiler<'_> { function.body().unwrap().syntax().text_range(), ); - // We ignore type guards here for now. - // Just set the function body HIR. + // Update the function's HIR with the body's HIR, for code generation purposes. let (Symbol::Function(Function { hir_id, .. }) | Symbol::InlineFunction(Function { hir_id, .. })) = self.db.symbol_mut(symbol_id) else { diff --git a/crates/rue-compiler/src/compiler/ty/function_type.rs b/crates/rue-compiler/src/compiler/ty/function_type.rs index 09e276c..4d334b6 100644 --- a/crates/rue-compiler/src/compiler/ty/function_type.rs +++ b/crates/rue-compiler/src/compiler/ty/function_type.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use rue_parser::{AstNode, FunctionType as Ast}; use crate::{ @@ -9,31 +11,69 @@ use crate::{ impl Compiler<'_> { pub fn compile_function_type(&mut self, function: &Ast) -> TypeId { let mut param_types = Vec::new(); + let mut type_names = HashSet::new(); let mut rest = Rest::Nil; - let len = function.params().len(); + let params = function.params(); + let len = params.len(); + + for (i, param) in params.into_iter().enumerate() { + // We don't actually use the names yet, + // but go ahead and check for duplicates. + // TODO: Use the name in the actual type? + if let Some(name) = param.name() { + if !type_names.insert(name.to_string()) { + self.db.error( + ErrorKind::DuplicateSymbol(name.to_string()), + name.text_range(), + ); + } + } - for (i, param) in function.params().into_iter().enumerate() { + // Compile the type of the parameter, if present. + // Otherwise, it's a parser error. let type_id = param .ty() .map_or(self.builtins.unknown, |ty| self.compile_type(ty)); + // Add the parameter type to the list. param_types.push(type_id); - if param.spread().is_some() { - if i + 1 == len { - rest = Rest::Parameter; - } else { - self.db - .error(ErrorKind::NonFinalSpread, param.syntax().text_range()); - } + // Check if it's a spread or optional parameter. + let last = i + 1 == len; + let spread = param.spread().is_some(); + let optional = param.optional().is_some(); + + if spread && optional { + self.db.error( + ErrorKind::OptionalParameterSpread, + param.syntax().text_range(), + ); + } else if spread && !last { + self.db.error( + ErrorKind::InvalidSpreadParameter, + param.syntax().text_range(), + ); + } else if optional && !last { + self.db.error( + ErrorKind::InvalidOptionalParameter, + param.syntax().text_range(), + ); + } else if spread { + rest = Rest::Spread; + } else if optional { + rest = Rest::Optional; } } + // Compile the return type of the function, if present. + // Otherwise, it's a parser error. let return_type = function .return_type() .map_or(self.builtins.unknown, |ty| self.compile_type(ty)); + // Allocate a new type for the function. + // TODO: Support generic types. self.db.alloc_type(Type::Function(FunctionType { param_types, rest, diff --git a/crates/rue-compiler/src/database.rs b/crates/rue-compiler/src/database.rs index 2b9fd33..fa839bc 100644 --- a/crates/rue-compiler/src/database.rs +++ b/crates/rue-compiler/src/database.rs @@ -157,6 +157,9 @@ impl Database { format!("Pair({}, {})", self.dbg_hir(*first), self.dbg_hir(*rest)) } Hir::Reference(symbol_id) => format!("Reference({})", self.dbg_symbol(*symbol_id)), + Hir::CheckExists(hir_id) => { + format!("CheckExists({})", self.dbg_hir(*hir_id)) + } Hir::FunctionCall { callee, args, diff --git a/crates/rue-compiler/src/database/type_system.rs b/crates/rue-compiler/src/database/type_system.rs index f3a7c7d..15e5ddf 100644 --- a/crates/rue-compiler/src/database/type_system.rs +++ b/crates/rue-compiler/src/database/type_system.rs @@ -77,6 +77,13 @@ impl Database { } } + pub fn unwrap_list(&mut self, ty: TypeId) -> Option { + match self.ty(ty) { + Type::List(inner) => Some(*inner), + _ => None, + } + } + pub fn substitute_type_visitor( &mut self, type_id: TypeId, @@ -203,7 +210,7 @@ impl Database { self.alloc_type(Type::Function(new_function)) } } - Type::Optional(inner) => { + Type::Optional(inner) | Type::PossiblyUndefined(inner) => { let new_inner = self.substitute_type_visitor(inner, substitutions, visited); if new_inner == inner { @@ -264,6 +271,11 @@ impl Database { // So we will go ahead and pretend they are equal to everything. (Type::Unknown, _) | (_, Type::Unknown) => Comparison::Equal, + // Possibly undefined is a special type. + (Type::PossiblyUndefined(..), _) | (_, Type::PossiblyUndefined(..)) => { + Comparison::Unrelated + } + // These are of course equal atomic types. (Type::Any, Type::Any) => Comparison::Equal, (Type::Int, Type::Int) => Comparison::Equal, @@ -472,7 +484,9 @@ impl Database { | Type::Bytes | Type::Bytes32 | Type::PublicKey => false, - Type::Optional(ty) => self.is_cyclic_visitor(ty, visited_aliases), + Type::Optional(ty) | Type::PossiblyUndefined(ty) => { + self.is_cyclic_visitor(ty, visited_aliases) + } } } } diff --git a/crates/rue-compiler/src/error.rs b/crates/rue-compiler/src/error.rs index b930d2e..86de908 100644 --- a/crates/rue-compiler/src/error.rs +++ b/crates/rue-compiler/src/error.rs @@ -116,11 +116,20 @@ pub enum ErrorKind { #[error("cannot call expression with type `{0}`")] UncallableType(String), - #[error("expected {expected} arguments, but found {found}")] + #[error( + "expected {expected} argument{}, but found {found}", + if *expected == 1 { "" } else { "s" } + )] ArgumentMismatch { expected: usize, found: usize }, - #[error("expected at least {expected} arguments, but found {found}")] - TooFewArgumentsWithVarargs { expected: usize, found: usize }, + #[error( + "expected at least {expected} argument{}, but found {found}", + if *expected == 1 { "" } else { "s" } + )] + ArgumentMismatchSpread { expected: usize, found: usize }, + + #[error("expected {} or {expected} arguments, but found {found}", expected - 1)] + ArgumentMismatchOptional { expected: usize, found: usize }, #[error("uninitializable type `{0}`")] UninitializableType(String), @@ -140,14 +149,26 @@ pub enum ErrorKind { #[error("cannot index into non-list type `{0}`")] IndexAccess(String), - #[error("the spread operator can only be used on the last element")] - NonFinalSpread, + #[error("the spread operator can only be used on the last list item")] + InvalidSpreadItem, + + #[error("the spread operator can only be used on the last argument")] + InvalidSpreadArgument, + + #[error("the spread operator can only be used on the last parameter")] + InvalidSpreadParameter, + + #[error("optional can only be used on the last parameter")] + InvalidOptionalParameter, + + #[error("the spread operator cannot be used on optional parameters")] + OptionalParameterSpread, - #[error("cannot spread expression in non-vararg function call")] - NonVarargSpread, + #[error("the function does not support the spread operator")] + DisallowedSpread, - #[error("cannot pass arguments directly (without spreading) to non-list vararg function call")] - NonListVararg, + #[error("the function requires the spread operator on the last argument")] + RequiredSpread, #[error("duplicate enum variant `{0}`")] DuplicateEnumVariant(String), diff --git a/crates/rue-compiler/src/hir.rs b/crates/rue-compiler/src/hir.rs index c0f18c7..62e3614 100644 --- a/crates/rue-compiler/src/hir.rs +++ b/crates/rue-compiler/src/hir.rs @@ -25,6 +25,7 @@ pub enum Hir { }, First(HirId), Rest(HirId), + CheckExists(HirId), Not(HirId), Raise(Option), Sha256(HirId), diff --git a/crates/rue-compiler/src/optimizer.rs b/crates/rue-compiler/src/optimizer.rs index c02eaa8..a53d50f 100644 --- a/crates/rue-compiler/src/optimizer.rs +++ b/crates/rue-compiler/src/optimizer.rs @@ -121,6 +121,7 @@ impl<'a> Optimizer<'a> { Hir::Atom(atom) => self.db.alloc_lir(Lir::Atom(atom.clone())), Hir::Pair(first, rest) => self.opt_pair(env_id, first, rest), Hir::Reference(symbol_id) => self.opt_reference(env_id, symbol_id), + Hir::CheckExists(value) => self.opt_check_exists(env_id, value), Hir::Definition { scope_id, hir_id } => { let definition_env_id = self.graph.env(scope_id); for symbol_id in self.db.env_mut(definition_env_id).definitions() { @@ -198,6 +199,29 @@ impl<'a> Optimizer<'a> { } } + fn opt_check_exists(&mut self, env_id: EnvironmentId, hir_id: HirId) -> LirId { + let value = self.opt_hir(env_id, hir_id); + let Lir::Path(path) = self.db.lir(value).clone() else { + panic!("invalid state, expected path for existence check"); + }; + self.db.alloc_lir(Lir::Path(Self::pack_bits(path))) + } + + fn pack_bits(mut n: u32) -> u32 { + let mut result = 0; + let mut position = 0; + + while n > 0 { + if n & 1 != 0 { + result |= 1 << position; + position += 1; + } + n >>= 1; + } + + result + } + fn opt_env_definition( &mut self, env_id: EnvironmentId, @@ -350,7 +374,7 @@ impl<'a> Optimizer<'a> { .into_iter() .enumerate() { - if i + 1 == param_len && ty.rest == Rest::Parameter { + if i + 1 == param_len && ty.rest == Rest::Spread { let mut rest = self.db.alloc_hir(Hir::Atom(Vec::new())); for (i, arg) in args.clone().into_iter().rev().enumerate() { if i == 0 && varargs { diff --git a/crates/rue-compiler/src/optimizer/dependency_graph.rs b/crates/rue-compiler/src/optimizer/dependency_graph.rs index ab4e0f3..0cc3909 100644 --- a/crates/rue-compiler/src/optimizer/dependency_graph.rs +++ b/crates/rue-compiler/src/optimizer/dependency_graph.rs @@ -163,6 +163,7 @@ impl<'a> GraphTraversal<'a> { | Hir::Strlen(hir_id) | Hir::First(hir_id) | Hir::Rest(hir_id) + | Hir::CheckExists(hir_id) | Hir::Not(hir_id) | Hir::IsCons(hir_id) => self.visit_hir(scope_id, hir_id, visited), @@ -303,6 +304,7 @@ impl<'a> GraphTraversal<'a> { | Hir::Strlen(hir_id) | Hir::First(hir_id) | Hir::Rest(hir_id) + | Hir::CheckExists(hir_id) | Hir::Not(hir_id) | Hir::IsCons(hir_id) => self.compute_hir_edges(scope_id, hir_id, visited), Hir::Raise(hir_id) => { @@ -394,10 +396,9 @@ impl<'a> GraphTraversal<'a> { .collect(); if !self.graph.env.contains_key(&scope_id) { - let environment_id = self.db.alloc_env(Environment::function( - parameters, - ty.rest == Rest::Parameter, - )); + let environment_id = self + .db + .alloc_env(Environment::function(parameters, ty.rest == Rest::Spread)); self.graph.env.insert(scope_id, environment_id); } diff --git a/crates/rue-compiler/src/ty.rs b/crates/rue-compiler/src/ty.rs index 3ccf45d..f98f741 100644 --- a/crates/rue-compiler/src/ty.rs +++ b/crates/rue-compiler/src/ty.rs @@ -26,6 +26,7 @@ pub enum Type { Function(FunctionType), Alias(TypeId), Optional(TypeId), + PossiblyUndefined(TypeId), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -63,7 +64,8 @@ pub struct FunctionType { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Rest { Nil, - Parameter, + Spread, + Optional, } #[derive(Debug, Clone)] diff --git a/crates/rue-parser/src/ast.rs b/crates/rue-parser/src/ast.rs index d7c4763..f2e0214 100644 --- a/crates/rue-parser/src/ast.rs +++ b/crates/rue-parser/src/ast.rs @@ -243,6 +243,13 @@ impl FunctionParam { .find(|token| token.kind() == SyntaxKind::Ident) } + pub fn optional(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(SyntaxElement::into_token) + .find(|token| token.kind() == SyntaxKind::Question) + } + pub fn ty(&self) -> Option { self.syntax().children().find_map(Type::cast) } @@ -716,6 +723,13 @@ impl LambdaParam { .find(|token| token.kind() == SyntaxKind::Ident) } + pub fn optional(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(SyntaxElement::into_token) + .find(|token| token.kind() == SyntaxKind::Question) + } + pub fn ty(&self) -> Option { self.syntax().children().find_map(Type::cast) } @@ -840,6 +854,20 @@ impl FunctionType { } impl FunctionTypeParam { + pub fn name(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(SyntaxElement::into_token) + .find(|token| token.kind() == SyntaxKind::Ident) + } + + pub fn optional(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(SyntaxElement::into_token) + .find(|token| token.kind() == SyntaxKind::Question) + } + pub fn spread(&self) -> Option { self.syntax() .children_with_tokens() diff --git a/crates/rue-parser/src/grammar.rs b/crates/rue-parser/src/grammar.rs index 8b1a3a4..082f223 100644 --- a/crates/rue-parser/src/grammar.rs +++ b/crates/rue-parser/src/grammar.rs @@ -91,6 +91,7 @@ fn function_param(p: &mut Parser<'_>) { p.start(SyntaxKind::FunctionParam); p.try_eat(SyntaxKind::Spread); p.expect(SyntaxKind::Ident); + p.try_eat(SyntaxKind::Question); p.expect(SyntaxKind::Colon); ty(p); p.finish(); @@ -527,6 +528,7 @@ fn lambda_param(p: &mut Parser<'_>) { p.start(SyntaxKind::LambdaParam); p.try_eat(SyntaxKind::Spread); p.expect(SyntaxKind::Ident); + p.try_eat(SyntaxKind::Question); if p.try_eat(SyntaxKind::Colon) { ty(p); } @@ -595,6 +597,9 @@ fn path_type(p: &mut Parser<'_>) { fn function_type_param(p: &mut Parser<'_>) { p.start(SyntaxKind::FunctionTypeParam); p.try_eat(SyntaxKind::Spread); + p.expect(SyntaxKind::Ident); + p.try_eat(SyntaxKind::Question); + p.expect(SyntaxKind::Colon); ty(p); p.finish(); } diff --git a/examples/p2_delegated_or_hidden.rue b/examples/p2_delegated_or_hidden.rue index 25ee32d..a4f66e4 100644 --- a/examples/p2_delegated_or_hidden.rue +++ b/examples/p2_delegated_or_hidden.rue @@ -3,7 +3,7 @@ fun main( synthetic_pk: PublicKey, original_pk: PublicKey?, - delegated_puzzle: fun(...Any) -> Condition[], + delegated_puzzle: fun(...solution: Any) -> Condition[], delegated_solution: Any ) -> Condition[] { let conditions = delegated_puzzle(...delegated_solution); diff --git a/std/stdlib.rue b/std/stdlib.rue index 108ca2d..14a2b6a 100644 --- a/std/stdlib.rue +++ b/std/stdlib.rue @@ -197,14 +197,14 @@ export fun calculate_coin_id( sha256(parent_coin_id + puzzle_hash + amount as Bytes) } -export fun map(list: T[], fn: fun(T) -> U) -> U[] { +export fun map(list: T[], fn: fun(item: T) -> U) -> U[] { if list is Nil { return nil; } [fn(list.first), ...map(list.rest, fn)] } -export fun filter(list: T[], fn: fun(T) -> Bool) -> T[] { +export fun filter(list: T[], fn: fun(item: T) -> Bool) -> T[] { if list is Nil { return nil; } @@ -214,7 +214,7 @@ export fun filter(list: T[], fn: fun(T) -> Bool) -> T[] { filter(list.rest, fn) } -export fun fold(list: T[], initial: U, fn: fun(U, T) -> U) -> U { +export fun fold(list: T[], initial: U, fn: fun(acc: U, item: T) -> U) -> U { if list is Nil { return initial; } diff --git a/tests.toml b/tests.toml index 0438118..d57f662 100644 --- a/tests.toml +++ b/tests.toml @@ -283,3 +283,55 @@ cost = 623 input = "()" output = "()" hash = "e7a613b58b23e44d70e889f8d8975724c9f786d19d5491428c6ca63eb87420ad" + +[optional_param] +bytes = 69 +cost = 3150 +input = "()" +output = "25" +hash = "391d788eddb3e3d3e83b78ad6e7e4fc9a9dd897096d933481d36bd2816f93863" + +[varargs] +bytes = 127 +cost = 11401 +input = "()" +output = "21" +hash = "1d14002c095c0e7269af3397809111a9d2464fa72a409edc08811e9740cf0b7f" + +[function_call] +parser_errors = [] +compiler_errors = [ + "expected 0 arguments, but found 1 at 3:11", + "expected 0 or 1 arguments, but found 2 at 7:11", + "the function does not support the spread operator at 8:25", + "expected 0 or 1 arguments, but found 2 at 9:11", + "the function does not support the spread operator at 9:28", + "expected 1 or 2 arguments, but found 0 at 11:11", + "expected 1 or 2 arguments, but found 3 at 14:11", + "expected 1 or 2 arguments, but found 4 at 15:11", + "the function does not support the spread operator at 16:25", + "the function does not support the spread operator at 17:28", + "expected 1 or 2 arguments, but found 3 at 18:11", + "the function does not support the spread operator at 18:31", + "expected 1 or 2 arguments, but found 4 at 19:11", + "the function does not support the spread operator at 19:34", + "the function does not support the spread operator at 20:28", + "expected type `Int[]`, but found `Int` at 26:28", + "expected type `Int[]`, but found `Int` at 27:31", + "expected at least 2 arguments, but found 0 at 31:11", + "expected type `Int[]`, but found `Int` at 36:31", + "expected 1 argument, but found 0 at 40:11", + "the function requires the spread operator on the last argument at 41:27", + "expected 1 argument, but found 2 at 42:11", + "the function requires the spread operator on the last argument at 42:27", + "expected 1 argument, but found 2 at 44:11", + "expected type `Int`, but found `Int[]` at 45:27", + "expected 1 argument, but found 2 at 46:11", + "expected type `Int`, but found `Int[]` at 46:30", + "expected 2 arguments, but found 0 at 48:11", + "expected 2 arguments, but found 1 at 49:11", + "the function requires the spread operator on the last argument at 50:30", + "expected 2 arguments, but found 1 at 51:11", + "expected 2 arguments, but found 1 at 53:11", + "expected type `Int`, but found `Int[]` at 54:30", +] diff --git a/tests/functions/function_call.rue b/tests/functions/function_call.rue new file mode 100644 index 0000000..0655ef1 --- /dev/null +++ b/tests/functions/function_call.rue @@ -0,0 +1,85 @@ +fun main() -> Nil { + assert empty(); + assert empty(1); + + assert one_optional(); + assert one_optional(1); + assert one_optional(1, 2); + assert one_optional(...1); + assert one_optional(1, ...2); + + assert two_optional(); + assert two_optional(1); + assert two_optional(1, 2); + assert two_optional(1, 2, 3); + assert two_optional(1, 2, 3, 4); + assert two_optional(...[1, 2, 3, 4]); + assert two_optional(1, ...[2, 3, 4]); + assert two_optional(1, 2, ...[3, 4]); + assert two_optional(1, 2, 3, ...[4]); + assert two_optional(1, ...2); + + assert one_spread_list(); + assert one_spread_list(1); + assert one_spread_list(1, 2); + assert one_spread_list(1, 2, 3); + assert one_spread_list(...1); + assert one_spread_list(1, ...2); + assert one_spread_list(...[1, 2]); + assert one_spread_list(1, ...[2]); + + assert two_spread_list(); + assert two_spread_list(1); + assert two_spread_list(1, 2); + assert two_spread_list(1, 2, 3); + assert two_spread_list(...1); + assert two_spread_list(1, ...2); + assert two_spread_list(...[1, 2]); + assert two_spread_list(1, ...[2]); + + assert one_spread_raw(); + assert one_spread_raw(1); + assert one_spread_raw(1, 2); + assert one_spread_raw(...1); + assert one_spread_raw(1, ...2); + assert one_spread_raw(...[1, 2]); + assert one_spread_raw(1, ...[2]); + + assert two_spread_raw(); + assert two_spread_raw(1); + assert two_spread_raw(1, 2); + assert two_spread_raw(...1); + assert two_spread_raw(1, ...2); + assert two_spread_raw(...[1, 2]); + assert two_spread_raw(1, ...[2]); + + nil +} + +fun empty() -> Bool { + true +} + +fun one_optional(_a?: Int) -> Bool { + true +} + +fun two_optional(_a: Int, _b?: Int) -> Bool { + true +} + +fun one_spread_list(..._a: Int[]) -> Bool { + true +} + +fun one_spread_raw(..._a: Int) -> Bool { + true +} + +fun two_spread_list(_a: Int, ..._b: Int[]) -> Bool { + true +} + +fun two_spread_raw(_a: Int, ..._b: Int) -> Bool { + true +} diff --git a/tests/functions/optional_param.rue b/tests/functions/optional_param.rue new file mode 100644 index 0000000..0e54d90 --- /dev/null +++ b/tests/functions/optional_param.rue @@ -0,0 +1,11 @@ +fun main() -> Int { + square(5) + square() +} + +fun square(num?: Int) -> Int { + if num.exists { + num * num + } else { + 0 + } +} diff --git a/tests/functions/varargs.rue b/tests/functions/varargs.rue new file mode 100644 index 0000000..d85de25 --- /dev/null +++ b/tests/functions/varargs.rue @@ -0,0 +1,10 @@ +fun main() -> Int { + sum(1, 2, 3, 4, ...[5, 6]) +} + +fun sum(...nums: Int[]) -> Int { + if nums is Nil { + return 0; + } + nums.first + sum(...nums.rest) +}