Skip to content

Commit

Permalink
feat: support query of cross-module references (#42)
Browse files Browse the repository at this point in the history
* feat: support cross-module references by wild-card import

* feat: support cross-module references by ident import

* feat: support cross-module references by alias import

* docs: update readme

* dev: remove two debug printing
  • Loading branch information
Myriad-Dreamin authored Mar 15, 2024
1 parent fe25933 commit 5fa4f8f
Show file tree
Hide file tree
Showing 19 changed files with 363 additions and 139 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ Language service (LSP) features:
- [Goto definitions](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#show-definitions-of-a-symbol)
- Right-click on a symbol and select "Go to Definition".
- Or ctrl+click on a symbol.
<!-- - [Goto declarations](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#find-all-references-to-a-symbol)
- Also known as "find all references". -->
- [References](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#find-all-references-to-a-symbol)
- Right-click on a symbol and select "Go to References" or "Find References".
- Or ctrl+click on a symbol.
- [Hover tips](https://code.visualstudio.com/api/language-extensions/programmatic-language-features#show-hovers)
- Also known as "hovering tooltip".
- [Inlay hints](https://www.jetbrains.com/help/idea/inlay-hints.html)
Expand Down
158 changes: 94 additions & 64 deletions crates/tinymist-query/src/analysis/def_use.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use core::fmt;
use std::{
collections::{HashMap, HashSet},
collections::HashMap,
ops::{Deref, Range},
sync::Arc,
};

use log::info;
use parking_lot::Mutex;
use serde::Serialize;
use typst::syntax::Source;
use typst_ts_core::{path::unix_slash, TypstFileId};
Expand All @@ -15,7 +14,7 @@ use crate::{adt::snapshot_map::SnapshotMap, analysis::find_source_by_import_path

use super::{
get_lexical_hierarchy, AnalysisContext, LexicalHierarchy, LexicalKind, LexicalScopeKind,
LexicalVarKind, ModSrc,
LexicalVarKind, ModSrc, SearchCtx,
};

pub use typst_ts_core::vector::ir::DefId;
Expand Down Expand Up @@ -60,14 +59,15 @@ impl Serialize for IdentRef {

#[derive(Serialize, Clone)]
pub struct IdentDef {
name: String,
kind: LexicalKind,
range: Range<usize>,
pub name: String,
pub kind: LexicalKind,
pub range: Range<usize>,
}

#[derive(Default)]
pub struct DefUseInfo {
ident_defs: indexmap::IndexMap<(TypstFileId, IdentRef), IdentDef>,
external_refs: HashMap<(TypstFileId, Option<String>), Vec<IdentRef>>,
ident_refs: HashMap<IdentRef, DefId>,
undefined_refs: Vec<IdentRef>,
exports_refs: Vec<DefId>,
Expand All @@ -84,28 +84,30 @@ impl DefUseInfo {
.iter()
.filter_map(move |(k, v)| if *v == id { Some(k) } else { None })
}
}

pub fn get_def_use<'a>(
world: &'a mut AnalysisContext<'a>,
source: Source,
) -> Option<Arc<DefUseInfo>> {
let mut ctx = SearchCtx {
ctx: world,
searched: Default::default(),
};
pub fn get_external_refs(
&self,
ext_id: TypstFileId,
ext_name: Option<String>,
) -> impl Iterator<Item = &IdentRef> {
self.external_refs
.get(&(ext_id, ext_name))
.into_iter()
.flatten()
}

get_def_use_inner(&mut ctx, source)
pub fn is_exported(&self, id: DefId) -> bool {
self.exports_refs.contains(&id)
}
}

struct SearchCtx<'w> {
ctx: &'w mut AnalysisContext<'w>,
searched: Mutex<HashSet<TypstFileId>>,
pub fn get_def_use(ctx: &mut AnalysisContext, source: Source) -> Option<Arc<DefUseInfo>> {
get_def_use_inner(&mut ctx.fork_for_search(), source)
}

fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseInfo>> {
let current_id = source.id();
if !ctx.searched.lock().insert(current_id) {
if !ctx.searched.insert(current_id) {
return None;
}

Expand All @@ -125,7 +127,7 @@ fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseIn
label_scope: SnapshotMap::default(),

current_id,
current_path: None,
ext_src: None,
};

collector.scan(&e);
Expand All @@ -138,17 +140,17 @@ fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseIn
res
}

struct DefUseCollector<'a, 'w> {
ctx: &'a mut SearchCtx<'w>,
struct DefUseCollector<'a, 'b, 'w> {
ctx: &'a mut SearchCtx<'b, 'w>,
info: DefUseInfo,
label_scope: SnapshotMap<String, DefId>,
id_scope: SnapshotMap<String, DefId>,

current_id: TypstFileId,
current_path: Option<&'a str>,
ext_src: Option<Source>,
}

impl<'a, 'w> DefUseCollector<'a, 'w> {
impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
fn enter<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
let id_snap = self.id_scope.snapshot();
let res = f(self);
Expand All @@ -167,12 +169,18 @@ impl<'a, 'w> DefUseCollector<'a, 'w> {
LexicalKind::Var(LexicalVarKind::Label) => self.insert(Ns::Label, e),
LexicalKind::Var(LexicalVarKind::LabelRef) => self.insert_ref(Ns::Label, e),
LexicalKind::Var(LexicalVarKind::Function)
| LexicalKind::Var(LexicalVarKind::Variable)
| LexicalKind::Mod(super::LexicalModKind::PathVar)
| LexicalKind::Mod(super::LexicalModKind::ModuleAlias)
| LexicalKind::Mod(super::LexicalModKind::Ident)
| LexicalKind::Mod(super::LexicalModKind::Alias { .. }) => {
self.insert(Ns::Value, e)
| LexicalKind::Var(LexicalVarKind::Variable) => self.insert(Ns::Value, e),
LexicalKind::Mod(super::LexicalModKind::PathVar)
| LexicalKind::Mod(super::LexicalModKind::ModuleAlias) => {
self.insert_module(Ns::Value, e)
}
LexicalKind::Mod(super::LexicalModKind::Ident) => {
self.insert(Ns::Value, e);
self.insert_extern(e.info.name.clone(), e.info.range.clone());
}
LexicalKind::Mod(super::LexicalModKind::Alias { target }) => {
self.insert(Ns::Value, e);
self.insert_extern(target.name.clone(), target.range.clone());
}
LexicalKind::Var(LexicalVarKind::ValRef) => self.insert_ref(Ns::Value, e),
LexicalKind::Block => {
Expand All @@ -184,7 +192,12 @@ impl<'a, 'w> DefUseCollector<'a, 'w> {
match p {
ModSrc::Expr(_) => {}
ModSrc::Path(p) => {
self.current_path = Some(p.deref());
let src = find_source_by_import_path(
self.ctx.ctx.world,
self.current_id,
p.deref(),
);
self.ext_src = src;
}
}

Expand All @@ -193,40 +206,35 @@ impl<'a, 'w> DefUseCollector<'a, 'w> {
self.scan(e.as_slice())?;
}

self.current_path = None;
self.ext_src = None;
}
LexicalKind::Mod(super::LexicalModKind::Star) => {
if let Some(path) = self.current_path {
let external_info =
find_source_by_import_path(self.ctx.ctx.world, self.current_id, path)
.and_then(|source| {
info!("diving source for def use: {:?}", source.id());
Some(source.id()).zip(get_def_use_inner(self.ctx, source))
});

if let Some((_, external_info)) = external_info {
for v in &external_info.exports_refs {
// Use FileId in ident_defs map should lose stacked import
// information, but it is currently
// not a problem.
let ((ext_id, _), ext_sym) =
external_info.ident_defs.get_index(v.0 as usize).unwrap();

let name = ext_sym.name.clone();

let ext_ref = IdentRef {
name: name.clone(),
range: ext_sym.range.clone(),
};

let (id, ..) = self
.info
.ident_defs
.insert_full((*ext_id, ext_ref), ext_sym.clone());

let id = DefId(id as u64);
self.id_scope.insert(name, id);
}
if let Some(source) = &self.ext_src {
info!("diving source for def use: {:?}", source.id());
let (_, external_info) =
Some(source.id()).zip(get_def_use_inner(self.ctx, source.clone()))?;

for v in &external_info.exports_refs {
// Use FileId in ident_defs map should lose stacked import
// information, but it is currently
// not a problem.
let ((ext_id, _), ext_sym) =
external_info.ident_defs.get_index(v.0 as usize).unwrap();

let name = ext_sym.name.clone();

let ext_ref = IdentRef {
name: name.clone(),
range: ext_sym.range.clone(),
};

let (id, ..) = self
.info
.ident_defs
.insert_full((*ext_id, ext_ref), ext_sym.clone());

let id = DefId(id as u64);
self.id_scope.insert(name, id);
}
}
}
Expand All @@ -237,6 +245,28 @@ impl<'a, 'w> DefUseCollector<'a, 'w> {
Some(())
}

fn insert_module(&mut self, label: Ns, e: &LexicalHierarchy) {
self.insert(label, e);
if let Some(src) = &self.ext_src {
self.info.external_refs.insert(
(src.id(), None),
vec![IdentRef {
name: e.info.name.clone(),
range: e.info.range.clone(),
}],
);
}
}

fn insert_extern(&mut self, name: String, range: Range<usize>) {
if let Some(src) = &self.ext_src {
self.info.external_refs.insert(
(src.id(), Some(name.clone())),
vec![IdentRef { name, range }],
);
}
}

fn insert(&mut self, label: Ns, e: &LexicalHierarchy) {
let snap = match label {
Ns::Label => &mut self.label_scope,
Expand Down
69 changes: 54 additions & 15 deletions crates/tinymist-query/src/analysis/global.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::{collections::HashMap, path::Path, sync::Arc};
use std::{
collections::{HashMap, HashSet},
path::Path,
sync::Arc,
};

use once_cell::sync::OnceCell;
use typst::{
Expand All @@ -9,7 +13,7 @@ use typst::{
use typst_ts_compiler::{service::WorkspaceProvider, TypstSystemWorld};
use typst_ts_core::{cow_mut::CowMut, ImmutPath, TypstFileId};

use super::DefUseInfo;
use super::{construct_module_dependencies, DefUseInfo, ModuleDependency};

pub struct ModuleAnalysisCache {
source: OnceCell<FileResult<Source>>,
Expand Down Expand Up @@ -42,27 +46,17 @@ pub struct Analysis {
pub struct AnalysisCaches {
modules: HashMap<TypstFileId, ModuleAnalysisCache>,
root_files: OnceCell<Vec<TypstFileId>>,
module_deps: OnceCell<HashMap<TypstFileId, ModuleDependency>>,
}

// fn search_in_workspace(
// world: &TypstSystemWorld,
// def_id: TypstFileId,
// ident: &str,
// new_name: &str,
// editions: &mut HashMap<Url, Vec<TextEdit>>,
// wq: &mut WorkQueue,
// position_encoding: PositionEncoding,
// ) -> Option<()> {
// }

pub struct AnalysisContext<'a> {
pub world: &'a TypstSystemWorld,
pub analysis: CowMut<'a, Analysis>,
caches: AnalysisCaches,
}

impl<'a> AnalysisContext<'a> {
pub fn new(world: &'a TypstSystemWorld) -> Self {
impl<'w> AnalysisContext<'w> {
pub fn new(world: &'w TypstSystemWorld) -> Self {
Self {
world,
analysis: CowMut::Owned(Analysis {
Expand All @@ -71,6 +65,7 @@ impl<'a> AnalysisContext<'a> {
caches: AnalysisCaches {
modules: HashMap::new(),
root_files: OnceCell::new(),
module_deps: OnceCell::new(),
},
}
}
Expand All @@ -84,6 +79,25 @@ impl<'a> AnalysisContext<'a> {
self.caches.root_files.get_or_init(|| self.search_files())
}

pub fn module_dependencies(&mut self) -> &HashMap<TypstFileId, ModuleDependency> {
if self.caches.module_deps.get().is_some() {
return self.caches.module_deps.get().unwrap();
} else {
// may cause multiple times to calculate, but it is okay because we have mutable
// reference to self.
let deps = construct_module_dependencies(self);
self.caches.module_deps.get_or_init(|| deps)
}
}

pub fn fork_for_search<'s>(&'s mut self) -> SearchCtx<'s, 'w> {
SearchCtx {
ctx: self,
searched: Default::default(),
worklist: Default::default(),
}
}

pub fn get_mut(&mut self, file_id: TypstFileId) -> &ModuleAnalysisCache {
self.caches.modules.entry(file_id).or_insert_with(|| {
let source = OnceCell::new();
Expand Down Expand Up @@ -148,3 +162,28 @@ impl<'a> AnalysisContext<'a> {
res
}
}

pub struct SearchCtx<'b, 'w> {
pub ctx: &'b mut AnalysisContext<'w>,
pub searched: HashSet<TypstFileId>,
pub worklist: Vec<TypstFileId>,
}

impl SearchCtx<'_, '_> {
pub fn push(&mut self, id: TypstFileId) -> bool {
if self.searched.insert(id) {
self.worklist.push(id);
true
} else {
false
}
}

pub fn push_dependents(&mut self, id: TypstFileId) {
let deps = self.ctx.module_dependencies().get(&id);
let dependents = deps.map(|e| e.dependents.clone()).into_iter().flatten();
for dep in dependents {
self.push(dep);
}
}
}
Loading

0 comments on commit 5fa4f8f

Please sign in to comment.