Repository: tweag/nickel
Pull request #500 (2021-12-10 11:30:41)

[CI] Only check formatting on stable channel
The CI is checking nickel against all channels, and it checks in particular formatting. But we just had a case where beta and stable had different and incompatible `cargo fmt` outputs, making it impossible to please the CI.

This PR disables formatting check on all rust channels but stable.


Patch:
diff --git a/examples/fibonacci/fibonacci.ncl b/examples/fibonacci/fibonacci.ncl
index 82cbb7a3..2cd9c89b 100644
--- a/examples/fibonacci/fibonacci.ncl
+++ b/examples/fibonacci/fibonacci.ncl
@@ -10,6 +10,6 @@ let fibonacci = {
   else if n == 1 then
     1
   else 
-    f (n - 1) + f (n - 2)
+    f (n-1) + f (n-2)
 }.f in
 fibonacci 10
diff --git a/flake.nix b/flake.nix
index 4dc20370..d0549822 100644
--- a/flake.nix
+++ b/flake.nix
@@ -70,7 +70,7 @@
         else
           [];
 
-      buildNickel = { system, isShell ? false, channel ? "stable" }:
+      buildNickel = { system, isShell ? false, channel ? "stable", checkFmt ?  false }:
         let
           pkgs = mkPkgs {inherit system;};
 
@@ -106,8 +106,10 @@
           checkPhase =
             ''
               cargo test --release --frozen --offline
-              cargo fmt --all -- --check
-            '';
+            '' + (if checkFmt then ''
+
+                cargo fmt --all -- --check
+              '' else "");
 
           installPhase =
             ''
@@ -225,8 +227,8 @@
           '';
         };
 
-      buildDevShell = { system, channel ? "stable" }: 
-      let 
+      buildDevShell = { system, channel ? "stable" }:
+      let
         pkgs = mkPkgs { inherit system; };
         nickel = buildNickel { inherit system; isShell = true; };
         rust = (mkRust pkgs channel).rust.override({
@@ -239,7 +241,7 @@
             [ $RESULT != 0 ] && echo "Please run \`cargo fmt\` before"
             exit $RESULT
           '';
-        
+
         installGitHooks = hookTypes:
           let mkHook = type: hooks: {
             hook = pkgs.writeShellScript type
@@ -268,7 +270,7 @@
           '';
           in
 
-          pkgs.writeShellScriptBin "install-git-hooks" 
+          pkgs.writeShellScriptBin "install-git-hooks"
           ''
 
             if [[ ! -d .git ]] || [[ ! -f flake.nix ]]; then
@@ -282,14 +284,14 @@
             fi
 
             mkdir -p ./.git/hooks
-            
+
             ${pkgs.lib.concatStringsSep "\n" (pkgs.lib.mapAttrsToList (type: hooks: installHookScript (mkHook type hooks)) hookTypes )}
 
             echo "Installed git hooks: $INSTALLED_GIT_HOOKS"
             printf "%s\n" "''${INSTALLED_GIT_HOOKS[@]}" > .git/hooks/nix-installed-hooks
           '';
-        
-        uninstallGitHooks = pkgs.writeShellScriptBin "uninstall-git-hooks" 
+
+        uninstallGitHooks = pkgs.writeShellScriptBin "uninstall-git-hooks"
           ''
           if [[ ! -e "$PWD/.git/hooks/nix-installed-hooks" ]]; then
             echo "Error: could find list of installed hooks."
@@ -332,8 +334,9 @@
         } //
         (builtins.listToAttrs
           (map (channel:
+            let checkFmt = channel == "stable"; in
             { name = "nickel-against-${channel}-rust-channel";
-              value = buildNickel { inherit system channel; };
+              value = buildNickel { inherit system channel checkFmt; };
             }
           )
           (builtins.attrNames RUST_CHANNELS))
diff --git a/lsp/nls/src/linearization.rs b/lsp/nls/src/linearization.rs
index 67957fa6..a5c5dcd7 100644
--- a/lsp/nls/src/linearization.rs
+++ b/lsp/nls/src/linearization.rs
@@ -1,7 +1,6 @@
-use std::{borrow::BorrowMut, cell::UnsafeCell, collections::HashMap, mem};
+use std::collections::HashMap;
 
-use codespan::ByteIndex;
-use log::{debug, error, Record};
+use log::debug;
 use nickel::{
     identifier::Ident,
     position::TermPos,
@@ -9,7 +8,7 @@ use nickel::{
     typecheck::{
         linearization::{
             Building, Completed, Environment, Linearization, LinearizationItem, Linearizer,
-            ScopeId, TermKind, Unresolved, UsageState,
+            ScopeId, TermKind, Unresolved,
         },
         reporting::{to_type, NameReg},
         TypeWrapper, UnifTable,
@@ -26,19 +25,7 @@ pub struct BuildingResource {
 trait BuildingExt {
     fn push(&mut self, item: LinearizationItem<Unresolved>);
     fn add_usage(&mut self, decl: usize, usage: usize);
-    fn register_fields(
-        &mut self,
-        record_fields: &HashMap<Ident, RichTerm>,
-        record: usize,
-        scope: Vec<ScopeId>,
-        env: &mut Environment,
-    );
     fn add_record_field(&mut self, record: usize, field: (Ident, usize));
-    fn resolve_reference<'a>(
-        &'a self,
-        item: &'a LinearizationItem<TypeWrapper>,
-    ) -> Option<&'a LinearizationItem<TypeWrapper>>;
-    fn resolve_record_references(&mut self, defers: Vec<(usize, usize, Ident)>);
     fn id_gen(&self) -> IdGen;
 }
 
@@ -77,40 +64,6 @@ impl BuildingExt for Linearization<Building<BuildingResource>> {
         };
     }
 
-    fn register_fields(
-        &mut self,
-        record_fields: &HashMap<Ident, RichTerm>,
-        record: usize,
-        scope: Vec<ScopeId>,
-        env: &mut Environment,
-    ) {
-        for (ident, value) in record_fields.into_iter() {
-            let id = self.id_gen().get_and_advance();
-            self.push(LinearizationItem {
-                id,
-                pos: ident.1,
-                // temporary, the actual type is resolved later and the item retyped
-                ty: TypeWrapper::Concrete(AbsType::Dyn()),
-                kind: TermKind::RecordField {
-                    record,
-                    ident: ident.clone(),
-                    usages: Vec::new(),
-                    value: None,
-                },
-                scope: scope.clone(),
-                meta: match value.term.as_ref() {
-                    Term::MetaValue(meta @ MetaValue { .. }) => Some(MetaValue {
-                        value: None,
-                        ..meta.clone()
-                    }),
-                    _ => None,
-                },
-            });
-            env.insert(ident.clone(), id);
-            self.add_record_field(record, (ident.clone(), id))
-        }
-    }
-
     fn add_record_field(&mut self, record: usize, (field_ident, reference_id): (Ident, usize)) {
         match self
             .state
@@ -127,146 +80,6 @@ impl BuildingExt for Linearization<Building<BuildingResource>> {
         }
     }
 
-    fn resolve_reference<'a>(
-        &'a self,
-        item: &'a LinearizationItem<TypeWrapper>,
-    ) -> Option<&'a LinearizationItem<TypeWrapper>> {
-        // if item is a usage, resolve the usage first
-        match item.kind {
-            TermKind::Usage(UsageState::Resolved(Some(pointed))) => {
-                self.state.resource.linearization.get(pointed)
-            }
-            _ => Some(item),
-        }
-        // load referenced value, either from record field or declaration
-        .and_then(|item_pointer| {
-            match item_pointer.kind {
-                // if declaration is a record field, resolve its value
-                TermKind::RecordField { value, .. } => {
-                    debug!("parent referenced a record field {:?}", value);
-                    value
-                        // retrieve record
-                        .and_then(|value_index| self.state.resource.linearization.get(value_index))
-                }
-                // if declaration is a let biding resolve its value
-                TermKind::Declaration(_, _) => {
-                    self.state.resource.linearization.get(item_pointer.id + 1)
-                }
-
-                // if something else was referenced, stop.
-                _ => Some(item_pointer),
-            }
-        })
-    }
-
-    fn resolve_record_references(&mut self, mut defers: Vec<(usize, usize, Ident)>) {
-        let mut unresolved: Vec<(usize, usize, Ident)> = Vec::new();
-
-        while let Some(deferred) = defers.pop() {
-            // child_item: current deferred usage item
-            //       i.e.: root.<child>
-            // parent_accessor_id: id of the parent usage
-            //               i.e.: <parent>.child
-            // child_ident: identifier the child item references
-            let (child_item, parent_accessor_id, child_ident) = &deferred;
-            // resolve the value referenced by the parent accessor element
-            // get the parent accessor, and read its resolved reference
-            let parent_referenced = self.state.resource.linearization.get(*parent_accessor_id);
-
-            if let Some(LinearizationItem {
-                kind: TermKind::Usage(UsageState::Deferred { .. }),
-                ..
-            }) = parent_referenced
-            {
-                debug!("parent references deferred usage");
-                unresolved.push(deferred.clone());
-                continue;
-            }
-
-            // load the parent referenced declaration (i.e.: a declaration or record field term)
-            let parent_declaration = parent_referenced
-                .and_then(|parent_usage_value| self.resolve_reference(parent_usage_value));
-
-            if let Some(LinearizationItem {
-                kind: TermKind::Usage(UsageState::Deferred { .. }),
-                ..
-            }) = parent_declaration
-            {
-                debug!("parent references deferred usage");
-                unresolved.push(deferred.clone());
-                continue;
-            }
-
-            let referenced_declaration = parent_declaration
-                // resolve indirection by following the usage
-                .and_then(|parent_declaration| self.resolve_reference(parent_declaration))
-                // get record field
-                .and_then(|parent_declaration| match &parent_declaration.kind {
-                    TermKind::Record(fields) => {
-                        fields.get(&child_ident).and_then(|child_declaration_id| {
-                            self.state.resource.linearization.get(*child_declaration_id)
-                        })
-                    }
-                    _ => None,
-                });
-
-            let referenced_declaration =
-                referenced_declaration.and_then(|referenced| match referenced.kind {
-                    TermKind::Usage(UsageState::Resolved(Some(pointed))) => {
-                        self.state.resource.linearization.get(pointed)
-                    }
-                    TermKind::RecordField { value, .. } => value
-                        // retrieve record
-                        .and_then(|value_index| self.state.resource.linearization.get(value_index))
-                        // retrieve field
-                        .and_then(|record| match &record.kind {
-                            TermKind::Record(fields) => {
-                                debug!(
-                                    "parent referenced a nested record indirectly`: {:?}",
-                                    fields
-                                );
-                                fields.get(&child_ident).and_then(|accessor_id| {
-                                    self.state.resource.linearization.get(*accessor_id)
-                                })
-                            }
-                            TermKind::Usage(UsageState::Resolved(Some(pointed))) => {
-                                self.state.resource.linearization.get(*pointed)
-                            }
-                            _ => None,
-                        })
-                        .or(Some(referenced)),
-
-                    _ => Some(referenced),
-                });
-
-            let referenced_id = referenced_declaration.map(|reference| reference.id);
-
-            debug!(
-                "Associating child {} to value {:?}",
-                child_ident, referenced_declaration
-            );
-
-            {
-                let child: &mut LinearizationItem<TypeWrapper> = self
-                    .state
-                    .resource
-                    .linearization
-                    .get_mut(*child_item)
-                    .unwrap();
-                child.kind = TermKind::Usage(UsageState::Resolved(referenced_id));
-            }
-
-            if let Some(referenced_id) = referenced_id {
-                self.add_usage(referenced_id, *child_item);
-            }
-
-            if defers.is_empty() && !unresolved.is_empty() {
-                debug!("unresolved references: {:?}", unresolved);
-                defers = mem::replace(&mut unresolved, Vec::new());
-            }
-        }
-    }
-
     fn id_gen(&self) -> IdGen {
         IdGen::new(self.state.resource.linearization.len())
     }
@@ -287,7 +100,7 @@ pub struct AnalysisHost {
     /// in their own scope immediately after the record, which
     /// gives the corresponding record field _term_ to the ident
     /// useable to construct a vale declaration.
-    record_fields: Option<(usize, Vec<(usize, Ident)>)>,
+    record_fields: Option<(usize, Vec<Ident>)>,
     /// Accesses to nested records are recorded recursively.
     /// ```
     /// outer.middle.inner -> inner(middle(outer))
@@ -322,56 +135,53 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
         let mut id_gen = lin.id_gen();
 
         // Register record field if appropriate
-        // `record` is the id [LinearizatonItem] of the enclosing record
-        // `offset` is used to find the [LinearizationItem] representing the field
-        // Field items are inserted immediately after the record
-        if !matches!(term, Term::Op1(UnaryOp::StaticAccess(_), _)) {
-            if let Some((record, (offset, Ident(_, field_pos)))) = self
-                .record_fields
-                .take()
-                .map(|(record, mut fields)| (record, fields.pop().unwrap()))
-            {
-                pos = field_pos.map(|mut pos| {
-                    pos.start = ByteIndex(0);
-                    pos.end = ByteIndex(0);
-                    pos
-                });
+        if let Some((record, field)) = self
+            .record_fields
+            .take()
+            .map(|(record, mut fields)| (record, fields.pop().unwrap()))
+        {
+            // 0. If the record_fields field is set, it is set by [Self::scope]
+            //    such that exactly one element/field is present
+            // 1. record fields have a position
+            // 2. the type of the field is the type of its value
+            // 3. the scope of the field is the scope of its parent (the record)
+            let meta = match term {
+                Term::MetaValue(meta) => Some(MetaValue {
+                    value: None,
+                    ..meta.clone()
+                }),
+                _ => None,
+            };
+            let id = id_gen.take();
+            lin.push(LinearizationItem {
+                id,
+                pos: field.1,
+                ty: ty.clone(),
+                kind: TermKind::RecordField {
+                    record,
+                    body_pos: match pos {
+                        TermPos::None => field.1,
+                        _ => pos.clone(),
+                    },
+                    ident: field.clone(),
+                    usages: Vec::new(),
+                },
+                scope: self.scope[..self.scope.len() - 1].to_vec(),
+                meta,
+            });
 
-                for field in lin
-                    .state
-                    .resource
-                    .linearization
-                    .get_mut(record + offset)
-                    .into_iter()
-                {
-                    debug!("{:?}", field.kind);
-                    let usage_offset = if matches!(term, Term::Var(_)) {
-                        debug!(
-                            "associating nested field {:?} with chain {:?}",
-                            term,
-                            self.access.as_ref()
-                        );
-                        self.access.as_ref().map(|v| v.len()).unwrap_or(0)
-                    } else {
-                        0
-                    };
-                    match field.kind {
-                        TermKind::RecordField { ref mut value, .. } => {
-                            *value = Some(id_gen.get() + usage_offset);
-                        }
-                        // The linearization item of a record with n fields is expected to be
-                        // followed by n linearization items representing each field
-                        _ => unreachable!(),
-                    }
-                }
+            if pos == TermPos::None {
+                pos = field.1;
             }
+
+            lin.add_record_field(record, (field, id));
         }
 
         if pos == TermPos::None {
             return;
         }
 
-        let id = id_gen.get();
+        let id = id_gen.id();
         match term {
             Term::Let(ident, _, _) | Term::Fun(ident, _) => {
                 self.env
@@ -386,46 +196,76 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
                 });
             }
             Term::Var(ident) => {
-                let root_id = id_gen.get_and_advance();
+                debug!("accessor chain: {:?}", self.access);
+
+                let mut accessors = self.access.take().unwrap_or_default();
+                accessors.push(ident.to_owned());
+
+                let mut root: Option<usize> = None;
+
+                for accessor in accessors.iter().rev() {
+                    debug!("root id is: {:?}", root);
+                    let child = if let Some(root_id) = root {
+                        match &lin
+                            .state
+                            .resource
+                            .linearization
+                            .get(root_id + 1)
+                            .unwrap()
+                            .kind
+                        {
+                            // resolve referenced field of record
+                            TermKind::Record(fields) => {
+                                debug!("referenced record: {:?}", fields);
+                                fields.get(accessor).and_then(|accessor_id| {
+                                    lin.state.resource.linearization.get(*accessor_id)
+                                })
+                            }
+                            // stop if not a field
+                            kind @ _ => {
+                                debug!("expected record, was: {:?}", kind);
+                                None
+                            }
+                        }
+                    } else {
+                        // resolve referenced top level variable
+                        self.env.get(accessor).and_then(|accessor_id| {
+                            lin.state.resource.linearization.get(accessor_id)
+                        })
+                    };
 
-                debug!(
-                    "adding usage of variable {} followed by chain {:?}",
-                    ident, self.access
-                );
+                    debug!("registering child element for '{}': {:?}", accessor, child);
 
-                lin.push(LinearizationItem {
-                    id: root_id,
-                    pos: ident.1,
-                    ty: TypeWrapper::Concrete(AbsType::Dyn()),
-                    scope: self.scope.clone(),
-                    kind: TermKind::Usage(UsageState::Resolved(self.env.get(ident))),
-                    meta: self.meta.take(),
-                });
+                    let id = id_gen.take();
+                    let child_id = child.map(|r| r.id);
 
-                if let Some(referenced) = self.env.get(ident) {
-                    lin.add_usage(referenced, root_id)
-                }
+                    lin.push(LinearizationItem {
+                        id,
+                        pos: accessor.1,
+                        ty: TypeWrapper::Concrete(AbsType::Dyn()),
+                        scope: self.scope.clone(),
+                        kind: TermKind::Usage(child_id),
+                        meta: self.meta.take(),
+                    });
 
-                if let Some(chain) = self.access.take() {
-                    let chain: Vec<_> = chain.into_iter().rev().collect();
-
-                    for accessor in chain.iter() {
-                        let id = id_gen.get_and_advance();
-                        lin.push(LinearizationItem {
-                            id,
-                            pos: accessor.1,
-                            ty: TypeWrapper::Concrete(AbsType::Dyn()),
-                            scope: self.scope.clone(),
-                            kind: TermKind::Usage(UsageState::Deferred {
-                                parent: id - 1,
-                                child: accessor.to_owned(),
-                            }),
-                            meta: self.meta.take(),
-                        });
+                    if let Some(child_id) = child_id {
+                        lin.add_usage(child_id, id);
                     }
+                    root = child_id;
                 }
             }
             Term::Record(fields, _) | Term::RecRecord(fields, _, _) => {
+                self.record_fields = Some((
+                    id,
+                    fields
+                        .keys()
+                        .cloned()
+                        .collect::<Vec<_>>()
+                        .into_iter()
+                        .rev()
+                        .collect(),
+                ));
+
                 lin.push(LinearizationItem {
                     id,
                     pos,
@@ -434,13 +274,6 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
                     scope: self.scope.clone(),
                     meta: self.meta.take(),
                 });
-
-                lin.register_fields(fields, id, self.scope.clone(), &mut self.env);
-                let mut field_names = fields.keys().cloned().collect::<Vec<_>>();
-                field_names.sort_unstable();
-
-                self.record_fields =
-                    Some((id + 1, field_names.into_iter().enumerate().rev().collect()));
             }
 
             Term::Op1(UnaryOp::StaticAccess(ident), _) => {
@@ -461,7 +294,7 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
                             match &**term {
                                 Term::Var(ident) => {
                                     let parent = self.env.get(&ident);
-                                    let id = id_gen.get_and_advance();
+                                    let id = id_gen.take();
                                     lin.push(LinearizationItem {
                                         id,
                                         pos: ident.1,
@@ -469,9 +302,7 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
                                         scope: self.scope.clone(),
                                         // id = parent: full let binding including the body
                                         // id = parent + 1: actual delcaration scope, i.e. _ = < definition >
-                                        kind: TermKind::Usage(UsageState::Resolved(
-                                            parent.map(|id| id),
-                                        )),
+                                        kind: TermKind::Usage(parent.map(|id| id)),
                                         meta: None,
                                     });
                                     if let Some(parent) = parent {
@@ -516,29 +347,11 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
     /// Additionally, resolves concrete types for all items.
     fn linearize(
         self,
-        mut lin: Linearization<Building<BuildingResource>>,
+        lin: Linearization<Building<BuildingResource>>,
         (table, reported_names): (UnifTable, HashMap<usize, Ident>),
     ) -> Linearization<Completed> {
-        debug!("linearizing");
-
-        // TODO: Storing defers while linearizing?
-        let defers: Vec<(usize, usize, Ident)> = lin
-            .state
-            .resource
-            .linearization
-            .iter()
-            .filter_map(|item| match &item.kind {
-                TermKind::Usage(UsageState::Deferred { parent, child }) => {
-                    Some((item.id, *parent, child.clone()))
-                }
-                _ => None,
-            })
-            .collect();
-
-        lin.resolve_record_references(defers);
-
         let mut lin_ = lin.state.resource.linearization;
-
+        eprintln!("linearizing");
         lin_.sort_by_key(|item| match item.pos {
             TermPos::Original(span) => (span.src_id, span.start),
             TermPos::Inherited(span) => (span.src_id, span.start),
@@ -549,7 +362,6 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
             }
         });
 
-        // create an index of id -> new position
         let mut id_mapping = HashMap::new();
         lin_.iter()
             .enumerate()
@@ -557,7 +369,6 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
                 id_mapping.insert(*id, index);
             });
 
-        // resolve types
         let lin_ = lin_
             .into_iter()
             .map(
@@ -616,7 +427,6 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
             .get(ident)
             .and_then(|index| lin.state.resource.linearization.get_mut(index))
         {
-            debug!("retyping {:?} to {:?}", ident, new_type);
             item.ty = new_type;
         }
     }
@@ -625,21 +435,17 @@ impl Linearizer<BuildingResource, (UnifTable, HashMap<usize, Ident>)> for Analys
 struct IdGen(usize);
 
 impl IdGen {
-    /// Make new Generator starting at `base`
     fn new(base: usize) -> Self {
         IdGen(base)
     }
 
-    /// Get the current id
-    fn get(&self) -> usize {
-        self.0
-    }
-
-    /// Return the **current id** and advance the generator.
-    /// Following calls to get (and get_and_advance) will return a new id
-    fn get_and_advance(&mut self) -> usize {
+    fn take(&mut self) -> usize {
         let current_id = self.0;
         self.0 += 1;
         current_id
     }
+
+    fn id(&self) -> usize {
+        self.0
+    }
 }
diff --git a/lsp/nls/src/requests/goto.rs b/lsp/nls/src/requests/goto.rs
index 3a21b414..3102f485 100644
--- a/lsp/nls/src/requests/goto.rs
+++ b/lsp/nls/src/requests/goto.rs
@@ -7,7 +7,7 @@ use lsp_types::{
 };
 use nickel::{
     position::{RawSpan, TermPos},
-    typecheck::linearization::{self, UsageState},
+    typecheck::linearization,
 };
 use serde_json::Value;
 
@@ -53,7 +53,7 @@ pub fn handle_to_definition(
     debug!("found referencing item: {:?}", item);
 
     let location = match item.kind {
-        linearization::TermKind::Usage(UsageState::Resolved(Some(usage_id))) => {
+        linearization::TermKind::Usage(Some(usage_id)) => {
             let definition = linearization.id_mapping[&usage_id];
             let definition = linearization.lin[definition].clone();
             let location = match definition.pos {
diff --git a/lsp/nls/src/requests/hover.rs b/lsp/nls/src/requests/hover.rs
index 582d38a1..eae57172 100644
--- a/lsp/nls/src/requests/hover.rs
+++ b/lsp/nls/src/requests/hover.rs
@@ -6,7 +6,7 @@ use lsp_types::{Hover, HoverContents, HoverParams, LanguageString, MarkedString,
 use nickel::{
     position::TermPos,
     term::MetaValue,
-    typecheck::linearization::{self, Completed, TermKind, UsageState},
+    typecheck::linearization::{self, Completed, TermKind},
     types::Types,
 };
 use serde_json::Value;
@@ -95,10 +95,8 @@ fn resolve_type_meta(
     let mut extra = Vec::new();
 
     let item = match item.kind {
-        TermKind::Usage(UsageState::Resolved(usage)) => usage
-            .and_then(|u| completed.get_item(u + 1))
-            .unwrap_or(item),
-        TermKind::Declaration(_, _) => completed.get_item(item.id + 1).unwrap_or(item),
+        TermKind::Usage(usage) => usage.and_then(|u| completed.get_item(u)).unwrap_or(item),
+        TermKind::Declaration(_, _) => completed.get_item(item.id).unwrap_or(item),
         _ => item,
     };
 
diff --git a/scripts/channel_nightly.toml b/scripts/channel_nightly.toml
index 4e576a50..33d28c71 100644
--- a/scripts/channel_nightly.toml
+++ b/scripts/channel_nightly.toml
@@ -1,3 +1,3 @@
 channel="nightly"
-date="2021-12-10"
-sha256="sha256-81ieKdpEPG/ROI0lBNtpGTvE4fcIgFIFl6xoMVR2ur0="
+date="2021-12-09"
+sha256="sha256-2vGxnRm/SSlBNaWam1VZBTt7zCLx+ytM+J3Gviz4RJo="
diff --git a/src/parser/lexer.rs b/src/parser/lexer.rs
index 7f043564..92d98e6d 100644
--- a/src/parser/lexer.rs
+++ b/src/parser/lexer.rs
@@ -40,7 +40,7 @@ pub enum NormalToken<'input> {
     #[error]
     Error,
 
-    #[regex("_?[a-zA-Z][_a-zA-Z0-9-]*")]
+    #[regex("_?[a-zA-Z][_a-zA-Z0-9]*")]
     Identifier(&'input str),
     #[regex("[0-9]*\\.?[0-9]+", |lex| lex.slice().parse())]
     NumLiteral(f64),
diff --git a/src/repl/mod.rs b/src/repl/mod.rs
index ff085b1c..2ebb77b9 100644
--- a/src/repl/mod.rs
+++ b/src/repl/mod.rs
@@ -14,6 +14,7 @@ use crate::term::{RichTerm, Term};
 use crate::types::Types;
 use crate::{eval, transformations, typecheck};
 use codespan::FileId;
+use md5::digest::generic_array::functional::FunctionalSequence;
 use simple_counter::*;
 use std::ffi::{OsStr, OsString};
 use std::io::Write;
diff --git a/src/typecheck/linearization.rs b/src/typecheck/linearization.rs
index 2b93736b..c7a1bae6 100644
--- a/src/typecheck/linearization.rs
+++ b/src/typecheck/linearization.rs
@@ -24,7 +24,7 @@
 
 use std::{collections::HashMap, marker::PhantomData};
 
-use super::{TypeWrapper, UnifTable};
+use super::TypeWrapper;
 use crate::environment::Environment as GenericEnvironment;
 use crate::term::MetaValue;
 use crate::types::Types;
@@ -66,8 +66,6 @@ pub struct Completed {
     pub scope_mapping: HashMap<Vec<ScopeId>, Vec<usize>>,
 }
 
-pub type CompletionSalt = (UnifTable, HashMap<usize, Ident>);
-
 pub struct Uninit;
 
 /// Marker trait for possible states of the linearization
@@ -118,25 +116,17 @@ pub struct LinearizationItem<S: ResolutionState> {
 #[derive(Debug, Clone, PartialEq)]
 pub enum TermKind {
     Declaration(Ident, Vec<usize>),
-    Usage(UsageState),
+    Usage(Option<usize>),
     Record(HashMap<Ident, usize>),
     RecordField {
         ident: Ident,
+        body_pos: TermPos,
         record: usize,
         usages: Vec<usize>,
-        value: Option<usize>,
     },
     Structure,
 }
 
-/// Some usages cannot be fully resolved in a first pass (i.e. recursive record fields)
-/// In these cases we defer the resolution to a second pass during linearization
-#[derive(Debug, Clone, PartialEq)]
-pub enum UsageState {
-    Resolved(Option<usize>),
-    Deferred { parent: usize, child: Ident },
-}
-
 /// The linearizer trait is what is refered to during typechecking.
 /// It is the interface to recording terms (while tracking their scope)
 /// and finalizing a linearization using generically defined external information


Test patch:
diff --git a/tests/contracts_fail.rs b/tests/contracts_fail.rs
index f3314921..87c59c26 100644
--- a/tests/contracts_fail.rs
+++ b/tests/contracts_fail.rs
@@ -99,12 +99,12 @@ fn records_contracts_poly() {
     // but this is not yet the case.
     // eval_string(&format!("{} {{foo = 2}}", extd)).unwrap_err();
 
-    let remv = "let f | forall a. {foo: Num | a} -> { | a} = fun x => x -$ \"foo\" in f";
+    let remv = "let f | forall a. {foo: Num | a} -> { | a} = fun x => x-$\"foo\" in f";
     assert_raise_blame!(&format!("{} {{}}", remv));
 
     let bad_cst = "let f | forall a. { | a} -> { | a} = fun x => {a=1} in f";
     let bad_acc = "let f | forall a. { | a} -> { | a} = fun x => %seq% x.a x in f";
-    let bad_extd = "let f | forall a. { | a} -> {foo: Num | a} = fun x => x -$ \"foo\" in f";
+    let bad_extd = "let f | forall a. { | a} -> {foo: Num | a} = fun x => x-$\"foo\" in f";
     let bad_rmv = "let f | forall a. {foo: Num | a} -> { | a} = fun x => x$[\"foo\"=1] in f";
 
     assert_raise_blame!(&format!("{} {{}}", bad_cst));
diff --git a/tests/pass/record-defs.ncl b/tests/pass/record-defs.ncl
index b32be944..a71d6c1c 100644
--- a/tests/pass/record-defs.ncl
+++ b/tests/pass/record-defs.ncl
@@ -24,7 +24,6 @@ let Assert = fun l x => x || %blame% l in
   {"#{x}-bar" = "foo"}."foo-bar" == "foo" | #Assert) &&
 
 ({"foo-bar"."baz+baz" = false}."foo-bar"."baz+baz" == false | #Assert) &&
-({foo-bar.baz-baz = false}.foo-bar.baz-baz == false | #Assert) &&
 
 // recursive_paths
 ({foo.bar.baz = 1, bar.baz.foo = foo.bar.baz + 1}