This commit is contained in:
Hong Jiarong 2025-12-20 16:06:59 +08:00
parent e4a9096075
commit 06da3f8f25
3 changed files with 94 additions and 8 deletions

View file

@ -6,6 +6,18 @@ use typst::syntax::{Span, ast};
use crate::DiagnosticVec;
#[derive(Debug)]
struct DiscardByReturnAnalysis {
cfg: Cfg,
reachable: Vec<bool>,
states: Vec<MustReturnState>,
}
#[derive(Default)]
pub(crate) struct DiscardByReturnCache {
by_span: std::collections::HashMap<u64, std::sync::Arc<DiscardByReturnAnalysis>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MustReturnKind {
No,
@ -466,15 +478,27 @@ fn warn_meta(expr: ast::Expr<'_>) -> Option<WarnMeta> {
})
}
pub(crate) fn lint_discarded_by_function_return(diag: &mut DiagnosticVec, body: ast::Expr<'_>) {
fn analyze_discarded_by_function_return(body: ast::Expr<'_>) -> Option<DiscardByReturnAnalysis> {
let cfg = Builder::new(body.span()).build_body(body);
if !cfg.has_return {
return;
return None;
}
let reachable = cfg.reachable_from_entry();
let states = cfg.compute_states();
Some(DiscardByReturnAnalysis {
cfg,
reachable,
states,
})
}
fn emit_discarded_by_function_return(diag: &mut DiagnosticVec, analysis: &DiscardByReturnAnalysis) {
let cfg = &analysis.cfg;
let reachable = &analysis.reachable;
let states = &analysis.states;
for (id, node) in cfg.nodes.iter().enumerate() {
if !reachable[id] {
continue;
@ -520,3 +544,22 @@ pub(crate) fn lint_discarded_by_function_return(diag: &mut DiagnosticVec, body:
diag.push(diag_);
}
}
pub(crate) fn lint_discarded_by_function_return_cached(
cache: &mut DiscardByReturnCache,
diag: &mut DiagnosticVec,
body: ast::Expr<'_>,
) {
let key = body.span().into_raw().get();
if let Some(analysis) = cache.by_span.get(&key) {
emit_discarded_by_function_return(diag, analysis);
return;
}
let Some(analysis) = analyze_discarded_by_function_return(body) else {
return;
};
let analysis = std::sync::Arc::new(analysis);
emit_discarded_by_function_return(diag, &analysis);
cache.by_span.insert(key, analysis);
}

View file

@ -22,6 +22,13 @@ mod cfg;
/// A type alias for a vector of diagnostics.
type DiagnosticVec = EcoVec<SourceDiagnostic>;
#[derive(Default)]
/// Cache container for lint analyses that are expensive to recompute (e.g. CFG +
/// dataflow for particular bodies).
pub struct LintCaches {
discard_by_return: cfg::DiscardByReturnCache,
}
/// The lint information about a file.
#[derive(Debug, Clone)]
pub struct LintInfo {
@ -39,8 +46,10 @@ pub fn lint_file(
ei: &ExprInfo,
ti: Arc<TypeInfo>,
known_issues: KnownIssues,
caches: &mut LintCaches,
) -> LintInfo {
let diagnostics = Linter::new(world, ei.clone(), ti, known_issues).lint(ei.source.root());
let diagnostics =
Linter::new(world, ei.clone(), ti, known_issues, caches).lint(ei.source.root());
LintInfo {
revision: ei.revision,
fid: ei.fid,
@ -77,7 +86,7 @@ impl KnownIssues {
}
}
struct Linter<'w> {
struct Linter<'w, 'c> {
world: &'w LspWorld,
ei: ExprInfo,
ti: Arc<TypeInfo>,
@ -85,14 +94,16 @@ struct Linter<'w> {
diag: DiagnosticVec,
loop_info: Option<LoopInfo>,
func_info: Option<FuncInfo>,
caches: &'c mut LintCaches,
}
impl<'w> Linter<'w> {
impl<'w, 'c> Linter<'w, 'c> {
fn new(
world: &'w LspWorld,
ei: ExprInfo,
ti: Arc<TypeInfo>,
known_issues: KnownIssues,
caches: &'c mut LintCaches,
) -> Self {
Self {
world,
@ -102,6 +113,7 @@ impl<'w> Linter<'w> {
diag: EcoVec::new(),
loop_info: None,
func_info: None,
caches,
}
}
@ -428,7 +440,11 @@ impl<'w> Linter<'w> {
let body = expr.body();
this.expr(body);
cfg::lint_discarded_by_function_return(&mut this.diag, body);
cfg::lint_discarded_by_function_return_cached(
&mut this.caches.discard_by_return,
&mut this.diag,
body,
);
Some(())
})
@ -441,7 +457,11 @@ impl<'w> Linter<'w> {
let body = expr.body();
this.expr(body);
cfg::lint_discarded_by_function_return(&mut this.diag, body);
cfg::lint_discarded_by_function_return_cached(
&mut this.caches.discard_by_return,
&mut this.diag,
body,
);
Some(())
})

View file

@ -836,9 +836,25 @@ impl SharedContext {
let ei = self.expr_stage(source);
let ti = self.type_check(source);
let guard = self.query_stat(source.id(), "lint");
let source_hash = hash128(source);
let lint_caches = self.slot.lint_caches.compute(source.id(), |prev| {
if let Some(prev) = prev {
if prev.lock().source_hash == source_hash {
return prev;
}
}
Arc::new(Mutex::new(LintCachesSlot {
source_hash,
caches: tinymist_lint::LintCaches::default(),
}))
});
self.slot.lint.compute(hash128(&(&ei, &ti, issues)), |_| {
guard.miss();
tinymist_lint::lint_file(self.world(), &ei, ti, issues.clone())
let mut caches = lint_caches.lock();
tinymist_lint::lint_file(self.world(), &ei, ti, issues.clone(), &mut caches.caches)
})
}
@ -1325,6 +1341,11 @@ pub struct ModuleAnalysisLocalCache {
type_check: OnceLock<Arc<TypeInfo>>,
}
struct LintCachesSlot {
source_hash: u128,
caches: tinymist_lint::LintCaches,
}
/// A revision-managed (per input change) cache for all level of analysis
/// results of a module.
#[derive(Default)]
@ -1391,6 +1412,7 @@ impl AnalysisRevCache {
expr_stage: slot.data.expr_stage.crawl(revision.get()),
type_check: slot.data.type_check.crawl(revision.get()),
lint: slot.data.lint.crawl(revision.get()),
lint_caches: slot.data.lint_caches.crawl(revision.get()),
})
.unwrap_or_else(|| self.default_slot.clone())
})
@ -1424,6 +1446,7 @@ struct AnalysisRevSlot {
expr_stage: IncrCacheMap<u128, ExprInfo>,
type_check: IncrCacheMap<u128, Arc<TypeInfo>>,
lint: IncrCacheMap<u128, LintInfo>,
lint_caches: IncrCacheMap<TypstFileId, Arc<Mutex<LintCachesSlot>>>,
}
impl Drop for AnalysisRevSlot {