Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The issue
Nickel uses the builtin Rust hashtable everywhere. This hashtable is randomized, and in particular the iteration order is random (across evaluations). For some hashtables used solely as maps, this doesn't matter (say the tables of the cache, mapping UID to file content or parsed term).
However, this makes for a quite bad experience with respect to typechecking: because typechecking fails on the first error, when typechecking a record, the error depends on the order of iteration (if there are several typechecking errors, but this include unbound identifiers, so it's far from uncommon). So, you see an error, try to fix it, re-run Nickel. You get a different error somewhere else - yay, one error less! Then, at the next run, the initial error comes again, because you didn't really fix it.
Beside typechecking, exporting also forces records, and the order of iteration is relevant again if several fields fail to evaluate - the same issue as with typechecking.
Introducing IndexMap
This PR introduces
indexmap::IndexMap
, an external hashmap which is a drop-in replacement for Rust's hashmaps, use the same algorithm under the hood, is advertised as being on par with the builtin hashmap (tested on rustc) but guarantees that the order if iteration is the order of insertion (which corresponds, for record fields, to the actual order of the original source code).We still sort fields for exports and other products of Nickel: for example, if records are merged, trying to guarantee a consistent order for the result is harder (and what could be this order, anyway?). Thus, the order guaranteed by
IndexMap
is mostly here to impose determinism (and it's a nice plus that it usually corresponds to the intuitive order the things were originally defined in), but isn't a replacement for sorting fields in some cases.Because the type
indexmap::IndexMap
now appears in several places of the API, the moduleterm
re-export it, so that consumers (e.g. the LSP) can reuse it without having to manually depend onindexmap
.The hashtables not related to a term or to something user-facing (the one of the caches, typically) still use
std::collections::HashMap
.Does this work?
Here is a simple test:
Run this example 20 times on master, and you'll most likely see an even distribution of errors pointing to either
foo
orbar
. With this PR, we always get an error onfoo
.