mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-12-23 08:47:50 +00:00
feat(analysis): derive ipcfg resolve map from ExprInfo
This commit is contained in:
parent
4fa2e7862d
commit
6f8a40cc0c
2 changed files with 141 additions and 1 deletions
|
|
@ -1,7 +1,9 @@
|
|||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use typst::syntax::ast::AstNode;
|
||||
use typst::syntax::{Span, SyntaxNode, ast};
|
||||
|
||||
use crate::syntax::{Expr, ExprInfo, RefExpr as AnalysisRefExpr};
|
||||
|
||||
use super::builder::build_cfgs_many;
|
||||
use super::ir::*;
|
||||
|
||||
|
|
@ -31,6 +33,65 @@ pub struct InterproceduralCfg {
|
|||
pub calls: Vec<CallEdge>,
|
||||
}
|
||||
|
||||
/// Builds a [`ResolveMap`] from an [`ExprInfo`] resolve table.
|
||||
///
|
||||
/// The resulting map can be passed to [`build_interprocedural_cfg`] to enable
|
||||
/// call edges for `let`-bound closures and imported symbols without requiring a
|
||||
/// separate resolver pass.
|
||||
///
|
||||
/// This is best-effort: only references that can be traced back to a concrete
|
||||
/// definition span (e.g. `Decl::Func` / `Decl::Var`) are included.
|
||||
pub fn resolve_map_from_expr_info(ei: &ExprInfo) -> ResolveMap {
|
||||
fn resolved_def_span(reference: &AnalysisRefExpr) -> Option<Span> {
|
||||
let mut visited: FxHashSet<crate::ty::Interned<AnalysisRefExpr>> = FxHashSet::default();
|
||||
let mut stack: Vec<Expr> = Vec::new();
|
||||
|
||||
if let Some(step) = reference.step.clone() {
|
||||
stack.push(step);
|
||||
}
|
||||
if let Some(root) = reference.root.clone() {
|
||||
stack.push(root);
|
||||
}
|
||||
|
||||
while let Some(expr) = stack.pop() {
|
||||
match expr {
|
||||
Expr::Decl(decl) => {
|
||||
if decl.is_def() {
|
||||
return Some(decl.span());
|
||||
}
|
||||
}
|
||||
Expr::Ref(r) => {
|
||||
if visited.insert(r.clone()) {
|
||||
if let Some(step) = r.step.clone() {
|
||||
stack.push(step);
|
||||
}
|
||||
if let Some(root) = r.root.clone() {
|
||||
stack.push(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Select(select) => {
|
||||
stack.push(select.lhs.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
let mut out = ResolveMap::default();
|
||||
for (&use_span, reference) in ei.resolves.iter() {
|
||||
if use_span.is_detached() {
|
||||
continue;
|
||||
}
|
||||
if let Some(def_span) = resolved_def_span(reference.as_ref()) {
|
||||
out.insert(use_span, def_span);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Builds per-body CFGs plus best-effort call edges between bodies.
|
||||
///
|
||||
/// `resolves` can optionally map callee identifier spans at call sites to their
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
use super::*;
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use typst::syntax::Source;
|
||||
use typst::syntax::ast::AstNode;
|
||||
use typst::syntax::{FileId, Span, VirtualPath, ast};
|
||||
use typst::utils::LazyHash;
|
||||
|
||||
use crate::docs::DocString;
|
||||
use crate::syntax::{Decl, Expr, ExprInfo, ExprInfoRepr, LexicalScope, RefExpr};
|
||||
|
||||
fn walk_exprs<'a>(node: &'a typst::syntax::SyntaxNode, f: &mut impl FnMut(ast::Expr<'a>)) {
|
||||
for child in node.children() {
|
||||
|
|
@ -239,6 +245,79 @@ fn ipcfg_let_var_bound_closure_call_edge_with_resolve_map() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ipcfg_resolve_map_from_expr_info_enables_let_bound_call_edge() {
|
||||
let source = Source::detached(
|
||||
r#"#{
|
||||
let f(x) = { x }
|
||||
f(1)
|
||||
}"#,
|
||||
);
|
||||
|
||||
let mut def_ident: Option<ast::Ident<'_>> = None;
|
||||
let mut use_ident: Option<ast::Ident<'_>> = None;
|
||||
walk_exprs(source.root(), &mut |expr| match expr {
|
||||
ast::Expr::LetBinding(let_) => {
|
||||
if let ast::LetBindingKind::Closure(ident) = let_.kind()
|
||||
&& ident.get() == "f"
|
||||
{
|
||||
def_ident = Some(ident);
|
||||
}
|
||||
}
|
||||
ast::Expr::FuncCall(call) => {
|
||||
if let ast::Expr::Ident(ident) = call.callee()
|
||||
&& ident.get() == "f"
|
||||
{
|
||||
use_ident = Some(ident);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
|
||||
let def_ident = def_ident.expect("def ident");
|
||||
let use_ident = use_ident.expect("use ident");
|
||||
|
||||
// Create a minimal ExprInfo with only the resolve we need:
|
||||
// use-site ident span -> reference chain that roots at the definition decl.
|
||||
let def_decl: crate::syntax::DeclExpr = Decl::func(def_ident).into();
|
||||
let use_decl: crate::syntax::DeclExpr = Decl::ident_ref(use_ident).into();
|
||||
let reference = RefExpr {
|
||||
decl: use_decl,
|
||||
step: Some(Expr::Decl(def_decl.clone())),
|
||||
root: Some(Expr::Decl(def_decl.clone())),
|
||||
term: None,
|
||||
};
|
||||
|
||||
let mut resolves: FxHashMap<Span, crate::ty::Interned<RefExpr>> = FxHashMap::default();
|
||||
resolves.insert(use_ident.span(), crate::ty::Interned::new(reference));
|
||||
|
||||
let ei = ExprInfo::new(ExprInfoRepr {
|
||||
fid: source.id(),
|
||||
revision: 0,
|
||||
source: source.clone(),
|
||||
root: Expr::Star,
|
||||
module_docstring: Arc::new(DocString::default()),
|
||||
exports: Arc::new(LazyHash::new(LexicalScope::default())),
|
||||
imports: FxHashMap::default(),
|
||||
exprs: FxHashMap::default(),
|
||||
resolves,
|
||||
docstrings: FxHashMap::default(),
|
||||
module_items: FxHashMap::default(),
|
||||
});
|
||||
|
||||
let resolves = resolve_map_from_expr_info(&ei);
|
||||
let ip = build_interprocedural_cfg(source.root(), Some(&resolves));
|
||||
let callee = ip
|
||||
.cfgs
|
||||
.decl_body(def_ident.span())
|
||||
.expect("callee body for declaration");
|
||||
assert!(
|
||||
ip.calls.iter().any(|e| e.callee_body == callee),
|
||||
"expected a call edge into the let-bound closure body, got {:#?}",
|
||||
ip.calls
|
||||
);
|
||||
}
|
||||
|
||||
fn source_at(path: &str, text: &str) -> Source {
|
||||
let id = FileId::new(None, VirtualPath::new(Path::new(path)));
|
||||
Source::new(id, text.to_owned())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue