From 48acf4e7c27bc460fd64c10bd4d78e9917fadab0 Mon Sep 17 00:00:00 2001 From: sezna Date: Sat, 3 Aug 2024 13:26:25 -0700 Subject: [PATCH 01/12] flatten TypeContext into the TypeChecker, and rename it to ConstraintGeneration --- petr-ir/src/lib.rs | 4 +- petr-typecheck/src/constraint_generation.rs | 193 ++++++++------------ petr-typecheck/src/lib.rs | 4 +- petr-typecheck/src/pretty_printing.rs | 12 +- petr-typecheck/src/tests.rs | 4 +- petr-typecheck/src/typed_ast.rs | 8 +- 6 files changed, 91 insertions(+), 134 deletions(-) diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index d296587..e42eb78 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -582,7 +582,7 @@ mod tests { use expect_test::{expect, Expect}; use petr_resolve::resolve_symbols; - use petr_typecheck::TypeChecker; + use petr_typecheck::TypeConstraintContext; use petr_utils::render_error; use super::*; @@ -607,7 +607,7 @@ mod tests { } panic!("resolving names failed"); } - let mut type_checker = TypeChecker::new(resolved); + let mut type_checker = TypeConstraintContext::new(resolved); type_checker.fully_type_check(); diff --git a/petr-typecheck/src/constraint_generation.rs b/petr-typecheck/src/constraint_generation.rs index 0c6920b..dab5add 100644 --- a/petr-typecheck/src/constraint_generation.rs +++ b/petr-typecheck/src/constraint_generation.rs @@ -72,41 +72,37 @@ enum TypeConstraintKindValue { Axiom, } -pub struct TypeContext { - types: IndexMap, - constraints: Vec, +pub type FunctionSignature = (FunctionId, Box<[GeneralType]>); + +pub struct TypeConstraintContext { + type_map: BTreeMap, + monomorphized_functions: BTreeMap, + typed_functions: BTreeMap, + errors: Vec, + resolved: QueryableResolvedItems, + variable_scope: Vec>, + types: IndexMap, + constraints: Vec, // known primitive type IDs - unit_ty: TypeVariable, - string_ty: TypeVariable, - int_ty: TypeVariable, - bool_ty: TypeVariable, + unit_ty: TypeVariable, + string_ty: TypeVariable, + int_ty: TypeVariable, + bool_ty: TypeVariable, error_recovery: TypeVariable, } -impl Default for TypeContext { - fn default() -> Self { - let mut types = IndexMap::default(); - // instantiate basic primitive types - let unit_ty = types.insert(SpecificType::Unit); - let string_ty = types.insert(SpecificType::String); - let bool_ty = types.insert(SpecificType::Boolean); - let int_ty = types.insert(SpecificType::Integer); - let error_recovery = types.insert(SpecificType::ErrorRecovery); - // insert primitive types - TypeContext { - types, - constraints: Default::default(), - bool_ty, - unit_ty, - string_ty, - int_ty, - error_recovery, - } - } +/// This trait defines the interface for AST nodes to output the type constraints that they +/// produce by modifying the constraint context. +pub trait GenerateTypeConstraints { + type Output; + fn type_check( + &self, + ctx: &mut TypeConstraintContext, + ) -> Self::Output; } -impl TypeContext { - fn unify( +impl TypeConstraintContext { + pub fn unify( &mut self, ty1: TypeVariable, ty2: TypeVariable, @@ -115,7 +111,7 @@ impl TypeContext { self.constraints.push(TypeConstraint::unify(ty1, ty2, span)); } - fn satisfies( + pub fn satisfies( &mut self, ty1: TypeVariable, ty2: TypeVariable, @@ -124,7 +120,7 @@ impl TypeContext { self.constraints.push(TypeConstraint::satisfies(ty1, ty2, span)); } - fn axiom( + pub fn axiom( &mut self, ty1: TypeVariable, span: Span, @@ -132,7 +128,7 @@ impl TypeContext { self.constraints.push(TypeConstraint::axiom(ty1, span)); } - fn new_variable( + pub fn new_variable( &mut self, span: Span, ) -> TypeVariable { @@ -143,7 +139,7 @@ impl TypeContext { } /// Update a type variable with a new SpecificType - fn update_type( + pub fn update_type( &mut self, t1: TypeVariable, known: SpecificType, @@ -154,43 +150,21 @@ impl TypeContext { pub fn types(&self) -> &IndexMap { &self.types } -} -pub type FunctionSignature = (FunctionId, Box<[GeneralType]>); - -pub struct TypeChecker { - ctx: TypeContext, - type_map: BTreeMap, - monomorphized_functions: BTreeMap, - typed_functions: BTreeMap, - errors: Vec, - resolved: QueryableResolvedItems, - variable_scope: Vec>, -} - -pub trait TypeCheck { - type Output; - fn type_check( - &self, - ctx: &mut TypeChecker, - ) -> Self::Output; -} - -impl TypeChecker { pub fn insert_type( &mut self, ty: &T, ) -> TypeVariable { let ty = ty.as_specific_ty(); // TODO: check if type already exists and return that ID instead - self.ctx.types.insert(ty) + self.types.insert(ty) } pub fn look_up_variable( &self, ty: TypeVariable, ) -> &SpecificType { - self.ctx.types.get(ty) + self.types.get(ty) } pub fn get_symbol( @@ -228,7 +202,7 @@ impl TypeChecker { self.errors.push(id.span.with_item(TypeConstraintError::Internal( "attempted to insert generic type into variable scope when no variable scope existed".into(), ))); - self.ctx.update_type(fresh_ty, SpecificType::ErrorRecovery); + self.update_type(fresh_ty, SpecificType::ErrorRecovery); }, }; fresh_ty @@ -261,7 +235,7 @@ impl TypeChecker { }) }) .collect::>(); - self.ctx.update_type( + self.update_type( ty, SpecificType::UserDefined { name: decl.name, @@ -306,11 +280,11 @@ impl TypeChecker { /// - satisfaction tries to make one type satisfy the constraints of another, although type /// constraints don't exist in the language yet pub fn into_solution(self) -> Result> { - let constraints = self.ctx.constraints.clone(); + let constraints = self.constraints.clone(); let mut solution = TypeSolution::new( - self.ctx.types.clone(), - self.ctx.error_recovery, - self.ctx.unit_ty, + self.types.clone(), + self.error_recovery, + self.unit_ty, self.typed_functions, self.monomorphized_functions, self.resolved.interner, @@ -321,7 +295,7 @@ impl TypeChecker { unreachable!("above filter ensures that all constraints are axioms here") }; // first, pin all axiomatic type variables in the solution - let ty = self.ctx.types.get(*axiomatic_variable).clone(); + let ty = self.types.get(*axiomatic_variable).clone(); solution.insert_solution(*axiomatic_variable, TypeSolutionEntry::new_axiomatic(ty), *span); } @@ -342,15 +316,28 @@ impl TypeChecker { } pub fn new(resolved: QueryableResolvedItems) -> Self { - let ctx = TypeContext::default(); - TypeChecker { - ctx, + let mut types = IndexMap::default(); + // instantiate basic primitive types + let unit_ty = types.insert(SpecificType::Unit); + let string_ty = types.insert(SpecificType::String); + let bool_ty = types.insert(SpecificType::Boolean); + let int_ty = types.insert(SpecificType::Integer); + let error_recovery = types.insert(SpecificType::ErrorRecovery); + + TypeConstraintContext { type_map: Default::default(), errors: Default::default(), typed_functions: Default::default(), resolved, variable_scope: Default::default(), monomorphized_functions: Default::default(), + types, + constraints: Default::default(), + unit_ty, + string_ty, + int_ty, + bool_ty, + error_recovery, } } @@ -369,7 +356,7 @@ impl TypeChecker { &mut self, span: Span, ) -> TypeVariable { - self.ctx.new_variable(span) + self.new_variable(span) } fn arrow_type( @@ -383,7 +370,7 @@ impl TypeChecker { } let ty = SpecificType::Arrow(tys); - self.ctx.types.insert(ty) + self.types.insert(ty) } pub fn to_petr_type( @@ -415,7 +402,7 @@ impl TypeChecker { ty: &petr_resolve::Type, ) -> TypeVariable { let petr_ty = self.to_petr_type(ty); - self.ctx.types.insert(petr_ty) + self.types.insert(petr_ty) } pub fn get_type( @@ -430,33 +417,7 @@ impl TypeChecker { literal: &petr_resolve::Literal, ) -> TypeVariable { let ty = SpecificType::Literal(literal.clone()); - self.ctx.types.insert(ty) - } - - pub fn unify( - &mut self, - ty1: TypeVariable, - ty2: TypeVariable, - span: Span, - ) { - self.ctx.unify(ty1, ty2, span); - } - - pub fn satisfies( - &mut self, - ty1: TypeVariable, - ty2: TypeVariable, - span: Span, - ) { - self.ctx.satisfies(ty1, ty2, span); - } - - pub fn axiom( - &mut self, - ty: TypeVariable, - span: Span, - ) { - self.ctx.axiom(ty, span); + self.types.insert(ty) } fn get_untyped_function( @@ -505,7 +466,7 @@ impl TypeChecker { Unit => self.unit(), Variable { ty, .. } => *ty, Intrinsic { ty, .. } => *ty, - ErrorRecovery(..) => self.ctx.error_recovery, + ErrorRecovery(..) => self.error_recovery, ExprWithBindings { expression, .. } => self.expr_ty(expression), TypeConstructor { ty, .. } => *ty, If { then_branch, .. } => self.expr_ty(then_branch), @@ -523,19 +484,19 @@ impl TypeChecker { } pub fn string(&self) -> TypeVariable { - self.ctx.string_ty + self.string_ty } pub fn unit(&self) -> TypeVariable { - self.ctx.unit_ty + self.unit_ty } pub fn int(&self) -> TypeVariable { - self.ctx.int_ty + self.int_ty } pub fn bool(&self) -> TypeVariable { - self.ctx.bool_ty + self.bool_ty } pub fn errors(&self) -> &[TypeError] { @@ -551,10 +512,6 @@ impl TypeChecker { self.satisfies(ty, expr_ty, expr.span()); } - pub fn ctx(&self) -> &TypeContext { - &self.ctx - } - /// terms: /// ### resolved type variable /// @@ -584,7 +541,7 @@ impl TypeChecker { use TypeConstraintKindValue as Kind; let mut constraints = ConstraintDeduplicator::default(); let mut errs = vec![]; - for constraint in &self.ctx.constraints { + for constraint in &self.constraints { let (mut tys, kind) = match &constraint.kind { TypeConstraintKind::Unify(t1, t2) => (vec![*t1, *t2], Kind::Unify), TypeConstraintKind::Satisfies(t1, t2) => (vec![*t1, *t2], Kind::Satisfies), @@ -596,7 +553,7 @@ impl TypeChecker { // track what we have seen, in case a circular reference is present let mut seen_vars = BTreeSet::new(); seen_vars.insert(*ty_var); - let mut ty = self.ctx.types.get(*ty_var); + let mut ty = self.types.get(*ty_var); while let SpecificType::Ref(t) = ty { if seen_vars.contains(t) { // circular reference @@ -604,14 +561,14 @@ impl TypeChecker { continue 'outer; } *ty_var = *t; - ty = self.ctx.types.get(*t); + ty = self.types.get(*t); } } constraints.insert((kind, tys), *constraint); } - self.ctx.constraints = constraints.into_values(); + self.constraints = constraints.into_values(); } pub fn push_error( @@ -669,7 +626,7 @@ impl ConstraintDeduplicator { pub fn unify_basic_math_op( lhs: &Expr, rhs: &Expr, - ctx: &mut TypeChecker, + ctx: &mut TypeConstraintContext, ) -> (TypedExpr, TypedExpr) { let lhs = lhs.type_check(ctx); let rhs = rhs.type_check(ctx); @@ -681,12 +638,12 @@ pub fn unify_basic_math_op( (lhs, rhs) } -impl TypeCheck for petr_resolve::Function { +impl GenerateTypeConstraints for petr_resolve::Function { type Output = Function; fn type_check( &self, - ctx: &mut TypeChecker, + ctx: &mut TypeConstraintContext, ) -> Self::Output { ctx.with_type_scope(|ctx| { let params = self.params.iter().map(|(name, ty)| (*name, ctx.to_type_var(ty))).collect::>(); @@ -713,12 +670,12 @@ impl TypeCheck for petr_resolve::Function { } } -impl TypeCheck for FunctionCall { +impl GenerateTypeConstraints for FunctionCall { type Output = TypedExprKind; fn type_check( &self, - ctx: &mut TypeChecker, + ctx: &mut TypeConstraintContext, ) -> Self::Output { let func_decl = ctx.get_function(&self.function).clone(); @@ -744,7 +701,7 @@ impl TypeCheck for FunctionCall { let concrete_arg_types: Vec<_> = args .iter() - .map(|(_, _, ty)| ctx.look_up_variable(*ty).generalize(ctx.ctx().types()).clone()) + .map(|(_, _, ty)| ctx.look_up_variable(*ty).generalize(&ctx.types).clone()) .collect(); let signature: FunctionSignature = (self.function, concrete_arg_types.clone().into_boxed_slice()); @@ -797,12 +754,12 @@ impl TypeCheck for FunctionCall { } } -impl TypeCheck for SpannedItem { +impl GenerateTypeConstraints for SpannedItem { type Output = TypedExpr; fn type_check( &self, - ctx: &mut TypeChecker, + ctx: &mut TypeConstraintContext, ) -> Self::Output { use petr_resolve::IntrinsicName::*; let kind = match self.item().intrinsic { diff --git a/petr-typecheck/src/lib.rs b/petr-typecheck/src/lib.rs index a2fab05..f5d5a69 100644 --- a/petr-typecheck/src/lib.rs +++ b/petr-typecheck/src/lib.rs @@ -6,7 +6,7 @@ mod error; -pub use constraint_generation::{unify_basic_math_op, FunctionSignature, TypeCheck, TypeChecker}; +pub use constraint_generation::{unify_basic_math_op, FunctionSignature, GenerateTypeConstraints, TypeConstraintContext}; pub use error::TypeConstraintError; pub use petr_bind::FunctionId; use petr_resolve::QueryableResolvedItems; @@ -28,7 +28,7 @@ pub type TypeError = SpannedItem; pub type TResult = Result; pub fn type_check(resolved: QueryableResolvedItems) -> Result>> { - let mut type_checker = TypeChecker::new(resolved); + let mut type_checker = TypeConstraintContext::new(resolved); type_checker.fully_type_check(); type_checker.into_solution() diff --git a/petr-typecheck/src/pretty_printing.rs b/petr-typecheck/src/pretty_printing.rs index d0d82c6..7f7f9bd 100644 --- a/petr-typecheck/src/pretty_printing.rs +++ b/petr-typecheck/src/pretty_printing.rs @@ -4,7 +4,7 @@ use types::SpecificType; use crate::*; #[cfg(test)] -pub fn pretty_print_type_checker(type_checker: &TypeChecker) -> String { +pub fn pretty_print_type_checker(type_checker: &TypeConstraintContext) -> String { let mut s = String::new(); for (id, ty) in type_checker.type_map() { let text = match id { @@ -24,7 +24,7 @@ pub fn pretty_print_type_checker(type_checker: &TypeChecker) -> String { }; s.push_str(&text); s.push_str(": "); - s.push_str(&pretty_print_ty(ty, type_checker.ctx().types(), &type_checker.resolved().interner)); + s.push_str(&pretty_print_ty(ty, type_checker.types(), &type_checker.resolved().interner)); s.push('\n'); match id { @@ -48,13 +48,13 @@ pub fn pretty_print_type_checker(type_checker: &TypeChecker) -> String { let arg_types = func .params .iter() - .map(|(_, ty)| pretty_print_ty(ty, type_checker.ctx().types(), &type_checker.resolved().interner)) + .map(|(_, ty)| pretty_print_ty(ty, type_checker.types(), &type_checker.resolved().interner)) .collect::>(); s.push_str(&format!( "\nfn {}({:?}) -> {}", func_name, arg_types, - pretty_print_ty(&func.return_ty, type_checker.ctx().types(), &type_checker.resolved().interner) + pretty_print_ty(&func.return_ty, type_checker.types(), &type_checker.resolved().interner) )); } @@ -130,10 +130,10 @@ pub fn pretty_print_petr_type( #[cfg(test)] pub fn pretty_print_typed_expr( typed_expr: &TypedExpr, - type_checker: &TypeChecker, + type_checker: &TypeConstraintContext, ) -> String { let interner = &type_checker.resolved().interner; - let types = &type_checker.ctx().types(); + let types = &type_checker.types(); match &typed_expr.kind { TypedExprKind::ExprWithBindings { bindings, expression } => { let mut s = String::new(); diff --git a/petr-typecheck/src/tests.rs b/petr-typecheck/src/tests.rs index 1a17ca4..2e02e35 100644 --- a/petr-typecheck/src/tests.rs +++ b/petr-typecheck/src/tests.rs @@ -2,7 +2,7 @@ use expect_test::{expect, Expect}; use petr_resolve::resolve_symbols; use petr_utils::render_error; -use crate::{constraint_generation::TypeChecker, pretty_printing::*}; +use crate::{constraint_generation::TypeConstraintContext, pretty_printing::*}; fn check( input: impl Into, @@ -20,7 +20,7 @@ fn check( errs.into_iter().for_each(|err| eprintln!("{:?}", render_error(&source_map, err))); panic!("unresolved symbols in test"); } - let mut type_checker = TypeChecker::new(resolved); + let mut type_checker = TypeConstraintContext::new(resolved); type_checker.fully_type_check(); let mut res = pretty_print_type_checker(&type_checker); diff --git a/petr-typecheck/src/typed_ast.rs b/petr-typecheck/src/typed_ast.rs index e0f9bc3..2b0c51a 100644 --- a/petr-typecheck/src/typed_ast.rs +++ b/petr-typecheck/src/typed_ast.rs @@ -3,7 +3,7 @@ use petr_resolve::{Expr, ExprKind, Literal}; use petr_utils::{Identifier, Span}; use crate::{ - constraint_generation::{TypeCheck, TypeChecker}, + constraint_generation::{GenerateTypeConstraints, TypeConstraintContext}, types::SpecificType, TypeVariable, }; @@ -137,12 +137,12 @@ impl std::fmt::Debug for TypedExpr { } } -impl TypeCheck for Expr { +impl GenerateTypeConstraints for Expr { type Output = TypedExpr; fn type_check( &self, - ctx: &mut TypeChecker, + ctx: &mut TypeConstraintContext, ) -> Self::Output { let kind = match &self.kind { ExprKind::Literal(lit) => { @@ -161,7 +161,7 @@ impl TypeCheck for Expr { let second_ty = ctx.expr_ty(expr); ctx.unify(first_ty, second_ty, expr.span()); } - let first_ty = ctx.ctx().types().get(first_ty).clone(); + let first_ty = ctx.types().get(first_ty).clone(); TypedExprKind::List { elements: type_checked_exprs, ty: ctx.insert_type::(&SpecificType::List(Box::new(first_ty))), From 5272692259489a901a19f848254034b24b8a6e5d Mon Sep 17 00:00:00 2001 From: sezna Date: Sat, 3 Aug 2024 13:44:12 -0700 Subject: [PATCH 02/12] start work on list --- petr-ir/src/lib.rs | 9 ++++++--- petr-stdlib/src/lib.rs | 1 + petr-stdlib/src/list.pt | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 petr-stdlib/src/list.pt diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index e42eb78..2d26d3d 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -2,7 +2,7 @@ // - reuse data labels // - figure out actual interface around "return destination" etc // - store position to jump back to after fn call -// - terminate instructions in correct places (end of entry point) +// - terminate nstructions in correct places (end of entry point) // - comments on IR ops // - dead code elimination // @@ -11,14 +11,14 @@ use std::{collections::BTreeMap, rc::Rc}; use petr_typecheck::{FunctionSignature, SpecificType, TypeSolution, TypeVariable, TypedExpr, TypedExprKind}; use petr_utils::{idx_map_key, Identifier, IndexMap, SpannedItem, SymbolId}; - mod error; mod opcodes; - pub use error::LoweringError; use opcodes::*; pub use opcodes::{DataLabel, Intrinsic, IrOpcode, LabelId, Reg, ReservedRegister}; +/// Top-level convenience function that takes a type solution (the output of the type checker) and +/// outputs lowered IR and a data section. pub fn lower(solution: TypeSolution) -> Result<(DataSection, Vec)> { let lowerer = Lowerer::new(solution)?; Ok(lowerer.finalize()) @@ -45,6 +45,7 @@ pub struct Lowerer { label_assigner: usize, } +/// Represents static data in the data section of the program. #[derive(Debug, Clone)] pub enum DataSectionEntry { Int64(i64), @@ -81,6 +82,8 @@ impl Lowerer { Ok(lowerer) } + /// Consumes the [`Lowerer`], performing lowering to IR and returning the data section and + /// program section. pub fn finalize(self) -> (DataSection, Vec) { let mut program_section = vec![]; diff --git a/petr-stdlib/src/lib.rs b/petr-stdlib/src/lib.rs index dec0a5f..a53282e 100644 --- a/petr-stdlib/src/lib.rs +++ b/petr-stdlib/src/lib.rs @@ -4,5 +4,6 @@ pub fn stdlib() -> Vec<(&'static str, &'static str)> { ("std/ops.pt", include_str!("ops.pt")), ("std/io.pt", include_str!("io.pt")), ("std/mem.pt", include_str!("mem.pt")), + ("std/list.pt", include_str!("list.pt")), ] } diff --git a/petr-stdlib/src/list.pt b/petr-stdlib/src/list.pt new file mode 100644 index 0000000..337f0a0 --- /dev/null +++ b/petr-stdlib/src/list.pt @@ -0,0 +1,2 @@ +Type List = Head element 'A tail 'list | Nil + From 153e9d3f57eb2b2577ce61d9bbe97b75556b4bc9 Mon Sep 17 00:00:00 2001 From: sezna Date: Sun, 4 Aug 2024 11:39:53 -0700 Subject: [PATCH 03/12] wip: fix bindings, but need to sort out an infinite loop --- petr-bind/src/binder.rs | 13 +- petr-bind/src/binder/tests.rs | 21 +- petr-bind/src/impls.rs | 32 +- petr-parse/src/parse_to_ast.rs | 9 +- petr-parse/src/parser/lexer.rs | 3 +- petr-playground/index.js | 20 +- petr-resolve/src/resolver.rs | 8 +- petr-stdlib/src/list.pt | 3 +- petr-stdlib/src/mem.pt | 2 +- petr-typecheck/src/constraint_generation.rs | 50 +- petr-typecheck/src/pretty_printing.rs | 9 +- petr-typecheck/src/tests.rs | 652 +++++++++++++------- petr-vm/src/tests.rs | Bin 6154 -> 7651 bytes 13 files changed, 564 insertions(+), 258 deletions(-) diff --git a/petr-bind/src/binder.rs b/petr-bind/src/binder.rs index f437b00..381ed9b 100644 --- a/petr-bind/src/binder.rs +++ b/petr-bind/src/binder.rs @@ -50,8 +50,7 @@ pub struct Binder { scopes: IndexMap, scope_chain: Vec, /// Some expressions define their own scopes, like expressions with bindings - // TODO rename to expr_scopes - exprs: BTreeMap, + expr_scopes: BTreeMap, bindings: IndexMap, functions: IndexMap>, types: IndexMap, @@ -215,7 +214,7 @@ impl Binder { types: IndexMap::default(), bindings: IndexMap::default(), modules: IndexMap::default(), - exprs: BTreeMap::new(), + expr_scopes: BTreeMap::new(), } } @@ -857,14 +856,18 @@ impl Binder { id: ExprId, scope: ScopeId, ) { - self.exprs.insert(id, scope); + self.expr_scopes.insert(id, scope); } pub fn get_expr_scope( &self, id: ExprId, ) -> Option { - self.exprs.get(&id).copied() + self.expr_scopes.get(&id).copied() + } + + pub fn exprs(&self) -> &BTreeMap { + &self.expr_scopes } } diff --git a/petr-bind/src/binder/tests.rs b/petr-bind/src/binder/tests.rs index b92ddef..26f9170 100644 --- a/petr-bind/src/binder/tests.rs +++ b/petr-bind/src/binder/tests.rs @@ -153,11 +153,12 @@ fn imports_work() { __Scopes__ 0: Root (parent none): std: Module ModuleId(0) - test: Module ModuleId(7) + test: Module ModuleId(9) 1: Module std (parent scopeid0): ops: Module ModuleId(1) io: Module ModuleId(3) mem: Module ModuleId(5) + list: Module ModuleId(7) 2: Module ops (parent scopeid0): add: Function functionid0 sub: Function functionid1 @@ -201,11 +202,21 @@ fn imports_work() { 16: Expr w/ Bindings (parent scopeid15): allocated: Binding 17: Function (parent scopeid10): - expr: FunctionParameter Named(Identifier { id: SymbolId(10), span: Span { source: SourceId(2), span: SourceSpan { offset: SourceOffset(246), length: 1 } } }) - 18: Module test (parent scopeid0): - main: Function functionid10 + expr: FunctionParameter Named(Identifier { id: SymbolId(10), span: Span { source: SourceId(2), span: SourceSpan { offset: SourceOffset(253), length: 1 } } }) + 18: Module list (parent scopeid0): + Head: Function functionid10 + Nil: Function functionid11 + List: Type TypeId(1) + 19: Type Cons (parent scopeid18): + 20: Function (parent scopeid18): + element: FunctionParameter Named(Identifier { id: SymbolId(10), span: Span { source: SourceId(3), span: SourceSpan { offset: SourceOffset(35), length: 1 } } }) + tail: FunctionParameter Named(Identifier { id: SymbolId(27), span: Span { source: SourceId(3), span: SourceSpan { offset: SourceOffset(43), length: 4 } } }) + 21: Type Cons (parent scopeid18): + 22: Function (parent scopeid18): + 23: Module test (parent scopeid0): + main: Function functionid12 symbolid2: Import add - 19: Function (parent scopeid18): + 24: Function (parent scopeid23): "#]], ); } diff --git a/petr-bind/src/impls.rs b/petr-bind/src/impls.rs index 5989f4e..d68477e 100644 --- a/petr-bind/src/impls.rs +++ b/petr-bind/src/impls.rs @@ -49,7 +49,36 @@ impl Bind for Expression { expression.bind(binder); binder.insert_expression(*expr_id, scope_id); }), - _ => (), + Expression::Literal(_) => (), + Expression::Operator(op) => { + op.lhs.bind(binder); + op.rhs.bind(binder); + }, + Expression::FunctionCall(f) => { + // bind all of the arguments + for arg in f.args.iter() { + arg.bind(binder); + } + }, + Expression::Variable(_) => (), + Expression::IntrinsicCall(i) => { + // bind all of the arguments + for arg in i.args.iter() { + arg.bind(binder); + } + }, + Expression::TypeConstructor(_, expr) => { + for arg in expr.iter() { + arg.bind(binder); + } + }, + Expression::If(i) => { + i.condition.bind(binder); + i.then_branch.bind(binder); + if let Some(ref else_branch) = i.else_branch { + else_branch.bind(binder); + } + }, } } } @@ -120,7 +149,6 @@ impl Bind for ImportStatement { ) -> Self::Output { // the alias, if any, or the last path element if there is no alias let name = self.alias.unwrap_or_else(|| *self.path.iter().last().expect("should never be empty")); - println!("handling import"); let import = crate::binder::ImportStatement { path: self.path.clone(), diff --git a/petr-parse/src/parse_to_ast.rs b/petr-parse/src/parse_to_ast.rs index 36efc2e..28c4840 100644 --- a/petr-parse/src/parse_to_ast.rs +++ b/petr-parse/src/parse_to_ast.rs @@ -165,7 +165,7 @@ impl Parse for FunctionDeclaration { p.token(Token::CloseParen)?; seq }; - p.token(Token::ReturnsKeyword)?; + p.one_of([Token::ReturnsKeyword, Token::ReturnsSymbol])?; let return_type = p.parse()?; let body = p.parse()?; Some(Self { @@ -315,9 +315,16 @@ impl Parse for Expression { Token::True | Token::False | Token::String | Token::Integer => Some(Expression::Literal(p.parse()?)), Token::Intrinsic => Some(Expression::IntrinsicCall(p.parse()?)), Token::Let => Some(Expression::Binding(p.parse()?)), + Token::OpenParen => { + let _open = p.token(Token::OpenParen)?; + let expr = p.parse()?; + let _close = p.token(Token::CloseParen)?; + Some(expr) + }, otherwise => { p.push_error(p.span().with_item(ParseErrorKind::ExpectedOneOf( vec![ + Token::OpenParen, Token::Identifier, Token::OpenBracket, Token::Tilde, diff --git a/petr-parse/src/parser/lexer.rs b/petr-parse/src/parser/lexer.rs index 3c84401..f41d381 100644 --- a/petr-parse/src/parser/lexer.rs +++ b/petr-parse/src/parser/lexer.rs @@ -39,6 +39,7 @@ pub enum Token { Identifier, #[regex(r#""([^"\\]|\\["\\bnfrt]|u[a-fA-F0-9]{4})*""#)] String, + // TODO: comments can't contain `-` #[regex(r#"(\{\-)[^-}]*(\-\})"#)] Comment, #[token("fn")] @@ -71,7 +72,7 @@ pub enum Token { ToKeyword, #[regex(r#"export\s+fn"#)] ExportFunctionKeyword, - #[token("Type")] + #[regex(r#"export\s+type"#)] ExportTypeKeyword, #[token("~")] Tilde, diff --git a/petr-playground/index.js b/petr-playground/index.js index af5ac79..c1afac8 100644 --- a/petr-playground/index.js +++ b/petr-playground/index.js @@ -110,10 +110,28 @@ monaco.languages.registerCompletionItemProvider("mySpecialLanguage", { */ +const defaultText = ` +{- Try running "format" on the right side of the screen to format this code! -} + +{- Calculates the nth number in the fibonacci sequence -} +fn fibonacci_sequence(n in 'int) returns 'int + if = n 0 then 0 + else if = n 1 then 1 + else + let lhs = - n 1; + rhs = - n 2; + + (~fibonacci_sequence lhs) (~fibonacci_sequence rhs) + + +{- sum types can be declared inline -} +fn cast_string_to_bool(s in 'sum "true" | "false") returns 'bool + if = s "true" then true + else false +` monaco.editor.create(document.getElementById('monaco-editor'), { - value: "fn main() returns 'unit \n ~std.io.print \"Hello, World!\"", + value: defaultText, language: 'petr', theme: "petr-theme", }); diff --git a/petr-resolve/src/resolver.rs b/petr-resolve/src/resolver.rs index 1ebab68..f6c4cec 100644 --- a/petr-resolve/src/resolver.rs +++ b/petr-resolve/src/resolver.rs @@ -501,7 +501,13 @@ impl Resolve for SpannedItem { Expr::new(ExprKind::Intrinsic(resolved), self.span()) }, Expression::Binding(bound_expression) => { - let scope_id = binder.get_expr_scope(bound_expression.expr_id).expect("invariant: scope should exist"); + let scope_id = binder.get_expr_scope(bound_expression.expr_id).unwrap_or_else(|| { + panic!( + "invariant: expr {:?} should have an associated scope, but available scopes were {:?}", + bound_expression.expr_id, + binder.exprs() + ) + }); let mut bindings: Vec = Vec::with_capacity(bound_expression.bindings.len()); for binding in &bound_expression.bindings { let rhs = binding.val.resolve(resolver, binder, scope_id)?; diff --git a/petr-stdlib/src/list.pt b/petr-stdlib/src/list.pt index 337f0a0..6e72385 100644 --- a/petr-stdlib/src/list.pt +++ b/petr-stdlib/src/list.pt @@ -1,2 +1,3 @@ -Type List = Head element 'A tail 'list | Nil + +export type List = Head element 'A tail 'list | Nil diff --git a/petr-stdlib/src/mem.pt b/petr-stdlib/src/mem.pt index 6d19b3d..43950bd 100644 --- a/petr-stdlib/src/mem.pt +++ b/petr-stdlib/src/mem.pt @@ -1,4 +1,4 @@ -Type Ptr = Unsized address 'int | Sized address 'int size 'int +export type Ptr = Unsized address 'int | Sized address 'int size 'int export fn malloc(size in 'int) returns 'Ptr let allocated = @malloc size diff --git a/petr-typecheck/src/constraint_generation.rs b/petr-typecheck/src/constraint_generation.rs index dab5add..ad83f77 100644 --- a/petr-typecheck/src/constraint_generation.rs +++ b/petr-typecheck/src/constraint_generation.rs @@ -221,8 +221,14 @@ impl TypeConstraintContext { } pub fn fully_type_check(&mut self) { + let mut ty_map: Vec<(TypeVariable, _, _)> = Vec::with_capacity(self.resolved.types().count()); for (id, decl) in self.resolved.types() { let ty = self.fresh_ty_var(decl.name.span); + ty_map.push((ty, id, decl)); + self.type_map.insert(id.into(), ty); + } + + for (ty, _id, decl) in ty_map { let variants = decl .variants .iter() @@ -235,6 +241,7 @@ impl TypeConstraintContext { }) }) .collect::>(); + self.update_type( ty, SpecificType::UserDefined { @@ -243,7 +250,6 @@ impl TypeConstraintContext { constant_literal_types: decl.constant_literal_types, }, ); - self.type_map.insert(id.into(), ty); } for (id, func) in self.resolved.functions() { @@ -386,7 +392,12 @@ impl TypeConstraintContext { // unifies to anything, fresh var SpecificType::ErrorRecovery }, - petr_resolve::Type::Named(ty_id) => SpecificType::Ref(*self.type_map.get(&ty_id.into()).expect("type did not exist in type map")), + petr_resolve::Type::Named(ty_id) => SpecificType::Ref( + *self + .type_map + .get(&ty_id.into()) + .unwrap_or_else(|| panic!("ty {ty_id} type did not exist in type map")), + ), petr_resolve::Type::Generic(generic_name) => { // TODO don't create an ID and then reference it -- this is messy let id = self.generic_type(generic_name); @@ -601,6 +612,10 @@ impl TypeConstraintContext { pub fn typed_functions(&self) -> &BTreeMap { &self.typed_functions } + + pub fn constraints(&self) -> &[TypeConstraint] { + &self.constraints + } } /// the `key` type is what we use to deduplicate constraints @@ -728,6 +743,7 @@ impl GenerateTypeConstraints for FunctionCall { return_ty: declared_return_type, body: func_decl.body.clone(), }; + // todo!("figure out how to re-do type check OR use replace_var_types to generate more constraints, maybe via unification?"); // update the parameter types to be the concrete types for (param, concrete_ty) in monomorphized_func_decl.params.iter_mut().zip(concrete_arg_types.iter()) { @@ -737,11 +753,17 @@ impl GenerateTypeConstraints for FunctionCall { // if there are any variable exprs in the body, update those ref types let mut num_replacements = 0; - replace_var_reference_types( + let new_constraints = replace_var_reference_types( &mut monomorphized_func_decl.body.kind, &monomorphized_func_decl.params, &mut num_replacements, ); + for constraint in new_constraints { + ctx.constraints.push(constraint); + } + + // re-do constraint generation on replaced func body + // monomorphized_func_decl.body.type_check(ctx); ctx.insert_monomorphized_function(signature, monomorphized_func_decl); // if there are any variable exprs in the body, update those ref types @@ -872,34 +894,38 @@ fn replace_var_reference_types( expr: &mut TypedExprKind, params: &Vec<(Identifier, TypeVariable)>, num_replacements: &mut usize, -) { +) -> Vec { match expr { TypedExprKind::Variable { ref mut ty, name } => { if let Some((_param_name, ty_var)) = params.iter().find(|(param_name, _)| param_name.id == name.id) { *num_replacements += 1; - *ty = *ty_var; + println!("replacing var named {} ", name.id); + // *ty = *ty_var; + return vec![TypeConstraint::unify(*ty, *ty_var, name.span)]; } + vec![] }, TypedExprKind::FunctionCall { args, .. } => { + let mut buf = Vec::new(); for (_, arg) in args { - replace_var_reference_types(&mut arg.kind, params, num_replacements); + buf.append(&mut replace_var_reference_types(&mut arg.kind, params, num_replacements)); } + buf }, TypedExprKind::Intrinsic { intrinsic, .. } => { use crate::Intrinsic::*; match intrinsic { // intrinsics which take one arg, grouped for convenience - Puts(a) | Malloc(a) | SizeOf(a) => { - replace_var_reference_types(&mut a.kind, params, num_replacements); - }, + Puts(a) | Malloc(a) | SizeOf(a) => replace_var_reference_types(&mut a.kind, params, num_replacements), // intrinsics which take two args, grouped for convenience Add(a, b) | Subtract(a, b) | Multiply(a, b) | Divide(a, b) | Equals(a, b) => { - replace_var_reference_types(&mut a.kind, params, num_replacements); - replace_var_reference_types(&mut b.kind, params, num_replacements); + let mut constraints = replace_var_reference_types(&mut a.kind, params, num_replacements); + constraints.append(&mut replace_var_reference_types(&mut b.kind, params, num_replacements)); + constraints }, } }, // TODO other expr kinds like bindings - _ => (), + _ => vec![], } } diff --git a/petr-typecheck/src/pretty_printing.rs b/petr-typecheck/src/pretty_printing.rs index 7f7f9bd..07a0dcc 100644 --- a/petr-typecheck/src/pretty_printing.rs +++ b/petr-typecheck/src/pretty_printing.rs @@ -39,8 +39,15 @@ pub fn pretty_print_type_checker(type_checker: &TypeConstraintContext) -> String s.push('\n'); } + if !type_checker.constraints().is_empty() { + s.push_str("__CONSTRAINTS__\n"); + for constraint in type_checker.constraints() { + s.push_str(&format!("{:?}\n", constraint)); + } + } + if !type_checker.monomorphized_functions().is_empty() { - s.push_str("__MONOMORPHIZED FUNCTIONS__"); + s.push_str("\n__MONOMORPHIZED FUNCTIONS__"); } for func in type_checker.monomorphized_functions().values() { diff --git a/petr-typecheck/src/tests.rs b/petr-typecheck/src/tests.rs index 2e02e35..53115fc 100644 --- a/petr-typecheck/src/tests.rs +++ b/petr-typecheck/src/tests.rs @@ -27,7 +27,7 @@ fn check( let solved_constraints = match type_checker.into_solution() { Ok(solution) => solution.pretty_print(), Err(errs) => { - res.push_str("__ERRORS__\n"); + res.push_str("\n__ERRORS__"); errs.into_iter().map(|err| format!("{:?}", err)).collect::>().join("\n") }, }; @@ -35,7 +35,7 @@ fn check( res.push('\n'); res.push_str(&solved_constraints); - expect.assert_eq(res.trim()); + expect.assert_eq(&res.trim().replace("\n\n\n", "\n\n")); } #[test] @@ -45,12 +45,15 @@ fn identity_resolution_concrete_type() { fn foo(x in 'int) returns 'int x "#, expect![[r#" - fn foo: (int → int) - variable x: int + fn foo: (int → int) + variable x: int + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(44), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(20), length: 1 } } } - __SOLVED TYPES__ - 5: int"#]], + __SOLVED TYPES__ + 5: int"#]], ); } @@ -61,12 +64,15 @@ fn identity_resolution_generic() { fn foo(x in 'A) returns 'A x "#, expect![[r#" - fn foo: (infer t5 → infer t5) - variable x: infer t5 + fn foo: (infer t5 → infer t5) + variable x: infer t5 + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(40), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(20), length: 1 } } } - __SOLVED TYPES__ - 6: infer t5"#]], + __SOLVED TYPES__ + 6: infer t5"#]], ); } @@ -78,20 +84,23 @@ fn identity_resolution_custom_type() { fn foo(x in 'MyType) returns 'MyType x "#, expect![[r#" - type MyType: MyType + type MyType: MyType - fn A: MyType - type constructor: MyType + fn A: MyType + type constructor: MyType - fn B: MyType - type constructor: MyType + fn B: MyType + type constructor: MyType - fn foo: (MyType → MyType) - variable x: MyType + fn foo: (MyType → MyType) + variable x: MyType + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(8), TypeVariable(9)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(82), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(8)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(52), length: 1 } } } - __SOLVED TYPES__ - 8: MyType"#]], + __SOLVED TYPES__ + 8: MyType"#]], ); } @@ -104,32 +113,42 @@ fn identity_resolution_two_custom_types() { fn foo(x in 'MyType) returns 'MyComposedType ~firstVariant(x) "#, expect![[r#" - type MyType: MyType + type MyType: MyType - type MyComposedType: MyComposedType + type MyComposedType: MyComposedType - fn A: MyType - type constructor: MyType + fn A: MyType + type constructor: MyType - fn B: MyType - type constructor: MyType + fn B: MyType + type constructor: MyType - fn firstVariant: (MyType → MyComposedType) - type constructor: MyComposedType + fn firstVariant: (MyType → MyComposedType) + type constructor: MyComposedType - fn secondVariant: (int → MyType → infer t16 → MyComposedType) - type constructor: MyComposedType + fn secondVariant: (int → MyType → infer t16 → MyComposedType) + type constructor: MyComposedType - fn foo: (MyType → MyComposedType) - function call to functionid2 with args: someField: MyType, returns MyComposedType + fn foo: (MyType → MyComposedType) + function call to functionid2 with args: someField: MyType, returns MyComposedType - __MONOMORPHIZED FUNCTIONS__ - fn firstVariant(["MyType"]) -> MyComposedType + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(23), TypeVariable(24)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(243), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(14), TypeVariable(18)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(114), length: 9 } } } + TypeConstraint { kind: Unify(TypeVariable(17), TypeVariable(20)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(148), length: 10 } } } + TypeConstraint { kind: Satisfies(TypeVariable(10), TypeVariable(24)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(243), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(12), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(66), length: 33 } } } + TypeConstraint { kind: Axiom(TypeVariable(23)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(191), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(14)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(114), length: 9 } } } + TypeConstraint { kind: Axiom(TypeVariable(17)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(148), length: 10 } } } + + __MONOMORPHIZED FUNCTIONS__ + fn firstVariant(["MyType"]) -> MyComposedType - __SOLVED TYPES__ - 14: int - 17: infer t16 - 23: MyType"#]], + __SOLVED TYPES__ + 14: int + 17: infer t16 + 23: MyType"#]], ); } @@ -175,14 +194,18 @@ fn pass_zero_arity_func_to_intrinsic() { fn my_func() returns 'unit @puts(~string_literal)"#, expect![[r#" - fn string_literal: string - literal: "This is a string literal." + fn string_literal: string + literal: "This is a string literal." + + fn my_func: unit + intrinsic: @puts(function call to functionid0 with args: ) - fn my_func: unit - intrinsic: @puts(function call to functionid0 with args: ) + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(1), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(135), length: 16 } } } + TypeConstraint { kind: Satisfies(TypeVariable(6), TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(44), length: 38 } } } - __MONOMORPHIZED FUNCTIONS__ - fn string_literal([]) -> string"#]], + __MONOMORPHIZED FUNCTIONS__ + fn string_literal([]) -> string"#]], ); } @@ -193,12 +216,14 @@ fn pass_literal_string_to_intrinsic() { fn my_func() returns 'unit @puts("test")"#, expect![[r#" - fn my_func: unit - intrinsic: @puts(literal: "test") + fn my_func: unit + intrinsic: @puts(literal: "test") + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(1), TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(52), length: 6 } } } - __SOLVED TYPES__ - 5: string"#]], + __SOLVED TYPES__ + 5: string"#]], ); } @@ -209,12 +234,14 @@ fn pass_wrong_type_literal_to_intrinsic() { fn my_func() returns 'unit @puts(true)"#, expect![[r#" - fn my_func: unit - intrinsic: @puts(literal: true) + fn my_func: unit + intrinsic: @puts(literal: true) - __ERRORS__ + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(1), TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(52), length: 4 } } } - SpannedItem UnificationFailure("string", "true") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(52), length: 4 } }]"#]], + __ERRORS__ + SpannedItem UnificationFailure("string", "true") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(52), length: 4 } }]"#]], ); } @@ -225,12 +252,14 @@ fn intrinsic_and_return_ty_dont_match() { fn my_func() returns 'bool @puts("test")"#, expect![[r#" - fn my_func: bool - intrinsic: @puts(literal: "test") + fn my_func: bool + intrinsic: @puts(literal: "test") + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(1), TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(52), length: 6 } } } - __SOLVED TYPES__ - 5: string"#]], + __SOLVED TYPES__ + 5: string"#]], ); } @@ -244,17 +273,21 @@ fn pass_wrong_type_fn_call_to_intrinsic() { fn my_func() returns 'unit @puts(~bool_literal)"#, expect![[r#" - fn bool_literal: bool - literal: true + fn bool_literal: bool + literal: true - fn my_func: unit - intrinsic: @puts(function call to functionid0 with args: ) + fn my_func: unit + intrinsic: @puts(function call to functionid0 with args: ) - __MONOMORPHIZED FUNCTIONS__ - fn bool_literal([]) -> bool - __ERRORS__ + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(1), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(110), length: 14 } } } + TypeConstraint { kind: Satisfies(TypeVariable(6), TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(40), length: 17 } } } - SpannedItem UnificationFailure("string", "bool") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(110), length: 14 } }]"#]], + __MONOMORPHIZED FUNCTIONS__ + fn bool_literal([]) -> bool + + __ERRORS__ + SpannedItem UnificationFailure("string", "bool") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(110), length: 14 } }]"#]], ); } @@ -273,22 +306,31 @@ fn multiple_calls_to_fn_dont_unify_params_themselves() { ~bool_literal(true, false) "#, expect![[r#" - fn bool_literal: (infer t5 → infer t7 → bool) - literal: true + fn bool_literal: (infer t5 → infer t7 → bool) + literal: true - fn my_func: bool - function call to functionid0 with args: a: 1, b: 2, returns bool + fn my_func: bool + function call to functionid0 with args: a: 1, b: 2, returns bool - fn my_second_func: bool - function call to functionid0 with args: a: true, b: false, returns bool + fn my_second_func: bool + function call to functionid0 with args: a: true, b: false, returns bool - __MONOMORPHIZED FUNCTIONS__ - fn bool_literal(["int", "int"]) -> bool - fn bool_literal(["bool", "bool"]) -> bool + __CONSTRAINTS__ + TypeConstraint { kind: Satisfies(TypeVariable(6), TypeVariable(12)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(136), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(6), TypeVariable(17)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(278), length: 4 } } } + TypeConstraint { kind: Satisfies(TypeVariable(8), TypeVariable(13)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(138), length: 2 } } } + TypeConstraint { kind: Satisfies(TypeVariable(8), TypeVariable(18)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(283), length: 6 } } } + TypeConstraint { kind: Satisfies(TypeVariable(10), TypeVariable(9)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(56), length: 17 } } } + TypeConstraint { kind: Axiom(TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(25), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(8)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(34), length: 1 } } } - __SOLVED TYPES__ - 6: infer t5 - 8: infer t7"#]], + __MONOMORPHIZED FUNCTIONS__ + fn bool_literal(["int", "int"]) -> bool + fn bool_literal(["bool", "bool"]) -> bool + + __SOLVED TYPES__ + 6: infer t5 + 8: infer t7"#]], ); } #[test] @@ -298,13 +340,15 @@ fn list_different_types_type_err() { fn my_list() returns 'list [ 1, true ] "#, expect![[r#" - fn my_list: infer t8 - list: [literal: 1, literal: true, ] + fn my_list: infer t8 + list: [literal: 1, literal: true, ] + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(48), length: 5 } } } - __SOLVED TYPES__ - 5: (1 | true) - 6: 1"#]], + __SOLVED TYPES__ + 5: (1 | true) + 6: 1"#]], ); } @@ -317,15 +361,20 @@ fn incorrect_number_of_args() { fn add_five(a in 'int) returns 'int ~add(5) "#, expect![[r#" - fn add: (int → int → int) - variable a: int + fn add: (int → int → int) + variable a: int - fn add_five: (int → int) - error recovery Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(113), length: 8 } } + fn add_five: (int → int) + error recovery Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(113), length: 8 } } - __ERRORS__ + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(59), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(24), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(35), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(90), length: 1 } } } - SpannedItem ArgumentCountMismatch { function: "add", expected: 2, got: 1 } [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(113), length: 8 } }]"#]], + __ERRORS__ + SpannedItem ArgumentCountMismatch { function: "add", expected: 2, got: 1 } [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(113), length: 8 } }]"#]], ); } @@ -342,24 +391,35 @@ fn infer_let_bindings() { a fn main() returns 'int ~hi(1, 2)"#, expect![[r#" - fn hi: (int → int → int) - a: variable: symbolid2 (int), - b: variable: symbolid4 (int), - c: literal: 20 (20), - d: literal: 30 (30), - e: literal: 42 (42), - "variable a: int" (int) - - fn main: int - function call to functionid0 with args: x: 1, y: 2, returns int - - __MONOMORPHIZED FUNCTIONS__ - fn hi(["int", "int"]) -> int - fn main([]) -> int - - __SOLVED TYPES__ - 5: int - 6: int"#]], + fn hi: (int → int → int) + a: variable: symbolid2 (int), + b: variable: symbolid4 (int), + c: literal: 20 (20), + d: literal: 30 (30), + e: literal: 42 (42), + "variable a: int" (int) + + fn main: int + function call to functionid0 with args: x: 1, y: 2, returns int + + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(66), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(8)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(81), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(7), TypeVariable(12)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(136), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(5), TypeVariable(15)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(165), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(6), TypeVariable(16)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(167), length: 2 } } } + TypeConstraint { kind: Satisfies(TypeVariable(13), TypeVariable(12)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(53), length: 84 } } } + TypeConstraint { kind: Satisfies(TypeVariable(19), TypeVariable(13)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(160), length: 10 } } } + TypeConstraint { kind: Axiom(TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(19), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(30), length: 1 } } } + + __MONOMORPHIZED FUNCTIONS__ + fn hi(["int", "int"]) -> int + fn main([]) -> int + + __SOLVED TYPES__ + 5: int + 6: int"#]], ) } @@ -371,18 +431,27 @@ fn if_rejects_non_bool_condition() { if x then 1 else 2 fn main() returns 'int ~hi(1)"#, expect![[r#" - fn hi: (int → int) - if variable: symbolid2 then literal: 1 else literal: 2 + fn hi: (int → int) + if variable: symbolid2 then literal: 1 else literal: 2 - fn main: int - function call to functionid0 with args: x: 1, returns int + fn main: int + function call to functionid0 with args: x: 1, returns int - __MONOMORPHIZED FUNCTIONS__ - fn hi(["int"]) -> int - fn main([]) -> int - __ERRORS__ + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(2), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(61), length: 2 } } } + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(62), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(7), TypeVariable(8)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(75), length: 2 } } } + TypeConstraint { kind: Satisfies(TypeVariable(5), TypeVariable(11)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(117), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(9), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(42), length: 35 } } } + TypeConstraint { kind: Satisfies(TypeVariable(13), TypeVariable(9)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(112), length: 7 } } } + TypeConstraint { kind: Axiom(TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(19), length: 1 } } } - SpannedItem UnificationFailure("bool", "int") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(61), length: 2 } }]"#]], + __MONOMORPHIZED FUNCTIONS__ + fn hi(["int"]) -> int + fn main([]) -> int + + __ERRORS__ + SpannedItem UnificationFailure("bool", "int") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(61), length: 2 } }]"#]], ) } @@ -394,18 +463,24 @@ fn if_rejects_non_unit_missing_else() { if true then 1 fn main() returns 'int ~hi()"#, expect![[r#" - fn hi: int - if literal: true then literal: 1 else unit + fn hi: int + if literal: true then literal: 1 else unit + + fn main: int + function call to functionid0 with args: returns int - fn main: int - function call to functionid0 with args: returns int + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(2), TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(52), length: 5 } } } + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(0)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(33), length: 46 } } } + TypeConstraint { kind: Satisfies(TypeVariable(7), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(33), length: 46 } } } + TypeConstraint { kind: Satisfies(TypeVariable(8), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(99), length: 6 } } } - __MONOMORPHIZED FUNCTIONS__ - fn hi([]) -> int - fn main([]) -> int - __ERRORS__ + __MONOMORPHIZED FUNCTIONS__ + fn hi([]) -> int + fn main([]) -> int - SpannedItem UnificationFailure("1", "unit") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(33), length: 46 } }]"#]], + __ERRORS__ + SpannedItem UnificationFailure("1", "unit") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(33), length: 46 } }]"#]], ) } @@ -418,19 +493,26 @@ fn if_allows_unit_missing_else() { fn main() returns 'unit ~hi()"#, expect![[r#" - fn hi: unit - if literal: true then intrinsic: @puts(literal: "hi") else unit + fn hi: unit + if literal: true then intrinsic: @puts(literal: "hi") else unit - fn main: unit - function call to functionid0 with args: returns unit + fn main: unit + function call to functionid0 with args: returns unit - __MONOMORPHIZED FUNCTIONS__ - fn hi([]) -> unit - fn main([]) -> unit + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(0), TypeVariable(0)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(34), length: 56 } } } + TypeConstraint { kind: Unify(TypeVariable(1), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(74), length: 0 } } } + TypeConstraint { kind: Unify(TypeVariable(2), TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(53), length: 5 } } } + TypeConstraint { kind: Satisfies(TypeVariable(7), TypeVariable(0)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(34), length: 56 } } } + TypeConstraint { kind: Satisfies(TypeVariable(8), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(111), length: 6 } } } + + __MONOMORPHIZED FUNCTIONS__ + fn hi([]) -> unit + fn main([]) -> unit - __SOLVED TYPES__ - 5: bool - 6: string"#]], + __SOLVED TYPES__ + 5: bool + 6: string"#]], ) } @@ -444,20 +526,26 @@ fn disallow_incorrect_constant_int() { ~OneOrTwo 10 "#, expect![[r#" - type OneOrTwo: OneOrTwo + type OneOrTwo: OneOrTwo - fn OneOrTwo: ((1 | 2) → OneOrTwo) - type constructor: OneOrTwo + fn OneOrTwo: ((1 | 2) → OneOrTwo) + type constructor: OneOrTwo - fn main: OneOrTwo - function call to functionid0 with args: OneOrTwo: 10, returns OneOrTwo + fn main: OneOrTwo + function call to functionid0 with args: OneOrTwo: 10, returns OneOrTwo - __MONOMORPHIZED FUNCTIONS__ - fn OneOrTwo(["int"]) -> OneOrTwo - fn main([]) -> OneOrTwo - __ERRORS__ + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(17), length: 33 } } } + TypeConstraint { kind: Satisfies(TypeVariable(12), TypeVariable(8)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(75), length: 46 } } } + TypeConstraint { kind: Satisfies(TypeVariable(6), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(104), length: 0 } } } + TypeConstraint { kind: Axiom(TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(17), length: 33 } } } + + __MONOMORPHIZED FUNCTIONS__ + fn OneOrTwo(["int"]) -> OneOrTwo + fn main([]) -> OneOrTwo - SpannedItem NotSubtype(["1", "2"], "10") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(104), length: 0 } }]"#]], + __ERRORS__ + SpannedItem NotSubtype(["1", "2"], "10") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(104), length: 0 } }]"#]], ) } @@ -471,20 +559,26 @@ fn disallow_incorrect_constant_string() { ~AOrB "c" "#, expect![[r#" - type AOrB: AOrB + type AOrB: AOrB - fn AOrB: (("A" | "B") → AOrB) - type constructor: AOrB + fn AOrB: (("A" | "B") → AOrB) + type constructor: AOrB - fn main: AOrB - function call to functionid0 with args: AOrB: "c", returns AOrB + fn main: AOrB + function call to functionid0 with args: AOrB: "c", returns AOrB - __MONOMORPHIZED FUNCTIONS__ - fn AOrB(["string"]) -> AOrB - fn main([]) -> AOrB - __ERRORS__ + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(17), length: 33 } } } + TypeConstraint { kind: Satisfies(TypeVariable(12), TypeVariable(8)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(71), length: 43 } } } + TypeConstraint { kind: Satisfies(TypeVariable(6), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(97), length: 0 } } } + TypeConstraint { kind: Axiom(TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(17), length: 33 } } } - SpannedItem NotSubtype(["\"A\"", "\"B\""], "\"c\"") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(97), length: 0 } }]"#]], + __MONOMORPHIZED FUNCTIONS__ + fn AOrB(["string"]) -> AOrB + fn main([]) -> AOrB + + __ERRORS__ + SpannedItem NotSubtype(["\"A\"", "\"B\""], "\"c\"") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(97), length: 0 } }]"#]], ) } @@ -498,20 +592,26 @@ fn disallow_incorrect_constant_bool() { ~AlwaysTrue false "#, expect![[r#" - type AlwaysTrue: AlwaysTrue + type AlwaysTrue: AlwaysTrue - fn AlwaysTrue: ((true) → AlwaysTrue) - type constructor: AlwaysTrue + fn AlwaysTrue: ((true) → AlwaysTrue) + type constructor: AlwaysTrue - fn main: AlwaysTrue - function call to functionid0 with args: AlwaysTrue: false, returns AlwaysTrue + fn main: AlwaysTrue + function call to functionid0 with args: AlwaysTrue: false, returns AlwaysTrue - __MONOMORPHIZED FUNCTIONS__ - fn AlwaysTrue(["bool"]) -> AlwaysTrue - fn main([]) -> AlwaysTrue - __ERRORS__ + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(13), length: 30 } } } + TypeConstraint { kind: Satisfies(TypeVariable(12), TypeVariable(8)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(70), length: 43 } } } + TypeConstraint { kind: Satisfies(TypeVariable(6), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(100), length: 0 } } } + TypeConstraint { kind: Axiom(TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(13), length: 30 } } } - SpannedItem NotSubtype(["true"], "false") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(100), length: 0 } }]"#]], + __MONOMORPHIZED FUNCTIONS__ + fn AlwaysTrue(["bool"]) -> AlwaysTrue + fn main([]) -> AlwaysTrue + + __ERRORS__ + SpannedItem NotSubtype(["true"], "false") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(100), length: 0 } }]"#]], ) } @@ -524,17 +624,23 @@ fn disallow_wrong_sum_type_in_add() { fn add(a in 'IntBelowFive, b in 'IntBelowFive) returns 'IntBelowFive @add(a, b) "#, expect![[r#" - type IntBelowFive: IntBelowFive + type IntBelowFive: IntBelowFive - fn IntBelowFive: ((1 | 2 | 3 | 4 | 5) → IntBelowFive) - type constructor: IntBelowFive + fn IntBelowFive: ((1 | 2 | 3 | 4 | 5) → IntBelowFive) + type constructor: IntBelowFive - fn add: (IntBelowFive → IntBelowFive → IntBelowFive) - intrinsic: @add(variable: symbolid3, variable: symbolid4) + fn add: (IntBelowFive → IntBelowFive → IntBelowFive) + intrinsic: @add(variable: symbolid3, variable: symbolid4) - __ERRORS__ + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(3), TypeVariable(13)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(208), length: 2 } } } + TypeConstraint { kind: Unify(TypeVariable(11), TypeVariable(13)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(209), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(17), length: 117 } } } + TypeConstraint { kind: Axiom(TypeVariable(11)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(159), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(17), length: 117 } } } - SpannedItem UnificationFailure("int", "IntBelowFive") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(208), length: 2 } }]"#]], + __ERRORS__ + SpannedItem UnificationFailure("int", "IntBelowFive") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(208), length: 2 } }]"#]], ) } @@ -572,28 +678,47 @@ fn sum_type_unifies_to_superset() { zz ", expect![[r#" - fn test: ((1 | 2 | 3) → (1 | 2 | 3)) - variable a: (1 | 2 | 3) - - fn test_: ((1 | 2) → (1 | 2)) - variable a: (1 | 2) - - fn main: int - x: literal: 2 (2), - y: function call to functionid1 with args: symbolid1: variable: symbolid5, ((1 | 2)), - z: function call to functionid0 with args: symbolid1: variable: symbolid6, ((1 | 2 | 3)), - zz: function call to functionid0 with args: symbolid1: variable: symbolid5, ((1 | 2 | 3)), - "variable zz: (1 | 2 | 3)" ((1 | 2 | 3)) - - __MONOMORPHIZED FUNCTIONS__ - fn test(["int"]) -> (1 | 2 | 3) - fn test_(["int"]) -> (1 | 2) - fn main([]) -> int - - __SOLVED TYPES__ - 5: (1 | 2 | 3) - 9: (1 | 2) - 11: (1 | 2)"#]], + fn test: ((1 | 2 | 3) → (1 | 2 | 3)) + variable a: (1 | 2 | 3) + + fn test_: ((1 | 2) → (1 | 2)) + variable a: (1 | 2) + + fn main: int + x: literal: 2 (2), + y: function call to functionid1 with args: symbolid1: variable: symbolid5, ((1 | 2)), + z: function call to functionid0 with args: symbolid1: variable: symbolid6, ((1 | 2 | 3)), + zz: function call to functionid0 with args: symbolid1: variable: symbolid5, ((1 | 2 | 3)), + "variable zz: (1 | 2 | 3)" ((1 | 2 | 3)) + + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(52), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(17)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(52), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(7), TypeVariable(19)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(628), length: 2 } } } + TypeConstraint { kind: Unify(TypeVariable(9), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(113), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(10), TypeVariable(15)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(113), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(11), TypeVariable(16)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(431), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(13), TypeVariable(18)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(539), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(5), TypeVariable(16)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(431), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(5), TypeVariable(18)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(539), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(7), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(53), length: 0 } } } + TypeConstraint { kind: Satisfies(TypeVariable(9), TypeVariable(14)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(329), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(11), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(114), length: 0 } } } + TypeConstraint { kind: Satisfies(TypeVariable(20), TypeVariable(19)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(151), length: 479 } } } + TypeConstraint { kind: Axiom(TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(8), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(9)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(77), length: 1 } } } + + __MONOMORPHIZED FUNCTIONS__ + fn test(["int"]) -> (1 | 2 | 3) + fn test_(["int"]) -> (1 | 2) + fn main([]) -> int + + __SOLVED TYPES__ + 5: (1 | 2 | 3) + 6: (1 | 2 | 3) + 9: (1 | 2) + 10: (1 | 2) + 11: (1 | 2)"#]], ) } @@ -608,25 +733,39 @@ fn specific_type_generalizes() { 42 "#, expect![[r#" - fn test: ((int | string) → (int | string)) - variable a: (int | string) + fn test: ((int | string) → (int | string)) + variable a: (int | string) - fn test_: (int → (int | string)) - variable a: int + fn test_: (int → (int | string)) + variable a: int - fn main: int - x: function call to functionid1 with args: symbolid1: literal: 5, ((int | string)), - y: function call to functionid0 with args: symbolid1: literal: "a string", ((int | string)), - "literal: 42" (42) + fn main: int + x: function call to functionid1 with args: symbolid1: literal: 5, ((int | string)), + y: function call to functionid0 with args: symbolid1: literal: "a string", ((int | string)), + "literal: 42" (42) + + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(62), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(16)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(62), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(9), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(127), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(10), TypeVariable(14)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(127), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(5), TypeVariable(15)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(234), length: 10 } } } + TypeConstraint { kind: Satisfies(TypeVariable(7), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(63), length: 0 } } } + TypeConstraint { kind: Satisfies(TypeVariable(9), TypeVariable(13)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(199), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(11), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(128), length: 0 } } } + TypeConstraint { kind: Satisfies(TypeVariable(18), TypeVariable(17)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(166), length: 100 } } } + TypeConstraint { kind: Axiom(TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(8), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(9)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(88), length: 1 } } } - __MONOMORPHIZED FUNCTIONS__ - fn test(["string"]) -> (int | string) - fn test_(["int"]) -> (int | string) - fn main([]) -> int + __MONOMORPHIZED FUNCTIONS__ + fn test(["string"]) -> (int | string) + fn test_(["int"]) -> (int | string) + fn main([]) -> int - __SOLVED TYPES__ - 5: (int | string) - 9: int"#]], + __SOLVED TYPES__ + 5: (int | string) + 9: int + 16: (int | string)"#]], ) } @@ -641,23 +780,36 @@ fn disallow_bad_generalization() { 42 "#, expect![[r#" - fn test: ((int | string) → (int | string)) - variable a: (int | string) + fn test: ((int | string) → (int | string)) + variable a: (int | string) - fn test_: (bool → (int | string)) - variable a: bool + fn test_: (bool → (int | string)) + variable a: bool - fn main: int - y: function call to functionid0 with args: symbolid1: function call to functionid1 with args: symbolid1: literal: true, , ((int | string)), - "literal: 42" (42) + fn main: int + y: function call to functionid0 with args: symbolid1: function call to functionid1 with args: symbolid1: literal: true, , ((int | string)), + "literal: 42" (42) + + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(62), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(15)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(62), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(9), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(128), length: 1 } } } + TypeConstraint { kind: Unify(TypeVariable(10), TypeVariable(14)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(128), length: 1 } } } + TypeConstraint { kind: Satisfies(TypeVariable(5), TypeVariable(11)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(306), length: 12 } } } + TypeConstraint { kind: Satisfies(TypeVariable(7), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(63), length: 0 } } } + TypeConstraint { kind: Satisfies(TypeVariable(9), TypeVariable(13)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(313), length: 4 } } } + TypeConstraint { kind: Satisfies(TypeVariable(11), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(129), length: 0 } } } + TypeConstraint { kind: Satisfies(TypeVariable(17), TypeVariable(16)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(167), length: 173 } } } + TypeConstraint { kind: Axiom(TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(8), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(9)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(88), length: 1 } } } - __MONOMORPHIZED FUNCTIONS__ - fn test(["(int | string)"]) -> (int | string) - fn test_(["bool"]) -> (int | string) - fn main([]) -> int - __ERRORS__ + __MONOMORPHIZED FUNCTIONS__ + fn test(["(int | string)"]) -> (int | string) + fn test_(["bool"]) -> (int | string) + fn main([]) -> int - SpannedItem NotSubtype(["int", "string"], "bool") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(129), length: 0 } }]"#]], + __ERRORS__ + SpannedItem NotSubtype(["int", "string"], "bool") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(129), length: 0 } }]"#]], ) } @@ -667,12 +819,15 @@ fn order_of_sum_type_doesnt_matter() { r#"fn test(a in 'sum 'int | 'string) returns 'sum 'string | 'int a "#, expect![[r#" - fn test: ((int | string) → (int | string)) - variable a: (int | string) + fn test: ((int | string) → (int | string)) + variable a: (int | string) + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(62), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(8), length: 1 } } } - __SOLVED TYPES__ - 5: (int | string)"#]], + __SOLVED TYPES__ + 5: (int | string)"#]], ) } @@ -682,12 +837,15 @@ fn can_return_superset() { r#"fn test(a in 'sum 'int | 'string) returns 'sum 'string | 'int | 'bool a "#, expect![[r#" - fn test: ((int | string) → (int | bool | string)) - variable a: (int | string) + fn test: ((int | string) → (int | bool | string)) + variable a: (int | string) + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(5), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(70), length: 1 } } } + TypeConstraint { kind: Axiom(TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(8), length: 1 } } } - __SOLVED TYPES__ - 5: (int | string)"#]], + __SOLVED TYPES__ + 5: (int | string)"#]], ) } @@ -699,6 +857,11 @@ fn if_exp_basic() { fn main: int if literal: true then literal: 1 else literal: 0 + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(2), TypeVariable(5)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(25), length: 5 } } } + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(42), length: 2 } } } + TypeConstraint { kind: Satisfies(TypeVariable(8), TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(22), length: 22 } } } + __MONOMORPHIZED FUNCTIONS__ fn main([]) -> int @@ -708,3 +871,38 @@ fn if_exp_basic() { 7: 1"#]], ); } + +#[test] +fn passthrough_type_should_get_rejected() { + check( + r#" +fn main() returns 'int + ~f(true) +fn f(expr in 'A) returns 'int + expr + "#, + expect![[r#" + fn main: int + function call to functionid1 with args: expr: true, returns int + + fn f: (infer t12 → int) + variable expr: infer t12 + + __CONSTRAINTS__ + TypeConstraint { kind: Unify(TypeVariable(6), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(70), length: 4 } } } + TypeConstraint { kind: Unify(TypeVariable(7), TypeVariable(10)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(70), length: 4 } } } + TypeConstraint { kind: Unify(TypeVariable(13), TypeVariable(14)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(70), length: 4 } } } + TypeConstraint { kind: Satisfies(TypeVariable(6), TypeVariable(9)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(31), length: 4 } } } + TypeConstraint { kind: Satisfies(TypeVariable(8), TypeVariable(7)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(66), length: 8 } } } + TypeConstraint { kind: Satisfies(TypeVariable(11), TypeVariable(8)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(23), length: 13 } } } + TypeConstraint { kind: Axiom(TypeVariable(6)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(42), length: 4 } } } + TypeConstraint { kind: Axiom(TypeVariable(13)), span: Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(42), length: 4 } } } + + __MONOMORPHIZED FUNCTIONS__ + fn main([]) -> int + fn f(["bool"]) -> int + + __ERRORS__ + SpannedItem FailedToSatisfy("int", "bool") [Span { source: SourceId(0), span: SourceSpan { offset: SourceOffset(66), length: 8 } }]"#]], + ) +} diff --git a/petr-vm/src/tests.rs b/petr-vm/src/tests.rs index b91eebbe42e00d016cb5e9b235505fa6c4d859ab..5ad43b56dfa42bb50ce1f7a0a25c4f8b8bf45c34 100644 GIT binary patch delta 1250 zcma)*&ubGw6vy#LO|p$*sW!9-eccF6Xqu*>Cu`B7AVM(*>#2mW-JQ)2Zf4@lY)kEr zP=p@5&cES7=-t1;n_k7U;2%)IHl&P4_ zm0WL|CkhmYc$X95mRI$cvBu1Zyhs@AbllU~8z=tr;P`oA}T>fJc$F?<>GY}<~OKRfKfsGL`b?rDzF<-KQb}jOC)7&@IdW#3CKOE zNC!fX#)%+RD_z`DM5?BJ4;iX-1uvCh-v$8myUc{<*^EBUO+5n)E1@QTs%U-ZxHtp4GQ>zQ2s{y$B$>ZuKVoSD>(!X155 znAD&1*CXp=rG{4i+sLM|bHT`<37c92(~EtL4N=QgpWU6-XW7Z2+Y8fpnRL5cDDZrr zNDDfoBn~9-(2RqNKrSK9An|b%Z~*dBv&AjiW?YbzKT}bQ#H7=rtYv(p49pt%`XqA; zLEP^B3I7ET$SCTzm^pU-9KZ(gLMP4Oz=s7(3@C$nT;B><0OrdeDPt=t5lrGxhX)^9 zNgBc$FsR{H5@ruo?eIw~2^^)ui}=rSU$knLqKqo~Q=#(EEjF8#p(L0vz+vCqg{=(f zHfKngra0_M@NBlXzX+RR5AYV@inYNFx#)Px1>-p*4 z->_=fti?14FB@6JGz?o7F;Ngl5+PfJt2SsLi8NS04=zCI=yG)tbUu*+3K0uQ!5Jci%JwGKj*Zcti&NY*@l;UvmMu8UZz?u0FAB> AxBvhE From 2cd673f0ecb5963dca2bb71da4db0b638c7741af Mon Sep 17 00:00:00 2001 From: sezna Date: Sun, 4 Aug 2024 12:02:50 -0700 Subject: [PATCH 04/12] found the inf loop -- need to debug --- petr-typecheck/src/constraint_generation.rs | 5 +++++ petr-typecheck/src/lib.rs | 2 ++ petr-vm/src/tests.rs | Bin 7651 -> 7793 bytes 3 files changed, 7 insertions(+) diff --git a/petr-typecheck/src/constraint_generation.rs b/petr-typecheck/src/constraint_generation.rs index ad83f77..39f59a7 100644 --- a/petr-typecheck/src/constraint_generation.rs +++ b/petr-typecheck/src/constraint_generation.rs @@ -251,9 +251,12 @@ impl TypeConstraintContext { }, ); } + println!("generating constraints for functions"); for (id, func) in self.resolved.functions() { + println!("checking func {}", func.name.id); let typed_function = func.type_check(self); + println!("done"); let ty = self.arrow_type([typed_function.params.iter().map(|(_, b)| *b).collect(), vec![typed_function.return_ty]].concat()); self.type_map.insert(id.into(), ty); @@ -262,6 +265,7 @@ impl TypeConstraintContext { // type check the main func with no params let main_func = self.get_main_function(); // construct a function call for the main function, if one exists + println!("generating constraints for main function call"); if let Some((id, func)) = main_func { let call = petr_resolve::FunctionCall { function: id, @@ -671,6 +675,7 @@ impl GenerateTypeConstraints for petr_resolve::Function { } // unify types within the body with the parameter + println!("checking body"); let body = self.body.type_check(ctx); let declared_return_type = ctx.to_type_var(&self.return_type); diff --git a/petr-typecheck/src/lib.rs b/petr-typecheck/src/lib.rs index f5d5a69..54bd1cb 100644 --- a/petr-typecheck/src/lib.rs +++ b/petr-typecheck/src/lib.rs @@ -29,8 +29,10 @@ pub type TResult = Result; pub fn type_check(resolved: QueryableResolvedItems) -> Result>> { let mut type_checker = TypeConstraintContext::new(resolved); + println!("generating constraints"); type_checker.fully_type_check(); + println!("solving"); type_checker.into_solution() } diff --git a/petr-vm/src/tests.rs b/petr-vm/src/tests.rs index 5ad43b56dfa42bb50ce1f7a0a25c4f8b8bf45c34..03760b03344e89034aa399b81d2503aa35c5ec5c 100644 GIT binary patch delta 190 zcmaEC{n2JaKci4VQD$CAPM)HMQbA%-ab{k+(&P@tACvE}a`6C1GOK_p p6p)lo{=upzg;124msyetQ&yI%G+Ca_kiDohFAu0~a|~O%H~?!BLXZFe delta 84 zcmexp^VoVrKjY*Tj2|a&VcM{{nOTH!^G22_j0zx7P?VWhl9Q*Xp_G`EUs|G2lCO|c fo}XHzq-o7HIg3qi@=i7_9=KFdY2M~nZ0+IzPzfHB From d4ad938cc8426782a422900ab699f04b12aa5e50 Mon Sep 17 00:00:00 2001 From: sezna Date: Sun, 4 Aug 2024 16:03:12 -0700 Subject: [PATCH 05/12] wip --- petr-codegen/src/lib.rs | 4 +- petr-ir/src/lib.rs | 90 ++++++++++++++++--- petr-typecheck/src/constraint_generation.rs | 91 +++++++++++++------- petr-typecheck/src/pretty_printing.rs | 2 +- petr-typecheck/src/solution.rs | 2 +- petr-typecheck/src/typed_ast.rs | 2 +- petr-vm/src/tests.rs | Bin 7793 -> 7846 bytes 7 files changed, 144 insertions(+), 47 deletions(-) diff --git a/petr-codegen/src/lib.rs b/petr-codegen/src/lib.rs index 6231e4d..5fd8684 100644 --- a/petr-codegen/src/lib.rs +++ b/petr-codegen/src/lib.rs @@ -80,7 +80,7 @@ impl IrContext { pub fn add_function( &mut self, func_name: &str, - function: petr_ir::Function, + function: petr_ir::LoweredFunction, ) -> Result<(), IrError> { let sig = self.module.make_signature(); let func_id = self @@ -121,7 +121,7 @@ impl IrContext { fn lower_function_body( &self, - _function: &petr_ir::Function, + _function: &petr_ir::LoweredFunction, _builder: &mut FunctionBuilder, ) -> Result<(), IrError> { todo!() diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index 2d26d3d..3896ed6 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -24,7 +24,7 @@ pub fn lower(solution: TypeSolution) -> Result<(DataSection, Vec)> { Ok(lowerer.finalize()) } -pub struct Function { +pub struct LoweredFunction { body: Vec, } @@ -38,9 +38,12 @@ pub struct Lowerer { data_section: DataSection, entry_point: Option, reg_assigner: usize, + /// This assigns IDs to function signatures, which are then pulled from the type solution and + /// lowered in a later stage + monomorphized_functions: IndexMap, + lowered_monomorphized_functions: BTreeMap, type_solution: TypeSolution, variables_in_scope: Vec>, - monomorphized_functions: IndexMap, errors: Vec>, label_assigner: usize, } @@ -65,9 +68,10 @@ impl Lowerer { reg_assigner: 0, type_solution, variables_in_scope: Default::default(), - monomorphized_functions: Default::default(), label_assigner: 0, errors: Default::default(), + monomorphized_functions: Default::default(), + lowered_monomorphized_functions: Default::default(), }; let monomorphized_entry_point_id = match entry_point { @@ -84,7 +88,7 @@ impl Lowerer { /// Consumes the [`Lowerer`], performing lowering to IR and returning the data section and /// program section. - pub fn finalize(self) -> (DataSection, Vec) { + pub fn finalize(mut self) -> (DataSection, Vec) { let mut program_section = vec![]; // insert jump to entry point as first instr @@ -96,10 +100,64 @@ impl Lowerer { program_section.push(IrOpcode::ReturnImmediate(0)); } + let owned_monomorphized_functions = self.monomorphized_functions.iter().map(|(a, b)| (a, b.clone())).collect::>(); + + for (label, sig) in owned_monomorphized_functions { + program_section.push(IrOpcode::FunctionLabel(dbg!(label))); + // lower the function body + let func_def = self + .type_solution + .monomorphized_functions + .get(&sig) + .expect("type checker should have this function signature") + .clone(); + + self.with_variable_context(|ctx| -> Result<_> { + // Pop parameters off the stack in reverse order -- the last parameter for the function + // will be the first thing popped off the stack + // When we lower a function call, we push them onto the stack from first to last. Since + // the stack is FILO, we reverse that order here. + + for (param_ty, (param_name, _)) in sig.1.iter().zip(func_def.params).rev() { + let param_ty = ctx.lower_type(param_ty.clone()); + // in order, assign parameters to registers + if param_ty.is_copy_type() { + // load from stack into register + let param_reg = ctx.fresh_reg(); + let ty_reg = TypedReg { + ty: param_ty.clone(), + reg: param_reg, + }; + program_section.push(IrOpcode::StackPop(ty_reg)); + // insert param into mapping + + ctx.insert_var(¶m_name, param_reg); + } else { + todo!("make reg a ptr to the value") + } + } + + let return_reg = ctx.fresh_reg(); + let return_dest = ReturnDestination::Reg(return_reg); + let mut expr_body = ctx.lower_expr(&func_def.body, return_dest)?; + program_section.append(&mut expr_body); + // load return value into func return register + + program_section.push(IrOpcode::Copy(Reg::Reserved(ReservedRegister::ReturnValueRegister), return_reg)); + + // jump back to caller + program_section.push(IrOpcode::Return()); + Ok(()) + }) + .unwrap(); + } + + /* for (label, (_signature, mut function)) in self.monomorphized_functions.into_iter() { program_section.push(IrOpcode::FunctionLabel(label)); program_section.append(&mut function.body); } + */ (self.data_section.clone(), program_section) } @@ -109,10 +167,19 @@ impl Lowerer { &mut self, func: FunctionSignature, ) -> Result { - if let Some(previously_monomorphized_definition) = self.monomorphized_functions.iter().find(|(_id, (sig, _))| *sig == func) { - return Ok(previously_monomorphized_definition.0); - } - + println!("inserting monomorphized func"); + return Ok(dbg!(self.monomorphized_functions.insert(func))); + panic!("type checker didn't type check this monomorphization"); + /* + + // otherwise, queue up this one + self.monomorphized_function_queue + .insert((func.clone(), self.type_solution.functions(&func).clone())); + + todo!( + "The problem is that before we are able to actually insert a monomorphized recursive function, we + get caught up in dealing with the fn body" + ); let func_def = self.type_solution.get_monomorphized_function(&func).clone(); let mut buf = vec![]; @@ -143,7 +210,9 @@ impl Lowerer { let return_reg = ctx.fresh_reg(); let return_dest = ReturnDestination::Reg(return_reg); + println!("before recursive call"); let mut expr_body = ctx.lower_expr(&func_def.body, return_dest)?; + println!("afterrecursive call"); buf.append(&mut expr_body); // load return value into func return register @@ -154,6 +223,7 @@ impl Lowerer { Ok(ctx.monomorphized_functions.insert((func, Function { body: buf }))) }) + */ } fn fresh_reg(&mut self) -> Reg { @@ -279,7 +349,6 @@ impl Lowerer { // the memory model for types is currently not finalized, // but for now, it is just sequential memory that is word-aligned - println!("lowering type constructor for ir type {ir_ty:?}"); let size_of_aggregate_type = ir_ty.size(); let ReturnDestination::Reg(return_destination) = return_destination; @@ -539,12 +608,13 @@ impl Lowerer { } else { result.push_str("\tNO ENTRY POINT\n"); } - for (id, (_sig, func)) in self.monomorphized_functions.iter() { + for (id, sig) in self.monomorphized_functions.iter() { result.push_str(&format!( "{}function {}:\n", if Some(id) == self.entry_point { "ENTRY: " } else { "" }, Into::::into(id) )); + let func = self.lowered_monomorphized_functions.get(sig).unwrap(); for opcode in &func.body { result.push_str(&format!(" {pc}\t{}\n", opcode)); pc += 1; diff --git a/petr-typecheck/src/constraint_generation.rs b/petr-typecheck/src/constraint_generation.rs index 39f59a7..5ce34aa 100644 --- a/petr-typecheck/src/constraint_generation.rs +++ b/petr-typecheck/src/constraint_generation.rs @@ -76,6 +76,9 @@ pub type FunctionSignature = (FunctionId, Box<[GeneralType]>); pub struct TypeConstraintContext { type_map: BTreeMap, + // functions that will be monomorphized + monomorphization_queue: BTreeMap, + // actually resolved function bodies for monomorphization monomorphized_functions: BTreeMap, typed_functions: BTreeMap, errors: Vec, @@ -251,12 +254,9 @@ impl TypeConstraintContext { }, ); } - println!("generating constraints for functions"); for (id, func) in self.resolved.functions() { - println!("checking func {}", func.name.id); let typed_function = func.type_check(self); - println!("done"); let ty = self.arrow_type([typed_function.params.iter().map(|(_, b)| *b).collect(), vec![typed_function.return_ty]].concat()); self.type_map.insert(id.into(), ty); @@ -265,7 +265,6 @@ impl TypeConstraintContext { // type check the main func with no params let main_func = self.get_main_function(); // construct a function call for the main function, if one exists - println!("generating constraints for main function call"); if let Some((id, func)) = main_func { let call = petr_resolve::FunctionCall { function: id, @@ -278,6 +277,28 @@ impl TypeConstraintContext { // before applying existing constraints, it is likely that many duplicate constraints // exist. We can safely remove any duplicate constraints to avoid excessive error // reporting. + + // add constraints from all monomorphizations + + // TODO figure out how to drain a btreemap instead here to avoid the clone + for (sig, monomorphic_func) in self.monomorphization_queue.clone() { + let monomorphized_func_decl = self.get_untyped_function(monomorphic_func.func_id).clone(); + let mut monomorphized_func_decl = monomorphized_func_decl.type_check(self); + // instantiate the param types with the args from the monomorphized func + let mut num_replacements = 0; + let new_constraints = replace_var_reference_types( + &mut monomorphized_func_decl.body.kind, + &monomorphized_func_decl.params, + &mut num_replacements, + ); + + self.monomorphized_functions.insert(sig, monomorphized_func_decl); + + for constraint in new_constraints { + self.constraints.push(constraint); + } + } + self.deduplicate_constraints(); } @@ -340,6 +361,7 @@ impl TypeConstraintContext { typed_functions: Default::default(), resolved, variable_scope: Default::default(), + monomorphization_queue: Default::default(), monomorphized_functions: Default::default(), types, constraints: Default::default(), @@ -442,6 +464,7 @@ impl TypeConstraintContext { self.resolved.get_function(function) } + // TODO deprecate this pub fn get_function( &mut self, id: &FunctionId, @@ -460,8 +483,8 @@ impl TypeConstraintContext { pub fn get_monomorphized_function( &self, id: &FunctionSignature, - ) -> &Function { - self.monomorphized_functions.get(id).expect("invariant: should exist") + ) -> &MonomorphizedFunction { + self.monomorphization_queue.get(id).expect("invariant: should exist") } // TODO unideal clone @@ -593,16 +616,16 @@ impl TypeConstraintContext { self.errors.push(e); } - pub fn monomorphized_functions(&self) -> &BTreeMap { - &self.monomorphized_functions + pub fn monomorphized_functions(&self) -> &BTreeMap { + &self.monomorphization_queue } pub(crate) fn insert_monomorphized_function( &mut self, signature: (FunctionId, Box<[GeneralType]>), - monomorphized_func_decl: Function, + monomorphized_func_decl: MonomorphizedFunction, ) { - self.monomorphized_functions.insert(signature, monomorphized_func_decl); + self.monomorphization_queue.insert(signature, monomorphized_func_decl); } pub fn type_map(&self) -> &BTreeMap { @@ -622,6 +645,14 @@ impl TypeConstraintContext { } } +#[derive(Clone)] +pub struct MonomorphizedFunction { + pub name: Identifier, + pub params: Vec<(Identifier, TypeVariable)>, + pub return_type: TypeVariable, + pub func_id: FunctionId, +} + /// the `key` type is what we use to deduplicate constraints #[derive(Default)] struct ConstraintDeduplicator { @@ -675,7 +706,6 @@ impl GenerateTypeConstraints for petr_resolve::Function { } // unify types within the body with the parameter - println!("checking body"); let body = self.body.type_check(ctx); let declared_return_type = ctx.to_type_var(&self.return_type); @@ -697,7 +727,7 @@ impl GenerateTypeConstraints for FunctionCall { &self, ctx: &mut TypeConstraintContext, ) -> Self::Output { - let func_decl = ctx.get_function(&self.function).clone(); + let func_decl = ctx.get_untyped_function(self.function).clone(); if self.args.len() != func_decl.params.len() { // TODO: support partial application @@ -711,8 +741,13 @@ impl GenerateTypeConstraints for FunctionCall { let mut args: Vec<(Identifier, TypedExpr, TypeVariable)> = Vec::with_capacity(self.args.len()); + let params = func_decl + .params + .iter() + .map(|(ident, ty)| (*ident, ctx.to_type_var(ty))) + .collect::>(); // unify all of the arg types with the param types - for (arg, (name, param_ty)) in self.args.iter().zip(func_decl.params.iter()) { + for (arg, (name, param_ty)) in self.args.iter().zip(params.iter()) { let arg = arg.type_check(ctx); let arg_ty = ctx.expr_ty(&arg); ctx.satisfies(*param_ty, arg_ty, arg.span()); @@ -727,28 +762,29 @@ impl GenerateTypeConstraints for FunctionCall { let signature: FunctionSignature = (self.function, concrete_arg_types.clone().into_boxed_slice()); // now that we know the argument types, check if this signature has been monomorphized // already + let declared_return_type = ctx.to_type_var(&func_decl.return_type); + // insert axiom for return type + ctx.axiom(declared_return_type, self.span()); + if ctx.monomorphized_functions().contains_key(&signature) { return TypedExprKind::FunctionCall { func: self.function, args: args.into_iter().map(|(name, expr, _)| (name, expr)).collect(), - ty: func_decl.return_ty, + ty: declared_return_type, }; } // unify declared return type with body return type - let declared_return_type = func_decl.return_ty; - - ctx.satisfy_expr_return(declared_return_type, &func_decl.body); + // ctx.satisfy_expr_return(declared_return_type, &func_decl.body); // to create a monomorphized func decl, we don't actually have to update all of the types // throughout the entire definition. We only need to update the parameter types. - let mut monomorphized_func_decl = Function { - name: func_decl.name, - params: func_decl.params.clone(), - return_ty: declared_return_type, - body: func_decl.body.clone(), + let mut monomorphized_func_decl = MonomorphizedFunction { + name: func_decl.name, + params: params.clone(), + return_type: declared_return_type, + func_id: self.function, }; - // todo!("figure out how to re-do type check OR use replace_var_types to generate more constraints, maybe via unification?"); // update the parameter types to be the concrete types for (param, concrete_ty) in monomorphized_func_decl.params.iter_mut().zip(concrete_arg_types.iter()) { @@ -758,14 +794,6 @@ impl GenerateTypeConstraints for FunctionCall { // if there are any variable exprs in the body, update those ref types let mut num_replacements = 0; - let new_constraints = replace_var_reference_types( - &mut monomorphized_func_decl.body.kind, - &monomorphized_func_decl.params, - &mut num_replacements, - ); - for constraint in new_constraints { - ctx.constraints.push(constraint); - } // re-do constraint generation on replaced func body // monomorphized_func_decl.body.type_check(ctx); @@ -904,7 +932,6 @@ fn replace_var_reference_types( TypedExprKind::Variable { ref mut ty, name } => { if let Some((_param_name, ty_var)) = params.iter().find(|(param_name, _)| param_name.id == name.id) { *num_replacements += 1; - println!("replacing var named {} ", name.id); // *ty = *ty_var; return vec![TypeConstraint::unify(*ty, *ty_var, name.span)]; } diff --git a/petr-typecheck/src/pretty_printing.rs b/petr-typecheck/src/pretty_printing.rs index 07a0dcc..f49c2d3 100644 --- a/petr-typecheck/src/pretty_printing.rs +++ b/petr-typecheck/src/pretty_printing.rs @@ -61,7 +61,7 @@ pub fn pretty_print_type_checker(type_checker: &TypeConstraintContext) -> String "\nfn {}({:?}) -> {}", func_name, arg_types, - pretty_print_ty(&func.return_ty, type_checker.types(), &type_checker.resolved().interner) + pretty_print_ty(&func.return_type, type_checker.types(), &type_checker.resolved().interner) )); } diff --git a/petr-typecheck/src/solution.rs b/petr-typecheck/src/solution.rs index 8477847..3b56b6f 100644 --- a/petr-typecheck/src/solution.rs +++ b/petr-typecheck/src/solution.rs @@ -61,7 +61,7 @@ pub struct TypeSolution { error_recovery: TypeVariable, unit: TypeVariable, functions: BTreeMap, - monomorphized_functions: BTreeMap, + pub monomorphized_functions: BTreeMap, } impl TypeSolution { diff --git a/petr-typecheck/src/typed_ast.rs b/petr-typecheck/src/typed_ast.rs index 2b0c51a..9bd9444 100644 --- a/petr-typecheck/src/typed_ast.rs +++ b/petr-typecheck/src/typed_ast.rs @@ -225,7 +225,7 @@ impl GenerateTypeConstraints for Expr { let else_branch = else_branch.type_check(ctx); let else_ty = ctx.expr_ty(&else_branch); - ctx.unify(then_ty, else_ty, else_branch.span()); + ctx.unify(else_ty, then_ty, else_branch.span()); TypedExprKind::If { condition: Box::new(condition), diff --git a/petr-vm/src/tests.rs b/petr-vm/src/tests.rs index 03760b03344e89034aa399b81d2503aa35c5ec5c..853677509a9f72758ce9a26705587dea3e4ab625 100644 GIT binary patch delta 143 zcmexpv&?owKjY*Tj2|a&VcM{{nOTH!^G24LjFa=(G$-$5Q`r2KtxDWP0SJ_%OHzwV zV!1%`TK_~MNG(wvmMlKA41#N_Pw{Ib-dw4D6%$%WD?9AJe7MfsEK hWsQWuTI-5ZfqV^Jg**jA&B+DalI#!x)5-dBP5|F2F{A(h delta 214 zcmZ2x`_X1YKci4VQD$CAPM)HMQbA%-ab{k+(&P@tA7TjMqSWI2oHCGvruF1%rma#4 zsglZqRE6Y>)Z}c4^k!{lNk&nGd`^COD$sb4&dv5L^B5HnQfZlai8+~7KuHB8Rg-V9 o>LHn*nU`6T2~$> Date: Sun, 4 Aug 2024 17:24:38 -0700 Subject: [PATCH 06/12] wip: identify problem in lowering} --- petr-ir/src/lib.rs | 77 +++++++++++++-------- petr-typecheck/src/constraint_generation.rs | 14 +--- petr-typecheck/src/lib.rs | 2 - petr-typecheck/src/solution.rs | 6 +- petr-vm/src/lib.rs | 3 + 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index 3896ed6..bc95d48 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -41,7 +41,7 @@ pub struct Lowerer { /// This assigns IDs to function signatures, which are then pulled from the type solution and /// lowered in a later stage monomorphized_functions: IndexMap, - lowered_monomorphized_functions: BTreeMap, + lowered_monomorphized_functions: BTreeMap, type_solution: TypeSolution, variables_in_scope: Vec>, errors: Vec>, @@ -82,28 +82,27 @@ impl Lowerer { }, }; + // The problem is that this generates more functions in the monomorphization queue, + // how do I solve that? + // TODO remove this clone -- it is expensive + for (sig, func) in lowerer.type_solution.monomorphized_functions.clone().into_iter() { + // println!("monomorphizing function {:?}", sig); + lowerer.monomorphize_function(sig)?; + } + lowerer.entry_point = monomorphized_entry_point_id; Ok(lowerer) } - /// Consumes the [`Lowerer`], performing lowering to IR and returning the data section and - /// program section. - pub fn finalize(mut self) -> (DataSection, Vec) { - let mut program_section = vec![]; - - // insert jump to entry point as first instr - if let Some(entry_point) = self.entry_point { - program_section.push(IrOpcode::JumpImmediateFunction(entry_point)); - } else { - // TODO use diagnostics here - eprintln!("Warning: Generating IR for program with no entry point"); - program_section.push(IrOpcode::ReturnImmediate(0)); - } - + /// Drains `self.monomorphized_functions` and lowers them into + /// `self.lowered_monomorphized_functions` + fn lower_monomorphized_functions(&mut self) { let owned_monomorphized_functions = self.monomorphized_functions.iter().map(|(a, b)| (a, b.clone())).collect::>(); + use petr_typecheck::Type; + for (label, sig) in owned_monomorphized_functions { - program_section.push(IrOpcode::FunctionLabel(dbg!(label))); + let mut buf = vec![]; // lower the function body let func_def = self .type_solution @@ -112,6 +111,8 @@ impl Lowerer { .expect("type checker should have this function signature") .clone(); + buf.push(IrOpcode::Comment(format!("fn {}: {} ({})", Into::::into(label), self.type_solution.interner().get(func_def.name.id), sig.1.iter().map(|ty| self.type_solution.pretty_print_type(&ty.as_specific_ty())).collect::>().join(", ")))); + self.with_variable_context(|ctx| -> Result<_> { // Pop parameters off the stack in reverse order -- the last parameter for the function // will be the first thing popped off the stack @@ -128,7 +129,7 @@ impl Lowerer { ty: param_ty.clone(), reg: param_reg, }; - program_section.push(IrOpcode::StackPop(ty_reg)); + buf.push(IrOpcode::StackPop(ty_reg)); // insert param into mapping ctx.insert_var(¶m_name, param_reg); @@ -140,24 +141,45 @@ impl Lowerer { let return_reg = ctx.fresh_reg(); let return_dest = ReturnDestination::Reg(return_reg); let mut expr_body = ctx.lower_expr(&func_def.body, return_dest)?; - program_section.append(&mut expr_body); + buf.append(&mut expr_body); // load return value into func return register - program_section.push(IrOpcode::Copy(Reg::Reserved(ReservedRegister::ReturnValueRegister), return_reg)); + buf.push(IrOpcode::Copy(Reg::Reserved(ReservedRegister::ReturnValueRegister), return_reg)); // jump back to caller - program_section.push(IrOpcode::Return()); + buf.push(IrOpcode::Return()); Ok(()) }) .unwrap(); + + self.lowered_monomorphized_functions.insert(label, LoweredFunction { body: buf }); } + } - /* - for (label, (_signature, mut function)) in self.monomorphized_functions.into_iter() { + /// Consumes the [`Lowerer`], performing lowering to IR and returning the data section and + /// program section. + pub fn finalize(mut self) -> (DataSection, Vec) { + + // first, codegen all monomorphized functions. this populates + // `lowered_monomorphized_functions` + self.lower_monomorphized_functions(); + self.lower_monomorphized_functions(); + + let mut program_section = vec![]; + + // insert jump to entry point as first instr + if let Some(entry_point) = self.entry_point { + program_section.push(IrOpcode::JumpImmediateFunction(entry_point)); + } else { + // TODO use diagnostics here + eprintln!("Warning: Generating IR for program with no entry point"); + program_section.push(IrOpcode::ReturnImmediate(0)); + } + + for (label, mut function) in self.lowered_monomorphized_functions.into_iter() { program_section.push(IrOpcode::FunctionLabel(label)); program_section.append(&mut function.body); } - */ (self.data_section.clone(), program_section) } @@ -167,9 +189,7 @@ impl Lowerer { &mut self, func: FunctionSignature, ) -> Result { - println!("inserting monomorphized func"); - return Ok(dbg!(self.monomorphized_functions.insert(func))); - panic!("type checker didn't type check this monomorphization"); + return Ok(self.monomorphized_functions.insert(func)); /* // otherwise, queue up this one @@ -266,8 +286,9 @@ impl Lowerer { buf.push(IrOpcode::PushPc()); let arg_petr_types = arg_types.iter().map(|(_name, ty)| self.type_solution.generalize(ty)).collect(); + let func_signature = (*func, arg_petr_types); - let monomorphized_func_id = self.monomorphize_function((*func, arg_petr_types))?; + let monomorphized_func_id = self.monomorphize_function(func_signature)?; // jump to the function buf.push(IrOpcode::JumpImmediateFunction(monomorphized_func_id)); @@ -614,7 +635,7 @@ impl Lowerer { if Some(id) == self.entry_point { "ENTRY: " } else { "" }, Into::::into(id) )); - let func = self.lowered_monomorphized_functions.get(sig).unwrap(); + let func = self.lowered_monomorphized_functions.get(&id).unwrap(); for opcode in &func.body { result.push_str(&format!(" {pc}\t{}\n", opcode)); pc += 1; diff --git a/petr-typecheck/src/constraint_generation.rs b/petr-typecheck/src/constraint_generation.rs index 5ce34aa..814f3e3 100644 --- a/petr-typecheck/src/constraint_generation.rs +++ b/petr-typecheck/src/constraint_generation.rs @@ -280,12 +280,13 @@ impl TypeConstraintContext { // add constraints from all monomorphizations + + let mut num_replacements = 0; // TODO figure out how to drain a btreemap instead here to avoid the clone for (sig, monomorphic_func) in self.monomorphization_queue.clone() { let monomorphized_func_decl = self.get_untyped_function(monomorphic_func.func_id).clone(); let mut monomorphized_func_decl = monomorphized_func_decl.type_check(self); // instantiate the param types with the args from the monomorphized func - let mut num_replacements = 0; let new_constraints = replace_var_reference_types( &mut monomorphized_func_decl.body.kind, &monomorphized_func_decl.params, @@ -763,6 +764,7 @@ impl GenerateTypeConstraints for FunctionCall { // now that we know the argument types, check if this signature has been monomorphized // already let declared_return_type = ctx.to_type_var(&func_decl.return_type); + // insert axiom for return type ctx.axiom(declared_return_type, self.span()); @@ -774,11 +776,7 @@ impl GenerateTypeConstraints for FunctionCall { }; } - // unify declared return type with body return type - // ctx.satisfy_expr_return(declared_return_type, &func_decl.body); - // to create a monomorphized func decl, we don't actually have to update all of the types - // throughout the entire definition. We only need to update the parameter types. let mut monomorphized_func_decl = MonomorphizedFunction { name: func_decl.name, params: params.clone(), @@ -792,12 +790,6 @@ impl GenerateTypeConstraints for FunctionCall { param.1 = param_ty; } - // if there are any variable exprs in the body, update those ref types - let mut num_replacements = 0; - - // re-do constraint generation on replaced func body - // monomorphized_func_decl.body.type_check(ctx); - ctx.insert_monomorphized_function(signature, monomorphized_func_decl); // if there are any variable exprs in the body, update those ref types diff --git a/petr-typecheck/src/lib.rs b/petr-typecheck/src/lib.rs index 54bd1cb..f5d5a69 100644 --- a/petr-typecheck/src/lib.rs +++ b/petr-typecheck/src/lib.rs @@ -29,10 +29,8 @@ pub type TResult = Result; pub fn type_check(resolved: QueryableResolvedItems) -> Result>> { let mut type_checker = TypeConstraintContext::new(resolved); - println!("generating constraints"); type_checker.fully_type_check(); - println!("solving"); type_checker.into_solution() } diff --git a/petr-typecheck/src/solution.rs b/petr-typecheck/src/solution.rs index 3b56b6f..ba8a62b 100644 --- a/petr-typecheck/src/solution.rs +++ b/petr-typecheck/src/solution.rs @@ -112,7 +112,7 @@ impl TypeSolution { self.solution.insert(ty, entry); } - fn pretty_print_type( + pub fn pretty_print_type( &self, ty: &SpecificType, ) -> String { @@ -534,4 +534,8 @@ impl TypeSolution { If { then_branch, .. } => self.expr_ty(then_branch), } } + + pub fn interner(&self) -> &SymbolInterner { + &self.interner + } } diff --git a/petr-vm/src/lib.rs b/petr-vm/src/lib.rs index c6fa965..15b1270 100644 --- a/petr-vm/src/lib.rs +++ b/petr-vm/src/lib.rs @@ -120,6 +120,9 @@ impl Vm { } let opcode = self.instructions.get(self.state.program_counter).clone(); self.state.program_counter = (self.state.program_counter.0 + 1).into(); + for (ix,ins) in self.instructions.iter() { + // println!("{ins}"); + } match opcode { IrOpcode::JumpImmediateFunction(label) => { let Some(offset) = self From c7c13a68298ed09bf7146ac935d2b34ce5b2c12a Mon Sep 17 00:00:00 2001 From: sezna Date: Mon, 5 Aug 2024 07:12:58 -0700 Subject: [PATCH 07/12] WIP: next up, manually trace IR on paper --- Cargo.lock | 1 + petr-ir/Cargo.toml | 2 + petr-ir/src/lib.rs | 127 +++++++++++++++++++++++++++++------------ petr-ir/src/opcodes.rs | 39 +------------ petr-vm/src/lib.rs | 18 +++--- petr-vm/src/tests.rs | Bin 7846 -> 7949 bytes 6 files changed, 109 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6898a2..a807d57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1045,6 +1045,7 @@ dependencies = [ "miette", "petr-parse", "petr-resolve", + "petr-stdlib", "petr-typecheck", "petr-utils", "thiserror", diff --git a/petr-ir/Cargo.toml b/petr-ir/Cargo.toml index 7eeba7b..cd0e034 100644 --- a/petr-ir/Cargo.toml +++ b/petr-ir/Cargo.toml @@ -21,6 +21,8 @@ thiserror = "1.0.61" petr-parse = { path = "../petr-parse", version = "0.1.0" } expect-test = "1.5.0" petr-resolve = { path = "../petr-resolve", version = "0.1.0" } +petr-stdlib = { path = "../petr-stdlib", version = "0.1.0" } + [features] debug = ["petr-utils/debug"] diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index bc95d48..eabe2cf 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -15,7 +15,7 @@ mod error; mod opcodes; pub use error::LoweringError; use opcodes::*; -pub use opcodes::{DataLabel, Intrinsic, IrOpcode, LabelId, Reg, ReservedRegister}; +pub use opcodes::{DataLabel, Intrinsic, IrOpcode, LabelId, Reg}; /// Top-level convenience function that takes a type solution (the output of the type checker) and /// outputs lowered IR and a data section. @@ -40,12 +40,13 @@ pub struct Lowerer { reg_assigner: usize, /// This assigns IDs to function signatures, which are then pulled from the type solution and /// lowered in a later stage - monomorphized_functions: IndexMap, + monomorphized_functions: BTreeMap, lowered_monomorphized_functions: BTreeMap, type_solution: TypeSolution, variables_in_scope: Vec>, errors: Vec>, label_assigner: usize, + monomorphized_function_label_assigner: usize, } /// Represents static data in the data section of the program. @@ -72,6 +73,7 @@ impl Lowerer { errors: Default::default(), monomorphized_functions: Default::default(), lowered_monomorphized_functions: Default::default(), + monomorphized_function_label_assigner: 0, }; let monomorphized_entry_point_id = match entry_point { @@ -90,18 +92,31 @@ impl Lowerer { lowerer.monomorphize_function(sig)?; } + lowerer.lower_monomorphized_functions(); + lowerer.entry_point = monomorphized_entry_point_id; Ok(lowerer) } + fn new_func_label(&mut self) -> MonomorphizedFunctionId { + let label = self.monomorphized_function_label_assigner.into(); + self.monomorphized_function_label_assigner += 1; + label + } + /// Drains `self.monomorphized_functions` and lowers them into /// `self.lowered_monomorphized_functions` fn lower_monomorphized_functions(&mut self) { - let owned_monomorphized_functions = self.monomorphized_functions.iter().map(|(a, b)| (a, b.clone())).collect::>(); + // TODO: figure out a way to drain a btreemap so we don't have to clone here + let owned_monomorphized_functions = self + .monomorphized_functions + .iter() + .map(|(a, b)| (a.clone(), b.clone())) + .collect::>(); use petr_typecheck::Type; - for (label, sig) in owned_monomorphized_functions { + for (sig, label) in owned_monomorphized_functions { let mut buf = vec![]; // lower the function body let func_def = self @@ -111,7 +126,16 @@ impl Lowerer { .expect("type checker should have this function signature") .clone(); - buf.push(IrOpcode::Comment(format!("fn {}: {} ({})", Into::::into(label), self.type_solution.interner().get(func_def.name.id), sig.1.iter().map(|ty| self.type_solution.pretty_print_type(&ty.as_specific_ty())).collect::>().join(", ")))); + buf.push(IrOpcode::Comment(format!( + "fn {}: {} ({})", + Into::::into(label), + self.type_solution.interner().get(func_def.name.id), + sig.1 + .iter() + .map(|ty| self.type_solution.pretty_print_type(&ty.as_specific_ty())) + .collect::>() + .join(", ") + ))); self.with_variable_context(|ctx| -> Result<_> { // Pop parameters off the stack in reverse order -- the last parameter for the function @@ -125,10 +149,7 @@ impl Lowerer { if param_ty.is_copy_type() { // load from stack into register let param_reg = ctx.fresh_reg(); - let ty_reg = TypedReg { - ty: param_ty.clone(), - reg: param_reg, - }; + let ty_reg = param_reg; buf.push(IrOpcode::StackPop(ty_reg)); // insert param into mapping @@ -142,12 +163,12 @@ impl Lowerer { let return_dest = ReturnDestination::Reg(return_reg); let mut expr_body = ctx.lower_expr(&func_def.body, return_dest)?; buf.append(&mut expr_body); - // load return value into func return register - buf.push(IrOpcode::Copy(Reg::Reserved(ReservedRegister::ReturnValueRegister), return_reg)); + // push return value onto stack + buf.push(IrOpcode::StackPush(return_reg)); // jump back to caller - buf.push(IrOpcode::Return()); + buf.push(IrOpcode::Return(return_reg)); Ok(()) }) .unwrap(); @@ -159,11 +180,8 @@ impl Lowerer { /// Consumes the [`Lowerer`], performing lowering to IR and returning the data section and /// program section. pub fn finalize(mut self) -> (DataSection, Vec) { - // first, codegen all monomorphized functions. this populates // `lowered_monomorphized_functions` - self.lower_monomorphized_functions(); - self.lower_monomorphized_functions(); let mut program_section = vec![]; @@ -189,7 +207,13 @@ impl Lowerer { &mut self, func: FunctionSignature, ) -> Result { - return Ok(self.monomorphized_functions.insert(func)); + if let Some(id) = self.monomorphized_functions.get(&func) { + return Ok(*id); + } else { + let new_id = self.new_func_label(); + self.monomorphized_functions.insert(func.clone(), new_id); + return Ok(new_id); + } /* // otherwise, queue up this one @@ -278,7 +302,7 @@ impl Lowerer { let petr_ty = self.type_solution.get_latest_type(arg_ty); arg_types.push((*arg_name, petr_ty.clone())); let ir_ty = self.lower_type(petr_ty.clone()); - expr.push(IrOpcode::StackPush(TypedReg { ty: ir_ty, reg })); + expr.push(IrOpcode::StackPush(reg)); buf.append(&mut expr); } @@ -293,17 +317,11 @@ impl Lowerer { // jump to the function buf.push(IrOpcode::JumpImmediateFunction(monomorphized_func_id)); - // after returning to this function, return the register - match return_destination { - ReturnDestination::Reg(reg) => { - buf.push(IrOpcode::Copy(reg, Reg::Reserved(ReservedRegister::ReturnValueRegister))); - }, - } + // after returning to this function, pop the stack into a register + let ReturnDestination::Reg(reg) = return_destination; - // add this call's function singature (label + concrete types) to the list of - // functions that need to be generated + buf.push(IrOpcode::StackPop(reg)); - // Ok(buf) }, List { elements, .. } => { @@ -629,13 +647,14 @@ impl Lowerer { } else { result.push_str("\tNO ENTRY POINT\n"); } - for (id, sig) in self.monomorphized_functions.iter() { + for (sig, id) in self.monomorphized_functions.iter() { result.push_str(&format!( "{}function {}:\n", - if Some(id) == self.entry_point { "ENTRY: " } else { "" }, - Into::::into(id) + if Some(id) == self.entry_point.as_ref() { "ENTRY: " } else { "" }, + Into::::into(*id) )); - let func = self.lowered_monomorphized_functions.get(&id).unwrap(); + dbg!(&id); + let func = self.lowered_monomorphized_functions.get(id).unwrap(); for opcode in &func.body { result.push_str(&format!(" {pc}\t{}\n", opcode)); pc += 1; @@ -680,15 +699,22 @@ mod tests { use petr_utils::render_error; use super::*; - fn check( + + fn check_with_stdlib( input: impl Into, expect: Expect, ) { let input = input.into(); - let parser = petr_parse::Parser::new(vec![ - ("std/ops.pt", "fn add(lhs in 'int, rhs in 'int) returns 'int @add lhs, rhs"), - ("test", &input), - ]); + let mut sources = petr_stdlib::stdlib(); + sources.push(("test", &input)); + check_inner(sources, expect); + } + + fn check_inner( + sources: Vec<(&str, &str)>, + expect: Expect, + ) { + let parser = petr_parse::Parser::new(sources); let (ast, errs, interner, source_map) = parser.into_result(); if !errs.is_empty() { errs.into_iter().for_each(|err| eprintln!("{:?}", render_error(&source_map, err))); @@ -725,6 +751,17 @@ mod tests { expect.assert_eq(&res); } + fn check( + input: impl Into, + expect: Expect, + ) { + let input = input.into(); + let sources = vec![ + ("std/ops.pt", "fn add(lhs in 'int, rhs in 'int) returns 'int @add lhs, rhs"), + ("test", &input), + ]; + check_inner(sources, expect) + } #[test] fn basic_main_func() { @@ -1024,4 +1061,24 @@ mod tests { "#]], ); } + + #[test] + fn fibonacci_sequence() { + check_with_stdlib( + r#" +fn fibonacci_sequence(n in 'int) returns 'int + if = n 0 then 0 + else if = n 1 then 1 + else + let lhs = - n 1; + rhs = - n 2; + fib_lhs = ~fibonacci_sequence lhs; + fib_rhs = ~fibonacci_sequence rhs; + + fib_lhs fib_rhs + +fn main() returns 'int ~fibonacci_sequence(10) +"#, + expect![[r#""#]], + ) + } } diff --git a/petr-ir/src/opcodes.rs b/petr-ir/src/opcodes.rs index 245c2a1..e3373d2 100644 --- a/petr-ir/src/opcodes.rs +++ b/petr-ir/src/opcodes.rs @@ -48,14 +48,14 @@ ir_ops! { Subtract "sub" Reg: dest, Reg: lhs, Reg: rhs; Divide "div" Reg: dest, Reg: lhs, Reg: rhs; LoadData "ld" Reg: dest, DataLabel: data; - StackPop "pop" TypedReg: dest; - StackPush "push" TypedReg: src; + StackPop "pop" Reg: dest; + StackPush "push" Reg: src; Intrinsic "intrinsic" Intrinsic: intr; FunctionLabel "func" MonomorphizedFunctionId: label; LoadImmediate "imm" Reg: dest, u64: imm; Copy "cp" Reg: dest, Reg: src; Label "label" LabelId: label; - Return "ret"; + Return "ret" Reg: return_value; ReturnImmediate "reti" u64: imm; PushPc "ppc"; StackPushImmediate "pushi" u64: imm; @@ -92,12 +92,6 @@ impl std::fmt::Display for Intrinsic { } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct TypedReg { - pub ty: IrTy, - pub reg: Reg, -} - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum IrTy { Ptr(Box), @@ -211,7 +205,6 @@ impl From for Size { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Reg { Virtual(usize), - Reserved(ReservedRegister), } impl std::fmt::Display for Reg { @@ -221,32 +214,6 @@ impl std::fmt::Display for Reg { ) -> std::fmt::Result { match self { Reg::Virtual(a) => write!(f, "v{a}"), - Reg::Reserved(reg) => write!( - f, - "rr({})", - match reg { - ReservedRegister::ReturnValueRegister => "func return value", - } - ), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum ReservedRegister { - /// where functions put their return values - ReturnValueRegister, -} - -impl std::fmt::Display for TypedReg { - fn fmt( - &self, - f: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - // TODO do we care about typed registers in IR? - match self.reg { - Reg::Virtual(a) => write!(f, "v{a}"), - Reg::Reserved(_) => todo!(), } } } diff --git a/petr-vm/src/lib.rs b/petr-vm/src/lib.rs index 15b1270..24221a5 100644 --- a/petr-vm/src/lib.rs +++ b/petr-vm/src/lib.rs @@ -6,7 +6,7 @@ use std::collections::BTreeMap; -use petr_ir::{DataLabel, DataSectionEntry, Intrinsic, IrOpcode, LabelId, Reg, ReservedRegister}; +use petr_ir::{DataLabel, DataSectionEntry, Intrinsic, IrOpcode, LabelId, Reg}; use petr_utils::{idx_map_key, IndexMap}; use thiserror::Error; @@ -120,8 +120,8 @@ impl Vm { } let opcode = self.instructions.get(self.state.program_counter).clone(); self.state.program_counter = (self.state.program_counter.0 + 1).into(); - for (ix,ins) in self.instructions.iter() { - // println!("{ins}"); + for (ix, ins) in self.instructions.iter() { + // println!("{ins}"); } match opcode { IrOpcode::JumpImmediateFunction(label) => { @@ -136,8 +136,11 @@ impl Vm { Ok(Continue) }, IrOpcode::Add(dest, lhs, rhs) => { + println!("Adding {} and {}", lhs, rhs); let lhs = self.get_register(lhs)?; let rhs = self.get_register(rhs)?; + let result = lhs.0.wrapping_add(rhs.0); + println!("Result is {}", result); self.set_register(dest, Value(lhs.0.wrapping_add(rhs.0))); Ok(Continue) }, @@ -169,11 +172,12 @@ impl Vm { let Some(data) = self.state.stack.pop() else { return Err(VmError::PoppedEmptyStack(opcode)); }; - self.set_register(dest.reg, data); + self.set_register(*dest, data); + println!("set register {} to {}", dest, data.0); Ok(Continue) }, IrOpcode::StackPush(val) => { - let data = self.get_register(val.reg)?; + let data = self.get_register(val)?; self.state.stack.push(data); Ok(Continue) }, @@ -209,8 +213,8 @@ impl Vm { }, IrOpcode::Jump(_) => todo!(), IrOpcode::Label(_) => Ok(Continue), - IrOpcode::Return() => { - let val = self.get_register(Reg::Reserved(ReservedRegister::ReturnValueRegister))?; + IrOpcode::Return(reg) => { + let val = self.get_register(reg)?; // pop the fn stack, return there let Some(offset) = self.state.call_stack.pop() else { return Ok(Terminate(val)); diff --git a/petr-vm/src/tests.rs b/petr-vm/src/tests.rs index 853677509a9f72758ce9a26705587dea3e4ab625..96899b4d127482fe4c06c35bfa426b816d8c492e 100644 GIT binary patch delta 174 zcmZ2x+iSN$N0uusGbuhNqgcUKVY0rgxVtr%0u+EGiogGci&q$w Date: Thu, 8 Aug 2024 13:37:18 -0700 Subject: [PATCH 08/12] Wip --- petr-vm/src/lib.rs | 18 ++++++++++++++++-- petr-vm/src/tests.rs | Bin 7949 -> 8995 bytes 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/petr-vm/src/lib.rs b/petr-vm/src/lib.rs index 24221a5..89ed296 100644 --- a/petr-vm/src/lib.rs +++ b/petr-vm/src/lib.rs @@ -46,6 +46,14 @@ impl Value { pub fn inner(&self) -> u64 { self.0 } + + pub fn is_false(&self) -> bool { + self.0 == 0 + } + + pub fn is_true(&self) -> bool { + !self.is_false() + } } #[derive(Debug, Error)] @@ -151,9 +159,11 @@ impl Vm { Ok(Continue) }, IrOpcode::Subtract(dest, lhs, rhs) => { + println!("Subtracting {} and {}", lhs, rhs); let lhs = self.get_register(lhs)?; let rhs = self.get_register(rhs)?; self.set_register(dest, Value(lhs.0.wrapping_sub(rhs.0))); + println!("Result is {}", lhs.0.wrapping_sub(rhs.0)); Ok(Continue) }, IrOpcode::Divide(dest, lhs, rhs) => { @@ -173,7 +183,6 @@ impl Vm { return Err(VmError::PoppedEmptyStack(opcode)); }; self.set_register(*dest, data); - println!("set register {} to {}", dest, data.0); Ok(Continue) }, IrOpcode::StackPush(val) => { @@ -263,7 +272,11 @@ impl Vm { IrOpcode::Comment(_) => Ok(Continue), IrOpcode::JumpIfFalseImmediate(condition, dest) => { let condition = self.get_register(condition)?; - if condition.0 == 0 { + if condition.is_true() { + println!("Not jumping"); + } + if condition.is_false() { + println!("Jumping to label {}", dest); self.jump_to_label(dest)? } Ok(Continue) @@ -308,6 +321,7 @@ impl Vm { dest: petr_ir::Reg, val: Value, ) { + println!("Setting register {} to {}", dest, val.0); self.state.registers.insert(dest, val); } diff --git a/petr-vm/src/tests.rs b/petr-vm/src/tests.rs index 96899b4d127482fe4c06c35bfa426b816d8c492e..de01f2e79b359a685a3c35784cbb1c638b702cba 100644 GIT binary patch delta 498 zcmeCRTkN*spA5SK5NIoCOiq*);nh&kRmf8?RL}%Mun4yXNWe%zQ&WL!@%l&_%;vR#u48KD0i6Ys zRme;OIv5m6Fd1W*4A|jN`@jLI3H5>*lZi3RUOBAx0$mOa&EnFW$qS`afg!1-47VAt i=@L@FxPyBU7;6YN*W`)PDw7|uvroRy#Xb4I93KF3>Yu0p delta 231 zcmZ4N)@!%npG Date: Mon, 12 Aug 2024 16:15:00 -0700 Subject: [PATCH 09/12] wip --- Cargo.lock | 14 +++ Cargo.toml | 1 + petr-ir/src/lib.rs | 79 +++++++++++++ petr-lower/Cargo.toml | 29 +++++ petr-lower/src/error.rs | 10 ++ petr-lower/src/lib.rs | 25 ++++ petr-lower/src/opcodes.rs | 219 +++++++++++++++++++++++++++++++++++ petr-lower/tmp | 237 ++++++++++++++++++++++++++++++++++++++ petr-vm/src/lib.rs | 4 + petr-vm/src/tests.rs | Bin 8995 -> 9823 bytes 10 files changed, 618 insertions(+) create mode 100644 petr-lower/Cargo.toml create mode 100644 petr-lower/src/error.rs create mode 100644 petr-lower/src/lib.rs create mode 100644 petr-lower/src/opcodes.rs create mode 100644 petr-lower/tmp diff --git a/Cargo.lock b/Cargo.lock index a807d57..5582a7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1051,6 +1051,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "petr-lower" +version = "0.1.0" +dependencies = [ + "expect-test", + "miette", + "petr-parse", + "petr-resolve", + "petr-stdlib", + "petr-typecheck", + "petr-utils", + "thiserror", +] + [[package]] name = "petr-parse" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index e4dca87..d4c89b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "petr-api", "petr-playground", "petr-stdlib", + "petr-lower" ] [workspace.package] diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index eabe2cf..c41c584 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -199,6 +199,12 @@ impl Lowerer { program_section.append(&mut function.body); } + // pretty print the program section + println!("\nPROGRAM SECTION\n"); + for (ix, opcode) in program_section.iter().enumerate() { + println!("{}: {}", ix, opcode); + } + (self.data_section.clone(), program_section) } @@ -1081,4 +1087,77 @@ fn main() returns 'int ~fibonacci_sequence(10) expect![[r#""#]], ) } + + #[test] + fn basic_recursion_with_else_if_and_add_result() { + check( + r#" +fn repro(n in 'int) returns 'int + if @equals(n, 0) then 0 + else if @equals( n,0) then 0 + else + let a = ~repro(@subtract(n ,1)); + b = ~repro(2); + 5 +fn main() returns 'int ~repro(5) + "#, + expect![[r#" + ; DATA_SECTION + 0: Int64(0) + 1: Int64(0) + 2: Int64(0) + 3: Int64(0) + 4: Int64(1) + 5: Int64(2) + 6: Int64(5) + 7: Int64(5) + + ; PROGRAM_SECTION + ENTRY: 0 + function 1: + 0 comment fn 1: repro (int) + 1 pop v0 + 2 cp v3 v0 + 3 ld v4 datalabel0 + 4 eq v2 v3 v4 + 5 cjump v2 labelid0 + 6 ld v1 datalabel1 + 7 jumpi labelid1 + 8 label labelid0 + 9 cp v6 v0 + 10 ld v7 datalabel2 + 11 eq v5 v6 v7 + 12 cjump v5 labelid2 + 13 ld v1 datalabel3 + 14 jumpi labelid3 + 15 label labelid2 + 16 cp v10 v0 + 17 ld v11 datalabel4 + 18 sub v9 v10 v11 + 19 push v9 + 20 ppc + 21 fjumpi monomorphizedfunctionid1 + 22 pop v8 + 23 ld v13 datalabel5 + 24 push v13 + 25 ppc + 26 fjumpi monomorphizedfunctionid1 + 27 pop v12 + 28 ld v1 datalabel6 + 29 label labelid3 + 30 label labelid1 + 31 push v1 + 32 ret v1 + ENTRY: function 0: + 33 comment fn 0: main () + 34 ld v15 datalabel7 + 35 push v15 + 36 ppc + 37 fjumpi monomorphizedfunctionid1 + 38 pop v14 + 39 push v14 + 40 ret v14 + "#]], + ) + } } diff --git a/petr-lower/Cargo.toml b/petr-lower/Cargo.toml new file mode 100644 index 0000000..b684c87 --- /dev/null +++ b/petr-lower/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "petr-lower" +version.workspace = true +edition.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true + +description = "lowering petr to different targets" +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +petr-utils = { path = "../petr-utils", version = "0.1.0", optional = true } +petr-typecheck = { path = "../petr-typecheck", version = "0.1.0" } +miette = { version = "5.10", features = ["fancy"] } +thiserror = "1.0.61" + +[dev-dependencies] +petr-parse = { path = "../petr-parse", version = "0.1.0" } +expect-test = "1.5.0" +petr-resolve = { path = "../petr-resolve", version = "0.1.0" } +petr-stdlib = { path = "../petr-stdlib", version = "0.1.0" } + + +[features] +debug = ["petr-utils/debug"] +default = ["dep:petr-utils"] diff --git a/petr-lower/src/error.rs b/petr-lower/src/error.rs new file mode 100644 index 0000000..01661b3 --- /dev/null +++ b/petr-lower/src/error.rs @@ -0,0 +1,10 @@ +use miette::Diagnostic; +use thiserror::Error; + +#[derive(Debug, Error, Diagnostic, Clone)] +pub enum LoweringError { + #[error("Internal compiler error: {0}")] + Internal(String), + #[error("Unable to infer type")] + UnableToInferType, +} diff --git a/petr-lower/src/lib.rs b/petr-lower/src/lib.rs new file mode 100644 index 0000000..5dd4f83 --- /dev/null +++ b/petr-lower/src/lib.rs @@ -0,0 +1,25 @@ +use std::collections::BTreeMap; + +trait Lowerer { + type Target; + fn lower_expr(&mut self, expr: &TypedExpr) -> Result; +} + + +struct JsFunction { + name: String, + args: Vec, + body: Vec, +} + +enum Js{ + Return(Box), + Assign(String, JsExpr), + Call(String, Vec), +} + +/// Lowers Petr into Javascript. +struct JSLowerer { + functions: BTreeMap; +} + diff --git a/petr-lower/src/opcodes.rs b/petr-lower/src/opcodes.rs new file mode 100644 index 0000000..e3373d2 --- /dev/null +++ b/petr-lower/src/opcodes.rs @@ -0,0 +1,219 @@ +use petr_utils::idx_map_key; + +use crate::MonomorphizedFunctionId; + +idx_map_key!(FunctionLabel); + +idx_map_key!(DataLabel); + +macro_rules! ir_ops { + ($($(#[$attr:meta])* + $op_name:ident $op_code:literal $($args:ty: $arg_name:ident),* + );+) => { + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + pub enum IrOpcode { + $( + $(#[$attr])* + $op_name($($args),*), + )+ + } + + + impl std::fmt::Display for IrOpcode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + $( + IrOpcode::$op_name($($arg_name),*) => { + write!(f, "{}", $op_code)?; + $( + write!(f, " {}", $arg_name)?; + )* + Ok(()) + } + )+ + } + } + } + + // TODO serialize/deserialize using above display + }; +} + +ir_ops! { + JumpImmediateFunction "fjumpi" MonomorphizedFunctionId: imm; + Jump "jump" Reg: dest; + Add "add" Reg: dest, Reg: lhs, Reg: rhs; + Multiply "mult" Reg: dest, Reg: lhs, Reg: rhs; + Subtract "sub" Reg: dest, Reg: lhs, Reg: rhs; + Divide "div" Reg: dest, Reg: lhs, Reg: rhs; + LoadData "ld" Reg: dest, DataLabel: data; + StackPop "pop" Reg: dest; + StackPush "push" Reg: src; + Intrinsic "intrinsic" Intrinsic: intr; + FunctionLabel "func" MonomorphizedFunctionId: label; + LoadImmediate "imm" Reg: dest, u64: imm; + Copy "cp" Reg: dest, Reg: src; + Label "label" LabelId: label; + Return "ret" Reg: return_value; + ReturnImmediate "reti" u64: imm; + PushPc "ppc"; + StackPushImmediate "pushi" u64: imm; + Malloc "malloc" Reg: ptr_dest, Reg: size; + MallocImmediate "malloci" Reg: ptr_dest, Size: imm; + /// Register `src` will itself have its value written to the memory pointed to by `dest_ptr` + WriteRegisterToMemory "sri" Reg: src, Reg: dest_ptr; + Comment "comment" String: comment; + JumpIfFalseImmediate "cjump" Reg: cond, LabelId: dest; + JumpImmediate "jumpi" LabelId: dest; + Equal "eq" Reg: dest, Reg: lhs, Reg: rhs +} + +idx_map_key!(LabelId); + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Intrinsic { + // given a pointer, print the thing it points to + Puts(Reg), +} + +impl std::fmt::Display for Intrinsic { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!( + f, + "@{}", + match self { + Intrinsic::Puts(x) => format!("puts({x})"), + } + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum IrTy { + Ptr(Box), + Int64, + Unit, + String, + Boolean, + UserDefinedType { + variants: Vec, + constant_literal_types: Vec, + }, + List(Box), +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct IrUserDefinedTypeVariant { + pub fields: Vec, +} + +impl IrUserDefinedTypeVariant { + pub fn size(&self) -> Size { + // the size of a product type is the sum of the sizes of its fields + self.fields.iter().map(|f| f.size().num_bytes()).sum::().into() + } +} + +impl IrTy { + pub fn size(&self) -> Size { + match self { + IrTy::Int64 => 8, + IrTy::Ptr(_) => 8, + IrTy::Unit => 0, + // size of the pointer to the string + IrTy::String => 8, + IrTy::Boolean => 1, + // the size of a sum type is the size of the largest variant + IrTy::UserDefinedType { + variants, + constant_literal_types, + } => { + return variants + .iter() + .map(|v| v.size()) + .chain(constant_literal_types.iter().map(|t| t.size())) + .max() + .expect("user defined type should have at least one variant") + }, + IrTy::List(ty) => { + // the size of a list is the size of a pointer + // to the first element + return IrTy::Ptr(ty.clone()).size(); + }, + } + .into() + } + + pub(crate) fn is_copy_type(&self) -> bool { + self.size().num_bytes() <= 8 + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Size(T) +where + T: SizeUnit; + +impl std::fmt::Debug for Size { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(f, "{:?} bytes", self.0.num_bytes()) + } +} + +impl std::fmt::Display for Size { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(f, "{} bytes", self.0.num_bytes()) + } +} + +impl Size { + pub fn num_bytes(&self) -> usize { + self.0.num_bytes() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Bytes(usize); + +pub trait SizeUnit: Clone + Copy + PartialEq + Eq + PartialOrd + Ord { + fn num_bytes(&self) -> usize; +} + +impl SizeUnit for Bytes { + fn num_bytes(&self) -> usize { + self.0 + } +} + +impl From for Size { + fn from(x: usize) -> Self { + Size(Bytes(x)) + } +} + +/// a virtual register +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Reg { + Virtual(usize), +} + +impl std::fmt::Display for Reg { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + match self { + Reg::Virtual(a) => write!(f, "v{a}"), + } + } +} diff --git a/petr-lower/tmp b/petr-lower/tmp new file mode 100644 index 0000000..b258f85 --- /dev/null +++ b/petr-lower/tmp @@ -0,0 +1,237 @@ + +running 1 test + + +error: expect test failed + --> petr-ir/src/lib.rs:1098:13 + +You can update all `expect!` tests by running: + + env UPDATE_EXPECT=1 cargo test + +To update a single test, place the cursor on `expect` token and use `run` feature of rust-analyzer. + +Expect: +---- +Value(6) +---- + +Actual: +---- +; DATA_SECTION +0: Int64(0) +1: Int64(0) +2: Int64(0) +3: Int64(0) +4: Int64(1) +5: Int64(2) +6: Int64(5) +7: Int64(5) + +; PROGRAM_SECTION + ENTRY: 0 +function 1: + 0 comment fn 1: sub (int, int) + 1 pop v0 + 2 pop v1 + 3 cp v3 v1 + 4 cp v4 v0 + 5 sub v2 v3 v4 + 6 push v2 + 7 ret v2 +function 2: + 8 comment fn 2: eq (int, int) + 9 pop v5 + 10 pop v6 + 11 cp v8 v6 + 12 cp v9 v5 + 13 eq v7 v8 v9 + 14 push v7 + 15 ret v7 +function 3: + 16 comment fn 3: Sized (int, int) + 17 pop v10 + 18 pop v11 + 19 malloci v12 16 bytes + 20 cp v14 v11 + 21 imm v13 0 + 22 add v13 v13 v12 + 23 sri v14 v13 + 24 cp v15 v10 + 25 imm v13 8 + 26 add v13 v13 v12 + 27 sri v15 v13 + 28 push v12 + 29 ret v12 +function 4: + 30 comment fn 4: repro (int) + 31 pop v16 + 32 cp v19 v16 + 33 push v19 + 34 ld v20 datalabel0 + 35 push v20 + 36 ppc + 37 fjumpi monomorphizedfunctionid2 + 38 pop v18 + 39 cjump v18 labelid0 + 40 ld v17 datalabel1 + 41 jumpi labelid1 + 42 label labelid0 + 43 cp v22 v16 + 44 push v22 + 45 ld v23 datalabel2 + 46 push v23 + 47 ppc + 48 fjumpi monomorphizedfunctionid2 + 49 pop v21 + 50 cjump v21 labelid2 + 51 ld v17 datalabel3 + 52 jumpi labelid3 + 53 label labelid2 + 54 cp v26 v16 + 55 push v26 + 56 ld v27 datalabel4 + 57 push v27 + 58 ppc + 59 fjumpi monomorphizedfunctionid1 + 60 pop v25 + 61 push v25 + 62 ppc + 63 fjumpi monomorphizedfunctionid4 + 64 pop v24 + 65 ld v29 datalabel5 + 66 push v29 + 67 ppc + 68 fjumpi monomorphizedfunctionid4 + 69 pop v28 + 70 ld v17 datalabel6 + 71 label labelid3 + 72 label labelid1 + 73 push v17 + 74 ret v17 +ENTRY: function 0: + 75 comment fn 0: main () + 76 ld v31 datalabel7 + 77 push v31 + 78 ppc + 79 fjumpi monomorphizedfunctionid4 + 80 pop v30 + 81 push v30 + 82 ret v30 + +---- + +Diff: +---- +Value(6); DATA_SECTION +0: Int64(0) +1: Int64(0) +2: Int64(0) +3: Int64(0) +4: Int64(1) +5: Int64(2) +6: Int64(5) +7: Int64(5) + +; PROGRAM_SECTION + ENTRY: 0 +function 1: + 0 comment fn 1: sub (int, int) + 1 pop v0 + 2 pop v1 + 3 cp v3 v1 + 4 cp v4 v0 + 5 sub v2 v3 v4 + 6 push v2 + 7 ret v2 +function 2: + 8 comment fn 2: eq (int, int) + 9 pop v5 + 10 pop v6 + 11 cp v8 v6 + 12 cp v9 v5 + 13 eq v7 v8 v9 + 14 push v7 + 15 ret v7 +function 3: + 16 comment fn 3: Sized (int, int) + 17 pop v10 + 18 pop v11 + 19 malloci v12 16 bytes + 20 cp v14 v11 + 21 imm v13 0 + 22 add v13 v13 v12 + 23 sri v14 v13 + 24 cp v15 v10 + 25 imm v13 8 + 26 add v13 v13 v12 + 27 sri v15 v13 + 28 push v12 + 29 ret v12 +function 4: + 30 comment fn 4: repro (int) + 31 pop v16 + 32 cp v19 v16 + 33 push v19 + 34 ld v20 datalabel0 + 35 push v20 + 36 ppc + 37 fjumpi monomorphizedfunctionid2 + 38 pop v18 + 39 cjump v18 labelid0 + 40 ld v17 datalabel1 + 41 jumpi labelid1 + 42 label labelid0 + 43 cp v22 v16 + 44 push v22 + 45 ld v23 datalabel2 + 46 push v23 + 47 ppc + 48 fjumpi monomorphizedfunctionid2 + 49 pop v21 + 50 cjump v21 labelid2 + 51 ld v17 datalabel3 + 52 jumpi labelid3 + 53 label labelid2 + 54 cp v26 v16 + 55 push v26 + 56 ld v27 datalabel4 + 57 push v27 + 58 ppc + 59 fjumpi monomorphizedfunctionid1 + 60 pop v25 + 61 push v25 + 62 ppc + 63 fjumpi monomorphizedfunctionid4 + 64 pop v24 + 65 ld v29 datalabel5 + 66 push v29 + 67 ppc + 68 fjumpi monomorphizedfunctionid4 + 69 pop v28 + 70 ld v17 datalabel6 + 71 label labelid3 + 72 label labelid1 + 73 push v17 + 74 ret v17 +ENTRY: function 0: + 75 comment fn 0: main () + 76 ld v31 datalabel7 + 77 push v31 + 78 ppc + 79 fjumpi monomorphizedfunctionid4 + 80 pop v30 + 81 push v30 + 82 ret v30 + +---- + +test tests::basic_recursion_with_else_if_and_add_result ... FAILED + +failures: + +failures: + tests::basic_recursion_with_else_if_and_add_result + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 8 filtered out; finished in 0.01s + diff --git a/petr-vm/src/lib.rs b/petr-vm/src/lib.rs index 89ed296..6a1ebe6 100644 --- a/petr-vm/src/lib.rs +++ b/petr-vm/src/lib.rs @@ -119,6 +119,7 @@ impl Vm { } fn execute(&mut self) -> Result { + println!("PC is {}", self.state.program_counter.0); use VmControlFlow::*; if self.state.program_counter.0 >= self.instructions.len() { return Err(VmError::ProgramCounterOutOfBounds( @@ -131,6 +132,7 @@ impl Vm { for (ix, ins) in self.instructions.iter() { // println!("{ins}"); } + println!("Opcode is: {opcode}"); match opcode { IrOpcode::JumpImmediateFunction(label) => { let Some(offset) = self @@ -228,10 +230,12 @@ impl Vm { let Some(offset) = self.state.call_stack.pop() else { return Ok(Terminate(val)); }; + println!("Returning to PC: {}", offset.0); self.state.program_counter = offset; Ok(Continue) }, IrOpcode::PushPc() => { + println!("Pushing PC: {}", self.state.program_counter.0 + 1); self.state.call_stack.push((self.state.program_counter.0 + 1).into()); Ok(Continue) }, diff --git a/petr-vm/src/tests.rs b/petr-vm/src/tests.rs index de01f2e79b359a685a3c35784cbb1c638b702cba..bdbcbcb5d9db52dff7b40268b89e60e50eb941ad 100644 GIT binary patch delta 533 zcma)(K}!Nb6vr_NVXqy6LU|g*ENqJgL8zH@s7tq?OT@a4&s>~lx0zWha`W6Jl09|y zWVi5p)U{imp>NQ*Dh6Ge%P{Z#=l_21z2VW<$@6H{XmOxzq#cL*j#N$NH3o02-$j}` zzbG4^kMu)9QQAQ$+l1s5N|m?3<3Xrw%6yK*G|j1W>f~{bg3P{IfVTlsr4`Yl*i`b{ zz657TD`ZP5VL|#-36EczgZIsGJryk<2KuQO&yjo}P^mje0@wuR-=IJNo-DzAq`77R zd#ZC1odztAKUbbogVE!Gt(|XWu?>NBS4OQp&L z}9LMls==Ks<3xSoF9h(F+X*>QO@F6J}FV$Sh*cCTN<>(FJAa0?11s5Wva=&>M^ z2wn^3Y2enMbTU1L3S8KS2Z4d`Nlk_gC0wcq2dg+IoNTeN5beyiwQIEEp|KX%v)l1Y JW_>9#egH}|uA%?{ delta 46 zcmccbv)FCI%E@vZos;z#WjNI`^9o8!G&QX^pI~fb**ul4lX0^V=Mw432@2v&wOjy@ CTMw83 From 4826e6a8fd5b0098b726e187b7400d8a799e4856 Mon Sep 17 00:00:00 2001 From: Alexander Hansen Date: Tue, 13 Aug 2024 08:44:25 -0700 Subject: [PATCH 10/12] wip --- petr-lower/src/error.rs | 10 -- petr-lower/src/lib.rs | 60 +++++++++-- petr-lower/src/opcodes.rs | 219 -------------------------------------- 3 files changed, 53 insertions(+), 236 deletions(-) delete mode 100644 petr-lower/src/error.rs delete mode 100644 petr-lower/src/opcodes.rs diff --git a/petr-lower/src/error.rs b/petr-lower/src/error.rs deleted file mode 100644 index 01661b3..0000000 --- a/petr-lower/src/error.rs +++ /dev/null @@ -1,10 +0,0 @@ -use miette::Diagnostic; -use thiserror::Error; - -#[derive(Debug, Error, Diagnostic, Clone)] -pub enum LoweringError { - #[error("Internal compiler error: {0}")] - Internal(String), - #[error("Unable to infer type")] - UnableToInferType, -} diff --git a/petr-lower/src/lib.rs b/petr-lower/src/lib.rs index 5dd4f83..bdbcd3f 100644 --- a/petr-lower/src/lib.rs +++ b/petr-lower/src/lib.rs @@ -1,25 +1,71 @@ use std::collections::BTreeMap; +use petr_typecheck::TypedExpr; + trait Lowerer { type Target; - fn lower_expr(&mut self, expr: &TypedExpr) -> Result; -} + type Error; + fn lower_expr( + &mut self, + expr: &TypedExpr, + ) -> Result; +} struct JsFunction { name: String, args: Vec, - body: Vec, + body: Vec, } -enum Js{ +enum Js { Return(Box), - Assign(String, JsExpr), + Assign(String, Box), Call(String, Vec), + Literal(JsLiteral), +} + +enum JsLiteral { + String(String), + Int(i64), + Float(f64), } /// Lowers Petr into Javascript. -struct JSLowerer { - functions: BTreeMap; +struct JSLowerer { + functions: BTreeMap, } +struct JsLoweringError { + kind: JsLoweringErrorKind, +} + +enum JsLoweringErrorKind {} + +impl Lowerer for JSLowerer { + type Error = JsLoweringError; + type Target = Js; + + fn lower_expr( + &mut self, + expr: &TypedExpr, + ) -> Result { + use petr_typecheck::TypedExprKind::*; + match &expr.kind { + FunctionCall { func, args, ty } => todo!(), + Literal { value, ty } => todo!(), + List { elements, ty } => todo!(), + Unit => todo!(), + Variable { ty, name } => todo!(), + Intrinsic { ty, intrinsic } => todo!(), + ErrorRecovery(_) => todo!(), + ExprWithBindings { bindings, expression } => todo!(), + TypeConstructor { ty, args } => todo!(), + If { + condition, + then_branch, + else_branch, + } => todo!(), + } + } +} diff --git a/petr-lower/src/opcodes.rs b/petr-lower/src/opcodes.rs deleted file mode 100644 index e3373d2..0000000 --- a/petr-lower/src/opcodes.rs +++ /dev/null @@ -1,219 +0,0 @@ -use petr_utils::idx_map_key; - -use crate::MonomorphizedFunctionId; - -idx_map_key!(FunctionLabel); - -idx_map_key!(DataLabel); - -macro_rules! ir_ops { - ($($(#[$attr:meta])* - $op_name:ident $op_code:literal $($args:ty: $arg_name:ident),* - );+) => { - - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] - pub enum IrOpcode { - $( - $(#[$attr])* - $op_name($($args),*), - )+ - } - - - impl std::fmt::Display for IrOpcode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - $( - IrOpcode::$op_name($($arg_name),*) => { - write!(f, "{}", $op_code)?; - $( - write!(f, " {}", $arg_name)?; - )* - Ok(()) - } - )+ - } - } - } - - // TODO serialize/deserialize using above display - }; -} - -ir_ops! { - JumpImmediateFunction "fjumpi" MonomorphizedFunctionId: imm; - Jump "jump" Reg: dest; - Add "add" Reg: dest, Reg: lhs, Reg: rhs; - Multiply "mult" Reg: dest, Reg: lhs, Reg: rhs; - Subtract "sub" Reg: dest, Reg: lhs, Reg: rhs; - Divide "div" Reg: dest, Reg: lhs, Reg: rhs; - LoadData "ld" Reg: dest, DataLabel: data; - StackPop "pop" Reg: dest; - StackPush "push" Reg: src; - Intrinsic "intrinsic" Intrinsic: intr; - FunctionLabel "func" MonomorphizedFunctionId: label; - LoadImmediate "imm" Reg: dest, u64: imm; - Copy "cp" Reg: dest, Reg: src; - Label "label" LabelId: label; - Return "ret" Reg: return_value; - ReturnImmediate "reti" u64: imm; - PushPc "ppc"; - StackPushImmediate "pushi" u64: imm; - Malloc "malloc" Reg: ptr_dest, Reg: size; - MallocImmediate "malloci" Reg: ptr_dest, Size: imm; - /// Register `src` will itself have its value written to the memory pointed to by `dest_ptr` - WriteRegisterToMemory "sri" Reg: src, Reg: dest_ptr; - Comment "comment" String: comment; - JumpIfFalseImmediate "cjump" Reg: cond, LabelId: dest; - JumpImmediate "jumpi" LabelId: dest; - Equal "eq" Reg: dest, Reg: lhs, Reg: rhs -} - -idx_map_key!(LabelId); - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Intrinsic { - // given a pointer, print the thing it points to - Puts(Reg), -} - -impl std::fmt::Display for Intrinsic { - fn fmt( - &self, - f: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - write!( - f, - "@{}", - match self { - Intrinsic::Puts(x) => format!("puts({x})"), - } - ) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum IrTy { - Ptr(Box), - Int64, - Unit, - String, - Boolean, - UserDefinedType { - variants: Vec, - constant_literal_types: Vec, - }, - List(Box), -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct IrUserDefinedTypeVariant { - pub fields: Vec, -} - -impl IrUserDefinedTypeVariant { - pub fn size(&self) -> Size { - // the size of a product type is the sum of the sizes of its fields - self.fields.iter().map(|f| f.size().num_bytes()).sum::().into() - } -} - -impl IrTy { - pub fn size(&self) -> Size { - match self { - IrTy::Int64 => 8, - IrTy::Ptr(_) => 8, - IrTy::Unit => 0, - // size of the pointer to the string - IrTy::String => 8, - IrTy::Boolean => 1, - // the size of a sum type is the size of the largest variant - IrTy::UserDefinedType { - variants, - constant_literal_types, - } => { - return variants - .iter() - .map(|v| v.size()) - .chain(constant_literal_types.iter().map(|t| t.size())) - .max() - .expect("user defined type should have at least one variant") - }, - IrTy::List(ty) => { - // the size of a list is the size of a pointer - // to the first element - return IrTy::Ptr(ty.clone()).size(); - }, - } - .into() - } - - pub(crate) fn is_copy_type(&self) -> bool { - self.size().num_bytes() <= 8 - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Size(T) -where - T: SizeUnit; - -impl std::fmt::Debug for Size { - fn fmt( - &self, - f: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - write!(f, "{:?} bytes", self.0.num_bytes()) - } -} - -impl std::fmt::Display for Size { - fn fmt( - &self, - f: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - write!(f, "{} bytes", self.0.num_bytes()) - } -} - -impl Size { - pub fn num_bytes(&self) -> usize { - self.0.num_bytes() - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Bytes(usize); - -pub trait SizeUnit: Clone + Copy + PartialEq + Eq + PartialOrd + Ord { - fn num_bytes(&self) -> usize; -} - -impl SizeUnit for Bytes { - fn num_bytes(&self) -> usize { - self.0 - } -} - -impl From for Size { - fn from(x: usize) -> Self { - Size(Bytes(x)) - } -} - -/// a virtual register -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Reg { - Virtual(usize), -} - -impl std::fmt::Display for Reg { - fn fmt( - &self, - f: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - match self { - Reg::Virtual(a) => write!(f, "v{a}"), - } - } -} From f90eac1c90c36f00cb637f759a0173a42d3d4f70 Mon Sep 17 00:00:00 2001 From: sezna Date: Mon, 19 Aug 2024 09:47:14 -0700 Subject: [PATCH 11/12] wip --- petr-lower/src/lib.rs | 177 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 166 insertions(+), 11 deletions(-) diff --git a/petr-lower/src/lib.rs b/petr-lower/src/lib.rs index 5dd4f83..aaf278b 100644 --- a/petr-lower/src/lib.rs +++ b/petr-lower/src/lib.rs @@ -1,25 +1,180 @@ +#![allow(warnings)] + use std::collections::BTreeMap; +use petr_typecheck::TypedExpr; +use petr_utils::Identifier; + +trait LoweredFunction { + type LoweredExpr; + fn new( + name: String, + args: Vec, + body: Vec, + returns: Option, + ) -> Self; +} + trait Lowerer { - type Target; - fn lower_expr(&mut self, expr: &TypedExpr) -> Result; + type LoweredExpr; + type Error; + type LoweredFunction: LoweredFunction; + + fn lower_expr( + &mut self, + expr: &TypedExpr, + ) -> Result; + + fn commit_lowered_function( + &mut self, + lowered_function: Self::LoweredFunction, + ) -> Result<(), Self::Error>; + + /// Mutates `self` to push a new function declaration. + fn lower_function( + &mut self, + function: &petr_typecheck::Function, + ) -> Result<(), Self::Error> { + let name = function.name.clone(); + let args = function.params.iter().map(|arg| arg.0.clone()).collect(); + let body = function.body.iter().map(|stmt| self.lower_expr(stmt)).collect::, _>>()?; + + let returns = if let Some(expr) = &function.returns { + Some(self.lower_expr(expr)?) + } else { + None + }; + + let lowered_function = Self::LoweredFunction::new(name, args, body, returns); + self.commit_lowered_function(lowered_function) + + Ok(()) + } } +struct LoweredFunction { + name: String, + args: Vec, + body: Vec, + returns: Option, +} struct JsFunction { - name: String, - args: Vec, - body: Vec, + name: String, + args: Vec, + body: Vec, + returns: Option, } -enum Js{ - Return(Box), - Assign(String, JsExpr), - Call(String, Vec), +enum JsExpr { + Literal(JsLiteral), + List(Vec), + // `String` is the var name + Variable(Identifier), + IIFE { + body: Vec, + return_val: Box, + }, + Ternary { + condition: Box, + then_branch: Box, + else_branch: Box, + }, + Call { + function: String, + args: Vec, + }, +} + +enum JsStatement { + Binding(Identifier, JsExpr), + // an expression can become a statement if it is just used with a semicolon after it + Expression(JsExpr), +} + +enum JsLiteral { + Integer(i64), + Boolean(bool), + String(String), } /// Lowers Petr into Javascript. -struct JSLowerer { - functions: BTreeMap; +struct JsLowerer { + functions: BTreeMap, +} + +impl Lowerer for JsLowerer { + type Error = (); + type LoweredExpr = JsExpr; + type LoweredFunction; + + fn lower_expr( + &mut self, + expr: &TypedExpr, + ) -> Result { + use petr_typecheck::{Literal::*, TypedExprKind::*}; + match &expr.kind { + FunctionCall { func, args, ty } => todo!(), + Literal { value, .. } => Ok(match value { + Integer(i) => JsExpr::Literal(JsLiteral::Integer(*i)), + Boolean(b) => JsExpr::Literal(JsLiteral::Boolean(*b)), + String(s) => JsExpr::Literal(JsLiteral::String(s.clone())), + }), + List { elements, ty } => { + let elements = elements.iter().map(|e| self.lower_expr(e)).collect::, _>>()?; + Ok(JsExpr::List(elements)) + }, + Unit => Ok(JsExpr::List(vec![])), + Variable { name, .. } => Ok(JsExpr::Variable(name.clone())), + Intrinsic { ty, intrinsic } => self.lower_intrinsic(intrinsic), + ErrorRecovery(_) => panic!("lowering shouldn't be performed on an AST with errors"), + ExprWithBindings { bindings, expression } => { + let mut body = vec![]; + for binding in bindings { + body.push(JsStatement::Binding(binding.0, self.lower_expr(&binding.1)?)); + } + + let return_val = Box::new(self.lower_expr(expression)?); + Ok(JsExpr::IIFE { body, return_val }) + }, + TypeConstructor { ty, args } => todo!(), + If { + condition, + then_branch, + else_branch, + } => { + let condition = Box::new(self.lower_expr(condition)?); + let then_branch = Box::new(self.lower_expr(then_branch)?); + let else_branch = Box::new(self.lower_expr(else_branch)?); + Ok(JsExpr::Ternary { + condition, + then_branch, + else_branch, + }) + }, + } + } + + fn commit_lowered_function( + &mut self, + lowered_function: Self::LoweredFunction, + ) -> Result<(), Self::Error> { + todo!() + } } +impl JsLowerer { + fn lower_intrinsic( + &mut self, + intrinsic: &petr_typecheck::Intrinsic, + ) -> Result { + use petr_typecheck::Intrinsic::*; + match intrinsic { + Puts(expr) => Ok(JsExpr::Call { + function: "console.log".to_string(), + args: vec![self.lower_expr(expr)?], + }), + _ => todo!(), + } + } +} From a4ef18f3f789175f530c53eb7be31a375e58ae15 Mon Sep 17 00:00:00 2001 From: sezna Date: Wed, 28 Aug 2024 10:26:59 -0700 Subject: [PATCH 12/12] further flesh out lowerer --- Cargo.lock | 1 + petr-lower/Cargo.toml | 2 +- petr-lower/src/lib.rs | 231 +++++++++++++++++++-------------- petr-typecheck/src/solution.rs | 6 +- 4 files changed, 142 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5582a7d..142a5f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1057,6 +1057,7 @@ version = "0.1.0" dependencies = [ "expect-test", "miette", + "petr-api", "petr-parse", "petr-resolve", "petr-stdlib", diff --git a/petr-lower/Cargo.toml b/petr-lower/Cargo.toml index b684c87..e6570d5 100644 --- a/petr-lower/Cargo.toml +++ b/petr-lower/Cargo.toml @@ -22,7 +22,7 @@ petr-parse = { path = "../petr-parse", version = "0.1.0" } expect-test = "1.5.0" petr-resolve = { path = "../petr-resolve", version = "0.1.0" } petr-stdlib = { path = "../petr-stdlib", version = "0.1.0" } - +petr-api = { path = "../petr-api", version = "0.1.0" } [features] debug = ["petr-utils/debug"] diff --git a/petr-lower/src/lib.rs b/petr-lower/src/lib.rs index 92d92fd..aea0daf 100644 --- a/petr-lower/src/lib.rs +++ b/petr-lower/src/lib.rs @@ -1,23 +1,87 @@ #![allow(warnings)] -use std::collections::BTreeMap; +use std::{collections::BTreeMap, fmt::Display, rc::Rc}; use petr_typecheck::TypedExpr; -use petr_utils::Identifier; +use petr_utils::{Identifier, SymbolId, SymbolInterner}; + +#[cfg(test)] +mod tests { + use expect_test::{expect, Expect}; + use petr_api::{render_error, resolve_symbols, type_check}; + + use crate::{JsLowerer, Lowerer as _}; + + fn check( + sources: Vec<(&str, String)>, + expect: Expect, + ) { + let parser = petr_parse::Parser::new(sources); + let (ast, errs, interner, source_map) = parser.into_result(); + if !errs.is_empty() { + errs.into_iter().for_each(|err| eprintln!("{:?}", render_error(&source_map, err))); + panic!("build failed: code didn't parse"); + } + let (errs, resolved) = resolve_symbols(ast, interner, Default::default()); + if !errs.is_empty() { + dbg!(&errs); + panic!("build failed: resolution"); + } + let solution = match type_check(resolved) { + Ok(s) => s, + Err(type_errs) => { + type_errs.into_iter().for_each(|err| eprintln!("{:?}", render_error(&source_map, err))); + panic!("build failed: code didn't type check"); + }, + }; + + // unideal clone, but this is a test + // TODO: improve typesolution -> Lowerer API + let mut lowerer = JsLowerer::from_interner(solution.interner.clone()); + for (id, function) in solution.functions() { + lowerer.lower_function(function).unwrap(); + } + + let js_source = lowerer.produce_target(); + expect.assert_eq(&js_source.to_string()); + + // TODO: execute JS and assert on the result in this test harness + } + + #[test] + fn returns_42() { + check( + vec![("test", "fn main() returns 'int 42".to_string())], + expect![[r#" + fn main() { + return 42; + } + "#]], + ); + } +} trait LoweredFunction { type LoweredExpr; fn new( - name: String, - args: Vec, - body: Vec, - returns: Option, + name: Rc, + args: Vec>, + body: Self::LoweredExpr, ) -> Self; } -trait Lowerer { + +trait SymbolRealizer { + fn realize_symbol_id( + &self, + id: SymbolId, + ) -> Rc; +} + +trait Lowerer: SymbolRealizer { type LoweredExpr; type Error; type LoweredFunction: LoweredFunction; + type Target: Display; fn lower_expr( &mut self, @@ -29,61 +93,50 @@ trait Lowerer { lowered_function: Self::LoweredFunction, ) -> Result<(), Self::Error>; + /// Produce the target language source code. + fn produce_target(self) -> Self::Target; + /// Mutates `self` to push a new function declaration. fn lower_function( &mut self, function: &petr_typecheck::Function, ) -> Result<(), Self::Error> { let name = function.name.clone(); - let args = function.params.iter().map(|arg| arg.0.clone()).collect(); - let body = function.body.iter().map(|stmt| self.lower_expr(stmt)).collect::, _>>()?; + let args = function.params.iter().map(|arg| self.realize_symbol_id(arg.0.id)).collect(); + let body = self.lower_expr(&function.body)?; + let realized_name = self.realize_symbol_id(function.name.id); - let returns = if let Some(expr) = &function.returns { - Some(self.lower_expr(expr)?) - } else { - None - }; - - let lowered_function = Self::LoweredFunction::new(name, args, body, returns); - self.commit_lowered_function(lowered_function) + let lowered_function = Self::LoweredFunction::new(realized_name, args, body); + self.commit_lowered_function(lowered_function); Ok(()) } } -struct LoweredFunction { - name: String, - args: Vec, - body: Vec, - returns: Option, +impl LoweredFunction for LoweredJsFunction { + type LoweredExpr = JsExpr; + + fn new( + name: Rc, + args: Vec>, + body: Self::LoweredExpr, + ) -> Self { + Self { name, args, body } + } } -||||||| 05bc7b9 -======= - fn lower_expr( - &mut self, - expr: &TypedExpr, - ) -> Result; + +struct LoweredJsFunction { + name: Rc, + args: Vec>, + body: JsExpr, } ->>>>>>> 4826e6a8fd5b0098b726e187b7400d8a799e4856 struct JsFunction { -<<<<<<< HEAD - name: String, - args: Vec, - body: Vec, - returns: Option, -||||||| 05bc7b9 - name: String, - args: Vec, - body: Vec, -======= name: String, args: Vec, - body: Vec, ->>>>>>> 4826e6a8fd5b0098b726e187b7400d8a799e4856 + body: JsExpr, } -<<<<<<< HEAD enum JsExpr { Literal(JsLiteral), List(Vec), @@ -110,16 +163,6 @@ enum JsStatement { Expression(JsExpr), } -enum JsLiteral { - Integer(i64), - Boolean(bool), - String(String), -||||||| 05bc7b9 -enum Js{ - Return(Box), - Assign(String, JsExpr), - Call(String, Vec), -======= enum Js { Return(Box), Assign(String, Box), @@ -129,29 +172,52 @@ enum Js { enum JsLiteral { String(String), - Int(i64), + Integer(i64), + Boolean(bool), Float(f64), ->>>>>>> 4826e6a8fd5b0098b726e187b7400d8a799e4856 } /// Lowers Petr into Javascript. -<<<<<<< HEAD struct JsLowerer { - functions: BTreeMap, -||||||| 05bc7b9 -struct JSLowerer { - functions: BTreeMap; -======= -struct JSLowerer { - functions: BTreeMap, ->>>>>>> 4826e6a8fd5b0098b726e187b7400d8a799e4856 + functions: BTreeMap, + symbol_interner: SymbolInterner, +} + +impl JsLowerer { + fn from_interner(interner: SymbolInterner) -> Self { + Self { + functions: Default::default(), + symbol_interner: interner, + } + } +} + +impl SymbolRealizer for JsLowerer { + fn realize_symbol_id( + &self, + id: SymbolId, + ) -> Rc { + self.symbol_interner.get(id) + } +} + +/// Simple wrapper type to denote that a string is JS source +struct JsSource(String); + +impl Display for JsSource { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(f, "{}", self.0) + } } -<<<<<<< HEAD impl Lowerer for JsLowerer { type Error = (); type LoweredExpr = JsExpr; - type LoweredFunction; + type LoweredFunction = LoweredJsFunction; + type Target = JsSource; fn lower_expr( &mut self, @@ -206,6 +272,10 @@ impl Lowerer for JsLowerer { ) -> Result<(), Self::Error> { todo!() } + + fn produce_target(self) -> Self::Target { + todo!() + } } impl JsLowerer { @@ -223,39 +293,8 @@ impl JsLowerer { } } } -||||||| 05bc7b9 -======= struct JsLoweringError { kind: JsLoweringErrorKind, } enum JsLoweringErrorKind {} - -impl Lowerer for JSLowerer { - type Error = JsLoweringError; - type Target = Js; - - fn lower_expr( - &mut self, - expr: &TypedExpr, - ) -> Result { - use petr_typecheck::TypedExprKind::*; - match &expr.kind { - FunctionCall { func, args, ty } => todo!(), - Literal { value, ty } => todo!(), - List { elements, ty } => todo!(), - Unit => todo!(), - Variable { ty, name } => todo!(), - Intrinsic { ty, intrinsic } => todo!(), - ErrorRecovery(_) => todo!(), - ExprWithBindings { bindings, expression } => todo!(), - TypeConstructor { ty, args } => todo!(), - If { - condition, - then_branch, - else_branch, - } => todo!(), - } - } -} ->>>>>>> 4826e6a8fd5b0098b726e187b7400d8a799e4856 diff --git a/petr-typecheck/src/solution.rs b/petr-typecheck/src/solution.rs index ba8a62b..e783caf 100644 --- a/petr-typecheck/src/solution.rs +++ b/petr-typecheck/src/solution.rs @@ -57,7 +57,7 @@ pub struct TypeSolution { solution: BTreeMap, unsolved_types: IndexMap, errors: Vec, - interner: SymbolInterner, + pub interner: SymbolInterner, error_recovery: TypeVariable, unit: TypeVariable, functions: BTreeMap, @@ -538,4 +538,8 @@ impl TypeSolution { pub fn interner(&self) -> &SymbolInterner { &self.interner } + + pub fn functions(&self) -> &BTreeMap { + &self.functions + } }