Repository: tweag/nickel
Pull request #198 (2020-11-06 11:20:25)

[Refactoring]Add and use helpers to build Nickel terms
Add helper functions and macros to make building terms directly a bit easier and readable, trying to reduce the `(a.into(), b.into()).into()` kind of pattern. Doing so, make a few other details more consistent across the code base (ex: remove the `RichTerm::new` constructor which is redundant with the `From<Term>` trait implementation, and both were used in different places), and remove trailing `println!` debug statements.


Patch:
diff --git a/src/eval.rs b/src/eval.rs
index cf324bec..52a24931 100644
--- a/src/eval.rs
+++ b/src/eval.rs
@@ -286,7 +286,7 @@ where
                     },
                     None,
                 );
-                stack.push_arg(Closure::atomic_closure(RichTerm::new(Term::Lbl(l))), None);
+                stack.push_arg(Closure::atomic_closure(Term::Lbl(l).into()), None);
                 Closure {
                     body: ty.contract(),
                     env,
@@ -483,9 +483,11 @@ mod tests {
     use crate::error::ImportError;
     use crate::label::Label;
     use crate::program::resolvers::{DummyResolver, SimpleResolver};
+    use crate::term::make as mk_term;
     use crate::term::StrChunk;
     use crate::term::{BinaryOp, UnaryOp};
     use crate::transformations::transform;
+    use crate::{mk_app, mk_fun};
 
     /// Evaluate a term without import support.
     fn eval_no_import(t: RichTerm) -> Result<Term, EvalError> {
@@ -500,18 +502,15 @@ mod tests {
         let boolean = Term::Bool(true);
         assert_eq!(Ok(boolean.clone()), eval_no_import(boolean.into()));
 
-        let lambda = Term::Fun(
-            Ident("x".to_string()),
-            RichTerm::app(RichTerm::var("x".into()), RichTerm::var("x".into())),
-        );
-        assert_eq!(Ok(lambda.clone()), eval_no_import(lambda.into()));
+        let lambda = mk_fun!("x", mk_app!(mk_term::var("x"), mk_term::var("x")));
+        assert_eq!(Ok(lambda.as_ref().clone()), eval_no_import(lambda.into()));
     }
 
     #[test]
     fn blame_panics() {
         let label = Label::dummy();
         if let Err(EvalError::BlameError(l, _)) =
-            eval_no_import(Term::Op1(UnaryOp::Blame(), Term::Lbl(label.clone()).into()).into())
+            eval_no_import(mk_term::op1(UnaryOp::Blame(), Term::Lbl(label.clone())))
         {
             assert_eq!(l, label);
         } else {
@@ -522,74 +521,56 @@ mod tests {
     #[test]
     #[should_panic]
     fn lone_var_panics() {
-        eval_no_import(RichTerm::var("unbound".into())).unwrap();
+        eval_no_import(mk_term::var("unbound")).unwrap();
     }
 
     #[test]
     fn only_fun_are_applicable() {
-        eval_no_import(RichTerm::app(Term::Bool(true).into(), Term::Num(45.).into()).into())
-            .unwrap_err();
+        eval_no_import(mk_app!(Term::Bool(true), Term::Num(45.))).unwrap_err();
     }
 
     #[test]
     fn simple_app() {
-        let t = RichTerm::app(
-            Term::Fun(Ident("x".to_string()), RichTerm::var("x".into())).into(),
-            Term::Num(5.0).into(),
-        );
-
+        let t = mk_app!(mk_term::id(), Term::Num(5.0));
         assert_eq!(Ok(Term::Num(5.0)), eval_no_import(t));
     }
 
     #[test]
     fn simple_let() {
-        let t = RichTerm::let_in("x", Term::Num(5.0).into(), RichTerm::var("x".into()));
-
+        let t = mk_term::let_in("x", Term::Num(5.0), mk_term::var("x"));
         assert_eq!(Ok(Term::Num(5.0)), eval_no_import(t));
     }
 
     #[test]
     fn simple_ite() {
-        let t = RichTerm::ite(
-            Term::Bool(true).into(),
-            Term::Num(5.0).into(),
-            Term::Bool(false).into(),
-        );
-
+        let t = mk_term::if_then_else(Term::Bool(true), Term::Num(5.0), Term::Bool(false));
         assert_eq!(Ok(Term::Num(5.0)), eval_no_import(t));
     }
 
     #[test]
     fn simple_plus() {
-        let t = RichTerm::plus(Term::Num(5.0).into(), Term::Num(7.5).into());
-
+        let t = mk_term::op2(BinaryOp::Plus(), Term::Num(5.0), Term::Num(7.5));
         assert_eq!(Ok(Term::Num(12.5)), eval_no_import(t));
     }
 
     #[test]
     fn simple_is_zero() {
-        let t = Term::Op1(UnaryOp::IsZero(), Term::Num(7.0).into()).into();
-
+        let t = mk_term::op1(UnaryOp::IsZero(), Term::Num(7.0));
         assert_eq!(Ok(Term::Bool(false)), eval_no_import(t));
     }
 
     #[test]
     fn asking_for_various_types() {
-        let num = Term::Op1(UnaryOp::IsNum(), Term::Num(45.3).into()).into();
+        let num = mk_term::op1(UnaryOp::IsNum(), Term::Num(45.3));
         assert_eq!(Ok(Term::Bool(true)), eval_no_import(num));
 
-        let boolean = Term::Op1(UnaryOp::IsBool(), Term::Bool(true).into()).into();
+        let boolean = mk_term::op1(UnaryOp::IsBool(), Term::Bool(true));
         assert_eq!(Ok(Term::Bool(true)), eval_no_import(boolean));
 
-        let lambda = Term::Op1(
+        let lambda = mk_term::op1(
             UnaryOp::IsFun(),
-            Term::Fun(
-                Ident("x".to_string()),
-                RichTerm::app(RichTerm::var("x".into()), RichTerm::var("x".into())),
-            )
-            .into(),
-        )
-        .into();
+            mk_fun!("x", mk_app!(mk_term::var("x"), mk_term::var("x"))),
+        );
         assert_eq!(Ok(Term::Bool(true)), eval_no_import(lambda));
     }
 
@@ -605,23 +586,21 @@ mod tests {
 
     #[test]
     fn merge_enriched_default() {
-        let t = Term::Op2(
+        let t = mk_term::op2(
             BinaryOp::Merge(),
-            Term::Num(1.0).into(),
-            Term::DefaultValue(Term::Num(2.0).into()).into(),
-        )
-        .into();
+            Term::Num(1.0),
+            Term::DefaultValue(Term::Num(2.0).into()),
+        );
         assert_eq!(Ok(Term::Num(1.0)), eval_no_import(t));
     }
 
     #[test]
     fn merge_incompatible_defaults() {
-        let t = Term::Op2(
+        let t = mk_term::op2(
             BinaryOp::Merge(),
-            Term::DefaultValue(Term::Num(1.0).into()).into(),
-            Term::DefaultValue(Term::Num(2.0).into()).into(),
-        )
-        .into();
+            Term::DefaultValue(Term::Num(1.0).into()),
+            Term::DefaultValue(Term::Num(2.0).into()),
+        );
 
         eval_no_import(t).unwrap_err();
     }
@@ -655,26 +634,19 @@ mod tests {
             R: ImportResolver,
         {
             transform(
-                RichTerm::let_in(var, Term::Import(String::from(import)).into(), body),
+                mk_term::let_in(var, Term::Import(String::from(import)), body),
                 resolver,
             )
         };
 
         // let x = import "does_not_exist" in x
-        match mk_import(
-            "x",
-            "does_not_exist",
-            RichTerm::var(String::from("x")),
-            &mut resolver,
-        )
-        .unwrap_err()
-        {
+        match mk_import("x", "does_not_exist", mk_term::var("x"), &mut resolver).unwrap_err() {
             ImportError::IOError(_, _, _) => (),
             _ => assert!(false),
         };
 
         // let x = import "bad" in x
-        match mk_import("x", "bad", RichTerm::var(String::from("x")), &mut resolver).unwrap_err() {
+        match mk_import("x", "bad", mk_term::var("x"), &mut resolver).unwrap_err() {
             ImportError::ParseError(_, _) => (),
             _ => assert!(false),
         };
@@ -682,7 +654,7 @@ mod tests {
         // let x = import "two" in x
         assert_eq!(
             eval(
-                mk_import("x", "two", RichTerm::var(String::from("x")), &mut resolver).unwrap(),
+                mk_import("x", "two", mk_term::var("x"), &mut resolver).unwrap(),
                 HashMap::new(),
                 &mut resolver
             )
@@ -693,13 +665,7 @@ mod tests {
         // let x = import "nested" in x
         assert_eq!(
             eval(
-                mk_import(
-                    "x",
-                    "nested",
-                    RichTerm::var(String::from("x")),
-                    &mut resolver
-                )
-                .unwrap(),
+                mk_import("x", "nested", mk_term::var("x"), &mut resolver).unwrap(),
                 HashMap::new(),
                 &mut resolver
             )
@@ -713,11 +679,7 @@ mod tests {
                 mk_import(
                     "x",
                     "lib",
-                    Term::Op1(
-                        UnaryOp::StaticAccess(Ident::from("f")),
-                        RichTerm::var(String::from("x"))
-                    )
-                    .into(),
+                    mk_term::op1(UnaryOp::StaticAccess(Ident::from("f")), mk_term::var("x")),
                     &mut resolver,
                 )
                 .unwrap(),
@@ -734,11 +696,7 @@ mod tests {
                 mk_import(
                     "x",
                     "cycle",
-                    Term::Op1(
-                        UnaryOp::StaticAccess(Ident::from("b")),
-                        RichTerm::var(String::from("x"))
-                    )
-                    .into(),
+                    mk_term::op1(UnaryOp::StaticAccess(Ident::from("b")), mk_term::var("x")),
                     &mut resolver,
                 )
                 .unwrap(),
@@ -755,18 +713,18 @@ mod tests {
         let mut chunks = vec![
             StrChunk::Literal(String::from("Hello")),
             StrChunk::Expr(
-                Term::Op2(
+                mk_term::op2(
                     BinaryOp::PlusStr(),
-                    Term::Str(String::from(", ")).into(),
-                    Term::Str(String::from("World!")).into(),
+                    mk_term::string(", "),
+                    mk_term::string("World!"),
                 )
                 .into(),
             ),
             StrChunk::Literal(String::from(" How")),
-            StrChunk::Expr(RichTerm::ite(
-                Term::Bool(true).into(),
-                Term::Str(String::from(" are")).into(),
-                Term::Str(String::from(" is")).into(),
+            StrChunk::Expr(mk_term::if_then_else(
+                Term::Bool(true),
+                mk_term::string(" are"),
+                mk_term::string(" is"),
             )),
             StrChunk::Literal(String::from(" you?")),
         ];
@@ -786,15 +744,15 @@ mod tests {
             StrChunk::Expr(
                 Term::Op2(
                     BinaryOp::PlusStr(),
-                    Term::Str(String::from(" ar")).into(),
-                    Term::Str(String::from("e")).into(),
+                    mk_term::string(" ar"),
+                    mk_term::string("e"),
                 )
                 .into(),
             ),
-            StrChunk::Expr(RichTerm::ite(
-                Term::Bool(true).into(),
-                Term::Str(String::from(" you")).into(),
-                Term::Str(String::from(" me")).into(),
+            StrChunk::Expr(mk_term::if_then_else(
+                Term::Bool(true),
+                mk_term::string(" you"),
+                mk_term::string(" me"),
             )),
         ];
         inner_chunks.reverse();
@@ -817,38 +775,23 @@ mod tests {
     fn global_env() {
         let mut global_env = HashMap::new();
         let mut resolver = DummyResolver {};
-        let thunk = Rc::new(RefCell::new(Closure {
-            body: Term::Num(1.0).into(),
-            env: HashMap::new(),
-        }));
+        let thunk = Rc::new(RefCell::new(Closure::atomic_closure(Term::Num(1.0).into())));
         global_env.insert(Ident::from("g"), (Rc::clone(&thunk), IdentKind::Let()));
 
-        let t = RichTerm::let_in(
-            "x",
-            Term::Num(2.0).into(),
-            Term::Var(Ident::from("x")).into(),
-        );
+        let t = mk_term::let_in("x", Term::Num(2.0), mk_term::var("x"));
         assert_eq!(
             eval(t, global_env.clone(), &mut resolver),
             Ok(Term::Num(2.0))
         );
 
-        let t = RichTerm::let_in(
-            "x",
-            Term::Num(2.0).into(),
-            Term::Var(Ident::from("g")).into(),
-        );
+        let t = mk_term::let_in("x", Term::Num(2.0), mk_term::var("g"));
         assert_eq!(
             eval(t, global_env.clone(), &mut resolver),
             Ok(Term::Num(1.0))
         );
 
         // Shadowing of global environment
-        let t = RichTerm::let_in(
-            "g",
-            Term::Num(2.0).into(),
-            Term::Var(Ident::from("g")).into(),
-        );
+        let t = mk_term::let_in("g", Term::Num(2.0), mk_term::var("g"));
         assert_eq!(
             eval(t, global_env.clone(), &mut resolver),
             Ok(Term::Num(2.0))
diff --git a/src/grammar.lalrpop b/src/grammar.lalrpop
index 02746406..aebe1076 100644
--- a/src/grammar.lalrpop
+++ b/src/grammar.lalrpop
@@ -1,5 +1,7 @@
 use crate::identifier::Ident;
 use crate::term::{BinaryOp, RichTerm, Term, UnaryOp, StrChunk};
+use crate::term::make as mk_term;
+use crate::mk_app;
 use crate::types::{Types, AbsType};
 use super::utils::{mk_span, mk_label};
 use super::lexer::{Token, NormalToken, StringToken, LexicalError};
@@ -20,12 +22,12 @@ SpTerm<Rule>: RichTerm =
     };
 
 LeftOp<Op, Current, Previous>: RichTerm =
-    <t1: Current> <op: Op> <t2: Previous> => RichTerm::new(Term::Op2(op, t1,
-    t2));
+    <t1: Current> <op: Op> <t2: Previous> => mk_term::op2(op, t1,
+    t2);
 
 LeftOpLazy<Op, Current, Previous>: RichTerm =
     <t1: Current> <op: Op> <t2: Previous> =>
-        RichTerm::app(RichTerm::new(Term::Op1(op, t1)), t2);
+        mk_app!(Term::Op1(op, t1), t2);
 
 RichTerm: RichTerm = {
     <l: @L> "fun" <ps:Pattern+> "=>" <t: SpTerm<Term>> <r: @R> => {
@@ -36,17 +38,17 @@ RichTerm: RichTerm = {
         })
     },
     "let" <id:Ident> "=" <t1:SpTerm<Term>> "in" <t2:SpTerm<Term>> =>
-        RichTerm::new(Term::Let(id, t1, t2)),
+        mk_term::let_in(id, t1, t2),
     "if" <b:SpTerm<Term>> "then" <t:SpTerm<Term>> "else" <e:SpTerm<Term>> =>
-        RichTerm::app(RichTerm::app(RichTerm::new(Term::Op1(UnaryOp::Ite(), b)), t), e),
-    "import" <s: Str> => RichTerm::new(Term::Import(s)),
+        mk_app!(Term::Op1(UnaryOp::Ite(), b), t, e),
+    "import" <s: Str> => RichTerm::from(Term::Import(s)),
     SpTerm<InfixExpr>,
 };
 
 Applicative: RichTerm = {
-    <t1:SpTerm< Applicative>> <t2: SpTerm<Atom>> => RichTerm::new(Term::App(t1, t2)),
-    <op: UOp> <t: SpTerm<Atom>> => RichTerm::new(Term::Op1(op, t)),
-    <op: BOpPre> <t1: SpTerm<Atom>> <t2: SpTerm<Atom>> => RichTerm::new(Term::Op2(op, t1, t2)),
+    <t1:SpTerm< Applicative>> <t2: SpTerm<Atom>> => mk_app!(t1, t2),
+    <op: UOp> <t: SpTerm<Atom>> => mk_term::op1(op, t),
+    <op: BOpPre> <t1: SpTerm<Atom>> <t2: SpTerm<Atom>> => mk_term::op2(op, t1, t2),
     SpTerm<RecordOperationChain>,
     SpTerm<Atom>,
 };
@@ -57,32 +59,32 @@ RecordOperand: RichTerm = {
 }
 
 RecordOperationChain: RichTerm = {
-    <t: SpTerm<RecordOperand>> "." <id: Ident> => RichTerm::new(Term::Op1(UnaryOp::StaticAccess(id), t)),
-    <t: SpTerm<RecordOperand>> ".$" <t_id: SpTerm<Atom>> => RichTerm::new(Term::Op2(BinaryOp::DynAccess(), t_id, t)),
-    <t: SpTerm<RecordOperand>> "-$" <t_id: SpTerm<Atom>> => RichTerm::new(Term::Op2(BinaryOp::DynRemove(), t_id, t)),
+    <t: SpTerm<RecordOperand>> "." <id: Ident> => mk_term::op1(UnaryOp::StaticAccess(id), t),
+    <t: SpTerm<RecordOperand>> ".$" <t_id: SpTerm<Atom>> => mk_term::op2(BinaryOp::DynAccess(), t_id, t),
+    <t: SpTerm<RecordOperand>> "-$" <t_id: SpTerm<Atom>> => mk_term::op2(BinaryOp::DynRemove(), t_id, t),
     <r: SpTerm<RecordOperand>> "$[" <id: SpTerm<Term>> "=" <t: SpTerm<Term>> "]" =>
-        RichTerm::new(Term::Op2(BinaryOp::DynExtend(t), id, r)),
+        mk_term::op2(BinaryOp::DynExtend(t), id, r),
 };
 
 Atom: RichTerm = {
     "(" <SpTerm<Term>> ")",
     <l: @L> "Promise(" <ty: Types> "," <t: SpTerm<Term>> ")" <r: @R> =>
-        RichTerm::new(Term::Promise(ty.clone(), mk_label(ty, src_id, l, r), t)),
+        RichTerm::from(Term::Promise(ty.clone(), mk_label(ty, src_id, l, r), t)),
     <l: @L> "Assume(" <ty: Types> "," <t: SpTerm<Term>> ")" <r: @R> =>
-        RichTerm::new(Term::Assume(ty.clone(), mk_label(ty, src_id, l, r), t)),
+        RichTerm::from(Term::Assume(ty.clone(), mk_label(ty, src_id, l, r), t)),
     <l: @L> "Contract(" <ty: Types> ")" <r: @R> =>
-        RichTerm::new(Term::Contract(ty.clone(), mk_label(ty, src_id, l, r))),
-    "Default(" <t: SpTerm<Term>> ")" => RichTerm::new(Term::DefaultValue(t)),
+        RichTerm::from(Term::Contract(ty.clone(), mk_label(ty, src_id, l, r))),
+    "Default(" <t: SpTerm<Term>> ")" => RichTerm::from(Term::DefaultValue(t)),
     <l: @L> "ContractDefault(" <ty: Types> "," <t: SpTerm<Term>> ")" <r: @R> =>
-        RichTerm::new(Term::ContractWithDefault(ty.clone(),
+        RichTerm::from(Term::ContractWithDefault(ty.clone(),
             mk_label(ty, src_id, l, r), t)
         ),
-    "Docstring(" <s: Str> "," <t: SpTerm<Term>> ")" => RichTerm::new(Term::Docstring(s, t)),
-    "num literal" => RichTerm::new(Term::Num(<>)),
-    Bool => RichTerm::new(Term::Bool(<>)),
+    "Docstring(" <s: Str> "," <t: SpTerm<Term>> ")" => RichTerm::from(Term::Docstring(s, t)),
+    "num literal" => RichTerm::from(Term::Num(<>)),
+    Bool => RichTerm::from(Term::Bool(<>)),
     <StrChunks>,
-    Ident => RichTerm::new(Term::Var(<>)),
-    "`" <Ident> => RichTerm::new(Term::Enum(<>)),
+    Ident => RichTerm::from(Term::Var(<>)),
+    "`" <Ident> => RichTerm::from(Term::Enum(<>)),
     "{" <fields: (RecordField ";")*> <last: RecordField?> "}" => {
         let mut static_map = HashMap::new();
         let mut dynamic_fields = Vec::new();
@@ -96,18 +98,18 @@ Atom: RichTerm = {
                 Right(t) => dynamic_fields.push(t),
             });
 
-        let static_rec = RichTerm::new(Term::RecRecord(static_map));
+        let static_rec = RichTerm::from(Term::RecRecord(static_map));
 
         dynamic_fields.into_iter().fold(static_rec, |rec, field| {
             let (id_t, t) = field;
-            RichTerm::new(Term::Op2(BinaryOp::DynExtend(t), id_t, rec))
+            RichTerm::from(Term::Op2(BinaryOp::DynExtend(t), id_t, rec))
         })
     },
     "[" <terms: (SpTerm<Atom> ",")*> <last: SpTerm<Term>?> "]" => {
         let terms : Vec<RichTerm> = terms.into_iter()
             .map(|x| x.0)
             .chain(last.into_iter()).collect();
-        RichTerm::new(Term::List(terms))
+        RichTerm::from(Term::List(terms))
     }
 };
 
@@ -144,10 +146,10 @@ StrChunks : RichTerm =
             .rev()
             .collect();
 
-        RichTerm::new(Term::StrChunks(chunks))
+        RichTerm::from(Term::StrChunks(chunks))
     };
 
-ChunkLiteral: String = 
+ChunkLiteral: String =
     <parts: ChunkLiteralPart+> => {
         parts.into_iter().fold(String::new(), |mut acc, part| {
             match part {
@@ -215,7 +217,7 @@ InfixExpr0: RichTerm = {
 PrefixExpr1: RichTerm = {
     InfixExpr0,
     "-" <t: PrefixExpr1> =>
-        RichTerm::new(Term::Op2(BinaryOp::Sub(), Term::Num(0.0).into(), t)),
+        mk_term::op2(BinaryOp::Sub(), Term::Num(0.0), t),
 }
 
 BinOp2: BinaryOp<RichTerm> = {
@@ -251,7 +253,7 @@ InfixExpr4: RichTerm = {
 
 PrefixExpr5: RichTerm = {
     InfixExpr4,
-    "!" <PrefixExpr5> => RichTerm::new(Term::Op1(UnaryOp::BoolNot(), <>)), 
+    "!" <PrefixExpr5> => mk_term::op1(UnaryOp::BoolNot(), <>),
 }
 
 BinOp6: BinaryOp<RichTerm> = {
diff --git a/src/merge.rs b/src/merge.rs
index 7feb067f..b271422d 100644
--- a/src/merge.rs
+++ b/src/merge.rs
@@ -55,9 +55,11 @@ use crate::error::EvalError;
 use crate::eval::{Closure, Environment};
 use crate::label::Label;
 use crate::position::RawSpan;
+use crate::term::make as mk_term;
 use crate::term::{BinaryOp, RichTerm, Term};
 use crate::transformations::Closurizable;
 use crate::types::{AbsType, Types};
+use crate::{mk_app, mk_fun};
 use std::collections::HashMap;
 
 /// Compute the merge of two evaluated operands.
@@ -356,21 +358,16 @@ fn mk_merge_closure(t1: RichTerm, env1: Environment, t2: RichTerm, env2: Environ
 /// notes](https://github.com/tweag/nickel/blob/master/notes/intersection-and-union-types.md) in
 /// the repository).
 fn merge_contracts(c1: RichTerm, l1: Label, c2: RichTerm, l2: Label) -> Types {
-    let contract = RichTerm::fun(
-        "_l".to_string(),
-        RichTerm::fun(
-            "x".to_string(),
-            RichTerm::app(
-                RichTerm::app(c1, Term::Lbl(l1).into()),
-                RichTerm::app(
-                    RichTerm::app(c2, Term::Lbl(l2).into()),
-                    RichTerm::var("x".to_string()),
-                ),
-            ),
-        ),
+    let contract: RichTerm = mk_fun!(
+        "_l",
+        "x",
+        mk_app!(
+            c1,
+            Term::Lbl(l1),
+            mk_app!(c2, Term::Lbl(l2), mk_term::var("x"))
+        )
     );
-
-    Types(AbsType::Flat(contract.into()))
+    Types(AbsType::Flat(contract))
 }
 
 /// [Closurize](../transformations/trait.Closurizable.html) two types with their respective
diff --git a/src/operation.rs b/src/operation.rs
index df11b14d..f39552ed 100644
--- a/src/operation.rs
+++ b/src/operation.rs
@@ -15,8 +15,10 @@ use crate::merge;
 use crate::merge::merge;
 use crate::position::RawSpan;
 use crate::stack::Stack;
+use crate::term::make as mk_term;
 use crate::term::{BinaryOp, RichTerm, StrChunk, Term, UnaryOp};
 use crate::transformations::Closurizable;
+use crate::{mk_app, mk_fun};
 use simple_counter::*;
 use std::collections::HashMap;
 
@@ -350,13 +352,10 @@ fn process_unary_operation(
         }
         UnaryOp::Wrap() => {
             if let Term::Sym(s) = *t {
-                Ok(Closure::atomic_closure(
-                    Term::Fun(
-                        Ident("x".to_string()),
-                        Term::Wrapped(s, RichTerm::var("x".to_string())).into(),
-                    )
-                    .into(),
-                ))
+                Ok(Closure::atomic_closure(mk_fun!(
+                    "x",
+                    Term::Wrapped(s, mk_term::var("x"))
+                )))
             } else {
                 Err(EvalError::TypeError(
                     String::from("Sym"),
@@ -394,7 +393,7 @@ fn process_unary_operation(
             if let Term::Record(map) = *t {
                 let mut fields: Vec<String> = map.keys().map(|Ident(id)| id.clone()).collect();
                 fields.sort();
-                let terms = fields.into_iter().map(|id| Term::Str(id).into()).collect();
+                let terms = fields.into_iter().map(mk_term::string).collect();
                 Ok(Closure::atomic_closure(Term::List(terms).into()))
             } else {
                 Err(EvalError::TypeError(
@@ -415,11 +414,7 @@ fn process_unary_operation(
                         let (Ident(s), t) = e;
                         (
                             Ident(s.clone()),
-                            Term::App(
-                                Term::App(f_as_var.clone(), Term::Str(s.clone()).into()).into(),
-                                t.clone(),
-                            )
-                            .into(),
+                            mk_app!(f_as_var.clone(), mk_term::string(s), t.clone()),
                         )
                     })
                     .collect();
@@ -457,8 +452,8 @@ fn process_unary_operation(
                 let first = terms
                     .next()
                     .expect("expected the argument to be a non-empty iterator");
-                let body = terms.fold(Term::Op1(UnaryOp::DeepSeq(), first).into(), |acc, t| {
-                    Term::App(Term::Op1(UnaryOp::DeepSeq(), t).into(), acc).into()
+                let body = terms.fold(mk_term::op1(UnaryOp::DeepSeq(), first), |acc, t| {
+                    mk_app!(mk_term::op1(UnaryOp::DeepSeq(), t), acc).into()
                 });
 
                 Ok(Closure { body, env })
@@ -788,19 +783,14 @@ fn process_binary_operation(
                 Ok(if let Term::Wrapped(s2, t) = *t2 {
                     if s1 == s2 {
                         Closure {
-                            body: Term::Fun(Ident("-invld".to_string()), t).into(),
+                            body: mk_fun!("-invld", t),
                             env: env2,
                         }
                     } else {
-                        Closure::atomic_closure(
-                            Term::Fun(Ident("x".to_string()), RichTerm::var("x".to_string()))
-                                .into(),
-                        )
+                        Closure::atomic_closure(mk_term::id())
                     }
                 } else {
-                    Closure::atomic_closure(
-                        Term::Fun(Ident("x".to_string()), RichTerm::var("x".to_string())).into(),
-                    )
+                    Closure::atomic_closure(mk_term::id())
                 })
             } else {
                 Err(EvalError::TypeError(
@@ -837,15 +827,13 @@ fn process_binary_operation(
                     .collect();
                 // lists.all (fun x => x) subeqs
                 Term::App(
-                    RichTerm::app(
+                    mk_app!(
                         // lists.all
-                        Term::Op1(
+                        mk_term::op1(
                             UnaryOp::StaticAccess(Ident::from("all")),
-                            RichTerm::var(String::from("lists")),
-                        )
-                        .into(),
-                        // identity (we should probably have it in the stdlib at some point)
-                        RichTerm::fun(String::from("x"), RichTerm::var(String::from("x"))),
+                            mk_term::var("lists"),
+                        ),
+                        mk_term::id()
                     ),
                     Term::List(subeqs).into(),
                 )
diff --git a/src/program.rs b/src/program.rs
index 4f941e18..aa75071a 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -1339,6 +1339,7 @@ Assume(#alwaysTrue -> #alwaysFalse, not ) true
         eval_string("\"a\" || false").unwrap_err();
     }
 
+    #[test]
     fn records_contracts_simple() {
         assert_peq!("Assume({ {| |} }, {})", "{}");
         eval_string("Assume({ {| |} }, {a=1})").unwrap_err();
diff --git a/src/term.rs b/src/term.rs
index 2d018ba7..c69153c6 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -146,6 +146,7 @@ pub enum StrChunk<E> {
 }
 
 impl Term {
+    #[cfg(test)]
     /// Recursively apply a function to all `Term`s contained in a `RichTerm`.
     pub fn apply_to_rich_terms<F>(&mut self, func: F)
     where
@@ -684,46 +685,16 @@ pub struct RichTerm {
 }
 
 impl RichTerm {
-    pub fn new(t: Term) -> RichTerm {
-        RichTerm {
-            term: Box::new(t),
-            pos: None,
-        }
-    }
-
     /// Erase recursively the positional information.
     ///
     /// It allows to use rust `Eq` trait to compare the values of the underlying terms.
+    #[cfg(test)]
     pub fn clean_pos(&mut self) {
         self.pos = None;
         self.term
             .apply_to_rich_terms(|rt: &mut Self| rt.clean_pos());
     }
 
-    pub fn app(rt1: RichTerm, rt2: RichTerm) -> RichTerm {
-        Term::App(rt1, rt2).into()
-    }
-
-    pub fn var(s: String) -> RichTerm {
-        Term::Var(Ident(s)).into()
-    }
-
-    pub fn fun(s: String, rt: RichTerm) -> RichTerm {
-        Term::Fun(Ident(s), rt).into()
-    }
-
-    pub fn let_in(id: &str, e: RichTerm, t: RichTerm) -> RichTerm {
-        Term::Let(Ident(id.to_string()), e, t).into()
-    }
-
-    pub fn ite(c: RichTerm, t: RichTerm, e: RichTerm) -> RichTerm {
-        RichTerm::app(RichTerm::app(Term::Op1(UnaryOp::Ite(), c).into(), t), e)
-    }
-
-    pub fn plus(t0: RichTerm, t1: RichTerm) -> RichTerm {
-        Term::Op2(BinaryOp::Plus(), t0, t1).into()
-    }
-
     /// Apply a transformation on a whole term by mapping a function `f` on each node in a
     /// bottom-up manner. `f` may return a generic error `E` and use the state `S` which is
     /// passed around.
@@ -981,6 +952,90 @@ impl AsRef<Term> for RichTerm {
 
 impl From<Term> for RichTerm {
     fn from(t: Term) -> Self {
-        Self::new(t)
+        RichTerm {
+            term: Box::new(t),
+            pos: None,
+        }
+    }
+}
+
+#[macro_use]
+/// Helpers to build `RichTerm` objects.
+pub mod make {
+    use super::*;
+
+    /// Multi-ary application for types implementing `Into<RichTerm>`.
+    #[macro_export]
+    macro_rules! mk_app {
+        ( $f:expr, $arg:expr) => {
+            $crate::term::RichTerm::from($crate::term::Term::App($crate::term::RichTerm::from($f), $crate::term::RichTerm::from($arg)))
+        };
+        ( $f:expr, $fst:expr , $( $args:expr ),+ ) => {
+            mk_app!(mk_app!($f, $fst), $( $args ),+)
+        };
+    }
+
+    /// Multi argument function for types implementing `Into<Ident>` (for the identifiers), and
+    /// `Into<RichTerm>` for the body.
+    #[macro_export]
+    macro_rules! mk_fun {
+        ( $id:expr, $body:expr ) => {
+            $crate::term::RichTerm::from($crate::term::Term::Fun($crate::identifier::Ident::from($id), $crate::term::RichTerm::from($body).into()))
+        };
+        ( $id1:expr, $id2:expr , $( $rest:expr ),+ ) => {
+            mk_fun!($crate::identifier::Ident::from($id1), mk_fun!($id2, $( $rest ),+))
+        };
+    }
+
+    pub fn var<I>(v: I) -> RichTerm
+    where
+        I: Into<Ident>,
+    {
+        Term::Var(v.into()).into()
+    }
+
+    pub fn let_in<I, T1, T2>(id: I, t1: T1, t2: T2) -> RichTerm
+    where
+        T1: Into<RichTerm>,
+        T2: Into<RichTerm>,
+        I: Into<Ident>,
+    {
+        Term::Let(id.into(), t1.into(), t2.into()).into()
+    }
+
+    #[cfg(test)]
+    pub fn if_then_else<T1, T2, T3>(cond: T1, t1: T2, t2: T3) -> RichTerm
+    where
+        T1: Into<RichTerm>,
+        T2: Into<RichTerm>,
+        T3: Into<RichTerm>,
+    {
+        mk_app!(Term::Op1(UnaryOp::Ite(), cond.into()), t1.into(), t2.into())
+    }
+
+    pub fn op1<T>(op: UnaryOp<RichTerm>, t: T) -> RichTerm
+    where
+        T: Into<RichTerm>,
+    {
+        Term::Op1(op, t.into()).into()
+    }
+
+    pub fn op2<T1, T2>(op: BinaryOp<RichTerm>, t1: T1, t2: T2) -> RichTerm
+    where
+        T1: Into<RichTerm>,
+        T2: Into<RichTerm>,
+    {
+        Term::Op2(op, t1.into(), t2.into()).into()
+    }
+
+    pub fn string<S>(s: S) -> RichTerm
+    where
+        S: Into<String>,
+    {
+        Term::Str(s.into()).into()
+    }
+
+    pub fn id() -> RichTerm {
+        mk_fun!("x", var("x"))
     }
 }
diff --git a/src/typecheck.rs b/src/typecheck.rs
index 11ac6cd0..0ecd754b 100644
--- a/src/typecheck.rs
+++ b/src/typecheck.rs
@@ -1640,8 +1640,10 @@ mod tests {
     use super::*;
     use crate::error::ImportError;
     use crate::label::Label;
+    use crate::mk_app;
     use crate::parser::lexer;
     use crate::program::resolvers::{DummyResolver, SimpleResolver};
+    use crate::term::make as mk_term;
     use crate::transformations::transform;
     use codespan::Files;
 
@@ -1667,20 +1669,14 @@ mod tests {
 
         type_check_no_import(&Term::Bool(true).into())?;
         type_check_no_import(&Term::Num(45.).into())?;
-        type_check_no_import(&RichTerm::fun(String::from("x"), RichTerm::var("x".into())).into())?;
-        type_check_no_import(&RichTerm::let_in(
-            "x",
-            Term::Num(3.).into(),
-            RichTerm::var("x".into()),
-        ))?;
-
-        type_check_no_import(&RichTerm::app(
-            Term::Num(5.).into(),
-            Term::Bool(true).into(),
-        ))?;
-        type_check_no_import(&RichTerm::plus(
-            Term::Num(4.).into(),
-            Term::Bool(false).into(),
+        type_check_no_import(&mk_term::id())?;
+        type_check_no_import(&mk_term::let_in("x", Term::Num(3.0), mk_term::var("x")))?;
+
+        type_check_no_import(&mk_app!(Term::Num(5.0), Term::Bool(true)))?;
+        type_check_no_import(&mk_term::op2(
+            BinaryOp::Plus(),
+            Term::Num(4.),
+            Term::Bool(false),
         ))?;
 
         Ok(())
@@ -1688,7 +1684,7 @@ mod tests {
 
     #[test]
     fn unbound_variable_always_throws() {
-        type_check_no_import(&RichTerm::var(String::from("x"))).unwrap_err();
+        type_check_no_import(&mk_term::var("x")).unwrap_err();
     }
 
     #[test]
@@ -2076,11 +2072,7 @@ mod tests {
             R: ImportResolver,
         {
             transform(
-                RichTerm::let_in(
-                    "x",
-                    Term::Import(String::from(import)).into(),
-                    RichTerm::var(String::from("x")),
-                ),
+                mk_term::let_in("x", Term::Import(String::from(import)), mk_term::var("x")),
                 resolver,
             )
         };
diff --git a/src/types.rs b/src/types.rs
index 50f9582a..ccbabd17 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -52,7 +52,9 @@
 //! enriched values `Contract` or `ContractDefault`. They ensure sane interaction between typed and
 //! untyped parts.
 use crate::identifier::Ident;
+use crate::term::make as mk_term;
 use crate::term::{RichTerm, Term, UnaryOp};
+use crate::{mk_app, mk_fun};
 use std::collections::HashMap;
 use std::fmt;
 
@@ -175,18 +177,16 @@ impl Types {
         sy: &mut i32,
     ) -> RichTerm {
         match self.0 {
-            AbsType::Dyn() => RichTerm::var("dyn".to_string()),
-            AbsType::Num() => RichTerm::var("num".to_string()),
-            AbsType::Bool() => RichTerm::var("bool".to_string()),
-            AbsType::Str() => RichTerm::var("string".to_string()),
-            AbsType::List() => RichTerm::var("list".to_string()),
+            AbsType::Dyn() => mk_term::var("dyn"),
+            AbsType::Num() => mk_term::var("num"),
+            AbsType::Bool() => mk_term::var("bool"),
+            AbsType::Str() => mk_term::var("string"),
+            AbsType::List() => mk_term::var("list"),
             AbsType::Sym() => panic!("Are you trying to check a Sym at runtime?"),
-            AbsType::Arrow(ref s, ref t) => RichTerm::app(
-                RichTerm::app(
-                    RichTerm::var("func".to_string()),
-                    s.contract_open(h.clone(), !pol, sy),
-                ),
-                t.contract_open(h, pol, sy),
+            AbsType::Arrow(ref s, ref t) => mk_app!(
+                mk_term::var("func"),
+                s.contract_open(h.clone(), !pol, sy),
+                t.contract_open(h, pol, sy)
             ),
             AbsType::Flat(ref t) => t.clone(),
             AbsType::Var(ref i) => {
@@ -196,31 +196,20 @@ impl Types {
                 rt.clone()
             }
             AbsType::Forall(ref i, ref t) => {
-                let inst_var = RichTerm::app(
-                    RichTerm::app(
-                        RichTerm::var("forall_var".to_string()),
-                        Term::Sym(*sy).into(),
-                    ),
-                    Term::Bool(pol).into(),
-                );
+                let inst_var = mk_app!(mk_term::var("forall_var"), Term::Sym(*sy), Term::Bool(pol));
 
-                let inst_tail = RichTerm::app(
-                    RichTerm::app(
-                        RichTerm::var("forall_tail".to_string()),
-                        Term::Sym(*sy).into(),
-                    ),
-                    Term::Bool(pol).into(),
-                );
+                let inst_tail =
+                    mk_app!(mk_term::var("forall_tail"), Term::Sym(*sy), Term::Bool(pol));
 
                 h.insert(i.clone(), (inst_var, inst_tail));
                 *sy += 1;
                 t.contract_open(h, pol, sy)
             }
-            AbsType::RowEmpty() | AbsType::RowExtend(_, _, _) => RichTerm::var("fail".to_string()),
+            AbsType::RowEmpty() | AbsType::RowExtend(_, _, _) => mk_term::var("fail"),
             AbsType::Enum(ref r) => {
                 fn form(ty: Types, h: HashMap<Ident, (RichTerm, RichTerm)>) -> RichTerm {
                     match ty.0 {
-                        AbsType::RowEmpty() => RichTerm::var("fail".to_string()),
+                        AbsType::RowEmpty() => mk_term::var("fail"),
                         AbsType::RowExtend(_, Some(_), _) => {
                             panic!("It should be a row without type")
                         }
@@ -229,20 +218,16 @@ impl Types {
                             let mut map = HashMap::new();
                             map.insert(id, Term::Bool(true).into());
 
-                            RichTerm::app(
-                                RichTerm::app(
-                                    RichTerm::var("row_extend".to_string()),
-                                    rest_contract,
-                                ),
-                                Term::Fun(
-                                    Ident("x".to_string()),
-                                    Term::Op1(
+                            mk_app!(
+                                mk_term::var("row_extend"),
+                                rest_contract,
+                                mk_fun!(
+                                    "x",
+                                    mk_term::op1(
                                         UnaryOp::Switch(map, Some(Term::Bool(false).into())),
-                                        Term::Var(Ident("x".to_string())).into(),
+                                        mk_term::var("x"),
                                     )
-                                    .into(),
                                 )
-                                .into(),
                             )
                         }
                         AbsType::Var(ref i) => {
@@ -265,7 +250,7 @@ impl Types {
                     h: HashMap<Ident, (RichTerm, RichTerm)>,
                 ) -> RichTerm {
                     match &ty.0 {
-                        AbsType::RowEmpty() => RichTerm::var(String::from("empty_tail")),
+                        AbsType::RowEmpty() => mk_term::var("empty_tail"),
                         AbsType::Var(id) => {
                             let (_, rt) = h
                                 .get(&id)
@@ -275,15 +260,11 @@ impl Types {
                         AbsType::RowExtend(id, Some(ty), rest) => {
                             let cont = form(sy, pol, rest.as_ref(), h.clone());
                             let row_contr = ty.contract_open(h, pol, sy);
-                            RichTerm::app(
-                                RichTerm::app(
-                                    RichTerm::app(
-                                        RichTerm::var(String::from("record_extend")),
-                                        Term::Str(format!("{}", id)).into(),
-                                    ),
-                                    row_contr,
-                                ),
-                                cont,
+                            mk_app!(
+                                mk_term::var("record_extend"),
+                                mk_term::string(format!("{}", id)),
+                                row_contr,
+                                cont
                             )
                         }
                         ty => panic!(
@@ -293,15 +274,11 @@ impl Types {
                     }
                 }
 
-                RichTerm::app(
-                    Term::Var(Ident(String::from("record"))).into(),
-                    form(sy, pol, ty, h),
-                )
+                mk_app!(mk_term::var("record"), form(sy, pol, ty, h))
+            }
+            AbsType::DynRecord(ref ty) => {
+                mk_app!(mk_term::var("dyn_record"), ty.contract_open(h, pol, sy))
             }
-            AbsType::DynRecord(ref ty) => RichTerm::app(
-                RichTerm::var(String::from("dyn_record")),
-                ty.contract_open(h, pol, sy),
-            ),
         }
     }
 
@@ -400,7 +377,6 @@ mod test {
         // Wrap the type in a contract to have it accepted by the parser.
         let wrapper = format!("Contract({})", s);
         let id = Files::new().add("<test>", wrapper.clone());
-        println!("wrapper: {}", wrapper);
 
         let rt = TermParser::new().parse(id, Lexer::new(&wrapper)).unwrap();
 


Test patch:
diff --git a/src/parser/tests.rs b/src/parser/tests.rs
index 68a9e33b..9747a363 100644
--- a/src/parser/tests.rs
+++ b/src/parser/tests.rs
@@ -1,5 +1,7 @@
 use super::lexer::{Lexer, LexicalError, NormalToken, StringToken, Token};
 use crate::identifier::Ident;
+use crate::mk_app;
+use crate::term::make as mk_term;
 use crate::term::Term::*;
 use crate::term::{BinaryOp, RichTerm, StrChunk, UnaryOp};
 use codespan::Files;
@@ -7,7 +9,6 @@ use codespan::Files;
 fn parse(s: &str) -> Option<RichTerm> {
     let id = Files::new().add("<test>", String::from(s));
 
-    println!("Parsing {}", s);
     super::grammar::TermParser::new()
         .parse(id, Lexer::new(&s))
         .map_err(|err| println!("{:?}", err))
@@ -100,36 +101,20 @@ fn booleans() {
 fn ite() {
     assert_eq!(
         parse_without_pos("if true then 3 else 4"),
-        RichTerm::app(
-            RichTerm::app(
-                Op1(UnaryOp::Ite(), Bool(true).into()).into(),
-                Num(3.0).into()
-            ),
-            Num(4.0).into()
-        )
+        mk_app!(mk_term::op1(UnaryOp::Ite(), Bool(true)), Num(3.0), Num(4.0))
     );
 }
 
 #[test]
 fn applications() {
-    println!("1 true 2: {:?}", parse_without_pos("1 true 2"));
-
     assert_eq!(
         parse_without_pos("1 true 2"),
-        RichTerm::app(
-            RichTerm::app(Num(1.0).into(), Bool(true).into()),
-            Num(2.0).into()
-        ),
+        mk_app!(Num(1.0), Bool(true), Num(2.0))
     );
+
     assert_eq!(
         parse_without_pos("1 (2 3) 4"),
-        RichTerm::app(
-            RichTerm::app(
-                Num(1.0).into(),
-                RichTerm::app(Num(2.0).into(), Num(3.0).into())
-            ),
-            Num(4.0).into()
-        ),
+        mk_app!(Num(1.0), mk_app!(Num(2.0), Num(3.0)), Num(4.0))
     );
 }
 
@@ -140,10 +125,7 @@ fn variables() {
 
 #[test]
 fn functions() {
-    assert_eq!(
-        parse_without_pos("fun x => x"),
-        Fun(Ident("x".into()), RichTerm::var("x".into())).into(),
-    );
+    assert_eq!(parse_without_pos("fun x => x"), mk_term::id());
 }
 
 #[test]
@@ -156,39 +138,35 @@ fn lets() {
 fn unary_op() {
     assert_eq!(
         parse_without_pos("isZero x"),
-        Op1(UnaryOp::IsZero(), RichTerm::var("x".to_string())).into()
+        mk_term::op1(UnaryOp::IsZero(), mk_term::var("x"))
     );
     assert_eq!(
         parse_without_pos("isZero x y"),
-        RichTerm::app(
-            Op1(UnaryOp::IsZero(), RichTerm::var("x".to_string())).into(),
-            RichTerm::var("y".to_string())
-        )
+        mk_app!(
+            mk_term::op1(UnaryOp::IsZero(), mk_term::var("x")),
+            mk_term::var("y")
+        ),
     );
 }
 
 #[test]
 fn enum_terms() {
-    assert_eq!(
-        parse_without_pos("`foo"),
-        Enum(Ident("foo".to_string())).into(),
-    );
+    assert_eq!(parse_without_pos("`foo"), Enum(Ident::from("foo")).into(),);
 
     assert_eq!(
         parse_without_pos("switch { foo => true, bar => false, _ => 456, } 123"),
-        Op1(
+        mk_term::op1(
             UnaryOp::Switch(
                 vec![
-                    (Ident("foo".to_string()), Bool(true).into()),
-                    (Ident("bar".to_string()), Bool(false).into())
+                    (Ident::from("foo"), Bool(true).into()),
+                    (Ident::from("bar"), Bool(false).into())
                 ]
                 .into_iter()
                 .collect(),
                 Some(Num(456.).into())
             ),
-            Num(123.).into()
+            Num(123.),
         )
-        .into()
     )
 }
 
@@ -198,9 +176,9 @@ fn record_terms() {
         parse_without_pos("{ a = 1; b = 2; c = 3;}"),
         RecRecord(
             vec![
-                (Ident("a".to_string()), Num(1.).into()),
-                (Ident("b".to_string()), Num(2.).into()),
-                (Ident("c".to_string()), Num(3.).into())
+                (Ident::from("a"), Num(1.).into()),
+                (Ident::from("b"), Num(2.).into()),
+                (Ident::from("c"), Num(3.).into())
             ]
             .into_iter()
             .collect()
@@ -210,26 +188,22 @@ fn record_terms() {
 
     assert_eq!(
         parse_without_pos("{ a = 1; $123 = (if 4 then 5 else 6); d = 42;}"),
-        Op2(
-            BinaryOp::DynExtend(
-                App(
-                    App(Op1(UnaryOp::Ite(), Num(4.).into()).into(), Num(5.).into()).into(),
-                    Num(6.).into()
-                )
-                .into()
-            ),
-            Num(123.).into(),
+        mk_term::op2(
+            BinaryOp::DynExtend(mk_app!(
+                mk_term::op1(UnaryOp::Ite(), Num(4.)),
+                Num(5.),
+                Num(6.)
+            )),
+            Num(123.),
             RecRecord(
                 vec![
-                    (Ident("a".to_string()), Num(1.).into()),
-                    (Ident("d".to_string()), Num(42.).into()),
+                    (Ident::from("a"), Num(1.).into()),
+                    (Ident::from("d"), Num(42.).into()),
                 ]
                 .into_iter()
                 .collect()
             )
-            .into()
         )
-        .into()
     );
 }