Repository: tweag/nickel
Pull request #966 (2022-12-05 14:33:07)

Display meta information when providing completion - Rebase by Julien
:warning: This is (supposed to be) exactly the same PR as https://github.com/tweag/nickel/pull/939 but rebased on `master`.

I did my best to fix the conflicts and squash all commits together, but I may or may not have respected the spirit of the original PR.

Please review with caution :pray: 

matthew-healy: One thing to note here is that because the entire PR history has been squashed down, and this PR only contains a single commit, the commit message of this will appear in the project history as "Rename `from` to `new`" which isn't accurate. If this branch ends up being merged then I'd recommend renaming the commit to better reflect its overall contents.
sir4ur0n: Yeah I squashed and kept commit messages that seemed to be useful (you can see them in the commit description), but otherwise I did not rework it
yannham: Thanks for doing that, @sir4ur0n. Unfortunately I have non trivial questions which need to be addressed, so let's wait for @ebresafegaga's return and an up-to-date rebase.
ebresafegaga: It works correctly on VS Code: 
![image](https://user-images.githubusercontent.com/32538936/207840204-76a6e7fe-4b35-48fa-b66b-43e0020344fa.png)

The only difference is that it only shows the type for the item currently selected, unlike `rust-analyzer`: 
![image](https://user-images.githubusercontent.com/32538936/207840863-6e712ac0-ae23-43bf-8f8c-4f7c1edfb2eb.png)


yannham: Interesting. I tried on the previous version of `lsp/metainfo`, before your rebase, and it works there. The difference in the logs seem to be that `kind` is set to `3` everywhere. I agree it shouldn't impact the completion, so maybe it's a coc bug.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5d5fbae6..79ab1a8b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -535,8 +535,8 @@ version = "0.1.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
 dependencies = [
- "quote",
- "syn",
+ "quote 1.0.18",
+ "syn 1.0.95",
 ]
 
 [[package]]
@@ -555,10 +555,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
 dependencies = [
  "convert_case",
- "proc-macro2",
- "quote",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
  "rustc_version",
- "syn",
+ "syn 1.0.95",
 ]
 
 [[package]]
@@ -813,6 +813,12 @@ version = "0.26.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
 
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
 [[package]]
 name = "half"
 version = "1.8.2"
@@ -953,7 +959,7 @@ dependencies = [
  "string_cache",
  "term",
  "tiny-keccak",
- "unicode-xid",
+ "unicode-xid 0.2.3",
 ]
 
 [[package]]
@@ -1040,10 +1046,10 @@ checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d"
 dependencies = [
  "beef",
  "fnv",
- "proc-macro2",
- "quote",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
  "regex-syntax",
- "syn",
+ "syn 1.0.95",
  "utf8-ranges",
 ]
 
@@ -1174,7 +1180,7 @@ dependencies = [
 
 [[package]]
 name = "nickel-lang"
-version = "0.3.0"
+version = "0.3.1"
 dependencies = [
  "ansi_term",
  "assert_matches",
@@ -1207,6 +1213,7 @@ dependencies = [
  "simple-counter",
  "structopt",
  "termimad",
+ "test-generator",
  "toml",
  "typed-arena 2.0.1",
  "void",
@@ -1215,7 +1222,7 @@ dependencies = [
 
 [[package]]
 name = "nickel-lang-lsp"
-version = "0.3.0"
+version = "0.3.1"
 dependencies = [
  "anyhow",
  "assert_matches",
@@ -1241,7 +1248,7 @@ dependencies = [
 
 [[package]]
 name = "nickel-lang-utilities"
-version = "0.3.0"
+version = "0.3.1"
 dependencies = [
  "codespan",
  "criterion",
@@ -1250,7 +1257,7 @@ dependencies = [
 
 [[package]]
 name = "nickel-repl"
-version = "0.3.0"
+version = "0.3.1"
 dependencies = [
  "nickel-lang",
 ]
@@ -1471,9 +1478,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
 dependencies = [
  "pest",
  "pest_meta",
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
+ "syn 1.0.95",
 ]
 
 [[package]]
@@ -1619,9 +1626,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
 dependencies = [
  "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
+ "syn 1.0.95",
  "version_check",
 ]
 
@@ -1631,11 +1638,20 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
 dependencies = [
- "proc-macro2",
- "quote",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
  "version_check",
 ]
 
+[[package]]
+name = "proc-macro2"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
+dependencies = [
+ "unicode-xid 0.1.0",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.39"
@@ -1654,13 +1670,22 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "quote"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
+dependencies = [
+ "proc-macro2 0.4.30",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
 dependencies = [
- "proc-macro2",
+ "proc-macro2 1.0.39",
 ]
 
 [[package]]
@@ -1823,8 +1848,8 @@ version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bb35a55ab810b5c0fe31606fe9b47d1354e4dc519bec0a102655f78ea2b38057"
 dependencies = [
- "quote",
- "syn",
+ "quote 1.0.18",
+ "syn 1.0.95",
 ]
 
 [[package]]
@@ -1896,9 +1921,9 @@ version = "1.0.137"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
+ "syn 1.0.95",
 ]
 
 [[package]]
@@ -1918,9 +1943,9 @@ version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
+ "syn 1.0.95",
 ]
 
 [[package]]
@@ -2085,9 +2110,9 @@ checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
 dependencies = [
  "heck",
  "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
+ "syn 1.0.95",
 ]
 
 [[package]]
@@ -2113,14 +2138,25 @@ dependencies = [
  "symbolic-common",
 ]
 
+[[package]]
+name = "syn"
+version = "0.15.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
+dependencies = [
+ "proc-macro2 0.4.30",
+ "quote 0.6.13",
+ "unicode-xid 0.1.0",
+]
+
 [[package]]
 name = "syn"
 version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
 dependencies = [
- "proc-macro2",
- "quote",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
  "unicode-ident",
 ]
 
@@ -2194,6 +2230,18 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "test-generator"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b23be2add79223226e1cb6446cb3e37506a5927089870687a0f1149bb7a073a"
+dependencies = [
+ "glob",
+ "proc-macro2 0.4.30",
+ "quote 0.6.13",
+ "syn 0.15.44",
+]
+
 [[package]]
 name = "textwrap"
 version = "0.11.0"
@@ -2218,9 +2266,9 @@ version = "1.0.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
+ "syn 1.0.95",
 ]
 
 [[package]]
@@ -2350,6 +2398,12 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
 
+[[package]]
+name = "unicode-xid"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.3"
@@ -2449,9 +2503,9 @@ dependencies = [
  "bumpalo",
  "log",
  "once_cell",
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
+ "syn 1.0.95",
  "wasm-bindgen-shared",
 ]
 
@@ -2461,7 +2515,7 @@ version = "0.2.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
 dependencies = [
- "quote",
+ "quote 1.0.18",
  "wasm-bindgen-macro-support",
 ]
 
@@ -2471,9 +2525,9 @@ version = "0.2.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2 1.0.39",
+ "quote 1.0.18",
+ "syn 1.0.95",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index e1557215..e74b2be6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "nickel-lang"
-version = "0.3.0"
+version = "0.3.1"
 authors = ["Nickel team"]
 license = "MIT"
 readme = "README.md"
@@ -68,8 +68,9 @@ pretty_assertions = "1.2.1"
 assert_matches = "1.5.0"
 criterion = "0.3"
 pprof = { version = "0.9.1", features = ["criterion", "flamegraph"] }
-nickel-lang-utilities = {path = "utilities", version = "0.3.0"}
+nickel-lang-utilities = {path = "utilities", version = "0.3.1"}
 similar = "2.1.0"
+test-generator = "0.3.1"
 
 [workspace]
 members = [
diff --git a/HACKING.md b/HACKING.md
index 11facda2..32842ad3 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -119,21 +119,27 @@ Tests are run via `cargo test`. They are two types of tests:
 - Unit tests, located directly in the corresponding module.
 - Integration tests, located in the dedicated crate `tests/integration`.
 
-You can take inspiration from the existing tests to add your own. By convention,
-tests expected to pass are written in a standalone Nickel file in
-`tests/integration/pass/`. Each `.ncl` file defines a list of expressions that
-must individually evaluate to the boolean `true`. The whole file is an
-expression that returns true if and only if every tests pass, or fail with a
-contract failure to help locating the failing test (instead of returning just
-`false`).
-
-If a test expected to pass is failing, run it directly using nickel with
-`nickel -f tests/integration/pass/test_that_doesnt_pass.ncl` to get better error
-messages than `cargo test`.
-
-Tests expected to fail are often embedded directly into rust source code,
-because you usually want to additionally check that the error is the one you
-expect. For example ([`tests/integration/records_fail.rs`](./tests/records_fail.rs)):
+### Happy-path testing
+
+Tests for the happy path - i.e., valid Nickel programs which do not raise errors
+are generally written in standalone Nickel files in the `tests/integration/pass`
+directory. All `.ncl` files in this directory are automatically converted into
+Rust integration tests, which run the file and assert that no errors were
+raised during evaluation.
+
+Each of these `.ncl` files is structured as an array of `Bool` expressions, which
+is ultimately passed to a `check` function defined in
+`tests/integration/pass/lib/assert.ncl`. This function applies an `Assert` contract
+to each value in the array, which checks that the value it is applied to evalutes
+to `true`. The benefit of using a contract for this is that if a test fails we
+can simply run the file directly using Nickel, which gives better error messages
+than the ones we get by default from `cargo test`.
+
+### Testing failures
+
+Tests which are expected to fail are currently written in Rust, because that makes
+it possible to additionally check which particular error was raised. For example,
+([`tests/integration/records_fail.rs`](./tests/integration/records_fail.rs)):
 
 ```rust
 #[test]
@@ -149,6 +155,30 @@ fn non_mergeable() {
 }
 ```
 
+The exception to the above is the suite of tests which check that the programs in
+`examples/` behave as expected. These tests are also auto-generated from the example
+programs, but since not every example is expected to pass, these files are annotated
+with a comment declaring the expected behaviour. For example
+([`examples/simple-contracts/simple-contract-div.ncl`](./examples/simple-contracts/simple-contract-div.ncl)):
+
+```nickel
+# test: blame
+
+let Even = fun label value =>
+  if builtin.is_num value && value % 2 == 0 then
+    value
+  else
+    contract.blame label in
+let DivBy3 = fun label value =>
+  if builtin.is_num value && value % 3 == 0 then
+    value
+  else
+    contract.blame label in
+# Will cause an error! 4 is not divisible by 3.
+(4 | Even
+   | DivBy3)
+```
+
 ## Benchmarking
 
 If your change is likely to impact performance, it is recommended to run the
diff --git a/RELEASES.md b/RELEASES.md
index bb4b961e..e31c904e 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,3 +1,11 @@
+Version 0.3.1 (2022-12-15)
+==========================
+
+Fixes
+-----
+
+- Fix blame error when calling `string.find` by @euank in https://github.com/tweag/nickel/pull/988
+
 Version 0.3  (2022-12-07)
 =========================
 
diff --git a/RELEASING.md b/RELEASING.md
index 2e337443..6ab0426a 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -75,10 +75,11 @@ crates and dependent repositories (such as the website) in a consistent state.
 
    **Commit those changes temporarily to please cargo, but they will be
    dropped later. Do not push**.
-2. Check that `cargo publish --dry-run` succeeds. You may have to create an
-   account on `crates.io` and get a key for `nickel-lang` and
-   `nickel-lang-lsp`: please refer to cargo documentation and contact the
-   Nickel maintainers.
+2. Check that `cargo publish --dry-run` succeeds. For this to work, you will
+   need to be signed in to `crates.io` with a GitHub account that is part of
+   the [Nickel Core team](https://github.com/orgs/nickel-lang/teams/core),
+   and have a `crates.io` API key saved locally on your machine (normally
+   via `cargo login`). For help with this, contact the Nickel maintainers.
 3. Release on crates.io: `cargo publish`
 4. Ditch the potential changes made to the cargo manifests at step 1. by
    dropping the corresponding commit.
@@ -107,7 +108,7 @@ crates and dependent repositories (such as the website) in a consistent state.
    $git checkout -b release/X.Y.Z
    $nix flake lock --update-input nickel
    $git add flake.lock
-   $git ci -m "Update to Nickel vX.Y.Z"
+   $git commit -m "Update to Nickel vX.Y.Z"
    $git push -u origin @
    ```
 
diff --git a/examples/arrays/arrays.ncl b/examples/arrays/arrays.ncl
index d0bdca46..3fd5d5c0 100644
--- a/examples/arrays/arrays.ncl
+++ b/examples/arrays/arrays.ncl
@@ -1,3 +1,5 @@
+# test: pass
+
 # Example array functions. This code is illustrative: prefer using the array 
 # stdlib functions `array.map` and `array.fold` instead.
 let my_array_lib = {
diff --git a/examples/config-gcc/config-gcc.ncl b/examples/config-gcc/config-gcc.ncl
index 49c951a9..487890b3 100644
--- a/examples/config-gcc/config-gcc.ncl
+++ b/examples/config-gcc/config-gcc.ncl
@@ -1,3 +1,5 @@
+# test: pass
+
 # Validate and normalize gcc flags. They can be either a string `-Wextra` or
 # a structured value `{flag = "W", arg = "extra"}`. Arguments are not checked.
 let GccFlag =
diff --git a/examples/fibonacci/fibonacci.ncl b/examples/fibonacci/fibonacci.ncl
index 6cfc9c64..dac7db23 100644
--- a/examples/fibonacci/fibonacci.ncl
+++ b/examples/fibonacci/fibonacci.ncl
@@ -1,3 +1,5 @@
+# test: pass
+
 # This is the naive, exponential version of fibonacci: don't call it on a big
 # value!
 let rec fibonacci = fun n =>
diff --git a/examples/merge-priorities/main.ncl b/examples/merge-priorities/main.ncl
index ac87ec9c..eda79774 100644
--- a/examples/merge-priorities/main.ncl
+++ b/examples/merge-priorities/main.ncl
@@ -1,3 +1,5 @@
+# test: pass
+
 # Merge several blocks into one final configuration. In a real world case, one
 # would also want contracts to validate the shape of the data.
 let server = import "server.ncl" in
diff --git a/examples/merge-priorities/security.ncl b/examples/merge-priorities/security.ncl
index ffcc5ad0..733d1931 100644
--- a/examples/merge-priorities/security.ncl
+++ b/examples/merge-priorities/security.ncl
@@ -1,3 +1,5 @@
+# test: ignore
+
 {
   server.host.options | priority 10 = "TLS",
 
diff --git a/examples/merge-priorities/server.ncl b/examples/merge-priorities/server.ncl
index 9474686a..8658c390 100644
--- a/examples/merge-priorities/server.ncl
+++ b/examples/merge-priorities/server.ncl
@@ -1,3 +1,5 @@
+# test: ignore
+
 {
   server.host.ip | default = "182.168.1.1",
   server.host.port | default = 80,
diff --git a/examples/merge/main.ncl b/examples/merge/main.ncl
index 53ca4795..d02accc7 100644
--- a/examples/merge/main.ncl
+++ b/examples/merge/main.ncl
@@ -1,3 +1,5 @@
+# test: pass
+
 # Merge several blocks into one final configuration. In a real world case, one
 # would also want contracts to validate the shape of the data.
 let server = import "server.ncl" in
diff --git a/examples/merge/security.ncl b/examples/merge/security.ncl
index 858918ad..cf114f98 100644
--- a/examples/merge/security.ncl
+++ b/examples/merge/security.ncl
@@ -1,3 +1,5 @@
+# test: ignore
+
 {
   server.host.options = "TLS",
 
diff --git a/examples/merge/server.ncl b/examples/merge/server.ncl
index eb11b585..e1da2276 100644
--- a/examples/merge/server.ncl
+++ b/examples/merge/server.ncl
@@ -1,3 +1,5 @@
+# test: ignore
+
 {
   server.host.ip = "182.168.1.1",
   server.host.port = 80,
diff --git a/examples/polymorphism/polymorphism.ncl b/examples/polymorphism/polymorphism.ncl
index 8c2d591a..4a374b75 100644
--- a/examples/polymorphism/polymorphism.ncl
+++ b/examples/polymorphism/polymorphism.ncl
@@ -1,3 +1,5 @@
+# test: pass
+
 # First projection, statically typed
 let fst : forall a b. a -> b -> a = fun x y => x in
 # Evaluation function, statically typed
diff --git a/examples/record-contract/record-contract.ncl b/examples/record-contract/record-contract.ncl
index a78be2c8..ee4fbf76 100644
--- a/examples/record-contract/record-contract.ncl
+++ b/examples/record-contract/record-contract.ncl
@@ -1,3 +1,5 @@
+# test: pass
+
 # An illustrative (thus incomplete and maybe incorrect) contract example for a
 # Kubernetes configuration.
 # Schema and example derived from https://github.com/kubernetes/examples/blob/master/guestbook-go/guestbook-controller.json.
diff --git a/examples/simple-contracts/simple-contract-bool.ncl b/examples/simple-contracts/simple-contract-bool.ncl
index 69a89a14..1e9a3105 100644
--- a/examples/simple-contracts/simple-contract-bool.ncl
+++ b/examples/simple-contracts/simple-contract-bool.ncl
@@ -1,3 +1,5 @@
+# test: pass
+
 # Example of simple custom contract, parametrized by a first argument.
 # In practice, for this kind of simple predicate, one should rather use
 # `contract.from_predicate`
diff --git a/examples/simple-contracts/simple-contract-div.ncl b/examples/simple-contracts/simple-contract-div.ncl
index 1de1d0b7..3821a759 100644
--- a/examples/simple-contracts/simple-contract-div.ncl
+++ b/examples/simple-contracts/simple-contract-div.ncl
@@ -1,6 +1,7 @@
-# /!\ THIS EXAMPLE IS EXPECTED TO FAIL
-# Illustrate a basic contract violation.
+# test: blame
 
+# /!\ THIS EXAMPLE IS EXPECTED TO FAIL
+# Illustrates a basic contract violation.
 let Even = fun label value =>
   if builtin.is_num value && value % 2 == 0 then
     value
@@ -11,6 +12,6 @@ let DivBy3 = fun label value =>
     value
   else
     contract.blame label in
-# Will cause an error! 4 is no divided by 3.
+# Will cause an error! 4 is not divisible by 3.
 (4 | Even
    | DivBy3)
diff --git a/lsp/nls/Cargo.toml b/lsp/nls/Cargo.toml
index ebbd75ab..b2ad9576 100644
--- a/lsp/nls/Cargo.toml
+++ b/lsp/nls/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "nickel-lang-lsp"
-version = "0.3.0"
+version = "0.3.1"
 authors = ["Nickel team"]
 license = "MIT"
 readme = "README.md"
@@ -31,7 +31,7 @@ lsp-types = "0.88"
 log = "0.4"
 env_logger = "0.9"
 anyhow = "1.0"
-nickel-lang = {path = "../../", version = "0.3.0"}
+nickel-lang = {path = "../../", version = "0.3.1"}
 derive_more = "0.99"
 lazy_static = "1"
 csv = "1"
diff --git a/lsp/nls/src/linearization/completed.rs b/lsp/nls/src/linearization/completed.rs
index 2582ee10..f65df0d4 100644
--- a/lsp/nls/src/linearization/completed.rs
+++ b/lsp/nls/src/linearization/completed.rs
@@ -127,13 +127,14 @@ impl Completed {
             _ => item,
         };
 
-        if let Some(MetaValue {
-            ref doc,
-            ref types,
-            ref contracts,
-            priority,
-            ..
-        }) = item.meta.as_ref()
+        if let Some(
+            meta @ MetaValue {
+                ref doc,
+                ref types,
+                priority,
+                ..
+            },
+        ) = item.meta.as_ref()
         {
             if let Some(doc) = doc {
                 extra.push(doc.to_owned());
@@ -141,14 +142,8 @@ impl Completed {
             if let Some(types) = types {
                 extra.push(types.label.tag.to_string());
             }
-            if !contracts.is_empty() {
-                extra.push(
-                    contracts
-                        .iter()
-                        .map(|contract| format!("{}", contract.label.types,))
-                        .collect::<Vec<_>>()
-                        .join(","),
-                );
+            if let Some(contracts) = meta.contracts_to_string() {
+                extra.push(contracts);
             }
 
             extra.push(format!("Merge Priority: {:?}", priority));
diff --git a/lsp/nls/src/requests/completion.rs b/lsp/nls/src/requests/completion.rs
index 768e0aa3..31bc4383 100644
--- a/lsp/nls/src/requests/completion.rs
+++ b/lsp/nls/src/requests/completion.rs
@@ -3,7 +3,9 @@ use codespan_lsp::position_to_byte_index;
 use lazy_static::lazy_static;
 use log::debug;
 use lsp_server::{RequestId, Response, ResponseError};
-use lsp_types::{CompletionItem, CompletionParams};
+use lsp_types::{
+    CompletionItem, CompletionItemKind, CompletionParams, Documentation, MarkupContent, MarkupKind,
+};
 use nickel_lang::{
     identifier::Ident,
     term::{MetaValue, RichTerm, Term},
@@ -29,18 +31,112 @@ use crate::{
 // We follow the path by traversing a term, type or contract which represents a record
 // and stop when there is nothing else on the path
 
+#[derive(Debug)]
+struct IdentWithType {
+    ident: Ident,
+    ty: Types,
+    item: Option<LinearizationItem<Types>>,
+}
+
+impl From<Ident> for IdentWithType {
+    fn from(ident: Ident) -> Self {
+        IdentWithType {
+            ident,
+            ty: Types(TypeF::Dyn),
+            item: None,
+        }
+    }
+}
+
+impl From<&str> for IdentWithType {
+    fn from(ident: &str) -> Self {
+        IdentWithType {
+            ident: Ident::from(ident),
+            ty: Types(TypeF::Dyn),
+            item: None,
+        }
+    }
+}
+
+impl IdentWithType {
+    fn detail(&self) -> String {
+        self.item
+            .as_ref()
+            .and_then(|item| {
+                item.meta.as_ref().and_then(|meta| {
+                    meta.types
+                        .as_ref()
+                        .map(|ty| ty.types.to_string())
+                        .or_else(|| meta.contracts_to_string())
+                })
+            })
+            .unwrap_or_else(|| self.ty.to_string())
+    }
+
+    fn completion_item_kind(&self) -> CompletionItemKind {
+        match &self.ty {
+            ty if ty.is_function_type() => CompletionItemKind::Function,
+            _ => CompletionItemKind::Property,
+        }
+    }
+
+    fn to_lsp_completion_item(&self) -> CompletionItem {
+        /// Attach quotes to a non-ASCII string
+        fn adjust_name(name: &str) -> String {
+            if name.is_ascii() {
+                String::from(name)
+            } else {
+                format!("\"{}\"", name)
+            }
+        }
+        let doc = || {
+            let item = self.item.as_ref()?;
+            let meta = item.meta.as_ref()?;
+            let doc = meta.doc.as_ref()?;
+            let doc = Documentation::MarkupContent(MarkupContent {
+                kind: MarkupKind::Markdown,
+                value: format!("{}\nMerge Priority {}", doc.clone(), meta.priority),
+            });
+            Some(doc)
+        };
+        CompletionItem {
+            label: adjust_name(self.ident.label()),
+            detail: Some(self.detail()),
+            kind: Some(self.completion_item_kind()),
+            documentation: doc(),
+            ..Default::default()
+        }
+    }
+}
+
 /// Find the record field associated with a particular ID in the linearization
 /// using lexical scoping rules.
 fn find_fields_from_term_kind(
     linearization: &Completed,
     id: ItemId,
     path: &mut Vec<Ident>,
-) -> Option<Vec<Ident>> {
+) -> Option<Vec<IdentWithType>> {
     let item = linearization.get_item(id)?;
     match item.kind {
         TermKind::Record(ref fields) => {
             if path.is_empty() {
-                Some(fields.keys().cloned().collect())
+                Some(
+                    fields
+                        .iter()
+                        .map(|(&ident, &id)| {
+                            // This unwrap is safe because, `id` is the field of the record
+                            // we're currently analyzing. We're sure that the linearization
+                            // phase doesn't produce wrong or invalid ids.
+                            let item = linearization.get_item(id).unwrap();
+                            let (ty, _) = linearization.resolve_item_type_meta(item);
+                            IdentWithType {
+                                ident,
+                                ty,
+                                item: Some(item.clone()),
+                            }
+                        })
+                        .collect(),
+                )
             } else {
                 let name = path.pop()?;
                 let new_id = fields.get(&name)?;
@@ -65,7 +161,7 @@ fn find_fields_from_contract(
     linearization: &Completed,
     id: ItemId,
     path: &mut Vec<Ident>,
-) -> Option<Vec<Ident>> {
+) -> Option<Vec<IdentWithType>> {
     let item = linearization.get_item(id)?;
     match &item.meta {
         Some(meta_value) => Some(find_fields_from_meta_value(meta_value, path)),
@@ -81,7 +177,10 @@ fn find_fields_from_contract(
 
 /// Find record field associated associated with a MetaValue.
 /// This can be gotten from the type or the contracts.
-fn find_fields_from_meta_value(meta_value: &MetaValue, path: &mut Vec<Ident>) -> Vec<Ident> {
+fn find_fields_from_meta_value(
+    meta_value: &MetaValue,
+    path: &mut Vec<Ident>,
+) -> Vec<IdentWithType> {
     meta_value
         .contracts
         .iter()
@@ -95,7 +194,7 @@ fn find_fields_from_meta_value(meta_value: &MetaValue, path: &mut Vec<Ident>) ->
 }
 
 /// Extract the fields from a given record type.
-fn find_fields_from_type(rrows: &RecordRows, path: &mut Vec<Ident>) -> Vec<Ident> {
+fn find_fields_from_type(rrows: &RecordRows, path: &mut Vec<Ident>) -> Vec<IdentWithType> {
     if let Some(current) = path.pop() {
         let type_of_current = rrows.iter().find_map(|item| match item {
             RecordRowsIteratorItem::Row(row) if row.id == current => Some(row.types.clone()),
@@ -115,20 +214,33 @@ fn find_fields_from_type(rrows: &RecordRows, path: &mut Vec<Ident>) -> Vec<Ident
         rrows
             .iter()
             .filter_map(|item| match item {
-                RecordRowsIteratorItem::Row(row) => Some(row.id),
+                RecordRowsIteratorItem::Row(row) => Some((row.id, row.types)),
                 _ => None,
             })
-            .collect::<Vec<_>>()
+            .map(|(ident, types)| IdentWithType {
+                ident,
+                item: None,
+                ty: types.clone(),
+            })
+            .collect()
     }
 }
 
 /// Extract record fields from a record term.
-fn find_fields_from_term(term: &RichTerm, path: &mut Vec<Ident>) -> Option<Vec<Ident>> {
+fn find_fields_from_term(term: &RichTerm, path: &mut Vec<Ident>) -> Option<Vec<IdentWithType>> {
     let current = path.pop();
     match (term.as_ref(), current) {
-        (Term::Record(data) | Term::RecRecord(data, ..), None) => {
-            Some(data.fields.keys().cloned().collect())
-        }
+        (Term::Record(data) | Term::RecRecord(data, ..), None) => Some(
+            data.fields
+                .keys()
+                .copied()
+                .map(|ident| IdentWithType {
+                    ident,
+                    ty: Types(TypeF::Flat(term.clone())),
+                    item: None,
+                })
+                .collect(),
+        ),
         (Term::Record(data) | Term::RecRecord(data, ..), Some(name)) => {
             let term = data.fields.get(&name)?;
             find_fields_from_term(term, path)
@@ -203,7 +315,11 @@ fn remove_duplicates(items: &Vec<CompletionItem>) -> Vec<CompletionItem> {
 
 /// Search the linearization to find the record information associated with a
 /// partiular ID, and in the scope of a given linearization item.
-fn collect_record_info(linearization: &Completed, id: ItemId, path: &mut Vec<Ident>) -> Vec<Ident> {
+fn collect_record_info(
+    linearization: &Completed,
+    id: ItemId,
+    path: &mut Vec<Ident>,
+) -> Vec<IdentWithType> {
     linearization
         .get_item(id)
         .map(|item| {
@@ -243,21 +359,12 @@ fn get_completion_identifiers(
         name: Ident,
         server: &Server,
         path: &mut Vec<Ident>,
-    ) -> Option<Vec<Ident>> {
+    ) -> Option<Vec<IdentWithType>> {
         let item_id = item.env.get(&name)?;
         let lin = server.lin_cache_get(&item_id.file_id).unwrap();
         Some(collect_record_info(lin, *item_id, path))
     }
 
-    /// Attach quotes to a non-ASCII string
-    fn adjust_name(name: String) -> String {
-        if name.is_ascii() {
-            name
-        } else {
-            format!("\"{}\"", name)
-        }
-    }
-
     let in_scope = match trigger {
         // Record Completion
         Some(server::DOT_COMPL_TRIGGER) => {
@@ -283,11 +390,16 @@ fn get_completion_identifiers(
                 complete(item, name, server, &mut path).unwrap_or_default()
             } else {
                 // variable name completion
+                let (ty, _) = linearization.resolve_item_type_meta(item);
                 linearization
                     .get_in_scope(item)
                     .iter()
                     .filter_map(|i| match i.kind {
-                        TermKind::Declaration(ref ident, _, _) => Some(*ident),
+                        TermKind::Declaration(ident, _, _) => Some(IdentWithType {
+                            ident,
+                            item: Some(item.clone()),
+                            ty: ty.clone(),
+                        }),
                         _ => None,
                     })
                     .collect::<Vec<_>>()
@@ -297,10 +409,7 @@ fn get_completion_identifiers(
 
     let in_scope: Vec<_> = in_scope
         .iter()
-        .map(|ident| CompletionItem {
-            label: adjust_name(ident.into_label()),
-            ..Default::default()
-        })
+        .map(|ident_meta| ident_meta.to_lsp_completion_item())
         .collect();
     Ok(remove_duplicates(&in_scope))
 }
@@ -435,12 +544,16 @@ mod tests {
         let mut path = vec![Ident::from("b"), Ident::from("a")];
         // unwrap: the conversion must succeed because we built a type without unification variable
         // nor type constants
-        let result = find_fields_from_type(&Box::new(a_record_type.try_into().unwrap()), &mut path);
-        let expected = vec![Ident::from("c1"), Ident::from("c2")];
-        assert_eq!(
-            result.iter().map(Ident::label).collect::<Vec<_>>(),
-            expected.iter().map(Ident::label).collect::<Vec<_>>()
-        )
+        let result: Vec<_> =
+            find_fields_from_type(&Box::new(a_record_type.try_into().unwrap()), &mut path)
+                .iter()
+                .map(|iwm| iwm.ident)
+                .collect();
+        let expected: Vec<_> = vec![IdentWithType::from("c1"), IdentWithType::from("c2")]
+            .iter()
+            .map(|iwm| iwm.ident)
+            .collect();
+        assert_eq!(result, expected)
     }
 
     #[test]
@@ -515,13 +628,18 @@ mod tests {
         fn single_case<const N: usize>(
             linearization: Vec<LinearizationItem<Types>>,
             ids: [ItemId; N],
-            mut expected: Vec<Ident>,
+            expected: Vec<IdentWithType>,
         ) {
+            let mut expected: Vec<_> = expected.iter().map(|iwm| iwm.ident).collect();
             expected.sort();
             let completed = make_completed(linearization);
             for id in ids {
-                let mut actual = find_fields_from_term_kind(&completed, id, &mut Vec::new())
-                    .expect("Expected Some");
+                let mut actual: Vec<_> =
+                    find_fields_from_term_kind(&completed, id, &mut Vec::new())
+                        .unwrap()
+                        .iter()
+                        .map(|iwm| iwm.ident)
+                        .collect();
                 actual.sort();
                 assert_eq!(actual, expected)
             }
@@ -560,7 +678,11 @@ mod tests {
             TermKind::Usage(UsageState::Resolved(ItemId { file_id, index: 0 })),
         );
         let linearization = vec![a, b, c, d, e];
-        let expected = vec![Ident::from("foo"), Ident::from("bar"), Ident::from("baz")];
+        let expected = vec![
+            IdentWithType::from("foo"),
+            IdentWithType::from("bar"),
+            IdentWithType::from("baz"),
+        ];
         single_case(
             linearization,
             [ItemId { file_id, index: 0 }, ItemId { file_id, index: 3 }],
@@ -622,10 +744,10 @@ mod tests {
             TermKind::Usage(UsageState::Resolved(ItemId { file_id, index: 4 })),
         );
         let expected = vec![
-            Ident::from("one"),
-            Ident::from("two"),
-            Ident::from("three"),
-            Ident::from("four"),
+            IdentWithType::from("one"),
+            IdentWithType::from("two"),
+            IdentWithType::from("three"),
+            IdentWithType::from("four"),
         ];
         let linearization = vec![a, b, c, d, e, f, g, h, i];
         single_case(
diff --git a/nickel-wasm-repl/Cargo.toml b/nickel-wasm-repl/Cargo.toml
index 1b3104aa..20428aaf 100644
--- a/nickel-wasm-repl/Cargo.toml
+++ b/nickel-wasm-repl/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "nickel-repl"
-version = "0.3.0"
+version = "0.3.1"
 authors = ["Nickel team"]
 license = "MIT"
 readme = "README.md"
@@ -11,7 +11,7 @@ keywords = ["configuration", "language", "nix", "nickel"]
 edition = "2018"
 
 [dependencies]
-nickel-lang = {default-features = false, path = "../", version = "0.3.0", features=["repl-wasm"]}
+nickel-lang = {default-features = false, path = "../", version = "0.3.1", features=["repl-wasm"]}
 
 [lib]
 crate-type = ["cdylib", "rlib"]
diff --git a/src/term/mod.rs b/src/term/mod.rs
index b51d858f..92a8fb7f 100644
--- a/src/term/mod.rs
+++ b/src/term/mod.rs
@@ -390,6 +390,20 @@ impl MetaValue {
         Default::default()
     }
 
+    pub fn contracts_to_string(&self) -> Option<String> {
+        if !self.contracts.is_empty() {
+            Some(
+                self.contracts
+                    .iter()
+                    .map(|contract| format!("{}", contract.label.types,))
+                    .collect::<Vec<_>>()
+                    .join(","),
+            )
+        } else {
+            None
+        }
+    }
+
     /// Flatten two nested metavalues into one, combining their metadata. If data that can't be
     /// combined (typically, the documentation or the type annotation) are set by both metavalues,
     /// outer's one are kept.
diff --git a/src/types.rs b/src/types.rs
index 2260361e..de4f6dce 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -820,6 +820,15 @@ impl Types {
         self.subcontract(HashMap::new(), true, &mut sy)
     }
 
+    /// Returns true if this type is a function type, false otherwise.
+    pub fn is_function_type(&self) -> bool {
+        match self {
+            Types(TypeF::Forall { body, .. }) => body.is_function_type(),
+            Types(TypeF::Arrow(..)) => true,
+            Types(_) => false,
+        }
+    }
+
     /// Return the contract corresponding to a subtype.
     ///
     /// # Arguments
diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml
index 3b0e2c0d..301efa48 100644
--- a/utilities/Cargo.toml
+++ b/utilities/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "nickel-lang-utilities"
-version = "0.3.0"
+version = "0.3.1"
 authors = ["The Nickel Team <nickel-lang@protonmail.com>"]
 description = "Common helper functions for the tests and benchmarks of the nickel-lang crate."
 edition = "2018"
@@ -10,6 +10,6 @@ bench = false
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-nickel-lang = {path = "../", version = "0.3.0"}
+nickel-lang = {path = "../", version = "0.3.1"}
 criterion = "0.3"
 codespan = "0.11"


Test patch:
diff --git a/tests/integration/examples.rs b/tests/integration/examples.rs
index 8bc974bc..8b311dca 100644
--- a/tests/integration/examples.rs
+++ b/tests/integration/examples.rs
@@ -1,56 +1,53 @@
 use assert_matches::assert_matches;
 use nickel_lang::error::{Error, EvalError};
-use nickel_lang::term::RichTerm;
 use nickel_lang_utilities::TestProgram;
+use std::fs::File;
+use std::io::{self, BufRead};
 use std::path::PathBuf;
+use test_generator::test_resources;
 
-fn eval_file(file: &str) -> Result<RichTerm, Error> {
+#[test_resources("examples/**/*.ncl")]
+fn eval_file(file: &str) {
     let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-    path.push(format!("examples/{}", file));
-    let mut p = TestProgram::new_from_file(path).expect("could not load file as a program");
-    p.eval_deep()
-}
-
-#[test]
-fn config_gcc() {
-    eval_file("config-gcc/config-gcc.ncl").unwrap();
-}
-
-#[test]
-fn fibonacci() {
-    eval_file("fibonacci/fibonacci.ncl").unwrap();
-}
-
-#[test]
-fn arrays() {
-    eval_file("arrays/arrays.ncl").unwrap();
-}
-
-#[test]
-fn merge_main() {
-    eval_file("merge/main.ncl").unwrap();
-}
-
-#[test]
-fn polymorphism() {
-    eval_file("polymorphism/polymorphism.ncl").unwrap();
-}
-
-#[test]
-fn record_contract() {
-    eval_file("record-contract/record-contract.ncl").unwrap();
-}
-
-#[test]
-fn simple_contract_bool() {
-    eval_file("simple-contracts/simple-contract-bool.ncl").unwrap();
-}
-
-/// This example is expected to fail.
-#[test]
-fn simple_contract_div() {
-    assert_matches!(
-        eval_file("simple-contracts/simple-contract-div.ncl"),
-        Err(Error::EvalError(EvalError::BlameError(..)))
-    );
+    path.push(file);
+
+    let annotation = extract_annotation(path.clone());
+
+    match annotation {
+        ExampleAnnotation::Ignore => (),
+        ExampleAnnotation::Blame => {
+            let mut p = TestProgram::new_from_file(path).expect("could not load file as a program");
+            assert_matches!(
+                p.eval_deep(),
+                Err(Error::EvalError(EvalError::BlameError(..)))
+            );
+        }
+        ExampleAnnotation::Pass => {
+            let mut p = TestProgram::new_from_file(path).expect("could not load file as a program");
+            p.eval_deep().unwrap();
+        }
+    }
+}
+
+enum ExampleAnnotation {
+    Pass,
+    Blame,
+    Ignore,
+}
+
+fn extract_annotation(path: PathBuf) -> ExampleAnnotation {
+    let file = File::open(path).unwrap();
+    let reader = io::BufReader::new(file);
+    let first_line = reader
+        .lines()
+        .next()
+        .expect("Found empty example file")
+        .expect("Error reading example file");
+
+    match first_line.strip_prefix("# test: ") {
+        Some("pass") => ExampleAnnotation::Pass,
+        Some("blame") => ExampleAnnotation::Blame,
+        Some("ignore") => ExampleAnnotation::Ignore,
+        _ => panic!("Invalid example file annotation. Example files must begin with `# test: (pass|blame|ignore)`")
+    }
 }
diff --git a/tests/integration/pass.rs b/tests/integration/pass.rs
index 9070d37a..8ab13ade 100644
--- a/tests/integration/pass.rs
+++ b/tests/integration/pass.rs
@@ -3,6 +3,7 @@ use nickel_lang_utilities::TestProgram;
 use std::ffi::OsString;
 use std::path::PathBuf;
 use std::thread;
+use test_generator::test_resources;
 
 // By default, tests are run with 2MB stack size, which can overflow in debug mode. We run the
 // tests with an increased stack size.
@@ -19,9 +20,10 @@ fn run(path: impl Into<OsString>) {
     );
 }
 
+#[test_resources("tests/integration/pass/*.ncl")]
 fn check_file(file: &str) {
     let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-    path.push(format!("tests/integration/pass/{}", file));
+    path.push(file);
 
     thread::Builder::new()
         .name(String::from(file))
@@ -31,109 +33,3 @@ fn check_file(file: &str) {
         .join()
         .unwrap();
 }
-
-#[test]
-fn basics() {
-    check_file("basics.ncl");
-}
-
-#[test]
-fn builtins() {
-    check_file("builtins.ncl");
-}
-
-#[test]
-fn complete() {
-    check_file("complete.ncl");
-}
-
-#[test]
-fn contracts() {
-    check_file("contracts.ncl");
-}
-
-#[test]
-fn eq() {
-    check_file("eq.ncl")
-}
-
-#[test]
-fn functions() {
-    check_file("functions.ncl");
-}
-
-#[test]
-fn arrays() {
-    check_file("arrays.ncl");
-}
-
-#[test]
-fn metavalues() {
-    check_file("metavalues.ncl");
-}
-
-#[test]
-fn records() {
-    check_file("records.ncl");
-}
-
-#[test]
-fn record_defs() {
-    check_file("record-defs.ncl");
-}
-
-#[test]
-fn strings() {
-    check_file("strings.ncl");
-}
-
-#[test]
-fn typechecking() {
-    check_file("typechecking.ncl");
-}
-
-#[test]
-fn types() {
-    check_file("types.ncl");
-}
-
-#[test]
-fn serialize() {
-    check_file("serialize.ncl");
-    check_file("serialize-package.ncl");
-}
-
-#[test]
-fn annot_parsing() {
-    check_file("annotations.ncl");
-}
-
-#[test]
-fn importing() {
-    check_file("import.ncl");
-}
-
-#[test]
-fn overriding() {
-    check_file("overriding.ncl");
-}
-
-#[test]
-fn recursive_let() {
-    check_file("recursive_let.ncl");
-}
-
-#[test]
-fn quote_in_indentifier() {
-    check_file("quote_in_identifier.ncl")
-}
-
-#[test]
-fn priorities() {
-    check_file("priorities.ncl")
-}
-
-#[test]
-fn multiple_overrides() {
-    check_file("multiple-overrides.ncl")
-}
diff --git a/tests/integration/pass/annotations.ncl b/tests/integration/pass/annotations.ncl
index 4146d2d7..718b4f46 100644
--- a/tests/integration/pass/annotations.ncl
+++ b/tests/integration/pass/annotations.ncl
@@ -1,4 +1,4 @@
-let {check, Assert, ..} = import "testlib.ncl" in
+let {check, Assert, ..} = import "lib/assert.ncl" in
 
 [
   # left_annot_precedence
diff --git a/tests/integration/pass/arrays.ncl b/tests/integration/pass/arrays.ncl
index 687320a5..244ac7a5 100644
--- a/tests/integration/pass/arrays.ncl
+++ b/tests/integration/pass/arrays.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   # accesses
diff --git a/tests/integration/pass/basics.ncl b/tests/integration/pass/basics.ncl
index efcc3161..8dff4222 100644
--- a/tests/integration/pass/basics.ncl
+++ b/tests/integration/pass/basics.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   # basic arithmetic
diff --git a/tests/integration/pass/builtins.ncl b/tests/integration/pass/builtins.ncl
index 5ef76d99..cc724aab 100644
--- a/tests/integration/pass/builtins.ncl
+++ b/tests/integration/pass/builtins.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   # is_record
diff --git a/tests/integration/pass/complete.ncl b/tests/integration/pass/complete.ncl
index 485382e0..16537223 100644
--- a/tests/integration/pass/complete.ncl
+++ b/tests/integration/pass/complete.ncl
@@ -1,4 +1,4 @@
-let {Assert, ..} = import "testlib.ncl" in
+let {Assert, ..} = import "lib/assert.ncl" in
 
 let Y | ((Num -> Num) -> Num -> Num) -> Num -> Num = fun f => (fun x => f (x x)) (fun x => f (x x)) in
 let dec : Num -> Num = fun x => x + (-1) in
diff --git a/tests/integration/pass/contracts.ncl b/tests/integration/pass/contracts.ncl
index 383e4ab4..4252689e 100644
--- a/tests/integration/pass/contracts.ncl
+++ b/tests/integration/pass/contracts.ncl
@@ -1,4 +1,4 @@
-let {check, Assert, ..} = import "testlib.ncl" in
+let {check, Assert, ..} = import "lib/assert.ncl" in
 
 [
   let AlwaysTrue = fun l t =>
diff --git a/tests/integration/pass/eq.ncl b/tests/integration/pass/eq.ncl
index 4b0ae086..09b63e21 100644
--- a/tests/integration/pass/eq.ncl
+++ b/tests/integration/pass/eq.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   # basic
diff --git a/tests/integration/pass/functions.ncl b/tests/integration/pass/functions.ncl
index ab5cf036..35bb110d 100644
--- a/tests/integration/pass/functions.ncl
+++ b/tests/integration/pass/functions.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   (fun x => x) 3 == 3,
diff --git a/tests/integration/pass/import.ncl b/tests/integration/pass/import.ncl
index 44f234c2..b8290a4f 100644
--- a/tests/integration/pass/import.ncl
+++ b/tests/integration/pass/import.ncl
@@ -1,3 +1,3 @@
-let {Assert, ..} = import "testlib.ncl" in
+let {Assert, ..} = import "lib/assert.ncl" in
 
-(import "imported.ncl" 3 == 3 | Assert)
+(import "lib/imported.ncl" 3 == 3 | Assert)
diff --git a/tests/integration/pass/testlib.ncl b/tests/integration/pass/lib/assert.ncl
similarity index 100%
rename from tests/integration/pass/testlib.ncl
rename to tests/integration/pass/lib/assert.ncl
diff --git a/tests/integration/pass/imported.ncl b/tests/integration/pass/lib/imported.ncl
similarity index 100%
rename from tests/integration/pass/imported.ncl
rename to tests/integration/pass/lib/imported.ncl
diff --git a/tests/integration/pass/typed-import.ncl b/tests/integration/pass/lib/typed-import.ncl
similarity index 100%
rename from tests/integration/pass/typed-import.ncl
rename to tests/integration/pass/lib/typed-import.ncl
diff --git a/tests/integration/pass/metavalues.ncl b/tests/integration/pass/metavalues.ncl
index 4533796d..0f6e30c3 100644
--- a/tests/integration/pass/metavalues.ncl
+++ b/tests/integration/pass/metavalues.ncl
@@ -1,4 +1,4 @@
-let {check, Assert, ..} = import "testlib.ncl" in
+let {check, Assert, ..} = import "lib/assert.ncl" in
 
 [
   {val | default | Num = 10}.val == 10,
diff --git a/tests/integration/pass/multiple-overrides.ncl b/tests/integration/pass/multiple-overrides.ncl
index afc6fba9..8890c150 100644
--- a/tests/integration/pass/multiple-overrides.ncl
+++ b/tests/integration/pass/multiple-overrides.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   # regression test for issue #908 (https://github.com/tweag/nickel/issues/908)
diff --git a/tests/integration/pass/overriding.ncl b/tests/integration/pass/overriding.ncl
index 72c94912..06f4a7be 100644
--- a/tests/integration/pass/overriding.ncl
+++ b/tests/integration/pass/overriding.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   {foo | default = 1} & {foo = 2} == {foo = 2},
diff --git a/tests/integration/pass/priorities.ncl b/tests/integration/pass/priorities.ncl
index b0580aa7..5ff41127 100644
--- a/tests/integration/pass/priorities.ncl
+++ b/tests/integration/pass/priorities.ncl
@@ -1,4 +1,4 @@
-let {Assert, check, ..} =  import "testlib.ncl" in
+let {Assert, check, ..} =  import "lib/assert.ncl" in
 
 [
   let block1 = {
diff --git a/tests/integration/pass/quote_in_identifier.ncl b/tests/integration/pass/quote_in_identifier.ncl
index 35ad3bf9..5d4c5df4 100644
--- a/tests/integration/pass/quote_in_identifier.ncl
+++ b/tests/integration/pass/quote_in_identifier.ncl
@@ -1,3 +1,3 @@
-let {Assert, ..} = import "testlib.ncl" in
+let {Assert, ..} = import "lib/assert.ncl" in
 
 let this-isn't-invalid = true in this-isn't-invalid | Assert
diff --git a/tests/integration/pass/record-defs.ncl b/tests/integration/pass/record-defs.ncl
index 8d57094f..e3b67ded 100644
--- a/tests/integration/pass/record-defs.ncl
+++ b/tests/integration/pass/record-defs.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   # piecewise_definitions
diff --git a/tests/integration/pass/records.ncl b/tests/integration/pass/records.ncl
index 303c9610..b5076845 100644
--- a/tests/integration/pass/records.ncl
+++ b/tests/integration/pass/records.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   # accesses
diff --git a/tests/integration/pass/recursive_let.ncl b/tests/integration/pass/recursive_let.ncl
index a866dbfc..06e9d40d 100644
--- a/tests/integration/pass/recursive_let.ncl
+++ b/tests/integration/pass/recursive_let.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   let rec f = fun n => if n == 0 then n else f (n - 1) in f 10 == 0,
diff --git a/tests/integration/pass/serialize.ncl b/tests/integration/pass/serialize.ncl
index 13acf4e9..965f2942 100644
--- a/tests/integration/pass/serialize.ncl
+++ b/tests/integration/pass/serialize.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 let assertSerInv = fun x =>
     let assertAux = fun format x =>
diff --git a/tests/integration/pass/strings.ncl b/tests/integration/pass/strings.ncl
index 65eb7ca2..f7129739 100644
--- a/tests/integration/pass/strings.ncl
+++ b/tests/integration/pass/strings.ncl
@@ -1,4 +1,4 @@
-let {check, ..} = import "testlib.ncl" in
+let {check, ..} = import "lib/assert.ncl" in
 
 [
   # interpolation
diff --git a/tests/integration/pass/typechecking.ncl b/tests/integration/pass/typechecking.ncl
index 42b47240..5c040ba4 100644
--- a/tests/integration/pass/typechecking.ncl
+++ b/tests/integration/pass/typechecking.ncl
@@ -176,10 +176,10 @@ let typecheck = [
   (let x = {val : Num | doc "some" | default = 1}.val in x + 1) : Num,
 
   # Typed import
-  import "typed-import.ncl" : Num,
+  import "lib/typed-import.ncl" : Num,
 
   # Regression test for #430 (https://github.com/tweag/nickel/issues/430)
-  let x = import "typed-import.ncl"
+  let x = import "lib/typed-import.ncl"
   in x : Num,
 
   # recursive_records_quoted
diff --git a/tests/integration/pass/types.ncl b/tests/integration/pass/types.ncl
index a422b9bb..fac37312 100644
--- a/tests/integration/pass/types.ncl
+++ b/tests/integration/pass/types.ncl
@@ -1,4 +1,4 @@
-let {Assert, ..} = import "testlib.ncl" in
+let {Assert, ..} = import "lib/assert.ncl" in
 
 (let plus : Num -> Num -> Num = fun x => fun y => x + y in
  plus (54 : Num) (6 : Num) == 60 | Assert)