1
1
mirror of https://github.com/tweag/nickel.git synced 2024-10-06 08:07:37 +03:00

Convert symbols request away from the linearizer (#1623)

This commit is contained in:
jneem 2023-09-22 07:12:36 -05:00 committed by GitHub
parent 17adb43f87
commit bcb0f71caa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 161 additions and 60 deletions

View File

@ -4,8 +4,8 @@ mod output;
pub use jsonrpc::Server;
use log::error;
use lsp_types::{
CompletionParams, DocumentFormattingParams, GotoDefinitionParams, HoverParams, ReferenceParams,
Url,
CompletionParams, DocumentFormattingParams, DocumentSymbolParams, GotoDefinitionParams,
HoverParams, ReferenceParams, Url,
};
pub use output::LspDebug;
use serde::Deserialize;
@ -35,6 +35,7 @@ pub enum Request {
Completion(CompletionParams),
Formatting(DocumentFormattingParams),
Hover(HoverParams),
Symbols(DocumentSymbolParams),
}
#[derive(Deserialize, Debug, Default)]

View File

@ -4,7 +4,7 @@
use std::io::Write;
use lsp_types::GotoDefinitionResponse;
use lsp_types::{DocumentSymbolResponse, GotoDefinitionResponse};
pub trait LspDebug {
fn debug(&self, w: impl Write) -> std::io::Result<()>;
@ -157,3 +157,34 @@ impl LspDebug for lsp_types::MarkupContent {
write!(w, "{}", self.value)
}
}
impl LspDebug for lsp_types::SymbolInformation {
fn debug(&self, mut w: impl Write) -> std::io::Result<()> {
let name = &self.name;
let kind = self.kind;
write!(w, "{name} ({kind:?})@{}", self.location.debug_str())
}
}
impl LspDebug for lsp_types::DocumentSymbol {
fn debug(&self, mut w: impl Write) -> std::io::Result<()> {
let name = &self.name;
let kind = self.kind;
let detail = self.detail.clone().unwrap_or_default();
write!(
w,
"{name} ({kind:?} / {detail:?})@{} in {}",
self.selection_range.debug_str(),
self.range.debug_str()
)
}
}
impl LspDebug for DocumentSymbolResponse {
fn debug(&self, w: impl Write) -> std::io::Result<()> {
match self {
DocumentSymbolResponse::Flat(xs) => xs.debug(w),
DocumentSymbolResponse::Nested(xs) => xs.debug(w),
}
}
}

View File

@ -11,7 +11,7 @@ use nickel_lang_core::{
};
use crate::linearization::{building::Building, AnalysisHost, Environment, LinRegistry};
use crate::linearization::{CombinedLinearizer, TypeCollector};
use crate::linearization::{CollectedTypes, CombinedLinearizer, TypeCollector};
pub trait CacheExt {
fn typecheck_with_analysis(
@ -78,7 +78,7 @@ impl CacheExt for Cache {
initial_ctxt.clone(),
self,
lin,
(building, HashMap::new()),
(building, CollectedTypes::default()),
)
.map_err(|err| vec![Error::TypecheckError(err)])?;

View File

@ -101,7 +101,7 @@ pub struct LinRegistry {
pub position_lookups: HashMap<FileId, PositionLookup>,
pub usage_lookups: HashMap<FileId, UsageLookup>,
pub parent_lookups: HashMap<FileId, ParentLookup>,
pub type_lookups: HashMap<FileId, HashMap<RichTermPtr, Type>>,
pub type_lookups: HashMap<FileId, CollectedTypes<Type>>,
}
impl LinRegistry {
@ -113,7 +113,7 @@ impl LinRegistry {
&mut self,
file_id: FileId,
linearization: Completed,
type_lookups: HashMap<RichTermPtr, Type>,
type_lookups: CollectedTypes<Type>,
term: &RichTerm,
) {
self.map.insert(file_id, linearization);
@ -154,7 +154,10 @@ impl LinRegistry {
pub fn get_type(&self, rt: &RichTerm) -> Option<&Type> {
let file = rt.pos.as_opt_ref()?.src_id;
self.type_lookups.get(&file)?.get(&RichTermPtr(rt.clone()))
self.type_lookups
.get(&file)?
.terms
.get(&RichTermPtr(rt.clone()))
}
pub fn get_parent_chain<'a>(&'a self, rt: &'a RichTerm) -> Option<ParentChainIter<'a>> {
@ -788,9 +791,24 @@ pub struct TypeCollector {
term_ids: Vec<RichTermPtr>,
}
#[derive(Clone, Debug)]
pub struct CollectedTypes<Ty> {
pub terms: HashMap<RichTermPtr, Ty>,
pub idents: HashMap<LocIdent, Ty>,
}
impl<Ty> Default for CollectedTypes<Ty> {
fn default() -> Self {
Self {
terms: HashMap::new(),
idents: HashMap::new(),
}
}
}
impl Linearizer for TypeCollector {
type Building = HashMap<RichTermPtr, UnifType>;
type Completed = HashMap<RichTermPtr, Type>;
type Building = CollectedTypes<UnifType>;
type Completed = CollectedTypes<Type>;
type CompletionExtra = Extra;
type ItemId = usize;
@ -804,7 +822,7 @@ impl Linearizer for TypeCollector {
fn add_term(&mut self, lin: &mut Self::Building, rt: &RichTerm, ty: UnifType) -> Option<usize> {
self.term_ids.push(RichTermPtr(rt.clone()));
lin.insert(RichTermPtr(rt.clone()), ty);
lin.terms.insert(RichTermPtr(rt.clone()), ty);
Some(self.term_ids.len() - 1)
}
@ -827,16 +845,33 @@ impl Linearizer for TypeCollector {
}
};
lin.into_iter()
let terms = lin
.terms
.into_iter()
.map(|(rt, uty)| (rt, transform_type(uty)))
.collect()
.collect();
let idents = lin
.idents
.into_iter()
.map(|(id, uty)| (id, transform_type(uty)))
.collect();
CollectedTypes { terms, idents }
}
fn retype(&mut self, lin: &mut Self::Building, item_id: Option<usize>, new_type: UnifType) {
if let Some(id) = item_id {
lin.insert(self.term_ids[id].clone(), new_type);
lin.terms.insert(self.term_ids[id].clone(), new_type);
}
}
fn retype_ident(
&mut self,
lin: &mut Self::Building,
ident: &nickel_lang_core::identifier::LocIdent,
new_type: UnifType,
) {
lin.idents.insert((*ident).into(), new_type);
}
}
pub struct CombinedLinearizer<T, U>(pub T, pub U);

View File

@ -1,15 +1,10 @@
use crate::{
files::uri_to_path,
linearization::interface::TermKind,
term::RawSpanExt,
trace::{Enrich, Trace},
};
use lsp_server::{RequestId, Response, ResponseError};
use lsp_types::{DocumentSymbol, DocumentSymbolParams, SymbolKind};
use nickel_lang_core::cache::SourcePath;
use serde_json::Value;
use nickel_lang_core::typ::Type;
use crate::server::Server;
use crate::{files::uri_to_path, term::RawSpanExt};
pub fn handle_document_symbols(
params: DocumentSymbolParams,
@ -17,47 +12,44 @@ pub fn handle_document_symbols(
server: &mut Server,
) -> Result<(), ResponseError> {
let path = uri_to_path(&params.text_document.uri)?;
let file_id = server.cache.id_of(&SourcePath::Path(path)).unwrap();
let file_id = server
.cache
.id_of(&SourcePath::Path(path))
.ok_or_else(|| crate::error::Error::FileNotFound(params.text_document.uri.clone()))?;
if let Some(completed) = server.lin_registry.map.get(&file_id) {
Trace::enrich(&id, completed);
let symbols = completed
.linearization
.iter()
.filter_map(|item| match &item.kind {
TermKind::Declaration { id: name, .. } => {
// Although the position maybe shouldn't be `None`, opening the std library
// source inside VSCode made the LSP panic on the previous `item.pos.unwrap()`.
// Before investigating further, let's not make the VSCode extension panic in
// the meantime.
let (file_id, span) = item.pos.into_opt()?.to_range();
let usage_lookups = server.lin_registry.usage_lookups.get(&file_id).unwrap();
let range =
codespan_lsp::byte_span_to_range(server.cache.files(), file_id, span)
.unwrap();
let type_lookups = server
.lin_registry
.type_lookups
.get(&file_id)
.ok_or_else(|| crate::error::Error::FileNotFound(params.text_document.uri.clone()))?;
// `deprecated` is a required field but causes a warning although we are not
// using it
#[allow(deprecated)]
Some(DocumentSymbol {
name: name.to_string(),
detail: Some(format!("{}", item.ty)),
kind: SymbolKind::Variable,
tags: None,
range,
selection_range: range,
children: None,
deprecated: None,
})
}
_ => None,
let mut symbols = usage_lookups
.symbols()
.filter_map(|ident| {
let (file_id, span) = ident.pos.into_opt()?.to_range();
let range =
codespan_lsp::byte_span_to_range(server.cache.files(), file_id, span).ok()?;
let ty = type_lookups.idents.get(&ident);
#[allow(deprecated)] // because the `deprecated` field is... wait for it... deprecated.
Some(DocumentSymbol {
name: ident.ident.to_string(),
detail: ty.map(Type::to_string),
kind: SymbolKind::Variable,
tags: None,
range,
selection_range: range,
children: None,
deprecated: None,
})
.collect::<Vec<_>>();
})
.collect::<Vec<_>>();
server.reply(Response::new_ok(id, symbols));
} else {
server.reply(Response::new_ok(id, Value::Null));
}
symbols.sort_by_key(|s| s.range.start);
server.reply(Response::new_ok(id, symbols));
Ok(())
}

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use nickel_lang_core::{
environment::Environment as GenericEnvironment,
@ -87,6 +87,10 @@ pub struct UsageLookup {
// environments) but its enough for us now.
def_table: HashMap<RawSpan, Environment>,
usage_table: HashMap<LocIdent, Vec<LocIdent>>,
// The list of all the symbols (and their locations) in the document.
//
// Currently, variables bound in `let` bindings and record fields count as symbols.
syms: HashSet<LocIdent>,
}
impl UsageLookup {
@ -125,6 +129,15 @@ impl UsageLookup {
.and_then(|span| self.def_table.get(span))
}
/// Return the list of symbols in the document.
pub fn symbols(&self) -> impl Iterator<Item = LocIdent> + '_ {
self.syms.iter().cloned()
}
fn add_sym(&mut self, id: impl Into<LocIdent>) {
self.syms.insert(id.into());
}
fn fill(&mut self, rt: &RichTerm, env: &Environment) {
rt.traverse_ref(
&mut |term: &RichTerm, env: &Environment| {
@ -154,6 +167,7 @@ impl UsageLookup {
Term::Let(id, val, body, attrs) => {
let mut new_env = env.clone();
new_env.def(*id, Some(val.clone()), None);
self.add_sym(*id);
self.fill(val, if attrs.rec { &new_env } else { env });
self.fill(body, &new_env);
@ -164,6 +178,7 @@ impl UsageLookup {
let mut new_env = env.clone();
if let Some(id) = maybe_id {
new_env.def(*id, Some(val.clone()), None);
self.add_sym(*id);
}
for m in &pat.matches {
@ -173,7 +188,8 @@ impl UsageLookup {
term: val.clone(),
path,
};
new_env.def(id, Some(term), Some(field.metadata));
new_env.def(id, Some(term.clone()), Some(field.metadata));
self.add_sym(id);
}
}
TraverseControl::ContinueWithScope(new_env)
@ -185,6 +201,7 @@ impl UsageLookup {
// 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()));
self.add_sym(*id);
}
TraverseControl::ContinueWithScope(new_env)

2
lsp/nls/test2.ncl Normal file
View File

@ -0,0 +1,2 @@
let x : { _ : { foo : Number | default = 1 } } = {} in
x.PATH.foo

View File

@ -0,0 +1,15 @@
### /syms.ncl
let foo = 3 in
let func = fun x => 1 in
{
name = "value",
other_name = {
inner_name = let inner_binding = 3 in "value",
},
type_checked_block = {
inner_name = { name = "value" },
} : _,
}
### [[request]]
### type = "Symbols"
### textDocument.uri = "file:///syms.ncl"

View File

@ -2,7 +2,8 @@ use std::collections::{hash_map::Entry, HashMap};
use assert_cmd::cargo::CommandCargoExt;
use lsp_types::request::{
Completion, Formatting, GotoDefinition, HoverRequest, References, Request as LspRequest,
Completion, DocumentSymbolRequest, Formatting, GotoDefinition, HoverRequest, References,
Request as LspRequest,
};
use nickel_lang_utils::project_root::project_root;
use test_generator::test_resources;
@ -40,6 +41,7 @@ impl TestHarness {
Request::Formatting(f) => self.request::<Formatting>(f),
Request::Hover(h) => self.request::<HoverRequest>(h),
Request::References(r) => self.request::<References>(r),
Request::Symbols(s) => self.request::<DocumentSymbolRequest>(s),
}
}

View File

@ -0,0 +1,6 @@
---
source: lsp/nls/tests/main.rs
expression: output
---
[foo (Variable / "Number")@0:4-0:7 in 0:4-0:7, func (Variable / "Dyn")@1:4-1:8 in 1:4-1:8, name (Variable / "String")@3:2-3:6 in 3:2-3:6, other_name (Variable / "Dyn")@4:2-4:12 in 4:2-4:12, inner_name (Variable / "Dyn")@5:4-5:14 in 5:4-5:14, inner_binding (Variable / "Number")@5:21-5:34 in 5:21-5:34, type_checked_block (Variable / "Dyn")@7:2-7:20 in 7:2-7:20, inner_name (Variable / "{ name : String }")@8:4-8:14 in 8:4-8:14, name (Variable / "String")@8:19-8:23 in 8:19-8:23]