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

Accurate record completion

This commit is contained in:
Oghenevwogaga Ebresafe 2022-10-17 15:59:44 +01:00
parent 6511b779e4
commit 89161e69b1
6 changed files with 155 additions and 70 deletions

View File

@ -55,7 +55,7 @@ impl Building {
TermKind::Structure => unreachable!(),
TermKind::Usage(_) => unreachable!(),
TermKind::Record(_) => unreachable!(),
TermKind::RecordRef { .. } => unreachable!(),
TermKind::RecordBind { .. } => unreachable!(),
TermKind::Declaration(_, ref mut usages, _)
| TermKind::RecordField { ref mut usages, .. } => usages.push(usage),
};

View File

@ -2,6 +2,8 @@ use std::collections::HashMap;
use codespan::ByteIndex;
use nickel_lang::{
environment::Environment,
identifier::Ident,
term::MetaValue,
typecheck::linearization::{LinearizationState, Scope},
};
@ -15,6 +17,7 @@ use super::{
#[derive(Debug, Default)]
pub struct Completed {
pub linearization: Vec<LinearizationItem<Resolved>>,
pub lin_env: Environment<Ident, usize>,
scope: HashMap<Scope, Vec<usize>>,
id_to_index: HashMap<ID, usize>,
}
@ -24,11 +27,13 @@ impl Completed {
linearization: Vec<LinearizationItem<Resolved>>,
scope: HashMap<Scope, Vec<usize>>,
id_to_index: HashMap<ID, usize>,
lin_env: Environment<Ident, usize>,
) -> Self {
Self {
linearization,
scope,
id_to_index,
lin_env,
}
}

View File

@ -34,7 +34,7 @@ pub enum TermKind {
usages: Vec<ID>,
value: ValueState,
},
RecordRef {
RecordBind {
ident: Ident,
fields: Vec<Ident>,
},

View File

@ -104,23 +104,33 @@ impl Linearizer for AnalysisHost {
mut pos: TermPos,
ty: TypeWrapper,
) {
fn get_record_ref_item(
// TODO: add support for nested records
fn get_record_bind_item(
ty: TypeWrapper,
scope: Scope,
ident: &Ident,
id: usize,
term: &RichTerm,
) -> Option<LinearizationItem<Unresolved>> {
let t = term.as_ref();
let mut t = term.as_ref();
match t {
Term::Record(fields, ..) => {
Term::MetaValue(MetaValue {
value: Some(term), ..
}) => {
t = term.as_ref();
}
_ => (),
}
match t {
Term::Record(fields, ..) | Term::RecRecord(fields, ..) => {
// panic!("{:?}", fields);
let fields = fields.keys().map(|item| item.clone()).collect();
Some(LinearizationItem {
id,
ty,
pos: ident.pos.clone(),
scope,
kind: TermKind::RecordRef {
kind: TermKind::RecordBind {
ident: ident.clone(),
fields,
},
@ -223,7 +233,7 @@ impl Linearizer for AnalysisHost {
});
match term {
Term::LetPattern(_, _, body, _) => {
get_record_ref_item(ty, self.scope.clone(), ident, id, body)
get_record_bind_item(ty, self.scope.clone(), ident, id, body)
.map_or((), |item| lin.push(item))
}
_ => (),
@ -255,7 +265,7 @@ impl Linearizer for AnalysisHost {
});
match term {
Term::LetPattern(..) => get_record_ref_item(
Term::LetPattern(..) => get_record_bind_item(
TypeWrapper::Concrete(AbsType::Dyn()),
self.scope.clone(),
&ident,
@ -267,7 +277,8 @@ impl Linearizer for AnalysisHost {
}
}
}
Term::Let(ident, ..) | Term::Fun(ident, ..) => {
Term::Let(ident, body, ..) | Term::Fun(ident, body) => {
// panic!("{:?}",term);
let value_ptr = match term {
Term::Let(..) => {
self.let_binding = Some(id);
@ -299,13 +310,14 @@ impl Linearizer for AnalysisHost {
meta: self.meta.take(),
});
match term {
Term::LetPattern(_, _, body, _) => {
get_record_ref_item(ty, self.scope.clone(), ident, id, body)
.map_or((), |item| lin.push(item))
}
_ => (),
}
get_record_bind_item(ty, self.scope.clone(), ident, id, body)
.map_or((), |item| lin.push(item))
// match term {
// Term::Let(_, _, body, _) => {
// }
// _ => (),
// }
}
Term::Var(ident) => {
let root_id = id_gen.get_and_advance();
@ -483,7 +495,7 @@ impl Linearizer for AnalysisHost {
})
.collect();
Linearization::new(Completed::new(lin_, scope, id_mapping))
Linearization::new(Completed::new(lin_, scope, id_mapping, self.env))
}
fn scope(&mut self) -> Self {

View File

@ -1,8 +1,8 @@
use codespan::ByteIndex;
use codespan_lsp::position_to_byte_index;
use log::debug;
// use log::debug;
use lsp_server::{RequestId, Response, ResponseError};
use lsp_types::{CompletionItem, CompletionParams};
use lsp_types::{CompletionContext, CompletionItem, CompletionParams};
use nickel_lang::{
identifier::Ident,
term::{Contract, MetaValue},
@ -11,7 +11,7 @@ use nickel_lang::{
use serde_json::Value;
use crate::{
linearization::interface::TermKind,
linearization::{completed::Completed, interface::TermKind},
server::Server,
trace::{Enrich, Trace},
};
@ -21,11 +21,19 @@ pub fn handle_completion(
id: RequestId,
server: &mut Server,
) -> Result<(), ResponseError> {
// let mut file = OpenOptions::new()
// .write(true)
// .append(false)
// .create(true)
// .open("/home/ebresafegaga/server.log")
// .unwrap();
let file_id = server
.cache
.id_of(params.text_document_position.text_document.uri.as_str())
.unwrap();
let text = server.cache.files().source(file_id);
let start = position_to_byte_index(
server.cache.files(),
file_id,
@ -33,27 +41,41 @@ pub fn handle_completion(
)
.unwrap();
let name = text[..start - 1]
.chars()
.rev()
.take_while(|c| c.is_ascii_alphabetic() || *c == '.')
.skip_while(|c| *c == '.') // skip . (if any)
.collect::<String>()
.chars()
.rev()
.collect::<String>();
// Make sure you parse the string to support:
// 1. Normal Identifiers
// 2. Record Index
// 3. Record terms - Maybe this can be gotten from the Linearizer
// This is the environment containing stdlib stuff.
// let env = &server.initial_ctxt.type_env;
// lin_env is a mapping from idents to IDs.
// This is from the Linearizer used to build this linearization.
// idealy we should get the id and use it instead of comparing strings
let Completed { lin_env: _, .. } = &server.lin_cache.get(&file_id).unwrap();
let locator = (file_id, ByteIndex(start as u32));
let linearization = server.lin_cache_get(&file_id)?;
Trace::enrich(&id, linearization);
let item = linearization.item_at(&locator);
if item == None {
server.reply(Response::new_ok(id, Value::Null));
return Ok(());
}
let item = item.unwrap().to_owned();
// How can we get data for record completion based on the TermKind
// 1. Record: just list it's fields
// 2. RecordRef: list the field it contains if the item we're at corresponds with it's ID
// 3. Check the type of the term: If it's a record, list it's fields
// 4. Check the MetaValue for it's contract: if it's a record, list it's fields
// Can we seperate record completion from just normal completion?
let trigger = match params.context {
Some(CompletionContext {
trigger_character: Some(s),
..
}) => Some(s),
_ => None,
};
/// Extract identifiers from a row type which forms a record.
/// Ensure that ty is really a row type.
@ -66,39 +88,85 @@ pub fn handle_completion(
.collect::<Vec<_>>()
}
let in_scope: Vec<_> = linearization
.get_in_scope(&item)
.iter()
.filter_map(|i| match i.kind {
TermKind::Declaration(ref ident, _, _) => Some((vec![ident.clone()], i.ty.clone())),
TermKind::RecordRef { ref fields, .. } if i.id == item.id => {
Some((fields.clone(), i.ty.clone()))
}
TermKind::Record(ref fields) => {
Some((fields.keys().map(|i| i.clone()).collect(), i.ty.clone()))
}
_ if matches!(i.ty, Types(AbsType::StaticRecord(..))) => match &i.ty {
Types(AbsType::StaticRecord(row)) => Some((extract_ident(row), i.ty.clone())),
_ => unreachable!(),
},
let in_scope = match trigger {
// Record completion
Some(s) if s == "." => linearization
.linearization
.iter()
.filter_map(|i| match i.kind {
TermKind::RecordBind {
ident, ref fields, ..
} if ident.label() == name => Some((fields.clone(), i.ty.clone())),
_ if i.meta.is_some() => match &i.meta {
Some(MetaValue { contracts, .. }) => {
let fields = contracts
.clone()
.iter()
.map(|Contract { types, .. }| match types {
Types(AbsType::StaticRecord(row)) => extract_ident(row),
_ => vec![],
})
.flatten()
.collect();
Some((fields, i.ty.clone()))
// Get static type info
TermKind::Declaration(ident, ..)
if ident.label() == name
&& matches!(i.ty, Types(AbsType::StaticRecord(..))) =>
{
match &i.ty {
Types(AbsType::StaticRecord(row)) => {
Some((extract_ident(row), i.ty.clone()))
}
_ => unreachable!(),
}
}
_ => unreachable!(),
},
_ => None,
})
// Get contract info
TermKind::Declaration(ident, ..) if ident.label() == name && i.meta.is_some() => {
match &i.meta {
Some(MetaValue { contracts, .. }) => {
let fields = contracts
.clone()
.iter()
.map(|Contract { types, .. }| match types {
Types(AbsType::StaticRecord(row)) => extract_ident(row),
_ => vec![],
})
.flatten()
.collect();
Some((fields, i.ty.clone()))
}
_ => unreachable!(),
}
}
_ => None,
})
.collect(),
// variable name completion
Some(..) | None => {
let item = linearization.item_at(&locator);
if item == None {
server.reply(Response::new_ok(id, Value::Null));
return Ok(());
} else {
let item = item.unwrap().to_owned();
linearization
.get_in_scope(&item)
.iter()
.filter_map(|i| match i.kind {
TermKind::Declaration(ref ident, _, _) => {
Some((vec![ident.clone()], i.ty.clone()))
}
_ => None,
})
.collect::<Vec<_>>()
}
}
};
// How can we get data for record completion based on the TermKind
// 1. Record: just list it's fields
// 2. RecordRef: list the field it contains if the item we're at corresponds with it's ID
// 3. Check the type of the term: If it's a record, list it's fields
// 4. Check the MetaValue for it's contract: if it's a record, list it's fields
// Can we seperate record completion from just normal completion?
// We also need data form the standard library functions
let in_scope: Vec<_> = in_scope
.iter()
.map(|(idents, _)| {
idents
.iter()
@ -113,6 +181,6 @@ pub fn handle_completion(
server.reply(Response::new_ok(id, in_scope));
debug!("found closest item: {:?}", item);
// debug!("found closest item: {:?}", item);
Ok(())
}

View File

@ -9,7 +9,7 @@ use crate::position::TermPos;
simple_counter::generate_counter!(GeneratedCounter, usize);
static INTERNER: Lazy<interner::Interner> = Lazy::new(interner::Interner::new);
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize)]
#[serde(into = "String", from = "String")]
pub struct Ident {
symbol: interner::Symbol,
@ -114,7 +114,7 @@ mod interner {
use typed_arena::Arena;
/// A symbol is a correspondance between an [Ident](super::Ident) and its string representation stored in the [Interner].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Symbol(u32);
/// The interner, which serves a double purpose: it pre-allocates space