From 4213b67f038585a457b29c74d7a48a7bab6da851 Mon Sep 17 00:00:00 2001 From: arty Date: Wed, 6 Mar 2024 09:07:32 -0800 Subject: [PATCH] Two failing tests with this merge. Should be fixable. --- src/compiler/codegen.rs | 2 + src/compiler/comptypes.rs | 82 ++- src/compiler/evaluate.rs | 548 ++++++++++--------- src/compiler/frontend.rs | 806 +++++++++++++++++++++++----- src/compiler/optimize/mod.rs | 1 + src/compiler/preprocessor/mod.rs | 13 +- src/compiler/rename.rs | 3 + src/tests/classic/run.rs | 2 - src/tests/compiler/clvm.rs | 2 - src/tests/compiler/optimizer/cse.rs | 2 + 10 files changed, 1052 insertions(+), 409 deletions(-) diff --git a/src/compiler/codegen.rs b/src/compiler/codegen.rs index af3fcb4c..a95fb0c1 100644 --- a/src/compiler/codegen.rs +++ b/src/compiler/codegen.rs @@ -1006,6 +1006,7 @@ fn generate_let_defun( args: inner_function_args, body, synthetic: Some(SyntheticType::NoInlinePreference), + ty: None, }), ) } @@ -1317,6 +1318,7 @@ pub fn hoist_body_let_binding( args: new_function_args, body: new_body, synthetic: Some(SyntheticType::WantNonInline), + ty: None, }), ); new_helpers_from_body.push(function); diff --git a/src/compiler/comptypes.rs b/src/compiler/comptypes.rs index 5d5cf41e..10cb4e32 100644 --- a/src/compiler/comptypes.rs +++ b/src/compiler/comptypes.rs @@ -13,6 +13,9 @@ use crate::compiler::clvm::{sha256tree, truthy}; use crate::compiler::dialect::AcceptedDialect; use crate::compiler::sexp::{decode_string, enlist, SExp}; use crate::compiler::srcloc::Srcloc; +use crate::compiler::typecheck::TheoryToSExp; +use crate::compiler::types::ast::{Polytype, TypeVar}; +use crate::util::Number; // Note: only used in tests, not normally dependencies. #[cfg(test)] @@ -204,7 +207,6 @@ pub enum BodyForm { Lambda(Box), } -/// Convey information about synthetically generated helper forms. #[derive(Clone, Debug, Serialize)] pub enum SyntheticType { NoInlinePreference, @@ -233,6 +235,8 @@ pub struct DefunData { pub body: Rc, /// Whether this defun was created during desugaring. pub synthetic: Option, + /// Type annotation if given. + pub ty: Option, } /// Specifies the information extracted from a macro definition allowing the @@ -274,6 +278,48 @@ pub struct DefconstData { pub body: Rc, /// This constant should exist in the left env rather than be inlined. pub tabled: bool, + /// Type annotation if given. + pub ty: Option, +} + +#[derive(Clone, Debug)] +pub struct StructMember { + pub loc: Srcloc, + pub name: Vec, + pub path: Number, + pub ty: Polytype, +} + +#[derive(Clone, Debug)] +pub struct StructDef { + pub loc: Srcloc, + pub name: Vec, + pub vars: Vec, + pub members: Vec, + pub proto: Rc, + pub ty: Polytype, +} + +#[derive(Clone, Debug)] +pub enum ChiaType { + Abstract(Srcloc, Vec), + Struct(StructDef), +} + +#[derive(Clone, Debug)] +pub enum TypeAnnoKind { + Colon(Polytype), + Arrow(Polytype), +} + +#[derive(Clone, Debug, Serialize)] +pub struct DeftypeData { + pub kw: Srcloc, + pub nl: Srcloc, + pub loc: Srcloc, + pub name: Vec, + pub args: Vec, + pub ty: Option, } /// Specifies where a constant is the classic kind (unevaluated) or a proper @@ -290,6 +336,8 @@ pub enum ConstantKind { /// individually parsable and represent the atomic units of the program. #[derive(Clone, Debug, Serialize)] pub enum HelperForm { + /// A type definition. + Deftype(DeftypeData), /// A constant definition (see DefconstData). Defconstant(DefconstData), /// A macro definition (see DefmacData). @@ -352,6 +400,8 @@ pub struct CompileForm { pub helpers: Vec, /// The expression the program evaluates, using the declared helpers. pub exp: Rc, + /// Type if specified. + pub ty: Option, } /// Represents a call to a defun, used by code generation. @@ -465,11 +515,12 @@ pub trait CompilerOpts { } /// Frontend uses this to accumulate frontend forms, used internally. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ModAccum { pub loc: Srcloc, pub includes: Vec, pub helpers: Vec, + pub left_capture: bool, pub exp_form: Option, } @@ -506,6 +557,7 @@ impl ModAccum { loc: self.loc.clone(), includes: self.includes.clone(), helpers: self.helpers.clone(), + left_capture: self.left_capture, exp_form: Some(c.clone()), } } @@ -517,6 +569,7 @@ impl ModAccum { loc: self.loc.clone(), includes: new_includes, helpers: self.helpers.clone(), + left_capture: self.left_capture, exp_form: self.exp_form.clone(), } } @@ -529,15 +582,17 @@ impl ModAccum { loc: self.loc.clone(), includes: self.includes.clone(), helpers: hs, + left_capture: self.left_capture, exp_form: self.exp_form.clone(), } } - pub fn new(loc: Srcloc) -> ModAccum { + pub fn new(loc: Srcloc, left_capture: bool) -> ModAccum { ModAccum { loc, includes: Vec::new(), helpers: Vec::new(), + left_capture, exp_form: None, } } @@ -576,6 +631,7 @@ impl CompileForm { .cloned() .collect(), exp: self.exp.clone(), + ty: self.ty.clone(), } } @@ -600,6 +656,7 @@ impl CompileForm { args: self.args.clone(), helpers: new_helpers, exp: self.exp.clone(), + ty: self.ty.clone(), } } } @@ -640,6 +697,7 @@ impl HelperForm { /// Get a reference to the HelperForm's name. pub fn name(&self) -> &Vec { match self { + HelperForm::Deftype(deft) => &deft.name, HelperForm::Defconstant(defc) => &defc.name, HelperForm::Defmacro(mac) => &mac.name, HelperForm::Defun(_, defun) => &defun.name, @@ -649,6 +707,7 @@ impl HelperForm { /// Get the location of the HelperForm's name. pub fn name_loc(&self) -> &Srcloc { match self { + HelperForm::Deftype(deft) => &deft.nl, HelperForm::Defconstant(defc) => &defc.nl, HelperForm::Defmacro(mac) => &mac.nl, HelperForm::Defun(_, defun) => &defun.nl, @@ -658,6 +717,7 @@ impl HelperForm { /// Return a general location for the whole HelperForm. pub fn loc(&self) -> Srcloc { match self { + HelperForm::Deftype(deft) => deft.loc.clone(), HelperForm::Defconstant(defc) => defc.loc.clone(), HelperForm::Defmacro(mac) => mac.loc.clone(), HelperForm::Defun(_, defun) => defun.loc.clone(), @@ -668,6 +728,22 @@ impl HelperForm { /// be re-parsed if needed. pub fn to_sexp(&self) -> Rc { match self { + HelperForm::Deftype(deft) => { + let mut result_vec = vec![ + Rc::new(SExp::atom_from_string(deft.loc.clone(), "deftype")), + Rc::new(SExp::Atom(deft.loc.clone(), deft.name.clone())), + ]; + + for a in deft.args.iter() { + result_vec.push(Rc::new(a.to_sexp())); + } + + if let Some(ty) = &deft.ty { + result_vec.push(Rc::new(ty.to_sexp())); + } + + Rc::new(list_to_cons(deft.loc.clone(), &result_vec)) + } HelperForm::Defconstant(defc) => match defc.kind { ConstantKind::Simple => Rc::new(list_to_cons( defc.loc.clone(), diff --git a/src/compiler/evaluate.rs b/src/compiler/evaluate.rs index 29100b5b..64dc4024 100644 --- a/src/compiler/evaluate.rs +++ b/src/compiler/evaluate.rs @@ -2,14 +2,13 @@ use std::borrow::Borrow; use std::collections::{HashMap, HashSet}; use std::rc::Rc; -use num_bigint::ToBigInt; - use clvm_rs::allocator::Allocator; +use num_bigint::ToBigInt; use crate::classic::clvm::__type_compatibility__::{bi_one, bi_zero}; use crate::classic::clvm_tools::stages::stage_0::TRunProgram; -use crate::compiler::clvm::{run, truthy}; +use crate::compiler::clvm::run; use crate::compiler::codegen::{codegen, hoist_assign_form}; use crate::compiler::compiler::is_at_capture; use crate::compiler::comptypes::{ @@ -19,14 +18,14 @@ use crate::compiler::comptypes::{ use crate::compiler::frontend::frontend; use crate::compiler::optimize::get_optimizer; use crate::compiler::runtypes::RunFailure; -use crate::compiler::sexp::SExp; +use crate::compiler::sexp::{enlist, SExp}; use crate::compiler::srcloc::Srcloc; use crate::compiler::stackvisit::{HasDepthLimit, VisitedMarker}; use crate::compiler::CompileContextWrapper; use crate::util::{number_from_u8, u8_from_number, Number}; const PRIM_RUN_LIMIT: usize = 1000000; -pub const EVAL_STACK_LIMIT: usize = 200; +pub const EVAL_STACK_LIMIT: usize = 190; // Stack depth checker. #[derive(Clone, Debug, Default)] @@ -51,7 +50,7 @@ trait VisitedInfoAccess { impl<'info> VisitedInfoAccess for VisitedMarker<'info, VisitedInfo> { fn get_function(&mut self, name: &[u8]) -> Option> { - if let Some(ref mut info) = self.info { + if let Some(info) = self.info.as_ref() { info.functions.get(name).cloned() } else { None @@ -61,6 +60,8 @@ impl<'info> VisitedInfoAccess for VisitedMarker<'info, VisitedInfo> { fn insert_function(&mut self, name: Vec, body: Rc) { if let Some(ref mut info) = self.info { info.functions.insert(name, body); + } else { + todo!(); } } } @@ -73,12 +74,50 @@ pub struct LambdaApply { // Frontend evaluator based on my fuzzer representation and direct interpreter of // that. + #[derive(Debug)] pub enum ArgInputs { Whole(Rc), Pair(Rc, Rc), } +/// EvalExtension provides internal capabilities to the evaluator that function +/// as extra primitives. They work entirely at the semantic layer of chialisp +/// and are preferred compared to CLVM primitives. These operate on BodyForm +/// so they have some ability to work on the semantics of chialisp values in +/// addition to reified values. +/// +/// These provide the primitive, value aware capabilities to the defmac system +/// which runs entirely in evaluator space. This is done because evaluator deals +/// in high level frontend values... Rather than having integers, symbols and +/// strings all crushed into a single atom value space, these observe the +/// differences and are able to judge and convert them in ways the user specifies. +/// +/// This allows these macros to pass on programs to the chialisp compiler that +/// are symbol and constant aware; it's able to write (for example) a matcher +/// that takes lists of mixed symbols and constants, isolate each and produce +/// lists of let bindings and match checks that pick out each. Since atoms are +/// passed on when appropriate vs constants and such, we can have macros produce +/// code and be completely certain that any atom landing in the chialisp compiler +/// was intended to be bound in some way and return an error if it isn't, having +/// the result plainly be an error if not. +/// +/// I also anticipate using EvalExtensions to analyze and control code shrinking +/// during some kinds of optimization. +pub trait EvalExtension { + #[allow(clippy::too_many_arguments)] + fn try_eval( + &self, + evaluator: &Evaluator, + prog_args: Rc, + env: &HashMap, Rc>, + loc: &Srcloc, + name: &[u8], + args: &[Rc], + body: Rc, + ) -> Result>, CompileErr>; +} + /// Evaluator is an object that simplifies expressions, given the helpers /// (helpers are forms that are reusable parts of programs, such as defconst, /// defun or defmacro) from a program. In the simplest form, it can be used to @@ -95,6 +134,7 @@ pub enum ArgInputs { /// whether input parameters to the program as a whole are used in the program's /// eventual results. The simplification it does is general eta conversion with /// some other local transformations thrown in. +#[derive(Clone)] pub struct Evaluator { opts: Rc, runner: Rc, @@ -102,6 +142,7 @@ pub struct Evaluator { helpers: Vec, mash_conditions: bool, ignore_exn: bool, + disable_calls: bool, } fn select_helper(bindings: &[HelperForm], name: &[u8]) -> Option { @@ -197,7 +238,7 @@ pub fn is_primitive(expr: &BodyForm) -> bool { ) } -fn make_operator1(l: &Srcloc, op: String, arg: Rc) -> BodyForm { +pub fn make_operator1(l: &Srcloc, op: String, arg: Rc) -> BodyForm { BodyForm::Call( l.clone(), vec![ @@ -208,7 +249,7 @@ fn make_operator1(l: &Srcloc, op: String, arg: Rc) -> BodyForm { ) } -fn make_operator2(l: &Srcloc, op: String, arg1: Rc, arg2: Rc) -> BodyForm { +pub fn make_operator2(l: &Srcloc, op: String, arg1: Rc, arg2: Rc) -> BodyForm { BodyForm::Call( l.clone(), vec![ @@ -336,16 +377,6 @@ fn arg_inputs_primitive(arginputs: Rc) -> bool { } } -fn decons_args(formed_tail: Rc) -> ArgInputs { - if let Some((head, tail)) = match_cons(formed_tail.clone()) { - let arg_head = decons_args(head.clone()); - let arg_tail = decons_args(tail.clone()); - ArgInputs::Pair(Rc::new(arg_head), Rc::new(arg_tail)) - } else { - ArgInputs::Whole(formed_tail) - } -} - pub fn build_argument_captures( l: &Srcloc, arguments_to_convert: &[Rc], @@ -353,7 +384,7 @@ pub fn build_argument_captures( args: Rc, ) -> Result, Rc>, CompileErr> { let formed_tail = tail.unwrap_or_else(|| Rc::new(BodyForm::Quoted(SExp::Nil(l.clone())))); - let mut formed_arguments = decons_args(formed_tail); + let mut formed_arguments = ArgInputs::Whole(formed_tail); for i_reverse in 0..arguments_to_convert.len() { let i = arguments_to_convert.len() - i_reverse - 1; @@ -673,27 +704,6 @@ pub fn eval_dont_expand_let(inline_hint: &Option) -> bool { matches!(inline_hint, Some(LetFormInlineHint::NonInline(_))) } -pub fn filter_capture_args(args: Rc, name_map: &HashMap, Rc>) -> Rc { - match args.borrow() { - SExp::Cons(l, a, b) => { - let a_filtered = filter_capture_args(a.clone(), name_map); - let b_filtered = filter_capture_args(b.clone(), name_map); - if !truthy(a_filtered.clone()) && !truthy(b_filtered.clone()) { - return Rc::new(SExp::Nil(l.clone())); - } - Rc::new(SExp::Cons(l.clone(), a_filtered, b_filtered)) - } - SExp::Atom(l, n) => { - if name_map.contains_key(n) { - Rc::new(SExp::Nil(l.clone())) - } else { - args - } - } - _ => Rc::new(SExp::Nil(args.loc())), - } -} - impl<'info> Evaluator { pub fn new( opts: Rc, @@ -707,17 +717,34 @@ impl<'info> Evaluator { helpers, mash_conditions: false, ignore_exn: false, + disable_calls: false, } } pub fn mash_conditions(&self) -> Self { Evaluator { - opts: self.opts.clone(), - runner: self.runner.clone(), - prims: self.prims.clone(), - helpers: self.helpers.clone(), mash_conditions: true, ignore_exn: true, + disable_calls: false, + ..self.clone() + } + } + + pub fn disable_calls(&self) -> Self { + Evaluator { + mash_conditions: false, + ignore_exn: true, + disable_calls: true, + ..self.clone() + } + } + + pub fn enable_calls_for_macro(&self) -> Self { + Evaluator { + mash_conditions: false, + ignore_exn: true, + disable_calls: false, + ..self.clone() } } @@ -755,16 +782,8 @@ impl<'info> Evaluator { )), )); - frontend(self.opts.clone(), &[frontend_macro_input]).and_then(|program| { - self.shrink_bodyform_visited( - allocator, - visited, - prog_args.clone(), - env, - program.exp, - false, - ) - }) + let program = frontend(self.opts.clone(), &[frontend_macro_input])?; + self.shrink_bodyform_visited(allocator, visited, prog_args, env, program.exp, false) } else { promote_program_to_bodyform( macro_expansion.to_sexp(), @@ -825,7 +844,6 @@ impl<'info> Evaluator { only_inline: bool, ) -> Result, CompileErr> { let mut lambda_env = env.clone(); - // Finish eta-expansion. // We're carrying an enriched environment which we can use to enrich @@ -863,11 +881,39 @@ impl<'info> Evaluator { ) } + fn defmac_ordering(&self) -> bool { + let dialect = self.opts.dialect(); + dialect.strict || dialect.stepping.unwrap_or(21) > 22 + } + + fn make_com_module(&self, l: &Srcloc, prog_args: Rc, body: Rc) -> Rc { + let end_of_list = if self.defmac_ordering() { + let mut mod_list: Vec> = self.helpers.iter().map(|h| h.to_sexp()).collect(); + mod_list.push(body); + Rc::new(enlist(l.clone(), &mod_list)) + } else { + let mut end_of_list = + Rc::new(SExp::Cons(l.clone(), body, Rc::new(SExp::Nil(l.clone())))); + + for h in self.helpers.iter() { + end_of_list = Rc::new(SExp::Cons(l.clone(), h.to_sexp(), end_of_list)); + } + + end_of_list + }; + + Rc::new(SExp::Cons( + l.clone(), + Rc::new(SExp::Atom(l.clone(), "mod".as_bytes().to_vec())), + Rc::new(SExp::Cons(l.clone(), prog_args, end_of_list)), + )) + } + #[allow(clippy::too_many_arguments)] fn invoke_primitive( &self, allocator: &mut Allocator, - visited_: &'_ mut VisitedMarker<'info, VisitedInfo>, + visited_: &'info mut VisitedMarker<'_, VisitedInfo>, call: &CallSpec, prog_args: Rc, arguments_to_convert: &[Rc], @@ -876,150 +922,131 @@ impl<'info> Evaluator { ) -> Result, CompileErr> { let mut all_primitive = true; let mut target_vec: Vec> = call.args.to_owned(); - let mut visited = VisitedMarker::again(call.loc.clone(), visited_)?; + let mut visited = VisitedMarker::again(call.original.loc(), visited_)?; - if call.name == "@".as_bytes() { + if call.name == b"@" || call.name == b"@*env*" { // Synthesize the environment for this function Ok(Rc::new(BodyForm::Quoted(SExp::Cons( call.loc.clone(), Rc::new(SExp::Nil(call.loc.clone())), prog_args, )))) - } else if call.name == "com".as_bytes() { - let mut end_of_list = Rc::new(SExp::Cons( - call.loc.clone(), - arguments_to_convert[0].to_sexp(), - Rc::new(SExp::Nil(call.loc.clone())), - )); - - for h in self.helpers.iter() { - end_of_list = Rc::new(SExp::Cons(call.loc.clone(), h.to_sexp(), end_of_list)) - } - - let use_body = SExp::Cons( - call.loc.clone(), - Rc::new(SExp::Atom(call.loc.clone(), "mod".as_bytes().to_vec())), - Rc::new(SExp::Cons(call.loc.clone(), prog_args, end_of_list)), - ); - - let compiled = self.compile_code(allocator, false, Rc::new(use_body))?; + } else if call.name == b"com" { + let use_body = + self.make_com_module(&call.loc, prog_args, arguments_to_convert[0].to_sexp()); + let compiled = self.compile_code(allocator, false, use_body)?; let compiled_borrowed: &SExp = compiled.borrow(); Ok(Rc::new(BodyForm::Quoted(compiled_borrowed.clone()))) - } else { - let pres = self - .lookup_prim(call.loc.clone(), call.name) - .map(|prim| { - // Reduce all arguments. - let mut converted_args = SExp::Nil(call.loc.clone()); - - for i_reverse in 0..arguments_to_convert.len() { - let i = arguments_to_convert.len() - i_reverse - 1; - let shrunk = self.shrink_bodyform_visited( - allocator, - &mut visited, - prog_args.clone(), - env, - arguments_to_convert[i].clone(), - only_inline, - )?; + } else if let Some(prim) = self.lookup_prim(call.loc.clone(), call.name) { + // Reduce all arguments. + let mut converted_args = SExp::Nil(call.loc.clone()); + + for i_reverse in 0..arguments_to_convert.len() { + let i = arguments_to_convert.len() - i_reverse - 1; + let shrunk = self.shrink_bodyform_visited( + allocator, + &mut visited, + prog_args.clone(), + env, + arguments_to_convert[i].clone(), + only_inline, + )?; - target_vec[i + 1] = shrunk.clone(); + target_vec[i + 1] = shrunk.clone(); - if !arg_inputs_primitive(Rc::new(ArgInputs::Whole(shrunk.clone()))) { - all_primitive = false; - } + if !arg_inputs_primitive(Rc::new(ArgInputs::Whole(shrunk.clone()))) { + all_primitive = false; + } - converted_args = - SExp::Cons(call.loc.clone(), shrunk.to_sexp(), Rc::new(converted_args)); - } + converted_args = + SExp::Cons(call.loc.clone(), shrunk.to_sexp(), Rc::new(converted_args)); + } - if all_primitive { - match self.run_prim( - allocator, - call.loc.clone(), - make_prim_call(call.loc.clone(), prim, Rc::new(converted_args)), - Rc::new(SExp::Nil(call.loc.clone())), - ) { - Ok(res) => Ok(res), - Err(e) => { - if only_inline || self.ignore_exn { - Ok(Rc::new(BodyForm::Call( - call.loc.clone(), - target_vec.clone(), - None, - ))) - } else { - Err(e) - } - } + if all_primitive { + match self.run_prim( + allocator, + call.loc.clone(), + make_prim_call(call.loc.clone(), prim, Rc::new(converted_args)), + Rc::new(SExp::Nil(call.loc.clone())), + ) { + Ok(res) => Ok(res), + Err(e) => { + if only_inline || self.ignore_exn { + Ok(Rc::new(BodyForm::Call( + call.loc.clone(), + target_vec.clone(), + None, + ))) + } else { + Err(e) } - } else if let Some(applied_lambda) = self.is_lambda_apply( - allocator, - &mut visited, - prog_args.clone(), - env, - &target_vec, - only_inline, - )? { - self.do_lambda_apply( - allocator, - &mut visited, - prog_args.clone(), - env, - &applied_lambda, - only_inline, - ) - } else { - // Since this is a primitive, there's no tail transform. - let reformed = - BodyForm::Call(call.loc.clone(), target_vec.clone(), call.tail.clone()); - self.chase_apply(allocator, &mut visited, Rc::new(reformed)) } - }) - .unwrap_or_else(|| { - // Build SExp arguments for external call or - // return the unevaluated chunk with minimized - // arguments. - Err(CompileErr( - call.loc.clone(), - format!( - "Don't yet support this call type {} {:?}", - call.original.to_sexp(), - call.original - ), - )) - })?; - Ok(pres) + } + } else if let Some(applied_lambda) = self.is_lambda_apply( + allocator, + &mut visited, + prog_args.clone(), + env, + &target_vec, + only_inline, + )? { + self.do_lambda_apply( + allocator, + &mut visited, + prog_args, + env, + &applied_lambda, + only_inline, + ) + } else { + let reformed = + BodyForm::Call(call.loc.clone(), target_vec.clone(), call.tail.clone()); + self.chase_apply(allocator, &mut visited, Rc::new(reformed)) + } + } else { + // Build SExp arguments for external call or + // return the unevaluated chunk with minimized + // arguments. + Err(CompileErr( + call.loc.clone(), + format!( + "Don't yet support this call type {} {:?}", + call.original.to_sexp(), + call.original + ), + )) } } fn continue_apply( &self, allocator: &mut Allocator, - visited: &'_ mut VisitedMarker<'info, VisitedInfo>, + visited_: &'_ mut VisitedMarker<'info, VisitedInfo>, env: Rc, run_program: Rc, ) -> Result, CompileErr> { + let mut visited = VisitedMarker::again(run_program.loc(), visited_)?; let bindings = HashMap::new(); let program = promote_program_to_bodyform(run_program.clone(), env)?; let apply_result = self.shrink_bodyform_visited( allocator, - visited, + &mut visited, Rc::new(SExp::Nil(run_program.loc())), &bindings, program, false, )?; - self.chase_apply(allocator, visited, apply_result) + self.chase_apply(allocator, &mut visited, apply_result) } fn do_mash_condition( &self, allocator: &mut Allocator, - visited: &'_ mut VisitedMarker<'info, VisitedInfo>, + visited_: &'_ mut VisitedMarker<'info, VisitedInfo>, maybe_condition: Rc, env: Rc, ) -> Result, CompileErr> { + let mut visited = VisitedMarker::again(maybe_condition.loc(), visited_)?; // The inner part could be an 'i' which we know passes on // one of the two conditional arguments. This was an apply so // we can distribute over the conditional arguments. @@ -1044,7 +1071,7 @@ impl<'info> Evaluator { let surrogate_apply_true = self.chase_apply( allocator, - visited, + &mut visited, Rc::new(BodyForm::Call( iftrue.loc(), vec![apply_head.clone(), iftrue.clone(), env.clone()], @@ -1054,7 +1081,7 @@ impl<'info> Evaluator { let surrogate_apply_false = self.chase_apply( allocator, - visited, + &mut visited, Rc::new(BodyForm::Call( iffalse.loc(), vec![apply_head, iffalse.clone(), env], @@ -1086,19 +1113,29 @@ impl<'info> Evaluator { fn chase_apply( &self, allocator: &mut Allocator, - visited: &'_ mut VisitedMarker<'info, VisitedInfo>, + visited_: &'_ mut VisitedMarker<'info, VisitedInfo>, body: Rc, ) -> Result, CompileErr> { + let mut visited = VisitedMarker::again(body.loc(), visited_)?; + // Matching a primitive so no tail argument. if let BodyForm::Call(l, vec, None) = body.borrow() { if is_apply_atom(vec[0].to_sexp()) { if let Ok(run_program) = dequote(l.clone(), vec[1].clone()) { - return self.continue_apply(allocator, visited, vec[2].clone(), run_program); + return self.continue_apply( + allocator, + &mut visited, + vec[2].clone(), + run_program, + ); } if self.mash_conditions { - if let Ok(mashed) = - self.do_mash_condition(allocator, visited, vec[1].clone(), vec[2].clone()) - { + if let Ok(mashed) = self.do_mash_condition( + allocator, + &mut visited, + vec[1].clone(), + vec[2].clone(), + ) { return Ok(mashed); } } @@ -1113,6 +1150,7 @@ impl<'info> Evaluator { &self, allocator: &mut Allocator, visited: &'_ mut VisitedMarker<'info, VisitedInfo>, + head_expr: Rc, call: &CallSpec, prog_args: Rc, arguments_to_convert: &[Rc], @@ -1144,23 +1182,43 @@ impl<'info> Evaluator { return Ok(call.original.clone()); } - let translated_tail = if let Some(t) = call.tail.as_ref() { - Some(self.shrink_bodyform_visited( - allocator, - visited, - prog_args.clone(), - env, - t.clone(), - only_inline, - )?) - } else { - None - }; + if self.disable_calls { + let mut call_vec = vec![head_expr]; + for a in arguments_to_convert.iter() { + call_vec.push(self.shrink_bodyform_visited( + allocator, + visited, + prog_args.clone(), + env, + a.clone(), + only_inline, + )?); + } + + let converted_tail = if let Some(t) = call.tail.as_ref() { + Some(self.shrink_bodyform_visited( + allocator, + visited, + prog_args, + env, + t.clone(), + only_inline, + )?) + } else { + None + }; + + return Ok(Rc::new(BodyForm::Call( + call.loc.clone(), + call_vec, + converted_tail, + ))); + } let argument_captures_untranslated = build_argument_captures( - &call.loc.clone(), + &call.loc, arguments_to_convert, - translated_tail.clone(), + call.tail.clone(), defun.args.clone(), )?; @@ -1189,8 +1247,8 @@ impl<'info> Evaluator { only_inline, ) } - _ => self - .invoke_primitive( + _ => { + let invoked = self.invoke_primitive( allocator, visited, call, @@ -1198,8 +1256,9 @@ impl<'info> Evaluator { arguments_to_convert, env, only_inline, - ) - .and_then(|res| self.chase_apply(allocator, visited, res)), + )?; + self.chase_apply(allocator, visited, invoked) + } } } @@ -1212,63 +1271,19 @@ impl<'info> Evaluator { ldata: &LambdaData, only_inline: bool, ) -> Result, CompileErr> { - if !truthy(ldata.capture_args.clone()) { - return Ok(Rc::new(BodyForm::Lambda(Box::new(ldata.clone())))); - } - // Rewrite the captures based on what we know at the call site. let new_captures = self.shrink_bodyform_visited( allocator, visited, - prog_args.clone(), + prog_args, env, ldata.captures.clone(), only_inline, )?; - // Break up and make binding map. - let deconsed_args = decons_args(new_captures.clone()); - let mut arg_captures = HashMap::new(); - create_argument_captures( - &mut arg_captures, - &deconsed_args, - ldata.capture_args.clone(), - )?; - - // Filter out elements that are not interpretable yet. - let mut interpretable_captures = HashMap::new(); - for (n, v) in arg_captures.iter() { - if dequote(v.loc(), v.clone()).is_ok() { - // This capture has already been made into a literal. - // We will substitute it in the lambda body and remove it - // from the capture set. - interpretable_captures.insert(n.clone(), v.clone()); - } - } - - let combined_args = Rc::new(SExp::Cons( - ldata.loc.clone(), - ldata.capture_args.clone(), - ldata.args.clone(), - )); - - // Eliminate the captures via beta substituion. - let simplified_body = self.shrink_bodyform_visited( - allocator, - visited, - combined_args.clone(), - &interpretable_captures, - ldata.body.clone(), - only_inline, - )?; - - let new_capture_args = - filter_capture_args(ldata.capture_args.clone(), &interpretable_captures); + // This is the first part of eta-conversion. Ok(Rc::new(BodyForm::Lambda(Box::new(LambdaData { - args: ldata.args.clone(), - capture_args: new_capture_args, captures: new_captures, - body: simplified_body, ..ldata.clone() })))) } @@ -1294,6 +1309,7 @@ impl<'info> Evaluator { args: function.args.clone(), helpers: self.helpers.clone(), exp: function.body.clone(), + ty: function.ty.clone(), }, )) } @@ -1378,7 +1394,7 @@ impl<'info> Evaluator { } BodyForm::Quoted(_) => Ok(body.clone()), BodyForm::Value(SExp::Atom(l, name)) => { - if name == &"@".as_bytes().to_vec() { + if name == &"@".as_bytes().to_vec() || name == &"@*env*".as_bytes().to_vec() { let literal_args = synthesize_args(prog_args.clone(), env)?; self.shrink_bodyform_visited( allocator, @@ -1397,41 +1413,33 @@ impl<'info> Evaluator { self.create_mod_for_fun(l, function.borrow()), only_inline, ) + } else if let Some(x) = env.get(name) { + if reflex_capture(name, x.clone()) { + Ok(x.clone()) + } else { + self.shrink_bodyform_visited( + allocator, + &mut visited, + prog_args, + env, + x.clone(), + only_inline, + ) + } + } else if let Some(x) = self.get_constant(name) { + self.shrink_bodyform_visited( + allocator, + &mut visited, + prog_args, + env, + x, + only_inline, + ) } else { - env.get(name) - .map(|x| { - if reflex_capture(name, x.clone()) { - Ok(x.clone()) - } else { - self.shrink_bodyform_visited( - allocator, - &mut visited, - prog_args.clone(), - env, - x.clone(), - only_inline, - ) - } - }) - .unwrap_or_else(|| { - self.get_constant(name) - .map(|x| { - self.shrink_bodyform_visited( - allocator, - &mut visited, - prog_args.clone(), - env, - x, - only_inline, - ) - }) - .unwrap_or_else(|| { - Ok(Rc::new(BodyForm::Value(SExp::Atom( - l.clone(), - name.clone(), - )))) - }) - }) + Ok(Rc::new(BodyForm::Value(SExp::Atom( + l.clone(), + name.clone(), + )))) } } BodyForm::Value(v) => Ok(Rc::new(BodyForm::Quoted(v.clone()))), @@ -1443,6 +1451,8 @@ impl<'info> Evaluator { )); } + // Allow us to punt all calls to functions, which preserved type + // signatures for type checking. let head_expr = parts[0].clone(); let arguments_to_convert: Vec> = parts.iter().skip(1).cloned().collect(); @@ -1451,6 +1461,7 @@ impl<'info> Evaluator { BodyForm::Value(SExp::Atom(_call_loc, call_name)) => self.handle_invoke( allocator, &mut visited, + head_expr.clone(), &CallSpec { loc: l.clone(), name: call_name, @@ -1466,6 +1477,7 @@ impl<'info> Evaluator { BodyForm::Value(SExp::Integer(_call_loc, call_int)) => self.handle_invoke( allocator, &mut visited, + head_expr.clone(), &CallSpec { loc: l.clone(), name: &u8_from_number(call_int.clone()), @@ -1484,10 +1496,10 @@ impl<'info> Evaluator { )), } } - BodyForm::Mod(l, program) => { + BodyForm::Mod(_, program) => { // A mod form yields the compiled code. let mut symbols = HashMap::new(); - let optimizer = get_optimizer(l, self.opts.clone())?; + let optimizer = get_optimizer(&program.loc(), self.opts.clone())?; let mut context_wrapper = CompileContextWrapper::new( allocator, self.runner.clone(), @@ -1642,7 +1654,7 @@ impl<'info> Evaluator { // primitive. let updated_opts = self .opts - .set_stdenv(!in_defun) + .set_stdenv(!in_defun && !self.opts.dialect().strict) .set_in_defun(in_defun) .set_frontend_opt(false); diff --git a/src/compiler/frontend.rs b/src/compiler/frontend.rs index 970b70cb..4c5115ea 100644 --- a/src/compiler/frontend.rs +++ b/src/compiler/frontend.rs @@ -3,18 +3,24 @@ use std::collections::HashMap; use std::collections::HashSet; use std::rc::Rc; -use crate::classic::clvm::__type_compatibility__::bi_one; +use log::debug; +use num_bigint::ToBigInt; + +use crate::classic::clvm::__type_compatibility__::{bi_one, bi_zero}; use crate::compiler::comptypes::{ - list_to_cons, ArgsAndTail, Binding, BindingPattern, BodyForm, CompileErr, CompileForm, - CompilerOpts, ConstantKind, DefconstData, DefmacData, DefunData, HelperForm, IncludeDesc, - LetData, LetFormInlineHint, LetFormKind, ModAccum, + list_to_cons, ArgsAndTail, Binding, BindingPattern, BodyForm, ChiaType, CompileErr, + CompileForm, CompilerOpts, ConstantKind, DefconstData, DefmacData, DeftypeData, DefunData, + HelperForm, IncludeDesc, LetData, LetFormInlineHint, LetFormKind, ModAccum, StructDef, + StructMember, SyntheticType, TypeAnnoKind, }; use crate::compiler::lambda::handle_lambda; use crate::compiler::preprocessor::preprocess; -use crate::compiler::rename::rename_children_compileform; +use crate::compiler::rename::{rename_assign_bindings, rename_children_compileform}; use crate::compiler::sexp::{decode_string, enlist, SExp}; -use crate::compiler::srcloc::Srcloc; -use crate::util::u8_from_number; +use crate::compiler::srcloc::{HasLoc, Srcloc}; +use crate::compiler::typecheck::{parse_type_sexp, parse_type_var}; +use crate::compiler::types::ast::{Polytype, Type, TypeVar}; +use crate::util::{u8_from_number, Number}; pub fn collect_used_names_sexp(body: Rc) -> Vec> { match body.borrow() { @@ -80,6 +86,7 @@ fn collect_used_names_bodyform(body: &BodyForm) -> Vec> { fn collect_used_names_helperform(body: &HelperForm) -> Vec> { match body { + HelperForm::Deftype(_) => Vec::new(), HelperForm::Defconstant(defc) => collect_used_names_bodyform(defc.body.borrow()), HelperForm::Defmacro(mac) => { let mut res = collect_used_names_compileform(mac.program.borrow()); @@ -256,17 +263,12 @@ fn make_let_bindings( body: Rc, ) -> Result>, CompileErr> { let err = Err(CompileErr(body.loc(), format!("Bad binding tail {body:?}"))); - let do_atomize = if !opts.dialect().strict { - |a: &SExp| -> SExp { a.atomize() } - } else { - |a: &SExp| -> SExp { a.clone() } - }; match body.borrow() { SExp::Nil(_) => Ok(vec![]), SExp::Cons(_, head, tl) => head .proper_list() .filter(|x| x.len() == 2) - .map(|x| match (do_atomize(&x[0]), &x[1]) { + .map(|x| match (x[0].clone(), &x[1]) { (SExp::Atom(l, name), expr) => { let compiled_body = compile_bodyform(opts.clone(), Rc::new(expr.clone()))?; let mut result = Vec::new(); @@ -301,6 +303,10 @@ pub fn make_provides_set(provides_set: &mut HashSet>, body_sexp: Rc) -> bool { + opts.dialect().stepping.unwrap_or(0) > 22 +} + fn handle_assign_form( opts: Rc, l: Srcloc, @@ -320,7 +326,6 @@ fn handle_assign_form( for idx in (0..(v.len() - 1) / 2).map(|idx| idx * 2) { let destructure_pattern = Rc::new(v[idx].clone()); let binding_body = compile_bodyform(opts.clone(), Rc::new(v[idx + 1].clone()))?; - // Ensure bindings aren't duplicated as we won't be able to // guarantee their order during toposort. let mut this_provides = HashSet::new(); @@ -344,12 +349,19 @@ fn handle_assign_form( })); } - let compiled_body = compile_bodyform(opts.clone(), Rc::new(v[v.len() - 1].clone()))?; + let mut compiled_body = compile_bodyform(opts.clone(), Rc::new(v[v.len() - 1].clone()))?; // We don't need to do much if there were no bindings. if bindings.is_empty() { return Ok(compiled_body); } + if at_or_above_23(opts) { + let (new_compiled_body, new_bindings) = + rename_assign_bindings(&l, &bindings, Rc::new(compiled_body))?; + compiled_body = new_compiled_body; + bindings = new_bindings; + }; + // Return a precise representation of this assign while storing up the work // we did breaking it down. Ok(BodyForm::Let( @@ -451,7 +463,7 @@ pub fn compile_bodyform( Some(LetFormInlineHint::NoChoice) }, ) - } else if *atom_name == "quote".as_bytes().to_vec() { + } else if *atom_name == b"quote" { if v.len() != 1 { return finish_err("quote"); } @@ -523,6 +535,7 @@ fn compile_defconst( name: name.to_vec(), body: Rc::new(bf), tabled: opts.frontend_opt() || opts.dialect().stepping.unwrap_or(0) > 22, + ty: None, })) } @@ -533,6 +546,7 @@ fn compile_defconstant( kl: Option, name: Vec, body: Rc, + ty: Option, ) -> Result { let body_borrowed: &SExp = body.borrow(); if let SExp::Cons(_, _, _) = body_borrowed { @@ -544,6 +558,7 @@ fn compile_defconstant( name: name.to_vec(), body: Rc::new(BodyForm::Value(body_borrowed.clone())), tabled: false, + ty, })) } else { compile_bodyform(opts, body).map(|bf| { @@ -555,6 +570,7 @@ fn compile_defconstant( name: name.to_vec(), body: Rc::new(bf), tabled: false, + ty, }) }) } @@ -580,7 +596,11 @@ pub struct CompileDefun { pub body: Rc, } -fn compile_defun(opts: Rc, data: CompileDefun) -> Result { +fn compile_defun( + opts: Rc, + data: CompileDefun, + ty: Option, +) -> Result { let mut take_form = data.body.clone(); if let SExp::Cons(_, f, _r) = data.body.borrow() { @@ -598,6 +618,7 @@ fn compile_defun(opts: Rc, data: CompileDefun) -> Result, @@ -638,15 +664,16 @@ struct OpName4Match { name: Vec, args: Rc, body: Rc, + orig: Vec, + ty: Option<(TypeKind, Rc)>, } -#[allow(clippy::type_complexity)] fn match_op_name_4(pl: &[SExp]) -> Option { if pl.is_empty() { return None; } - match &pl[0] { + match &pl[0].atomize() { SExp::Atom(l, op_name) => { if pl.len() < 3 { return Some(OpName4Match { @@ -656,31 +683,53 @@ fn match_op_name_4(pl: &[SExp]) -> Option { name: Vec::new(), args: Rc::new(SExp::Nil(l.clone())), body: Rc::new(SExp::Nil(l.clone())), + orig: pl.to_owned(), + ty: None, }); } - match &pl[1] { + match &pl[1].atomize() { SExp::Atom(ll, name) => { + let mut tail_idx = 3; let mut tail_list = Vec::new(); - for elt in pl.iter().skip(3) { + let mut type_anno = None; + if pl.len() > 3 { + if let SExp::Atom(_, colon) = &pl[3] { + if *colon == vec![b':'] { + // Type annotation + tail_idx += 2; + type_anno = Some((TypeKind::Colon, Rc::new(pl[4].clone()))); + } else if *colon == vec![b'-', b'>'] { + // Type annotation + tail_idx += 2; + type_anno = Some((TypeKind::Arrow, Rc::new(pl[4].clone()))); + } + } + } + for elt in pl.iter().skip(tail_idx) { tail_list.push(Rc::new(elt.clone())); } + Some(OpName4Match { - opl: l.clone(), - op_name: op_name.clone(), nl: ll.clone(), + op_name: op_name.clone(), + opl: l.clone(), name: name.clone(), args: Rc::new(pl[2].clone()), body: Rc::new(enlist(l.clone(), &tail_list)), + orig: pl.to_owned(), + ty: type_anno, }) } _ => Some(OpName4Match { + nl: pl[0].loc(), opl: l.clone(), op_name: op_name.clone(), - nl: pl[1].loc(), name: Vec::new(), args: Rc::new(SExp::Nil(l.clone())), body: Rc::new(SExp::Nil(l.clone())), + orig: pl.to_owned(), + ty: None, }), } } @@ -688,35 +737,463 @@ fn match_op_name_4(pl: &[SExp]) -> Option { } } +fn extract_type_variables_from_forall_stack(tvars: &mut Vec, t: &Polytype) -> Polytype { + if let Type::TForall(v, t1) = t { + tvars.push(v.clone()); + extract_type_variables_from_forall_stack(tvars, t1.borrow()) + } else { + t.clone() + } +} + +pub struct ArgTypeResult { + pub stripped_args: Rc, + pub arg_names: Vec>, + pub individual_types: HashMap, Polytype>, + pub individual_paths: HashMap, Number>, + pub individual_locs: HashMap, Srcloc>, + pub whole_args: Polytype, +} + +#[allow(clippy::too_many_arguments)] +fn recover_arg_type_inner( + arg_names: &mut Vec>, + individual_types: &mut HashMap, Polytype>, + individual_paths: &mut HashMap, Number>, + individual_locs: &mut HashMap, Srcloc>, + depth: Number, + path: Number, + args: Rc, + have_anno: bool, +) -> Result<(bool, Rc, Polytype), CompileErr> { + match &args.atomize() { + SExp::Nil(l) => Ok((have_anno, args.clone(), Type::TUnit(l.clone()))), + SExp::Atom(l, n) => { + arg_names.push(n.clone()); + individual_types.insert(n.clone(), Type::TAny(l.clone())); + individual_paths.insert(n.clone(), depth + path); + individual_locs.insert(n.clone(), l.clone()); + Ok((false, args.clone(), Type::TAny(l.clone()))) + } + SExp::Cons(l, a, b) => { + // There are a few cases: + // (normal destructuring) + // (@ name sub) + // (@ name sub : ty) + // (X : ty) + // We want to catch the final case and pass through its unannotated + // counterpart. + let next_depth = depth.clone() * 2_u32.to_bigint().unwrap(); + if let Some(lst) = args.proper_list() { + // Dive in + if lst.len() == 5 { + if let (SExp::Atom(l, n), SExp::Atom(_l2, n2)) = + (&lst[0].atomize(), &lst[3].atomize()) + { + if n == &vec![b'@'] && n2 == &vec![b':'] { + // At capture with annotation + return Err(CompileErr(l.clone(), "An at-capture with a type alias is currently unsupported. A struct can be used instead.".to_string())); + }; + }; + } else if lst.len() == 3 { + if let (SExp::Atom(l0, n0), SExp::Atom(_l1, n1)) = + (&lst[0].atomize(), &lst[1].atomize()) + { + if n1 == &vec![b':'] { + // Name with annotation + let ty = parse_type_sexp(Rc::new(lst[2].clone()))?; + arg_names.push(n0.clone()); + individual_types.insert(n0.clone(), ty.clone()); + individual_paths.insert(n0.clone(), depth + path); + individual_locs.insert(n0.clone(), l0.clone()); + return Ok((true, Rc::new(lst[0].clone()), ty)); + }; + }; + } + } + + let (got_ty_a, stripped_a, ty_a) = recover_arg_type_inner( + arg_names, + individual_types, + individual_paths, + individual_locs, + next_depth.clone(), + path.clone(), + a.clone(), + have_anno, + )?; + let (got_ty_b, stripped_b, ty_b) = recover_arg_type_inner( + arg_names, + individual_types, + individual_paths, + individual_locs, + next_depth, + path + depth, + b.clone(), + have_anno, + )?; + Ok(( + got_ty_a || got_ty_b, + Rc::new(SExp::Cons(l.clone(), stripped_a, stripped_b)), + Type::TPair(Rc::new(ty_a), Rc::new(ty_b)), + )) + } + _ => Err(CompileErr( + args.loc(), + "unrecognized argument form".to_string(), + )), + } +} + +pub fn recover_arg_type(args: Rc, always: bool) -> Result, CompileErr> { + let mut arg_names = Vec::new(); + let mut individual_types = HashMap::new(); + let mut individual_paths = HashMap::new(); + let mut individual_locs = HashMap::new(); + let (got_any, stripped, ty) = recover_arg_type_inner( + &mut arg_names, + &mut individual_types, + &mut individual_paths, + &mut individual_locs, + bi_one(), + bi_zero(), + args, + false, + )?; + if got_any || always { + Ok(Some(ArgTypeResult { + arg_names, + stripped_args: stripped, + individual_types, + individual_paths, + individual_locs, + whole_args: ty, + })) + } else { + Ok(None) + } +} + +// Given type recovered argument type and a candidate return type (possibly with +// a forall stack), construct the user's expected function type. If no arg types +// were given, the arg type is Any. If the bottom of the stack is a function +// type, its left hand type is replaced with the given arg type or Any. If it +// isn't a function type, it's promoted to be a function taking the given arg +// type or Any. +fn promote_with_arg_type(argty: &Polytype, funty: &Polytype) -> Polytype { + match funty { + Type::TForall(v, t) => { + Type::TForall(v.clone(), Rc::new(promote_with_arg_type(argty, t.borrow()))) + } + Type::TFun(_t1, t2) => Type::TFun(Rc::new(argty.clone()), t2.clone()), + _ => Type::TFun(Rc::new(argty.clone()), Rc::new(funty.clone())), + } +} + +// Returns None if result_ty is None and there are no type signatures recovered +// from the args. +// +// If the function's type signature is given with a TForall, the type variables +// given will be in scope for the arguments. +// If type arguments are given, the function's type signature must not be given +// as a function type (since all functions are arity-1 in chialisp). The final +// result type will be enriched to include the argument types. +fn augment_fun_type_with_args( + args: Rc, + result_ty: Option, +) -> Result<(Rc, Option), CompileErr> { + if let Some(atr) = recover_arg_type(args.clone(), false)? { + let mut tvars = Vec::new(); + + let actual_result_ty = if let Some(TypeAnnoKind::Arrow(rty)) = result_ty { + extract_type_variables_from_forall_stack(&mut tvars, &rty) + } else if let Some(TypeAnnoKind::Colon(rty)) = result_ty { + let want_rty = extract_type_variables_from_forall_stack(&mut tvars, &rty); + // If it's a function type, we have to give it as "args" + if let Type::TFun(t1, t2) = want_rty { + let t1_borrowed: &Polytype = t1.borrow(); + if t1_borrowed != &Type::TVar(TypeVar("args".to_string(), t1.loc())) { + return Err(CompileErr(t1.loc(), "When arguments are annotated, if the full function type is given, it must be given as (args -> ...). The 'args' type variable will contain the type implied by the individual argument annotations.".to_string())); + } + + let t2_borrowed: &Polytype = t2.borrow(); + t2_borrowed.clone() + } else { + want_rty + } + } else { + Type::TAny(args.loc()) + }; + + Ok(( + atr.stripped_args.clone(), + Some(promote_with_arg_type(&atr.whole_args, &actual_result_ty)), + )) + } else { + // No arg types were given. If a type was given for the result (non-fun) + // use Any -> Any + // else use the whole thing. + Ok(result_ty + .map(|rty| match rty { + TypeAnnoKind::Colon(t) => (args.clone(), Some(t)), + TypeAnnoKind::Arrow(t) => ( + args.clone(), + Some(Type::TFun(Rc::new(Type::TAny(args.loc())), Rc::new(t))), + ), + }) + .unwrap_or_else(|| (args.clone(), None))) + } +} + +fn create_constructor_code(sdef: &StructDef, proto: Rc) -> BodyForm { + match proto.atomize() { + SExp::Atom(l, n) => BodyForm::Value(SExp::Atom(l, n)), + SExp::Cons(l, a, b) => BodyForm::Call( + l.clone(), + vec![ + Rc::new(BodyForm::Value(SExp::Atom(l, b"c*".to_vec()))), + Rc::new(create_constructor_code(sdef, a)), + Rc::new(create_constructor_code(sdef, b)), + ], + None, + ), + _ => BodyForm::Quoted(SExp::Nil(sdef.loc.clone())), + } +} + +fn create_constructor(sdef: &StructDef) -> HelperForm { + let mut access_name = "new_".as_bytes().to_vec(); + access_name.append(&mut sdef.name.clone()); + + // Iterate through the arguments in reverse to build up a linear argument + // list. + // Build up the type list in the same way. + let mut arguments = Rc::new(SExp::Nil(sdef.loc.clone())); + let mut argtype = Type::TUnit(sdef.loc.clone()); + + for m in sdef.members.iter().rev() { + argtype = Type::TPair(Rc::new(m.ty.clone()), Rc::new(argtype)); + arguments = Rc::new(SExp::Cons( + m.loc.clone(), + Rc::new(SExp::Atom(m.loc.clone(), m.name.clone())), + arguments, + )); + } + + let construction = create_constructor_code(sdef, sdef.proto.clone()); + let mut target_ty = Type::TVar(TypeVar(decode_string(&sdef.name), sdef.loc.clone())); + + for a in sdef.vars.iter().rev() { + target_ty = Type::TApp(Rc::new(target_ty), Rc::new(Type::TVar(a.clone()))); + } + + let mut funty = Type::TFun(Rc::new(argtype), Rc::new(target_ty)); + + for a in sdef.vars.iter().rev() { + funty = Type::TForall(a.clone(), Rc::new(funty)); + } + + HelperForm::Defun( + true, + Box::new(DefunData { + kw: None, + nl: sdef.loc.clone(), + loc: sdef.loc.clone(), + name: access_name, + orig_args: arguments.clone(), + args: arguments, + body: Rc::new(construction), + synthetic: Some(SyntheticType::NoInlinePreference), + ty: Some(funty), + }), + ) +} + +pub fn generate_type_helpers(ty: &ChiaType) -> Vec { + match ty { + ChiaType::Abstract(_, _) => vec![], + ChiaType::Struct(sdef) => { + // Construct ((S : )) + let struct_argument = Rc::new(SExp::Cons( + sdef.loc.clone(), + Rc::new(SExp::Atom(sdef.loc.clone(), vec![b'S'])), + Rc::new(SExp::Nil(sdef.loc.clone())), + )); + let mut members: Vec = sdef + .members + .iter() + .map(|m| { + let mut access_name = "get_".as_bytes().to_vec(); + access_name.append(&mut sdef.name.clone()); + access_name.push(b'_'); + access_name.append(&mut m.name.clone()); + + let mut argty = Type::TVar(TypeVar(decode_string(&sdef.name), m.loc.clone())); + + for a in sdef.vars.iter().rev() { + argty = Type::TApp(Rc::new(argty), Rc::new(Type::TVar(a.clone()))); + } + + let mut funty = Type::TFun( + Rc::new(Type::TPair( + Rc::new(argty), + Rc::new(Type::TUnit(m.loc.clone())), + )), + Rc::new(m.ty.clone()), + ); + + for a in sdef.vars.iter().rev() { + funty = Type::TForall(a.clone(), Rc::new(funty)); + } + + HelperForm::Defun( + true, + Box::new(DefunData { + kw: None, + nl: m.loc.clone(), + loc: m.loc.clone(), + name: access_name, + orig_args: struct_argument.clone(), + args: struct_argument.clone(), + body: Rc::new(BodyForm::Call( + m.loc.clone(), + vec![ + Rc::new(BodyForm::Value(SExp::Atom( + m.loc.clone(), + vec![b'a', b'*'], + ))), + Rc::new(BodyForm::Quoted(SExp::Integer( + m.loc.clone(), + m.path.clone(), + ))), + Rc::new(BodyForm::Value(SExp::Atom(m.loc.clone(), vec![b'S']))), + ], + None, + )), + synthetic: Some(SyntheticType::NoInlinePreference), + ty: Some(funty), + }), + ) + }) + .collect(); + + let ctor = create_constructor(sdef); + members.push(ctor); + members + } + } +} + +fn parse_chia_type(v: Vec) -> Result { + // (deftype name args... (def)) + if let SExp::Atom(l, n) = &v[1].atomize() { + // Name + if v.len() == 2 { + // An abstract type + return Ok(ChiaType::Abstract(v[1].loc(), n.clone())); + } + + let vars: Vec = v.iter().skip(2).take(v.len() - 3).cloned().collect(); + let expr = Rc::new(v[v.len() - 1].clone()); + + let mut var_vec = Vec::new(); + for var in vars.iter() { + var_vec.push(parse_type_var(Rc::new(var.clone()))?); + } + + let type_of_body = recover_arg_type(expr, true)?.unwrap(); + let mut member_vec = Vec::new(); + for k in type_of_body.arg_names.iter() { + let arg_path = type_of_body + .individual_paths + .get(k) + .cloned() + .unwrap_or_else(bi_one); + let arg_loc = type_of_body + .individual_locs + .get(k) + .cloned() + .unwrap_or_else(|| l.clone()); + let arg_type = type_of_body + .individual_types + .get(k) + .cloned() + .unwrap_or_else(|| Type::TAny(arg_loc.clone())); + member_vec.push(StructMember { + loc: arg_loc, + name: k.clone(), + path: arg_path, + ty: arg_type, + }); + } + return Ok(ChiaType::Struct(StructDef { + loc: l.clone(), + name: n.clone(), + vars: var_vec, + members: member_vec, + proto: type_of_body.stripped_args, + ty: type_of_body.whole_args, + })); + } + + Err(CompileErr( + v[0].loc(), + "Don't know how to interpret as type definition".to_string(), + )) +} + +#[derive(Debug, Clone)] +pub struct HelperFormResult { + pub chia_type: Option, + pub new_helpers: Vec, +} + pub fn compile_helperform( opts: Rc, body: Rc, -) -> Result, CompileErr> { +) -> Result, CompileErr> { let l = location_span(body.loc(), body.clone()); + let plist = body.proper_list(); - if let Some(matched) = body.proper_list().and_then(|pl| match_op_name_4(&pl)) { - if matched.op_name == b"defconstant" { - compile_defconstant( + if let Some(matched) = plist.and_then(|pl| match_op_name_4(&pl)) { + let inline = matched.op_name == "defun-inline".as_bytes().to_vec(); + let is_defmac = matched.op_name == "defmac".as_bytes().to_vec(); + if matched.op_name == "defconstant".as_bytes().to_vec() { + let definition = compile_defconstant( opts, l, matched.nl, Some(matched.opl), matched.name.to_vec(), matched.args, - ) - .map(Some) + None, + )?; + Ok(Some(HelperFormResult { + chia_type: None, + new_helpers: vec![definition], + })) } else if matched.op_name == b"defconst" { - compile_defconst( + let definition = compile_defconst( opts, l, matched.nl, Some(matched.opl), matched.name.to_vec(), matched.args, - ) - .map(Some) - } else if matched.op_name == b"defmacro" || matched.op_name == b"defmac" { - compile_defmacro( + )?; + Ok(Some(HelperFormResult { + chia_type: None, + new_helpers: vec![definition], + })) + } else if matched.op_name == b"defmacro" || is_defmac { + if is_defmac { + return Ok(Some(HelperFormResult { + chia_type: None, + new_helpers: vec![], + })); + } + + let definition = compile_defmacro( opts, l, matched.nl, @@ -724,36 +1201,73 @@ pub fn compile_helperform( matched.name.to_vec(), matched.args, matched.body, - ) - .map(Some) - } else if matched.op_name == b"defun" { - compile_defun( + )?; + Ok(Some(HelperFormResult { + chia_type: None, + new_helpers: vec![definition], + })) + } else if matched.op_name == "defun".as_bytes().to_vec() || inline { + let use_type_anno = if let Some((k, ty)) = matched.ty { + match k { + TypeKind::Arrow => Some(TypeAnnoKind::Arrow(parse_type_sexp(ty)?)), + TypeKind::Colon => Some(TypeAnnoKind::Colon(parse_type_sexp(ty)?)), + } + } else { + None + }; + + let (stripped_args, parsed_type) = + augment_fun_type_with_args(matched.args.clone(), use_type_anno)?; + + let definition = compile_defun( opts, CompileDefun { l, nl: matched.nl, kwl: Some(matched.opl), - inline: false, + inline, name: matched.name.to_vec(), - args: matched.args, + args: stripped_args, body: matched.body, }, - ) - .map(Some) - } else if matched.op_name == b"defun-inline" { - compile_defun( - opts, - CompileDefun { - l, + parsed_type, + )?; + Ok(Some(HelperFormResult { + chia_type: None, + new_helpers: vec![definition], + })) + } else if matched.op_name == "deftype".as_bytes().to_vec() { + let parsed_chia = parse_chia_type(matched.orig)?; + let mut helpers = generate_type_helpers(&parsed_chia); + debug!("parsed_chia {:?}", parsed_chia); + let new_form = match &parsed_chia { + ChiaType::Abstract(l, n) => HelperForm::Deftype(DeftypeData { + kw: matched.opl, nl: matched.nl, - kwl: Some(matched.opl), - inline: true, - name: matched.name.to_vec(), - args: matched.args, - body: matched.body, - }, - ) - .map(Some) + loc: l.clone(), + name: n.clone(), + args: vec![], + ty: None, + }), + ChiaType::Struct(sdef) => { + if let SExp::Atom(_, _) = sdef.proto.borrow() { + return Err(CompileErr(sdef.loc.clone(), "A struct with a single element acting as an alias is currently a hazard. This will be fixed in the future.".to_string())); + } + HelperForm::Deftype(DeftypeData { + kw: matched.opl, + nl: matched.nl, + loc: sdef.loc.clone(), + name: sdef.name.clone(), + args: sdef.vars.clone(), + ty: Some(sdef.ty.clone()), + }) + } + }; + helpers.insert(0, new_form); + Ok(Some(HelperFormResult { + chia_type: Some(parsed_chia), + new_helpers: helpers, + })) } else { Err(CompileErr( matched.body.loc(), @@ -761,53 +1275,68 @@ pub fn compile_helperform( )) } } else { - Err(CompileErr( - body.loc(), - "Helper wasn't in the proper form".to_string(), - )) + Ok(None) } } -fn compile_mod_( - mc: &ModAccum, - opts: Rc, - args: Rc, - content: Rc, -) -> Result { - match content.borrow() { - SExp::Nil(l) => Err(CompileErr( - l.clone(), - "no expression at end of mod".to_string(), - )), - SExp::Cons(l, body, tail) => match tail.borrow() { - SExp::Nil(_) => match mc.exp_form { - Some(_) => Err(CompileErr(l.clone(), "too many expressions".to_string())), - _ => Ok(mc.set_final(&CompileForm { - loc: mc.loc.clone(), - include_forms: mc.includes.clone(), - args, - helpers: mc.helpers.clone(), - exp: Rc::new(compile_bodyform(opts.clone(), body.clone())?), - })), - }, - _ => { - let helper = compile_helperform(opts.clone(), body.clone())?; - match helper { - None => Err(CompileErr( - l.clone(), - "only the last form can be an exprssion in mod".to_string(), - )), - Some(form) => match mc.exp_form { - None => compile_mod_(&mc.add_helper(form), opts, args, tail.clone()), - Some(_) => Err(CompileErr(l.clone(), "too many expressions".to_string())), - }, - } +trait ModCompileForms { + fn compile_mod_body( + &self, + opts: Rc, + include_forms: Vec, + args: Rc, + body: Rc, + ty: Option, + ) -> Result; + + fn compile_mod_helper( + &self, + opts: Rc, + args: Rc, + body: Rc, + ty: Option, + ) -> Result; +} + +impl ModCompileForms for ModAccum { + fn compile_mod_body( + &self, + opts: Rc, + include_forms: Vec, + args: Rc, + body: Rc, + ty: Option, + ) -> Result { + Ok(self.set_final(&CompileForm { + loc: self.loc.clone(), + args, + include_forms, + helpers: self.helpers.clone(), + exp: Rc::new(compile_bodyform(opts, body)?), + ty, + })) + } + + fn compile_mod_helper( + &self, + opts: Rc, + _args: Rc, + body: Rc, + _ty: Option, + ) -> Result { + let mut mc = self.clone(); + if let Some(helpers) = compile_helperform(opts.clone(), body.clone())? { + for form in helpers.new_helpers.iter() { + debug!("process helper {}", decode_string(form.name())); + mc = mc.add_helper(form.clone()); } - }, - _ => Err(CompileErr( - content.loc(), - format!("inappropriate sexp {content}"), - )), + Ok(mc) + } else { + Err(CompileErr( + body.loc(), + "only the last form can be an exprssion in mod".to_string(), + )) + } } } @@ -860,17 +1389,51 @@ fn frontend_start( } if *mod_atom == b"mod" { - let args = Rc::new(x[1].clone()); - let body_vec: Vec> = - x.iter().skip(2).map(|s| Rc::new(s.clone())).collect(); + let args = Rc::new(x[1].atomize()); + let mut skip_idx = 2; + let mut ty: Option = None; + + if x.len() < 3 { + return Err(CompileErr(x[0].loc(), "incomplete mod form".to_string())); + } + + if let SExp::Atom(_, colon) = &x[2].atomize() { + if *colon == vec![b':'] && x.len() > 3 { + let use_ty = parse_type_sexp(Rc::new(x[3].atomize()))?; + ty = Some(TypeAnnoKind::Colon(use_ty)); + skip_idx += 2; + } else if *colon == vec![b'-', b'>'] && x.len() > 3 { + let use_ty = parse_type_sexp(Rc::new(x[3].atomize()))?; + ty = Some(TypeAnnoKind::Arrow(use_ty)); + skip_idx += 2; + } + } + let (stripped_args, parsed_type) = augment_fun_type_with_args(args, ty)?; + + let body_vec: Vec> = x + .iter() + .skip(skip_idx) + .map(|s| Rc::new(s.clone())) + .collect(); let body = Rc::new(enlist(pre_forms[0].loc(), &body_vec)); let ls = preprocess(opts.clone(), includes, body)?; - return compile_mod_( - &ModAccum::new(l.clone()), + let mut ma = ModAccum::new(l.clone(), false); + for form in ls.iter().take(ls.len() - 1) { + ma = ma.compile_mod_helper( + opts.clone(), + stripped_args.clone(), + form.clone(), + parsed_type.clone(), + )?; + } + + return ma.compile_mod_body( opts.clone(), - args, - Rc::new(list_to_cons(l, &ls)), + includes.clone(), + stripped_args, + ls[ls.len() - 1].clone(), + parsed_type, ); } } @@ -903,7 +1466,11 @@ pub fn compute_live_helpers( helper_list .iter() - .filter(|h| !opts.frontend_check_live() || helper_names.contains(h.name())) + .filter(|h| { + matches!(h, HelperForm::Deftype(_)) + || !opts.frontend_check_live() + || helper_names.contains(h.name()) + }) .cloned() .collect() } @@ -945,33 +1512,12 @@ pub fn frontend( let our_mod = rename_children_compileform(&compiled?)?; - let expr_names: HashSet> = collect_used_names_bodyform(our_mod.exp.borrow()) - .iter() - .map(|x| x.to_vec()) - .collect(); - - let helper_list = our_mod.helpers.iter().map(|h| (h.name(), h)); - let mut helper_map = HashMap::new(); - - for hpair in helper_list { - helper_map.insert(hpair.0.clone(), hpair.1.clone()); - } - - let helper_names = calculate_live_helpers(&HashSet::new(), &expr_names, &helper_map); - - let mut live_helpers = Vec::new(); - for h in our_mod.helpers { - if !opts.frontend_check_live() || helper_names.contains(h.name()) { - live_helpers.push(h); - } - } + let live_helpers = compute_live_helpers(opts.clone(), &our_mod.helpers, our_mod.exp.clone()); Ok(CompileForm { - loc: our_mod.loc.clone(), include_forms: includes.to_vec(), - args: our_mod.args.clone(), helpers: live_helpers, - exp: our_mod.exp.clone(), + ..our_mod }) } diff --git a/src/compiler/optimize/mod.rs b/src/compiler/optimize/mod.rs index ca7b19fb..b50bdcb1 100644 --- a/src/compiler/optimize/mod.rs +++ b/src/compiler/optimize/mod.rs @@ -320,6 +320,7 @@ fn constant_fun_result( // single capture argument. None, )), + ty: None, }; let optimizer = if let Ok(res) = get_optimizer(&call_spec.loc, opts.clone()) { res diff --git a/src/compiler/preprocessor/mod.rs b/src/compiler/preprocessor/mod.rs index 69fadd9d..7740003e 100644 --- a/src/compiler/preprocessor/mod.rs +++ b/src/compiler/preprocessor/mod.rs @@ -292,6 +292,7 @@ impl Preprocessor { include_forms: vec![], helpers: self.helpers.clone(), exp: mdata.body.clone(), + ty: None, }; let program_sexp = Rc::new(SExp::Cons( @@ -368,16 +369,20 @@ impl Preprocessor { Rc::new(SExp::Cons(args.loc(), args.clone(), body)), )), )); - if let Some(helper) = compile_helperform(self.opts.clone(), target_defun)? { - self.add_helper(rename_args_helperform(&helper)?); + if let Some(helpers) = compile_helperform(self.opts.clone(), target_defun)? { + for helper in helpers.new_helpers.iter() { + self.add_helper(rename_args_helperform(&helper)?); + } } else { return Err(CompileErr( definition.loc(), "defmac found but couldn't be converted to function".to_string(), )); } - } else if let Some(helper) = compile_helperform(self.opts.clone(), definition)? { - self.add_helper(rename_args_helperform(&helper)?); + } else if let Some(helpers) = compile_helperform(self.opts.clone(), definition)? { + for helper in helpers.new_helpers.iter() { + self.add_helper(rename_args_helperform(&helper)?); + } } } } diff --git a/src/compiler/rename.rs b/src/compiler/rename.rs index be4349f1..25435799 100644 --- a/src/compiler/rename.rs +++ b/src/compiler/rename.rs @@ -405,6 +405,7 @@ fn rename_in_helperform( h: &HelperForm, ) -> Result { match h { + HelperForm::Deftype(_) => Ok(h.clone()), HelperForm::Defconstant(defc) => Ok(HelperForm::Defconstant(DefconstData { body: Rc::new(rename_in_bodyform(namemap, defc.body.clone())?), ..defc.clone() @@ -425,6 +426,7 @@ fn rename_in_helperform( pub fn rename_args_helperform(h: &HelperForm) -> Result { match h { + HelperForm::Deftype(_) => Ok(h.clone()), HelperForm::Defconstant(defc) => Ok(HelperForm::Defconstant(DefconstData { body: Rc::new(rename_args_bodyform(defc.body.borrow())?), ..defc.clone() @@ -523,5 +525,6 @@ pub fn rename_args_compileform(c: &CompileForm) -> Result