Repository: tweag/nickel
Pull request #866 (2022-10-11 15:41:53)

Fix testlib ignoring last test
The order of the argument of `foldl` were reversed in the test library, meaning that the `Assert` contract was applied to the accumulator, and not to the current test. There is no consequence for all tests but the last one, which doesn't trigger a blame error even if it evaluates to `false`.


Patch:
diff --git a/lsp/nls/src/linearization/mod.rs b/lsp/nls/src/linearization/mod.rs
index dbbd6364..91cc82e4 100644
--- a/lsp/nls/src/linearization/mod.rs
+++ b/lsp/nls/src/linearization/mod.rs
@@ -9,7 +9,7 @@ use nickel_lang::{
     typecheck::{
         linearization::{Linearization, Linearizer, Scope, ScopeId},
         reporting::{to_type, NameReg},
-        TypeWrapper,
+        TypeWrapper, UnifTable,
     },
     types::AbsType,
 };
@@ -90,12 +90,10 @@ impl Default for AnalysisHost {
     }
 }
 
-use nickel_lang::typecheck::Extra;
-use nickel_lang::types::Types;
 impl Linearizer for AnalysisHost {
     type Building = Building;
     type Completed = Completed;
-    type CompletionExtra = Extra;
+    type CompletionExtra = (UnifTable, HashMap<usize, Ident>);
 
     fn add_term(
         &mut self,
@@ -349,11 +347,7 @@ impl Linearizer for AnalysisHost {
     fn complete(
         self,
         mut lin: Linearization<Building>,
-        Extra {
-            table,
-            names: reported_names,
-            wildcards,
-        }: Extra,
+        (table, reported_names): (UnifTable, HashMap<usize, Ident>),
     ) -> Linearization<Completed> {
         debug!("linearizing");
 
@@ -396,12 +390,6 @@ impl Linearizer for AnalysisHost {
                 id_mapping.insert(*id, index);
             });
 
-        fn transform_wildcard(wildcars: &Vec<Types>, t: Types) -> Types {
-            match t {
-                Types(AbsType::Wildcard(i)) => wildcars.get(i).unwrap_or(&t).clone(),
-                _ => t,
-            }
-        }
         // resolve types
         let lin_ = linearization
             .into_iter()
@@ -422,10 +410,6 @@ impl Linearizer for AnalysisHost {
                     meta,
                 },
             )
-            .map(|item| LinearizationItem {
-                ty: transform_wildcard(&wildcards, item.ty),
-                ..item
-            })
             .collect();
 
         Linearization::new(Completed::new(lin_, scope, id_mapping))
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 3e00a0d5..d4c7dc1d 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -133,7 +133,7 @@ impl Default for EvalMode {
 }
 
 // The current state of the Nickel virtual machine.
-pub struct VirtualMachine<R: ImportResolver> {
+pub struct VirtualMachine<'a, R: ImportResolver> {
     // Behavior of evaluation with respect to metavalues.
     eval_mode: EvalMode,
     // The main stack, storing arguments, thunks and pending computations.
@@ -141,11 +141,11 @@ pub struct VirtualMachine<R: ImportResolver> {
     // The call stack, for error reporting.
     call_stack: CallStack,
     // The interface used to fetch imports.
-    import_resolver: R,
+    import_resolver: &'a mut R,
 }
 
-impl<R: ImportResolver> VirtualMachine<R> {
-    pub fn new(import_resolver: R) -> Self {
+impl<'a, R: ImportResolver> VirtualMachine<'a, R> {
+    pub fn new(import_resolver: &'a mut R) -> Self {
         VirtualMachine {
             import_resolver,
             eval_mode: Default::default(),
@@ -169,14 +169,6 @@ impl<R: ImportResolver> VirtualMachine<R> {
         self.eval_mode = new_mode;
     }
 
-    pub fn import_resolver(&self) -> &R {
-        &self.import_resolver
-    }
-
-    pub fn import_resolver_mut(&mut self) -> &mut R {
-        &mut self.import_resolver
-    }
-
     /// Evaluate a Nickel term. Wrapper around [eval_closure] that starts from an empty local
     /// environment and drops the final environment.
     pub fn eval(&mut self, t0: RichTerm, initial_env: &Environment) -> Result<RichTerm, EvalError> {
diff --git a/src/eval/operation.rs b/src/eval/operation.rs
index 0fdeba9e..0eb45c98 100644
--- a/src/eval/operation.rs
+++ b/src/eval/operation.rs
@@ -85,7 +85,7 @@ impl std::fmt::Debug for OperationCont {
     }
 }
 
-impl<R: ImportResolver> VirtualMachine<R> {
+impl<'a, R: ImportResolver> VirtualMachine<'a, R> {
     /// Process to the next step of the evaluation of an operation.
     ///
     /// Depending on the content of the stack, it either starts the evaluation of the first argument,
@@ -2726,7 +2726,8 @@ mod tests {
     #[test]
     fn ite_operation() {
         let cont = OperationCont::Op1(UnaryOp::Ite(), TermPos::None);
-        let mut vm = VirtualMachine::new(DummyResolver {});
+        let mut resolver = DummyResolver {};
+        let mut vm = VirtualMachine::new(&mut resolver);
 
         vm.stack.push_arg(
             Closure::atomic_closure(Term::Num(5.0).into()),
@@ -2771,7 +2772,8 @@ mod tests {
             body: Term::Num(7.0).into(),
             env: Environment::new(),
         };
-        let mut vm = VirtualMachine::new(DummyResolver {});
+        let mut resolver = DummyResolver {};
+        let mut vm = VirtualMachine::new(&mut resolver);
         vm.stack.push_op_cont(cont, 0, TermPos::None);
 
         clos = vm.continuate_operation(clos).unwrap();
@@ -2815,7 +2817,8 @@ mod tests {
             TermPos::None,
         );
 
-        let mut vm = VirtualMachine::new(DummyResolver {});
+        let mut resolver = DummyResolver {};
+        let mut vm = VirtualMachine::new(&mut resolver);
         let mut clos = Closure {
             body: Term::Num(6.0).into(),
             env: Environment::new(),
diff --git a/src/parser/lexer.rs b/src/parser/lexer.rs
index b7f4a052..543c0850 100644
--- a/src/parser/lexer.rs
+++ b/src/parser/lexer.rs
@@ -33,10 +33,6 @@ use crate::parser::error::{LexicalError, ParseError};
 use logos::Logos;
 use std::ops::Range;
 
-// **IMPORTANT**
-// When adding or removing tokens that might be parsed as identifiers,
-// please update the KEYWORDS array
-// list
 /// The tokens in normal mode.
 #[derive(Logos, Debug, PartialEq, Clone)]
 pub enum NormalToken<'input> {
@@ -294,12 +290,6 @@ pub enum NormalToken<'input> {
     LineComment,
 }
 
-pub const KEYWORDS: &[&str] = &[
-    "Dyn", "Num", "Bool", "Str", "Array", "if", "then", "else", "forall", "in", "let", "rec",
-    "switch", "null", "true", "false", "fun", "import", "merge", "default", "doc", "optional",
-    "priority", "force",
-];
-
 /// The tokens in string mode.
 #[derive(Logos, Debug, PartialEq, Clone)]
 pub enum StringToken<'input> {
diff --git a/src/pretty.rs b/src/pretty.rs
index dc7e58b7..7aefab56 100644
--- a/src/pretty.rs
+++ b/src/pretty.rs
@@ -1,5 +1,4 @@
 use crate::destruct::{self, Destruct};
-use crate::parser::lexer::KEYWORDS;
 use crate::term::{BinaryOp, MetaValue, RichTerm, Term, UnaryOp};
 use crate::types::{AbsType, Types};
 pub use pretty::{DocAllocator, DocBuilder, Pretty};
@@ -46,7 +45,7 @@ where
 {
     fn quote_if_needed(&'a self, id: &crate::identifier::Ident) -> DocBuilder<'a, Self, A> {
         let reg = Regex::new("^_?[a-zA-Z][_a-zA-Z0-9-]*$").unwrap();
-        if reg.is_match(id.as_ref()) && !KEYWORDS.contains(&id.as_ref()) {
+        if reg.is_match(id.as_ref()) {
             self.as_string(id)
         } else {
             self.as_string(id).double_quotes()
diff --git a/src/program.rs b/src/program.rs
index 123e48bf..f09f6629 100644
--- a/src/program.rs
+++ b/src/program.rs
@@ -22,7 +22,6 @@
 //! 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::eval::VirtualMachine;
 use crate::identifier::Ident;
 use crate::parser::lexer::Lexer;
 use crate::term::{RichTerm, Term};
@@ -40,8 +39,8 @@ use std::result::Result;
 pub struct Program {
     /// The id of the program source in the file database.
     main_id: FileId,
-    /// The state of the Nickel virtual machine.
-    vm: VirtualMachine<Cache>,
+    /// The cache holding the sources and parsed terms of the main source as well as imports.
+    cache: Cache,
 }
 
 impl Program {
@@ -53,9 +52,8 @@ impl Program {
     pub fn new_from_file(path: impl Into<OsString>) -> std::io::Result<Program> {
         let mut cache = Cache::new(ErrorTolerance::Strict);
         let main_id = cache.add_file(path)?;
-        let vm = VirtualMachine::new(cache);
 
-        Ok(Program { main_id, vm })
+        Ok(Program { main_id, cache })
     }
 
     /// Create a program by reading it from a generic source.
@@ -66,9 +64,8 @@ impl Program {
     {
         let mut cache = Cache::new(ErrorTolerance::Strict);
         let main_id = cache.add_source(source_name, source)?;
-        let vm = VirtualMachine::new(cache);
 
-        Ok(Program { main_id, vm })
+        Ok(Program { main_id, cache })
     }
 
     /// Retrieve the parsed term and typecheck it, and generate a fresh initial environment. Return
@@ -77,56 +74,49 @@ impl Program {
         let Envs {
             eval_env,
             type_ctxt,
-        } = self.vm.import_resolver_mut().prepare_stdlib()?;
-        self.vm
-            .import_resolver_mut()
-            .prepare(self.main_id, &type_ctxt)?;
-        Ok((
-            self.vm.import_resolver().get(self.main_id).unwrap(),
-            eval_env,
-        ))
+        } = self.cache.prepare_stdlib()?;
+        self.cache.prepare(self.main_id, &type_ctxt)?;
+        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()?;
-        self.vm.reset();
-        self.vm.eval(t, &initial_env).map_err(|e| e.into())
+        let mut vm = eval::VirtualMachine::new(&mut self.cache);
+        vm.eval(t, &initial_env).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()?;
-        self.vm.reset();
-        self.vm.eval_full(t, &initial_env).map_err(|e| e.into())
+        let mut vm = eval::VirtualMachine::new(&mut self.cache);
+        vm.eval_full(t, &initial_env).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()?;
-        self.vm.reset();
-        self.vm.eval_deep(t, &initial_env).map_err(|e| e.into())
+        let mut vm = eval::VirtualMachine::new(&mut self.cache);
+        vm.eval_deep(t, &initial_env).map_err(|e| e.into())
     }
 
     /// Wrapper for [`query`].
     pub fn query(&mut self, path: Option<String>) -> Result<Term, Error> {
-        let initial_env = self.vm.import_resolver_mut().prepare_stdlib()?;
-        query(&mut self.vm, self.main_id, &initial_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.vm.import_resolver_mut().parse(self.main_id)?;
-        self.vm.import_resolver_mut().load_stdlib()?;
-        let initial_env = self.vm.import_resolver().mk_type_ctxt().expect("program::typecheck(): stdlib has been loaded but was not found in cache on mk_types_env()");
-        self.vm
-            .import_resolver_mut()
+        self.cache.parse(self.main_id)?;
+        self.cache.load_stdlib()?;
+        let initial_env = self.cache.mk_type_ctxt().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.vm
-            .import_resolver_mut()
+        self.cache
             .typecheck(self.main_id, &initial_env)
             .map_err(|cache_err| {
                 cache_err.unwrap_error("program::typecheck(): expected source to be parsed")
@@ -139,18 +129,18 @@ impl Program {
     where
         E: ToDiagnostic<FileId>,
     {
-        report(self.vm.import_resolver_mut(), error)
+        report(&mut self.cache, error)
     }
 
     /// Create a markdown file with documentation for the specified program in `.nickel/doc/program_main_file_name.md`
     #[cfg(feature = "doc")]
     pub fn output_doc(&mut self, out: &mut dyn std::io::Write) -> Result<(), Error> {
-        doc::output_doc(self.vm.import_resolver_mut(), self.main_id, out)
+        doc::output_doc(&mut self.cache, self.main_id, out)
     }
 
     #[cfg(debug_assertions)]
     pub fn set_skip_stdlib(&mut self) {
-        self.vm.import_resolver_mut().skip_stdlib = true;
+        self.cache.skip_stdlib = true;
     }
 
     pub fn pprint_ast(
@@ -161,10 +151,10 @@ impl Program {
         use crate::pretty::*;
         use pretty::BoxAllocator;
 
-        let Program { ref main_id, vm } = self;
+        let Program { ref main_id, cache } = self;
         let allocator = BoxAllocator;
 
-        let rt = vm.import_resolver().parse_nocache(*main_id)?.0;
+        let rt = cache.parse_nocache(*main_id)?.0;
         let rt = if apply_transforms {
             crate::transform::transform(rt, None).unwrap()
         } else {
@@ -192,19 +182,18 @@ impl Program {
 //TODO: not sure where this should go. It seems to embed too much logic to be in `Cache`, but is
 //common to both `Program` and `Repl`. Leaving it here as a stand-alone function for now
 pub fn query(
-    vm: &mut VirtualMachine<Cache>,
+    cache: &mut Cache,
     file_id: FileId,
     initial_env: &Envs,
     path: Option<String>,
 ) -> Result<Term, Error> {
-    vm.import_resolver_mut()
-        .prepare(file_id, &initial_env.type_ctxt)?;
+    cache.prepare(file_id, &initial_env.type_ctxt)?;
     let t = if let Some(p) = path {
         // Parsing `y.path`. We `seq` it to force the evaluation of the underlying value,
         // which can be then showed to the user. The newline gives better messages in case of
         // errors.
         let source = format!("x.{}", p);
-        let query_file_id = vm.import_resolver_mut().add_tmp("<query>", source.clone());
+        let query_file_id = cache.add_tmp("<query>", source.clone());
         let new_term =
             parser::grammar::TermParser::new().parse_term(query_file_id, Lexer::new(&source))?;
 
@@ -213,15 +202,15 @@ pub fn query(
         eval::env_add(
             &mut env,
             Ident::from("x"),
-            vm.import_resolver().get_owned(file_id).unwrap(),
+            cache.get_owned(file_id).unwrap(),
             eval::Environment::new(),
         );
         eval::subst(new_term, &eval::Environment::new(), &env)
     } else {
-        vm.import_resolver().get_owned(file_id).unwrap()
+        cache.get_owned(file_id).unwrap()
     };
 
-    vm.reset();
+    let mut vm = eval::VirtualMachine::new(cache);
     Ok(vm.eval_meta(t, &initial_env.eval_env)?.into())
 }
 
diff --git a/src/repl/mod.rs b/src/repl/mod.rs
index c48027a9..7f38c911 100644
--- a/src/repl/mod.rs
+++ b/src/repl/mod.rs
@@ -8,7 +8,6 @@
 //! formatting), etc.
 use crate::cache::{Cache, Envs, ErrorTolerance};
 use crate::error::{Error, EvalError, IOError, ParseError, ParseErrors, ReplError};
-use crate::eval::VirtualMachine;
 use crate::identifier::Ident;
 use crate::parser::{grammar, lexer, ExtendedTerm};
 use crate::term::{RichTerm, Term};
@@ -68,6 +67,8 @@ pub trait Repl {
 
 /// Standard implementation of the REPL backend.
 pub struct ReplImpl {
+    /// The underlying cache, storing input, loaded files and parsed terms.
+    cache: Cache,
     /// The parser, supporting toplevel let declaration.
     parser: grammar::ExtendedTermParser,
     /// The current environment (for evaluation and typing). Contain the initial environment with
@@ -76,38 +77,35 @@ pub struct ReplImpl {
     /// The initial typing context, without the toplevel declarations made inside the REPL. Used to
     /// typecheck imports in a fresh environment.
     initial_type_ctxt: typecheck::Context,
-    /// The state of the Nickel virtual machine, holding a cache of loaded files and parsed terms.
-    vm: VirtualMachine<Cache>,
 }
 
 impl ReplImpl {
     /// Create a new empty REPL.
     pub fn new() -> Self {
         ReplImpl {
+            cache: Cache::new(ErrorTolerance::Strict),
             parser: grammar::ExtendedTermParser::new(),
             env: Envs::new(),
             initial_type_ctxt: typecheck::Context::new(),
-            vm: VirtualMachine::new(Cache::new(ErrorTolerance::Strict)),
         }
     }
 
     /// Load and process the stdlib, and use it to populate the eval environment as well as the
     /// typing environment.
     pub fn load_stdlib(&mut self) -> Result<(), Error> {
-        self.env = self.vm.import_resolver_mut().prepare_stdlib()?;
+        self.env = self.cache.prepare_stdlib()?;
         self.initial_type_ctxt = self.env.type_ctxt.clone();
         Ok(())
     }
 
     fn eval_(&mut self, exp: &str, eval_full: bool) -> Result<EvalResult, Error> {
-        self.vm.reset();
         let eval_function = if eval_full {
             eval::VirtualMachine::eval_full
         } else {
             eval::VirtualMachine::eval
         };
 
-        let file_id = self.vm.import_resolver_mut().add_string(
+        let file_id = self.cache.add_string(
             format!("repl-input-{}", InputNameCounter::next()),
             String::from(exp),
         );
@@ -131,21 +129,13 @@ impl ReplImpl {
             id: Option<Ident>,
             t: RichTerm,
         ) -> Result<RichTerm, Error> {
-            let (t, pending) =
-                import_resolution::resolve_imports(t, repl_impl.vm.import_resolver_mut())?;
+            let (t, pending) = import_resolution::resolve_imports(t, &mut repl_impl.cache)?;
             for id in &pending {
-                repl_impl
-                    .vm
-                    .import_resolver_mut()
-                    .resolve_imports(*id)
-                    .unwrap();
+                repl_impl.cache.resolve_imports(*id).unwrap();
             }
 
-            let wildcards = typecheck::type_check(
-                &t,
-                repl_impl.env.type_ctxt.clone(),
-                repl_impl.vm.import_resolver(),
-            )?;
+            let wildcards =
+                typecheck::type_check(&t, repl_impl.env.type_ctxt.clone(), &repl_impl.cache)?;
 
             if let Some(id) = id {
                 typecheck::env_add(
@@ -153,7 +143,7 @@ impl ReplImpl {
                     id.clone(),
                     &t,
                     &repl_impl.env.type_ctxt.term_env,
-                    repl_impl.vm.import_resolver(),
+                    &repl_impl.cache,
                 );
                 repl_impl
                     .env
@@ -165,8 +155,7 @@ impl ReplImpl {
 
             for id in &pending {
                 repl_impl
-                    .vm
-                    .import_resolver_mut()
+                    .cache
                     .typecheck(*id, &repl_impl.initial_type_ctxt)
                     .map_err(|cache_err| {
                         cache_err.unwrap_error("repl::eval_(): expected imports to be parsed")
@@ -177,8 +166,7 @@ impl ReplImpl {
                 .map_err(|err| Error::ParseErrors(err.into()))?;
             for id in &pending {
                 repl_impl
-                    .vm
-                    .import_resolver_mut()
+                    .cache
                     .transform(*id)
                     .unwrap_or_else(|_| panic!("repl::eval_(): expected imports to be parsed"));
             }
@@ -189,7 +177,12 @@ impl ReplImpl {
         match term {
             ExtendedTerm::RichTerm(t) => {
                 let t = prepare(self, None, t)?;
-                Ok(eval_function(&mut self.vm, t, &self.env.eval_env)?.into())
+                Ok(eval_function(
+                    &mut eval::VirtualMachine::new(&mut self.cache),
+                    t,
+                    &self.env.eval_env,
+                )?
+                .into())
             }
             ExtendedTerm::ToplevelLet(id, t) => {
                 let t = prepare(self, Some(id.clone()), t)?;
@@ -212,12 +205,11 @@ impl Repl for ReplImpl {
 
     fn load(&mut self, path: impl AsRef<OsStr>) -> Result<RichTerm, Error> {
         let file_id = self
-            .vm
-            .import_resolver_mut()
+            .cache
             .add_file(OsString::from(path.as_ref()))
             .map_err(IOError::from)?;
-        self.vm.import_resolver_mut().parse(file_id)?;
-        let RichTerm { term, pos } = self.vm.import_resolver().get_ref(file_id).unwrap();
+        self.cache.parse(file_id)?;
+        let RichTerm { term, pos } = self.cache.get_ref(file_id).unwrap();
 
         // Check that the entry is a record, which is a precondition of transform_inner
         match term.as_ref() {
@@ -229,24 +221,20 @@ impl Repl for ReplImpl {
                 )))
             }
         };
-        self.vm
-            .import_resolver_mut()
-            .transform_inner(file_id)
-            .map_err(|err| {
-                err.unwrap_error("load(): expected term to be parsed before transformation")
-            })?;
-
-        let term = self.vm.import_resolver().get_owned(file_id).unwrap();
-        let (term, pending) =
-            import_resolution::resolve_imports(term, self.vm.import_resolver_mut())?;
+        self.cache.transform_inner(file_id).map_err(|err| {
+            err.unwrap_error("load(): expected term to be parsed before transformation")
+        })?;
+
+        let term = self.cache.get_owned(file_id).unwrap();
+        let (term, pending) = import_resolution::resolve_imports(term, &mut self.cache)?;
         for id in &pending {
-            self.vm.import_resolver_mut().resolve_imports(*id).unwrap();
+            self.cache.resolve_imports(*id).unwrap();
         }
         typecheck::env_add_term(
             &mut self.env.type_ctxt.type_env,
             &term,
             &self.env.type_ctxt.term_env,
-            self.vm.import_resolver(),
+            &self.cache,
         )
         .unwrap();
         eval::env_add_term(&mut self.env.eval_env, term.clone()).unwrap();
@@ -255,19 +243,14 @@ impl Repl for ReplImpl {
     }
 
     fn typecheck(&mut self, exp: &str) -> Result<Types, Error> {
-        let file_id = self
-            .vm
-            .import_resolver_mut()
-            .add_tmp("<repl-typecheck>", String::from(exp));
+        let file_id = self.cache.add_tmp("<repl-typecheck>", String::from(exp));
         // We ignore non fatal errors while type checking.
-        let (term, _) = self.vm.import_resolver().parse_nocache(file_id)?;
-        let (term, pending) =
-            import_resolution::resolve_imports(term, self.vm.import_resolver_mut())?;
+        let (term, _) = self.cache.parse_nocache(file_id)?;
+        let (term, pending) = import_resolution::resolve_imports(term, &mut self.cache)?;
         for id in &pending {
-            self.vm.import_resolver_mut().resolve_imports(*id).unwrap();
+            self.cache.resolve_imports(*id).unwrap();
         }
-        let wildcards =
-            typecheck::type_check(&term, self.env.type_ctxt.clone(), self.vm.import_resolver())?;
+        let wildcards = typecheck::type_check(&term, self.env.type_ctxt.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
@@ -285,7 +268,7 @@ impl Repl for ReplImpl {
         Ok(typecheck::apparent_type(
             term.as_ref(),
             Some(&self.env.type_ctxt.type_env),
-            Some(self.vm.import_resolver()),
+            Some(&self.cache),
         )
         .into())
     }
@@ -293,15 +276,12 @@ impl Repl for ReplImpl {
     fn query(&mut self, exp: &str) -> Result<Term, Error> {
         use crate::program;
 
-        let file_id = self
-            .vm
-            .import_resolver_mut()
-            .add_tmp("<repl-query>", String::from(exp));
-        program::query(&mut self.vm, file_id, &self.env, None)
+        let file_id = self.cache.add_tmp("<repl-query>", String::from(exp));
+        program::query(&mut self.cache, file_id, &self.env, None)
     }
 
     fn cache_mut(&mut self) -> &mut Cache {
-        self.vm.import_resolver_mut()
+        &mut self.cache
     }
 }
 
diff --git a/src/serialize.rs b/src/serialize.rs
index c744091c..4fa5365f 100644
--- a/src/serialize.rs
+++ b/src/serialize.rs
@@ -333,7 +333,7 @@ mod tests {
                     .unwrap();
 
             assert_eq!(
-                VirtualMachine::new(DummyResolver {})
+                VirtualMachine::new(&mut DummyResolver {})
                     .eval(
                         mk_term::op2(BinaryOp::Eq(), from_json, evaluated.clone()),
                         &Environment::new(),
@@ -342,7 +342,7 @@ mod tests {
                 Ok(Term::Bool(true))
             );
             assert_eq!(
-                VirtualMachine::new(DummyResolver {})
+                VirtualMachine::new(&mut DummyResolver {})
                     .eval(
                         mk_term::op2(BinaryOp::Eq(), from_yaml, evaluated.clone()),
                         &Environment::new(),
@@ -351,7 +351,7 @@ mod tests {
                 Ok(Term::Bool(true))
             );
             assert_eq!(
-                VirtualMachine::new(DummyResolver {})
+                VirtualMachine::new(&mut DummyResolver {})
                     .eval(
                         mk_term::op2(BinaryOp::Eq(), from_toml, evaluated),
                         &Environment::new(),
diff --git a/src/typecheck/mod.rs b/src/typecheck/mod.rs
index e226691b..44ac65b5 100644
--- a/src/typecheck/mod.rs
+++ b/src/typecheck/mod.rs
@@ -195,14 +195,6 @@ pub struct State<'a> {
     wildcard_vars: &'a mut Vec<TypeWrapper>,
 }
 
-/// Immutable and owned data, required by the LSP to carry out specific analysis.
-/// It is basically an owned-subset of the typecheking state.
-pub struct Extra {
-    pub table: UnifTable,
-    pub names: HashMap<usize, Ident>,
-    pub wildcards: Vec<Types>,
-}
-
 /// Typecheck a term.
 ///
 /// Return the inferred type in case of success. This is just a wrapper that calls
@@ -233,7 +225,7 @@ pub fn type_check_linearize<LL>(
     mut linearizer: LL,
 ) -> Result<(Wildcards, LL::Completed), TypecheckError>
 where
-    LL: Linearizer<CompletionExtra = Extra>,
+    LL: Linearizer<CompletionExtra = (UnifTable, HashMap<usize, Ident>)>,
 {
     let (mut table, mut names) = (UnifTable::new(), HashMap::new());
     let mut building = Linearization::new(LL::Building::default());
@@ -257,13 +249,8 @@ where
         )?;
     }
 
-    let result = wildcard_vars_to_type(wildcard_vars.clone(), &table);
-    let extra = Extra {
-        table,
-        names,
-        wildcards: result.clone(),
-    };
-    let lin = linearizer.complete(building, extra).into_inner();
+    let result = wildcard_vars_to_type(wildcard_vars, &table);
+    let lin = linearizer.complete(building, (table, names)).into_inner();
 
     Ok((result, lin))
 }
diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs
index db73824b..813c55f9 100644
--- a/utilities/src/lib.rs
+++ b/utilities/src/lib.rs
@@ -148,7 +148,9 @@ pub fn bench_terms<'r>(rts: Vec<Bench<'r>>) -> Box<dyn Fn(&mut Criterion) + 'r>
                             c_local.typecheck(id, &type_ctxt).unwrap();
                         } else {
                             c_local.prepare(id, &type_ctxt).unwrap();
-                            VirtualMachine::new(c_local).eval(t, &eval_env).unwrap();
+                            VirtualMachine::new(&mut c_local)
+                                .eval(t, &eval_env)
+                                .unwrap();
                         }
                     },
                     criterion::BatchSize::LargeInput,
@@ -220,7 +222,7 @@ macro_rules! ncl_bench_group {
                                 c_local.typecheck(id, &type_ctxt).unwrap();
                             } else {
                                 c_local.prepare(id, &type_ctxt).unwrap();
-                                VirtualMachine::new(c_local).eval(t, &eval_env).unwrap();
+                                VirtualMachine::new(&mut c_local).eval(t, &eval_env).unwrap();
                             }
                         },
                         criterion::BatchSize::LargeInput,


Test patch:
diff --git a/src/eval/tests.rs b/src/eval/tests.rs
index e50dee13..ae224ee4 100644
--- a/src/eval/tests.rs
+++ b/src/eval/tests.rs
@@ -11,7 +11,7 @@ use codespan::Files;
 
 /// Evaluate a term without import support.
 fn eval_no_import(t: RichTerm) -> Result<Term, EvalError> {
-    VirtualMachine::new(DummyResolver {})
+    VirtualMachine::new(&mut DummyResolver {})
         .eval(t, &Environment::new())
         .map(Term::from)
 }
@@ -147,22 +147,19 @@ fn merge_incompatible_defaults() {
 
 #[test]
 fn imports() {
-    let mut vm = VirtualMachine::new(SimpleResolver::new());
-    vm.import_resolver_mut()
-        .add_source(String::from("two"), String::from("1 + 1"));
-    vm.import_resolver_mut()
-        .add_source(String::from("lib"), String::from("{f = true}"));
-    vm.import_resolver_mut()
-        .add_source(String::from("bad"), String::from("^$*/.23ab 0°@"));
-    vm.import_resolver_mut().add_source(
+    let mut resolver = SimpleResolver::new();
+    resolver.add_source(String::from("two"), String::from("1 + 1"));
+    resolver.add_source(String::from("lib"), String::from("{f = true}"));
+    resolver.add_source(String::from("bad"), String::from("^$*/.23ab 0°@"));
+    resolver.add_source(
         String::from("nested"),
         String::from("let x = import \"two\" in x + 1"),
     );
-    vm.import_resolver_mut().add_source(
+    resolver.add_source(
         String::from("cycle"),
         String::from("let x = import \"cycle_b\" in {a = 1, b = x.a}"),
     );
-    vm.import_resolver_mut().add_source(
+    resolver.add_source(
         String::from("cycle_b"),
         String::from("let x = import \"cycle\" in {a = x.a}"),
     );
@@ -171,35 +168,35 @@ fn imports() {
         var: &str,
         import: &str,
         body: RichTerm,
-        vm: &mut VirtualMachine<R>,
+        resolver: &mut R,
     ) -> Result<RichTerm, ImportError>
     where
         R: ImportResolver,
     {
         resolve_imports(
             mk_term::let_in(var, mk_term::import(import), body),
-            vm.import_resolver_mut(),
+            resolver,
         )
         .map(|(t, _)| t)
     }
 
     // let x = import "does_not_exist" in x
-    match mk_import("x", "does_not_exist", mk_term::var("x"), &mut vm).unwrap_err() {
+    match mk_import("x", "does_not_exist", mk_term::var("x"), &mut resolver).unwrap_err() {
         ImportError::IOError(_, _, _) => (),
         _ => assert!(false),
     };
 
     // let x = import "bad" in x
-    match mk_import("x", "bad", mk_term::var("x"), &mut vm).unwrap_err() {
+    match mk_import("x", "bad", mk_term::var("x"), &mut resolver).unwrap_err() {
         ImportError::ParseErrors(_, _) => (),
         _ => assert!(false),
     };
 
     // let x = import "two" in x
-    let mk_import_two = mk_import("x", "two", mk_term::var("x"), &mut vm).unwrap();
-    vm.reset();
+    let mk_import_two = mk_import("x", "two", mk_term::var("x"), &mut resolver).unwrap();
     assert_eq!(
-        vm.eval(mk_import_two, &Environment::new(),)
+        VirtualMachine::new(&mut resolver)
+            .eval(mk_import_two, &Environment::new(),)
             .map(Term::from)
             .unwrap(),
         Term::Num(2.0)
@@ -210,11 +207,11 @@ fn imports() {
         "x",
         "lib",
         mk_term::op1(UnaryOp::StaticAccess(Ident::from("f")), mk_term::var("x")),
-        &mut vm,
+        &mut resolver,
     );
-    vm.reset();
     assert_eq!(
-        vm.eval(mk_import_lib.unwrap(), &Environment::new(),)
+        VirtualMachine::new(&mut resolver)
+            .eval(mk_import_lib.unwrap(), &Environment::new(),)
             .map(Term::from)
             .unwrap(),
         Term::Bool(true)
@@ -287,6 +284,7 @@ fn interpolation_nested() {
 #[test]
 fn initial_env() {
     let mut initial_env = Environment::new();
+    let mut resolver = DummyResolver {};
     initial_env.insert(
         Ident::from("g"),
         Thunk::new(
@@ -297,7 +295,7 @@ fn initial_env() {
 
     let t = mk_term::let_in("x", Term::Num(2.0), mk_term::var("x"));
     assert_eq!(
-        VirtualMachine::new(DummyResolver {})
+        VirtualMachine::new(&mut resolver)
             .eval(t, &initial_env)
             .map(Term::from),
         Ok(Term::Num(2.0))
@@ -305,7 +303,7 @@ fn initial_env() {
 
     let t = mk_term::let_in("x", Term::Num(2.0), mk_term::var("g"));
     assert_eq!(
-        VirtualMachine::new(DummyResolver {})
+        VirtualMachine::new(&mut resolver)
             .eval(t, &initial_env)
             .map(Term::from),
         Ok(Term::Num(1.0))
@@ -314,7 +312,7 @@ fn initial_env() {
     // Shadowing of the initial environment
     let t = mk_term::let_in("g", Term::Num(2.0), mk_term::var("g"));
     assert_eq!(
-        VirtualMachine::new(DummyResolver {})
+        VirtualMachine::new(&mut resolver)
             .eval(t, &initial_env)
             .map(Term::from),
         Ok(Term::Num(2.0))
diff --git a/tests/pass/testlib.ncl b/tests/pass/testlib.ncl
index 33f780e3..0512edf7 100644
--- a/tests/pass/testlib.ncl
+++ b/tests/pass/testlib.ncl
@@ -26,5 +26,5 @@
 
     But will give more precise error reporting when one test fails.
     "%m
-    = array.foldl (fun x y => (x | Assert) && y) true,
+    = array.foldl (fun acc test => (test | Assert) && acc) true,
 }