mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-24 13:10:07 +00:00
feat: resolve definitions with dynamic analysis (#1904)
Some checks failed
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / build-vsc-assets (push) Has been cancelled
tinymist::ci / build-vscode (push) Has been cancelled
tinymist::ci / build-vscode-others (push) Has been cancelled
tinymist::ci / publish-vscode (push) Has been cancelled
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-2022) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Has been cancelled
tinymist::ci / build-binary (push) Has been cancelled
Some checks failed
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / build-vsc-assets (push) Has been cancelled
tinymist::ci / build-vscode (push) Has been cancelled
tinymist::ci / build-vscode-others (push) Has been cancelled
tinymist::ci / publish-vscode (push) Has been cancelled
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-2022) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Has been cancelled
tinymist::ci / build-binary (push) Has been cancelled
* feat: dyn resolve targets * fix: test cases * feat: static analysis again based on dyn analysis result
This commit is contained in:
parent
35e8f447b0
commit
1359e9975b
11 changed files with 218 additions and 69 deletions
|
|
@ -52,6 +52,10 @@ impl Definition {
|
||||||
pub(crate) fn value(&self) -> Option<Value> {
|
pub(crate) fn value(&self) -> Option<Value> {
|
||||||
self.term.as_ref()?.value()
|
self.term.as_ref()?.value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_value(value: Value, name: impl FnOnce() -> Option<StrRef>) -> Option<Self> {
|
||||||
|
value_to_def(value, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait HasNameRange {
|
trait HasNameRange {
|
||||||
|
|
@ -336,7 +340,7 @@ static WHERE_FUNC: LazyLock<Option<&'static Func>> = LazyLock::new(|| {
|
||||||
Some(func)
|
Some(func)
|
||||||
});
|
});
|
||||||
|
|
||||||
fn value_to_def(value: Value, name: impl FnOnce() -> Option<Interned<str>>) -> Option<Definition> {
|
fn value_to_def(value: Value, name: impl FnOnce() -> Option<StrRef>) -> Option<Definition> {
|
||||||
let val = Ty::Value(InsTy::new(value.clone()));
|
let val = Ty::Value(InsTy::new(value.clone()));
|
||||||
Some(match value {
|
Some(match value {
|
||||||
Value::Func(func) => {
|
Value::Func(func) => {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use parking_lot::Mutex;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use tinymist_analysis::docs::DocString;
|
use tinymist_analysis::docs::DocString;
|
||||||
use tinymist_analysis::stats::AllocStats;
|
use tinymist_analysis::stats::AllocStats;
|
||||||
|
use tinymist_analysis::syntax::classify_def_loosely;
|
||||||
use tinymist_analysis::ty::term_value;
|
use tinymist_analysis::ty::term_value;
|
||||||
use tinymist_analysis::{analyze_expr_, analyze_import_};
|
use tinymist_analysis::{analyze_expr_, analyze_import_};
|
||||||
use tinymist_lint::LintInfo;
|
use tinymist_lint::LintInfo;
|
||||||
|
|
@ -861,12 +862,68 @@ impl SharedContext {
|
||||||
definition(self, source, doc, syntax)
|
definition(self, source, doc, syntax)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
|
pub(crate) fn def_of_syntax_or_dyn(
|
||||||
self.type_of_span_(&self.source_by_id(span.id()?).ok()?, span)
|
self: &Arc<Self>,
|
||||||
|
source: &Source,
|
||||||
|
doc: Option<&TypstDocument>,
|
||||||
|
syntax: SyntaxClass,
|
||||||
|
) -> Option<Definition> {
|
||||||
|
let def = self.def_of_syntax(source, doc, syntax.clone());
|
||||||
|
match def.as_ref().map(|d| d.decl.kind()) {
|
||||||
|
// todo: DefKind::Function
|
||||||
|
Some(DefKind::Reference | DefKind::Module | DefKind::Function) => return def,
|
||||||
|
Some(DefKind::Struct | DefKind::Constant | DefKind::Variable) | None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn type_of_span_(self: &Arc<Self>, source: &Source, span: Span) -> Option<Ty> {
|
// Checks that we resolved a high-equality definition.
|
||||||
self.type_check(source).type_of_span(span)
|
let know_ty_well = def
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|d| self.simplified_type_of_span(d.decl.span()))
|
||||||
|
.filter(|ty| !matches!(ty, Ty::Any))
|
||||||
|
.is_some();
|
||||||
|
if know_ty_well {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
let def_ref = def.as_ref();
|
||||||
|
let def_name = || Some(def_ref?.name().clone());
|
||||||
|
let dyn_def = self
|
||||||
|
.analyze_expr(syntax.node())
|
||||||
|
.iter()
|
||||||
|
.find_map(|(value, _)| {
|
||||||
|
let def = Definition::from_value(value.clone(), def_name)?;
|
||||||
|
None.or_else(|| {
|
||||||
|
let source = self.source_by_id(def.decl.file_id()?).ok()?;
|
||||||
|
let node = LinkedNode::new(source.root()).find(def.decl.span())?;
|
||||||
|
let def_at_the_span = classify_def_loosely(node)?;
|
||||||
|
self.def_of_span(&source, doc, def_at_the_span.name()?.span())
|
||||||
|
})
|
||||||
|
.or(Some(def))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Uses the dynamic definition or the fallback definition.
|
||||||
|
dyn_def.or(def)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn simplified_type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
|
||||||
|
let source = self.source_by_id(span.id()?).ok()?;
|
||||||
|
let (ti, ty) = self.type_of_span_(&source, span)?;
|
||||||
|
Some(ti.simplify(ty, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
|
||||||
|
let source = self.source_by_id(span.id()?).ok()?;
|
||||||
|
Some(self.type_of_span_(&source, span)?.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn type_of_span_(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
source: &Source,
|
||||||
|
span: Span,
|
||||||
|
) -> Option<(Arc<TypeInfo>, Ty)> {
|
||||||
|
let ti = self.type_check(source);
|
||||||
|
let ty = ti.type_of_span(span)?;
|
||||||
|
Some((ti, ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn post_type_of_node(self: &Arc<Self>, node: LinkedNode) -> Option<Ty> {
|
pub(crate) fn post_type_of_node(self: &Arc<Self>, node: LinkedNode) -> Option<Ty> {
|
||||||
|
|
@ -889,6 +946,24 @@ impl SharedContext {
|
||||||
super::sig_of_type(self, ti, ty)
|
super::sig_of_type(self, ti, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sig_of_type_or_dyn(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
ti: &TypeInfo,
|
||||||
|
callee_ty: Ty,
|
||||||
|
callee: &SyntaxNode,
|
||||||
|
) -> Option<Signature> {
|
||||||
|
self.sig_of_type(ti, callee_ty).or_else(|| {
|
||||||
|
self.analyze_expr(callee).iter().find_map(|(value, _)| {
|
||||||
|
let Value::Func(callee) = value else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Converts with cache
|
||||||
|
analyze_signature(self, SignatureTarget::Runtime(callee.clone()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to find imported target from the current source file.
|
/// Try to find imported target from the current source file.
|
||||||
/// This function will try to resolves target statically.
|
/// This function will try to resolves target statically.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -222,12 +222,12 @@ impl<'a> PostTypeChecker<'a> {
|
||||||
target,
|
target,
|
||||||
is_set,
|
is_set,
|
||||||
} => {
|
} => {
|
||||||
let callee = self.check_or(callee, context_ty)?;
|
let callee_ty = self.check_or(callee, context_ty)?;
|
||||||
crate::log_debug_ct!(
|
crate::log_debug_ct!(
|
||||||
"post check call target: ({callee:?})::{target:?} is_set: {is_set}"
|
"post check call target: ({callee_ty:?})::{target:?} is_set: {is_set}"
|
||||||
);
|
);
|
||||||
|
|
||||||
let sig = self.ctx.sig_of_type(self.info, callee)?;
|
let sig = self.ctx.sig_of_type_or_dyn(self.info, callee_ty, callee)?;
|
||||||
crate::log_debug_ct!("post check call sig: {target:?} {sig:?}");
|
crate::log_debug_ct!("post check call sig: {target:?} {sig:?}");
|
||||||
let mut resp = SignatureReceiver::default();
|
let mut resp = SignatureReceiver::default();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/// path: jsx-typings.typ
|
||||||
|
|
||||||
|
/// Creates a counter component.
|
||||||
|
///
|
||||||
|
/// - initialCounter (int): The initial value for the counter.
|
||||||
|
/// -> content
|
||||||
|
#let Counter(initialCounter: 0) = none
|
||||||
|
|
||||||
|
#let modules = (
|
||||||
|
"$components/Counter.astro": (
|
||||||
|
Counter: Counter,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
-----
|
||||||
|
/// path: jsx-runtime.typ
|
||||||
|
|
||||||
|
#let typing-path = sys.inputs.at("x-jsx-typings", default: "jsx-typings.typ")
|
||||||
|
|
||||||
|
#import typing-path as typings
|
||||||
|
|
||||||
|
#let require = (it, hint: none) => typings.modules.at(it)
|
||||||
|
|
||||||
|
-----
|
||||||
|
/// contains: initialCounter
|
||||||
|
#import "jsx-runtime.typ": require
|
||||||
|
|
||||||
|
#let (Counter: Counter) = require("$components/Counter.astro");
|
||||||
|
|
||||||
|
#Counter(
|
||||||
|
/* range 0..1 */
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/completion.rs
|
||||||
|
description: Completion on / (142..143)
|
||||||
|
expression: "JsonRepr::new_pure(results)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/completion/func_jsx_import.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"isIncomplete": false,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"command": {
|
||||||
|
"command": "tinymist.triggerSuggestAndParameterHints",
|
||||||
|
"title": ""
|
||||||
|
},
|
||||||
|
"kind": 5,
|
||||||
|
"label": "initialCounter",
|
||||||
|
"labelDetails": {
|
||||||
|
"description": "0 | int"
|
||||||
|
},
|
||||||
|
"sortText": "000",
|
||||||
|
"textEdit": {
|
||||||
|
"newText": "initialCounter: ${1:}",
|
||||||
|
"range": {
|
||||||
|
"end": {
|
||||||
|
"character": 2,
|
||||||
|
"line": 6
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"character": 2,
|
||||||
|
"line": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
30
crates/tinymist-query/src/fixtures/hover/jsx.typ
Normal file
30
crates/tinymist-query/src/fixtures/hover/jsx.typ
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/// path: jsx-typings.typ
|
||||||
|
|
||||||
|
/// Creates a counter component.
|
||||||
|
///
|
||||||
|
/// - initialCounter (int): The initial value for the counter.
|
||||||
|
/// -> content
|
||||||
|
#let Counter(initialCounter: 0) = none
|
||||||
|
|
||||||
|
#let modules = (
|
||||||
|
"$components/Counter.astro": (
|
||||||
|
Counter: Counter,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
-----
|
||||||
|
/// path: jsx-runtime.typ
|
||||||
|
|
||||||
|
#let typing-path = sys.inputs.at("x-jsx-typings", default: "jsx-typings.typ")
|
||||||
|
|
||||||
|
#import typing-path as typings
|
||||||
|
|
||||||
|
#let require = (it, hint: none) => typings.modules.at(it)
|
||||||
|
|
||||||
|
-----
|
||||||
|
#import "jsx-runtime.typ": require
|
||||||
|
|
||||||
|
#let (Counter: Counter) = require("$components/Counter.astro");
|
||||||
|
|
||||||
|
#(/* ident after */ Counter)
|
||||||
|
#Counter(initialCounter: 0)
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/hover.rs
|
||||||
|
expression: content
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/hover/jsx.typ
|
||||||
|
---
|
||||||
|
Range: 4:20:4:27
|
||||||
|
|
||||||
|
```typc
|
||||||
|
let Counter(
|
||||||
|
initialCounter: int = 0,
|
||||||
|
) = content | none;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
======
|
||||||
|
|
||||||
|
|
||||||
|
Creates a counter component.
|
||||||
|
|
||||||
|
# Named Parameters
|
||||||
|
|
||||||
|
## initialCounter
|
||||||
|
|
||||||
|
```typc
|
||||||
|
type: 0 | int
|
||||||
|
```
|
||||||
|
|
||||||
|
The initial value for the counter.
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
---
|
|
||||||
source: crates/tinymist-query/src/hover.rs
|
|
||||||
expression: content
|
|
||||||
input_file: crates/tinymist-query/src/fixtures/playground/base.typ
|
|
||||||
---
|
|
||||||
Range: 5:20:5:26
|
|
||||||
|
|
||||||
```typc
|
|
||||||
let my-fun(
|
|
||||||
note: any,
|
|
||||||
mode: str = "typ",
|
|
||||||
setting: (any) => any = Closure(..),
|
|
||||||
) = none;
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
======
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
failed to parse docs: error: unclosed delimiter
|
|
||||||
┌─ /dummy-root/__wrap_md_main.typ:2:0
|
|
||||||
│
|
|
||||||
2 │ *
|
|
||||||
│ ^
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```typ
|
|
||||||
*
|
|
||||||
```
|
|
||||||
|
|
||||||
# Positional Parameters
|
|
||||||
|
|
||||||
## note
|
|
||||||
|
|
||||||
```typc
|
|
||||||
type:
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Named Parameters
|
|
||||||
|
|
||||||
## mode
|
|
||||||
|
|
||||||
```typc
|
|
||||||
type: "typ"
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## setting (named)
|
|
||||||
|
|
||||||
```typc
|
|
||||||
type: (any) => any
|
|
||||||
```
|
|
||||||
|
|
@ -32,7 +32,7 @@ impl StatefulRequest for GotoDefinitionRequest {
|
||||||
let syntax = ctx.classify_for_decl(&source, self.position)?;
|
let syntax = ctx.classify_for_decl(&source, self.position)?;
|
||||||
let origin_selection_range = ctx.to_lsp_range(syntax.node().range(), &source);
|
let origin_selection_range = ctx.to_lsp_range(syntax.node().range(), &source);
|
||||||
|
|
||||||
let def = ctx.def_of_syntax(&source, doc, syntax)?;
|
let def = ctx.def_of_syntax_or_dyn(&source, doc, syntax)?;
|
||||||
|
|
||||||
let fid = def.file_id()?;
|
let fid = def.file_id()?;
|
||||||
let name_range = def.name_range(ctx.shared()).unwrap_or_default();
|
let name_range = def.name_range(ctx.shared()).unwrap_or_default();
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ impl HoverWorker<'_> {
|
||||||
let syntax = classify_syntax(leaf.clone(), self.cursor)?;
|
let syntax = classify_syntax(leaf.clone(), self.cursor)?;
|
||||||
let def = self
|
let def = self
|
||||||
.ctx
|
.ctx
|
||||||
.def_of_syntax(&self.source, self.doc.as_ref(), syntax.clone())?;
|
.def_of_syntax_or_dyn(&self.source, self.doc.as_ref(), syntax.clone())?;
|
||||||
|
|
||||||
use Decl::*;
|
use Decl::*;
|
||||||
match def.decl.as_ref() {
|
match def.decl.as_ref() {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ impl SemanticRequest for SignatureHelpRequest {
|
||||||
};
|
};
|
||||||
|
|
||||||
let syntax = classify_syntax(callee, cursor)?;
|
let syntax = classify_syntax(callee, cursor)?;
|
||||||
let def = ctx.def_of_syntax(&source, None, syntax)?;
|
let def = ctx.def_of_syntax_or_dyn(&source, None, syntax)?;
|
||||||
let sig = ctx.sig_of_def(def.clone())?;
|
let sig = ctx.sig_of_def(def.clone())?;
|
||||||
crate::log_debug_ct!("got signature {sig:?}");
|
crate::log_debug_ct!("got signature {sig:?}");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue