Skip to content

Commit

Permalink
feat: lsp hover for system pkg and builtin(str) func information. (#702)
Browse files Browse the repository at this point in the history
* feat: lsp hover for system pkg and builtin(str) func information. Show func pkg, function signature and doc when request hover on a function name

* remove func pkg and show self_ty

* use a function to encapsulate the display style of function signatures and schema definition
  • Loading branch information
He1pa authored Sep 11, 2023
1 parent be09eef commit 40d1dcc
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 50 deletions.
16 changes: 16 additions & 0 deletions kclvm/sema/src/resolver/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,22 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> {
selector_expr.attr.get_span_pos(),
);
}

if let TypeKind::Function(func) = &value_ty.kind {
self.insert_object(
&selector_expr.attr.node.get_name(),
ScopeObject {
name: selector_expr.attr.node.get_name(),
start: selector_expr.attr.get_pos(),
end: selector_expr.attr.get_end_pos(),
ty: value_ty.clone(),
kind: ScopeObjectKind::FunctionCall,
used: false,
doc: Some(func.doc.clone()),
},
)
}

value_ty
}

Expand Down
1 change: 1 addition & 0 deletions kclvm/sema/src/resolver/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub enum ScopeObjectKind {
Definition,
Parameter,
TypeAlias,
FunctionCall,
Module(Module),
}

Expand Down
1 change: 1 addition & 0 deletions kclvm/tools/src/LSP/src/document_symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ fn scope_obj_kind_to_document_symbol_kind(kind: ScopeObjectKind) -> SymbolKind {
ScopeObjectKind::Parameter => SymbolKind::VARIABLE,
ScopeObjectKind::TypeAlias => SymbolKind::TYPE_PARAMETER,
ScopeObjectKind::Module(_) => SymbolKind::MODULE,
ScopeObjectKind::FunctionCall => SymbolKind::FUNCTION,
}
}

Expand Down
45 changes: 37 additions & 8 deletions kclvm/tools/src/LSP/src/goto_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use kclvm_ast::ast::{Expr, Identifier, ImportStmt, Node, Program, Stmt};
use kclvm_compiler::pkgpath_without_prefix;
use kclvm_error::Position as KCLPos;

use kclvm_sema::resolver::scope::{ProgramScope, Scope, ScopeObject};
use kclvm_sema::builtin::get_system_member_function_ty;
use kclvm_sema::resolver::scope::{ProgramScope, Scope, ScopeObject, ScopeObjectKind};
use kclvm_sema::ty::{DictType, SchemaType};
use lsp_types::{GotoDefinitionResponse, Url};
use lsp_types::{Location, Range};
Expand Down Expand Up @@ -124,7 +125,6 @@ pub(crate) fn find_def(
}

let (inner_expr, parent) = inner_most_expr_in_stmt(&node.node, kcl_pos, None);

if let Some(expr) = inner_expr {
if let Expr::Identifier(id) = expr.node {
let id_node = Node::node_with_pos(
Expand Down Expand Up @@ -202,14 +202,43 @@ pub(crate) fn resolve_var(
kclvm_sema::ty::TypeKind::Schema(schema_type) => {
find_attr_in_schema(schema_type, &node_names[1..], scope_map)
}
kclvm_sema::ty::TypeKind::Module(module_ty) => {
match scope_map.get(&pkgpath_without_prefix!(module_ty.pkgpath)) {
Some(scope) => {
return resolve_var(&node_names[1..], &scope.borrow(), scope_map);
kclvm_sema::ty::TypeKind::Module(module_ty) => match module_ty.kind {
kclvm_sema::ty::ModuleKind::User => {
match scope_map.get(&pkgpath_without_prefix!(module_ty.pkgpath)) {
Some(scope) => {
return resolve_var(
&node_names[1..],
&scope.borrow(),
scope_map,
);
}
None => None,
}
None => None,
}
}
kclvm_sema::ty::ModuleKind::System => {
if node_names.len() == 2 {
let func_name_node = node_names[1].clone();
let func_name = func_name_node.node.clone();
let ty = get_system_member_function_ty(&name, &func_name);
match &ty.kind {
kclvm_sema::ty::TypeKind::Function(func_ty) => {
return Some(Definition::Object(ScopeObject {
name: func_name,
start: func_name_node.get_pos(),
end: func_name_node.get_end_pos(),
ty: ty.clone(),
kind: ScopeObjectKind::FunctionCall,
used: false,
doc: Some(func_ty.doc.clone()),
}))
}
_ => return None,
}
}
None
}
kclvm_sema::ty::ModuleKind::Plugin => None,
},
kclvm_sema::ty::TypeKind::Dict(DictType { attrs, .. }) => {
let key_name = names[1].clone();
match attrs.get(&key_name) {
Expand Down
185 changes: 145 additions & 40 deletions kclvm/tools/src/LSP/src/hover.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use indexmap::IndexSet;
use kclvm_ast::ast::Program;
use kclvm_error::Position as KCLPos;
use kclvm_sema::resolver::scope::{ProgramScope, ScopeObjectKind};
use kclvm_sema::{
resolver::scope::{ProgramScope, ScopeObjectKind},
ty::{FunctionType, SchemaType},
};
use lsp_types::{Hover, HoverContents, MarkedString};

use crate::goto_def::find_def;
Expand All @@ -15,55 +17,30 @@ pub(crate) fn hover(
) -> Option<lsp_types::Hover> {
match program.pos_to_stmt(kcl_pos) {
Some(node) => {
let mut docs: IndexSet<String> = IndexSet::new();
let mut docs: Vec<String> = vec![];
if let Some(def) = find_def(node, kcl_pos, prog_scope) {
if let crate::goto_def::Definition::Object(obj) = def {
match obj.kind {
ScopeObjectKind::Definition => {
// Schema Definition hover
// ```
// pkg
// schema Foo(Base)
// -----------------
// doc
// -----------------
// Attributes:
// attr1: type
// attr2? type
// ```
let schema_ty = obj.ty.into_schema_type();
let base: String = if let Some(base) = schema_ty.base {
format!("({})", base.name)
} else {
"".to_string()
};
docs.insert(format!(
"{}\n\nschema {}{}",
schema_ty.pkgpath, schema_ty.name, base
));
if !schema_ty.doc.is_empty() {
docs.insert(schema_ty.doc.clone());
}
let mut attrs = vec!["Attributes:".to_string()];
for (name, attr) in schema_ty.attrs {
attrs.push(format!(
"{}{}:{}",
name,
if attr.is_optional { "?" } else { "" },
format!(" {}", attr.ty.ty_str()),
));
docs.extend(build_schema_hover_content(&obj.ty.into_schema_type()))
}
ScopeObjectKind::FunctionCall => {
let ty = obj.ty.clone();
match &ty.kind {
kclvm_sema::ty::TypeKind::Function(func_ty) => {
docs.extend(build_func_hover_content(func_ty, obj.name))
}
_ => {}
}
docs.insert(attrs.join("\n\n"));
}
// todo: hover ScopeObjectKind::Attribute optional, default value
_ => {
// Variable
// ```
// name: type
//```
docs.insert(format!("{}: {}", obj.name, obj.ty.ty_str()));
docs.push(format!("{}: {}", obj.name, obj.ty.ty_str()));
if let Some(doc) = obj.doc {
docs.insert(doc);
docs.push(doc);
}
}
}
Expand All @@ -77,7 +54,7 @@ pub(crate) fn hover(

// Convert docs to Hover. This function will convert to
// None, Scalar or Array according to the number of positions
fn docs_to_hover(docs: IndexSet<String>) -> Option<lsp_types::Hover> {
fn docs_to_hover(docs: Vec<String>) -> Option<lsp_types::Hover> {
match docs.len() {
0 => None,
1 => Some(Hover {
Expand All @@ -95,6 +72,82 @@ fn docs_to_hover(docs: IndexSet<String>) -> Option<lsp_types::Hover> {
}
}

// Build hover content for schema definition
// Schema Definition hover
// ```
// pkg
// schema Foo(Base)
// -----------------
// doc
// -----------------
// Attributes:
// attr1: type
// attr2? type
// ```
fn build_schema_hover_content(schema_ty: &SchemaType) -> Vec<String> {
let mut docs = vec![];
let base: String = if let Some(base) = &schema_ty.base {
format!("({})", base.name)
} else {
"".to_string()
};
docs.push(format!(
"{}\n\nschema {}{}",
schema_ty.pkgpath, schema_ty.name, base
));
if !schema_ty.doc.is_empty() {
docs.push(schema_ty.doc.clone());
}
let mut attrs = vec!["Attributes:".to_string()];
for (name, attr) in &schema_ty.attrs {
attrs.push(format!(
"{}{}:{}",
name,
if attr.is_optional { "?" } else { "" },
format!(" {}", attr.ty.ty_str()),
));
}
docs.push(attrs.join("\n\n"));
docs
}

// Build hover content for function call
// ```
// pkg
// -----------------
// function func_name(arg1: type, arg2: type, ..) -> type
// -----------------
// doc
// ```
fn build_func_hover_content(func_ty: &FunctionType, name: String) -> Vec<String> {
let mut docs = vec![];
if let Some(ty) = &func_ty.self_ty {
let self_ty = format!("{}\n\n", ty.ty_str());
docs.push(self_ty);
}

let mut sig = format!("fn {}(", name);
if func_ty.params.is_empty() {
sig.push_str(")");
} else {
for (i, p) in func_ty.params.iter().enumerate() {
sig.push_str(&format!("{}: {}", p.name, p.ty.ty_str()));

if i != func_ty.params.len() - 1 {
sig.push_str(", ");
}
}
sig.push_str(")");
}
sig.push_str(&format!(" -> {}", func_ty.return_ty.ty_str()));
docs.push(sig);

if !func_ty.doc.is_empty() {
docs.push(func_ty.doc.clone());
}
docs
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;
Expand Down Expand Up @@ -232,4 +285,56 @@ mod tests {
_ => unreachable!("test error"),
}
}

#[test]
#[bench_test]
fn func_def_hover() {
let (file, program, prog_scope, _) = compile_test_file("src/test_data/hover_test/hover.k");

let pos = KCLPos {
filename: file.clone(),
line: 22,
column: Some(18),
};
let got = hover(&program, &pos, &prog_scope).unwrap();

match got.contents {
lsp_types::HoverContents::Array(vec) => {
assert_eq!(vec.len(), 2);
if let MarkedString::String(s) = vec[0].clone() {
assert_eq!(s, "fn encode(value: str, encoding: str) -> str");
}
if let MarkedString::String(s) = vec[1].clone() {
assert_eq!(
s,
"Encode the string `value` using the codec registered for encoding."
);
}
}
_ => unreachable!("test error"),
}

let pos = KCLPos {
filename: file.clone(),
line: 23,
column: Some(14),
};
let got = hover(&program, &pos, &prog_scope).unwrap();

match got.contents {
lsp_types::HoverContents::Array(vec) => {
assert_eq!(vec.len(), 3);
if let MarkedString::String(s) = vec[0].clone() {
assert_eq!(s, "str\n\n");
}
if let MarkedString::String(s) = vec[1].clone() {
assert_eq!(s, "fn count(sub: str, start: int, end: int) -> int");
}
if let MarkedString::String(s) = vec[2].clone() {
assert_eq!(s, "Return the number of non-overlapping occurrences of substring sub in the range [start, end]. Optional arguments start and end are interpreted as in slice notation.");
}
}
_ => unreachable!("test error"),
}
}
}
2 changes: 1 addition & 1 deletion kclvm/tools/src/LSP/src/test_data/goto_def_test/goto_def.k
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@ items1 = [item | {
spec.containers: [{
"securityContext": {"capabilities": {"add" += [cc] if cc not in (container?.securityContext?.capabilities?.drop or []) else [] for cc in capabilities}}
} for container in item.spec.containers]
} for item in option("items")]
} for item in option("items")]
6 changes: 5 additions & 1 deletion kclvm/tools/src/LSP/src/test_data/hover_test/hover.k
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ schema Person:
p = Person{
name: "Alice"
age: 1
}
}

import base64
abdc = base64.encode("1")
abcd = "a".count()

0 comments on commit 40d1dcc

Please sign in to comment.