Repository: tweag/nickel
Pull request #812 (2022-08-31 09:13:24)

Update flake.lock + fix CI
Derived from the automatic flake updating. The original one was failing the CI, seemingly because markdown-lint has become more restrictive in its new version. This PR adds a few markdown comments to locally disabled the offending rule and make the CI pass.


Patch:
diff --git a/.github/workflows/update-flake-lock.yml b/.github/workflows/update-flake-lock.yml
index 65e6074c..1056fa2a 100644
--- a/.github/workflows/update-flake-lock.yml
+++ b/.github/workflows/update-flake-lock.yml
@@ -16,4 +16,4 @@ jobs:
           extra_nix_config: |
             access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
       - name: Update flake.lock
-        uses: DeterminateSystems/update-flake-lock@v13
+        uses: DeterminateSystems/update-flake-lock@v12
diff --git a/RATIONALE.md b/RATIONALE.md
index 56b6882d..a491afc1 100644
--- a/RATIONALE.md
+++ b/RATIONALE.md
@@ -96,6 +96,8 @@ perks:
 
 #### Typing JSON
 
+<!-- markdownlint-disable MD051 -->
+
 The second motivation for a non fully static type system is that some code may
 be hard to type. JSON is a de-facto standard format for configuration and Nickel
 aims at being straightforwardly convertible to and from JSON. If it were to be
@@ -105,6 +107,8 @@ fully statically typed, it would have to type things like heterogeneous lists:
 of offering typing capabilities for common idioms, but when the type system
 falls short of expressivity, you can still write your code without types.
 
+<!-- markdownlint-enable MD051 -->
+
 #### Data validation
 
 Another peculiarity is that there is an external tool which will consume the
diff --git a/benches/arrays.rs b/benches/arrays.rs
index f4d14a9e..4427688c 100644
--- a/benches/arrays.rs
+++ b/benches/arrays.rs
@@ -1,29 +1,6 @@
 use criterion::{criterion_main, Criterion};
-use nickel_lang::term::{ArrayAttrs, RichTerm, Term};
 use nickel_lang_utilities::{ncl_bench_group, EvalMode};
 use pprof::criterion::{Output, PProfProfiler};
-use pretty::{BoxAllocator, DocBuilder, Pretty};
-
-/// Generates a pseaudo-random Nickel array as a string.
-fn ncl_random_array(len: usize) -> String {
-    let m = 2_u64.pow(32);
-    let a = 1664525;
-    let c = 1013904223;
-
-    let mut numbers = Vec::with_capacity(len);
-    let mut acc = 1337;
-
-    for _ in 0..len {
-        acc = (a * acc + c) % m;
-        numbers.push(RichTerm::from(Term::Num(acc as f64)));
-    }
-
-    let xs = RichTerm::from(Term::Array(numbers, ArrayAttrs::default()));
-    let doc: DocBuilder<_, ()> = xs.pretty(&BoxAllocator);
-    let mut out = Vec::new();
-    doc.render(80, &mut out).unwrap();
-    String::from_utf8(out).unwrap()
-}
 
 ncl_bench_group! {
 name = benches;
@@ -131,7 +108,7 @@ config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flam
     }, {
         name = "sort normal",
         path = "arrays/sort",
-        args = (ncl_random_array(50)),
+        args = (20),
     }, {
         name = "sum normal 50",
         path = "arrays/sum",
@@ -150,15 +127,6 @@ config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flam
         path = "arrays/primes",
         args = (30),
         eval_mode = EvalMode::DeepSeq,
-    }, {
-        name = "random normal",
-        path = "arrays/random",
-        args = (50),
-    }, {
-        name = "random deepseq",
-        path = "arrays/random",
-        args = (50),
-        eval_mode = EvalMode::DeepSeq,
     }
 }
 criterion_main!(benches);
diff --git a/benches/arrays/random.ncl b/benches/arrays/random.ncl
deleted file mode 100644
index 48a0772e..00000000
--- a/benches/arrays/random.ncl
+++ /dev/null
@@ -1,22 +0,0 @@
-let mgc 
-  | doc "A multiplicative congruential pseudorandom number generator (MCG)."
-  = fun params x => (params.a * x + params.c) % params.m in
-
-# NOTE: These parameters are from "Table 7: Good multipliers for MCGs with m = 2^32"
-# of the 2021 paper "Computationally Easy, Spectrally Good Multipliers for
-# Congruential Pseudorandom Number Generators", by Guy Steele and Sebastiano Vigna.
-# But, they cannot be used because they require bit-shifting to select the 32 MSBs.
-# let params = { m = num.pow 2 32, a = 2480367069 } in
-
-# These parameters are from the "Numerical Recipes" book.
-let params = { m = num.pow 2 32, a = 1664525, c = 1013904223 } in
-
-let rec array_random = fun init n =>
-  if n == 0 then [init]
-  else 
-    let next = mgc params init in
-    [next] @ array_random next (n - 1)
-in
-
-{ run = array_random 42 }
-
diff --git a/benches/arrays/sort.ncl b/benches/arrays/sort.ncl
index 3204d762..e0a9f834 100644
--- a/benches/arrays/sort.ncl
+++ b/benches/arrays/sort.ncl
@@ -1,7 +1,5 @@
-let ascending = fun x y => 
-  if x < y then `Lesser 
-  else if x == y then `Equal 
-  else `Greater 
-in
-
-{ run = array.sort ascending }
+let nums = [3288, 2438, 1462, 437, 17, 1929, 2595, 2702, 831, 3685, 1906, 420, 2646, 1906, 2539, 184, 299, 3455, 2320, 1471, 1812, 3837, 1000, 2097, 945, 1973, 1090, 2285, 2272, 1045, 848, 3194, 2113, 537, 1953, 561, 2082, 2416, 3182, 3660, 3379, 1458, 39, 2720, 2469, 811, 837, 548, 3021, 2961, 2860, 2092, 942, 2511, 1769, 168, 130, 2755, 2552, 1057, 2619, 1848, 2373, 2012, 2339, 2521, 3593, 2784, 3094, 2260, 1994, 690, 828, 1298, 3434, 2929, 2188, 1285, 3783, 3635, 971, 3334, 87, 1578, 1363, 533, 3634, 1874, 830, 3976, 1140, 789, 1483, 1966, 1626, 3552, 2968, 3301, 749, 2576] in
+let ascending = fun x y => if x < y then `Less else if (x == y) then `Equal else `Greater in
+{
+    run = fun n => array.sort ascending nums
+}
diff --git a/benches/stdlib.rs b/benches/stdlib.rs
index 21ed10a0..fc23c25a 100644
--- a/benches/stdlib.rs
+++ b/benches/stdlib.rs
@@ -6,7 +6,7 @@ use nickel_lang::cache::Cache;
 pub fn typecheck_stdlib(c: &mut Criterion) {
     let mut cache = Cache::new();
     cache.load_stdlib().unwrap();
-    let type_env = cache.mk_type_env().unwrap();
+    let type_env = cache.mk_types_env().unwrap();
     c.bench_function("typecheck stdlib", |b| {
         b.iter_batched(
             || cache.clone(),
diff --git a/doc/manual/merging.md b/doc/manual/merging.md
index 125f0e52..63975c2a 100644
--- a/doc/manual/merging.md
+++ b/doc/manual/merging.md
@@ -28,6 +28,8 @@ the associated technical document [RFC001][rfc001].
 The section describes the behavior and use-cases of merge, by considering the
 following situations:
 
+<!-- markdownlint-disable MD051 -->
+
 - [Merging two records without common fields](#simple-merge-(no-common-fields))
 - [Merging records with common fields](#recursive-merge-(with-common-fields))
 - [Merging records with metadata](#merging-record-with-metadata)
@@ -36,6 +38,8 @@ following situations:
   - [Documentation](#documentation)
 - [Recursive overriding](#recursive-overriding)
 
+<!-- markdownlint-enable MD051 -->
+
 ## Simple merge (no common fields)
 
 Merging two records with no common fields results in a record with the fields
diff --git a/flake.lock b/flake.lock
index a3e05ff5..7f218289 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
   "nodes": {
     "flake-utils": {
       "locked": {
-        "lastModified": 1656065134,
-        "narHash": "sha256-oc6E6ByIw3oJaIyc67maaFcnjYOz1mMcOtHxbEf9NwQ=",
+        "lastModified": 1659877975,
+        "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "bee6a7250dd1b01844a2de7e02e4df7d8a0a206c",
+        "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
         "type": "github"
       },
       "original": {
@@ -32,11 +32,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1655983783,
-        "narHash": "sha256-0h1FzkYWei24IdKNpCX93onkF/FMiXQG8SdEbTc0r8A=",
+        "lastModified": 1661450036,
+        "narHash": "sha256-0/9UyJLtfWqF4uvOrjFIzk8ue1YYUHa6JIhV0mALkH0=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "6141b8932a5cf376fe18fcd368cecd9ad946cb68",
+        "rev": "f3d0897be466aa09a37f6bf59e62c360c3f9a6cc",
         "type": "github"
       },
       "original": {
@@ -55,11 +55,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1656169028,
-        "narHash": "sha256-y9DRauokIeVHM7d29lwT8A+0YoGUBXV3H0VErxQeA8s=",
+        "lastModified": 1660830093,
+        "narHash": "sha256-HUhx3a82C7bgp2REdGFeHJdhEAzMGCk3V8xIvfBqg1I=",
         "owner": "cachix",
         "repo": "pre-commit-hooks.nix",
-        "rev": "db3bd555d3a3ceab208bed48f983ccaa6a71a25e",
+        "rev": "8cb8ea5f1c7bc2984f460587fddd5f2e558f6eb8",
         "type": "github"
       },
       "original": {
@@ -87,11 +87,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1656125323,
-        "narHash": "sha256-Ckez1NOVyPLOEI2zbSok6oDCm/xJxX/pietAWukEwoo=",
+        "lastModified": 1661568992,
+        "narHash": "sha256-BKoSLBBI7tXHyulp9AnB1SINU2+zzSZEt6mdhkR5uvw=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "59a3a304c987dc65d2a33de0a77a1ae6ae777cd4",
+        "rev": "790c16639c7d8da8b36d0aa6a7c1f589af8c5f68",
         "type": "github"
       },
       "original": {
diff --git a/lsp/nls/src/cache.rs b/lsp/nls/src/cache.rs
index 5a174674..93f8887c 100644
--- a/lsp/nls/src/cache.rs
+++ b/lsp/nls/src/cache.rs
@@ -14,7 +14,7 @@ pub trait CacheExt {
     fn typecheck_with_analysis(
         &mut self,
         file_id: FileId,
-        initial_env: &typecheck::Environment,
+        global_env: &typecheck::Environment,
         lin_cache: &mut HashMap<FileId, Completed>,
     ) -> Result<CacheOp<()>, CacheError<TypecheckError>>;
 }
@@ -31,11 +31,10 @@ impl CacheExt for Cache {
             Ok(self.add_string(path, source))
         }
     }
-
     fn typecheck_with_analysis<'a>(
         &mut self,
         file_id: FileId,
-        initial_env: &typecheck::Environment,
+        global_env: &typecheck::Environment,
         lin_cache: &mut HashMap<FileId, Completed>,
     ) -> Result<CacheOp<()>, CacheError<TypecheckError>> {
         if !self.terms_mut().contains_key(&file_id) {
@@ -49,8 +48,7 @@ impl CacheExt for Cache {
             Ok(CacheOp::Cached(()))
         } else if *state >= EntryState::Parsed {
             let host = AnalysisHost::new();
-            let (_, linearized) =
-                typecheck::type_check_linearize(term, initial_env.clone(), self, host)?;
+            let (_, linearized) = typecheck::type_check(term, global_env, self, host)?;
             self.update_state(file_id, EntryState::Typechecked);
             lin_cache.insert(file_id, linearized);
             Ok(CacheOp::Done(()))
diff --git a/lsp/nls/src/files.rs b/lsp/nls/src/files.rs
index e9ed6693..b1211d29 100644
--- a/lsp/nls/src/files.rs
+++ b/lsp/nls/src/files.rs
@@ -72,7 +72,7 @@ pub fn handle_save(server: &mut Server, params: DidChangeTextDocumentParams) ->
 fn typecheck(server: &mut Server, file_id: FileId) -> Result<CacheOp<()>, Vec<Diagnostic<FileId>>> {
     server
         .cache
-        .typecheck_with_analysis(file_id, &server.initial_env, &mut server.lin_cache)
+        .typecheck_with_analysis(file_id, &server.global_env, &mut server.lin_cache)
         .map_err(|error| match error {
             CacheError::Error(tc_error) => tc_error.to_diagnostic(server.cache.files_mut(), None),
             CacheError::NotParsed => unreachable!(),
diff --git a/lsp/nls/src/server.rs b/lsp/nls/src/server.rs
index f3de4d0b..9d7e7dbb 100644
--- a/lsp/nls/src/server.rs
+++ b/lsp/nls/src/server.rs
@@ -29,7 +29,7 @@ pub struct Server {
     pub connection: Connection,
     pub cache: Cache,
     pub lin_cache: HashMap<FileId, Completed>,
-    pub initial_env: Environment,
+    pub global_env: Environment,
 }
 
 impl Server {
@@ -61,13 +61,13 @@ impl Server {
     pub fn new(connection: Connection) -> Server {
         let mut cache = Cache::new();
         cache.load_stdlib().unwrap();
-        let initial_env = cache.mk_type_env().unwrap();
+        let global_env = cache.mk_types_env().unwrap();
         let lin_cache = HashMap::new();
         Server {
             connection,
             cache,
             lin_cache,
-            initial_env,
+            global_env,
         }
     }
 
diff --git a/src/cache.rs b/src/cache.rs
index 20d6fbe2..a1316fb8 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -6,8 +6,8 @@ use crate::position::TermPos;
 use crate::stdlib as nickel_stdlib;
 use crate::term::{RichTerm, SharedTerm, Term};
 use crate::transform::import_resolution;
-use crate::typecheck::type_check;
 use crate::typecheck::{self, Wildcards};
+use crate::typecheck::{linearization::StubHost, type_check};
 use crate::types::UnboundTypeVariableError;
 use crate::{eval, parser, transform};
 use codespan::{FileId, Files};
@@ -78,7 +78,7 @@ pub struct Cache {
 
 /// wrapping eval environment with typing environment
 #[derive(Debug, Clone)]
-pub struct Envs {
+pub struct GlobalEnv {
     /// The eval environment.
     pub eval_env: eval::Environment,
     /// The typing environment, counterpart of the eval environment for typechecking. Entries are
@@ -87,9 +87,9 @@ pub struct Envs {
     pub type_env: typecheck::Environment,
 }
 
-impl Envs {
+impl GlobalEnv {
     pub fn new() -> Self {
-        Envs {
+        GlobalEnv {
             eval_env: eval::Environment::new(),
             type_env: typecheck::Environment::new(),
         }
@@ -432,7 +432,7 @@ impl Cache {
     pub fn typecheck(
         &mut self,
         file_id: FileId,
-        initial_env: &typecheck::Environment,
+        global_env: &typecheck::Environment,
     ) -> Result<CacheOp<()>, CacheError<TypecheckError>> {
         match self.terms.get(&file_id) {
             Some(CachedTerm { state, .. }) if *state >= EntryState::Typechecked => {
@@ -440,14 +440,15 @@ impl Cache {
             }
             Some(CachedTerm { term, state, .. }) if *state >= EntryState::Parsed => {
                 if *state < EntryState::Typechecking {
-                    let wildcards = type_check(term, initial_env.clone(), self)?;
+                    let (wildcards, _) =
+                        type_check(term, global_env, self, StubHost::<(), (), _>::new())?;
                     self.update_state(file_id, EntryState::Typechecking);
                     self.wildcards.insert(file_id, wildcards);
                 }
 
                 if let Some(imports) = self.imports.get(&file_id).cloned() {
                     for f in imports.into_iter() {
-                        self.typecheck(f, initial_env)?;
+                        self.typecheck(f, global_env)?;
                     }
                 }
 
@@ -506,7 +507,7 @@ impl Cache {
     /// records.
     ///
     /// Note that this requirement may be relaxed in the future by e.g. evaluating stdlib entries
-    /// before adding their fields to the initial environment.
+    /// before adding their fields to the global environment.
     ///
     /// # Preconditions
     ///
@@ -638,7 +639,7 @@ impl Cache {
     pub fn prepare(
         &mut self,
         file_id: FileId,
-        initial_env: &typecheck::Environment,
+        global_env: &typecheck::Environment,
     ) -> Result<CacheOp<()>, Error> {
         let mut result = CacheOp::Cached(());
 
@@ -655,7 +656,7 @@ impl Cache {
             result = CacheOp::Done(());
         };
 
-        let typecheck_res = self.typecheck(file_id, initial_env).map_err(|cache_err| {
+        let typecheck_res = self.typecheck(file_id, global_env).map_err(|cache_err| {
             cache_err
                 .unwrap_error("cache::prepare(): expected source to be parsed before typechecking")
         })?;
@@ -690,14 +691,14 @@ impl Cache {
     pub fn prepare_nocache(
         &mut self,
         file_id: FileId,
-        initial_env: &typecheck::Environment,
+        global_env: &typecheck::Environment,
     ) -> Result<(RichTerm, Vec<FileId>), Error> {
         let (term, errs) = self.parse_nocache(file_id)?;
         if errs.no_errors() {
             return Err(Error::ParseErrors(errs));
         }
         let (term, pending) = import_resolution::resolve_imports(term, self)?;
-        let wildcards = type_check(&term, initial_env.clone(), self)?;
+        let (wildcards, _) = type_check(&term, global_env, self, StubHost::<(), (), _>::new())?;
         let term = transform::transform(term, Some(&wildcards))
             .map_err(|err| Error::ParseErrors(err.into()))?;
         Ok((term, pending))
@@ -811,31 +812,31 @@ impl Cache {
 
     /// Typecheck the standard library. Currently only used in the test suite.
     pub fn typecheck_stdlib(&mut self) -> Result<CacheOp<()>, CacheError<TypecheckError>> {
-        // We have a small bootstraping problem: to typecheck the initial environment, we already
-        // need an initial evaluation environment, since stdlib parts may reference each other. But
+        // We have a small bootstraping problem: to typecheck the global environment, we already
+        // need a global evaluation environment, since stdlib parts may reference each other. But
         // typechecking is performed before program transformations, so this environment is not
-        // final one. We have create a temporary initial environment just for typechecking, which is dropped
+        // final one. We have create a temporary global environment just for typechecking, which is dropped
         // right after. However:
         // 1. The stdlib is meant to stay relatively light.
         // 2. Typechecking the standard library ought to occur only during development. Once the
         //    stdlib is stable, we won't have typecheck it at every execution.
-        let initial_env = self.mk_type_env().map_err(|err| match err {
+        let global_env = self.mk_types_env().map_err(|err| match err {
             CacheError::NotParsed => CacheError::NotParsed,
             CacheError::Error(_) => unreachable!(),
         })?;
-        self.typecheck_stdlib_(&initial_env)
+        self.typecheck_stdlib_(&global_env)
     }
 
     /// Typecheck the stdlib, provided the initial typing environment. Has to be public because
     /// it's used in benches. It probably does not have to be used for something else.
     pub fn typecheck_stdlib_(
         &mut self,
-        initial_env: &typecheck::Environment,
+        global_env: &typecheck::Environment,
     ) -> Result<CacheOp<()>, CacheError<TypecheckError>> {
         if let Some(ids) = self.stdlib_ids.as_ref().cloned() {
             ids.iter()
                 .try_fold(CacheOp::Cached(()), |cache_op, file_id| {
-                    match self.typecheck(*file_id, initial_env)? {
+                    match self.typecheck(*file_id, global_env)? {
                         done @ CacheOp::Done(()) => Ok(done),
                         _ => Ok(cache_op),
                     }
@@ -846,16 +847,17 @@ impl Cache {
     }
 
     /// Load, parse, and apply program transformations to the standard library. Do not typecheck
-    /// for performance reasons: this is done in the test suite. Return an initial environment
-    /// containing both the evaluation and type environments. If you only need the type environment, use
-    /// `load_stdlib` then `mk_type_env` to avoid transformations and evaluation preparation.
-    pub fn prepare_stdlib(&mut self) -> Result<Envs, Error> {
+    /// for performance reason: this is done in the test suite.
+    /// Return a global environment containing both eval and type environment. If you need only the
+    /// type environment, use `load_stdlib()` then `mk_global_type` to avoid
+    /// transformations and evaluation preparation.
+    pub fn prepare_stdlib(&mut self) -> Result<GlobalEnv, Error> {
         #[cfg(debug_assertions)]
         if self.skip_stdlib {
-            return Ok(Envs::new());
+            return Ok(GlobalEnv::new());
         }
         self.load_stdlib()?;
-        let type_env = self.mk_type_env().unwrap();
+        let type_env = self.mk_types_env().unwrap();
 
         self.stdlib_ids
             .as_ref()
@@ -868,12 +870,12 @@ impl Cache {
                     .unwrap_error("cache::prepare_stdlib(): expected standard library to be parsed")
             })?;
         let eval_env = self.mk_eval_env().unwrap();
-        Ok(Envs { eval_env, type_env })
+        Ok(GlobalEnv { eval_env, type_env })
     }
 
-    /// Generate the initial typing environment from the list of `file_ids` corresponding to the
-    /// standard library parts.
-    pub fn mk_type_env(&self) -> Result<typecheck::Environment, CacheError<Void>> {
+    /// Generate a global typing environment from the list of `file_ids` corresponding to the standard
+    /// library parts.
+    pub fn mk_types_env(&self) -> Result<typecheck::Environment, CacheError<Void>> {
         let stdlib_terms_vec =
             self.stdlib_ids
                 .as_ref()
@@ -882,15 +884,15 @@ impl Cache {
                         .iter()
                         .map(|file_id| {
                             self.get_owned(*file_id).expect(
-                                "cache::mk_type_env(): can't build environment, stdlib not parsed",
-                            )
+                            "cache::mk_global_env(): can't build environment, stdlib not parsed",
+                        )
                         })
                         .collect())
                 })?;
-        Ok(typecheck::mk_initial_env(stdlib_terms_vec).unwrap())
+        Ok(typecheck::Envs::mk_global(stdlib_terms_vec).unwrap())
     }
 
-    /// Generate the initial evaluation environment from the list of `file_ids` corresponding to the standard
+    /// Generate a global evaluation environment from the list of `file_ids` corresponding to the standard
     /// library parts.
     pub fn mk_eval_env(&self) -> Result<eval::Environment, CacheError<Void>> {
         if let Some(ids) = self.stdlib_ids.as_ref().cloned() {
@@ -899,7 +901,7 @@ impl Cache {
                 let result = eval::env_add_term(
                     &mut eval_env,
                     self.get_owned(*file_id).expect(
-                        "cache::mk_eval_env(): can't build environment, stdlib not parsed",
+                        "cache::mk_global_env(): can't build environment, stdlib not parsed",
                     ),
                 );
                 if let Err(eval::EnvBuildError::NotARecord(rt)) = result {
diff --git a/src/error.rs b/src/error.rs
index 24afc4a3..23f6616c 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -52,8 +52,6 @@ pub enum EvalError {
         /* position of the original unevaluated expression */ TermPos,
         /* evaluated expression */ RichTerm,
     ),
-    /// Tried to evaluate a term which wasn't parsed correctly.
-    ParseError(ParseError),
     /// A term which is not a function has been applied to an argument.
     NotAFunc(
         /* term */ RichTerm,
@@ -1038,7 +1036,6 @@ impl ToDiagnostic<FileId> for EvalError {
                     .with_labels(labels)
                     .with_notes(vec![msg.clone()])]
             }
-            EvalError::ParseError(parse_error) => parse_error.to_diagnostic(files, contract_id),
             EvalError::NotAFunc(t, arg, pos_opt) => vec![Diagnostic::error()
                 .with_message("not a function")
                 .with_labels(vec![
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index bce1a65c..b0b37375 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -9,7 +9,7 @@
 //! - The term being currently evaluated
 //! - The main stack, storing arguments, thunks and pending computations
 //! - A pair of [environment][Environment], mapping identifiers to [closures][Closure]:
-//!     * The initial environment contains builtin functions accessible from anywhere, and alive
+//!     * The global environment contains builtin functions accessible from anywhere, and alive
 //!     during the whole evaluation
 //!     * The local environment contains the variables in scope of the current term and is subject
 //!     to garbage collection (currently reference counting based)
@@ -182,42 +182,42 @@ pub fn env_add(env: &mut Environment, id: Ident, rt: RichTerm, local_env: Enviro
 /// environment and drops the final environment.
 pub fn eval<R>(
     t0: RichTerm,
-    initial_env: &Environment,
+    global_env: &Environment,
     resolver: &mut R,
 ) -> Result<RichTerm, EvalError>
 where
     R: ImportResolver,
 {
-    eval_closure(Closure::atomic_closure(t0), initial_env, resolver, true).map(|(term, _)| term)
+    eval_closure(Closure::atomic_closure(t0), global_env, resolver, true).map(|(term, _)| term)
 }
 
 /// Fully evaluate a Nickel term: the result is not a WHNF but to a value with all variables substituted.
 pub fn eval_full<R>(
     t0: RichTerm,
-    initial_env: &Environment,
+    global_env: &Environment,
     resolver: &mut R,
 ) -> Result<RichTerm, EvalError>
 where
     R: ImportResolver,
 {
-    eval_deep_closure(t0, initial_env, resolver).map(|(term, env)| subst(term, initial_env, &env))
+    eval_deep_closure(t0, global_env, resolver).map(|(term, env)| subst(term, global_env, &env))
 }
 
 /// Fully evaluates a Nickel term like `eval_full`, but does not substitute all variables.
 pub fn eval_deep<R>(
     t0: RichTerm,
-    initial_env: &Environment,
+    global_env: &Environment,
     resolver: &mut R,
 ) -> Result<RichTerm, EvalError>
 where
     R: ImportResolver,
 {
-    eval_deep_closure(t0, initial_env, resolver).map(|(term, _)| term)
+    eval_deep_closure(t0, global_env, resolver).map(|(term, _)| term)
 }
 
 fn eval_deep_closure<R>(
     t0: RichTerm,
-    initial_env: &Environment,
+    global_env: &Environment,
     resolver: &mut R,
 ) -> Result<(RichTerm, Environment), EvalError>
 where
@@ -235,12 +235,7 @@ where
             Term::Var(var)
         ),
     );
-    eval_closure(
-        Closure::atomic_closure(wrapper),
-        initial_env,
-        resolver,
-        true,
-    )
+    eval_closure(Closure::atomic_closure(wrapper), global_env, resolver, true)
 }
 
 /// Evaluate a Nickel Term, stopping when a meta value is encountered at the top-level without
@@ -250,19 +245,19 @@ where
 /// Used to query the metadata of a value.
 pub fn eval_meta<R>(
     t: RichTerm,
-    initial_env: &Environment,
+    global_env: &Environment,
     resolver: &mut R,
 ) -> Result<RichTerm, EvalError>
 where
     R: ImportResolver,
 {
-    let (mut rt, env) = eval_closure(Closure::atomic_closure(t), initial_env, resolver, false)?;
+    let (mut rt, env) = eval_closure(Closure::atomic_closure(t), global_env, resolver, false)?;
 
     if let Term::MetaValue(ref mut meta) = *SharedTerm::make_mut(&mut rt.term) {
         if let Some(t) = meta.value.take() {
             let (evaluated, env) =
-                eval_closure(Closure { body: t, env }, initial_env, resolver, true)?;
-            let substituted = subst(evaluated, initial_env, &env);
+                eval_closure(Closure { body: t, env }, global_env, resolver, true)?;
+            let substituted = subst(evaluated, global_env, &env);
 
             meta.value = Some(substituted);
         }
@@ -280,8 +275,8 @@ where
 /// # Arguments
 ///
 /// - `clos`: the closure to evaluate
-/// - `initial_env`: the initial environment containing the stdlib items.
-///   Accessible from anywhere in the program.
+/// - `global_env`: the global environment containing the builtin functions of the language. Accessible from anywhere in the
+/// program.
 /// - `resolver`: the interface to fetch imports.
 /// - `enriched_strict`: if evaluation is strict with respect to enriched values (metavalues).
 ///   Standard evaluation should be strict, but set to false when extracting the metadata of value.
@@ -293,7 +288,7 @@ where
 ///  - the evaluated term with its final environment
 pub fn eval_closure<R>(
     mut clos: Closure,
-    initial_env: &Environment,
+    global_env: &Environment,
     resolver: &mut R,
     mut enriched_strict: bool,
 ) -> Result<(RichTerm, Environment), EvalError>
@@ -320,7 +315,7 @@ where
             Term::Var(x) => {
                 let mut thunk = env
                     .get(x)
-                    .or_else(|| initial_env.get(x))
+                    .or_else(|| global_env.get(x))
                     .ok_or_else(|| EvalError::UnboundIdentifier(x.clone(), pos))?;
                 std::mem::drop(env); // thunk may be a 1RC pointer
 
@@ -632,9 +627,6 @@ where
                     env: local_env,
                 }
             }
-            Term::ParseError(parse_error) => {
-                return Err(EvalError::ParseError(parse_error.clone()));
-            }
             // Continuation of operations and thunk update
             _ if stack.is_top_thunk() || stack.is_top_cont() => {
                 clos = Closure {
@@ -691,20 +683,20 @@ fn update_thunks(stack: &mut Stack, closure: &Closure) {
 }
 
 /// Recursively substitute each variable occurrence of a term for its value in the environment.
-pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> RichTerm {
+pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichTerm {
     let RichTerm { term, pos } = rt;
 
     match term.into_owned() {
         Term::Var(id) => env
             .get(&id)
-            .or_else(|| initial_env.get(&id))
+            .or_else(|| global_env.get(&id))
             .map(|thunk| {
                 let closure = thunk.get_owned();
-                subst(closure.body, initial_env, &closure.env)
+                subst(closure.body, global_env, &closure.env)
             })
             .unwrap_or_else(|| RichTerm::new(Term::Var(id), pos)),
         v @ Term::Null
-        | v @ Term::ParseError(_)
+        | v @ Term::ParseError
         | v @ Term::Bool(_)
         | v @ Term::Num(_)
         | v @ Term::Str(_)
@@ -717,56 +709,56 @@ pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> Rich
         | v @ Term::Import(_)
         | v @ Term::ResolvedImport(_) => RichTerm::new(v, pos),
         Term::Let(id, t1, t2, attrs) => {
-            let t1 = subst(t1, initial_env, env);
-            let t2 = subst(t2, initial_env, env);
+            let t1 = subst(t1, global_env, env);
+            let t2 = subst(t2, global_env, env);
 
             RichTerm::new(Term::Let(id, t1, t2, attrs), 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, initial_env, env);
-            let t2 = subst(t2, initial_env, env);
+            let t1 = subst(t1, global_env, env);
+            let t2 = subst(t2, global_env, env);
 
             RichTerm::new(Term::App(t1, t2), pos)
         }
         Term::Switch(t, cases, default) => {
             let default =
-                default.map(|d| subst(d, initial_env, env));
+                default.map(|d| subst(d, global_env, env));
             let cases = cases
                 .into_iter()
                 .map(|(id, t)| {
                     (
                         id,
-                        subst(t, initial_env, env),
+                        subst(t, global_env, env),
                     )
                 })
                 .collect();
-            let t = subst(t, initial_env, env);
+            let t = subst(t, global_env, env);
 
             RichTerm::new(Term::Switch(t, cases, default), pos)
         }
         Term::Op1(op, t) => {
-            let t = subst(t, initial_env, env);
+            let t = subst(t, global_env, env);
 
             RichTerm::new(Term::Op1(op, t), pos)
         }
         Term::Op2(op, t1, t2) => {
-            let t1 = subst(t1, initial_env, env);
-            let t2 = subst(t2, initial_env, env);
+            let t1 = subst(t1, global_env, env);
+            let t2 = subst(t2, global_env, env);
 
             RichTerm::new(Term::Op2(op, t1, t2), pos)
         }
         Term::OpN(op, ts) => {
             let ts = ts
                 .into_iter()
-                .map(|t| subst(t, initial_env, env))
+                .map(|t| subst(t, global_env, env))
                 .collect();
 
             RichTerm::new(Term::OpN(op, ts), pos)
         }
         Term::Sealed(i, t) => {
-            let t = subst(t, initial_env, env);
+            let t = subst(t, global_env, env);
 
             RichTerm::new(Term::Sealed(i, t), pos)
         }
@@ -776,7 +768,7 @@ pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> Rich
                 .map(|(id, t)| {
                     (
                         id,
-                        subst(t, initial_env, env),
+                        subst(t, global_env, env),
                     )
                 })
                 .collect();
@@ -789,7 +781,7 @@ pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> Rich
                 .map(|(id, t)| {
                     (
                         id,
-                        subst(t, initial_env, env),
+                        subst(t, global_env, env),
                     )
                 })
                 .collect();
@@ -798,8 +790,8 @@ pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> Rich
                 .into_iter()
                 .map(|(id_t, t)| {
                     (
-                        subst(id_t, initial_env, env),
-                        subst(t, initial_env, env),
+                        subst(id_t, global_env, env),
+                        subst(t, global_env, env),
                     )
                 })
                 .collect();
@@ -809,7 +801,7 @@ pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> Rich
         Term::Array(ts, attrs) => {
             let ts = ts
                 .into_iter()
-                .map(|t| subst(t, initial_env, env))
+                .map(|t| subst(t, global_env, env))
                 .collect();
 
             RichTerm::new(Term::Array(ts, attrs), pos)
@@ -820,7 +812,7 @@ pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> Rich
                 .map(|chunk| match chunk {
                     chunk @ StrChunk::Literal(_) => chunk,
                     StrChunk::Expr(t, indent) => StrChunk::Expr(
-                        subst(t, initial_env, env),
+                        subst(t, global_env, env),
                         indent,
                     ),
                 })
@@ -840,7 +832,7 @@ pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> Rich
             //         let types = match ctr.types {
             //             Types(AbsType::Flat(t)) => Types(AbsType::Flat(subst(
             //                 t,
-            //                 initial_env,
+            //                 global_env,
             //                 env,
             //                 Cow::Borrowed(bound.as_ref()),
             //             ))),
@@ -855,7 +847,7 @@ pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> Rich
             //     let types = match ctr.types {
             //         Types(AbsType::Flat(t)) => Types(AbsType::Flat(subst(
             //             t,
-            //             initial_env,
+            //             global_env,
             //             env,
             //             Cow::Borrowed(bound.as_ref()),
             //         ))),
@@ -865,7 +857,7 @@ pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> Rich
             //     Contract { types, ..ctr }
             // });
 
-            let value = meta.value.map(|t| subst(t, initial_env, env));
+            let value = meta.value.map(|t| subst(t, global_env, env));
 
             let meta = MetaValue {
                 doc: meta.doc,
diff --git a/src/eval/operation.rs b/src/eval/operation.rs
index 6624cb7c..8fd7544b 100644
--- a/src/eval/operation.rs
+++ b/src/eval/operation.rs
@@ -2011,13 +2011,13 @@ fn process_binary_operation(
 
             if let Term::Enum(ref id) = t1.as_ref() {
                 // Serialization needs all variables term to be fully substituted
-                let initial_env = Environment::new();
+                let global_env = Environment::new();
                 let rt2 = subst(
                     RichTerm {
                         term: t2,
                         pos: pos2,
                     },
-                    &initial_env,
+                    &global_env,
                     &env2,
                 );
 
diff --git a/src/parser/grammar.lalrpop b/src/parser/grammar.lalrpop
index 4ed7552a..835236c9 100644
--- a/src/parser/grammar.lalrpop
+++ b/src/parser/grammar.lalrpop
@@ -219,11 +219,9 @@ UniTerm: UniTerm = {
         UniTerm::from(mk_app!(Term::Op1(UnaryOp::Ite(), cond), t1, t2)),
     <l: @L> <t: !> <r: @R> => {
         let pos = mk_pos(src_id, l, r);
-        errors.push(t.clone());
+        errors.push(t);
 
-        UniTerm::from(RichTerm::new(Term::ParseError(
-            crate::error::ParseError::from_lalrpop(t.error, src_id)
-        ), pos))
+        UniTerm::from(RichTerm::new(Term::ParseError, pos))
     },
 };
 
diff --git a/src/pretty.rs b/src/pretty.rs
index f6474d0f..7063de95 100644
--- a/src/pretty.rs
+++ b/src/pretty.rs
@@ -684,7 +684,7 @@ where
                 .append(allocator.space())
                 .append(allocator.as_string(f.to_string_lossy()).double_quotes()),
             ResolvedImport(id) => allocator.text(format!("import <file_id: {:?}>", id)),
-            ParseError(_) => allocator
+            ParseError => allocator
                 .text("#<PARSE ERROR!>")
                 .append(allocator.hardline()),
         }
diff --git a/src/program.rs b/src/program.rs
index 21b9c1f2..d0181656 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -19,7 +19,7 @@
 //! text, in the Nickel executable. The embedding is done by way of the [crate::stdlib], which
 //! exposes the standard library files as strings. The embedded strings are then parsed by the
 //! functions in [`crate::cache`] (see [`crate::cache::Cache::mk_eval_env`]).
-//! Each such value is added to the initial environment before the evaluation of the program.
+//! Each such value is added to the global environment before the evaluation of the program.
 use crate::cache::*;
 use crate::error::{Error, ToDiagnostic};
 use crate::identifier::Ident;
@@ -68,50 +68,50 @@ impl Program {
         Ok(Program { main_id, cache })
     }
 
-    /// Retrieve the parsed term and typecheck it, and generate a fresh initial environment. Return
+    /// Retrieve the parsed term and typecheck it, and generate a fresh global environment. Return
     /// both.
     fn prepare_eval(&mut self) -> Result<(RichTerm, eval::Environment), Error> {
-        let Envs { eval_env, type_env } = self.cache.prepare_stdlib()?;
+        let GlobalEnv { eval_env, type_env } = self.cache.prepare_stdlib()?;
         self.cache.prepare(self.main_id, &type_env)?;
         Ok((self.cache.get(self.main_id).unwrap(), eval_env))
     }
 
     /// Parse if necessary, typecheck and then evaluate the program.
     pub fn eval(&mut self) -> Result<RichTerm, Error> {
-        let (t, initial_env) = self.prepare_eval()?;
-        eval::eval(t, &initial_env, &mut self.cache).map_err(|e| e.into())
+        let (t, global_env) = self.prepare_eval()?;
+        eval::eval(t, &global_env, &mut self.cache).map_err(|e| e.into())
     }
 
     /// Same as `eval`, but proceeds to a full evaluation.
     pub fn eval_full(&mut self) -> Result<RichTerm, Error> {
-        let (t, initial_env) = self.prepare_eval()?;
-        eval::eval_full(t, &initial_env, &mut self.cache).map_err(|e| e.into())
+        let (t, global_env) = self.prepare_eval()?;
+        eval::eval_full(t, &global_env, &mut self.cache).map_err(|e| e.into())
     }
 
     /// Same as `eval_full`, but does not substitute all variables.
     pub fn eval_deep(&mut self) -> Result<RichTerm, Error> {
-        let (t, initial_env) = self.prepare_eval()?;
-        eval::eval_deep(t, &initial_env, &mut self.cache).map_err(|e| e.into())
+        let (t, global_env) = self.prepare_eval()?;
+        eval::eval_deep(t, &global_env, &mut self.cache).map_err(|e| e.into())
     }
 
     /// Wrapper for [`query`].
     pub fn query(&mut self, path: Option<String>) -> Result<Term, Error> {
-        let initial_env = self.cache.prepare_stdlib()?;
-        query(&mut self.cache, self.main_id, &initial_env, path)
+        let global_env = self.cache.prepare_stdlib()?;
+        query(&mut self.cache, self.main_id, &global_env, path)
     }
 
     /// Load, parse, and typecheck the program and the standard library, if not already done.
     pub fn typecheck(&mut self) -> Result<(), Error> {
         self.cache.parse(self.main_id)?;
         self.cache.load_stdlib()?;
-        let initial_env = self.cache.mk_type_env().expect("program::typecheck(): stdlib has been loaded but was not found in cache on mk_types_env()");
+        let global_env = self.cache.mk_types_env().expect("program::typecheck(): stdlib has been loaded but was not found in cache on mk_types_env()");
         self.cache
             .resolve_imports(self.main_id)
             .map_err(|cache_err| {
                 cache_err.unwrap_error("program::typecheck(): expected source to be parsed")
             })?;
         self.cache
-            .typecheck(self.main_id, &initial_env)
+            .typecheck(self.main_id, &global_env)
             .map_err(|cache_err| {
                 cache_err.unwrap_error("program::typecheck(): expected source to be parsed")
             })?;
@@ -178,10 +178,10 @@ impl Program {
 pub fn query(
     cache: &mut Cache,
     file_id: FileId,
-    initial_env: &Envs,
+    global_env: &GlobalEnv,
     path: Option<String>,
 ) -> Result<Term, Error> {
-    cache.prepare(file_id, &initial_env.type_env)?;
+    cache.prepare(file_id, &global_env.type_env)?;
 
     let t = if let Some(p) = path {
         // Parsing `y.path`. We `seq` it to force the evaluation of the underlying value,
@@ -205,7 +205,7 @@ pub fn query(
         cache.get_owned(file_id).unwrap()
     };
 
-    Ok(eval::eval_meta(t, &initial_env.eval_env, cache)?.into())
+    Ok(eval::eval_meta(t, &global_env.eval_env, cache)?.into())
 }
 
 /// Pretty-print an error.
diff --git a/src/repl/mod.rs b/src/repl/mod.rs
index 6ba6c85d..e9e02d62 100644
--- a/src/repl/mod.rs
+++ b/src/repl/mod.rs
@@ -6,7 +6,7 @@
 //! Dually, the frontend is the user-facing part, which may be a CLI, a web application, a
 //! jupyter-kernel (which is not exactly user-facing, but still manages input/output and
 //! formatting), etc.
-use crate::cache::{Cache, Envs};
+use crate::cache::{Cache, GlobalEnv};
 use crate::error::{Error, EvalError, IOError, ParseError, ParseErrors, ReplError};
 use crate::identifier::Ident;
 use crate::parser::{grammar, lexer, ExtendedTerm};
@@ -71,12 +71,12 @@ pub struct ReplImpl {
     cache: Cache,
     /// The parser, supporting toplevel let declaration.
     parser: grammar::ExtendedTermParser,
-    /// The current environment. Contain the initial environment with the stdlib, plus toplevel
+    /// The global environment. Contain the global environment with the stdlib, plus toplevel
     /// declarations and loadings made inside the REPL.
-    env: Envs,
+    env: GlobalEnv,
     /// The initial type environment, without the toplevel declarations made inside the REPL. Used
     /// to typecheck imports in a fresh environment.
-    initial_type_env: typecheck::Environment,
+    init_type_env: typecheck::Environment,
 }
 
 impl ReplImpl {
@@ -85,8 +85,8 @@ impl ReplImpl {
         ReplImpl {
             cache: Cache::new(),
             parser: grammar::ExtendedTermParser::new(),
-            env: Envs::new(),
-            initial_type_env: typecheck::Environment::new(),
+            env: GlobalEnv::new(),
+            init_type_env: typecheck::Environment::new(),
         }
     }
 
@@ -94,7 +94,7 @@ impl ReplImpl {
     /// typing environment.
     pub fn load_stdlib(&mut self) -> Result<(), Error> {
         self.env = self.cache.prepare_stdlib()?;
-        self.initial_type_env = self.env.type_env.clone();
+        self.init_type_env = self.env.type_env.clone();
         Ok(())
     }
 
@@ -135,16 +135,16 @@ impl ReplImpl {
             }
 
             let wildcards =
-                typecheck::type_check(&t, repl_impl.env.type_env.clone(), &repl_impl.cache)?;
+                typecheck::type_check_in_env(&t, &repl_impl.env.type_env, &repl_impl.cache)?;
 
             if let Some(id) = id {
-                typecheck::env_add(&mut repl_impl.env.type_env, id, &t, &repl_impl.cache);
+                typecheck::Envs::env_add(&mut repl_impl.env.type_env, id, &t, &repl_impl.cache);
             }
 
             for id in &pending {
                 repl_impl
                     .cache
-                    .typecheck(*id, &repl_impl.initial_type_env)
+                    .typecheck(*id, &repl_impl.init_type_env)
                     .map_err(|cache_err| {
                         cache_err.unwrap_error("repl::eval_(): expected imports to be parsed")
                     })?;
@@ -213,7 +213,7 @@ impl Repl for ReplImpl {
         for id in &pending {
             self.cache.resolve_imports(*id).unwrap();
         }
-        typecheck::env_add_term(&mut self.env.type_env, &term, &self.cache).unwrap();
+        typecheck::Envs::env_add_term(&mut self.env.type_env, &term, &self.cache).unwrap();
         eval::env_add_term(&mut self.env.eval_env, term.clone()).unwrap();
 
         Ok(term)
@@ -227,7 +227,7 @@ impl Repl for ReplImpl {
         for id in &pending {
             self.cache.resolve_imports(*id).unwrap();
         }
-        let wildcards = typecheck::type_check(&term, self.env.type_env.clone(), &self.cache)?;
+        let wildcards = typecheck::type_check_in_env(&term, &self.env.type_env, &self.cache)?;
         // Substitute the wildcard types for their inferred types
         // We need to `traverse` the term, in case the type depends on inner terms that also contain wildcards
         let term = term
@@ -242,10 +242,12 @@ impl Repl for ReplImpl {
             )
             .unwrap();
 
-        Ok(
-            typecheck::apparent_type(term.as_ref(), Some(&self.env.type_env), Some(&self.cache))
-                .into(),
+        Ok(typecheck::apparent_type(
+            term.as_ref(),
+            Some(&typecheck::Envs::from_global(&self.env.type_env)),
+            Some(&self.cache),
         )
+        .into())
     }
 
     fn query(&mut self, exp: &str) -> Result<Term, Error> {
diff --git a/src/term.rs b/src/term.rs
index a563dfe7..5988522d 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -17,7 +17,6 @@
 //! the term level, and together with [crate::eval::merge], they allow for flexible and modular
 //! definitions of contracts, record and metadata all together.
 use crate::destruct::Destruct;
-use crate::error::ParseError;
 use crate::identifier::Ident;
 use crate::label::Label;
 use crate::match_sharedterm;
@@ -158,7 +157,7 @@ pub enum Term {
     #[serde(skip)]
     ResolvedImport(FileId),
     #[serde(skip)]
-    ParseError(ParseError),
+    ParseError,
 }
 
 pub type SealingKey = i32;
@@ -362,7 +361,7 @@ impl Term {
     {
         use self::Term::*;
         match self {
-            Null | ParseError(_) => (),
+            Null | ParseError => (),
             Switch(ref mut t, ref mut cases, ref mut def) => {
                 cases.iter_mut().for_each(|c| {
                     let (_, t) = c;
@@ -449,7 +448,7 @@ impl Term {
             | Term::Import(_)
             | Term::ResolvedImport(_)
             | Term::StrChunks(_)
-            | Term::ParseError(_) => None,
+            | Term::ParseError => None,
         }
         .map(String::from)
     }
@@ -513,7 +512,7 @@ impl Term {
                 format!("<{}{}={}>", content, value_label, value)
             }
             Term::Var(id) => id.to_string(),
-            Term::ParseError(_) => String::from("<parse error>"),
+            Term::ParseError => String::from("<parse error>"),
             Term::Let(..)
             | Term::LetPattern(..)
             | Term::App(_, _)
@@ -581,7 +580,7 @@ impl Term {
             | Term::ResolvedImport(_)
             | Term::StrChunks(_)
             | Term::RecRecord(..)
-            | Term::ParseError(_) => false,
+            | Term::ParseError => false,
         }
     }
 
@@ -621,7 +620,7 @@ impl Term {
             | Term::ResolvedImport(_)
             | Term::StrChunks(_)
             | Term::RecRecord(..)
-            | Term::ParseError(_) => false,
+            | Term::ParseError => false,
         }
     }
 
@@ -653,7 +652,7 @@ impl Term {
             | Term::MetaValue(..)
             | Term::Import(..)
             | Term::ResolvedImport(..)
-            | Term::ParseError(_) => false,
+            | Term::ParseError => false,
         }
     }
 }
diff --git a/src/transform/free_vars.rs b/src/transform/free_vars.rs
index c3626de3..9ad53263 100644
--- a/src/transform/free_vars.rs
+++ b/src/transform/free_vars.rs
@@ -24,7 +24,7 @@ fn collect_free_vars(rt: &mut RichTerm, free_vars: &mut HashSet<Ident>) {
         Term::Var(id) => {
             free_vars.insert(id.clone());
         }
-        Term::ParseError(_)
+        Term::ParseError
         | Term::Null
         | Term::Bool(_)
         | Term::Num(_)
diff --git a/src/typecheck/mod.rs b/src/typecheck/mod.rs
index 738c1f7d..4baa364b 100644
--- a/src/typecheck/mod.rs
+++ b/src/typecheck/mod.rs
@@ -75,59 +75,101 @@ pub type Environment = GenericEnvironment<Ident, TypeWrapper>;
 /// Mapping from wildcard ID to inferred type
 pub type Wildcards = Vec<Types>;
 
+/// A structure holding the two typing environments, the global and the local.
+///
+/// The global typing environment is constructed from the global term environment (see
+/// [`crate::eval::eval`]) which holds the Nickel builtin functions. It is a read-only shared
+/// environment used to retrieve the type of such functions.
+#[derive(Debug, PartialEq, Clone)]
+pub struct Envs<'a> {
+    global: &'a Environment,
+    local: Environment,
+}
+
 #[derive(Clone, Debug)]
 pub enum EnvBuildError {
     NotARecord(RichTerm),
 }
 
-/// Populate the initial typing environment from a `Vec` of parsed files.
-pub fn mk_initial_env(initial_env: Vec<RichTerm>) -> Result<Environment, EnvBuildError> {
-    Ok(initial_env
-        .iter()
-        .map(|rt| {
-            if let Term::RecRecord(rec, ..) = rt.as_ref() {
-                Ok(rec
-                    .iter()
-                    .map(|(id, rt)| (id.clone(), infer_record_type(rt.as_ref()))))
-            } else {
-                Err(EnvBuildError::NotARecord(rt.clone()))
-            }
-        })
-        .collect::<Result<Vec<_>, EnvBuildError>>()?
-        .into_iter()
-        .flatten()
-        .collect())
-}
+impl<'a> Envs<'a> {
+    /// Create an `Envs` value with an empty local environment from a global environment.
+    pub fn from_global(global: &'a Environment) -> Self {
+        Envs {
+            global,
+            local: Environment::new(),
+        }
+    }
 
-/// Add the bindings of a record to a typing environment. Ignore fields whose name are defined
-/// through interpolation.
-//TODO: support the case of a record with a type annotation.
-pub fn env_add_term(
-    env: &mut Environment,
-    rt: &RichTerm,
-    resolver: &dyn ImportResolver,
-) -> Result<(), EnvBuildError> {
-    let RichTerm { term, pos } = rt;
-
-    match term.as_ref() {
-        Term::Record(bindings, _) | Term::RecRecord(bindings, ..) => {
-            for (id, t) in bindings {
-                let tyw: TypeWrapper = apparent_type(t.as_ref(), Some(env), Some(resolver)).into();
-                env.insert(id.clone(), tyw);
-            }
+    /// Similar to [`Envs::from_global`], but use another `Envs` to provide the global environment.
+    pub fn from_envs(envs: &'a Envs) -> Self {
+        Envs {
+            global: envs.global,
+            local: Environment::new(),
+        }
+    }
 
-            Ok(())
+    /// Populate a new global typing environment from a `Vec` of parsed files.
+    pub fn mk_global(envs: Vec<RichTerm>) -> Result<Environment, EnvBuildError> {
+        Ok(envs
+            .iter()
+            .map(|rt| {
+                if let Term::RecRecord(rec, ..) = rt.as_ref() {
+                    Ok(rec
+                        .iter()
+                        .map(|(id, rt)| (id.clone(), infer_record_type(rt.as_ref()))))
+                } else {
+                    Err(EnvBuildError::NotARecord(rt.clone()))
+                }
+            })
+            .collect::<Result<Vec<_>, EnvBuildError>>()?
+            .into_iter()
+            .flatten()
+            .collect())
+    }
+
+    /// Add the bindings of a record to a typing environment. Ignore fields whose name are defined
+    /// through interpolation.
+    //TODO: support the case of a record with a type annotation.
+    pub fn env_add_term(
+        env: &mut Environment,
+        rt: &RichTerm,
+        resolver: &dyn ImportResolver,
+    ) -> Result<(), EnvBuildError> {
+        let RichTerm { term, pos } = rt;
+
+        match term.as_ref() {
+            Term::Record(bindings, _) | Term::RecRecord(bindings, ..) => {
+                for (id, t) in bindings {
+                    let tyw: TypeWrapper =
+                        apparent_type(t.as_ref(), Some(&Envs::from_global(env)), Some(resolver))
+                            .into();
+                    env.insert(id.clone(), tyw);
+                }
+
+                Ok(())
+            }
+            t => Err(EnvBuildError::NotARecord(RichTerm::new(t.clone(), *pos))),
         }
-        t => Err(EnvBuildError::NotARecord(RichTerm::new(t.clone(), *pos))),
     }
-}
 
-/// Bind one term in a typing environment.
-pub fn env_add(env: &mut Environment, id: Ident, rt: &RichTerm, resolver: &dyn ImportResolver) {
-    env.insert(
-        id,
-        apparent_type(rt.as_ref(), Some(env), Some(resolver)).into(),
-    );
+    /// Bind one term in a typing environment.
+    pub fn env_add(env: &mut Environment, id: Ident, rt: &RichTerm, resolver: &dyn ImportResolver) {
+        env.insert(
+            id,
+            apparent_type(rt.as_ref(), Some(&Envs::from_global(env)), Some(resolver)).into(),
+        );
+    }
+
+    /// Fetch a binding from the environment. Try first in the local environment, and then in the
+    /// global.
+    pub fn get(&self, ident: &Ident) -> Option<TypeWrapper> {
+        self.local.get(ident).or_else(|| self.global.get(ident))
+    }
+
+    /// Wrapper to insert a new binding in the local environment.
+    pub fn insert(&mut self, ident: Ident, tyw: TypeWrapper) {
+        self.local.insert(ident, tyw);
+    }
 }
 
 /// The shared state of unification.
@@ -149,30 +191,16 @@ pub struct State<'a> {
 
 /// Typecheck a term.
 ///
-/// Return the inferred type in case of success. This is just a wrapper that calls
-/// `type_check_linearize` with a blanket implementation for the linearizer.
+/// Return the inferred type in case of success. This is just a wrapper that calls `type_check_`
+/// with a fresh unification variable as goal.
 ///
 /// Note that this function doesn't recursively typecheck imports (anymore), but just the current
 /// file. It however still needs the resolver to get the apparent type of imports.
 ///
 /// Return the type inferred for type wildcards.
-pub fn type_check(
-    t: &RichTerm,
-    initial_env: Environment,
-    resolver: &impl ImportResolver,
-) -> Result<Wildcards, TypecheckError> {
-    type_check_linearize(t, initial_env, resolver, StubHost::<(), (), _>::new())
-        .map(|(wildcards, _)| wildcards)
-}
-
-/// Typecheck a term and build its linearization. A linearization is a sequential data structure
-/// that holds additional information (compared to the AST), such as types of subterms, variable
-/// usages, etc.
-///
-/// Linearization is solely used by the LSP server.
-pub fn type_check_linearize<LL>(
+pub fn type_check<LL>(
     t: &RichTerm,
-    initial_env: Environment,
+    global_env: &Environment,
     resolver: &impl ImportResolver,
     mut linearizer: LL,
 ) -> Result<(Wildcards, LL::Completed), TypecheckError>
@@ -194,7 +222,7 @@ where
 
         walk(
             &mut state,
-            initial_env,
+            Envs::from_global(global_env),
             &mut building,
             linearizer.scope(),
             t,
@@ -207,11 +235,48 @@ where
     Ok((result, lin))
 }
 
+/// Typecheck a term using the given global typing environment. Same as [`type_check`], but it
+/// directly takes a global typing environment, instead of building one from a term environment as
+/// `type_check` does.
+///
+/// This function is used to typecheck an import in a clean environment, when we don't have access
+/// to the original term environment anymore, and hence cannot call `type_check` directly, but we
+/// already have built a global typing environment.
+///
+/// Return the type inferred for type wildcards. This is just a wrapper that calls `type_check_`
+/// with a fresh unification variable as goal.
+pub fn type_check_in_env(
+    t: &RichTerm,
+    global: &Environment,
+    resolver: &dyn ImportResolver,
+) -> Result<Wildcards, TypecheckError> {
+    let mut table = UnifTable::new();
+    let mut wildcard_vars = Vec::new();
+
+    let mut state = State {
+        resolver,
+        table: &mut table,
+        constr: &mut RowConstr::new(),
+        names: &mut HashMap::new(),
+        wildcard_vars: &mut wildcard_vars,
+    };
+
+    walk(
+        &mut state,
+        Envs::from_global(global),
+        &mut Linearization::new(()),
+        StubHost::<()>::new(),
+        t,
+    )?;
+
+    Ok(wildcard_vars_to_type(wildcard_vars, &table))
+}
+
 /// Walk the AST of a term looking for statically typed block to check. Fill the linearization
 /// alongside and store the apparent type of variable inside the typing environment.
 fn walk<L: Linearizer>(
     state: &mut State,
-    mut env: Environment,
+    mut envs: Envs,
     lin: &mut Linearization<L::Building>,
     mut linearizer: L,
     rt: &RichTerm,
@@ -221,11 +286,11 @@ fn walk<L: Linearizer>(
         lin,
         t,
         *pos,
-        apparent_type(t, Some(&env), Some(state.resolver)).into(),
+        apparent_type(t, Some(&envs), Some(state.resolver)).into(),
     );
 
     match t.as_ref() {
-        Term::ParseError(_)
+        Term::ParseError
         | Term::Null
         | Term::Bool(_)
         | Term::Num(_)
@@ -237,7 +302,7 @@ fn walk<L: Linearizer>(
         // caller.
         | Term::Import(_)
         | Term::ResolvedImport(_) => Ok(()),
-        Term::Var(x) => env
+        Term::Var(x) => envs
             .get(x)
             .ok_or_else(|| TypecheckError::UnboundIdentifier(x.clone(), *pos))
             .map(|_| ()),
@@ -248,78 +313,78 @@ fn walk<L: Linearizer>(
                     match chunk {
                         StrChunk::Literal(_) => Ok(()),
                         StrChunk::Expr(t, _) => {
-                            walk(state, env.clone(), lin, linearizer.scope(), t)
+                            walk(state, envs.clone(), lin, linearizer.scope(), t)
                         }
                     }
                 })
         }
         Term::Fun(id, t) => {
             // The parameter of an un-annotated function is assigned the type `Dyn`.
-            env.insert(id.clone(), mk_typewrapper::dynamic());
-            walk(state, env, lin, linearizer, t)
+            envs.insert(id.clone(), mk_typewrapper::dynamic());
+            walk(state, envs, lin, linearizer, t)
         }
         Term::FunPattern(id, pat, t) => {
             if let Some(id) = id {
-                env.insert(id.clone(), binding_type(state, t.as_ref(), &env, false));
+                envs.insert(id.clone(), binding_type(state, t.as_ref(), &envs, false));
             }
 
-            inject_pat_vars(pat, &mut env);
-            walk(state, env, lin, linearizer, t)
+            inject_pat_vars(pat, &mut envs);
+            walk(state, envs, lin, linearizer, t)
         }
         Term::Array(terms, _) => terms
             .iter()
             .try_for_each(|t| -> Result<(), TypecheckError> {
-                walk(state, env.clone(), lin, linearizer.scope(), t)
+                walk(state, envs.clone(), lin, linearizer.scope(), t)
             }),
         Term::Let(x, re, rt, attrs) => {
-            let ty_let = binding_type(state, re.as_ref(), &env, false);
+            let ty_let = binding_type(state, re.as_ref(), &envs, false);
 
             if attrs.rec {
-                env.insert(x.clone(), ty_let.clone());
+                envs.insert(x.clone(), ty_let.clone());
             }
 
             linearizer.retype_ident(lin, x, ty_let.clone());
-            walk(state, env.clone(), lin, linearizer.scope(), re)?;
+            walk(state, envs.clone(), lin, linearizer.scope(), re)?;
 
             if !attrs.rec {
-                env.insert(x.clone(), ty_let);
+                envs.insert(x.clone(), ty_let);
             }
 
-            walk(state, env, lin, linearizer, rt)
+            walk(state, envs, lin, linearizer, rt)
         }
         Term::LetPattern(x, pat, re, rt) => {
-            let ty_let = binding_type(state, re.as_ref(), &env, false);
-            walk(state, env.clone(), lin, linearizer.scope(), re)?;
+            let ty_let = binding_type(state, re.as_ref(), &envs, false);
+            walk(state, envs.clone(), lin, linearizer.scope(), re)?;
 
             if let Some(x) = x {
                 linearizer.retype_ident(lin, x, ty_let.clone());
-                env.insert(x.clone(), ty_let);
+                envs.insert(x.clone(), ty_let);
             }
 
-            inject_pat_vars(pat, &mut env);
+            inject_pat_vars(pat, &mut envs);
 
-            walk(state, env, lin, linearizer, rt)
+            walk(state, envs, lin, linearizer, rt)
         }
         Term::App(e, t) => {
-            walk(state, env.clone(), lin, linearizer.scope(), e)?;
-            walk(state, env, lin, linearizer, t)
+            walk(state, envs.clone(), lin, linearizer.scope(), e)?;
+            walk(state, envs, lin, linearizer, t)
         }
         Term::Switch(exp, cases, default) => {
             cases.values().chain(default.iter()).try_for_each(|case| {
-                walk(state, env.clone(), lin, linearizer.scope(), case)
+                walk(state, envs.clone(), lin, linearizer.scope(), case)
             })?;
 
-            walk(state, env, lin, linearizer, exp)
+            walk(state, envs, lin, linearizer, exp)
         }
         Term::RecRecord(stat_map, dynamic, ..) => {
             for id in stat_map.keys() {
                 let binding_type = binding_type(
                     state,
                     stat_map.get(id).unwrap().as_ref(),
-                    &env,
+                    &envs,
                     false,
                 );
-                env.insert(id.clone(), binding_type.clone());
+                envs.insert(id.clone(), binding_type.clone());
                 linearizer.retype_ident(lin, id, binding_type);
             }
 
@@ -328,26 +393,26 @@ fn walk<L: Linearizer>(
                 .map(|(_, t)| t)
                 .chain(dynamic.iter().map(|(_, t)| t))
                 .try_for_each(|t| -> Result<(), TypecheckError> {
-                    walk(state, env.clone(), lin, linearizer.scope(), t)
+                    walk(state, envs.clone(), lin, linearizer.scope(), t)
                 })
         }
         Term::Record(stat_map, _) => {
             stat_map
                 .iter()
                 .try_for_each(|(_, t)| -> Result<(), TypecheckError> {
-                    walk(state, env.clone(), lin, linearizer.scope(), t)
+                    walk(state, envs.clone(), lin, linearizer.scope(), t)
                 })
         }
-        Term::Op1(_, t) => walk(state, env.clone(), lin, linearizer.scope(), t),
+        Term::Op1(_, t) => walk(state, envs.clone(), lin, linearizer.scope(), t),
         Term::Op2(_, t1, t2) => {
-            walk(state, env.clone(), lin, linearizer.scope(), t1)?;
-            walk(state, env, lin, linearizer, t2)
+            walk(state, envs.clone(), lin, linearizer.scope(), t1)?;
+            walk(state, envs, lin, linearizer, t2)
         }
         Term::OpN(_, args) => {
            args.iter().try_for_each(|t| -> Result<(), TypecheckError> {
                     walk(
                         state,
-                        env.clone(),
+                        envs.clone(),
                         lin,
                         linearizer.scope(),
                         t,
@@ -357,7 +422,7 @@ fn walk<L: Linearizer>(
         }
         // An type annotation switches mode to check.
         Term::MetaValue(meta) => {
-            meta.contracts.iter().chain(meta.types.iter()).try_for_each(|ty| walk_type(state, env.clone(), lin, linearizer.scope_meta(), &ty.types))?;
+            meta.contracts.iter().chain(meta.types.iter()).try_for_each(|ty| walk_type(state, envs.clone(), lin, linearizer.scope_meta(), &ty.types))?;
 
             match meta {
                 MetaValue {
@@ -367,9 +432,9 @@ fn walk<L: Linearizer>(
                 } => {
                     let tyw2 = TypeWrapper::from(ty2.clone());
                     let instantiated = instantiate_foralls(state, tyw2, ForallInst::Constant);
-                    type_check_(state, env, lin, linearizer, t, instantiated)
+                    type_check_(state, envs, lin, linearizer, t, instantiated)
                 }
-                MetaValue {value: Some(t), .. } =>  walk(state, env, lin, linearizer, t),
+                MetaValue {value: Some(t), .. } =>  walk(state, envs, lin, linearizer, t),
                 // A metavalue without a body nor a type annotation is a record field without definition.
                 // TODO: we might have something to with the linearizer to clear the current
                 // metadata. It looks like it may be unduly attached to the next field definition,
@@ -377,7 +442,7 @@ fn walk<L: Linearizer>(
                 _ => Ok(()),
             }
         }
-        Term::Sealed(_, t) => walk(state, env, lin, linearizer, t),
+        Term::Sealed(_, t) => walk(state, envs, lin, linearizer, t),
    }
 }
 
@@ -385,7 +450,7 @@ fn walk<L: Linearizer>(
 /// instead of a term.
 fn walk_type<L: Linearizer>(
     state: &mut State,
-    env: Environment,
+    envs: Envs,
     lin: &mut Linearization<L::Building>,
     mut linearizer: L,
     ty: &Types,
@@ -402,36 +467,36 @@ fn walk_type<L: Linearizer>(
        | AbsType::Wildcard(_)
        | AbsType::RowEmpty() => Ok(()),
        AbsType::Arrow(ty1, ty2) => {
-           walk_type(state, env.clone(), lin, linearizer.scope(), ty1.as_ref())?;
-           walk_type(state, env, lin, linearizer, ty2.as_ref())
+           walk_type(state, envs.clone(), lin, linearizer.scope(), ty1.as_ref())?;
+           walk_type(state, envs, lin, linearizer, ty2.as_ref())
        }
        AbsType::RowExtend(_, ty_row, tail) => {
-         if let Some(ty_row) = ty_row { walk_type(state, env.clone(), lin, linearizer.scope(), ty_row)? };
-         walk_type(state, env, lin,linearizer, tail)
+         if let Some(ty_row) = ty_row { walk_type(state, envs.clone(), lin, linearizer.scope(), ty_row)? };
+         walk_type(state, envs, lin,linearizer, tail)
        }
-       AbsType::Flat(t) => walk(state, env, lin, linearizer, t),
+       AbsType::Flat(t) => walk(state, envs, lin, linearizer, t),
        AbsType::Enum(ty2)
        | AbsType::DynRecord(ty2)
        | AbsType::StaticRecord(ty2)
        | AbsType::Array(ty2)
-       | AbsType::Forall(_, ty2) => walk_type(state, env, lin, linearizer, ty2),
+       | AbsType::Forall(_, ty2) => walk_type(state, envs, lin, linearizer, ty2),
     }
 }
 
 // TODO: The insertion of values in the type environment is done but everything is
 // typed as `Dyn`.
-fn inject_pat_vars(pat: &Destruct, env: &mut Environment) {
+fn inject_pat_vars(pat: &Destruct, envs: &mut Envs) {
     if let Destruct::Record { matches, rest, .. } = pat {
         if let Some(id) = rest {
-            env.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
+            envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
         }
         matches.iter().for_each(|m| match m {
-            Match::Simple(id, ..) => env.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn())),
+            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);
-                env.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
+                envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
                 if !pat.is_empty() {
-                    inject_pat_vars(pat, env);
+                    inject_pat_vars(pat, envs);
                 }
             }
         });
@@ -453,7 +518,7 @@ fn inject_pat_vars(pat: &Destruct, env: &mut Environment) {
 /// liearizer accordingly
 fn type_check_<L: Linearizer>(
     state: &mut State,
-    mut env: Environment,
+    mut envs: Envs,
     lin: &mut Linearization<L::Building>,
     mut linearizer: L,
     rt: &RichTerm,
@@ -463,7 +528,7 @@ fn type_check_<L: Linearizer>(
     linearizer.add_term(lin, t, *pos, ty.clone());
 
     match t.as_ref() {
-        Term::ParseError(_) => Ok(()),
+        Term::ParseError => Ok(()),
         // null is inferred to be of type Dyn
         Term::Null => unify(state, ty, mk_typewrapper::dynamic())
             .map_err(|err| err.into_typecheck_err(state, rt.pos)),
@@ -484,7 +549,7 @@ fn type_check_<L: Linearizer>(
                         StrChunk::Literal(_) => Ok(()),
                         StrChunk::Expr(t, _) => type_check_(
                             state,
-                            env.clone(),
+                            envs.clone(),
                             lin,
                             linearizer.scope(),
                             t,
@@ -503,8 +568,8 @@ fn type_check_<L: Linearizer>(
 
             unify(state, ty, arr).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
 
-            env.insert(x.clone(), src);
-            type_check_(state, env, lin, linearizer, t, trg)
+            envs.insert(x.clone(), src);
+            type_check_(state, envs, lin, linearizer, t, trg)
         }
         Term::FunPattern(x, pat, t) => {
             let src = state.table.fresh_unif_var();
@@ -514,11 +579,11 @@ fn type_check_<L: Linearizer>(
             let arr = mk_tyw_arrow!(src.clone(), trg.clone());
             if let Some(x) = x {
                 linearizer.retype_ident(lin, x, src.clone());
-                env.insert(x.clone(), src);
+                envs.insert(x.clone(), src);
             }
-            inject_pat_vars(pat, &mut env);
+            inject_pat_vars(pat, &mut envs);
             unify(state, ty, arr).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
-            type_check_(state, env, lin, linearizer, t, trg)
+            type_check_(state, envs, lin, linearizer, t, trg)
         }
         Term::Array(terms, _) => {
             let ty_elts = state.table.fresh_unif_var();
@@ -531,7 +596,7 @@ fn type_check_<L: Linearizer>(
                 .try_for_each(|t| -> Result<(), TypecheckError> {
                     type_check_(
                         state,
-                        env.clone(),
+                        envs.clone(),
                         lin,
                         linearizer.scope(),
                         t,
@@ -545,15 +610,15 @@ fn type_check_<L: Linearizer>(
                 .map_err(|err| err.into_typecheck_err(state, rt.pos))
         }
         Term::Let(x, re, rt, attrs) => {
-            let ty_let = binding_type(state, re.as_ref(), &env, true);
+            let ty_let = binding_type(state, re.as_ref(), &envs, true);
             if attrs.rec {
-                env.insert(x.clone(), ty_let.clone());
+                envs.insert(x.clone(), ty_let.clone());
             }
 
             linearizer.retype_ident(lin, x, ty_let.clone());
             type_check_(
                 state,
-                env.clone(),
+                envs.clone(),
                 lin,
                 linearizer.scope(),
                 re,
@@ -561,15 +626,15 @@ fn type_check_<L: Linearizer>(
             )?;
 
             if !attrs.rec {
-                env.insert(x.clone(), ty_let);
+                envs.insert(x.clone(), ty_let);
             }
-            type_check_(state, env, lin, linearizer, rt, ty)
+            type_check_(state, envs, lin, linearizer, rt, ty)
         }
         Term::LetPattern(x, pat, re, rt) => {
-            let ty_let = binding_type(state, re.as_ref(), &env, true);
+            let ty_let = binding_type(state, re.as_ref(), &envs, true);
             type_check_(
                 state,
-                env.clone(),
+                envs.clone(),
                 lin,
                 linearizer.scope(),
                 re,
@@ -578,17 +643,17 @@ fn type_check_<L: Linearizer>(
 
             if let Some(x) = x {
                 linearizer.retype_ident(lin, x, ty_let.clone());
-                env.insert(x.clone(), ty_let);
+                envs.insert(x.clone(), ty_let);
             }
-            inject_pat_vars(pat, &mut env);
-            type_check_(state, env, lin, linearizer, rt, ty)
+            inject_pat_vars(pat, &mut envs);
+            type_check_(state, envs, lin, linearizer, rt, ty)
         }
         Term::App(e, t) => {
             let src = state.table.fresh_unif_var();
             let arr = mk_tyw_arrow!(src.clone(), ty);
 
-            type_check_(state, env.clone(), lin, linearizer.scope(), e, arr)?;
-            type_check_(state, env, lin, linearizer, t, src)
+            type_check_(state, envs.clone(), lin, linearizer.scope(), e, arr)?;
+            type_check_(state, envs, lin, linearizer, t, src)
         }
         Term::Switch(exp, cases, default) => {
             // Currently, if it has a default value, we typecheck the whole thing as
@@ -598,7 +663,7 @@ fn type_check_<L: Linearizer>(
             for case in cases.values() {
                 type_check_(
                     state,
-                    env.clone(),
+                    envs.clone(),
                     lin,
                     linearizer.scope(),
                     case,
@@ -608,7 +673,7 @@ fn type_check_<L: Linearizer>(
 
             let row = match default {
                 Some(t) => {
-                    type_check_(state, env.clone(), lin, linearizer.scope(), t, res.clone())?;
+                    type_check_(state, envs.clone(), lin, linearizer.scope(), t, res.clone())?;
                     state.table.fresh_unif_var()
                 }
                 None => cases.iter().try_fold(
@@ -620,10 +685,10 @@ fn type_check_<L: Linearizer>(
             };
 
             unify(state, ty, res).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
-            type_check_(state, env, lin, linearizer, exp, mk_tyw_enum!(; row))
+            type_check_(state, envs, lin, linearizer, exp, mk_tyw_enum!(; row))
         }
         Term::Var(x) => {
-            let x_ty = env
+            let x_ty = envs
                 .get(x)
                 .ok_or_else(|| TypecheckError::UnboundIdentifier(x.clone(), *pos))?;
 
@@ -641,7 +706,7 @@ fn type_check_<L: Linearizer>(
             let ty_dyn = state.table.fresh_unif_var();
 
             for id in stat_map.keys() {
-                env.insert(id.clone(), ty_dyn.clone());
+                envs.insert(id.clone(), ty_dyn.clone());
                 linearizer.retype_ident(lin, id, ty_dyn.clone())
             }
 
@@ -650,7 +715,7 @@ fn type_check_<L: Linearizer>(
                 .try_for_each(|(_, t)| -> Result<(), TypecheckError> {
                     type_check_(
                         state,
-                        env.clone(),
+                        envs.clone(),
                         lin,
                         linearizer.scope(),
                         t,
@@ -667,8 +732,8 @@ fn type_check_<L: Linearizer>(
             // Fields defined by interpolation are ignored.
             if let Term::RecRecord(..) = t.as_ref() {
                 for (id, rt) in stat_map {
-                    let tyw = binding_type(state, rt.as_ref(), &env, true);
-                    env.insert(id.clone(), tyw.clone());
+                    let tyw = binding_type(state, rt.as_ref(), &envs, true);
+                    envs.insert(id.clone(), tyw.clone());
                     linearizer.retype_ident(lin, id, tyw);
                 }
             }
@@ -686,7 +751,7 @@ fn type_check_<L: Linearizer>(
                     .try_for_each(|(_, t)| -> Result<(), TypecheckError> {
                         type_check_(
                             state,
-                            env.clone(),
+                            envs.clone(),
                             lin,
                             linearizer.scope(),
                             t,
@@ -701,14 +766,14 @@ fn type_check_<L: Linearizer>(
                         // annotations) have already be determined and put in the typing
                         // environment, and we need to use the same.
                         let ty = if let Term::RecRecord(..) = t.as_ref() {
-                            env.get(id).unwrap()
+                            envs.get(id).unwrap()
                         } else {
                             state.table.fresh_unif_var()
                         };
 
                         type_check_(
                             state,
-                            env.clone(),
+                            envs.clone(),
                             lin,
                             linearizer.scope(),
                             field,
@@ -726,7 +791,7 @@ fn type_check_<L: Linearizer>(
         Term::Op1(op, t) => {
             let (ty_arg, ty_res) = get_uop_type(state, op)?;
 
-            type_check_(state, env.clone(), lin, linearizer.scope(), t, ty_arg)?;
+            type_check_(state, envs.clone(), lin, linearizer.scope(), t, ty_arg)?;
 
             let instantiated = instantiate_foralls(state, ty_res, ForallInst::Ptr);
             unify(state, ty, instantiated).map_err(|err| err.into_typecheck_err(state, rt.pos))
@@ -735,8 +800,8 @@ fn type_check_<L: Linearizer>(
             let (ty_arg1, ty_arg2, ty_res) = get_bop_type(state, op)?;
 
             unify(state, ty, ty_res).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
-            type_check_(state, env.clone(), lin, linearizer.scope(), t1, ty_arg1)?;
-            type_check_(state, env, lin, linearizer, t2, ty_arg2)
+            type_check_(state, envs.clone(), lin, linearizer.scope(), t1, ty_arg1)?;
+            type_check_(state, envs, lin, linearizer, t2, ty_arg2)
         }
         Term::OpN(op, args) => {
             let (tys_op, ty_ret) = get_nop_type(state, op)?;
@@ -745,7 +810,7 @@ fn type_check_<L: Linearizer>(
 
             tys_op.into_iter().zip(args.iter()).try_for_each(
                 |(ty_t, t)| -> Result<_, TypecheckError> {
-                    type_check_(state, env.clone(), lin, linearizer.scope(), t, ty_t)?;
+                    type_check_(state, envs.clone(), lin, linearizer.scope(), t, ty_t)?;
                     Ok(())
                 },
             )?;
@@ -757,7 +822,7 @@ fn type_check_<L: Linearizer>(
                 .iter()
                 .chain(meta.types.iter())
                 .try_for_each(|ty| {
-                    walk_type(state, env.clone(), lin, linearizer.scope_meta(), &ty.types)
+                    walk_type(state, envs.clone(), lin, linearizer.scope_meta(), &ty.types)
                 })?;
 
             match meta {
@@ -771,7 +836,7 @@ fn type_check_<L: Linearizer>(
                         instantiate_foralls(state, tyw2.clone(), ForallInst::Constant);
 
                     unify(state, tyw2, ty).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
-                    type_check_(state, env, lin, linearizer, t, instantiated)
+                    type_check_(state, envs, lin, linearizer, t, instantiated)
                 }
                 // A metavalue without a type annotation but with a contract annotation switches
                 // the typechecker back to walk mode. If there are several contracts, we
@@ -788,7 +853,7 @@ fn type_check_<L: Linearizer>(
                     // if there's an inner value, we still have to walk it, as it may contain
                     // statically typed blocks.
                     if let Some(t) = value {
-                        walk(state, env, lin, linearizer, t)
+                        walk(state, envs, lin, linearizer, t)
                     } else {
                         // TODO: we might have something to with the linearizer to clear the current
                         // metadata. It looks like it may be unduly attached to the next field definition,
@@ -798,7 +863,9 @@ fn type_check_<L: Linearizer>(
                 }
                 // A non-empty metavalue without a type or a contract annotation is typechecked in
                 // the same way as its inner value
-                MetaValue { value: Some(t), .. } => type_check_(state, env, lin, linearizer, t, ty),
+                MetaValue { value: Some(t), .. } => {
+                    type_check_(state, envs, lin, linearizer, t, ty)
+                }
                 // A metavalue without a body nor a type annotation is a record field without definition.
                 // We infer it to be of type `Dyn` for now.
                 // TODO: we might have something to with the linearizer to clear the current
@@ -810,7 +877,7 @@ fn type_check_<L: Linearizer>(
         }
         Term::SealingKey(_) => unify(state, ty, mk_typewrapper::sym())
             .map_err(|err| err.into_typecheck_err(state, rt.pos)),
-        Term::Sealed(_, t) => type_check_(state, env, lin, linearizer, t, ty),
+        Term::Sealed(_, t) => type_check_(state, envs, lin, linearizer, t, ty),
         Term::Import(_) => unify(state, ty, mk_typewrapper::dynamic())
             .map_err(|err| err.into_typecheck_err(state, rt.pos)),
         // We use the apparent type of the import for checking. This function doesn't recursively
@@ -820,8 +887,12 @@ fn type_check_<L: Linearizer>(
                 .resolver
                 .get(*file_id)
                 .expect("Internal error: resolved import not found during typechecking.");
-            let ty_import: TypeWrapper =
-                apparent_type(t.as_ref(), Some(&env), Some(state.resolver)).into();
+            let ty_import: TypeWrapper = apparent_type(
+                t.as_ref(),
+                Some(&Envs::from_envs(&envs)),
+                Some(state.resolver),
+            )
+            .into();
             unify(state, ty, ty_import).map_err(|err| err.into_typecheck_err(state, rt.pos))
         }
     }
@@ -843,8 +914,8 @@ fn type_check_<L: Linearizer>(
 ///     * in non strict mode, wildcards are assigned `Dyn`.
 ///     * in strict mode, the wildcard is typechecked, and we return the unification variable
 ///       corresponding to it.
-fn binding_type(state: &mut State, t: &Term, env: &Environment, strict: bool) -> TypeWrapper {
-    let ty_apt = apparent_type(t, Some(env), Some(state.resolver));
+fn binding_type(state: &mut State, t: &Term, envs: &Envs, strict: bool) -> TypeWrapper {
+    let ty_apt = apparent_type(t, Some(envs), Some(state.resolver));
 
     match ty_apt {
         ApparentType::Annotated(ty) if strict => {
@@ -934,7 +1005,7 @@ impl From<ApparentType> for TypeWrapper {
 ///   the future, such as `Dyn -> Dyn` for functions, `{ | Dyn}` for records, and so on).
 pub fn apparent_type(
     t: &Term,
-    env: Option<&Environment>,
+    envs: Option<&Envs>,
     resolver: Option<&dyn ImportResolver>,
 ) -> ApparentType {
     match t {
@@ -947,7 +1018,7 @@ pub fn apparent_type(
             ApparentType::Annotated(contracts.get(0).unwrap().types.clone())
         }
         Term::MetaValue(MetaValue { value: Some(v), .. }) => {
-            apparent_type(v.as_ref(), env, resolver)
+            apparent_type(v.as_ref(), envs, resolver)
         }
         Term::Num(_) => ApparentType::Inferred(Types(AbsType::Num())),
         Term::Bool(_) => ApparentType::Inferred(Types(AbsType::Bool())),
@@ -956,7 +1027,7 @@ pub fn apparent_type(
         Term::Array(..) => {
             ApparentType::Approximated(Types(AbsType::Array(Box::new(Types(AbsType::Dyn())))))
         }
-        Term::Var(id) => env
+        Term::Var(id) => envs
             .and_then(|envs| envs.get(id))
             .map(ApparentType::FromEnv)
             .unwrap_or(ApparentType::Approximated(Types(AbsType::Dyn()))),
@@ -965,7 +1036,7 @@ pub fn apparent_type(
                 let t = r
                     .get(*f)
                     .expect("Internal error: resolved import not found during typechecking.");
-                apparent_type(&t.term, env, Some(r))
+                apparent_type(&t.term, envs, Some(r))
             } else {
                 ApparentType::Approximated(Types(AbsType::Dyn()))
             }
diff --git a/stdlib/array.ncl b/stdlib/array.ncl
index 10e9ab92..75b3788a 100644
--- a/stdlib/array.ncl
+++ b/stdlib/array.ncl
@@ -260,13 +260,13 @@
 
         For example:
         ```nickel
-          sort (fun x y => if x < y then `Lesser else if (x == y) then `Equal else `Greater) [ 4, 5, 1, 2 ] =>
+          sort (fun x y => if x < y then `Less else if (x == y) then `Equal else `Greater) [ 4, 5, 1, 2 ] =>
             [ 1, 2, 4, 5 ]
         ```
         "%m
       = fun cmp l =>
         let first = %head% l in
-        let parts = partition (fun x => (cmp x first == `Lesser)) (%tail% l) in
+        let parts = partition (fun x => (cmp x first == `Less)) (%tail% l) in
         if %length% l <= 1 then
           l
         else
diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs
index 2aff59bd..2843a3a6 100644
--- a/utilities/src/lib.rs
+++ b/utilities/src/lib.rs
@@ -117,10 +117,10 @@ impl<'b> Bench<'b> {
 }
 
 pub fn bench_terms<'r>(rts: Vec<Bench<'r>>) -> Box<dyn Fn(&mut Criterion) + 'r> {
-    use nickel_lang::cache::Envs;
+    use nickel_lang::cache::GlobalEnv;
 
     let mut cache = Cache::new();
-    let Envs { eval_env, type_env } = cache.prepare_stdlib().unwrap();
+    let GlobalEnv { eval_env, type_env } = cache.prepare_stdlib().unwrap();
     Box::new(move |c: &mut Criterion| {
         rts.iter().for_each(|bench| {
             let t = bench.term();
@@ -180,7 +180,7 @@ macro_rules! ncl_bench_group {
     (name = $group_name:ident; config = $config:expr; $($b:tt),+ $(,)*) => {
         pub fn $group_name() {
             use nickel_lang::{
-                cache::{Envs, Cache, ImportResolver},
+                cache::{GlobalEnv, Cache, ImportResolver},
                 eval::eval,
                 transform::import_resolution::resolve_imports,
             };
@@ -188,7 +188,7 @@ macro_rules! ncl_bench_group {
             let mut c: criterion::Criterion<_> = $config
                 .configure_from_args();
             let mut cache = Cache::new();
-            let Envs {eval_env, type_env} = cache.prepare_stdlib().unwrap();
+            let GlobalEnv{eval_env, type_env} = cache.prepare_stdlib().unwrap();
             $(
                 let bench = $crate::ncl_bench!$b;
                 let t = bench.term();


Test patch:
diff --git a/src/eval/tests.rs b/src/eval/tests.rs
index b6554864..8f86e747 100644
--- a/src/eval/tests.rs
+++ b/src/eval/tests.rs
@@ -285,10 +285,10 @@ fn interpolation_nested() {
 }
 
 #[test]
-fn initial_env() {
-    let mut initial_env = Environment::new();
+fn global_env() {
+    let mut global_env = Environment::new();
     let mut resolver = DummyResolver {};
-    initial_env.insert(
+    global_env.insert(
         Ident::from("g"),
         Thunk::new(
             Closure::atomic_closure(Term::Num(1.0).into()),
@@ -298,20 +298,20 @@ fn initial_env() {
 
     let t = mk_term::let_in("x", Term::Num(2.0), mk_term::var("x"));
     assert_eq!(
-        eval(t, &initial_env, &mut resolver).map(Term::from),
+        eval(t, &global_env, &mut resolver).map(Term::from),
         Ok(Term::Num(2.0))
     );
 
     let t = mk_term::let_in("x", Term::Num(2.0), mk_term::var("g"));
     assert_eq!(
-        eval(t, &initial_env, &mut resolver).map(Term::from),
+        eval(t, &global_env, &mut resolver).map(Term::from),
         Ok(Term::Num(1.0))
     );
 
-    // Shadowing of the initial environment
+    // Shadowing of global environment
     let t = mk_term::let_in("g", Term::Num(2.0), mk_term::var("g"));
     assert_eq!(
-        eval(t, &initial_env, &mut resolver).map(Term::from),
+        eval(t, &global_env, &mut resolver).map(Term::from),
         Ok(Term::Num(2.0))
     );
 }
@@ -330,7 +330,7 @@ fn mk_env(bindings: Vec<(&str, RichTerm)>) -> Environment {
 
 #[test]
 fn substitution() {
-    let initial_env = mk_env(vec![
+    let global_env = mk_env(vec![
         ("glob1", Term::Num(1.0).into()),
         ("glob2", parse("\"Glob2\"").unwrap()),
         ("glob3", Term::Bool(false).into()),
@@ -342,14 +342,14 @@ fn substitution() {
 
     let t = parse("let x = 1 in if loc1 then 1 + loc2 else glob3").unwrap();
     assert_eq!(
-        subst(t, &initial_env, &env),
+        subst(t, &global_env, &env),
         parse("let x = 1 in if true then 1 + (if false then 1 else \"Glob2\") else false").unwrap()
     );
 
     let t = parse("switch {`x => [1, glob1], `y => loc2, `z => {id = true, other = glob3}} loc1")
         .unwrap();
     assert_eq!(
-        subst(t, &initial_env, &env),
+        subst(t, &global_env, &env),
         parse("switch {`x => [1, 1], `y => (if false then 1 else \"Glob2\"), `z => {id = true, other = false}} true").unwrap()
     );
 }
diff --git a/tests/imports.rs b/tests/imports.rs
index b208da39..ba242860 100644
--- a/tests/imports.rs
+++ b/tests/imports.rs
@@ -1,5 +1,5 @@
 use assert_matches::assert_matches;
-use nickel_lang::error::{Error, EvalError, ParseError, TypecheckError};
+use nickel_lang::error::{Error, EvalError, TypecheckError};
 use nickel_lang::program::Program;
 use nickel_lang::term::Term;
 use std::io::BufReader;
@@ -112,39 +112,3 @@ fn circular_imports_fail() {
         Ok(Term::RecRecord(..)) | Ok(Term::Record(..))
     );
 }
-
-#[test]
-fn import_unexpected_token_fail() {
-    let mut prog = Program::new_from_source(
-        BufReader::new(mk_import("unexpected_token.ncl").as_bytes()),
-        "should_fail",
-    )
-    .unwrap();
-    assert_matches!(
-        prog.eval(),
-        Err(Error::EvalError(EvalError::ParseError(
-            ParseError::UnexpectedToken(..)
-        )))
-    );
-}
-
-#[test]
-fn import_unexpected_token_in_record_fail() {
-    let mut prog = Program::new_from_source(
-        BufReader::new(
-            format!(
-                "let x = {} in \"Hello, \" ++ x.name",
-                mk_import("unexpected_token_in_record.ncl")
-            )
-            .as_bytes(),
-        ),
-        "should_fail",
-    )
-    .unwrap();
-    assert_matches!(
-        prog.eval(),
-        Err(Error::EvalError(EvalError::ParseError(
-            ParseError::UnexpectedToken(..)
-        )))
-    );
-}
diff --git a/tests/imports/unexpected_token.ncl b/tests/imports/unexpected_token.ncl
deleted file mode 100644
index 5400529f..00000000
--- a/tests/imports/unexpected_token.ncl
+++ /dev/null
@@ -1,1 +0,0 @@
-"Nickel"$
diff --git a/tests/imports/unexpected_token_in_record.ncl b/tests/imports/unexpected_token_in_record.ncl
deleted file mode 100644
index 6fc3ef61..00000000
--- a/tests/imports/unexpected_token_in_record.ncl
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  name = "Nickel",,
-}
diff --git a/tests/parse_fail.rs b/tests/parse_fail.rs
deleted file mode 100644
index b65ee8b3..00000000
--- a/tests/parse_fail.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-use assert_matches::assert_matches;
-use nickel_lang::error::Error;
-
-use nickel_lang_utilities::eval;
-
-#[test]
-fn unexpected_token() {
-    assert_matches!(eval("\"Nickel\"$"), Err(Error::ParseErrors(..)));
-}
-
-#[test]
-fn unexpected_token_in_record() {
-    assert_matches!(eval("{ name = \"Nickel\",, }"), Err(Error::ParseErrors(..)));
-}
diff --git a/tests/pass/arrays.ncl b/tests/pass/arrays.ncl
index 6933a786..55cc54a2 100644
--- a/tests/pass/arrays.ncl
+++ b/tests/pass/arrays.ncl
@@ -10,13 +10,6 @@ let Assert = fun l x => x || %blame% l in
   array.length [1,2,3] == 3,
   array.length ([] @ [1,2] @ [3,4] @ []) == 4,
 
-  # sort
-  let cmp = fun x y =>
-    if x < y then `Lesser
-    else if x == y then `Equal
-    else `Greater
-  in array.sort cmp [3, 42, -1, -5] == [-5, -1, 3, 42],
-
   # Test case added after https://github.com/tweag/nickel/issues/154
   let x = 1 in let l = [x] @ [2] in %head% l == 1,
 
diff --git a/tests/typecheck_fail.rs b/tests/typecheck_fail.rs
index c25b00ad..105ad46e 100644
--- a/tests/typecheck_fail.rs
+++ b/tests/typecheck_fail.rs
@@ -4,11 +4,11 @@ use nickel_lang::cache::resolvers::DummyResolver;
 use nickel_lang::error::TypecheckError;
 use nickel_lang::parser::{grammar, lexer};
 use nickel_lang::term::RichTerm;
+use nickel_lang::typecheck::{type_check_in_env, Environment};
 use nickel_lang::types::{AbsType, Types};
-use nickel_lang::{typecheck, typecheck::Environment};
 
 fn type_check(rt: &RichTerm) -> Result<(), TypecheckError> {
-    typecheck::type_check(rt, Environment::new(), &mut DummyResolver {}).map(|_| ())
+    type_check_in_env(rt, &Environment::new(), &mut DummyResolver {}).map(|_| ())
 }
 
 fn type_check_expr(s: impl std::string::ToString) -> Result<(), TypecheckError> {