diff --git a/crates/tinymist-query/src/analysis/definition.rs b/crates/tinymist-query/src/analysis/definition.rs index d8019465..cdf99620 100644 --- a/crates/tinymist-query/src/analysis/definition.rs +++ b/crates/tinymist-query/src/analysis/definition.rs @@ -52,6 +52,10 @@ impl Definition { pub(crate) fn value(&self) -> Option { self.term.as_ref()?.value() } + + pub(crate) fn from_value(value: Value, name: impl FnOnce() -> Option) -> Option { + value_to_def(value, name) + } } trait HasNameRange { @@ -336,7 +340,7 @@ static WHERE_FUNC: LazyLock> = LazyLock::new(|| { Some(func) }); -fn value_to_def(value: Value, name: impl FnOnce() -> Option>) -> Option { +fn value_to_def(value: Value, name: impl FnOnce() -> Option) -> Option { let val = Ty::Value(InsTy::new(value.clone())); Some(match value { Value::Func(func) => { diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index d457af0c..ee4fa5a4 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -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, span: Span) -> Option { - self.type_of_span_(&self.source_by_id(span.id()?).ok()?, span) + pub(crate) fn def_of_syntax_or_dyn( + self: &Arc, + source: &Source, + doc: Option<&TypstDocument>, + syntax: SyntaxClass, + ) -> Option { + 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, source: &Source, span: Span) -> Option { - self.type_check(source).type_of_span(span) + pub(crate) fn simplified_type_of_span(self: &Arc, span: Span) -> Option { + 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, span: Span) -> Option { + 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, + source: &Source, + span: Span, + ) -> Option<(Arc, 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, node: LinkedNode) -> Option { @@ -889,6 +946,24 @@ impl SharedContext { super::sig_of_type(self, ti, ty) } + pub(crate) fn sig_of_type_or_dyn( + self: &Arc, + ti: &TypeInfo, + callee_ty: Ty, + callee: &SyntaxNode, + ) -> Option { + 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. /// diff --git a/crates/tinymist-query/src/analysis/post_tyck.rs b/crates/tinymist-query/src/analysis/post_tyck.rs index c3b93b6a..12ff51be 100644 --- a/crates/tinymist-query/src/analysis/post_tyck.rs +++ b/crates/tinymist-query/src/analysis/post_tyck.rs @@ -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(); diff --git a/crates/tinymist-query/src/fixtures/completion/func_jsx_import.typ b/crates/tinymist-query/src/fixtures/completion/func_jsx_import.typ new file mode 100644 index 00000000..cf25c735 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/func_jsx_import.typ @@ -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 */ +) diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_jsx_import.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_jsx_import.typ.snap new file mode 100644 index 00000000..04f75eab --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_jsx_import.typ.snap @@ -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 + } + } + } + } + ] + } +] diff --git a/crates/tinymist-query/src/fixtures/hover/jsx.typ b/crates/tinymist-query/src/fixtures/hover/jsx.typ new file mode 100644 index 00000000..588f9251 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/hover/jsx.typ @@ -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) diff --git a/crates/tinymist-query/src/fixtures/hover/snaps/test@jsx.typ.snap b/crates/tinymist-query/src/fixtures/hover/snaps/test@jsx.typ.snap new file mode 100644 index 00000000..96f9a77f --- /dev/null +++ b/crates/tinymist-query/src/fixtures/hover/snaps/test@jsx.typ.snap @@ -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. diff --git a/crates/tinymist-query/src/fixtures/playground/snaps/test.snap b/crates/tinymist-query/src/fixtures/playground/snaps/test.snap deleted file mode 100644 index fd2b4e05..00000000 --- a/crates/tinymist-query/src/fixtures/playground/snaps/test.snap +++ /dev/null @@ -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 -``` diff --git a/crates/tinymist-query/src/goto_definition.rs b/crates/tinymist-query/src/goto_definition.rs index 28f49219..772ea8f0 100644 --- a/crates/tinymist-query/src/goto_definition.rs +++ b/crates/tinymist-query/src/goto_definition.rs @@ -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(); diff --git a/crates/tinymist-query/src/hover.rs b/crates/tinymist-query/src/hover.rs index ac16b9bf..89de53d0 100644 --- a/crates/tinymist-query/src/hover.rs +++ b/crates/tinymist-query/src/hover.rs @@ -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() { diff --git a/crates/tinymist-query/src/signature_help.rs b/crates/tinymist-query/src/signature_help.rs index fd253550..ccec87df 100644 --- a/crates/tinymist-query/src/signature_help.rs +++ b/crates/tinymist-query/src/signature_help.rs @@ -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:?}");