feat: feed more information to linter (#1642)

* feat: move expr struct to common crate

* feat: feed more information to linter
This commit is contained in:
Myriad-Dreamin 2025-04-09 16:57:33 +08:00 committed by GitHub
parent fcb060280d
commit 6cf7739fb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 205 additions and 202 deletions

View file

@ -669,8 +669,9 @@ mod lint_tests {
fn test() {
snapshot_testing("lint", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let expr = ctx.expr_stage(&source);
let result = tinymist_lint::lint_source(&source);
let result = tinymist_lint::lint_source(&expr);
let result = crate::diagnostics::DiagWorker::new(ctx).convert_all(result.iter());
let result = result
.into_iter()

View file

@ -1,27 +1,25 @@
use tinymist_analysis::docs::DocString;
use tinymist_std::TakeAs;
use super::*;
use crate::syntax::DocString;
impl TypeChecker<'_> {
pub fn check_docstring(&mut self, base_id: &Interned<Decl>) -> Option<Arc<DocString>> {
let docstring = self.ei.docstrings.get(base_id)?.clone();
Some(Arc::new(
docstring.take().rename_based_on(base_id.clone(), self),
self.rename_based_on(docstring.take(), base_id.clone()),
))
}
}
impl DocString {
fn rename_based_on(self, documenting_id: Interned<Decl>, base: &mut TypeChecker) -> DocString {
fn rename_based_on(&mut self, docs: DocString, documenting_id: Interned<Decl>) -> DocString {
let DocString {
docs,
var_bounds,
vars,
mut res_ty,
} = self;
} = docs;
let mut renamer = IdRenamer {
base,
base: self,
var_bounds: &var_bounds,
base_id: documenting_id,
};

View file

@ -4,8 +4,8 @@ use typst::foundations::{Element, Type};
use super::*;
use crate::analysis::ParamAttrs;
use crate::docs::{SignatureDocsT, TypelessParamDocs, UntypedDefDocs};
use crate::syntax::{def::*, DocString, VarDoc};
use crate::docs::{DocString, SignatureDocsT, TypelessParamDocs, UntypedDefDocs, VarDoc};
use crate::syntax::def::*;
use crate::ty::*;
static EMPTY_DOCSTRING: LazyLock<DocString> = LazyLock::new(DocString::default);

View file

@ -4,7 +4,7 @@ use tinymist_project::LspWorld;
use tinymist_world::vfs::WorkspaceResolver;
use typst::{diag::SourceDiagnostic, syntax::Span};
use crate::{analysis::Analysis, prelude::*, LspWorldExt};
use crate::{analysis::Analysis, prelude::*, syntax::ExprInfo, LspWorldExt};
use regex::RegexSet;
@ -56,7 +56,8 @@ impl<'w> DiagWorker<'w> {
let Ok(source) = self.ctx.world.source(dep) else {
continue;
};
let res = lint_source(&source);
let expr = self.ctx.expr_stage(&source);
let res = lint_file(&expr);
if !res.is_empty() {
for diag in res {
self.handle(&diag);
@ -245,6 +246,6 @@ impl DiagnosticRefiner for OutOfRootHintRefiner {
}
#[comemo::memoize]
fn lint_source(source: &Source) -> EcoVec<SourceDiagnostic> {
fn lint_file(source: &ExprInfo) -> EcoVec<SourceDiagnostic> {
tinymist_lint::lint_source(source)
}

View file

@ -1,8 +1,4 @@
use std::{
collections::BTreeMap,
ops::Deref,
sync::{LazyLock, OnceLock},
};
use std::{collections::BTreeMap, ops::Deref, sync::LazyLock};
use ecow::eco_format;
use typst::foundations::{IntoValue, Module, Str, Type};
@ -10,7 +6,7 @@ use typst::foundations::{IntoValue, Module, Str, Type};
use crate::{adt::interner::Interned, StrRef};
use crate::{adt::snapshot_map::SnapshotMap, analysis::SharedContext};
use crate::{
docs::{convert_docs, identify_pat_docs, identify_tidy_module_docs, UntypedDefDocs, VarDocsT},
docs::{convert_docs, identify_pat_docs, identify_tidy_module_docs, DocString, VarDoc},
prelude::*,
syntax::{Decl, DefKind},
ty::{BuiltinTy, DynTypeBounds, InsTy, PackageId, SigTy, Ty, TypeVar, TypeVarBounds},
@ -18,59 +14,6 @@ use crate::{
use super::DeclExpr;
/// The documentation string of an item
#[derive(Debug, Clone, Default)]
pub struct DocString {
/// The documentation of the item
pub docs: Option<EcoString>,
/// The typing on definitions
pub var_bounds: HashMap<DeclExpr, TypeVarBounds>,
/// The variable doc associated with the item
pub vars: BTreeMap<StrRef, VarDoc>,
/// The type of the resultant type
pub res_ty: Option<Ty>,
}
impl DocString {
/// Gets the docstring as a variable doc
pub fn as_var(&self) -> VarDoc {
VarDoc {
docs: self.docs.clone().unwrap_or_default(),
ty: self.res_ty.clone(),
}
}
/// Get the documentation of a variable associated with the item
pub fn get_var(&self, name: &StrRef) -> Option<&VarDoc> {
self.vars.get(name)
}
/// Get the type of a variable associated with the item
pub fn var_ty(&self, name: &StrRef) -> Option<&Ty> {
self.get_var(name).and_then(|v| v.ty.as_ref())
}
}
/// The documentation string of a variable associated with some item.
#[derive(Debug, Clone, Default)]
pub struct VarDoc {
/// The documentation of the variable
pub docs: EcoString,
/// The type of the variable
pub ty: Option<Ty>,
}
impl VarDoc {
/// Convert the variable doc to an untyped version
pub fn to_untyped(&self) -> Arc<UntypedDefDocs> {
Arc::new(UntypedDefDocs::Variable(VarDocsT {
docs: self.docs.clone(),
return_ty: (),
def_docs: OnceLock::new(),
}))
}
}
pub(crate) fn compute_docstring(
ctx: &Arc<SharedContext>,
fid: TypstFileId,

View file

@ -18,12 +18,13 @@ use typst::{
use crate::{
analysis::{QueryStatGuard, SharedContext},
docs::DocString,
prelude::*,
syntax::{find_module_level_docs, resolve_id_by_path, DefKind},
ty::{BuiltinTy, InsTy, Ty},
};
use super::{compute_docstring, def::*, DocCommentMatcher, DocString, InterpretMode};
use super::{compute_docstring, def::*, DocCommentMatcher, InterpretMode};
pub type ExprRoute = FxHashMap<TypstFileId, Option<Arc<LazyHash<LexicalScope>>>>;
@ -76,18 +77,8 @@ pub(crate) fn expr_of(
let docstrings_base = Arc::new(Mutex::new(FxHashMap::default()));
let docstrings = docstrings_base.clone();
let exprs_base: Arc<
parking_lot::lock_api::Mutex<
parking_lot::RawMutex,
HashMap<Span, Expr, rustc_hash::FxBuildHasher>,
>,
> = Arc::new(Mutex::new(FxHashMap::default()));
let exprs: Arc<
parking_lot::lock_api::Mutex<
parking_lot::RawMutex,
HashMap<Span, Expr, rustc_hash::FxBuildHasher>,
>,
> = exprs_base.clone();
let exprs_base = Arc::new(Mutex::new(FxHashMap::default()));
let exprs = exprs_base.clone();
let imports_base = Arc::new(Mutex::new(FxHashMap::default()));
let imports = imports_base.clone();
@ -145,109 +136,7 @@ pub(crate) fn expr_of(
crate::log_debug_ct!("expr_of end {:?}", source.id());
route.remove(&info.fid);
ExprInfo(Arc::new(LazyHash::new(info)))
}
#[derive(Debug, Clone, Hash)]
pub struct ExprInfo(Arc<LazyHash<ExprInfoRepr>>);
impl Deref for ExprInfo {
type Target = Arc<LazyHash<ExprInfoRepr>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct ExprInfoRepr {
pub fid: TypstFileId,
pub revision: usize,
pub source: Source,
pub resolves: FxHashMap<Span, Interned<RefExpr>>,
pub module_docstring: Arc<DocString>,
pub docstrings: FxHashMap<DeclExpr, Arc<DocString>>,
pub exprs: FxHashMap<Span, Expr>,
pub imports: FxHashMap<TypstFileId, Arc<LazyHash<LexicalScope>>>,
pub exports: Arc<LazyHash<LexicalScope>>,
pub root: Expr,
}
impl std::hash::Hash for ExprInfoRepr {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.revision.hash(state);
self.source.hash(state);
self.exports.hash(state);
self.root.hash(state);
let mut resolves = self.resolves.iter().collect::<Vec<_>>();
resolves.sort_by_key(|(fid, _)| fid.into_raw());
resolves.hash(state);
let mut imports = self.imports.iter().collect::<Vec<_>>();
imports.sort_by_key(|(fid, _)| *fid);
imports.hash(state);
}
}
impl ExprInfoRepr {
pub fn get_def(&self, decl: &Interned<Decl>) -> Option<Expr> {
if decl.is_def() {
return Some(Expr::Decl(decl.clone()));
}
let resolved = self.resolves.get(&decl.span())?;
Some(Expr::Ref(resolved.clone()))
}
pub fn get_refs(
&self,
decl: Interned<Decl>,
) -> impl Iterator<Item = (&Span, &Interned<RefExpr>)> {
let of = Some(Expr::Decl(decl.clone()));
self.resolves
.iter()
.filter(move |(_, r)| match (decl.as_ref(), r.decl.as_ref()) {
(Decl::Label(..), Decl::Label(..)) => r.decl == decl,
(Decl::Label(..), Decl::ContentRef(..)) => r.decl.name() == decl.name(),
(Decl::Label(..), _) => false,
_ => r.decl == decl || r.root == of,
})
}
pub fn is_exported(&self, decl: &Interned<Decl>) -> bool {
let of = Expr::Decl(decl.clone());
self.exports
.get(decl.name())
.is_some_and(|export| match export {
Expr::Ref(ref_expr) => ref_expr.root == Some(of),
exprt => *exprt == of,
})
}
#[allow(dead_code)]
fn show(&self) {
use std::io::Write;
let vpath = self
.fid
.vpath()
.resolve(Path::new("target/exprs/"))
.unwrap();
let root = vpath.with_extension("root.expr");
std::fs::create_dir_all(root.parent().unwrap()).unwrap();
std::fs::write(root, format!("{}", self.root)).unwrap();
let scopes = vpath.with_extension("scopes.expr");
std::fs::create_dir_all(scopes.parent().unwrap()).unwrap();
{
let mut scopes = std::fs::File::create(scopes).unwrap();
for (span, expr) in self.exprs.iter() {
writeln!(scopes, "{span:?} -> {expr}").unwrap();
}
}
let imports = vpath.with_extension("imports.expr");
std::fs::create_dir_all(imports.parent().unwrap()).unwrap();
std::fs::write(imports, format!("{:#?}", self.imports)).unwrap();
let exports = vpath.with_extension("exports.expr");
std::fs::create_dir_all(exports.parent().unwrap()).unwrap();
std::fs::write(exports, format!("{:#?}", self.exports)).unwrap();
}
ExprInfo::new(info)
}
type ConcolicExpr = (Option<Expr>, Option<Ty>);

View file

@ -4,14 +4,15 @@
#![allow(missing_docs)]
pub(crate) mod lexical_hierarchy;
pub use lexical_hierarchy::*;
pub(crate) mod module;
pub use module::*;
pub(crate) mod expr;
pub use expr::*;
pub(crate) mod docs;
pub use docs::*;
pub(crate) mod expr;
pub(crate) mod index;
pub(crate) mod lexical_hierarchy;
pub(crate) mod module;
pub(crate) use docs::*;
pub use expr::*;
pub use index::*;
pub use lexical_hierarchy::*;
pub use module::*;
pub use tinymist_analysis::syntax::*;