mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-22 20:35:20 +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> {
|
||||
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 {
|
||||
|
|
@ -336,7 +340,7 @@ static WHERE_FUNC: LazyLock<Option<&'static Func>> = LazyLock::new(|| {
|
|||
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()));
|
||||
Some(match value {
|
||||
Value::Func(func) => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use parking_lot::Mutex;
|
|||
use rustc_hash::FxHashMap;
|
||||
use tinymist_analysis::docs::DocString;
|
||||
use tinymist_analysis::stats::AllocStats;
|
||||
use tinymist_analysis::syntax::classify_def_loosely;
|
||||
use tinymist_analysis::ty::term_value;
|
||||
use tinymist_analysis::{analyze_expr_, analyze_import_};
|
||||
use tinymist_lint::LintInfo;
|
||||
|
|
@ -861,12 +862,68 @@ impl SharedContext {
|
|||
definition(self, source, doc, syntax)
|
||||
}
|
||||
|
||||
pub(crate) fn type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
|
||||
self.type_of_span_(&self.source_by_id(span.id()?).ok()?, span)
|
||||
pub(crate) fn def_of_syntax_or_dyn(
|
||||
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 => {}
|
||||
}
|
||||
|
||||
// Checks that we resolved a high-equality definition.
|
||||
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 type_of_span_(self: &Arc<Self>, source: &Source, span: Span) -> Option<Ty> {
|
||||
self.type_check(source).type_of_span(span)
|
||||
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> {
|
||||
|
|
@ -889,6 +946,24 @@ impl SharedContext {
|
|||
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.
|
||||
/// This function will try to resolves target statically.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -222,12 +222,12 @@ impl<'a> PostTypeChecker<'a> {
|
|||
target,
|
||||
is_set,
|
||||
} => {
|
||||
let callee = self.check_or(callee, context_ty)?;
|
||||
let callee_ty = self.check_or(callee, context_ty)?;
|
||||
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:?}");
|
||||
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 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 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 def = self
|
||||
.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::*;
|
||||
match def.decl.as_ref() {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl SemanticRequest for SignatureHelpRequest {
|
|||
};
|
||||
|
||||
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())?;
|
||||
crate::log_debug_ct!("got signature {sig:?}");
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue