diff --git a/crates/tinymist-lint/src/cfg.rs b/crates/tinymist-lint/src/cfg.rs index a7ce83e27..2c902962e 100644 --- a/crates/tinymist-lint/src/cfg.rs +++ b/crates/tinymist-lint/src/cfg.rs @@ -6,6 +6,18 @@ use typst::syntax::{Span, ast}; use crate::DiagnosticVec; +#[derive(Debug)] +struct DiscardByReturnAnalysis { + cfg: Cfg, + reachable: Vec, + states: Vec, +} + +#[derive(Default)] +pub(crate) struct DiscardByReturnCache { + by_span: std::collections::HashMap>, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum MustReturnKind { No, @@ -466,15 +478,27 @@ fn warn_meta(expr: ast::Expr<'_>) -> Option { }) } -pub(crate) fn lint_discarded_by_function_return(diag: &mut DiagnosticVec, body: ast::Expr<'_>) { +fn analyze_discarded_by_function_return(body: ast::Expr<'_>) -> Option { 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); +} diff --git a/crates/tinymist-lint/src/lib.rs b/crates/tinymist-lint/src/lib.rs index 314647a58..c69ad634a 100644 --- a/crates/tinymist-lint/src/lib.rs +++ b/crates/tinymist-lint/src/lib.rs @@ -22,6 +22,13 @@ mod cfg; /// A type alias for a vector of diagnostics. type DiagnosticVec = EcoVec; +#[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, 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, @@ -85,14 +94,16 @@ struct Linter<'w> { diag: DiagnosticVec, loop_info: Option, func_info: Option, + caches: &'c mut LintCaches, } -impl<'w> Linter<'w> { +impl<'w, 'c> Linter<'w, 'c> { fn new( world: &'w LspWorld, ei: ExprInfo, ti: Arc, 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(()) }) diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index 11b9822c8..b749dcee1 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -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>, } +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, type_check: IncrCacheMap>, lint: IncrCacheMap, + lint_caches: IncrCacheMap>>, } impl Drop for AnalysisRevSlot {