From 29a10c144e45cf4783908db3db881aa451bcc512 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:22:39 +0800 Subject: [PATCH] fix: import type inference result from other modules (#2168) fix #2131 --- crates/tinymist-analysis/src/syntax/def.rs | 14 +++- .../src/analysis/completion/scope.rs | 3 +- crates/tinymist-query/src/analysis/global.rs | 45 ++++++++---- crates/tinymist-query/src/docs/module.rs | 10 +-- .../completion/import_star_typing.typ | 9 +++ .../src/fixtures/completion/import_typing.typ | 9 +++ .../snaps/test@import_star.typ.snap | 12 ++-- .../snaps/test@import_star_typing.typ.snap | 68 +++++++++++++++++++ .../snaps/test@import_typing.typ.snap | 68 +++++++++++++++++++ .../snaps/test@item_shadow2.typ.snap | 4 +- .../snaps/test@touying-core-slides.typ-2.snap | 2 +- ...est@touying-utils-cover-with-rect.typ.snap | 2 +- .../test@touying-utils-markup-text.typ.snap | 2 +- crates/tinymist-query/src/syntax/expr.rs | 4 +- tests/e2e/e2e/lsp.rs | 2 +- 15 files changed, 215 insertions(+), 39 deletions(-) create mode 100644 crates/tinymist-query/src/fixtures/completion/import_star_typing.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/import_typing.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@import_star_typing.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@import_typing.typ.snap diff --git a/crates/tinymist-analysis/src/syntax/def.rs b/crates/tinymist-analysis/src/syntax/def.rs index 0552881c..c2951244 100644 --- a/crates/tinymist-analysis/src/syntax/def.rs +++ b/crates/tinymist-analysis/src/syntax/def.rs @@ -512,8 +512,18 @@ impl Decl { }) } - /// Creates a module declaration with a name and file ID. - pub fn module(name: Interned, fid: TypstFileId) -> Self { + /// Creates a module declaration with a file ID. + pub fn module(fid: TypstFileId) -> Self { + let name = { + let stem = fid.vpath().as_rooted_path().file_stem(); + stem.and_then(|s| Some(Interned::new_str(s.to_str()?))) + .unwrap_or_default() + }; + Self::Module(ModuleDecl { name, fid }) + } + + /// Creates a module declaration with a name and a file ID. + pub fn module_with_name(name: Interned, fid: TypstFileId) -> Self { Self::Module(ModuleDecl { name, fid }) } diff --git a/crates/tinymist-query/src/analysis/completion/scope.rs b/crates/tinymist-query/src/analysis/completion/scope.rs index 750cf022..ed3e2445 100644 --- a/crates/tinymist-query/src/analysis/completion/scope.rs +++ b/crates/tinymist-query/src/analysis/completion/scope.rs @@ -202,8 +202,7 @@ fn analyze_import_source(ctx: &LocalContext, types: &TypeInfo, s: ast::Expr) -> return Some(types.simplify(res, false)); } - let m = ctx.module_by_syntax(s.to_untyped())?; - Some(Ty::Value(InsTy::new_at(m, s.span()))) + ctx.module_term_by_syntax(s.to_untyped(), false) } pub(crate) enum ScopeCheckKind { diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index 0f134b2d..6e672ed6 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -11,7 +11,7 @@ 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::ty::{BuiltinTy, InsTy, term_value}; use tinymist_analysis::{analyze_expr_, analyze_import_}; use tinymist_lint::{KnownIssues, LintInfo}; use tinymist_project::{LspComputeGraph, LspWorld, TaskWhen}; @@ -700,51 +700,68 @@ impl SharedContext { }) } - /// Get a module by file id. + /// Gets a module by file id. pub fn module_by_id(&self, fid: TypstFileId) -> SourceResult { let source = self.source_by_id(fid).at(Span::detached())?; self.module_by_src(source) } - /// Get a module by string. + /// Gets a module by string. pub fn module_by_str(&self, rr: String) -> Option { let src = Source::new(*DETACHED_ENTRY, rr); self.module_by_src(src).ok() } - /// Get (Create) a module by source. + /// Gets (Creates) a module by source. pub fn module_by_src(&self, source: Source) -> SourceResult { eval_compat(&self.world, &source) } - /// Try to load a module from the current source file. - pub fn module_by_syntax(&self, source: &SyntaxNode) -> Option { + /// Gets a module value from a given source file. + pub fn module_by_syntax(self: &Arc, source: &SyntaxNode) -> Option { + self.module_term_by_syntax(source, true) + .and_then(|ty| ty.value()) + } + + /// Gets a module term from a given source file. If `value` is true, it will + /// prefer to get a value instead of a term. + pub fn module_term_by_syntax(self: &Arc, source: &SyntaxNode, value: bool) -> Option { let (src, scope) = self.analyze_import(source); if let Some(scope) = scope { - return Some(scope); + return Some(match scope { + Value::Module(m) if m.file_id().is_some() => { + Ty::Builtin(BuiltinTy::Module(Decl::module(m.file_id()?).into())) + } + scope => Ty::Value(InsTy::new(scope)), + }); } match src { Some(Value::Str(s)) => { let id = resolve_id_by_path(&self.world, source.span().id()?, s.as_str())?; - self.module_by_id(id).ok().map(Value::Module) + + Some(if value { + Ty::Value(InsTy::new(Value::Module(self.module_by_id(id).ok()?))) + } else { + Ty::Builtin(BuiltinTy::Module(Decl::module(id).into())) + }) } _ => None, } } - /// Get the expression information of a source file. + /// Gets the expression information of a source file. pub(crate) fn expr_stage_by_id(self: &Arc, fid: TypstFileId) -> Option { Some(self.expr_stage(&self.source_by_id(fid).ok()?)) } - /// Get the expression information of a source file. + /// Gets the expression information of a source file. pub(crate) fn expr_stage(self: &Arc, source: &Source) -> ExprInfo { let mut route = ExprRoute::default(); self.expr_stage_(source, &mut route) } - /// Get the expression information of a source file. + /// Gets the expression information of a source file. pub(crate) fn expr_stage_( self: &Arc, source: &Source, @@ -769,13 +786,13 @@ impl SharedContext { Some(self.expr_stage_(source, route).exports.clone()) } - /// Get the type check information of a source file. + /// Gets the type check information of a source file. pub(crate) fn type_check(self: &Arc, source: &Source) -> Arc { let mut route = TypeEnv::default(); self.type_check_(source, &mut route) } - /// Get the type check information of a source file. + /// Gets the type check information of a source file. pub(crate) fn type_check_( self: &Arc, source: &Source, @@ -796,7 +813,7 @@ impl SharedContext { }) } - /// Get the lint result of a source file. + /// Gets the lint result of a source file. #[typst_macros::time(span = source.root().span())] pub(crate) fn lint(self: &Arc, source: &Source, issues: &KnownIssues) -> LintInfo { let ei = self.expr_stage(source); diff --git a/crates/tinymist-query/src/docs/module.rs b/crates/tinymist-query/src/docs/module.rs index 5435d6c9..7c078421 100644 --- a/crates/tinymist-query/src/docs/module.rs +++ b/crates/tinymist-query/src/docs/module.rs @@ -119,15 +119,11 @@ struct ScanDefCtx<'a> { impl ScanDefCtx<'_> { fn defs(&mut self, paths: EcoVec<&str>, ei: ExprInfo) -> DefInfo { - let name = { - let stem = ei.fid.vpath().as_rooted_path().file_stem(); - stem.and_then(|s| Some(Interned::new_str(s.to_str()?))) - .unwrap_or_default() - }; - let module_decl = Decl::module(name.clone(), ei.fid).into(); + let module_decl = Decl::module(ei.fid); + let key = module_decl.name().clone(); let site = Some(self.root); let paths = paths.clone(); - self.def(&name, paths, site.as_ref(), &module_decl, None) + self.def(&key, paths, site.as_ref(), &module_decl.into(), None) } fn expr( diff --git a/crates/tinymist-query/src/fixtures/completion/import_star_typing.typ b/crates/tinymist-query/src/fixtures/completion/import_star_typing.typ new file mode 100644 index 00000000..beb03a95 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/import_star_typing.typ @@ -0,0 +1,9 @@ +/// path: base.typ +/// - body (content): The body of the body +/// -> content +#let todo(body) = body; // redefine just for auto-completion + +----- +/// contains: todo +#import "base.typ" as baz: * +#to(/* range -2..0 */ ); diff --git a/crates/tinymist-query/src/fixtures/completion/import_typing.typ b/crates/tinymist-query/src/fixtures/completion/import_typing.typ new file mode 100644 index 00000000..974add57 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/import_typing.typ @@ -0,0 +1,9 @@ +/// path: base.typ +/// - body (content): The body of the body +/// -> content +#let todo(body) = body; // redefine just for auto-completion + +----- +/// contains: todo +#import "base.typ": todo +#to(/* range -2..0 */ ); diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star.typ.snap index daaaa48a..c6dcef6f 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star.typ.snap @@ -12,7 +12,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ "kind": 3, "label": "aa", "labelDetails": { - "description": "() => any" + "description": "() => 1" }, "sortText": "000", "textEdit": { @@ -33,7 +33,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ "kind": 3, "label": "aab", "labelDetails": { - "description": "() => any" + "description": "() => 1" }, "sortText": "001", "textEdit": { @@ -54,7 +54,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ "kind": 3, "label": "aabc", "labelDetails": { - "description": "() => any" + "description": "() => 1" }, "sortText": "002", "textEdit": { @@ -75,7 +75,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ "kind": 3, "label": "aac", "labelDetails": { - "description": "() => any" + "description": "() => 1" }, "sortText": "003", "textEdit": { @@ -101,7 +101,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ "kind": 3, "label": "aabc", "labelDetails": { - "description": "() => any" + "description": "() => 1" }, "sortText": "002", "textEdit": { @@ -122,7 +122,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ "kind": 3, "label": "aac", "labelDetails": { - "description": "() => any" + "description": "() => 1" }, "sortText": "003", "textEdit": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star_typing.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star_typing.typ.snap new file mode 100644 index 00000000..bac7bbb7 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star_typing.typ.snap @@ -0,0 +1,68 @@ +--- +source: crates/tinymist-query/src/completion.rs +description: Completion on o( (50..52) +expression: "JsonRepr::new_pure(results)" +input_file: crates/tinymist-query/src/fixtures/completion/import_star_typing.typ +--- +[ + { + "isIncomplete": false, + "items": [ + { + "command": { + "command": "tinymist.triggerSuggestAndParameterHints", + "title": "" + }, + "kind": 3, + "label": "todo", + "labelDetails": { + "description": "(content) => content" + }, + "sortText": "218", + "textEdit": { + "newText": "todo(${1:})", + "range": { + "end": { + "character": 3, + "line": 2 + }, + "start": { + "character": 1, + "line": 2 + } + } + } + } + ] + }, + { + "isIncomplete": false, + "items": [ + { + "command": { + "command": "tinymist.triggerSuggestAndParameterHints", + "title": "" + }, + "kind": 3, + "label": "todo", + "labelDetails": { + "description": "(content) => content" + }, + "sortText": "218", + "textEdit": { + "newText": "todo(${1:})", + "range": { + "end": { + "character": 3, + "line": 2 + }, + "start": { + "character": 1, + "line": 2 + } + } + } + } + ] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_typing.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_typing.typ.snap new file mode 100644 index 00000000..eaa7f53e --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_typing.typ.snap @@ -0,0 +1,68 @@ +--- +source: crates/tinymist-query/src/completion.rs +description: Completion on o( (46..48) +expression: "JsonRepr::new_pure(results)" +input_file: crates/tinymist-query/src/fixtures/completion/import_typing.typ +--- +[ + { + "isIncomplete": false, + "items": [ + { + "command": { + "command": "tinymist.triggerSuggestAndParameterHints", + "title": "" + }, + "kind": 3, + "label": "todo", + "labelDetails": { + "description": "(content) => content" + }, + "sortText": "217", + "textEdit": { + "newText": "todo(${1:})", + "range": { + "end": { + "character": 3, + "line": 2 + }, + "start": { + "character": 1, + "line": 2 + } + } + } + } + ] + }, + { + "isIncomplete": false, + "items": [ + { + "command": { + "command": "tinymist.triggerSuggestAndParameterHints", + "title": "" + }, + "kind": 3, + "label": "todo", + "labelDetails": { + "description": "(content) => content" + }, + "sortText": "217", + "textEdit": { + "newText": "todo(${1:})", + "range": { + "end": { + "character": 3, + "line": 2 + }, + "start": { + "character": 1, + "line": 2 + } + } + } + } + ] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@item_shadow2.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@item_shadow2.typ.snap index 2b47ebd7..e351aba9 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@item_shadow2.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@item_shadow2.typ.snap @@ -12,7 +12,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/item_shadow2.typ "kind": 3, "label": "base", "labelDetails": { - "description": "() => any" + "description": "() => 1" }, "sortText": "009", "textEdit": { @@ -38,7 +38,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/item_shadow2.typ "kind": 3, "label": "base", "labelDetails": { - "description": "() => any" + "description": "() => 1" }, "sortText": "009", "textEdit": { diff --git a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ-2.snap b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ-2.snap index df42d8fe..8d31fed2 100644 --- a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ-2.snap +++ b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ-2.snap @@ -12,7 +12,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-core-slides.typ "kind": 3, "label": "config-xxx", "labelDetails": { - "description": "() => any" + "description": "() => none" }, "sortText": "033", "textEdit": { diff --git a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-cover-with-rect.typ.snap b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-cover-with-rect.typ.snap index bdf82bbf..0fae4f7f 100644 --- a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-cover-with-rect.typ.snap +++ b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-cover-with-rect.typ.snap @@ -43,7 +43,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-utils-cover-with-rec "labelDetails": { "description": "type" }, - "sortText": "203", + "sortText": "204", "textEdit": { "newText": "stroke(${1:})", "range": { diff --git a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-markup-text.typ.snap b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-markup-text.typ.snap index 16aa5059..5ccec960 100644 --- a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-markup-text.typ.snap +++ b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-markup-text.typ.snap @@ -76,7 +76,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-utils-markup-text.ty "labelDetails": { "description": "type" }, - "sortText": "199", + "sortText": "200", "textEdit": { "newText": "str", "range": { diff --git a/crates/tinymist-query/src/syntax/expr.rs b/crates/tinymist-query/src/syntax/expr.rs index 45714634..a68186aa 100644 --- a/crates/tinymist-query/src/syntax/expr.rs +++ b/crates/tinymist-query/src/syntax/expr.rs @@ -716,7 +716,7 @@ impl ExprWorker<'_> { // todo: dyn resolve src_expr match m.file_id() { Some(fid) => Some(Expr::Decl( - Decl::module(m.name().unwrap().into(), fid).into(), + Decl::module_with_name(m.name().unwrap().into(), fid).into(), )), None => Some(Expr::Type(Ty::Value(InsTy::new(Value::Module(m))))), } @@ -751,7 +751,7 @@ impl ExprWorker<'_> { ) -> Option { let fid = resolve_id_by_path(&self.ctx.world, self.fid, src)?; let name = Decl::calc_path_stem(src); - let module = Expr::Decl(Decl::module(name.clone(), fid).into()); + let module = Expr::Decl(Decl::module_with_name(name.clone(), fid).into()); let import_path = if is_import { Decl::import_path(source.span(), name) diff --git a/tests/e2e/e2e/lsp.rs b/tests/e2e/e2e/lsp.rs index a75e1e3c..954dd4bf 100644 --- a/tests/e2e/e2e/lsp.rs +++ b/tests/e2e/e2e/lsp.rs @@ -33,7 +33,7 @@ fn test_lsp() { }); let hash = replay_log(&root.join("vscode")); - insta::assert_snapshot!(hash, @"siphash128_13:5019cb39687d1a16853bc80320d00e26"); + insta::assert_snapshot!(hash, @"siphash128_13:37838db38884e581e48751286df407d0"); } }