1
1
mirror of https://github.com/oxalica/nil.git synced 2024-11-23 03:57:06 +03:00

Complete attrset fields

This commit is contained in:
oxalica 2022-11-07 10:50:59 +08:00
parent 2cd3354204
commit efca332ce9
2 changed files with 159 additions and 25 deletions

View File

@ -1,10 +1,11 @@
use crate::def::{AstPtr, NameKind};
use crate::{DefDatabase, FileId, FilePos};
use crate::{FileId, FilePos, TyDatabase};
use builtin::{BuiltinKind, ALL_BUILTINS};
use either::Either::{Left, Right};
use rowan::ast::AstNode;
use smol_str::SmolStr;
use syntax::{ast, best_token_at_offset, match_ast, SyntaxKind, TextRange, T};
use syntax::semantic::AttrKind;
use syntax::{ast, best_token_at_offset, match_ast, SyntaxKind, SyntaxNode, TextRange, T};
#[rustfmt::skip]
const EXPR_POS_KEYWORDS: &[&str] = &[
@ -73,7 +74,7 @@ impl TryFrom<NameKind> for CompletionItemKind {
}
pub(crate) fn completions(
db: &dyn DefDatabase,
db: &dyn TyDatabase,
FilePos { file_id, pos }: FilePos,
) -> Option<Vec<CompletionItem>> {
let parse = db.parse(file_id);
@ -99,14 +100,29 @@ pub(crate) fn completions(
Left(ref_node) => complete_expr(db, file_id, source_range, ref_node),
Right(name_node) => {
let path_node = ast::Attrpath::cast(name_node.syntax().parent()?)?;
let _entry_node = ast::AttrpathValue::cast(path_node.syntax().parent()?)?;
complete_attrpath_def(db, file_id, source_range, path_node, name_node)
let parent_node = path_node.syntax().parent()?;
let container_node = match_ast! {
match parent_node {
ast::AttrpathValue(_) => parent_node.parent()?,
ast::HasAttr(n) => n.set()?.syntax().clone(),
ast::Select(n) => n.set()?.syntax().clone(),
_ => return None,
}
};
complete_attrpath(
db,
file_id,
source_range,
container_node,
path_node,
name_node,
)
}
}
}
fn complete_expr(
db: &dyn DefDatabase,
db: &dyn TyDatabase,
file_id: FileId,
source_range: TextRange,
ref_node: ast::Ref,
@ -189,29 +205,76 @@ fn complete_expr(
Some(items)
}
fn complete_attrpath_def(
_db: &dyn DefDatabase,
_file_id: FileId,
fn complete_attrpath(
db: &dyn TyDatabase,
file_id: FileId,
source_range: TextRange,
container_node: SyntaxNode,
path_node: ast::Attrpath,
_name_node: ast::Name,
name_node: ast::Name,
) -> Option<Vec<CompletionItem>> {
if path_node.attrs().count() >= 2 {
return None;
let mut items = Vec::new();
let is_let = ast::LetIn::can_cast(container_node.kind());
let attr_cnt = path_node.attrs().count();
if attr_cnt <= 1 {
items.extend(
ATTR_POS_KEYWORDS
.iter()
.copied()
.chain(is_let.then_some("in"))
.map(|kw| keyword_to_completion(kw, source_range)),
)
}
let in_let = path_node
.syntax()
.ancestors()
.find_map(ast::LetIn::cast)
.is_some();
Some(
ATTR_POS_KEYWORDS
.iter()
.copied()
.chain(in_let.then_some("in"))
.map(|kw| keyword_to_completion(kw, source_range))
.collect(),
)
(|| -> Option<()> {
let source_map = db.source_map(file_id);
let infer = db.infer(file_id);
let mut attrs = path_node.attrs();
let set_ty = if is_let {
let name = source_map.name_for_node(AstPtr::new(attrs.next()?.syntax()))?;
infer.ty_for_name(name)
} else {
let container_expr = source_map.expr_for_node(AstPtr::new(&container_node))?;
infer.ty_for_expr(container_expr)
};
// Resolve prefix paths, except for the last one.
// foo.a.b.c
// ^-----^
let set = attrs
.take(attr_cnt.saturating_sub(if is_let { 2 } else { 1 }))
.try_fold(set_ty, |set_ty, attr| match AttrKind::of(attr) {
AttrKind::Static(Some(field)) => set_ty.kind(&infer).as_attrset()?.get(&field),
_ => None,
})?
.kind(&infer)
.as_attrset()?;
let current_input = name_node
.token()
.map_or(SmolStr::default(), |tok| tok.text().into());
items.extend(
set.iter()
// We should not report current incomplete definition.
// This is covered by `no_incomplete_field`.
.filter(|(name, _)| **name != current_input)
.map(|(name, ty)| CompletionItem {
label: name.clone(),
source_range,
replace: name.clone(),
kind: CompletionItemKind::Field,
brief: Some(infer.display_ty(ty).to_string()),
doc: None,
}),
);
Some(())
})();
Some(items)
}
fn keyword_to_completion(kw: &str, source_range: TextRange) -> CompletionItem {
@ -329,4 +392,62 @@ mod tests {
check_no("let a.i$0", "inherit");
check_no("let a.${i$0", "inherit");
}
#[test]
fn select_known_field() {
check(
"{ foo.bar = 1; }.f$0",
"foo",
expect!["(Field) { foo.bar = 1; }.foo"],
);
check(
"{ foo.bar = 1; }.foo.b$0",
"bar",
expect!["(Field) { foo.bar = 1; }.foo.bar"],
);
check(
"let a.foo = 1; in a.f$0",
"foo",
expect!["(Field) let a.foo = 1; in a.foo"],
);
check(
"{ foo }@b: b.f$0",
"foo",
expect!["(Field) { foo }@b: b.foo"],
);
}
#[test]
fn define_known_field_let() {
check(
"let a.f$0 = 1; in a.foo.bar",
"foo",
expect!["(Field) let a.foo = 1; in a.foo.bar"],
);
check(
"let a.foo.b$0 = 1; in a.foo.bar",
"bar",
expect!["(Field) let a.foo.bar = 1; in a.foo.bar"],
);
}
#[test]
fn define_known_field_attrset() {
check(
"let f = { foo }: foo.bar; in f { f$0 }",
"foo",
expect!["(Field) let f = { foo }: foo.bar; in f { foo }"],
);
check(
"let f = { foo }: foo.bar; in f { foo.b$0 }",
"bar",
expect!["(Field) let f = { foo }: foo.bar; in f { foo.bar }"],
);
}
#[test]
fn no_incomplete_field() {
check_no("a: a.f$0", "f");
}
}

View File

@ -14,6 +14,12 @@ use syntax::ast::{BinaryOpKind, UnaryOpKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ty(u32);
impl Ty {
pub fn kind(self, infer: &InferenceResult) -> &TyKind {
infer.kind(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TyKind {
Unknown,
@ -36,6 +42,13 @@ impl TyKind {
fn intern(self, ctx: &mut InferCtx<'_>) -> Ty {
Ty(ctx.table.push(self))
}
pub fn as_attrset(&self) -> Option<&Attrset> {
match self {
Self::Attrset(v) => Some(v),
_ => None,
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]