Repository: tweag/nickel
Pull request #801 (2022-08-09 17:56:20)

Refactoring of global typing environment
Get rid of the useless separation between the global typing environment and the current typing environment. It was done for performance reasons, when cloning environments was expensive. But now, environments support sharing and cheap clone: instead of having two separate environments, we can just start from the initial (previously "global") and insert directly into it. Doing so, we get rid of the `typecheck::Envs` as well as simplify a bit the API of `typecheck`.

This PR also renames "global" to "initial", which is now more faithful.

Note that one could be tempted to the same to the eval environment: alas, the situation is more complex. We are creating new environments quite a lot (through closurization) during evaluation. Those environment are totally empty: if we would get rid of the current separate initial environment, we would need to create new environments by cloning this initial environment, instead of currently creating entirely empty ones, which is cumbersome (`initial_env` would have to be threaded through all evaluation sub functions). Thus, for evaluation, it's simpler to keep the initial environment separate.


Patch:
diff --git a/benches/stdlib.rs b/benches/stdlib.rs
index fc23c25a..21ed10a0 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_types_env().unwrap();
+    let type_env = cache.mk_type_env().unwrap();
     c.bench_function("typecheck stdlib", |b| {
         b.iter_batched(
             || cache.clone(),
diff --git a/lsp/nls/src/cache.rs b/lsp/nls/src/cache.rs
index 93f8887c..5a174674 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,
-        global_env: &typecheck::Environment,
+        initial_env: &typecheck::Environment,
         lin_cache: &mut HashMap<FileId, Completed>,
     ) -> Result<CacheOp<()>, CacheError<TypecheckError>>;
 }
@@ -31,10 +31,11 @@ impl CacheExt for Cache {
             Ok(self.add_string(path, source))
         }
     }
+
     fn typecheck_with_analysis<'a>(
         &mut self,
         file_id: FileId,
-        global_env: &typecheck::Environment,
+        initial_env: &typecheck::Environment,
         lin_cache: &mut HashMap<FileId, Completed>,
     ) -> Result<CacheOp<()>, CacheError<TypecheckError>> {
         if !self.terms_mut().contains_key(&file_id) {
@@ -48,7 +49,8 @@ impl CacheExt for Cache {
             Ok(CacheOp::Cached(()))
         } else if *state >= EntryState::Parsed {
             let host = AnalysisHost::new();
-            let (_, linearized) = typecheck::type_check(term, global_env, self, host)?;
+            let (_, linearized) =
+                typecheck::type_check_linearize(term, initial_env.clone(), 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 b1211d29..e9ed6693 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.global_env, &mut server.lin_cache)
+        .typecheck_with_analysis(file_id, &server.initial_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 9d7e7dbb..f3de4d0b 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 global_env: Environment,
+    pub initial_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 global_env = cache.mk_types_env().unwrap();
+        let initial_env = cache.mk_type_env().unwrap();
         let lin_cache = HashMap::new();
         Server {
             connection,
             cache,
             lin_cache,
-            global_env,
+            initial_env,
         }
     }
 
diff --git a/src/cache.rs b/src/cache.rs
index a1316fb8..20d6fbe2 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 GlobalEnv {
+pub struct Envs {
     /// 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 GlobalEnv {
     pub type_env: typecheck::Environment,
 }
 
-impl GlobalEnv {
+impl Envs {
     pub fn new() -> Self {
-        GlobalEnv {
+        Envs {
             eval_env: eval::Environment::new(),
             type_env: typecheck::Environment::new(),
         }
@@ -432,7 +432,7 @@ impl Cache {
     pub fn typecheck(
         &mut self,
         file_id: FileId,
-        global_env: &typecheck::Environment,
+        initial_env: &typecheck::Environment,
     ) -> Result<CacheOp<()>, CacheError<TypecheckError>> {
         match self.terms.get(&file_id) {
             Some(CachedTerm { state, .. }) if *state >= EntryState::Typechecked => {
@@ -440,15 +440,14 @@ impl Cache {
             }
             Some(CachedTerm { term, state, .. }) if *state >= EntryState::Parsed => {
                 if *state < EntryState::Typechecking {
-                    let (wildcards, _) =
-                        type_check(term, global_env, self, StubHost::<(), (), _>::new())?;
+                    let wildcards = type_check(term, initial_env.clone(), self)?;
                     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, global_env)?;
+                        self.typecheck(f, initial_env)?;
                     }
                 }
 
@@ -507,7 +506,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 global environment.
+    /// before adding their fields to the initial environment.
     ///
     /// # Preconditions
     ///
@@ -639,7 +638,7 @@ impl Cache {
     pub fn prepare(
         &mut self,
         file_id: FileId,
-        global_env: &typecheck::Environment,
+        initial_env: &typecheck::Environment,
     ) -> Result<CacheOp<()>, Error> {
         let mut result = CacheOp::Cached(());
 
@@ -656,7 +655,7 @@ impl Cache {
             result = CacheOp::Done(());
         };
 
-        let typecheck_res = self.typecheck(file_id, global_env).map_err(|cache_err| {
+        let typecheck_res = self.typecheck(file_id, initial_env).map_err(|cache_err| {
             cache_err
                 .unwrap_error("cache::prepare(): expected source to be parsed before typechecking")
         })?;
@@ -691,14 +690,14 @@ impl Cache {
     pub fn prepare_nocache(
         &mut self,
         file_id: FileId,
-        global_env: &typecheck::Environment,
+        initial_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, global_env, self, StubHost::<(), (), _>::new())?;
+        let wildcards = type_check(&term, initial_env.clone(), self)?;
         let term = transform::transform(term, Some(&wildcards))
             .map_err(|err| Error::ParseErrors(err.into()))?;
         Ok((term, pending))
@@ -812,31 +811,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 global environment, we already
-        // need a global evaluation environment, since stdlib parts may reference each other. But
+        // 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
         // typechecking is performed before program transformations, so this environment is not
-        // final one. We have create a temporary global environment just for typechecking, which is dropped
+        // final one. We have create a temporary initial 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 global_env = self.mk_types_env().map_err(|err| match err {
+        let initial_env = self.mk_type_env().map_err(|err| match err {
             CacheError::NotParsed => CacheError::NotParsed,
             CacheError::Error(_) => unreachable!(),
         })?;
-        self.typecheck_stdlib_(&global_env)
+        self.typecheck_stdlib_(&initial_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,
-        global_env: &typecheck::Environment,
+        initial_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, global_env)? {
+                    match self.typecheck(*file_id, initial_env)? {
                         done @ CacheOp::Done(()) => Ok(done),
                         _ => Ok(cache_op),
                     }
@@ -847,17 +846,16 @@ impl Cache {
     }
 
     /// Load, parse, and apply program transformations to the standard library. Do not typecheck
-    /// 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> {
+    /// 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> {
         #[cfg(debug_assertions)]
         if self.skip_stdlib {
-            return Ok(GlobalEnv::new());
+            return Ok(Envs::new());
         }
         self.load_stdlib()?;
-        let type_env = self.mk_types_env().unwrap();
+        let type_env = self.mk_type_env().unwrap();
 
         self.stdlib_ids
             .as_ref()
@@ -870,12 +868,12 @@ impl Cache {
                     .unwrap_error("cache::prepare_stdlib(): expected standard library to be parsed")
             })?;
         let eval_env = self.mk_eval_env().unwrap();
-        Ok(GlobalEnv { eval_env, type_env })
+        Ok(Envs { eval_env, type_env })
     }
 
-    /// 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>> {
+    /// 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>> {
         let stdlib_terms_vec =
             self.stdlib_ids
                 .as_ref()
@@ -884,15 +882,15 @@ impl Cache {
                         .iter()
                         .map(|file_id| {
                             self.get_owned(*file_id).expect(
-                            "cache::mk_global_env(): can't build environment, stdlib not parsed",
-                        )
+                                "cache::mk_type_env(): can't build environment, stdlib not parsed",
+                            )
                         })
                         .collect())
                 })?;
-        Ok(typecheck::Envs::mk_global(stdlib_terms_vec).unwrap())
+        Ok(typecheck::mk_initial_env(stdlib_terms_vec).unwrap())
     }
 
-    /// Generate a global evaluation environment from the list of `file_ids` corresponding to the standard
+    /// Generate the initial 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() {
@@ -901,7 +899,7 @@ impl Cache {
                 let result = eval::env_add_term(
                     &mut eval_env,
                     self.get_owned(*file_id).expect(
-                        "cache::mk_global_env(): can't build environment, stdlib not parsed",
+                        "cache::mk_eval_env(): can't build environment, stdlib not parsed",
                     ),
                 );
                 if let Err(eval::EnvBuildError::NotARecord(rt)) = result {
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index b0b37375..ea7c6f8c 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 global environment contains builtin functions accessible from anywhere, and alive
+//!     * The initial 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,
-    global_env: &Environment,
+    initial_env: &Environment,
     resolver: &mut R,
 ) -> Result<RichTerm, EvalError>
 where
     R: ImportResolver,
 {
-    eval_closure(Closure::atomic_closure(t0), global_env, resolver, true).map(|(term, _)| term)
+    eval_closure(Closure::atomic_closure(t0), initial_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,
-    global_env: &Environment,
+    initial_env: &Environment,
     resolver: &mut R,
 ) -> Result<RichTerm, EvalError>
 where
     R: ImportResolver,
 {
-    eval_deep_closure(t0, global_env, resolver).map(|(term, env)| subst(term, global_env, &env))
+    eval_deep_closure(t0, initial_env, resolver).map(|(term, env)| subst(term, initial_env, &env))
 }
 
 /// Fully evaluates a Nickel term like `eval_full`, but does not substitute all variables.
 pub fn eval_deep<R>(
     t0: RichTerm,
-    global_env: &Environment,
+    initial_env: &Environment,
     resolver: &mut R,
 ) -> Result<RichTerm, EvalError>
 where
     R: ImportResolver,
 {
-    eval_deep_closure(t0, global_env, resolver).map(|(term, _)| term)
+    eval_deep_closure(t0, initial_env, resolver).map(|(term, _)| term)
 }
 
 fn eval_deep_closure<R>(
     t0: RichTerm,
-    global_env: &Environment,
+    initial_env: &Environment,
     resolver: &mut R,
 ) -> Result<(RichTerm, Environment), EvalError>
 where
@@ -235,7 +235,12 @@ where
             Term::Var(var)
         ),
     );
-    eval_closure(Closure::atomic_closure(wrapper), global_env, resolver, true)
+    eval_closure(
+        Closure::atomic_closure(wrapper),
+        initial_env,
+        resolver,
+        true,
+    )
 }
 
 /// Evaluate a Nickel Term, stopping when a meta value is encountered at the top-level without
@@ -245,19 +250,19 @@ where
 /// Used to query the metadata of a value.
 pub fn eval_meta<R>(
     t: RichTerm,
-    global_env: &Environment,
+    initial_env: &Environment,
     resolver: &mut R,
 ) -> Result<RichTerm, EvalError>
 where
     R: ImportResolver,
 {
-    let (mut rt, env) = eval_closure(Closure::atomic_closure(t), global_env, resolver, false)?;
+    let (mut rt, env) = eval_closure(Closure::atomic_closure(t), initial_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 }, global_env, resolver, true)?;
-            let substituted = subst(evaluated, global_env, &env);
+                eval_closure(Closure { body: t, env }, initial_env, resolver, true)?;
+            let substituted = subst(evaluated, initial_env, &env);
 
             meta.value = Some(substituted);
         }
@@ -275,8 +280,8 @@ where
 /// # Arguments
 ///
 /// - `clos`: the closure to evaluate
-/// - `global_env`: the global environment containing the builtin functions of the language. Accessible from anywhere in the
-/// program.
+/// - `initial_env`: the initial environment containing the stdlib items.
+///   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.
@@ -288,7 +293,7 @@ where
 ///  - the evaluated term with its final environment
 pub fn eval_closure<R>(
     mut clos: Closure,
-    global_env: &Environment,
+    initial_env: &Environment,
     resolver: &mut R,
     mut enriched_strict: bool,
 ) -> Result<(RichTerm, Environment), EvalError>
@@ -315,7 +320,7 @@ where
             Term::Var(x) => {
                 let mut thunk = env
                     .get(x)
-                    .or_else(|| global_env.get(x))
+                    .or_else(|| initial_env.get(x))
                     .ok_or_else(|| EvalError::UnboundIdentifier(x.clone(), pos))?;
                 std::mem::drop(env); // thunk may be a 1RC pointer
 
@@ -683,16 +688,16 @@ 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, global_env: &Environment, env: &Environment) -> RichTerm {
+pub fn subst(rt: RichTerm, initial_env: &Environment, env: &Environment) -> RichTerm {
     let RichTerm { term, pos } = rt;
 
     match term.into_owned() {
         Term::Var(id) => env
             .get(&id)
-            .or_else(|| global_env.get(&id))
+            .or_else(|| initial_env.get(&id))
             .map(|thunk| {
                 let closure = thunk.get_owned();
-                subst(closure.body, global_env, &closure.env)
+                subst(closure.body, initial_env, &closure.env)
             })
             .unwrap_or_else(|| RichTerm::new(Term::Var(id), pos)),
         v @ Term::Null
@@ -709,56 +714,56 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
         | v @ Term::Import(_)
         | v @ Term::ResolvedImport(_) => RichTerm::new(v, pos),
         Term::Let(id, t1, t2, attrs) => {
-            let t1 = subst(t1, global_env, env);
-            let t2 = subst(t2, global_env, env);
+            let t1 = subst(t1, initial_env, env);
+            let t2 = subst(t2, initial_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, global_env, env);
-            let t2 = subst(t2, global_env, env);
+            let t1 = subst(t1, initial_env, env);
+            let t2 = subst(t2, initial_env, env);
 
             RichTerm::new(Term::App(t1, t2), pos)
         }
         Term::Switch(t, cases, default) => {
             let default =
-                default.map(|d| subst(d, global_env, env));
+                default.map(|d| subst(d, initial_env, env));
             let cases = cases
                 .into_iter()
                 .map(|(id, t)| {
                     (
                         id,
-                        subst(t, global_env, env),
+                        subst(t, initial_env, env),
                     )
                 })
                 .collect();
-            let t = subst(t, global_env, env);
+            let t = subst(t, initial_env, env);
 
             RichTerm::new(Term::Switch(t, cases, default), pos)
         }
         Term::Op1(op, t) => {
-            let t = subst(t, global_env, env);
+            let t = subst(t, initial_env, env);
 
             RichTerm::new(Term::Op1(op, t), pos)
         }
         Term::Op2(op, t1, t2) => {
-            let t1 = subst(t1, global_env, env);
-            let t2 = subst(t2, global_env, env);
+            let t1 = subst(t1, initial_env, env);
+            let t2 = subst(t2, initial_env, env);
 
             RichTerm::new(Term::Op2(op, t1, t2), pos)
         }
         Term::OpN(op, ts) => {
             let ts = ts
                 .into_iter()
-                .map(|t| subst(t, global_env, env))
+                .map(|t| subst(t, initial_env, env))
                 .collect();
 
             RichTerm::new(Term::OpN(op, ts), pos)
         }
         Term::Sealed(i, t) => {
-            let t = subst(t, global_env, env);
+            let t = subst(t, initial_env, env);
 
             RichTerm::new(Term::Sealed(i, t), pos)
         }
@@ -768,7 +773,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
                 .map(|(id, t)| {
                     (
                         id,
-                        subst(t, global_env, env),
+                        subst(t, initial_env, env),
                     )
                 })
                 .collect();
@@ -781,7 +786,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
                 .map(|(id, t)| {
                     (
                         id,
-                        subst(t, global_env, env),
+                        subst(t, initial_env, env),
                     )
                 })
                 .collect();
@@ -790,8 +795,8 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
                 .into_iter()
                 .map(|(id_t, t)| {
                     (
-                        subst(id_t, global_env, env),
-                        subst(t, global_env, env),
+                        subst(id_t, initial_env, env),
+                        subst(t, initial_env, env),
                     )
                 })
                 .collect();
@@ -801,7 +806,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
         Term::Array(ts, attrs) => {
             let ts = ts
                 .into_iter()
-                .map(|t| subst(t, global_env, env))
+                .map(|t| subst(t, initial_env, env))
                 .collect();
 
             RichTerm::new(Term::Array(ts, attrs), pos)
@@ -812,7 +817,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
                 .map(|chunk| match chunk {
                     chunk @ StrChunk::Literal(_) => chunk,
                     StrChunk::Expr(t, indent) => StrChunk::Expr(
-                        subst(t, global_env, env),
+                        subst(t, initial_env, env),
                         indent,
                     ),
                 })
@@ -832,7 +837,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
             //         let types = match ctr.types {
             //             Types(AbsType::Flat(t)) => Types(AbsType::Flat(subst(
             //                 t,
-            //                 global_env,
+            //                 initial_env,
             //                 env,
             //                 Cow::Borrowed(bound.as_ref()),
             //             ))),
@@ -847,7 +852,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
             //     let types = match ctr.types {
             //         Types(AbsType::Flat(t)) => Types(AbsType::Flat(subst(
             //             t,
-            //             global_env,
+            //             initial_env,
             //             env,
             //             Cow::Borrowed(bound.as_ref()),
             //         ))),
@@ -857,7 +862,7 @@ pub fn subst(rt: RichTerm, global_env: &Environment, env: &Environment) -> RichT
             //     Contract { types, ..ctr }
             // });
 
-            let value = meta.value.map(|t| subst(t, global_env, env));
+            let value = meta.value.map(|t| subst(t, initial_env, env));
 
             let meta = MetaValue {
                 doc: meta.doc,
diff --git a/src/eval/operation.rs b/src/eval/operation.rs
index 8fd7544b..6624cb7c 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 global_env = Environment::new();
+                let initial_env = Environment::new();
                 let rt2 = subst(
                     RichTerm {
                         term: t2,
                         pos: pos2,
                     },
-                    &global_env,
+                    &initial_env,
                     &env2,
                 );
 
diff --git a/src/program.rs b/src/program.rs
index d0181656..21b9c1f2 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 global environment before the evaluation of the program.
+//! Each such value is added to the initial 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 global environment. Return
+    /// Retrieve the parsed term and typecheck it, and generate a fresh initial environment. Return
     /// both.
     fn prepare_eval(&mut self) -> Result<(RichTerm, eval::Environment), Error> {
-        let GlobalEnv { eval_env, type_env } = self.cache.prepare_stdlib()?;
+        let Envs { 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, global_env) = self.prepare_eval()?;
-        eval::eval(t, &global_env, &mut self.cache).map_err(|e| e.into())
+        let (t, initial_env) = self.prepare_eval()?;
+        eval::eval(t, &initial_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, global_env) = self.prepare_eval()?;
-        eval::eval_full(t, &global_env, &mut self.cache).map_err(|e| e.into())
+        let (t, initial_env) = self.prepare_eval()?;
+        eval::eval_full(t, &initial_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, global_env) = self.prepare_eval()?;
-        eval::eval_deep(t, &global_env, &mut self.cache).map_err(|e| e.into())
+        let (t, initial_env) = self.prepare_eval()?;
+        eval::eval_deep(t, &initial_env, &mut self.cache).map_err(|e| e.into())
     }
 
     /// Wrapper for [`query`].
     pub fn query(&mut self, path: Option<String>) -> Result<Term, Error> {
-        let global_env = self.cache.prepare_stdlib()?;
-        query(&mut self.cache, self.main_id, &global_env, path)
+        let initial_env = self.cache.prepare_stdlib()?;
+        query(&mut self.cache, self.main_id, &initial_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 global_env = self.cache.mk_types_env().expect("program::typecheck(): stdlib has been loaded but was not found in cache on mk_types_env()");
+        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()");
         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, &global_env)
+            .typecheck(self.main_id, &initial_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,
-    global_env: &GlobalEnv,
+    initial_env: &Envs,
     path: Option<String>,
 ) -> Result<Term, Error> {
-    cache.prepare(file_id, &global_env.type_env)?;
+    cache.prepare(file_id, &initial_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, &global_env.eval_env, cache)?.into())
+    Ok(eval::eval_meta(t, &initial_env.eval_env, cache)?.into())
 }
 
 /// Pretty-print an error.
diff --git a/src/repl/mod.rs b/src/repl/mod.rs
index e9e02d62..6ba6c85d 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, GlobalEnv};
+use crate::cache::{Cache, Envs};
 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 global environment. Contain the global environment with the stdlib, plus toplevel
+    /// The current environment. Contain the initial environment with the stdlib, plus toplevel
     /// declarations and loadings made inside the REPL.
-    env: GlobalEnv,
+    env: Envs,
     /// The initial type environment, without the toplevel declarations made inside the REPL. Used
     /// to typecheck imports in a fresh environment.
-    init_type_env: typecheck::Environment,
+    initial_type_env: typecheck::Environment,
 }
 
 impl ReplImpl {
@@ -85,8 +85,8 @@ impl ReplImpl {
         ReplImpl {
             cache: Cache::new(),
             parser: grammar::ExtendedTermParser::new(),
-            env: GlobalEnv::new(),
-            init_type_env: typecheck::Environment::new(),
+            env: Envs::new(),
+            initial_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.init_type_env = self.env.type_env.clone();
+        self.initial_type_env = self.env.type_env.clone();
         Ok(())
     }
 
@@ -135,16 +135,16 @@ impl ReplImpl {
             }
 
             let wildcards =
-                typecheck::type_check_in_env(&t, &repl_impl.env.type_env, &repl_impl.cache)?;
+                typecheck::type_check(&t, repl_impl.env.type_env.clone(), &repl_impl.cache)?;
 
             if let Some(id) = id {
-                typecheck::Envs::env_add(&mut repl_impl.env.type_env, id, &t, &repl_impl.cache);
+                typecheck::env_add(&mut repl_impl.env.type_env, id, &t, &repl_impl.cache);
             }
 
             for id in &pending {
                 repl_impl
                     .cache
-                    .typecheck(*id, &repl_impl.init_type_env)
+                    .typecheck(*id, &repl_impl.initial_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::Envs::env_add_term(&mut self.env.type_env, &term, &self.cache).unwrap();
+        typecheck::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_in_env(&term, &self.env.type_env, &self.cache)?;
+        let wildcards = typecheck::type_check(&term, self.env.type_env.clone(), &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,12 +242,10 @@ impl Repl for ReplImpl {
             )
             .unwrap();
 
-        Ok(typecheck::apparent_type(
-            term.as_ref(),
-            Some(&typecheck::Envs::from_global(&self.env.type_env)),
-            Some(&self.cache),
+        Ok(
+            typecheck::apparent_type(term.as_ref(), Some(&self.env.type_env), Some(&self.cache))
+                .into(),
         )
-        .into())
     }
 
     fn query(&mut self, exp: &str) -> Result<Term, Error> {
diff --git a/src/typecheck/mod.rs b/src/typecheck/mod.rs
index 4baa364b..7faff74d 100644
--- a/src/typecheck/mod.rs
+++ b/src/typecheck/mod.rs
@@ -75,101 +75,59 @@ 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),
 }
 
-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(),
-        }
-    }
-
-    /// 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(),
-        }
-    }
-
-    /// 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(())
+/// 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()))
             }
-            t => Err(EnvBuildError::NotARecord(RichTerm::new(t.clone(), *pos))),
-        }
-    }
+        })
+        .collect::<Result<Vec<_>, EnvBuildError>>()?
+        .into_iter()
+        .flatten()
+        .collect())
+}
 
-    /// 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(),
-        );
-    }
+/// 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);
+            }
 
-    /// 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))
+            Ok(())
+        }
+        t => Err(EnvBuildError::NotARecord(RichTerm::new(t.clone(), *pos))),
     }
+}
 
-    /// Wrapper to insert a new binding in the local environment.
-    pub fn insert(&mut self, ident: Ident, tyw: TypeWrapper) {
-        self.local.insert(ident, tyw);
-    }
+/// 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(),
+    );
 }
 
 /// The shared state of unification.
@@ -191,16 +149,30 @@ pub struct State<'a> {
 
 /// Typecheck a term.
 ///
-/// Return the inferred type in case of success. This is just a wrapper that calls `type_check_`
-/// with a fresh unification variable as goal.
+/// 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.
 ///
 /// 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<LL>(
+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>(
     t: &RichTerm,
-    global_env: &Environment,
+    initial_env: Environment,
     resolver: &impl ImportResolver,
     mut linearizer: LL,
 ) -> Result<(Wildcards, LL::Completed), TypecheckError>
@@ -222,7 +194,7 @@ where
 
         walk(
             &mut state,
-            Envs::from_global(global_env),
+            initial_env,
             &mut building,
             linearizer.scope(),
             t,
@@ -235,48 +207,11 @@ 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 envs: Envs,
+    mut env: Environment,
     lin: &mut Linearization<L::Building>,
     mut linearizer: L,
     rt: &RichTerm,
@@ -286,7 +221,7 @@ fn walk<L: Linearizer>(
         lin,
         t,
         *pos,
-        apparent_type(t, Some(&envs), Some(state.resolver)).into(),
+        apparent_type(t, Some(&env), Some(state.resolver)).into(),
     );
 
     match t.as_ref() {
@@ -302,7 +237,7 @@ fn walk<L: Linearizer>(
         // caller.
         | Term::Import(_)
         | Term::ResolvedImport(_) => Ok(()),
-        Term::Var(x) => envs
+        Term::Var(x) => env
             .get(x)
             .ok_or_else(|| TypecheckError::UnboundIdentifier(x.clone(), *pos))
             .map(|_| ()),
@@ -313,78 +248,78 @@ fn walk<L: Linearizer>(
                     match chunk {
                         StrChunk::Literal(_) => Ok(()),
                         StrChunk::Expr(t, _) => {
-                            walk(state, envs.clone(), lin, linearizer.scope(), t)
+                            walk(state, env.clone(), lin, linearizer.scope(), t)
                         }
                     }
                 })
         }
         Term::Fun(id, t) => {
             // The parameter of an un-annotated function is assigned the type `Dyn`.
-            envs.insert(id.clone(), mk_typewrapper::dynamic());
-            walk(state, envs, lin, linearizer, t)
+            env.insert(id.clone(), mk_typewrapper::dynamic());
+            walk(state, env, lin, linearizer, t)
         }
         Term::FunPattern(id, pat, t) => {
             if let Some(id) = id {
-                envs.insert(id.clone(), binding_type(state, t.as_ref(), &envs, false));
+                env.insert(id.clone(), binding_type(state, t.as_ref(), &env, false));
             }
 
-            inject_pat_vars(pat, &mut envs);
-            walk(state, envs, lin, linearizer, t)
+            inject_pat_vars(pat, &mut env);
+            walk(state, env, lin, linearizer, t)
         }
         Term::Array(terms, _) => terms
             .iter()
             .try_for_each(|t| -> Result<(), TypecheckError> {
-                walk(state, envs.clone(), lin, linearizer.scope(), t)
+                walk(state, env.clone(), lin, linearizer.scope(), t)
             }),
         Term::Let(x, re, rt, attrs) => {
-            let ty_let = binding_type(state, re.as_ref(), &envs, false);
+            let ty_let = binding_type(state, re.as_ref(), &env, false);
 
             if attrs.rec {
-                envs.insert(x.clone(), ty_let.clone());
+                env.insert(x.clone(), ty_let.clone());
             }
 
             linearizer.retype_ident(lin, x, ty_let.clone());
-            walk(state, envs.clone(), lin, linearizer.scope(), re)?;
+            walk(state, env.clone(), lin, linearizer.scope(), re)?;
 
             if !attrs.rec {
-                envs.insert(x.clone(), ty_let);
+                env.insert(x.clone(), ty_let);
             }
 
-            walk(state, envs, lin, linearizer, rt)
+            walk(state, env, lin, linearizer, rt)
         }
         Term::LetPattern(x, pat, re, rt) => {
-            let ty_let = binding_type(state, re.as_ref(), &envs, false);
-            walk(state, envs.clone(), lin, linearizer.scope(), re)?;
+            let ty_let = binding_type(state, re.as_ref(), &env, false);
+            walk(state, env.clone(), lin, linearizer.scope(), re)?;
 
             if let Some(x) = x {
                 linearizer.retype_ident(lin, x, ty_let.clone());
-                envs.insert(x.clone(), ty_let);
+                env.insert(x.clone(), ty_let);
             }
 
-            inject_pat_vars(pat, &mut envs);
+            inject_pat_vars(pat, &mut env);
 
-            walk(state, envs, lin, linearizer, rt)
+            walk(state, env, lin, linearizer, rt)
         }
         Term::App(e, t) => {
-            walk(state, envs.clone(), lin, linearizer.scope(), e)?;
-            walk(state, envs, lin, linearizer, t)
+            walk(state, env.clone(), lin, linearizer.scope(), e)?;
+            walk(state, env, lin, linearizer, t)
         }
         Term::Switch(exp, cases, default) => {
             cases.values().chain(default.iter()).try_for_each(|case| {
-                walk(state, envs.clone(), lin, linearizer.scope(), case)
+                walk(state, env.clone(), lin, linearizer.scope(), case)
             })?;
 
-            walk(state, envs, lin, linearizer, exp)
+            walk(state, env, 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(),
-                    &envs,
+                    &env,
                     false,
                 );
-                envs.insert(id.clone(), binding_type.clone());
+                env.insert(id.clone(), binding_type.clone());
                 linearizer.retype_ident(lin, id, binding_type);
             }
 
@@ -393,26 +328,26 @@ fn walk<L: Linearizer>(
                 .map(|(_, t)| t)
                 .chain(dynamic.iter().map(|(_, t)| t))
                 .try_for_each(|t| -> Result<(), TypecheckError> {
-                    walk(state, envs.clone(), lin, linearizer.scope(), t)
+                    walk(state, env.clone(), lin, linearizer.scope(), t)
                 })
         }
         Term::Record(stat_map, _) => {
             stat_map
                 .iter()
                 .try_for_each(|(_, t)| -> Result<(), TypecheckError> {
-                    walk(state, envs.clone(), lin, linearizer.scope(), t)
+                    walk(state, env.clone(), lin, linearizer.scope(), t)
                 })
         }
-        Term::Op1(_, t) => walk(state, envs.clone(), lin, linearizer.scope(), t),
+        Term::Op1(_, t) => walk(state, env.clone(), lin, linearizer.scope(), t),
         Term::Op2(_, t1, t2) => {
-            walk(state, envs.clone(), lin, linearizer.scope(), t1)?;
-            walk(state, envs, lin, linearizer, t2)
+            walk(state, env.clone(), lin, linearizer.scope(), t1)?;
+            walk(state, env, lin, linearizer, t2)
         }
         Term::OpN(_, args) => {
            args.iter().try_for_each(|t| -> Result<(), TypecheckError> {
                     walk(
                         state,
-                        envs.clone(),
+                        env.clone(),
                         lin,
                         linearizer.scope(),
                         t,
@@ -422,7 +357,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, envs.clone(), lin, linearizer.scope_meta(), &ty.types))?;
+            meta.contracts.iter().chain(meta.types.iter()).try_for_each(|ty| walk_type(state, env.clone(), lin, linearizer.scope_meta(), &ty.types))?;
 
             match meta {
                 MetaValue {
@@ -432,9 +367,9 @@ fn walk<L: Linearizer>(
                 } => {
                     let tyw2 = TypeWrapper::from(ty2.clone());
                     let instantiated = instantiate_foralls(state, tyw2, ForallInst::Constant);
-                    type_check_(state, envs, lin, linearizer, t, instantiated)
+                    type_check_(state, env, lin, linearizer, t, instantiated)
                 }
-                MetaValue {value: Some(t), .. } =>  walk(state, envs, lin, linearizer, t),
+                MetaValue {value: Some(t), .. } =>  walk(state, env, 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,
@@ -442,7 +377,7 @@ fn walk<L: Linearizer>(
                 _ => Ok(()),
             }
         }
-        Term::Sealed(_, t) => walk(state, envs, lin, linearizer, t),
+        Term::Sealed(_, t) => walk(state, env, lin, linearizer, t),
    }
 }
 
@@ -450,7 +385,7 @@ fn walk<L: Linearizer>(
 /// instead of a term.
 fn walk_type<L: Linearizer>(
     state: &mut State,
-    envs: Envs,
+    env: Environment,
     lin: &mut Linearization<L::Building>,
     mut linearizer: L,
     ty: &Types,
@@ -467,36 +402,36 @@ fn walk_type<L: Linearizer>(
        | AbsType::Wildcard(_)
        | AbsType::RowEmpty() => Ok(()),
        AbsType::Arrow(ty1, ty2) => {
-           walk_type(state, envs.clone(), lin, linearizer.scope(), ty1.as_ref())?;
-           walk_type(state, envs, lin, linearizer, ty2.as_ref())
+           walk_type(state, env.clone(), lin, linearizer.scope(), ty1.as_ref())?;
+           walk_type(state, env, lin, linearizer, ty2.as_ref())
        }
        AbsType::RowExtend(_, ty_row, 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)
+         if let Some(ty_row) = ty_row { walk_type(state, env.clone(), lin, linearizer.scope(), ty_row)? };
+         walk_type(state, env, lin,linearizer, tail)
        }
-       AbsType::Flat(t) => walk(state, envs, lin, linearizer, t),
+       AbsType::Flat(t) => walk(state, env, lin, linearizer, t),
        AbsType::Enum(ty2)
        | AbsType::DynRecord(ty2)
        | AbsType::StaticRecord(ty2)
        | AbsType::Array(ty2)
-       | AbsType::Forall(_, ty2) => walk_type(state, envs, lin, linearizer, ty2),
+       | AbsType::Forall(_, ty2) => walk_type(state, env, 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, envs: &mut Envs) {
+fn inject_pat_vars(pat: &Destruct, env: &mut Environment) {
     if let Destruct::Record { matches, rest, .. } = pat {
         if let Some(id) = rest {
-            envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
+            env.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
         }
         matches.iter().for_each(|m| match m {
-            Match::Simple(id, ..) => envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn())),
+            Match::Simple(id, ..) => env.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn())),
             Match::Assign(id, _, (bind_id, pat)) => {
                 let id = bind_id.as_ref().unwrap_or(id);
-                envs.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
+                env.insert(id.clone(), TypeWrapper::Concrete(AbsType::Dyn()));
                 if !pat.is_empty() {
-                    inject_pat_vars(pat, envs);
+                    inject_pat_vars(pat, env);
                 }
             }
         });
@@ -518,7 +453,7 @@ fn inject_pat_vars(pat: &Destruct, envs: &mut Envs) {
 /// liearizer accordingly
 fn type_check_<L: Linearizer>(
     state: &mut State,
-    mut envs: Envs,
+    mut env: Environment,
     lin: &mut Linearization<L::Building>,
     mut linearizer: L,
     rt: &RichTerm,
@@ -549,7 +484,7 @@ fn type_check_<L: Linearizer>(
                         StrChunk::Literal(_) => Ok(()),
                         StrChunk::Expr(t, _) => type_check_(
                             state,
-                            envs.clone(),
+                            env.clone(),
                             lin,
                             linearizer.scope(),
                             t,
@@ -568,8 +503,8 @@ fn type_check_<L: Linearizer>(
 
             unify(state, ty, arr).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
 
-            envs.insert(x.clone(), src);
-            type_check_(state, envs, lin, linearizer, t, trg)
+            env.insert(x.clone(), src);
+            type_check_(state, env, lin, linearizer, t, trg)
         }
         Term::FunPattern(x, pat, t) => {
             let src = state.table.fresh_unif_var();
@@ -579,11 +514,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());
-                envs.insert(x.clone(), src);
+                env.insert(x.clone(), src);
             }
-            inject_pat_vars(pat, &mut envs);
+            inject_pat_vars(pat, &mut env);
             unify(state, ty, arr).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
-            type_check_(state, envs, lin, linearizer, t, trg)
+            type_check_(state, env, lin, linearizer, t, trg)
         }
         Term::Array(terms, _) => {
             let ty_elts = state.table.fresh_unif_var();
@@ -596,7 +531,7 @@ fn type_check_<L: Linearizer>(
                 .try_for_each(|t| -> Result<(), TypecheckError> {
                     type_check_(
                         state,
-                        envs.clone(),
+                        env.clone(),
                         lin,
                         linearizer.scope(),
                         t,
@@ -610,15 +545,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(), &envs, true);
+            let ty_let = binding_type(state, re.as_ref(), &env, true);
             if attrs.rec {
-                envs.insert(x.clone(), ty_let.clone());
+                env.insert(x.clone(), ty_let.clone());
             }
 
             linearizer.retype_ident(lin, x, ty_let.clone());
             type_check_(
                 state,
-                envs.clone(),
+                env.clone(),
                 lin,
                 linearizer.scope(),
                 re,
@@ -626,15 +561,15 @@ fn type_check_<L: Linearizer>(
             )?;
 
             if !attrs.rec {
-                envs.insert(x.clone(), ty_let);
+                env.insert(x.clone(), ty_let);
             }
-            type_check_(state, envs, lin, linearizer, rt, ty)
+            type_check_(state, env, lin, linearizer, rt, ty)
         }
         Term::LetPattern(x, pat, re, rt) => {
-            let ty_let = binding_type(state, re.as_ref(), &envs, true);
+            let ty_let = binding_type(state, re.as_ref(), &env, true);
             type_check_(
                 state,
-                envs.clone(),
+                env.clone(),
                 lin,
                 linearizer.scope(),
                 re,
@@ -643,17 +578,17 @@ fn type_check_<L: Linearizer>(
 
             if let Some(x) = x {
                 linearizer.retype_ident(lin, x, ty_let.clone());
-                envs.insert(x.clone(), ty_let);
+                env.insert(x.clone(), ty_let);
             }
-            inject_pat_vars(pat, &mut envs);
-            type_check_(state, envs, lin, linearizer, rt, ty)
+            inject_pat_vars(pat, &mut env);
+            type_check_(state, env, 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, envs.clone(), lin, linearizer.scope(), e, arr)?;
-            type_check_(state, envs, lin, linearizer, t, src)
+            type_check_(state, env.clone(), lin, linearizer.scope(), e, arr)?;
+            type_check_(state, env, lin, linearizer, t, src)
         }
         Term::Switch(exp, cases, default) => {
             // Currently, if it has a default value, we typecheck the whole thing as
@@ -663,7 +598,7 @@ fn type_check_<L: Linearizer>(
             for case in cases.values() {
                 type_check_(
                     state,
-                    envs.clone(),
+                    env.clone(),
                     lin,
                     linearizer.scope(),
                     case,
@@ -673,7 +608,7 @@ fn type_check_<L: Linearizer>(
 
             let row = match default {
                 Some(t) => {
-                    type_check_(state, envs.clone(), lin, linearizer.scope(), t, res.clone())?;
+                    type_check_(state, env.clone(), lin, linearizer.scope(), t, res.clone())?;
                     state.table.fresh_unif_var()
                 }
                 None => cases.iter().try_fold(
@@ -685,10 +620,10 @@ fn type_check_<L: Linearizer>(
             };
 
             unify(state, ty, res).map_err(|err| err.into_typecheck_err(state, rt.pos))?;
-            type_check_(state, envs, lin, linearizer, exp, mk_tyw_enum!(; row))
+            type_check_(state, env, lin, linearizer, exp, mk_tyw_enum!(; row))
         }
         Term::Var(x) => {
-            let x_ty = envs
+            let x_ty = env
                 .get(x)
                 .ok_or_else(|| TypecheckError::UnboundIdentifier(x.clone(), *pos))?;
 
@@ -706,7 +641,7 @@ fn type_check_<L: Linearizer>(
             let ty_dyn = state.table.fresh_unif_var();
 
             for id in stat_map.keys() {
-                envs.insert(id.clone(), ty_dyn.clone());
+                env.insert(id.clone(), ty_dyn.clone());
                 linearizer.retype_ident(lin, id, ty_dyn.clone())
             }
 
@@ -715,7 +650,7 @@ fn type_check_<L: Linearizer>(
                 .try_for_each(|(_, t)| -> Result<(), TypecheckError> {
                     type_check_(
                         state,
-                        envs.clone(),
+                        env.clone(),
                         lin,
                         linearizer.scope(),
                         t,
@@ -732,8 +667,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(), &envs, true);
-                    envs.insert(id.clone(), tyw.clone());
+                    let tyw = binding_type(state, rt.as_ref(), &env, true);
+                    env.insert(id.clone(), tyw.clone());
                     linearizer.retype_ident(lin, id, tyw);
                 }
             }
@@ -751,7 +686,7 @@ fn type_check_<L: Linearizer>(
                     .try_for_each(|(_, t)| -> Result<(), TypecheckError> {
                         type_check_(
                             state,
-                            envs.clone(),
+                            env.clone(),
                             lin,
                             linearizer.scope(),
                             t,
@@ -766,14 +701,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() {
-                            envs.get(id).unwrap()
+                            env.get(id).unwrap()
                         } else {
                             state.table.fresh_unif_var()
                         };
 
                         type_check_(
                             state,
-                            envs.clone(),
+                            env.clone(),
                             lin,
                             linearizer.scope(),
                             field,
@@ -791,7 +726,7 @@ fn type_check_<L: Linearizer>(
         Term::Op1(op, t) => {
             let (ty_arg, ty_res) = get_uop_type(state, op)?;
 
-            type_check_(state, envs.clone(), lin, linearizer.scope(), t, ty_arg)?;
+            type_check_(state, env.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))
@@ -800,8 +735,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, envs.clone(), lin, linearizer.scope(), t1, ty_arg1)?;
-            type_check_(state, envs, lin, linearizer, t2, ty_arg2)
+            type_check_(state, env.clone(), lin, linearizer.scope(), t1, ty_arg1)?;
+            type_check_(state, env, lin, linearizer, t2, ty_arg2)
         }
         Term::OpN(op, args) => {
             let (tys_op, ty_ret) = get_nop_type(state, op)?;
@@ -810,7 +745,7 @@ fn type_check_<L: Linearizer>(
 
             tys_op.into_iter().zip(args.iter()).try_for_each(
                 |(ty_t, t)| -> Result<_, TypecheckError> {
-                    type_check_(state, envs.clone(), lin, linearizer.scope(), t, ty_t)?;
+                    type_check_(state, env.clone(), lin, linearizer.scope(), t, ty_t)?;
                     Ok(())
                 },
             )?;
@@ -822,7 +757,7 @@ fn type_check_<L: Linearizer>(
                 .iter()
                 .chain(meta.types.iter())
                 .try_for_each(|ty| {
-                    walk_type(state, envs.clone(), lin, linearizer.scope_meta(), &ty.types)
+                    walk_type(state, env.clone(), lin, linearizer.scope_meta(), &ty.types)
                 })?;
 
             match meta {
@@ -836,7 +771,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, envs, lin, linearizer, t, instantiated)
+                    type_check_(state, env, 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
@@ -853,7 +788,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, envs, lin, linearizer, t)
+                        walk(state, env, 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,
@@ -863,9 +798,7 @@ 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, envs, lin, linearizer, t, ty)
-                }
+                MetaValue { value: Some(t), .. } => type_check_(state, env, 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
@@ -877,7 +810,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, envs, lin, linearizer, t, ty),
+        Term::Sealed(_, t) => type_check_(state, env, 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
@@ -887,12 +820,8 @@ 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(&Envs::from_envs(&envs)),
-                Some(state.resolver),
-            )
-            .into();
+            let ty_import: TypeWrapper =
+                apparent_type(t.as_ref(), Some(&env), Some(state.resolver)).into();
             unify(state, ty, ty_import).map_err(|err| err.into_typecheck_err(state, rt.pos))
         }
     }
@@ -914,8 +843,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, envs: &Envs, strict: bool) -> TypeWrapper {
-    let ty_apt = apparent_type(t, Some(envs), Some(state.resolver));
+fn binding_type(state: &mut State, t: &Term, env: &Environment, strict: bool) -> TypeWrapper {
+    let ty_apt = apparent_type(t, Some(env), Some(state.resolver));
 
     match ty_apt {
         ApparentType::Annotated(ty) if strict => {
@@ -1005,7 +934,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,
-    envs: Option<&Envs>,
+    env: Option<&Environment>,
     resolver: Option<&dyn ImportResolver>,
 ) -> ApparentType {
     match t {
@@ -1018,7 +947,7 @@ pub fn apparent_type(
             ApparentType::Annotated(contracts.get(0).unwrap().types.clone())
         }
         Term::MetaValue(MetaValue { value: Some(v), .. }) => {
-            apparent_type(v.as_ref(), envs, resolver)
+            apparent_type(v.as_ref(), env, resolver)
         }
         Term::Num(_) => ApparentType::Inferred(Types(AbsType::Num())),
         Term::Bool(_) => ApparentType::Inferred(Types(AbsType::Bool())),
@@ -1027,7 +956,7 @@ pub fn apparent_type(
         Term::Array(..) => {
             ApparentType::Approximated(Types(AbsType::Array(Box::new(Types(AbsType::Dyn())))))
         }
-        Term::Var(id) => envs
+        Term::Var(id) => env
             .and_then(|envs| envs.get(id))
             .map(ApparentType::FromEnv)
             .unwrap_or(ApparentType::Approximated(Types(AbsType::Dyn()))),
@@ -1036,7 +965,7 @@ pub fn apparent_type(
                 let t = r
                     .get(*f)
                     .expect("Internal error: resolved import not found during typechecking.");
-                apparent_type(&t.term, envs, Some(r))
+                apparent_type(&t.term, env, Some(r))
             } else {
                 ApparentType::Approximated(Types(AbsType::Dyn()))
             }
diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs
index 2843a3a6..2aff59bd 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::GlobalEnv;
+    use nickel_lang::cache::Envs;
 
     let mut cache = Cache::new();
-    let GlobalEnv { eval_env, type_env } = cache.prepare_stdlib().unwrap();
+    let Envs { 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::{GlobalEnv, Cache, ImportResolver},
+                cache::{Envs, 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 GlobalEnv{eval_env, type_env} = cache.prepare_stdlib().unwrap();
+            let Envs {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 8f86e747..b6554864 100644
--- a/src/eval/tests.rs
+++ b/src/eval/tests.rs
@@ -285,10 +285,10 @@ fn interpolation_nested() {
 }
 
 #[test]
-fn global_env() {
-    let mut global_env = Environment::new();
+fn initial_env() {
+    let mut initial_env = Environment::new();
     let mut resolver = DummyResolver {};
-    global_env.insert(
+    initial_env.insert(
         Ident::from("g"),
         Thunk::new(
             Closure::atomic_closure(Term::Num(1.0).into()),
@@ -298,20 +298,20 @@ fn global_env() {
 
     let t = mk_term::let_in("x", Term::Num(2.0), mk_term::var("x"));
     assert_eq!(
-        eval(t, &global_env, &mut resolver).map(Term::from),
+        eval(t, &initial_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, &global_env, &mut resolver).map(Term::from),
+        eval(t, &initial_env, &mut resolver).map(Term::from),
         Ok(Term::Num(1.0))
     );
 
-    // Shadowing of global environment
+    // Shadowing of the initial environment
     let t = mk_term::let_in("g", Term::Num(2.0), mk_term::var("g"));
     assert_eq!(
-        eval(t, &global_env, &mut resolver).map(Term::from),
+        eval(t, &initial_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 global_env = mk_env(vec![
+    let initial_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, &global_env, &env),
+        subst(t, &initial_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, &global_env, &env),
+        subst(t, &initial_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/typecheck_fail.rs b/tests/typecheck_fail.rs
index 105ad46e..c25b00ad 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> {
-    type_check_in_env(rt, &Environment::new(), &mut DummyResolver {}).map(|_| ())
+    typecheck::type_check(rt, Environment::new(), &mut DummyResolver {}).map(|_| ())
 }
 
 fn type_check_expr(s: impl std::string::ToString) -> Result<(), TypecheckError> {