Repository: tweag/nickel
Pull request #576 (2022-01-18 10:41:49)

Doc/syntax
This PR adds a document in the manual explaining nickel syntax.


Patch:
diff --git a/doc/manual/syntax.md b/doc/manual/syntax.md
new file mode 100644
index 00000000..54d4a5c0
--- /dev/null
+++ b/doc/manual/syntax.md
@@ -0,0 +1,446 @@
+# Nickel Syntax
+
+## Simple values
+
+There are three basic kind of values in Nickel : 
+ 1. numeric values, 
+ 2. boolean values,
+ 3. strings.
+ 
+### Numeric values
+
+Nickel has a support for numbers, positive and negative, with or without decimals.
+Internally, those numbers are stored as 64-bits floating point numbers, following the IEEE 754 standard.
+
+Examples:
+
+```
+1
+0.543
+42
+-1000000
+-6.8
+```
+
+There are a some predefined operators for working with numbers :
+| Operator | Description                                          | Example       |
+|:--------:|:----------------------------------------------------:|:-------------:|
+| +        | The addition operator                                | `1 + 2 = 3`   |
+| \-       | The subtraction operator                             | `1 - 2 = -1`  |
+| *        | The multiplication operator                          | `1 * 2 = 2`   |
+| /        | The division operator                                | `1 / 2 = 0.5` |
+| %        | The modulo operator (returns the *signed* remainder) | `5 % 3 = 2`   |
+
+> **Remark about the `-` operator:**  
+> Since `-` can be used inside an identifier, the subtraction operators **needs** to be surrounded by spaces:
+> write `1 - 1`, not `1-1`.
+
+Numbers can be compared using the following operators :
+| Operator | Description      | Example   |
+|:--------:|:----------------:|:---------:|
+| ==       | Equal            | `5 == 5`  |
+| !=       | Not Equal        | `5 != 4`  |
+| <        | Smaller than     | `2 < 3`   |
+| >        | Greater than     | `1 > -5`  |
+| >=       | Greater or Equal | `1 >= 1`  |
+| <=       | Smaller or Equal | `-1 <= 6` |
+
+In the table below, you will find the operators sorted from highest to lowest precedence:
+|       Operators      | Associativity | Remark                                        |
+|:--------------------:|:-------------:|-----------------------------------------------|
+|       `( ... )`      |               | parentheses always have the highest precedence |
+|          `-`         |               | unary negation (as in `-1`)                   |
+|     `*`, `/`, `%`    | left-to-right |                                               |
+|       `+`, `-`       | left-to-right | binary addition and subtraction               |
+| `<`, `>`, `=<`, `>=` | left-to-right |                                               |
+|      `==`, `!=`      | left-to-right |                                               |
+
+### Boolean values
+
+The boolean values in Nickel are denoted `true` and `false`.
+
+Nickel features the classical boolean operators *AND* (&&), *OR* (||) and *NOT* (!).
+The _AND_ and _OR_ operators are lazy in the evaluation of the second argument: for example, in `exp1 && exp2`, `exp2` is only evaluated if `exp1` evaluates to `false`.
+
+Examples:
+```
+> true && false
+false
+
+> false || true
+true
+
+> ! true
+false
+```
+
+### Strings
+
+Nickel can work with sequences of characters, or strings. 
+Strings are enclosed by `" ... "` for a single line string or by `m#" ... "#m` for a multiline string.
+They can be concatenated with the operator `++`.
+Strings must be UTF-8 valid.
+
+The string interpolation syntax is `"#{ < expression that evaluates to a string > }"`.
+
+Examples:
+```
+> "Hello, World!"
+"Hello, World!"
+
+> m#"Well, if this isn't a multiline string?
+Yes it is, indeed it is"#m
+"Well, if this isn't a string?
+Yes it is, indeed it is"
+
+> "Hello" ++ "World"
+"HelloWorld"
+
+> let h = "Hello" in "#{h} World"
+"Hello World"
+
+> let n = 5 in "The number #{n}."
+error: Type error
+
+> let n = 5 in "The number #{strings.from_num n}."
+"The number 5."
+```
+
+Multiline strings are useful to write indented lines. The indentation is stripped from the beginning of the first line, and first and last lines are ignored if they are empty or contain only spaces.
+
+Example:
+```
+> m#"
+This line has no identation.
+  This line is indented.
+    This line is even more indented.
+This line has no more identation.
+"#m
+"This line has no indentation.
+  This line is indented.
+    This line is even more indented.
+This line has no more indentation." 
+```
+
+The only special sequence in a multiline string is the string interpolation.
+
+Examples:
+```
+> m#"Multiline\nString?"#m
+"Multiline\nString?"
+
+> m#"Multiline#{"\n"}String"#m
+"Multiline
+String"
+```
+
+A multiline string can be introduced and closed by multiple `#` signs, as long as this amount is equal. If you want to use string interpolation, you must use the same amount of `#` as in the delimiters.
+
+Examples:
+```
+> m##"Hello World"##m
+"Hello World"
+
+> m#####"Hello World"#####m
+"Hello World"
+
+> let w = "World" in m##"Hello #{w}"##m
+"Hello #{w}"
+
+> let w = "World" in m##"Hello ##{w}"##m
+"Hello World"
+```
+
+Multiline strings are "indentation-aware". This means that one could use an indented string interpolation and the indentation would behave as expected:
+```
+> let log = m#"
+if log:
+  print("log:", s)
+"#m in m#"
+def concat(str_list, log=false):
+  res = []
+  for s in str_list:
+    #{log}
+    res.append(s)
+  return res
+"#m
+"def concat(str_list, log=false):
+  res = []
+  for s in str_list:
+    if log:
+      print("log:", s)
+    res.append(s)
+  return res"
+```
+
+## Equality
+
+Operators `==` and `!=` are used to compare values. Two values of different types are never equal: that is, `==` doesn't perform implicit conversions.
+
+Examples:
+```
+> 1 == 1
+true
+
+> 5 == 5.0
+true
+
+> "Hello" == "Hello"
+true
+
+> "Hello" != "World"
+true
+
+> 5 == "Hello"
+false
+
+> true == "true"
+false
+```
+
+
+## Composite values
+
+### List
+A list is a sequence of values. They are delimited by `[` and `]`, and elements are separated with `,`.
+
+Examples:
+```
+[1, 2, 3]
+["Hello", "World"]
+[1, true, "true"]
+[]
+```
+
+Lists can be concatenated with the operator `@`:
+```
+> [1] @ [2, 3]
+[ 1, 2, 3 ]
+```
+
+### Record
+Records are key-value storage, or in Nickel terms, field-value storage. They are delimited by `{` and `}`, and elements are separated with `,`.
+Field-value elements are noted as `field = value`.
+The fields are strings, but can be written without quotes `"` if they respect identifiers syntax. Values can be of any type.
+Elements inside a record are unordered.
+Two records can be _merged_ together using the operator `&`. The reader can find more information about merging in the relevant documentation.
+
+Examples:
+```
+{}
+{a = 3}
+{my_id_n5 = "my id number 5", "my id n4" = "my id number 4" }
+{"5" = 5, six = 6}
+```
+
+Accessing a record field can be done using the `.` operator :
+```
+> { a = 1, b = 5 }.a
+1
+
+> { a = 1 }.b
+error: Missing field
+
+> { "1" = "one" }."1"
+"one"
+```
+
+It is possible to write records of records via the *piecewise syntax*, where we separate fields by dots:
+```
+> { a = { b = 1 } }
+{ a = { b = 1 } }
+ 
+> { a.b = 1 }
+{ a = { b = 1 } }
+
+> { a.b = 1, a.c = 2, b = 3}
+{ a = { b = 1, c = 2 }, b = 3 }
+```
+
+When fields are enclosed with double quotes (`"`), you can use string interpolation to create or access fields:
+```
+> let k = "a" in { "#{k}" = 1 }
+{ a = 1 }
+
+> let k = "a" in { a = 1 }."#{k}"
+1
+```
+
+## Constructs
+
+### If-Then-Else
+This construct allows conditional branching in your code. You can use it as `if <bool expr> then <expr> else <expr>`.
+
+Examples:
+```
+> if true then "TRUE :)" else "false :("
+"TRUE :)"
+
+> if false then "Not this one" else "This one"
+"This one"
+
+> if "forty-two" == 42 then "equal?" else "unequal"
+"unequal"
+
+> ["1"] @ (if 42 == "42" then ["3"] else ["2"]) @ ["3"]
+["1", "2", "3"]
+```
+
+### Let-In
+Let-in allows the binding of an expression. It is used as `let <ident> = <expr> in <expr>`.
+
+Examples:
+```
+> let r = { a = "a", b = "b" } in r.a
+"a"
+
+> let inner = { inside = true } in let outer = { outside = inner.inside } in outer.outside
+true
+
+> let a = 1 in let b = 2 in a + b
+3
+```
+
+## Functions
+A function is declared using the `fun` keyword, then arguments separated with spaces, and finally an arrow `=>` to add the body of the function.
+To call a function, just add the arguments after it separated with spaces.
+Functions in Nickel are curried, meaning that a function taking multiple arguments is actually a function that takes a single argument and returns a function taking the rest of the arguments, until it is applied.
+
+Examples:
+```
+> (fun a b => a + b) 1 2
+3
+
+let add = fun a b => a + b in add 1 2
+3
+
+> let add = fun a b => a + b in
+    let add1 = add 1 in
+        add1 2
+3
+```
+
+All existing infix operators in Nickel can be turned into functions by putting them inside parentheses.
+
+Examples:
+```
+> 1 + 2
+3
+
+> (+) 1 2
+3
+
+> let increment = fun n => (+) 1 n in
+    increment 41
+42
+
+> let increment = (+) 1 in
+    increment 41
+42
+
+> let flatten = lists.fold (@) [] in flatten [[1, 2], [3], [4, 5]]
+[ 1, 2, 3, 4, 5 ]
+```
+
+Functions might be composed using the *pipe operator*. The pipe operator allows for a function application `f x` to be written as `x |> f`.
+This operator is left-associative, so `x |> f |> g` will be interpreted as `g (f x)`.
+
+Examples:
+```
+> "Hello World" |> strings.split " "
+["Hello", "World"]
+
+> "Hello World"
+  |> strings.split " "
+  |> lists.head
+"Hello"
+
+> "Hello World"
+  |> strings.split " "
+  |> lists.head
+  |> strings.uppercase
+"HELLO"
+```
+
+## Typing
+To give a type to a value, we write it with `< value > : < type >`.
+More information on typing in the relevant document.
+
+Examples:
+```
+5 : Num
+"Hello" : Str
+
+(fun a b => a + b) : Num -> Num -> Num
+let add : Num -> Num -> Num = fun a b => a + b
+
+{a: Num = 1, b: Bool = true, c : List Num = [ 1 ]}
+let r : {a : Num, b : Bool, c : List Num} = { a = 1, b = true, c = [ 1 ] }
+
+{ a = 1, b = 2 } : { _ : Num }
+let r : { _ : Num } = { a = 1, b = 2 }
+```
+
+## Metadata
+Metadata are used to attach contracts (more information in relevant documentation), documentation or priority to values.
+A metadata is introduced with the syntax `<value> | <metadata>`. Multiple metadata can be chained.
+
+
+Examples:
+```
+> 5 | Num
+5
+
+> 5 | Bool
+error: Blame error: contract broken by a value.
+
+> let SmallNum = contracts.from_predicate (fun x => x < 5) in
+1 | #SmallNum
+1
+
+> let SmallNum = contracts.from_predicate (fun x => x < 5) in
+10 | #SmallNum
+error: Blame error: contract broken by a value.
+
+> let SmallNum = contracts.from_predicate (fun x => x < 5) in
+  let NotTooSmallNum = contracts.from_predicate (fun x => x >= 2) in
+  3 | Num
+    | #SmallNum
+    | #NotTooSmallNum
+3
+```
+
+Adding documentation can be done with `| doc < string >`.  
+Examples:
+```
+> 5 | doc "The number five"
+5
+
+> true | Bool | doc m#"
+    If something is true,
+    it is based on facts rather than being invented or imagined,
+    and is accurate and reliable.
+    (Collins dictionary)
+    "#m
+true
+```
+
+Record contracts can set default values using the `default` metadata:
+It is noted as `| default = < default value >`.
+This is especially useful when merging records (more about this in the dedicated document about merge).  
+Examples:
+```
+> let Ais2ByDefault = { a | default = 2 } in
+  {} | #Ais2ByDefault
+{ a = 2 }
+
+> let Ais2ByDefault = { a | default = 2 } in
+  { a = 1 } | #Ais2ByDefault
+{ a = 1 }
+
+> { foo | default = 1, bar = foo + 1 }
+{ foo = 1, bar = 2 }
+
+> {foo | default = 1, bar = foo + 1} & {foo = 2}
+{ foo = 2, bar = 3 }
+```
diff --git a/scripts/channel_beta.toml b/scripts/channel_beta.toml
index 29b5bb72..a5abdbed 100644
--- a/scripts/channel_beta.toml
+++ b/scripts/channel_beta.toml
@@ -1,3 +1,3 @@
 channel="beta"
-date="2022-01-14"
-sha256="sha256-Qsf9R7GoiSyfgMWS7PfYASHL/B/VmAC5gd6uB5k0dGQ="
+date="2022-01-09"
+sha256="sha256-J4PElelxqC8MVCyN6BCwJcXSdPJMoxtjgT6EuYFC6lo="
diff --git a/scripts/channel_nightly.toml b/scripts/channel_nightly.toml
index 525b7457..acd8be8e 100644
--- a/scripts/channel_nightly.toml
+++ b/scripts/channel_nightly.toml
@@ -1,3 +1,3 @@
 channel="nightly"
-date="2022-01-18"
-sha256="sha256-nlzEr6Xn2QzTEBbRR03sCSOeexqmwCLoczOXbTSjqiQ="
+date="2022-01-13"
+sha256="sha256-5LiUnOIBfJY2ktoAa3e5+QZ3S295OyyFUy3xHfu/kOQ="
diff --git a/scripts/channel_stable.toml b/scripts/channel_stable.toml
index 60ebf81a..d5f6f953 100644
--- a/scripts/channel_stable.toml
+++ b/scripts/channel_stable.toml
@@ -1,3 +1,3 @@
 channel="stable"
-date="2022-01-13"
-sha256="sha256-eQBpSmy9+oHfVyPs0Ea+GVZ0fvIatj6QVhNhYKOJ6Jk="
+date="2021-12-02"
+sha256="sha256-6PfBjfCI9DaNRyGigEmuUP2pcamWsWGc4g7SNEHqD2c="
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index cdc4cde5..f71a9919 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -746,7 +746,6 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
                 RichTerm::new(Term::Let(id, t1, t2, btype), pos)
             }
             p @ Term::LetPattern(..) => panic!("Pattern {:?} has not been transformed before evaluation", p),
-            p @ Term::FunPattern(..) => panic!("Pattern {:?} has not been transformed before evaluation", p),
             Term::App(t1, t2) => {
                 let t1 = subst_(t1, global_env, env, Cow::Borrowed(bound.as_ref()));
                 let t2 = subst_(t2, global_env, env, bound);
diff --git a/src/grammar.lalrpop b/src/grammar.lalrpop
index 5c52f28b..845cb263 100644
--- a/src/grammar.lalrpop
+++ b/src/grammar.lalrpop
@@ -93,10 +93,10 @@ RootTerm: RichTerm = {
 
         mk_term::let_pat(pat.0, pat.1, t1, t2)
     },
-    <l: @L> "fun" <pats: Pattern+> "=>" <t: Term> <r: @R> => {
+    <l: @L> "fun" <ids:Ident+> "=>" <t: Term> <r: @R> => {
         let pos = mk_pos(src_id, l, r);
-        pats.into_iter().rev().fold(t, |t, (id, destruct)| RichTerm {
-            term: SharedTerm::new(Term::FunPattern(id, destruct, t)),
+        ids.into_iter().rev().fold(t, |t, p| RichTerm {
+            term: SharedTerm::new(Term::Fun(p, t)),
             pos,
         })
     },
@@ -161,7 +161,7 @@ RecordOperand: RichTerm = {
 
 RecordOperationChain: RichTerm = {
     <t: WithPos<RecordOperand>> "." <id: Ident> => mk_term::op1(UnaryOp::StaticAccess(id), t),
-    <t: WithPos<RecordOperand>> "." <t_id: WithPos<StrChunks>> => mk_access(t_id, t),
+    <t: WithPos<RecordOperand>> "." <t_id: WithPos<StrChunks>> => mk_term::op2(BinaryOp::DynAccess(), t_id, t),
     <t: WithPos<RecordOperand>> "-$" <t_id: WithPos<Atom>> => mk_term::op2(BinaryOp::DynRemove(), t_id, t),
     <r: WithPos<RecordOperand>> "$[" <id: Term> "=" <t: Term> "]" =>
         mk_app!(mk_term::op2(BinaryOp::DynExtend(), id, r), t),
diff --git a/src/parser/utils.rs b/src/parser/utils.rs
index 73471720..8c194f25 100644
--- a/src/parser/utils.rs
+++ b/src/parser/utils.rs
@@ -95,36 +95,6 @@ impl InfixOp {
     }
 }
 
-/// Turn dynamic accesses using literal chunks only into static accesses
-pub fn mk_access(access: RichTerm, root: RichTerm) -> RichTerm {
-    let label = match *access.term {
-        Term::StrChunks(ref chunks) => {
-            chunks
-                .iter()
-                .fold(Some(String::new()), |acc, next| match (acc, next) {
-                    (Some(mut acc), StrChunk::Literal(lit)) => {
-                        acc.push_str(lit);
-                        Some(acc)
-                    }
-                    _ => None,
-                })
-        }
-        _ => None,
-    };
-
-    if let Some(label) = label {
-        mk_term::op1(
-            UnaryOp::StaticAccess(Ident {
-                label,
-                pos: access.pos,
-            }),
-            root,
-        )
-    } else {
-        mk_term::op2(BinaryOp::DynAccess(), access, root)
-    }
-}
-
 /// Elaborate a record field definition specified as a path, like `a.b.c = foo`, into a regular
 /// flat definition `a = {b = {c = foo}}`.
 ///
diff --git a/src/term.rs b/src/term.rs
index 03a8c340..ad8e728d 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -57,12 +57,9 @@ pub enum Term {
     /// efficiently from the back of it.
     #[serde(skip)]
     StrChunks(Vec<StrChunk<RichTerm>>),
-    /// A standard function.
+    /// A function.
     #[serde(skip)]
     Fun(Ident, RichTerm),
-    /// A function able to destruct its arguments.
-    #[serde(skip)]
-    FunPattern(Option<Ident>, Destruct, RichTerm),
     /// A blame label.
     #[serde(skip)]
     Lbl(Label),
@@ -345,10 +342,7 @@ impl Term {
             }
             Bool(_) | Num(_) | Str(_) | Lbl(_) | Var(_) | Sym(_) | Enum(_) | Import(_)
             | ResolvedImport(_) => {}
-            Fun(_, ref mut t)
-            | FunPattern(_, _, ref mut t)
-            | Op1(_, ref mut t)
-            | Wrapped(_, ref mut t) => {
+            Fun(_, ref mut t) | Op1(_, ref mut t) | Wrapped(_, ref mut t) => {
                 func(t);
             }
             MetaValue(ref mut meta) => {
@@ -389,7 +383,7 @@ impl Term {
             Term::Bool(_) => Some("Bool"),
             Term::Num(_) => Some("Num"),
             Term::Str(_) => Some("Str"),
-            Term::Fun(_, _) | Term::FunPattern(_, _, _) => Some("Fun"),
+            Term::Fun(_, _) => Some("Fun"),
             Term::Lbl(_) => Some("Label"),
             Term::Enum(_) => Some("Enum"),
             Term::Record(..) | Term::RecRecord(..) => Some("Record"),
@@ -433,7 +427,7 @@ impl Term {
 
                 format!("\"{}\"", chunks_str.join(""))
             }
-            Term::Fun(_, _) | Term::FunPattern(_, _, _) => String::from("<func>"),
+            Term::Fun(_, _) => String::from("<func>"),
             Term::Lbl(_) => String::from("<label>"),
             Term::Enum(id) => {
                 let re = regex::Regex::new("_?[a-zA-Z][_a-zA-Z0-9]*").unwrap();
@@ -527,7 +521,6 @@ impl Term {
             | Term::Sym(_) => true,
             Term::Let(..)
             | Term::LetPattern(..)
-            | Term::FunPattern(..)
             | Term::App(_, _)
             | Term::Var(_)
             | Term::Switch(..)
@@ -567,7 +560,6 @@ impl Term {
             | Term::Record(..)
             | Term::List(_)
             | Term::Fun(_, _)
-            | Term::FunPattern(_, _, _)
             | Term::App(_, _)
             | Term::Switch(..)
             | Term::Var(_)
@@ -978,13 +970,6 @@ impl RichTerm {
                     pos,
                 )
             },
-            Term::FunPattern(id, d, t) => {
-                let t = t.traverse(f, state, method)?;
-                RichTerm::new(
-                    Term::FunPattern(id, d, t),
-                    pos,
-                )
-            },
             Term::Let(id, t1, t2, btype) => {
                 let t1 = t1.traverse(f, state, method)?;
                 let t2 = t2.traverse(f, state, method)?;
diff --git a/src/transform/desugar_destructuring.rs b/src/transform/desugar_destructuring.rs
index 8b3d3858..a1931424 100644
--- a/src/transform/desugar_destructuring.rs
+++ b/src/transform/desugar_destructuring.rs
@@ -6,7 +6,7 @@
 //!
 //! # Example
 //!
-//! ## The let pattern:
+//! The let pattern:
 //! ```text
 //! let x @ {a, b=d, ..} = {a=1,b=2,c="ignored"} in ...
 //! ```
@@ -17,19 +17,6 @@
 //! let d = x.b in
 //! ...
 //! ```
-//!
-//! ## The function pattern
-//! ```text
-//! let f = fun x@{a, b=c} {d ? 2, ..w} => <do_something> in ...
-//! ```
-//! will be transformed to:
-//! ```text
-//! let f = fun x %unnamed% => (
-//!     let {a, b=c} = x in
-//!     let {d ? 2, ..w} = %unnamed% in
-//!     <do_something>
-//! ) in ...
-//! ```
 use crate::destruct::{Destruct, Match};
 use crate::identifier::Ident;
 use crate::match_sharedterm;
@@ -38,49 +25,10 @@ use crate::term::{
     BinaryOp::DynRemove, BindingType, MetaValue, RichTerm, Term, UnaryOp::StaticAccess,
 };
 
-/// Entry point of the patterns desugaring.
-/// It desugar a `RichTerm` if possible (the term is a let pattern or a function with patterns in
-/// its arguments).
-/// ## Warning:
-/// The transformation is generaly not recursive. The result can contain patterns itself.
-pub fn transform_one(rt: RichTerm) -> RichTerm {
-    match *rt.term {
-        Term::LetPattern(..) => desugar_with_contract(rt),
-        Term::FunPattern(..) => desugar_fun(rt),
-        _ => rt,
-    }
-}
-
-/// Desugar a function with patterns as arguments.
-/// This function does not perform nested transformation because internaly it's only used in a top
-/// down traversal. This means that the return value is a normal `Term::Fun` but it can contain
-/// `Term::FunPattern` and `Term::LetPattern` inside.
-pub fn desugar_fun(rt: RichTerm) -> RichTerm {
-    match_sharedterm! {rt.term, with {
-        Term::FunPattern(x, pat, t_) if !pat.is_empty() => {
-            let x = x.unwrap_or_else(super::fresh_var);
-            let t_pos = t_.pos;
-            RichTerm::new(
-                Term::Fun(
-                    x.clone(),
-                    RichTerm::new(Term::LetPattern(None, pat, Term::Var(x).into(), t_), t_pos /* TODO: should we use rt.pos? */),
-                ),
-                rt.pos,
-            )
-        },
-        Term::FunPattern(Some(x), Destruct::Empty, t_) => RichTerm::new(Term::Fun(x, t_), rt.pos),
-        t@Term::FunPattern(..) => panic!(
-            "A function can not have empty pattern without name in {:?}",
-            t
-        ),
-    } else rt
-    }
-}
-
-/// Wrap the desugar `LetPattern` in a meta value containing the "Record contract" needed to check the
+/// Wrap the desugar term in a meta value containing the "Record contract" needed to check the
 /// pattern exhaustively and also fill the default values (`?` operator) if not presents in the
-/// record. This function should be, in the general case, considered as the entry point of the let
-/// patterns transformation.
+/// record. This function should be, in the general case, considered as the entry point of this
+/// transformation.
 pub fn desugar_with_contract(rt: RichTerm) -> RichTerm {
     match_sharedterm!(rt.term,
         with {
diff --git a/src/transform/mod.rs b/src/transform/mod.rs
index c27467b2..cb4fd827 100644
--- a/src/transform/mod.rs
+++ b/src/transform/mod.rs
@@ -1,4 +1,3 @@
-//! Various post transformations of nickel code.
 use crate::cache::ImportResolver;
 use crate::eval::{lazy::Thunk, Closure, Environment, IdentKind};
 use crate::identifier::Ident;
@@ -24,7 +23,7 @@ pub fn transform(rt: RichTerm) -> RichTerm {
         .traverse(
             &mut |rt: RichTerm, _| -> Result<RichTerm, ()> {
                 // before anything, we have to desugar the syntax
-                let rt = desugar_destructuring::transform_one(rt);
+                let rt = desugar_destructuring::desugar_with_contract(rt);
                 // We need to do contract generation before wrapping stuff in variables
                 let rt = apply_contracts::transform_one(rt);
                 Ok(rt)
diff --git a/src/typecheck/mod.rs b/src/typecheck/mod.rs
index 607063a7..bb10647f 100644
--- a/src/typecheck/mod.rs
+++ b/src/typecheck/mod.rs
@@ -281,29 +281,6 @@ fn type_check_<L: Linearizer>(
     rt: &RichTerm,
     ty: TypeWrapper,
 ) -> Result<(), TypecheckError> {
-    use crate::destruct::*;
-    // TODO: The insertion of values in the type environment is done but everything is
-    // typed as `Dyn`.
-    fn inject_pat_vars(pat: &Destruct, envs: &mut Envs) {
-        if let Destruct::Record(matches, _, rst) = pat {
-            if let Some(id) = rst {
-                envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
-            }
-            matches.iter().for_each(|m| match m {
-                Match::Simple(id, ..) => {
-                    envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()))
-                }
-                Match::Assign(id, _, (bind_id, pat)) => {
-                    let id = bind_id.as_ref().unwrap_or(id);
-                    envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
-                    if !pat.is_empty() {
-                        inject_pat_vars(&pat, envs);
-                    }
-                }
-            });
-        }
-    }
-
     let RichTerm { term: t, pos } = rt;
     linearizer.add_term(lin, t, *pos, ty.clone());
 
@@ -353,20 +330,6 @@ fn type_check_<L: Linearizer>(
             envs.insert(x.clone(), src);
             type_check_(state, envs, lin, linearizer, strict, t, trg)
         }
-        Term::FunPattern(x, pat, t) => {
-            let src = TypeWrapper::Ptr(new_var(state.table));
-            // TODO what to do here, this makes more sense to me, but it means let x = foo in bar
-            // behaves quite different to (\x.bar) foo, worth considering if it's ok to type these two differently
-            let trg = TypeWrapper::Ptr(new_var(state.table));
-            let arr = mk_tyw_arrow!(src.clone(), trg.clone());
-            if let Some(x) = x {
-                linearizer.retype_ident(lin, x, src.clone());
-                envs.insert(x.clone(), src);
-            }
-            inject_pat_vars(pat, &mut envs);
-            unify(state, strict, ty, arr).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
-            type_check_(state, envs, lin, linearizer, strict, t, trg)
-        }
         Term::List(terms) => {
             let ty_elts = TypeWrapper::Ptr(new_var(state.table));
 
@@ -411,6 +374,28 @@ fn type_check_<L: Linearizer>(
             type_check_(state, envs, lin, linearizer, strict, rt, ty)
         }
         Term::LetPattern(x, pat, re, rt) => {
+            use crate::destruct::*;
+            // TODO: The insertion of values in the type environment is done but everything is
+            // typed as `Dyn`.
+            fn inject_pat_vars(pat: &Destruct, envs: &mut Envs) {
+                if let Destruct::Record(matches, _, rst) = pat {
+                    if let Some(id) = rst {
+                        envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
+                    }
+                    matches.iter().for_each(|m| match m {
+                        Match::Simple(id, ..) => {
+                            envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()))
+                        }
+                        Match::Assign(id, _, (bind_id, pat)) => {
+                            let id = bind_id.as_ref().unwrap_or(id);
+                            envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
+                            if !pat.is_empty() {
+                                inject_pat_vars(&pat, envs);
+                            }
+                        }
+                    });
+                }
+            }
             let ty_let = binding_type(re.as_ref(), &envs, state.table, strict, state.resolver);
             type_check_(
                 state,


Test patch:
diff --git a/src/parser/tests.rs b/src/parser/tests.rs
index 792d979c..5c10d20f 100644
--- a/src/parser/tests.rs
+++ b/src/parser/tests.rs
@@ -125,10 +125,7 @@ fn variables() {
 
 #[test]
 fn functions() {
-    assert_eq!(
-        crate::transform::desugar_destructuring::desugar_fun(parse_without_pos("fun x => x")),
-        mk_term::id()
-    );
+    assert_eq!(parse_without_pos("fun x => x"), mk_term::id());
 }
 
 #[test]
diff --git a/tests/destructuring.rs b/tests/destructuring.rs
index 672d4d29..740eb0ac 100644
--- a/tests/destructuring.rs
+++ b/tests/destructuring.rs
@@ -83,8 +83,3 @@ fn typecontract_fail() {
         Err(Error::EvalError(EvalError::BlameError(..)))
     );
 }
-
-#[test]
-fn fun() {
-    assert_eq!(eval_file("destructuring/fun.ncl"), Ok(Term::Bool(true)));
-}
diff --git a/tests/destructuring/fun.ncl b/tests/destructuring/fun.ncl
deleted file mode 100644
index 3509e88f..00000000
--- a/tests/destructuring/fun.ncl
+++ /dev/null
@@ -1,2 +0,0 @@
-let f = fun x@{a, b} {c=d, ..} => a - x.a + b - x.b + d in
-f {a=1, b=2} {c=3, d=4} == 3
diff --git a/tests/pass/typechecking.ncl b/tests/pass/typechecking.ncl
index 04582eee..a9400346 100644
--- a/tests/pass/typechecking.ncl
+++ b/tests/pass/typechecking.ncl
@@ -90,7 +90,7 @@ let typecheck = [
       (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, }."#{"bl" ++ "a"}" : Num),
+  ({ "#{if true then "foo" else "bar"}" = 2, }."bla" : Num),
   ({ foo = 3, bar = 4, } : {_ : Num}),
 
   // seq