diff --git a/core/src/term/mod.rs b/core/src/term/mod.rs index 6c66f6af0d..564a947f6f 100644 --- a/core/src/term/mod.rs +++ b/core/src/term/mod.rs @@ -304,8 +304,12 @@ impl Traverse for RuntimeContract { Ok(RuntimeContract { contract, ..self }) } - fn traverse_ref(&self, f: &mut dyn FnMut(&RichTerm) -> TraverseControl) -> Option { - self.contract.traverse_ref(f) + fn traverse_ref( + &self, + f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl, + state: &S, + ) -> Option { + self.contract.traverse_ref(f, state) } } @@ -455,8 +459,12 @@ impl Traverse for LabeledType { .map(|typ| LabeledType { typ, label }) } - fn traverse_ref(&self, f: &mut dyn FnMut(&RichTerm) -> TraverseControl) -> Option { - self.typ.traverse_ref(f) + fn traverse_ref( + &self, + f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl, + state: &S, + ) -> Option { + self.typ.traverse_ref(f, state) } } @@ -568,11 +576,15 @@ impl Traverse for TypeAnnotation { Ok(TypeAnnotation { typ, contracts }) } - fn traverse_ref(&self, f: &mut dyn FnMut(&RichTerm) -> TraverseControl) -> Option { + fn traverse_ref( + &self, + f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl, + state: &S, + ) -> Option { self.contracts .iter() - .find_map(|c| c.traverse_ref(f)) - .or_else(|| self.typ.as_ref().and_then(|t| t.traverse_ref(f))) + .find_map(|c| c.traverse_ref(f, state)) + .or_else(|| self.typ.as_ref().and_then(|t| t.traverse_ref(f, state))) } } @@ -1539,8 +1551,14 @@ impl RichTerm { } /// Flow control for tree traverals. -pub enum TraverseControl { +pub enum TraverseControl { /// Normal control flow: continue recursing into the children. + /// + /// Pass the state &S to all children. + ContinueWithScope(S), + /// Normal control flow: continue recursing into the children. + /// + /// The state that was passed to the parent will be re-used for the children. Continue, /// Skip this branch of the tree. SkipBranch, @@ -1548,7 +1566,7 @@ pub enum TraverseControl { Return(U), } -impl From> for TraverseControl { +impl From> for TraverseControl { fn from(value: Option) -> Self { match value { Some(u) => TraverseControl::Return(u), @@ -1571,7 +1589,19 @@ pub trait Traverse: Sized { /// /// Through its return value, `f` can short-circuit one branch of the traversal or /// the entire traversal. - fn traverse_ref(&self, f: &mut dyn FnMut(&T) -> TraverseControl) -> Option; + /// + /// This traversal can make use of "scoped" state. The `scope` argument is passed to + /// each callback, and the callback can optionally override that scope just for its + /// own subtree in the traversal. For example, when traversing a tree of terms you can + /// maintain an environment. Most of the time the environment should get passed around + /// unchanged, but a `Term::Let` should override the environment of its subtree. It + /// does this by returning a `TraverseControl::ContinueWithScope` that contains the + /// new environment. + fn traverse_ref( + &self, + f: &mut dyn FnMut(&T, &S) -> TraverseControl, + scope: &S, + ) -> Option; } impl Traverse for RichTerm { @@ -1758,9 +1788,14 @@ impl Traverse for RichTerm { } } - fn traverse_ref(&self, f: &mut dyn FnMut(&RichTerm) -> TraverseControl) -> Option { - match f(self) { - TraverseControl::Continue => {} + fn traverse_ref( + &self, + f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl, + state: &S, + ) -> Option { + let child_state = match f(self, state) { + TraverseControl::Continue => None, + TraverseControl::ContinueWithScope(s) => Some(s), TraverseControl::SkipBranch => { return None; } @@ -1768,6 +1803,7 @@ impl Traverse for RichTerm { return Some(ret); } }; + let state = child_state.as_ref().unwrap_or(state); match &*self.term { Term::Null @@ -1784,7 +1820,7 @@ impl Traverse for RichTerm { | Term::RuntimeError(_) => None, Term::StrChunks(chunks) => chunks.iter().find_map(|ch| { if let StrChunk::Expr(term, _) = ch { - term.traverse_ref(f) + term.traverse_ref(f, state) } else { None } @@ -1792,29 +1828,37 @@ impl Traverse for RichTerm { Term::Fun(_, t) | Term::FunPattern(_, _, t) | Term::Op1(_, t) - | Term::Sealed(_, t, _) => t.traverse_ref(f), + | Term::Sealed(_, t, _) => t.traverse_ref(f, state), Term::Let(_, t1, t2, _) | Term::LetPattern(_, _, t1, t2) | Term::App(t1, t2) - | Term::Op2(_, t1, t2) => t1.traverse_ref(f).or_else(|| t2.traverse_ref(f)), - Term::Record(data) => data.fields.values().find_map(|field| field.traverse_ref(f)), + | Term::Op2(_, t1, t2) => t1 + .traverse_ref(f, state) + .or_else(|| t2.traverse_ref(f, state)), + Term::Record(data) => data + .fields + .values() + .find_map(|field| field.traverse_ref(f, state)), Term::RecRecord(data, dyn_data, _) => data .fields .values() - .find_map(|field| field.traverse_ref(f)) + .find_map(|field| field.traverse_ref(f, state)) .or_else(|| { dyn_data.iter().find_map(|(id, field)| { - id.traverse_ref(f).or_else(|| field.traverse_ref(f)) + id.traverse_ref(f, state) + .or_else(|| field.traverse_ref(f, state)) }) }), Term::Match { cases, default } => cases .iter() - .find_map(|(_id, t)| t.traverse_ref(f)) - .or_else(|| default.as_ref().and_then(|t| t.traverse_ref(f))), - Term::Array(ts, _) => ts.iter().find_map(|t| t.traverse_ref(f)), - Term::OpN(_, ts) => ts.iter().find_map(|t| t.traverse_ref(f)), - Term::Annotated(annot, t) => t.traverse_ref(f).or_else(|| annot.traverse_ref(f)), - Term::Type(ty) => ty.traverse_ref(f), + .find_map(|(_id, t)| t.traverse_ref(f, state)) + .or_else(|| default.as_ref().and_then(|t| t.traverse_ref(f, state))), + Term::Array(ts, _) => ts.iter().find_map(|t| t.traverse_ref(f, state)), + Term::OpN(_, ts) => ts.iter().find_map(|t| t.traverse_ref(f, state)), + Term::Annotated(annot, t) => t + .traverse_ref(f, state) + .or_else(|| annot.traverse_ref(f, state)), + Term::Type(ty) => ty.traverse_ref(f, state), } } } @@ -1833,12 +1877,16 @@ impl Traverse for RichTerm { self.traverse(&f_on_term, state, order) } - fn traverse_ref(&self, f: &mut dyn FnMut(&Type) -> TraverseControl) -> Option { - let mut f_on_term = |rt: &RichTerm| match &*rt.term { - Term::Type(ty) => ty.traverse_ref(f).into(), + fn traverse_ref( + &self, + f: &mut dyn FnMut(&Type, &S) -> TraverseControl, + state: &S, + ) -> Option { + let mut f_on_term = |rt: &RichTerm, state: &S| match &*rt.term { + Term::Type(ty) => ty.traverse_ref(f, state).into(), _ => TraverseControl::Continue, }; - self.traverse_ref(&mut f_on_term) + self.traverse_ref(&mut f_on_term, state) } } diff --git a/core/src/term/record.rs b/core/src/term/record.rs index d327b35a08..1d972d0ea0 100644 --- a/core/src/term/record.rs +++ b/core/src/term/record.rs @@ -192,15 +192,19 @@ impl Traverse for Field { }) } - fn traverse_ref(&self, f: &mut dyn FnMut(&RichTerm) -> TraverseControl) -> Option { + fn traverse_ref( + &self, + f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl, + state: &S, + ) -> Option { self.metadata .annotation - .traverse_ref(f) - .or_else(|| self.value.as_ref().and_then(|v| v.traverse_ref(f))) + .traverse_ref(f, state) + .or_else(|| self.value.as_ref().and_then(|v| v.traverse_ref(f, state))) .or_else(|| { self.pending_contracts .iter() - .find_map(|c| c.traverse_ref(f)) + .find_map(|c| c.traverse_ref(f, state)) }) } } diff --git a/core/src/typ.rs b/core/src/typ.rs index dde8d28023..fdde1b4be1 100644 --- a/core/src/typ.rs +++ b/core/src/typ.rs @@ -625,11 +625,16 @@ impl Traverse for RecordRows { Ok(RecordRows(inner)) } - fn traverse_ref(&self, f: &mut dyn FnMut(&Type) -> TraverseControl) -> Option { + fn traverse_ref( + &self, + f: &mut dyn FnMut(&Type, &S) -> TraverseControl, + state: &S, + ) -> Option { match &self.0 { - RecordRowsF::Extend { row, tail } => { - row.typ.traverse_ref(f).or_else(|| tail.traverse_ref(f)) - } + RecordRowsF::Extend { row, tail } => row + .typ + .traverse_ref(f, state) + .or_else(|| tail.traverse_ref(f, state)), _ => None, } } @@ -1056,10 +1061,13 @@ impl Type { /// Searches for a `TypeF::Flat`. If one is found, returns the term it contains. pub fn find_flat(&self) -> Option { - self.traverse_ref(&mut |ty: &Type| match &ty.typ { - TypeF::Flat(f) => TraverseControl::Return(f.clone()), - _ => TraverseControl::Continue, - }) + self.traverse_ref( + &mut |ty: &Type, _: &()| match &ty.typ { + TypeF::Flat(f) => TraverseControl::Return(f.clone()), + _ => TraverseControl::Continue, + }, + &(), + ) } } @@ -1099,9 +1107,14 @@ impl Traverse for Type { } } - fn traverse_ref(&self, f: &mut dyn FnMut(&Type) -> TraverseControl) -> Option { - match f(self) { - TraverseControl::Continue => {} + fn traverse_ref( + &self, + f: &mut dyn FnMut(&Type, &S) -> TraverseControl, + state: &S, + ) -> Option { + let child_state = match f(self, state) { + TraverseControl::Continue => None, + TraverseControl::ContinueWithScope(s) => Some(s), TraverseControl::SkipBranch => { return None; } @@ -1109,6 +1122,7 @@ impl Traverse for Type { return Some(ret); } }; + let state = child_state.as_ref().unwrap_or(state); match &self.typ { TypeF::Dyn @@ -1119,12 +1133,14 @@ impl Traverse for Type { | TypeF::Var(_) | TypeF::Enum(_) | TypeF::Wildcard(_) => None, - TypeF::Flat(rt) => rt.traverse_ref(f), - TypeF::Arrow(t1, t2) => t1.traverse_ref(f).or_else(|| t2.traverse_ref(f)), + TypeF::Flat(rt) => rt.traverse_ref(f, state), + TypeF::Arrow(t1, t2) => t1 + .traverse_ref(f, state) + .or_else(|| t2.traverse_ref(f, state)), TypeF::Forall { body: t, .. } | TypeF::Dict { type_fields: t, .. } - | TypeF::Array(t) => t.traverse_ref(f), - TypeF::Record(rrows) => rrows.traverse_ref(f), + | TypeF::Array(t) => t.traverse_ref(f, state), + TypeF::Record(rrows) => rrows.traverse_ref(f, state), } } } @@ -1144,10 +1160,14 @@ impl Traverse for Type { self.traverse(&f_on_type, state, order) } - fn traverse_ref(&self, f: &mut dyn FnMut(&RichTerm) -> TraverseControl) -> Option { - let mut f_on_type = |ty: &Type| match &ty.typ { + fn traverse_ref( + &self, + f: &mut dyn FnMut(&RichTerm, &S) -> TraverseControl, + state: &S, + ) -> Option { + let mut f_on_type = |ty: &Type, s: &S| match &ty.typ { TypeF::Flat(t) => { - if let Some(ret) = t.traverse_ref(f) { + if let Some(ret) = t.traverse_ref(f, s) { TraverseControl::Return(ret) } else { TraverseControl::SkipBranch @@ -1155,7 +1175,7 @@ impl Traverse for Type { } _ => TraverseControl::Continue, }; - self.traverse_ref(&mut f_on_type) + self.traverse_ref(&mut f_on_type, state) } } diff --git a/lsp/nls/src/field_walker.rs b/lsp/nls/src/field_walker.rs index 5173645479..35f144f581 100644 --- a/lsp/nls/src/field_walker.rs +++ b/lsp/nls/src/field_walker.rs @@ -118,6 +118,18 @@ pub struct DefWithPath { pub metadata: Option, } +impl DefWithPath { + pub fn completion_item(&self) -> CompletionItem { + CompletionItem { + label: ident_quoted(&self.ident.into()), + detail: self.metadata.as_ref().and_then(metadata_detail), + kind: Some(CompletionItemKind::Property), + documentation: self.metadata.as_ref().and_then(metadata_doc), + ..Default::default() + } + } +} + #[cfg(test)] impl DefWithPath { pub fn path(&self) -> &[Ident] { @@ -197,7 +209,7 @@ impl<'a> FieldResolver<'a> { /// This a best-effort thing; it doesn't do full evaluation but it has some reasonable /// heuristics. For example, it knows that the fields defined on a merge of two records /// are the fields defined on either record. - fn resolve_term(&self, rt: &RichTerm) -> Vec { + pub fn resolve_term(&self, rt: &RichTerm) -> Vec { let term_fields = match rt.term.as_ref() { Term::Record(data) | Term::RecRecord(data, ..) => { vec![FieldHaver::RecordTerm(data.clone())] @@ -229,6 +241,7 @@ impl<'a> FieldResolver<'a> { let defs = self.resolve_annot(annot); defs.chain(self.resolve_term(term)).collect() } + Term::Type(typ) => self.resolve_type(typ), _ => Default::default(), }; diff --git a/lsp/nls/src/linearization/mod.rs b/lsp/nls/src/linearization/mod.rs index c35ea36f28..67cfd6e74f 100644 --- a/lsp/nls/src/linearization/mod.rs +++ b/lsp/nls/src/linearization/mod.rs @@ -7,7 +7,7 @@ use nickel_lang_core::{ position::TermPos, term::{ record::{Field, FieldMetadata}, - RichTerm, Term, UnaryOp, + RichTerm, Term, Traverse, TraverseControl, UnaryOp, }, typ::TypeF, typecheck::{linearization::Linearizer, reporting::NameReg, UnifType}, @@ -30,6 +30,57 @@ pub mod interface; pub type Environment = nickel_lang_core::environment::Environment; +#[derive(Clone, Debug)] +pub struct ParentLookup { + table: HashMap, +} + +impl ParentLookup { + pub fn new(rt: &RichTerm) -> Self { + let mut table = HashMap::new(); + let mut traverse_merge = + |rt: &RichTerm, parent: &Option| -> TraverseControl, ()> { + if let Some(parent) = parent { + table.insert(RichTermPtr(rt.clone()), parent.clone()); + } + TraverseControl::ContinueWithScope(Some(rt.clone())) + }; + + rt.traverse_ref(&mut traverse_merge, &None); + + ParentLookup { table } + } + + pub fn parent(&self, rt: &RichTerm) -> Option<&RichTerm> { + self.table.get(&RichTermPtr(rt.clone())) + } + + pub fn parent_chain<'a>(&'a self, rt: &'a RichTerm) -> ParentChainIter<'_> { + ParentChainIter { + table: self, + next: Some(rt), + } + } +} + +pub struct ParentChainIter<'a> { + table: &'a ParentLookup, + next: Option<&'a RichTerm>, +} + +impl<'a> Iterator for ParentChainIter<'a> { + type Item = &'a RichTerm; + + fn next(&mut self) -> Option { + if let Some(next) = self.next { + self.next = self.table.parent(next); + Some(next) + } else { + None + } + } +} + /// A registry mapping file ids to their corresponding linearization. The registry stores the /// linearization of every file that has been imported and analyzed, including the main open /// document. @@ -43,6 +94,7 @@ pub struct LinRegistry { // which point we'll rename `LinRegistry` (and probably just have one HashMap) pub position_lookups: HashMap, pub usage_lookups: HashMap, + pub parent_lookups: HashMap, pub type_lookups: HashMap, } @@ -62,6 +114,7 @@ impl LinRegistry { self.position_lookups .insert(file_id, PositionLookup::new(term)); self.usage_lookups.insert(file_id, UsageLookup::new(term)); + self.parent_lookups.insert(file_id, ParentLookup::new(term)); self.type_lookups.extend(type_lookups); } @@ -86,6 +139,11 @@ impl LinRegistry { pub fn get_type(&self, rt: &RichTerm) -> Option<&Type> { self.type_lookups.get(&RichTermPtr(rt.clone())) } + + pub fn get_parent_chain<'a>(&'a self, rt: &'a RichTerm) -> Option> { + let file = rt.pos.as_opt_ref()?.src_id; + Some(self.parent_lookups.get(&file)?.parent_chain(rt)) + } } #[derive(PartialEq, Copy, Debug, Clone, Eq, Hash)] diff --git a/lsp/nls/src/position.rs b/lsp/nls/src/position.rs index b616cf8023..1afd90a08a 100644 --- a/lsp/nls/src/position.rs +++ b/lsp/nls/src/position.rs @@ -109,7 +109,7 @@ impl PositionLookup { /// Create a position lookup table for looking up subterms of `rt` based on their positions. pub fn new(rt: &RichTerm) -> Self { let mut all_ranges = Vec::new(); - let mut fun = |term: &RichTerm| { + let mut fun = |term: &RichTerm, _state: &()| { if let TermPos::Original(pos) = &term.pos { all_ranges.push(( Range { @@ -119,10 +119,10 @@ impl PositionLookup { RichTermPtr(term.clone()), )); } - TraverseControl::<()>::Continue + TraverseControl::<(), ()>::Continue }; - rt.traverse_ref(&mut fun); + rt.traverse_ref(&mut fun, &()); PositionLookup { ranges: make_disjoint(all_ranges), diff --git a/lsp/nls/src/requests/completion.rs b/lsp/nls/src/requests/completion.rs index e29538014d..a049a46784 100644 --- a/lsp/nls/src/requests/completion.rs +++ b/lsp/nls/src/requests/completion.rs @@ -17,7 +17,6 @@ use lsp_server::{RequestId, Response, ResponseError}; use lsp_types::{ CompletionItem, CompletionItemKind, CompletionParams, Documentation, MarkupContent, MarkupKind, }; -use nickel_lang_core::cache::InputFormat; use nickel_lang_core::{ cache, identifier::{Ident, LocIdent}, @@ -28,6 +27,7 @@ use nickel_lang_core::{ }, typ::{RecordRows, RecordRowsIteratorItem, Type, TypeF}, }; +use nickel_lang_core::{cache::InputFormat, term::BinaryOp}; use std::collections::HashSet; use std::ffi::OsString; use std::io; @@ -708,16 +708,55 @@ fn sanitize_term_for_completion( } } -fn term_based_completion( - term: RichTerm, - server: &Server, -) -> Result, ResponseError> { +fn term_based_completion(term: RichTerm, server: &Server) -> Vec { log::info!("term based completion path: {term:?}"); let (start_term, path) = extract_static_path(term); let defs = FieldResolver::new(server).resolve_term_path(&start_term, &path); - Ok(defs.iter().flat_map(FieldHaver::completion_items).collect()) + defs.iter().flat_map(FieldHaver::completion_items).collect() +} + +fn env_completion(rt: &RichTerm, server: &Server) -> Vec { + let env = server.lin_registry.get_env(rt).cloned().unwrap_or_default(); + let resolver = FieldResolver::new(server); + let mut items: Vec<_> = env + .iter_elems() + .map(|(_, def_with_path)| def_with_path.completion_item()) + .collect(); + + // Iterate through all ancestors of our term, looking for identifiers that are "in scope" + // because they're in an uncle/aunt/cousin that gets merged into our direct ancestors. + if let Some(parents) = server.lin_registry.get_parent_chain(rt) { + // We're only interested in adding identifiers from terms that are records or + // merges/annotations of records. But actually we can skip the records, because any + // records that are our direct ancestor have already contributed to `env`. + let env_term = |rt: &RichTerm| { + matches!( + rt.as_ref(), + Term::Op2(BinaryOp::Merge(_), _, _) | Term::Annotated(_, _) + ) + }; + + let mut parents = parents.peekable(); + while let Some(rt) = parents.next() { + // If a parent and a grandparent were both merges, we can skip the parent + // because the grandparent will have a superset of its fields. This prevents + // quadratic behavior on long chains of merges. + if let Some(gp) = parents.peek() { + if env_term(gp) { + continue; + } + } + + if env_term(rt) { + let records = resolver.resolve_term(rt); + items.extend(records.iter().flat_map(FieldHaver::completion_items)); + } + } + } + + items } pub fn handle_completion( @@ -760,10 +799,14 @@ pub fn handle_completion( .and_then(|rt| sanitize_term_for_completion(rt, cursor, server)); let mut completions = match sanitized_term { - Some(sanitized) => term_based_completion(sanitized, server)?, + Some(sanitized) => term_based_completion(sanitized, server), None => Vec::new(), }; + if let Some(term) = &term { + completions.extend_from_slice(&env_completion(term, server)); + } + log::info!("term-based completion provided {completions:?}"); #[cfg(feature = "old-completer")] { diff --git a/lsp/nls/src/usage.rs b/lsp/nls/src/usage.rs index d6d2f6b38b..54757ea809 100644 --- a/lsp/nls/src/usage.rs +++ b/lsp/nls/src/usage.rs @@ -127,84 +127,81 @@ impl UsageLookup { } fn fill(&mut self, rt: &RichTerm, env: &Environment) { - rt.traverse_ref(&mut |term: &RichTerm| { - if let Some(span) = term.pos.as_opt_ref() { - self.def_table.insert(*span, env.clone()); - } - - match term.term.as_ref() { - Term::Fun(id, body) => { - let mut new_env = env.clone(); - new_env.def_noval(*id, None); - self.fill(body, &new_env); - TraverseControl::SkipBranch + rt.traverse_ref( + &mut |term: &RichTerm, env: &Environment| { + if let Some(span) = term.pos.as_opt_ref() { + self.def_table.insert(*span, env.clone()); } - Term::FunPattern(maybe_id, pat, body) => { - let mut new_env = env.clone(); - if let Some(id) = maybe_id { + + match term.term.as_ref() { + Term::Fun(id, _body) => { + let mut new_env = env.clone(); new_env.def_noval(*id, None); + TraverseControl::ContinueWithScope(new_env) } + Term::FunPattern(maybe_id, pat, _body) => { + let mut new_env = env.clone(); + if let Some(id) = maybe_id { + new_env.def_noval(*id, None); + } - for m in &pat.matches { - for (_path, id, field) in m.to_flattened_bindings() { - new_env.def_noval(id, Some(field.metadata)); + for m in &pat.matches { + for (_path, id, field) in m.to_flattened_bindings() { + new_env.def_noval(id, Some(field.metadata)); + } } + TraverseControl::ContinueWithScope(new_env) } - self.fill(body, &new_env); - TraverseControl::SkipBranch - } - Term::Let(id, val, body, attrs) => { - let mut new_env = env.clone(); - new_env.def(*id, Some(val.clone()), None); + Term::Let(id, val, body, attrs) => { + let mut new_env = env.clone(); + new_env.def(*id, Some(val.clone()), None); - self.fill(val, if attrs.rec { &new_env } else { env }); - self.fill(body, &new_env); + self.fill(val, if attrs.rec { &new_env } else { env }); + self.fill(body, &new_env); - TraverseControl::SkipBranch - } - Term::LetPattern(maybe_id, pat, val, body) => { - let mut new_env = env.clone(); - if let Some(id) = maybe_id { - new_env.def(*id, Some(val.clone()), None); + TraverseControl::SkipBranch } + Term::LetPattern(maybe_id, pat, val, _body) => { + let mut new_env = env.clone(); + if let Some(id) = maybe_id { + new_env.def(*id, Some(val.clone()), None); + } - for m in &pat.matches { - for (path, id, field) in m.to_flattened_bindings() { - let path = path.iter().map(|i| i.ident()).rev().collect(); - let term = TermAtPath { - term: val.clone(), - path, - }; - new_env.def(id, Some(term), Some(field.metadata)); + for m in &pat.matches { + for (path, id, field) in m.to_flattened_bindings() { + let path = path.iter().map(|i| i.ident()).rev().collect(); + let term = TermAtPath { + term: val.clone(), + path, + }; + new_env.def(id, Some(term), Some(field.metadata)); + } } + TraverseControl::ContinueWithScope(new_env) } - self.fill(body, &new_env); - TraverseControl::SkipBranch - } - Term::RecRecord(data, _interp_fields, _deps) => { - let mut new_env = env.clone(); + Term::RecRecord(data, _interp_fields, _deps) => { + let mut new_env = env.clone(); - // Records are recursive and the order of fields is unimportant, so define - // all the fields in the environment and then recurse into their values. - for (id, field) in &data.fields { - new_env.def(*id, field.value.clone(), Some(field.metadata.clone())); - } + // Records are recursive and the order of fields is unimportant, so define + // all the fields in the environment and then recurse into their values. + for (id, field) in &data.fields { + new_env.def(*id, field.value.clone(), Some(field.metadata.clone())); + } - for val in data.fields.values().filter_map(|fld| fld.value.as_ref()) { - self.fill(val, &new_env); + TraverseControl::ContinueWithScope(new_env) } - TraverseControl::SkipBranch - } - Term::Var(id) => { - let id = LocIdent::from(*id); - if let Some(def) = env.get(&id.ident) { - self.usage_table.entry(def.ident).or_default().push(id); + Term::Var(id) => { + let id = LocIdent::from(*id); + if let Some(def) = env.get(&id.ident) { + self.usage_table.entry(def.ident).or_default().push(id); + } + TraverseControl::Continue } - TraverseControl::Continue + _ => TraverseControl::<_, ()>::Continue, } - _ => TraverseControl::<()>::Continue, - } - }); + }, + env, + ); } } diff --git a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-basic.ncl.snap b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-basic.ncl.snap index bf38fc7908..2337a8910c 100644 --- a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-basic.ncl.snap +++ b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-basic.ncl.snap @@ -3,16 +3,16 @@ source: lsp/nls/tests/main.rs expression: output --- [a, b, config] -[foo, verified, version] -[foo, verified, version] -[foo, verified, version] -[really] -[really] -[foo, really, verified, version] -["has a space", lalala] -[falala] +[a, b, config, foo, verified, version] +[a, b, config, foo, verified, version] +[a, b, config, foo, verified, version] +[a, b, config, really] +[a, b, config, really] +[config, foo, really, verified, version] +["has a space", config, lalala] +[config, falala] +[config, field] [config, field] -[field] [config, subfield] [config, f, foo] [config, field] diff --git a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-dict.ncl.snap b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-dict.ncl.snap index 4ca1c4b19c..2c2567eebf 100644 --- a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-dict.ncl.snap +++ b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-dict.ncl.snap @@ -2,6 +2,6 @@ source: lsp/nls/tests/main.rs expression: output --- -[foo] -[foo] +[foo, x] +[Dict, foo, x] diff --git a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-incomplete.ncl.snap b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-incomplete.ncl.snap index 75ec33eba2..8659046967 100644 --- a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-incomplete.ncl.snap +++ b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__completion-incomplete.ncl.snap @@ -3,12 +3,12 @@ source: lsp/nls/tests/main.rs expression: output --- [config] -[foo, verified, version] -[foo, verified, version] -[foo, verified, version] -[really] -[really] -[foo, really, verified, version] -["has a space", lalala] -[falala] +[config, foo, verified, version] +[config, foo, verified, version] +[config, foo, verified, version] +[config, really] +[config, really] +[config, foo, really, verified, version] +["has a space", config, lalala] +[config, falala]