use std::collections::HashMap; use std::mem; use ast::hir::{Binding, Pattern}; use ast::Literal; use void::{ResultVoidExt, Void}; use crate::ast::hir::{Decl, Expr}; use crate::ast::{self, Ident}; use super::Visitor; /// Strip all values with a unit type in positive (non-return) position pub(crate) struct StripPositiveUnits {} impl<'a, 'ast> Visitor<'a, 'ast, ast::Type<'ast>> for StripPositiveUnits { type Error = Void; fn pre_visit_expr( &mut self, expr: &mut Expr<'ast, ast::Type<'ast>>, ) -> Result<(), Self::Error> { let mut extracted = vec![]; if let Expr::Call { args, .. } = expr { // TODO(grfn): replace with drain_filter once it's stabilized let mut i = 0; while i != args.len() { if args[i].type_() == &ast::Type::Unit { let expr = args.remove(i); if !matches!(expr, Expr::Literal(Literal::Unit, _)) { extracted.push(expr) }; } else { i += 1 } } } if !extracted.is_empty() { let body = mem::replace(expr, Expr::Literal(Literal::Unit, ast::Type::Unit)); *expr = Expr::Let { bindings: extracted .into_iter() .map(|expr| Binding { pat: Pattern::Id( Ident::from_str_unchecked("___discarded"), expr.type_().clone(), ), body: expr, }) .collect(), type_: body.type_().clone(), body: Box::new(body), }; } Ok(()) } fn post_visit_call( &mut self, _fun: &mut Expr<'ast, ast::Type<'ast>>, _type_args: &mut HashMap<Ident<'ast>, ast::Type<'ast>>, args: &mut Vec<Expr<'ast, ast::Type<'ast>>>, ) -> Result<(), Self::Error> { args.retain(|arg| arg.type_() != &ast::Type::Unit); Ok(()) } fn visit_type(&mut self, type_: &mut ast::Type<'ast>) -> Result<(), Self::Error> { if let ast::Type::Function(ft) = type_ { ft.args.retain(|a| a != &ast::Type::Unit); } Ok(()) } fn post_visit_fun_decl( &mut self, _name: &mut Ident<'ast>, _type_args: &mut Vec<Ident>, args: &mut Vec<(Ident, ast::Type<'ast>)>, _body: &mut Box<Expr<ast::Type<'ast>>>, _type_: &mut ast::Type<'ast>, ) -> Result<(), Self::Error> { args.retain(|(_, ty)| ty != &ast::Type::Unit); Ok(()) } } pub(crate) fn run_toplevel<'a>(toplevel: &mut Vec<Decl<'a, ast::Type<'a>>>) { let mut pass = StripPositiveUnits {}; for decl in toplevel.iter_mut() { pass.visit_decl(decl).void_unwrap(); } } #[cfg(test)] mod tests { use super::*; use crate::parser::toplevel; use crate::tc::typecheck_toplevel; use pretty_assertions::assert_eq; #[test] fn unit_only_arg() { let (_, program) = toplevel( "ty f : fn () -> int fn f _ = 1 ty main : fn -> int fn main = f ()", ) .unwrap(); let (_, expected) = toplevel( "ty f : fn -> int fn f = 1 ty main : fn -> int fn main = f()", ) .unwrap(); let expected = typecheck_toplevel(expected).unwrap(); let mut program = typecheck_toplevel(program).unwrap(); run_toplevel(&mut program); assert_eq!(program, expected); } #[test] fn unit_and_other_arg() { let (_, program) = toplevel( "ty f : fn (), int -> int fn f _ x = x ty main : fn -> int fn main = f () 1", ) .unwrap(); let (_, expected) = toplevel( "ty f : fn int -> int fn f x = x ty main : fn -> int fn main = f 1", ) .unwrap(); let expected = typecheck_toplevel(expected).unwrap(); let mut program = typecheck_toplevel(program).unwrap(); run_toplevel(&mut program); assert_eq!(program, expected); } #[test] fn unit_expr_and_other_arg() { let (_, program) = toplevel( "ty f : fn (), int -> int fn f _ x = x ty g : fn int -> () fn g _ = () ty main : fn -> int fn main = f (g 2) 1", ) .unwrap(); let (_, expected) = toplevel( "ty f : fn int -> int fn f x = x ty g : fn int -> () fn g _ = () ty main : fn -> int fn main = let ___discarded = g 2 in f 1", ) .unwrap(); assert_eq!(expected.len(), 6); let expected = typecheck_toplevel(expected).unwrap(); let mut program = typecheck_toplevel(program).unwrap(); run_toplevel(&mut program); assert_eq!(program, expected); } }