Repository: tweag/nickel
Pull request #317 (2021-03-09 10:23:09)

Full annotations
Depend on #315. Required for #254. Allow both type and meta annotations on the same term, as described in #314. If both a type annotation and a contract annotation is provided, the type annotation is used by the typechecker, while both contracts will be enforced at run-time.


Patch:
diff --git a/README.md b/README.md
index f85f2fa6..2232d92f 100644
--- a/README.md
+++ b/README.md
@@ -104,15 +104,10 @@ decisions and trade-offs.
 
 ### Run
 
-1. *(optional)* make a symbolic link to the executable:
+1. (*optional*) Make a symbolic link to the executable:
   ```
   nickel$ ln -S nickel target/debug/nickel
   ```
-  Alternatively, you can use `cargo run` to launch nickel. To pass arguments to
-  nickel using `cargo run`, use `--`:
-  ```
-  nickel$ cargo run -- -f program.ncl
-  ```
 
 2. Run your first program:
   ```
@@ -123,32 +118,12 @@ decisions and trade-offs.
   Or load it from a file:
   ```
   nickel$ echo 'let s = "world" in "Hello, " ++ s' > program.ncl
-  nickel$ ./nickel -f program.ncl
+  nickel$ ./nickel < program.ncl
   Typechecked: Ok(Types(Dyn))
   Done: Str("Hello, world")
   ```
 
-3. Start an REPL:
-  ```
-  nickel$ ./nickel repl
-  nickel> let x = 2 in x + x
-  nickel> x
-  4
-
-  nickel>
-  ```
-  Use `:help` for a list of available commands.
-
-4. Export your configuration to JSON, YAML or TOML:
-  ```
-  nickel$ ./nickel export --format json <<< '{foo = "Hello, world!"}'
-  {
-    "foo": "Hello, world!"
-  }
-  ```
-
-Use `nickel help` for a list of subcommands, and `nickel help <subcommand>`
-for help about a specific subcommand.
+By default, Nickel reads from the standard input. It may change in the future.
 
 ### Tests
 ```
@@ -175,11 +150,12 @@ and others important practical aspects are still being debated. We aim to
 transition from an experimental stage to a minimum viable product stage.  The
 next points to deal with are:
 
-- [Stdlib stabilization](https://github.com/tweag/nickel/issues/321)
-- [Overriding](https://github.com/tweag/nickel/pull/330)
-- Memory management (use reference counting) & basic performance improvements
+- Imports
 - [List comprehensions](https://github.com/tweag/nickel/issues/80)
 - [Destructuring](https://github.com/tweag/nickel/issues/81)
+- [String interpolation](https://github.com/tweag/nickel/issues/82)
+- [Recursive records](https://github.com/tweag/nickel/issues/83)
+- Syntax
 
 ## Related projects and inspirations
 - [Cue](https://cuelang.org/) is a configuration language with a focus on data
diff --git a/src/eval.rs b/src/eval.rs
index 249373f5..4862db7d 100644
--- a/src/eval.rs
+++ b/src/eval.rs
@@ -1098,7 +1098,7 @@ mod tests {
     fn imports() {
         let mut resolver = SimpleResolver::new();
         resolver.add_source(String::from("two"), String::from("1 + 1"));
-        resolver.add_source(String::from("lib"), String::from("{f = true}"));
+        resolver.add_source(String::from("lib"), String::from("{ f = true }"));
         resolver.add_source(String::from("bad"), String::from("^$*/.23ab 0°@"));
         resolver.add_source(
             String::from("nested"),
@@ -1106,7 +1106,7 @@ mod tests {
         );
         resolver.add_source(
             String::from("cycle"),
-            String::from("let x = import \"cycle_b\" in {a = 1, b = x.a}"),
+            String::from("let x = import \"cycle_b\" in {a = 1; b = x.a}"),
         );
         resolver.add_source(
             String::from("cycle_b"),
@@ -1314,11 +1314,11 @@ mod tests {
                 .unwrap()
         );
 
-        let t = parse("switch {x => [1, glob1], y => loc2, z => {id = true, other = glob3}} loc1")
+        let t = parse("switch {x => [1, glob1], y => loc2, z => {id = true; other = glob3},} loc1")
             .unwrap();
         assert_eq!(
             subst(t, &global_env, &env),
-            parse("switch {x => [1, 1], y => (if false then 1 else \"Glob2\"), z => {id = true, other = false}} true").unwrap()
+            parse("switch {x => [1, 1], y => (if false then 1 else \"Glob2\"), z => {id = true; other = false},} true").unwrap()
         );
     }
 }
diff --git a/src/grammar.lalrpop b/src/grammar.lalrpop
index 45a65650..1eac68ef 100644
--- a/src/grammar.lalrpop
+++ b/src/grammar.lalrpop
@@ -13,11 +13,17 @@ use super::lexer::{Token, NormalToken, StringToken, MultiStringToken, LexicalErr
 use std::collections::HashMap;
 use codespan::FileId;
 
-grammar<'input>(src_id: FileId, );
+grammar<'input>(src_id: FileId);
 
 SpTerm<Rule>: RichTerm = <l: @L> <t: Rule> <r: @R> => t.with_pos(mk_pos(src_id, l, r));
 
-TypeAnnot: (usize, Types, usize) = ":" <l: @L> <ty: Types> <r: @R> => (l, ty, r);
+TypeAnnot: MetaValue = ":" <l: @L> <ty: Types> <r: @R> => MetaValue {
+    doc: None,
+    types: Some(Contract {types: ty.clone(), label: mk_label(ty, src_id, l, r)}),
+    contracts: Vec::new(),
+    priority: Default::default(),
+    value: None,
+};
 
 MetaAnnotAtom: MetaValue = {
     "|" <l: @L> <ty: Types> <r: @R> => MetaValue {
@@ -34,7 +40,7 @@ MetaAnnotAtom: MetaValue = {
         priority: MergePriority::Default,
         value: None,
     },
-    "|" "doc" <s: StaticString> => MetaValue {
+    "|" "doc" <s: Str> => MetaValue {
         doc: Some(strip_indent_doc(s)),
         types: None,
         contracts: Vec::new(),
@@ -45,6 +51,18 @@ MetaAnnotAtom: MetaValue = {
 
 MetaAnnot: MetaValue = <anns: MetaAnnotAtom+> => anns.into_iter().fold(MetaValue::new(), MetaValue::flatten);
 
+Annot: MetaValue = {
+    <TypeAnnot>,
+    <ty_ann: TypeAnnot?> <meta: MetaAnnot> => {
+        if let Some(ty_meta) = ty_ann {
+            MetaValue::flatten(ty_meta, meta)
+        }
+        else {
+            meta
+        }
+    }
+};
+
 LeftOp<Op, Current, Previous>: RichTerm =
     <t1: Current> <op: Op> <t2: Previous> => mk_term::op2(op, t1, t2);
 
@@ -52,65 +70,50 @@ LeftOpLazy<Op, Current, Previous>: RichTerm =
     <t1: Current> <op: Op> <t2: Previous> => mk_app!(Term::Op1(op, t1), t2);
 
 pub Term: RichTerm = {
-    SpTerm<RichTerm>,
-    SpTerm<TypedTerm>,
-    SpTerm<MetaTerm>,
+    <t: SpTerm<RichTerm>> <meta: Annot?> => {
+        let pos = t.pos;
+
+        if let Some(mut meta) = meta {
+            meta.value = Some(t);
+            RichTerm::new(Term::MetaValue(meta), pos)
+        }
+        else {
+            t
+        }
+    }
 };
 
 pub ExtendedTerm: ExtendedTerm = {
-    "let" <id:Ident> <ann: TypeAnnot?> "=" <t1: Term> => {
-        let t1 = if let Some((l, ty, r)) = ann {
-            let pos = t1.pos.clone();
-            RichTerm::new(Term::Promise(ty.clone(), mk_label(ty, src_id, l, r), t1), pos)
+    "let" <id:Ident> <meta: Annot?> "=" <t: Term> => {
+        let pos = t.pos;
+
+        let t = if let Some(mut meta) = meta {
+            meta.value = Some(t);
+            RichTerm::new(Term::MetaValue(meta), pos)
         }
         else {
-            t1
+            t
         };
 
-        ExtendedTerm::ToplevelLet(id, t1)
-    },
-    "let" <id:Ident> <meta: MetaAnnot> "=" <t1: Term> => {
-        //TODO: bump LALRPOP version to >= 0.18.0 which allows mutable x in
-        //actions and remove this
-        let mut meta = meta;
-        let pos = t1.pos.clone();
-        meta.value = Some(t1);
-
-        ExtendedTerm::ToplevelLet(id, RichTerm::new(Term::MetaValue(meta), pos))
+        ExtendedTerm::ToplevelLet(id, t)
     },
     Term => ExtendedTerm::RichTerm(<>),
 }
 
-TypedTerm: RichTerm = {
-    <t: SpTerm<RichTerm>> <ann: TypeAnnot> => {
-        let (l, ty, r) = ann;
-        RichTerm::from(Term::Promise(ty.clone(), mk_label(ty, src_id, l, r), t))
-    }
-};
-
-MetaTerm: RichTerm = {
-    <t: SpTerm<RichTerm>> <meta: MetaAnnot> => {
-        let pos = t.pos.clone();
-        //TODO: bump LALRPOP version to >= 0.18.0 which allows mutable x in
-        //actions and remove this
-        let mut meta = meta;
-        meta.value = Some(t);
-        RichTerm::new(Term::MetaValue(meta), pos)
-    }
-};
-
 RichTerm: RichTerm = {
     <l: @L> "fun" <ps:Pattern+> "=>" <t: SpTerm<RichTerm>> <r: @R> => {
         let pos = mk_pos(src_id, l, r);
         ps.into_iter().rev().fold(t, |t, p| RichTerm {
             term: Box::new(Term::Fun(p, t)),
-            pos: pos.clone()
+            pos,
         })
     },
-    "let" <id:Ident> <ann: TypeAnnot?> "=" <t1: Term> "in" <t2:SpTerm<RichTerm>> => {
-        let t1 = if let Some((l, ty, r)) = ann {
-            let pos = t1.pos.clone();
-            RichTerm::new(Term::Promise(ty.clone(), mk_label(ty, src_id, l, r), t1), pos)
+    "let" <id:Ident> <meta: Annot?> "=" <t1: Term> "in" <t2:SpTerm<RichTerm>> => {
+        let pos = t1.pos;
+
+        let t1 = if let Some(mut meta) = meta {
+            meta.value = Some(t1);
+            RichTerm::new(Term::MetaValue(meta), pos)
         }
         else {
             t1
@@ -118,13 +121,6 @@ RichTerm: RichTerm = {
 
         mk_term::let_in(id, t1, t2)
     },
-    "let" <id:Ident> <meta: MetaAnnot> "=" <t1: Term> "in" <t2:SpTerm<RichTerm>> => {
-        //TODO: bump LALRPOP version to >= 0.18.0 which allows mutable x in
-        //actions and remove this
-        let mut meta = meta;
-        meta.value = Some(t1);
-        mk_term::let_in(id, Term::MetaValue(meta), t2)
-    },
     "switch" "{" <cases: (switch_case ",")*> <last: switch_case?> "}" <exp: SpTerm<RichTerm>> => {
         let mut acc = HashMap::with_capacity(cases.len());
         let mut default = None;
@@ -148,7 +144,7 @@ RichTerm: RichTerm = {
     },
     "if" <b:Term> "then" <t:Term> "else" <e:SpTerm<RichTerm>> =>
         mk_app!(Term::Op1(UnaryOp::Ite(), b), t, e),
-    "import" <s: StaticString> => RichTerm::from(Term::Import(OsString::from(s))),
+    "import" <s: Str> => RichTerm::from(Term::Import(OsString::from(s))),
     SpTerm<InfixExpr>,
 };
 
@@ -180,7 +176,7 @@ Atom: RichTerm = {
     <StrChunks>,
     Ident => RichTerm::from(Term::Var(<>)),
     "`" <Ident> => RichTerm::from(Term::Enum(<>)),
-    "{" <fields: (<RecordField> ",")*> <last: RecordField?> "}" => {
+    "{" <fields: (<RecordField> ";")*> <last: RecordField?> "}" => {
         let fields = fields.into_iter().chain(last.into_iter());
         RichTerm::from(build_record(fields))
     },
@@ -192,10 +188,11 @@ Atom: RichTerm = {
 };
 
 RecordField: (FieldPathElem, RichTerm) = {
-    <path: FieldPath> <ann: TypeAnnot?> "=" <t: Term> => {
-        let t = if let Some((l, ty, r)) = ann {
-            let pos = t.pos.clone();
-            RichTerm::new(Term::Promise(ty.clone(), mk_label(ty, src_id, l, r), t), pos)
+    <path: FieldPath> <ty_ann: TypeAnnot?> "=" <t: Term> => {
+        let t = if let Some(mut meta) = ty_ann {
+            let pos = t.pos;
+            meta.value = Some(t);
+            RichTerm::new(Term::MetaValue(meta), pos)
         }
         else {
             t
@@ -203,6 +200,14 @@ RecordField: (FieldPathElem, RichTerm) = {
 
         elaborate_field_path(path, t)
     },
+    <path: FieldPath> <ty_ann: TypeAnnot> <meta: MetaAnnot> "=" <t: Term> => {
+        let pos = t.pos;
+        let mut meta = MetaValue::flatten(ty_ann, meta);
+        meta.value = Some(t);
+        let t = RichTerm::new(Term::MetaValue(meta), pos);
+
+        elaborate_field_path(path, t)
+    },
     <l: @L> <path: FieldPath> <meta: MetaAnnot> <r: @R> <t: ("=" <Term>)?> => {
         let mut meta = meta;
         let pos = t.as_ref()
@@ -241,7 +246,7 @@ Bool: bool = {
 StrChunks : RichTerm = {
   <start: StringStart> <fst: ChunkLiteral?> <chunks: (ChunkExpr+ChunkLiteral)*> <lasts:
     ChunkExpr*> <end: StringEnd> => {
-        debug_assert_eq!(start, end);
+        assert_eq!(start, end);
 
         let chunks: Vec<StrChunk<RichTerm>> = fst.into_iter()
             .map(StrChunk::Literal)
@@ -292,7 +297,7 @@ ChunkExpr: StrChunk<RichTerm> = HashBrace <t: SpTerm<RichTerm>> "}" => StrChunk:
 
 HashBrace = { "#{", "multstr #{" };
 
-StaticString: String = StringStart <s: ChunkLiteral> StringEnd => s;
+Str: String = "\"" <s: ChunkLiteral> "\"" => s;
 
 ChunkLiteralPart: ChunkLiteralPart<'input> = {
     "str literal" => ChunkLiteralPart::Str(<>),
@@ -446,7 +451,6 @@ BOpPre: BinaryOp = {
     "hash" => BinaryOp::Hash(),
     "serialize" => BinaryOp::Serialize(),
     "deserialize" => BinaryOp::Deserialize(),
-    "pow" => BinaryOp::Pow(),
 }
 
 Types: Types = {
@@ -556,6 +560,7 @@ extern {
         "$" => Token::Normal(NormalToken::Dollar),
         "=" => Token::Normal(NormalToken::Equals),
         "!=" => Token::Normal(NormalToken::NotEquals),
+        ";" => Token::Normal(NormalToken::SemiCol),
         "&" => Token::Normal(NormalToken::Ampersand),
         "." => Token::Normal(NormalToken::Dot),
         "$[" => Token::Normal(NormalToken::DollarBracket),
@@ -617,7 +622,6 @@ extern {
         "tail" => Token::Normal(NormalToken::Tail),
         "length" => Token::Normal(NormalToken::Length),
         "fieldsOf" => Token::Normal(NormalToken::FieldsOf),
-        "pow" => Token::Normal(NormalToken::Pow),
 
         "hasField" => Token::Normal(NormalToken::HasField),
         "map" => Token::Normal(NormalToken::Map),
diff --git a/src/operation.rs b/src/operation.rs
index 14b23b22..afefdce7 100644
--- a/src/operation.rs
+++ b/src/operation.rs
@@ -848,36 +848,6 @@ fn process_binary_operation(
                 ))
             }
         }
-        BinaryOp::Pow() => {
-            if let Term::Num(n1) = *t1 {
-                if let Term::Num(n2) = *t2 {
-                    Ok(Closure::atomic_closure(RichTerm::new(
-                        Term::Num(n1.powf(n2)),
-                        pos_op_inh,
-                    )))
-                } else {
-                    Err(EvalError::TypeError(
-                        String::from("Num"),
-                        String::from("pow, 2nd argument"),
-                        snd_pos,
-                        RichTerm {
-                            term: t2,
-                            pos: pos2,
-                        },
-                    ))
-                }
-            } else {
-                Err(EvalError::TypeError(
-                    String::from("Num"),
-                    String::from("pow, 1st argument"),
-                    fst_pos,
-                    RichTerm {
-                        term: t1,
-                        pos: pos1,
-                    },
-                ))
-            }
-        }
         BinaryOp::PlusStr() => {
             if let Term::Str(s1) = *t1 {
                 if let Term::Str(s2) = *t2 {
diff --git a/src/parser/lexer.rs b/src/parser/lexer.rs
index e08ca86b..647823b6 100644
--- a/src/parser/lexer.rs
+++ b/src/parser/lexer.rs
@@ -86,6 +86,8 @@ pub enum NormalToken<'input> {
     Equals,
     #[token("!=")]
     NotEquals,
+    #[token(";")]
+    SemiCol,
     #[token("&")]
     Ampersand,
     #[token(".")]
@@ -189,8 +191,6 @@ pub enum NormalToken<'input> {
     Length,
     #[token("%fieldsOf%")]
     FieldsOf,
-    #[token("%pow%")]
-    Pow,
 
     #[token("%hasField%")]
     HasField,
diff --git a/src/program.rs b/src/program.rs
index 7103646d..a93a0bfa 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -264,7 +264,8 @@ mod tests {
         assert_eq!(t, *expd.term);
 
         let t = clean_pos(
-            eval_full("let x = 1 in let y = 1 + x in let z = {foo.bar.baz = y} in z").unwrap(),
+            eval_full("let x = 1 in let y = 1 + x in let z = { foo = {bar = { baz  = y } } } in z")
+                .unwrap(),
         );
         // Records are parsed as RecRecords, so we need to build one by hand
         let expd = mk_record!((
@@ -277,6 +278,6 @@ mod tests {
         // Check that substitution do not replace bound variables. Before the fixing commit, this
         // example would go into an infinite loop, and stack overflow. If it does, this just means
         // that this test fails.
-        eval_full("{y = fun x => x, x = fun y => y}").unwrap();
+        eval_full("{y = fun x => x; x = fun y => y}").unwrap();
     }
 }
diff --git a/src/serialize.rs b/src/serialize.rs
index 54f09c16..6c004b1d 100644
--- a/src/serialize.rs
+++ b/src/serialize.rs
@@ -334,7 +334,7 @@ mod tests {
     #[test]
     fn records() {
         assert_json_eq!(
-            "{a = 1, b = 2+2, c = 3, d = null}",
+            "{a = 1; b = 2+2; c = 3; d = null}",
             json!({"a": 1, "b": 4, "c": 3, "d": null})
         );
 
@@ -344,15 +344,15 @@ mod tests {
         );
 
         assert_json_eq!(
-            "{foo = let z = 0.5 + 0.5 in z, bar = [\"str\", true || false], baz = {subfoo = !false} & {subbar = 1 - 1}}",
+            "{foo = let z = 0.5 + 0.5 in z; bar = [\"str\", true || false]; baz = {subfoo = !false} & {subbar = 1 - 1}}",
             json!({"foo": 1, "bar": ["str", true], "baz": {"subfoo": true, "subbar": 0}})
         );
     }
 
     #[test]
-    fn meta_values() {
+    fn enriched_values() {
         assert_json_eq!(
-            "{a | default = 1, b | doc \"doc\" = 2+2, c = 3}",
+            "{a | default = 1; b | doc \"doc\" = 2+2; c = 3}",
             json!({"a": 1, "b": 4, "c": 3})
         );
 
@@ -369,10 +369,10 @@ mod tests {
 
     #[test]
     fn prevalidation() {
-        assert_pass_validation!(ExportFormat::Json, "{a = 1, b = {c = fun x => x}}", false);
+        assert_pass_validation!(ExportFormat::Json, "{a = 1; b = { c = fun x => x }}", false);
         assert_pass_validation!(
             ExportFormat::Json,
-            "{foo.bar = let y = \"a\" in y, b = [[fun x => x]]}",
+            "{foo = { bar = let y = \"a\" in y}; b = [[fun x => x]] }",
             false
         );
         assert_pass_validation!(ExportFormat::Json, "{foo = null}", true);
@@ -384,6 +384,8 @@ mod tests {
         assert_involutory!("{val = 1 + 1}");
         assert_involutory!("{val = \"Some string\"}");
         assert_involutory!("{val = [\"a\", 3, []]}");
-        assert_involutory!("{a.foo.bar = \"2\", b = false, c = [{d = \"e\"}, {d = \"f\"}]}");
+        assert_involutory!(
+            "{a = {foo = {bar = \"2\"}}; b = false; c = [{d = \"e\"}, {d = \"f\"}]}"
+        );
     }
 }
diff --git a/src/stdlib.rs b/src/stdlib.rs
index 89e5d7fb..4ce0f371 100644
--- a/src/stdlib.rs
+++ b/src/stdlib.rs
@@ -13,11 +13,10 @@ pub const CONTRACTS: (&str, &str) = (
 );
 pub const LISTS: (&str, &str) = ("<stdlib/lists>", include_str!("../stdlib/lists.ncl"));
 pub const RECORDS: (&str, &str) = ("<stdlib/records>", include_str!("../stdlib/records.ncl"));
-pub const NUMS: (&str, &str) = ("<stdlib/nums>", include_str!("../stdlib/nums.ncl"));
 
 /// Return the list `(name, source_code)` of all the stdlib modules.
 pub fn modules() -> Vec<(&'static str, &'static str)> {
-    vec![BUILTINS, CONTRACTS, LISTS, RECORDS, NUMS]
+    vec![BUILTINS, CONTRACTS, LISTS, RECORDS]
 }
 
 /// Accessors to the builtin contracts.
diff --git a/src/term.rs b/src/term.rs
index 26a1982d..3616533b 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -616,8 +616,6 @@ pub enum BinaryOp {
     Div(),
     /// Modulo of numerals.
     Modulo(),
-    /// Raise a number to a power.
-    Pow(),
     /// Concatenation of strings.
     PlusStr(),
     /// Polymorphic equality.
diff --git a/src/transformations.rs b/src/transformations.rs
index 10133b2b..6bbba9e6 100644
--- a/src/transformations.rs
+++ b/src/transformations.rs
@@ -226,9 +226,8 @@ pub mod apply_contracts {
     use super::{RichTerm, Term};
     use crate::mk_app;
 
-    /// If the top-level node of the AST is a meta-value, wrap the inner value inside generated
-    /// `Assume`s corresponding to the meta-value's contracts. Otherwise, return the term
-    /// unchanged.
+    /// If the top-level node of the AST is a meta-value, apply the meta-value's contracts to the
+    /// inner value.  Otherwise, return the term unchanged.
     pub fn transform_one(rt: RichTerm) -> RichTerm {
         let RichTerm { term, pos } = rt;
 
diff --git a/src/typecheck.rs b/src/typecheck.rs
index 69469d55..0504d6a7 100644
--- a/src/typecheck.rs
+++ b/src/typecheck.rs
@@ -682,10 +682,9 @@ fn type_check_(
         }) => {
             let tyw2 = to_typewrapper(ty2.clone());
 
-            let instantiated = instantiate_foralls(state, tyw2, ForallInst::Constant);
+            let instantiated = instantiate_foralls(state, tyw2.clone(), ForallInst::Constant);
 
-            unify(state, strict, ty, to_typewrapper(ty2.clone()))
-                .map_err(|err| err.into_typecheck_err(state, rt.pos))?;
+            unify(state, strict, ty, tyw2).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
             type_check_(state, envs, true, t, instantiated)
         }
         // A metavalue with at least one contract is an assume. If there's several
@@ -1722,11 +1721,6 @@ pub fn get_bop_type(state: &mut State, op: &BinaryOp) -> Result<TypeWrapper, Typ
             mk_typewrapper::str(),
             mk_typewrapper::dynamic()
         ),
-        BinaryOp::Pow() => mk_tyw_arrow!(
-            mk_typewrapper::num(),
-            mk_typewrapper::num(),
-            mk_typewrapper::num()
-        ),
     })
 }
 
diff --git a/stdlib/builtins.ncl b/stdlib/builtins.ncl
index 27615dc1..a7a1188c 100644
--- a/stdlib/builtins.ncl
+++ b/stdlib/builtins.ncl
@@ -1,11 +1,11 @@
 {
   builtins = {
-    isNum : Dyn -> Bool = fun x => %isNum% x,
-    isBool : Dyn -> Bool = fun x => %isBool% x,
-    isStr : Dyn -> Bool = fun x => %isStr% x,
-    isFun : Dyn -> Bool = fun x => %isFun% x,
-    isList : Dyn -> Bool = fun x => %isList% x,
-    isRecord : Dyn -> Bool = fun x => %isRecord% x,
+    isNum : Dyn -> Bool = fun x => %isNum% x;
+    isBool : Dyn -> Bool = fun x => %isBool% x;
+    isStr : Dyn -> Bool = fun x => %isStr% x;
+    isFun : Dyn -> Bool = fun x => %isFun% x;
+    isList : Dyn -> Bool = fun x => %isList% x;
+    isRecord : Dyn -> Bool = fun x => %isRecord% x;
     typeOf : Dyn -> <
       TypeNum,
       TypeBool,
@@ -20,20 +20,20 @@
       else if %isFun% x then `TypeFun
       else if %isList% x then `TypeList
       else if %isRecord% x then `TypeRecord
-      else `Other,
+      else `Other;
 
-    seq : forall a. Dyn -> a -> a = fun x y => %seq% x y,
-    deepSeq : forall a. Dyn -> a -> a = fun x y => %deepSeq% x y,
+    seq : forall a. Dyn -> a -> a = fun x y => %seq% x y;
+    deepSeq : forall a. Dyn -> a -> a = fun x y => %deepSeq% x y;
 
-    id : forall a. a -> a = fun x => x,
+    id : forall a. a -> a = fun x => x;
 
     hash : <Md5, Sha1, Sha256, Sha512> -> Str -> Str =
-      fun type s => %hash% type s,
+      fun type s => %hash% type s;
 
     serialize : <Json, Toml, Yaml> -> Dyn -> Str = fun format x =>
-      %serialize% format (%deepSeq% x x),
+      %serialize% format (%deepSeq% x x);
 
     deserialize : <Json, Toml, Yaml> -> Str -> Dyn = fun format x =>
-      %deserialize% format x,
+      %deserialize% format x;
   }
 }
diff --git a/stdlib/contracts.ncl b/stdlib/contracts.ncl
index 1a952575..68ec43e5 100644
--- a/stdlib/contracts.ncl
+++ b/stdlib/contracts.ncl
@@ -1,46 +1,46 @@
 {
-  dyn = fun l t => t,
+  dyn = fun l t => t;
 
-  num = fun l t => if %isNum% t then t else %blame% l,
+  num = fun l t => if %isNum% t then t else %blame% l;
 
-  bool = fun l t => if %isBool% t then t else %blame% l,
+  bool = fun l t => if %isBool% t then t else %blame% l;
 
-  string = fun l t => if %isStr% t then t else %blame% l,
+  string = fun l t => if %isStr% t then t else %blame% l;
 
-  list = fun elt l t => if %isList% t then %map% t (elt (%goList% l)) else %blame% l,
+  list = fun elt l t => if %isList% t then %map% t (elt (%goList% l)) else %blame% l;
 
   func = fun s t l e =>
       if %isFun% e then
           (fun x => t (%goCodom% l) (e (s (%chngPol% (%goDom% l)) x)))
       else
-          %blame% l,
+          %blame% l;
 
   forall_var = fun sy pol l t =>
       let lPol = %polarity% l in
       if pol == lPol then
           %unwrap% sy t (%blame% l)
       else
-          %wrap% sy t,
+          %wrap% sy t;
 
-  fail = fun l t => %blame% (%tag% "Fail" l),
+  fail = fun l t => %blame% (%tag% "Fail" l);
 
   row_extend = fun contr case l t =>
       if (case t) then
           t
       else
-          contr (%tag% "NotRowExt" l) t,
+          contr (%tag% "NotRowExt" l) t;
 
   record = fun cont l t =>
       if %isRecord% t then
           cont {} l t
       else
-          %blame% (%tag% "not a record" l),
+          %blame% (%tag% "not a record" l);
 
   dyn_record = fun contr l t =>
       if %isRecord% t then
           %recordMap% t (fun _field => contr l)
       else
-          %blame% (%tag% "not a record" l),
+          %blame% (%tag% "not a record" l);
 
   record_extend = fun field contr cont acc l t =>
       if %hasField% field t then
@@ -48,7 +48,7 @@
           let t = t -$ field in
           cont acc l t
       else
-          %blame% (%tag% "missing field `#{field}`" l),
+          %blame% (%tag% "missing field `#{field}`" l);
 
   forall_tail = fun sy pol acc l t =>
       let magic_fld = "_%wrapped" in
@@ -64,16 +64,16 @@
           else
               %blame% (%tag% "missing polymorphic part" l)
       else
-          acc$[magic_fld = %wrap% sy t],
+          acc$[magic_fld = %wrap% sy t];
 
-  dyn_tail = fun acc l t => acc & t,
+  dyn_tail = fun acc l t => acc & t;
 
   empty_tail = fun acc l t =>
       if t == {} then acc
-      else %blame% (%tag% "extra field `#{%head% (%fieldsOf% t)}`" l),
+      else %blame% (%tag% "extra field `#{%head% (%fieldsOf% t)}`" l);
 
   contracts = {
-    blame = fun l => %blame% l,
-    tag = fun msg l => %tag% msg l,
-  },
+    blame = fun l => %blame% l;
+    tag = fun msg l => %tag% msg l;
+  };
 }
diff --git a/stdlib/lists.ncl b/stdlib/lists.ncl
index d3ec025a..0ad2c076 100644
--- a/stdlib/lists.ncl
+++ b/stdlib/lists.ncl
@@ -1,16 +1,16 @@
 {
   lists = {
-    head : forall a. List a -> a = fun l => %head% l,
+    head : forall a. List a -> a = fun l => %head% l;
 
-    tail : forall a. List a -> List a = fun l => %tail% l,
+    tail : forall a. List a -> List a = fun l => %tail% l;
 
-    length : forall a. List a -> Num = fun l => %length% l,
+    length : forall a. List a -> Num = fun l => %length% l;
 
-    map : forall a b. (a -> b) -> List a -> List b = fun f l => %map% l f,
+    map : forall a b. (a -> b) -> List a -> List b = fun f l => %map% l f;
 
-    elemAt : forall a. List a -> Num -> a = fun l n => %elemAt% l n,
+    elemAt : forall a. List a -> Num -> a = fun l n => %elemAt% l n;
 
-    concat : forall a. List a -> List a -> List a = fun l1 l2 => l1 @ l2,
+    concat : forall a. List a -> List a -> List a = fun l1 l2 => l1 @ l2;
 
     foldl : forall a b. (a -> b -> a) -> a -> List b -> a =
       fun f fst l =>
@@ -18,33 +18,33 @@
           fst
         else
           let rest = foldl f fst (%tail% l) in
-          %seq% rest (f rest (%head% l)),
+          %seq% rest (f rest (%head% l));
 
     fold : forall a b. (a -> b -> b) -> List a -> b -> b =
       fun f l fst =>
         if %length% l == 0 then
           fst
         else
-          f (%head% l) (fold f (%tail% l) fst),
+          f (%head% l) (fold f (%tail% l) fst);
 
-    cons : forall a. a -> List a -> List a = fun x l => [x] @ l,
+    cons : forall a. a -> List a -> List a = fun x l => [x] @ l;
 
     filter : forall a. (a -> Bool) -> List a -> List a =
       fun pred l =>
-        fold (fun x acc => if pred x then acc @ [x] else acc) l [],
+        fold (fun x acc => if pred x then acc @ [x] else acc) l [];
 
     //TODO: this enforces a List contract at each iteration. Get rid of it once
     //parametrized list types lands and `fold` get a more precise type.
     flatten : forall a. List (List a) -> List a =
       fun l =>
-        fold (fun l acc => l @ acc) l [],
+        fold (fun l acc => l @ acc) l [];
 
     all : forall a. (a -> Bool) -> List a -> Bool =
       fun pred l =>
-        fold (fun x acc => if pred x then acc else false) l true,
+        fold (fun x acc => if pred x then acc else false) l true;
 
     any : forall a. (a -> Bool) -> List a -> Bool =
       fun pred l =>
-        fold (fun x acc => if pred x then true else acc) l false,
+        fold (fun x acc => if pred x then true else acc) l false;
   }
 }
diff --git a/stdlib/nums.ncl b/stdlib/nums.ncl
deleted file mode 100644
index d8f12f9c..00000000
--- a/stdlib/nums.ncl
+++ /dev/null
@@ -1,64 +0,0 @@
-{
-  nums = {
-    Int = fun label value =>
-      if %isNum% value then
-        if value % 1 == 0 then
-          value
-        else
-          %blame% (%tag% "not an integer" label)
-      else
-        %blame% (%tag% "not a number" label),
-
-    Nat = fun label value =>
-      if %isNum% value then
-        if value % 1 == 0 && value >= 0 then
-          value
-        else
-          %blame% (%tag% "not a natural" label)
-      else
-        %blame% (%tag% "not a number" label),
-
-    PosNat = fun label value =>
-      if %isNum% value then
-        if value % 1 == 0 && value > 0 then
-          value
-        else
-          %blame% (%tag% "not positive integer" label)
-      else
-        %blame% (%tag% "not a number" label),
-
-    NonZero = fun label value =>
-      if %isNum% value then
-        if value != 0 then
-          value
-        else
-          %blame% (%tag% "non-zero" label)
-      else
-        %blame% (%tag% "not a number" label),
-
-    isInt : Num -> Bool = fun x =>
-      %isNum% x && (x % 1 == 0),
-
-    min : Num -> Num -> Num = fun x y =>
-      if x <= y then x else y,
-
-    max : Num -> Num -> Num = fun x y =>
-      if x >= y then x else y,
-
-    floor : Num -> Num = fun x =>
-      if x >= 0 then x - (x % 1)
-      else x - 1 - (x % 1),
-
-    abs : Num -> Num = fun x =>
-      if x < 0 then -x else x,
-
-    fract : Num -> Num = fun x =>
-      x % 1,
-
-    trunc : Num -> Num = fun x =>
-      x - (x % 1),
-
-    pow : Num -> Num -> Num = fun x n =>
-      %pow% x n,
-  }
-}
diff --git a/stdlib/records.ncl b/stdlib/records.ncl
index 42b850c8..8349657c 100644
--- a/stdlib/records.ncl
+++ b/stdlib/records.ncl
@@ -1,10 +1,10 @@
 {
   records = {
-    map : forall a b. (Str -> a -> b) -> {_: a} -> {_: b} = fun f r => %recordMap% r f,
+    map : forall a b. (Str -> a -> b) -> {_: a} -> {_: b} = fun f r => %recordMap% r f;
 
     // TODO: change Dyn to { | Dyn} once the PR introducing open contracts lands
-    fieldsOf : Dyn -> List Str = fun r => %fieldsOf% r,
+    fieldsOf : Dyn -> List Str = fun r => %fieldsOf% r;
 
-    hasField : Str -> Dyn -> Bool = fun r field => %hasField% r field,
+    hasField : Str -> Dyn -> Bool = fun r field => %hasField% r field;
   }
 }


Test patch:
diff --git a/src/parser/tests.rs b/src/parser/tests.rs
index d9c0e39d..c4f8695e 100644
--- a/src/parser/tests.rs
+++ b/src/parser/tests.rs
@@ -163,7 +163,7 @@ fn enum_terms() {
 #[test]
 fn record_terms() {
     assert_eq!(
-        parse_without_pos("{ a = 1, b = 2, c = 3}"),
+        parse_without_pos("{ a = 1; b = 2; c = 3;}"),
         RecRecord(
             vec![
                 (Ident::from("a"), Num(1.).into()),
@@ -177,7 +177,7 @@ fn record_terms() {
     );
 
     assert_eq!(
-        parse_without_pos("{ a = 1, \"#{123}\" = (if 4 then 5 else 6), d = 42}"),
+        parse_without_pos("{ a = 1; \"#{123}\" = (if 4 then 5 else 6); d = 42;}"),
         mk_app!(
             mk_term::op2(
                 BinaryOp::DynExtend(),
@@ -336,7 +336,7 @@ fn line_comments() {
     assert_eq!(
         parse_without_pos(
             "{ // Some comment
-            field = foo, // Some description
+            field = foo; // Some description
             } // Some other"
         ),
         parse_without_pos("{field = foo}")
diff --git a/tests/contracts_fail.rs b/tests/contracts_fail.rs
index 23258e22..dd3e4084 100644
--- a/tests/contracts_fail.rs
+++ b/tests/contracts_fail.rs
@@ -60,7 +60,7 @@ fn merge_compose_contract() {
             "let Even = fun l x => if x % 2 == 0 then x else %blame% l in
         let DivBy3 = fun l x => if x % 3 ==  0 then x else %blame% l in
         let composed = {{a | #Even}} & {{a | #DivBy3}} in
-        (({{a = {},}}) & composed).a",
+        (({{a = {};}}) & composed).a",
             arg
         )
     };
@@ -76,21 +76,21 @@ fn merge_compose_contract() {
 fn records_contracts_simple() {
     assert_raise_blame!("{a=1} | {}");
 
-    assert_raise_blame!("let x | {a: Num, s: Str} = {a = 1, s = 2} in %deepSeq% x x");
-    assert_raise_blame!("let x | {a: Num, s: Str} = {a = \"a\", s = \"b\"} in %deepSeq% x x");
+    assert_raise_blame!("let x | {a: Num, s: Str} = {a = 1; s = 2} in %deepSeq% x x");
+    assert_raise_blame!("let x | {a: Num, s: Str} = {a = \"a\"; s = \"b\"} in %deepSeq% x x");
     assert_raise_blame!("let x | {a: Num, s: Str} = {a = 1} in %deepSeq% x x");
     assert_raise_blame!("let x | {a: Num, s: Str} = {s = \"a\"} in %deepSeq% x x");
     assert_raise_blame!(
-        "let x | {a: Num, s: Str} = {a = 1, s = \"a\", extra = 1} in %deepSeq% x x"
+        "let x | {a: Num, s: Str} = {a = 1; s = \"a\"; extra = 1} in %deepSeq% x x"
     );
 
     assert_raise_blame!(
-        "let x | {a: Num, s: {foo: Bool}} = {a = 1, s = {foo = 2}} in %deepSeq% x x"
+        "let x | {a: Num, s: {foo: Bool}} = {a = 1; s = {foo = 2}} in %deepSeq% x x"
     );
     assert_raise_blame!(
-        "let x | {a: Num, s: {foo: Bool}} = {a = 1, s = {foo = true, extra = 1}} in %deepSeq% x x"
+        "let x | {a: Num, s: {foo: Bool}} = {a = 1; s = {foo = true; extra = 1}} in %deepSeq% x x"
     );
-    assert_raise_blame!("let x | {a: Num, s: {foo: Bool}} = {a = 1, s = {}} in %deepSeq% x x");
+    assert_raise_blame!("let x | {a: Num, s: {foo: Bool}} = {a = 1; s = {}} in %deepSeq% x x");
 }
 
 #[test]
@@ -103,7 +103,7 @@ fn records_contracts_poly() {
     assert_raise_blame!(&format!("{} {{}}", remv));
 
     let bad_cst = "let f | forall a. { | a} -> { | a} = fun x => {a=1} in f";
-    let bad_acc = "let f | forall a. { | a} -> { | a} = fun x => %seq% x.a x in f";
+    let bad_acc = "let f | forall a. { | a} -> { | a} = fun x => %seq% (x.a) x in f";
     let bad_extd = "let f | forall a. { | a} -> {foo: Num | a} = fun x => x-$\"foo\" in f";
     let bad_rmv = "let f | forall a. {foo: Num | a} -> { | a} = fun x => x$[\"foo\"=1] in f";
 
@@ -126,7 +126,7 @@ fn records_contracts_poly() {
                 -> {a: Num | a}
                 -> { | a}
             = fun f rec => (f rec) -$ \"a\" -$ \"b\" in
-        f (fun x => x) {a = 1, b = true, c = 3}"
+        f (fun x => x) {a = 1; b = true; c = 3}"
     );
 }
 
diff --git a/tests/infinite_rec.rs b/tests/infinite_rec.rs
index c1ac5995..0216de3d 100644
--- a/tests/infinite_rec.rs
+++ b/tests/infinite_rec.rs
@@ -11,15 +11,15 @@ fn infinite_loops() {
         Err(Error::EvalError(EvalError::InfiniteRecursion(..)))
     );
     assert_matches!(
-        eval("{x = y, y = z, z = x }.x"),
+        eval("{x = y; y = z; z = x }.x"),
         Err(Error::EvalError(EvalError::InfiniteRecursion(..)))
     );
     assert_matches!(
-        eval("{x = y + z, y = z + x, z = 1}.x"),
+        eval("{x = y + z; y = z + x; z = 1}.x"),
         Err(Error::EvalError(EvalError::InfiniteRecursion(..)))
     );
     assert_matches!(
-        eval("{x = (fun a => a + y) 0, y = (fun a => a + x) 0}.x"),
+        eval("{x = (fun a => a + y) 0; y = (fun a => a + x) 0}.x"),
         Err(Error::EvalError(EvalError::InfiniteRecursion(..)))
     );
 }
diff --git a/tests/pass/builtins.ncl b/tests/pass/builtins.ncl
index 1e2bac24..3795a036 100644
--- a/tests/pass/builtins.ncl
+++ b/tests/pass/builtins.ncl
@@ -7,16 +7,16 @@ let Assert = fun l x => x || %blame% l in
 /// just that the evaluation succeeds
 (%seq% 1 true | #Assert) &&
 (let x = (1 + 1) in %seq% x x == 2 | #Assert) &&
-(let r = {a=(1 + 1),} in
+(let r = {a=(1 + 1);} in
   %deepSeq% r (r.a) == 2 | #Assert) &&
-(let r = {a=(1 + 1),b=("a" ++ "b"),} in
+(let r = {a=(1 + 1);b=("a" ++ "b");} in
   %deepSeq% r (r.b) == "ab" | #Assert) &&
 (let r = {a = {b = 1 + 1}} in
   %deepSeq% r (r.a.b) == 2 | #Assert) &&
 
 (let inj = fun x => {b=(x + 2)} in
   let cat = fun x => fun y => x ++ y in
-  let r = {a=(inj 1),b=(cat "a" "b")} in
+  let r = {a=(inj 1);b=(cat "a" "b")} in
   %deepSeq% r (r.a.b) == 3 | #Assert) &&
 
 true
diff --git a/tests/pass/contracts.ncl b/tests/pass/contracts.ncl
index 3cea5432..0d3454e3 100644
--- a/tests/pass/contracts.ncl
+++ b/tests/pass/contracts.ncl
@@ -41,12 +41,12 @@ f `boo == 3) &&
 
 // records_simple
 (({} | {}) == {} | #Assert) &&
-(let x | {a: Num, s: Str} = {a = 1, s = "a"} in
-  %deepSeq% x x == {a = 1, s = "a"}
+(let x | {a: Num, s: Str} = {a = 1; s = "a"} in
+  %deepSeq% x x == {a = 1; s = "a"}
   | #Assert) &&
 
-(let x | {a: Num, s: {foo: Bool}} = {a = 1, s = { foo = true}} in
-  %deepSeq% x x == {a = 1, s = { foo = true}}
+(let x | {a: Num, s: {foo: Bool}} = {a = 1; s = { foo = true}} in
+  %deepSeq% x x == {a = 1; s = { foo = true}}
   | #Assert) &&
 
 // polymorphism
@@ -57,27 +57,27 @@ f `boo == 3) &&
     x -$ "foo" in
 
   (id {} == {} | #Assert) &&
-  (id {a = 1, b = false} == {a = 1, b = false} | #Assert) &&
+  (id {a = 1; b = false} == {a = 1; b = false} | #Assert) &&
   (extend {} == {foo = 1} | #Assert) &&
-  (extend {bar = false} == {foo = 1, bar = false} | #Assert) &&
+  (extend {bar = false} == {foo = 1; bar = false} | #Assert) &&
   (remove {foo = 1} == {} | #Assert) &&
-  (remove {foo = 1, bar = 1} == {bar = 1} | #Assert) &&
+  (remove {foo = 1; bar = 1} == {bar = 1} | #Assert) &&
   (remove (extend {}) == {} | #Assert) &&
   (extend (remove {foo = 2}) == {foo =1} | #Assert) &&
   (let f | forall a b. {f: a -> a, arg: a | b} -> a =
       fun rec => rec.f (rec.arg) in
-    f { f = fun x => x ++ " suffix", arg = "foo" }
+    f { f = fun x => x ++ " suffix"; arg = "foo" }
     == "foo suffix"
     | #Assert)
 ) &&
 
 // records_dynamic_tail
-(({a = 1, b = "b"} | {a: Num, b: Str | Dyn}) == {a = 1, b = "b"}
+(({a = 1; b = "b"} | {a: Num, b: Str | Dyn}) == {a = 1; b = "b"}
   | #Assert) &&
-(({a = 1, b = "b", c = false} | {a: Num, b: Str | Dyn})
-  == {a = 1, b = "b", c = false}
+(({a = 1; b = "b"; c = false} | {a: Num, b: Str | Dyn})
+  == {a = 1; b = "b"; c = false}
   | #Assert) &&
-((fun r => r.b | {a: Num | Dyn} -> Dyn) {a = 1, b = 2} == 2
+((fun r => r.b | {a: Num | Dyn} -> Dyn) {a = 1; b = 2} == 2
   | #Assert) &&
 
 
@@ -86,4 +86,11 @@ f `boo == 3) &&
 (([1, 2, 3] | List Num) == [1, 2, 3] | #Assert) &&
 ((["1", "2", "false"] | List Str) == ["1", "2", "false"] | #Assert) &&
 
+// full_annotations
+// Check that the contract introduced by the type annotation doesn't interact
+// with the `default` attribute
+(({foo : {bar: Bool} | default = {bar = false}} & {foo.bar = true}).foo.bar
+  | #Assert) &&
+
+
 true
diff --git a/tests/pass/eq.ncl b/tests/pass/eq.ncl
index b605e992..da314768 100644
--- a/tests/pass/eq.ncl
+++ b/tests/pass/eq.ncl
@@ -36,30 +36,30 @@ let Assert = fun l x => x || %blame% l in
 // records
 ({} == {} | #Assert) &&
 ({}$["a" = 1]$["b" = true]
-  == {a = 1, b = true}
+  == {a = 1; b = true}
   | #Assert) &&
-({a = 1 + 0, b = 1 + 1, c = 1 + 1 + 1}
-  == { a = 1, b = 2, c = 3 }
+({a = 1 + 0; b = 1 + 1; c = 1 + 1 + 1}
+  == { a = 1; b = 2; c = 3 }
   | #Assert) &&
 ({
-    foo = 1 + 0,
-    bar = "a" ++ "b",
+    foo = 1 + 0;
+    bar = "a" ++ "b";
     baz = if true then true else false
-  } == {foo = 1, bar = "ab", baz = true}
+  } == {foo = 1; bar = "ab"; baz = true}
   | #Assert) &&
 
 ({}$["a" = { a = { a = {} } }]
   == { a = { a = { a = {} } } }
   | #Assert) &&
 ({
-    foo = {bar = 2 + (-1)},
+    foo = {bar = 2 + (-1)};
     baz = {foo = {bar = 1 + 1}}
   }
-  == {foo = {bar = 1}, baz = {foo = {bar = 2}}}
+  == {foo = {bar = 1}; baz = {foo = {bar = 2}}}
   | #Assert) &&
 ({} != {a = true} | #Assert) &&
 ({a = 1} != {a = 2} | #Assert) &&
-({ a = "a", b = true } != { a = true, b = "a"} | #Assert) &&
+({ a = "a"; b = true } != { a = true; b = "a"} | #Assert) &&
 ({ a = { a = true } } != {a = { a = { a = true } } } | #Assert) &&
 
 // Now that the equality operator directly uses the stack to store its continuation (see
@@ -71,8 +71,8 @@ let Assert = fun l x => x || %blame% l in
       if b then true else false in
     not (not (not (not ((x) == (y))))) in
   (eq_with_ctxt
-      {a = 1 + 0, b = 1 + 1 + a, c = 0, d = 0}
-      { a = 1, b = 3, c = 0, d = 0}
+      {a = 1 + 0; b = 1 + 1 + a; c = 0; d = 0}
+      { a = 1; b = 3; c = 0; d = 0}
     | #Assert) &&
   (eq_with_ctxt
       [[1,2,3,4], [1,0,3,4], [1,2,3,4], [1,2,3,4]]
diff --git a/tests/pass/metavalues.ncl b/tests/pass/metavalues.ncl
index 4bd158aa..39a5b4fe 100644
--- a/tests/pass/metavalues.ncl
+++ b/tests/pass/metavalues.ncl
@@ -8,23 +8,23 @@ let Assert = fun l x => x || %blame% l in
   builtins.seq (x.a) ((x & {a=2}).a) == 2 | #Assert) &&
 
 // merge_default
-(({a = 2} & {a | default = 0, b | default = true}) == {a = 2, b = true}
+(({a = 2} & {a | default = 0; b | default = true}) == {a = 2; b = true}
   | #Assert) &&
-({a | default = {x = 1}} & {a | default = {y = "y"}} == {a = {x = 1, y = "y"}}
+({a | default = {x = 1}} & {a | default = {y = "y"}} == {a = {x = 1; y = "y"}}
   | #Assert) &&
 
 // merge_contract
-({a = 2, b | Bool} & {a | Num, b | default = true}
-  == {a = 2, b = true}
+({a = 2; b | Bool} & {a | Num; b | default = true}
+  == {a = 2; b = true}
   | #Assert) &&
 
 // merge_default_contract
-({a = 2} & {a | default | Num = 0, b | default = true}
-  == {a = 2, b = true}
+({a = 2} & {a | default | Num = 0; b | default = true}
+  == {a = 2; b = true}
   | #Assert) &&
 
 ({a=2} & {a | Num} & {a | default = 3} == {a = 2} | #Assert) &&
-({a=2} & {b | Num} & {b | default = 3} == {a = 2, b = 3} | #Assert) &&
+({a=2} & {b | Num} & {b | default = 3} == {a = 2; b = 3} | #Assert) &&
 (({a | default = 1} & {b | Num} & {a | default = 1}).a
   == 1
   | #Assert) &&
diff --git a/tests/pass/records.ncl b/tests/pass/records.ncl
index ec728c10..c4c1e437 100644
--- a/tests/pass/records.ncl
+++ b/tests/pass/records.ncl
@@ -1,13 +1,13 @@
 let Assert = fun l x => x || %blame% l in
 
 // accesses
-(({foo = 3, bar = true}).bar == true | #Assert) &&
-({"#{if true then "foo" else "bar"}" = false, bar = true}.foo
+(({foo = 3; bar = true}).bar == true | #Assert) &&
+({"#{if true then "foo" else "bar"}" = false; bar = true}.foo
   == false
   | #Assert) &&
 
-(({foo = 3, bar = true})."bar" == true | #Assert) &&
-({"#{if true then "foo" else "bar"}" = false, bar = true}."#{"foo"}"
+(({foo = 3; bar = true})."bar" == true | #Assert) &&
+({"#{if true then "foo" else "bar"}" = false; bar = true}."#{"foo"}"
  == false 
  | #Assert) &&
 
@@ -15,67 +15,67 @@ let Assert = fun l x => x || %blame% l in
   | #Assert) &&
 
 // primitive_ops
-(records.hasField "foo" {foo = 1, bar = 2} | #Assert) &&
-(records.hasField "fop" {foo = 1, bar = 2} == false | #Assert) &&
-(records.hasField "foo" ({foo = 2, bar = 3} -$ "foo")
+(records.hasField "foo" {foo = 1; bar = 2} | #Assert) &&
+(records.hasField "fop" {foo = 1; bar = 2} == false | #Assert) &&
+(records.hasField "foo" ({foo = 2; bar = 3} -$ "foo")
   == false
   | #Assert) &&
 
 (records.hasField "foo" ({bar = 3}$["foo" = 1]) | #Assert) &&
 
 // lazyness of map
-((records.map (fun x y => y + 1) {foo = 1, bar = "it's lazy"}).foo
+((records.map (fun x y => y + 1) {foo = 1; bar = "it's lazy"}).foo
   == 2
   | #Assert) &&
 
 (let r = records.map
     (fun y x => if %isNum% x then x + 1 else 0)
-    {foo = 1, bar = "it's lazy"} in
+    {foo = 1; bar = "it's lazy"} in
   (r.foo) + (r.bar) == 2
   | #Assert) &&
 
 // merging
-({a = 1} & {b=true} == {a = 1, b = true} | #Assert) &&
-({a = 1, b = 2} & {b = 2, c = 3}
-  == {a = 1, b = 2, c = 3}
+({a = 1} & {b=true} == {a = 1; b = true} | #Assert) &&
+({a = 1; b = 2} & {b = 2; c = 3}
+  == {a = 1; b = 2; c = 3}
   | #Assert) &&
 
 ({a = {b = 1}} & {a = {c = true}}
-  == {a = {b = 1, c = true}}
+  == {a = {b = 1; c = true}}
   | #Assert) &&
 
 // merge_complex
 (let rec1 = {
-    a = false,
-    b = if true then (1 + 1) else (2 + 0),
-    c= ((fun x => x) (fun y => y)) 2,
+    a = false;
+    b = if true then (1 + 1) else (2 + 0);
+    c= ((fun x => x) (fun y => y)) 2;
   } in
   let rec2 = {
-    b = ((fun x => x) (fun y => y)) 2,
-    c = if true then (1 + 1) else (2 + 0),
-    d = true,
+    b = ((fun x => x) (fun y => y)) 2;
+    c = if true then (1 + 1) else (2 + 0);
+    d = true;
   } in
   let result = {
-    a = false,
-    b = 2,
-    c = 2,
-    d = true,
+    a = false;
+    b = 2;
+    c = 2;
+    d = true;
   } in
   rec1 & rec2 == result
   | #Assert) &&
 
 // merge_with_env
 ((fun y => ((fun x => {a=y}) 1) & ({b=false})) 2
-  == {a = 2, b = false}
+  == {a = 2; b = false}
   | #Assert) &&
 
 // merge_with_env_nested
-({b={c=10}} & ((fun x => {a=x, b={c=x}}) 10)
-  == {a=10, b = {c = 10}}
+({b={c=10}} & ((fun x => {a=x; b={c=x}}) 10)
+  == {a=10; b = {c = 10}}
   | #Assert) &&
 
 // recursive_records
-({a = 1, b = a + 1, c = b + a} == {a = 1, b = 2, c = 3} | #Assert) &&
+({a = 1; b = a + 1; c = b + a} == {a = 1; b = 2; c = 3} | #Assert) &&
 ({f = fun x y =>
     if x == 0 then y else f (x + (-1)) (y + 1)
   }.f 5 5
@@ -87,7 +87,7 @@ let Assert = fun l x => x || %blame% l in
     f = fun x =>
       if x == 0 then
         res
-      else g x,
+      else g x;
     g = fun y => f (y + (-1))
   }.f 10 in
   with_res "done" == "done"
diff --git a/tests/pass/serialize.ncl b/tests/pass/serialize.ncl
index bca17a1e..54cbfc79 100644
--- a/tests/pass/serialize.ncl
+++ b/tests/pass/serialize.ncl
@@ -18,15 +18,15 @@ let assertDeserInv = fun x =>
 (assertSerInv {val = 1 + 1} | #Assert) &&
 (assertSerInv {val = "Some string"} | #Assert) &&
 (assertSerInv {val = ["a", 3, []]} | #Assert) &&
-(assertSerInv {a.foo.bar = "2", b = false, c = [{d = "e"}, {d = "f"}]}
+(assertSerInv {a.foo.bar = "2"; b = false; c = [{d = "e"}, {d = "f"}]}
   | #Assert) &&
 
-(assertDeserInv {a = 1, b = 4, c = 3} | #Assert) &&
+(assertDeserInv {a = 1; b = 4; c = 3} | #Assert) &&
 (assertDeserInv {a.b.c = "richtig"} | #Assert) &&
 (assertDeserInv {
-    foo = 1,
-    bar = ["str", true],
-    baz = {subfoo = true, subbar = 0}
+    foo = 1;
+    bar = ["str", true];
+    baz = {subfoo = true; subbar = 0}
   } | #Assert) &&
 
 
diff --git a/tests/pass/typechecking.ncl b/tests/pass/typechecking.ncl
index b0264fa0..9c789180 100644
--- a/tests/pass/typechecking.ncl
+++ b/tests/pass/typechecking.ncl
@@ -76,22 +76,22 @@ let typecheck = [
 
   // static records
   ({bla = 1} : {bla : Num}),
-  ({blo = true, bla = 1} : {bla : Num, blo : Bool}),
+  ({blo = true; bla = 1} : {bla : Num, blo : Bool}),
   ({blo = 1}.blo : Num),
-  ({bla = true, blo = 1}.blo : Num),
-  let r : {bla : Bool, blo : Num} = {blo = 1, bla = true} in
+  ({bla = true; blo = 1}.blo : Num),
+  let r : {bla : Bool, blo : Num} = {blo = 1; bla = true} in
     ((if r.bla then r.blo else 2) : Num),
 
   let f : forall a. (forall r. {bla : Bool, blo : a, ble : a | r} -> a) =
       fun r => if r.bla then r.blo else r.ble in
-    (if (f {bla = true, blo = false, ble = true, blip = 1, }) then
-      (f {bla = true, blo = 1, ble = 2, blip = `blip, })
+    (if (f {bla = true; blo = false; ble = true; blip = 1; }) then
+      (f {bla = true; blo = 1; ble = 2; blip = `blip; })
     else
-      (f {bla = true, blo = 3, ble = 4, bloppo = `bloppop, }) : Num),
+      (f {bla = true; blo = 3; ble = 4; bloppo = `bloppop; }) : Num),
 
-  ({ "#{if true then "foo" else "bar"}" = 2, } : {_ : Num}),
-  ({ "#{if true then "foo" else "bar"}" = 2, }."bla" : Num),
-  ({ foo = 3, bar = 4, } : {_ : Num}),
+  ({ "#{if true then "foo" else "bar"}" = 2; } : {_ : Num}),
+  ({ "#{if true then "foo" else "bar"}" = 2; }."bla" : Num),
+  ({ foo = 3; bar = 4; } : {_ : Num}),
 
   // seq
   (%seq% false 1 : Num),
@@ -115,18 +115,18 @@ let typecheck = [
   fun i l => %elemAt% l i : forall a. Num -> List a -> a,
 
   // recursive_records
-  {a : Num = 1, b = a + 1} : {a : Num, b : Num},
+  {a : Num = 1; b = a + 1} : {a : Num, b : Num},
   {a : Num = 1 + a} : {a : Num},
   {a : Num = 1 + a} : {a : Num},
 
   // let_inference
   (let x = 1 + 2 in let f = fun x => x + 1 in f x) : Num, 
   // (let x = 1 + 2 in let f = fun x => x ++ "a" in f x) : Num,
-  {a = 1, b = 1 + a} : {a : Num, b : Num},
-  {f = fun x => if x == 0 then 1 else 1 + (f (x + (-1))),}
+  {a = 1; b = 1 + a} : {a : Num, b : Num},
+  {f = fun x => if x == 0 then 1 else 1 + (f (x + (-1)));}
     : {f : Num -> Num},
 
-  { f = fun x => if x == 0 then 1 else 1 + (f (x + (-1))),}
+  { f = fun x => if x == 0 then 1 else 1 + (f (x + (-1)));}
     : {f : Num -> Num},
 
   // polymorphic_row_constraints
@@ -134,8 +134,8 @@ let typecheck = [
     let remove | forall c. {a: Str | c} -> { | c} = 0 in
     (let good = remove (extend {}) in 0) : Num,
 
-  let r | {a: Num | Dyn} = {a = 1, b = 2} in (r.a : Num),
-  ({a = 1, b = 2} | {a: Num | Dyn}) : {a: Num | Dyn},
+  let r | {a: Num | Dyn} = {a = 1; b = 2} in (r.a : Num),
+  ({a = 1; b = 2} | {a: Num | Dyn}) : {a: Num | Dyn},
 
   //Regression test following [#270](https://github.com/tweag/nickel/issues/270). Check that
   //unifying a variable with itself doesn't introduce a loop. The failure of this test results
@@ -161,6 +161,9 @@ let typecheck = [
   // T` (for `T` a type or a type variable) to `List Dyn`.
   {foo = [1]} : {foo : List Num},
   (let y = [] in y) : forall a. List a,
+
+  // full_annotations
+  let x : Num | doc "some" | default = 1 in x + 1 : Num,
 ] in
 
 
diff --git a/tests/records_fail.rs b/tests/records_fail.rs
index 59655ee5..fc608f1b 100644
--- a/tests/records_fail.rs
+++ b/tests/records_fail.rs
@@ -11,7 +11,7 @@ fn records_access() {
         Err(Error::EvalError(EvalError::FieldMissing(..)))
     );
     assert_matches!(
-        eval("({ \"#{(if false then \"foo\" else \"bar\")}\" = false, bar = true, }).foo"),
+        eval("({ \"#{(if false then \"foo\" else \"bar\")}\" = false; bar = true; }).foo"),
         Err(Error::EvalError(EvalError::Other(msg, ..))) if msg.starts_with("$[ .. ]"));
 }
 
@@ -22,7 +22,7 @@ fn non_mergeable() {
         Err(Error::EvalError(EvalError::MergeIncompatibleArgs(..)))
     );
     assert_matches!(
-        eval("({a | default = false} & {a | default = true}).a"),
+        eval("({a | default = false;} & {a | default = true}).a"),
         Err(Error::EvalError(EvalError::MergeIncompatibleArgs(..)))
     );
     assert_matches!(
diff --git a/tests/typecheck_fail.rs b/tests/typecheck_fail.rs
index a7284081..50d5aa10 100644
--- a/tests/typecheck_fail.rs
+++ b/tests/typecheck_fail.rs
@@ -119,19 +119,19 @@ fn static_record_simple() {
     assert_typecheck_fails!(
         "let f : forall a. (forall r. {bla : Bool, blo : a, ble : a | r} -> a) =
             fun r => if r.bla then r.blo else r.ble in
-         (f {bla = true, blo = 1, ble = true, blip = `blip} : Num)"
+         (f {bla = true; blo = 1; ble = true; blip = `blip} : Num)"
     );
     assert_typecheck_fails!(
         "let f : forall a. (forall r. {bla : Bool, blo : a, ble : a | r} -> a) =
             fun r => if r.bla then (r.blo + 1) else r.ble in
-         (f {bla = true, blo = 1, ble = 2, blip = `blip} : Num)"
+         (f {bla = true; blo = 1; ble = 2; blip = `blip} : Num)"
     );
 }
 
 #[test]
 fn dynamic_record_simple() {
     assert_typecheck_fails!(
-        "({ \"#{if true then \"foo\" else \"bar\"}\" = 2, \"foo\" = true, }.\"bla\") : Num"
+        "({ \"#{if true then \"foo\" else \"bar\"}\" = 2; \"foo\" = true; }.\"bla\") : Num"
     );
 }
 
@@ -150,44 +150,48 @@ fn lists_operations() {
     );
 }
 
-#[test]
-fn imports() {
-    let mut resolver = SimpleResolver::new();
-    resolver.add_source(String::from("good"), String::from("1 + 1 : Num"));
-    resolver.add_source(String::from("bad"), String::from("false : Num"));
-    resolver.add_source(
-        String::from("proxy"),
-        String::from("let x = import \"bad\" in x"),
-    );
-
-    fn mk_import<R>(import: &str, resolver: &mut R) -> Result<RichTerm, ImportError>
-    where
-        R: ImportResolver,
-    {
-        transform(
-            mk_term::let_in("x", mk_term::import(import), mk_term::var("x")),
-            resolver,
-        )
-    };
-
-    type_check_in_env(
-        &mk_import("good", &mut resolver).unwrap(),
-        &Environment::new(),
-        &mut resolver,
-    )
-    .unwrap();
-    type_check_in_env(
-        &mk_import("proxy", &mut resolver).unwrap(),
-        &Environment::new(),
-        &mut resolver,
-    )
-    .unwrap_err();
-}
+// FIXME: these tests are boguous, because they transform the term before typechecking, which is
+// not the case in the actual evaluation pipeline. Currently, typechecking is done before program
+// transformation, and thus can't typecheck imported files, which is an issue that needs to be
+// solved before re-introducing these tests. See https://github.com/tweag/nickel/issues/316.
+// #[test]
+// fn imports() {
+//     let mut resolver = SimpleResolver::new();
+//     resolver.add_source(String::from("good"), String::from("1 + 1 : Num"));
+//     resolver.add_source(String::from("bad"), String::from("false : Num"));
+//     resolver.add_source(
+//         String::from("proxy"),
+//         String::from("let x = import \"bad\" in x"),
+//     );
+//
+//     fn mk_import<R>(import: &str, resolver: &mut R) -> Result<RichTerm, ImportError>
+//     where
+//         R: ImportResolver,
+//     {
+//         transform(
+//             mk_term::let_in("x", mk_term::import(import), mk_term::var("x")),
+//             resolver,
+//         )
+//     };
+//
+//     type_check_in_env(
+//         &mk_import("good", &mut resolver).unwrap(),
+//         &Environment::new(),
+//         &mut resolver,
+//     )
+//     .unwrap();
+//     type_check_in_env(
+//         &mk_import("proxy", &mut resolver).unwrap(),
+//         &Environment::new(),
+//         &mut resolver,
+//     )
+//     .unwrap_err();
+// }
 
 #[test]
 fn recursive_records() {
-    assert_typecheck_fails!("{a : Num = true, b = a + 1} : {a : Num, b : Num}");
-    assert_typecheck_fails!("{a = 1, b : Bool = a} : {a : Num, b : Bool}");
+    assert_typecheck_fails!("{a : Num = true; b = a + 1} : {a : Num, b : Num}");
+    assert_typecheck_fails!("{a = 1; b : Bool = a} : {a : Num, b : Bool}");
 }
 
 #[test]
@@ -223,7 +227,7 @@ fn polymorphic_row_constraints() {
 
     let mut res = type_check_expr(
         "let extend | forall c. { | c} -> {a: Str | c} = null in
-           (let bad = extend {a = 1} in 0) : Num",
+           (let bad = extend {a = 1;} in 0) : Num",
     );
     assert_row_conflict(res);
 
@@ -238,7 +242,7 @@ fn polymorphic_row_constraints() {
 fn dynamic_row_tail() {
     // Currently, typechecking is conservative wrt the dynamic row type, meaning it can't
     // convert to a less precise type with a dynamic tail.
-    assert_typecheck_fails!("{a = 1, b = 2} : {a: Num | Dyn}");
+    assert_typecheck_fails!("{a = 1; b = 2} : {a: Num | Dyn}");
     assert_typecheck_fails!("{a = 1} : {a: Num | Dyn}");
     assert_typecheck_fails!("({a = 1} | {a: Num | Dyn}) : {a: Num}");
     assert_typecheck_fails!("{a = 1} : {a: Num | Dyn}");