feat: read and check type annotations in docstring (Part. 1) (#679)

* feat: read and check type annotations in docstring

* fix: remove test.snap in playground

* chore: refactor and remove some dirty changes
This commit is contained in:
Myriad-Dreamin 2024-10-15 14:52:50 +08:00 committed by GitHub
parent c8e11e92f1
commit 3ed401740e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 407 additions and 99 deletions

View file

@ -1,13 +1,16 @@
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::{collections::HashSet, ops::Deref}; use std::{collections::HashSet, ops::Deref};
use comemo::Tracked; use comemo::{Track, Tracked};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use reflexo::hash::{hash128, FxDashMap}; use reflexo::hash::{hash128, FxDashMap};
use reflexo::{debug_loc::DataSource, ImmutPath}; use reflexo::{debug_loc::DataSource, ImmutPath};
use typst::diag::{eco_format, FileError, FileResult, PackageError}; use tinymist_world::LspWorld;
use typst::eval::Eval; use tinymist_world::DETACHED_ENTRY;
use typst::foundations::{self, Bytes, Func, Styles}; use typst::diag::{eco_format, At, FileError, FileResult, PackageError, SourceResult};
use typst::engine::Route;
use typst::eval::{Eval, Tracer};
use typst::foundations::{self, Bytes, Func, Module, Styles};
use typst::layout::Position; use typst::layout::Position;
use typst::syntax::{package::PackageSpec, Span, VirtualPath}; use typst::syntax::{package::PackageSpec, Span, VirtualPath};
use typst::{model::Document, text::Font}; use typst::{model::Document, text::Font};
@ -148,7 +151,7 @@ impl ModuleAnalysisCache {
/// The resources for analysis. /// The resources for analysis.
pub trait AnalysisResources { pub trait AnalysisResources {
/// Get the world surface for Typst compiler. /// Get the world surface for Typst compiler.
fn world(&self) -> &dyn World; fn world(&self) -> &LspWorld;
/// Resolve the real path for a package spec. /// Resolve the real path for a package spec.
fn resolve(&self, spec: &PackageSpec) -> Result<Arc<Path>, PackageError>; fn resolve(&self, spec: &PackageSpec) -> Result<Arc<Path>, PackageError>;
@ -226,7 +229,7 @@ impl<'w> AnalysisContext<'w> {
} }
/// Get the world surface for Typst compiler. /// Get the world surface for Typst compiler.
pub fn world(&self) -> &'w dyn World { pub fn world(&self) -> &'w LspWorld {
self.resources.world() self.resources.world()
} }
@ -281,6 +284,19 @@ impl<'w> AnalysisContext<'w> {
} }
} }
/// Get file's id by its path
pub fn file_id_by_path(&self, p: &Path) -> FileResult<TypstFileId> {
// todo: source in packages
let relative_path = p.strip_prefix(&self.root).map_err(|_| {
FileError::Other(Some(eco_format!(
"not in root, path is {p:?}, root is {:?}",
self.root
)))
})?;
Ok(TypstFileId::new(None, VirtualPath::new(relative_path)))
}
/// Resolve the real path for a file id. /// Resolve the real path for a file id.
pub fn path_for_id(&self, id: TypstFileId) -> Result<PathBuf, FileError> { pub fn path_for_id(&self, id: TypstFileId) -> Result<PathBuf, FileError> {
if id.vpath().as_rootless_path() == Path::new("-") { if id.vpath().as_rootless_path() == Path::new("-") {
@ -319,19 +335,6 @@ impl<'w> AnalysisContext<'w> {
self.get(id).unwrap().source(self, id) self.get(id).unwrap().source(self, id)
} }
/// Get the fileId from its path
pub fn file_id_by_path(&self, p: &Path) -> FileResult<TypstFileId> {
// todo: source in packages
let relative_path = p.strip_prefix(&self.root).map_err(|_| {
FileError::Other(Some(eco_format!(
"not in root, path is {p:?}, root is {:?}",
self.root
)))
})?;
Ok(TypstFileId::new(None, VirtualPath::new(relative_path)))
}
/// Get the source of a file by file path. /// Get the source of a file by file path.
pub fn source_by_path(&mut self, p: &Path) -> FileResult<Source> { pub fn source_by_path(&mut self, p: &Path) -> FileResult<Source> {
// todo: source in packages // todo: source in packages
@ -339,6 +342,31 @@ impl<'w> AnalysisContext<'w> {
self.source_by_id(id) self.source_by_id(id)
} }
/// Get a module by file id.
pub fn module_by_id(&mut self, fid: TypstFileId) -> SourceResult<Module> {
let source = self.source_by_id(fid).at(Span::detached())?;
self.module_by_src(source)
}
/// Get a module by string.
pub fn module_by_str(&mut self, rr: String) -> Option<Module> {
let src = Source::new(*DETACHED_ENTRY, rr);
self.module_by_src(src).ok()
}
/// Get (Create) a module by source.
pub fn module_by_src(&mut self, source: Source) -> SourceResult<Module> {
let route = Route::default();
let mut tracer = Tracer::default();
typst::eval::eval(
(self.world() as &dyn World).track(),
route.track(),
tracer.track_mut(),
&source,
)
}
/// Get a syntax object at a position. /// Get a syntax object at a position.
pub fn deref_syntax_at<'s>( pub fn deref_syntax_at<'s>(
&mut self, &mut self,
@ -517,7 +545,7 @@ impl<'w> AnalysisContext<'w> {
pub fn import_info(&mut self, source: Source) -> Option<Arc<ImportInfo>> { pub fn import_info(&mut self, source: Source) -> Option<Arc<ImportInfo>> {
use comemo::Track; use comemo::Track;
let w = self.resources.world(); let w = self.resources.world();
let w = w.track(); let w = (w as &dyn World).track();
let token = &self.analysis.workers.import; let token = &self.analysis.workers.import;
token.enter(|| import_info(w, source)) token.enter(|| import_info(w, source))
@ -582,13 +610,13 @@ impl<'w> AnalysisContext<'w> {
) -> Option<Arc<BibInfo>> { ) -> Option<Arc<BibInfo>> {
use comemo::Track; use comemo::Track;
let w = self.resources.world(); let w = self.resources.world();
let w = w.track(); let w = (w as &dyn World).track();
bib_info(w, span, bib_paths.collect()) bib_info(w, span, bib_paths.collect())
} }
pub(crate) fn with_vm<T>(&self, f: impl FnOnce(&mut typst::eval::Vm) -> T) -> T { pub(crate) fn with_vm<T>(&self, f: impl FnOnce(&mut typst::eval::Vm) -> T) -> T {
crate::upstream::with_vm(self.world(), f) crate::upstream::with_vm((self.world() as &dyn World).track(), f)
} }
pub(crate) fn const_eval(&self, rr: ast::Expr<'_>) -> Option<Value> { pub(crate) fn const_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {

View file

@ -2,6 +2,7 @@
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use ecow::EcoString;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use reflexo::vector::ir::DefId; use reflexo::vector::ir::DefId;
use typst::{ use typst::{
@ -43,6 +44,9 @@ pub(crate) fn type_check(ctx: &mut AnalysisContext, source: Source) -> Option<Ar
def_use_info, def_use_info,
info: &mut info, info: &mut info,
externals: HashMap::new(), externals: HashMap::new(),
docs_scope: HashMap::new(),
documenting_id: None,
generated: HashMap::new(),
mode: InterpretMode::Markup, mode: InterpretMode::Markup,
}; };
let lnk = LinkedNode::new(source.root()); let lnk = LinkedNode::new(source.root());
@ -70,6 +74,9 @@ struct TypeChecker<'a, 'w> {
def_use_info: Arc<DefUseInfo>, def_use_info: Arc<DefUseInfo>,
info: &'a mut TypeScheme, info: &'a mut TypeScheme,
docs_scope: HashMap<EcoString, Option<Ty>>,
documenting_id: Option<DefId>,
generated: HashMap<DefId, u32>,
externals: HashMap<DefId, Option<Ty>>, externals: HashMap<DefId, Option<Ty>>,
mode: InterpretMode, mode: InterpretMode,
} }
@ -106,11 +113,25 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
w w
} }
fn get_def_id(&mut self, s: Span, r: &IdentRef) -> Option<DefId> {
self.def_use_info
.get_ref(r)
.or_else(|| Some(self.def_use_info.get_def(s.id()?, r)?.0))
}
fn generate_var(&mut self, name: StrRef, id: DefId) -> Ty {
let next_id = self.generated.entry(id).or_insert(0);
*next_id += 1;
let encoded = DefId((id.0 + 1) * 0x100_0000_0000 + (*next_id as u64));
log::debug!("generate var {name:?} {encoded:?}");
let bounds = TypeVarBounds::new(TypeVar { name, def: encoded }, TypeBounds::default());
let var = bounds.as_type();
self.info.vars.insert(encoded, bounds);
var
}
fn get_var(&mut self, s: Span, r: IdentRef) -> Option<Ty> { fn get_var(&mut self, s: Span, r: IdentRef) -> Option<Ty> {
let def_id = self let def_id = self.get_def_id(s, &r)?;
.def_use_info
.get_ref(&r)
.or_else(|| Some(self.def_use_info.get_def(s.id()?, &r)?.0))?;
// todo: false positive of clippy // todo: false positive of clippy
#[allow(clippy::map_entry)] #[allow(clippy::map_entry)]
@ -134,6 +155,13 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
Some(var.as_type()) Some(var.as_type())
} }
fn with_docs_scope<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
let res = f(self);
self.docs_scope.clear();
self.documenting_id = None;
res
}
fn import_ty(&mut self, def_id: DefId) -> Option<Ty> { fn import_ty(&mut self, def_id: DefId) -> Option<Ty> {
if let Some(ty) = self.externals.get(&def_id) { if let Some(ty) = self.externals.get(&def_id) {
return ty.clone(); return ty.clone();

View file

@ -1,9 +1,9 @@
//! Type checking on source file //! Type checking on source file
use std::collections::BTreeMap; use std::{collections::BTreeMap, sync::LazyLock};
use typst::{ use typst::{
foundations::Value, foundations::{IntoValue, Module, Str, Type, Value},
syntax::{ syntax::{
ast::{self, AstNode}, ast::{self, AstNode},
LinkedNode, SyntaxKind, LinkedNode, SyntaxKind,
@ -11,7 +11,21 @@ use typst::{
}; };
use super::*; use super::*;
use crate::{adt::interner::Interned, ty::*}; use crate::{
adt::interner::Interned,
docs::{convert_docs, identify_func_docs},
syntax::{find_docs_of, get_non_strict_def_target},
ty::*,
};
#[derive(Debug)]
struct ParamDoc {
// docs: String,
ty: Option<Ty>,
// default: Option<String>,
}
type ParamDocs = HashMap<String, ParamDoc>;
impl<'a, 'w> TypeChecker<'a, 'w> { impl<'a, 'w> TypeChecker<'a, 'w> {
pub(crate) fn check_syntax(&mut self, root: LinkedNode) -> Option<Ty> { pub(crate) fn check_syntax(&mut self, root: LinkedNode) -> Option<Ty> {
@ -371,7 +385,48 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
fn check_closure(&mut self, root: LinkedNode<'_>) -> Option<Ty> { fn check_closure(&mut self, root: LinkedNode<'_>) -> Option<Ty> {
let closure: ast::Closure = root.cast()?; let closure: ast::Closure = root.cast()?;
// let _params = self.check_expr_in(closure.params().span(), root.clone()); let docs = None.or_else(|| {
// use parent of params, todo: reliable way to get the def target
let def = get_non_strict_def_target(root.clone())?;
find_docs_of(&self.source, def)
});
let parsed = docs.and_then(|docs| {
let documenting_id = closure
.name()
.and_then(|n| self.get_def_id(n.span(), &to_ident_ref(&root, n)?))?;
let converted = convert_docs(self.ctx.world(), &docs).ok()?;
let converted = identify_func_docs(&converted).ok()?;
let module = self.ctx.module_by_str(docs)?;
// Wrap a docs scope
self.with_docs_scope(|this| {
this.documenting_id = Some(documenting_id);
let mut params = ParamDocs::new();
for param in converted.params.into_iter() {
params.insert(
param.name,
ParamDoc {
// docs: param.docs,
ty: this.check_doc_types(&module, param.types),
// default: param.default,
},
);
}
Some((
Some(converted.docs),
params,
converted
.return_ty
.map(|ty| this.check_doc_types(&module, ty)),
))
})
});
let (_docs, param_docs, _ret) = parsed.unwrap_or_default();
log::debug!("check closure: {:?} -> {param_docs:#?}", closure.name());
let mut pos = vec![]; let mut pos = vec![];
let mut named = BTreeMap::new(); let mut named = BTreeMap::new();
@ -381,22 +436,32 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
for param in closure.params().children() { for param in closure.params().children() {
match param { match param {
ast::Param::Pos(pattern) => { ast::Param::Pos(pattern) => {
pos.push(self.check_pattern(pattern, Ty::Any, root.clone())); pos.push(self.check_pattern(pattern, Ty::Any, &param_docs, root.clone()));
} }
ast::Param::Named(e) => { ast::Param::Named(e) => {
let name = e.name().get();
let exp = self.check_expr_in(e.expr().span(), root.clone()); let exp = self.check_expr_in(e.expr().span(), root.clone());
let v = self.get_var(e.name().span(), to_ident_ref(&root, e.name())?)?; let v = self.get_var(e.name().span(), to_ident_ref(&root, e.name())?)?;
let anno = param_docs.get(name.as_str()).and_then(|p| p.ty.clone());
log::debug!("check closure param: {name} with {exp:?} and annotation {anno:?}");
if let Some(anno) = anno {
self.constrain(&v, &anno);
}
// todo: this is less efficient than v.lbs.push(exp), we may have some idea to // todo: this is less efficient than v.lbs.push(exp), we may have some idea to
// optimize it, so I put a todo here. // optimize it, so I put a todo here.
self.constrain(&exp, &v); self.constrain(&exp, &v);
named.insert(e.name().into(), v); named.insert(name.into(), v);
defaults.insert(e.name().into(), exp); defaults.insert(name.into(), exp);
} }
// todo: spread left/right // todo: spread left/right
ast::Param::Spread(a) => { ast::Param::Spread(a) => {
if let Some(e) = a.sink_ident() { if let Some(e) = a.sink_ident() {
let exp = Ty::Builtin(BuiltinTy::Args); let exp = Ty::Builtin(BuiltinTy::Args);
let v = self.get_var(e.span(), to_ident_ref(&root, e)?)?; let v = self.get_var(e.span(), to_ident_ref(&root, e)?)?;
let anno = param_docs.get(e.get().as_str()).and_then(|p| p.ty.clone());
if let Some(anno) = anno {
self.constrain(&v, &anno);
}
self.constrain(&exp, &v); self.constrain(&exp, &v);
rest = Some(v); rest = Some(v);
} }
@ -456,7 +521,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
.map(|init| self.check_expr_in(init.span(), root.clone())) .map(|init| self.check_expr_in(init.span(), root.clone()))
.unwrap_or_else(|| Ty::Builtin(BuiltinTy::Infer)); .unwrap_or_else(|| Ty::Builtin(BuiltinTy::Infer));
self.check_pattern(pattern, value, root.clone()); self.check_pattern(pattern, value, &ParamDocs::default(), root.clone());
} }
} }
@ -568,8 +633,14 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
.unwrap_or(Ty::Builtin(BuiltinTy::Undef)) .unwrap_or(Ty::Builtin(BuiltinTy::Undef))
} }
fn check_pattern(&mut self, pattern: ast::Pattern<'_>, value: Ty, root: LinkedNode<'_>) -> Ty { fn check_pattern(
self.check_pattern_(pattern, value, root) &mut self,
pattern: ast::Pattern<'_>,
value: Ty,
param_docs: &ParamDocs,
root: LinkedNode<'_>,
) -> Ty {
self.check_pattern_(pattern, value, param_docs, root)
.unwrap_or(Ty::Builtin(BuiltinTy::Undef)) .unwrap_or(Ty::Builtin(BuiltinTy::Undef))
} }
@ -577,19 +648,180 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
&mut self, &mut self,
pattern: ast::Pattern<'_>, pattern: ast::Pattern<'_>,
value: Ty, value: Ty,
param_docs: &ParamDocs,
root: LinkedNode<'_>, root: LinkedNode<'_>,
) -> Option<Ty> { ) -> Option<Ty> {
Some(match pattern { Some(match pattern {
ast::Pattern::Normal(ast::Expr::Ident(ident)) => { ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
let v = self.get_var(ident.span(), to_ident_ref(&root, ident)?)?; let v = self.get_var(ident.span(), to_ident_ref(&root, ident)?)?;
let anno = param_docs
.get(ident.get().as_str())
.and_then(|p| p.ty.clone());
log::debug!("check pattern: {ident:?} with {value:?} and annotation {anno:?}");
if let Some(anno) = anno {
self.constrain(&v, &anno);
}
self.constrain(&value, &v); self.constrain(&value, &v);
v v
} }
ast::Pattern::Normal(_) => Ty::Any, ast::Pattern::Normal(_) => Ty::Any,
ast::Pattern::Placeholder(_) => Ty::Any, ast::Pattern::Placeholder(_) => Ty::Any,
ast::Pattern::Parenthesized(exp) => self.check_pattern(exp.pattern(), value, root), ast::Pattern::Parenthesized(exp) => {
self.check_pattern(exp.pattern(), value, param_docs, root)
}
// todo: pattern // todo: pattern
ast::Pattern::Destructuring(_destruct) => Ty::Any, ast::Pattern::Destructuring(_destruct) => Ty::Any,
}) })
} }
fn check_doc_types(&mut self, m: &Module, strs: String) -> Option<Ty> {
let mut types = vec![];
for name in strs.split(",").map(|e| e.trim()) {
let Some(ty) = self.check_doc_type_ident(m, name) else {
continue;
};
types.push(ty);
}
Some(Ty::from_types(types.into_iter()))
}
fn check_doc_type_ident(&mut self, m: &Module, name: &str) -> Option<Ty> {
static TYPE_REPRS: LazyLock<HashMap<&'static str, Ty>> = LazyLock::new(|| {
let values = Vec::from_iter(
[
Value::None,
Value::Auto,
// Value::Bool(Default::default()),
Value::Int(Default::default()),
Value::Float(Default::default()),
Value::Length(Default::default()),
Value::Angle(Default::default()),
Value::Ratio(Default::default()),
Value::Relative(Default::default()),
Value::Fraction(Default::default()),
Value::Str(Default::default()),
]
.map(|v| v.ty())
.into_iter()
.chain([
Type::of::<typst::visualize::Color>(),
Type::of::<typst::visualize::Gradient>(),
Type::of::<typst::visualize::Pattern>(),
Type::of::<typst::symbols::Symbol>(),
Type::of::<typst::foundations::Version>(),
Type::of::<typst::foundations::Bytes>(),
Type::of::<typst::foundations::Label>(),
Type::of::<typst::foundations::Datetime>(),
Type::of::<typst::foundations::Duration>(),
Type::of::<typst::foundations::Content>(),
Type::of::<typst::foundations::Styles>(),
Type::of::<typst::foundations::Array>(),
Type::of::<typst::foundations::Dict>(),
Type::of::<typst::foundations::Func>(),
Type::of::<typst::foundations::Args>(),
Type::of::<typst::foundations::Type>(),
Type::of::<typst::foundations::Module>(),
]),
);
let shorts = values
.clone()
.into_iter()
.map(|ty| (ty.short_name(), Ty::Builtin(BuiltinTy::Type(ty))));
let longs = values
.into_iter()
.map(|ty| (ty.long_name(), Ty::Builtin(BuiltinTy::Type(ty))));
let builtins = [
("any", Ty::Any),
("bool", Ty::Boolean(None)),
("boolean", Ty::Boolean(None)),
("false", Ty::Boolean(Some(false))),
("true", Ty::Boolean(Some(true))),
];
HashMap::from_iter(shorts.chain(longs).chain(builtins))
});
let builtin_ty = TYPE_REPRS.get(name).cloned();
builtin_ty.or_else(|| self.check_doc_type_anno(m, name))
}
fn check_doc_type_anno(&mut self, m: &Module, name: &str) -> Option<Ty> {
if let Some(v) = self.docs_scope.get(name) {
return v.clone();
}
let v = m.scope().get(name)?;
log::debug!("check doc type annotation: {name:?}");
if let Value::Content(c) = v {
let anno = c.clone().unpack::<typst::text::RawElem>().ok()?;
let text = anno.text().clone().into_value().cast::<Str>().ok()?;
let code = typst::syntax::parse_code(&text.as_str().replace('\'', "θ"));
let mut exprs = code.cast::<ast::Code>()?.exprs();
let ret = self.check_doc_type_expr(m, exprs.next()?);
self.docs_scope.insert(name.into(), ret.clone());
ret
} else {
None
}
}
fn check_doc_type_expr(&mut self, m: &Module, s: ast::Expr) -> Option<Ty> {
log::debug!("check doc type expr: {s:?}");
match s {
ast::Expr::Ident(i) => self.check_doc_type_ident(m, i.get().as_str()),
ast::Expr::Closure(c) => {
log::debug!("check doc closure annotation: {c:?}");
let mut pos = vec![];
let mut named = BTreeMap::new();
let mut rest = None;
for param in c.params().children() {
match param {
ast::Param::Pos(ast::Pattern::Normal(ast::Expr::Ident(i))) => {
let base_ty = self.docs_scope.get(i.get().as_str()).cloned();
pos.push(base_ty.flatten().unwrap_or(Ty::Any));
}
ast::Param::Pos(_) => {
pos.push(Ty::Any);
}
ast::Param::Named(e) => {
let exp = self.check_doc_type_expr(m, e.expr()).unwrap_or(Ty::Any);
named.insert(e.name().into(), exp);
}
// todo: spread left/right
ast::Param::Spread(s) => {
let Some(i) = s.sink_ident() else {
continue;
};
let name = i.get().clone();
let rest_ty = self
.docs_scope
.get(i.get().as_str())
.cloned()
.flatten()
.unwrap_or_else(|| {
self.generate_var(
name.as_str().into(),
self.documenting_id.unwrap(),
)
});
self.docs_scope.insert(name, Some(rest_ty.clone()));
rest = Some(rest_ty);
}
}
}
let body = self.check_doc_type_expr(m, c.body())?;
let sig = SigTy::new(pos, named, rest, Some(body)).into();
Some(Ty::Func(sig))
}
ast::Expr::Dict(d) => {
log::debug!("check doc dict annotation: {d:?}");
None
}
_ => None,
}
}
} }

View file

@ -9,7 +9,6 @@ use std::ops::Range;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use comemo::Track;
use ecow::{eco_vec, EcoString, EcoVec}; use ecow::{eco_vec, EcoString, EcoVec};
use indexmap::IndexSet; use indexmap::IndexSet;
use itertools::Itertools; use itertools::Itertools;
@ -19,14 +18,12 @@ use serde::{Deserialize, Serialize};
use tinymist_world::base::{EntryState, ShadowApi, TaskInputs}; use tinymist_world::base::{EntryState, ShadowApi, TaskInputs};
use tinymist_world::LspWorld; use tinymist_world::LspWorld;
use typst::diag::{eco_format, StrResult}; use typst::diag::{eco_format, StrResult};
use typst::engine::Route;
use typst::eval::Tracer;
use typst::foundations::{Bytes, Module, Value}; use typst::foundations::{Bytes, Module, Value};
use typst::syntax::package::{PackageManifest, PackageSpec}; use typst::syntax::package::{PackageManifest, PackageSpec};
use typst::syntax::{FileId, Span, VirtualPath}; use typst::syntax::{FileId, Span, VirtualPath};
use typst::World; use typst::World;
use self::tidy::*; pub(crate) use self::tidy::*;
use crate::analysis::analyze_dyn_signature; use crate::analysis::analyze_dyn_signature;
use crate::syntax::{find_docs_of, get_non_strict_def_target, IdentRef}; use crate::syntax::{find_docs_of, get_non_strict_def_target, IdentRef};
use crate::ty::Ty; use crate::ty::Ty;
@ -238,25 +235,15 @@ pub fn get_manifest(world: &LspWorld, toml_id: FileId) -> StrResult<PackageManif
.map_err(|err| eco_format!("package manifest is malformed ({})", err.message())) .map_err(|err| eco_format!("package manifest is malformed ({})", err.message()))
} }
struct ScanSymbolCtx<'a> { struct ScanSymbolCtx<'a, 'w> {
world: &'a LspWorld, ctx: &'a mut AnalysisContext<'w>,
for_spec: Option<&'a PackageSpec>, for_spec: Option<&'a PackageSpec>,
aliases: &'a mut HashMap<FileId, Vec<String>>, aliases: &'a mut HashMap<FileId, Vec<String>>,
extras: &'a mut Vec<SymbolInfo>, extras: &'a mut Vec<SymbolInfo>,
root: FileId, root: FileId,
} }
impl ScanSymbolCtx<'_> { impl ScanSymbolCtx<'_, '_> {
fn module(&mut self, fid: FileId) -> StrResult<Module> {
let source = self.world.source(fid).map_err(|e| eco_format!("{e}"))?;
let route = Route::default();
let mut tracer = Tracer::default();
let w: &dyn typst::World = self.world;
typst::eval::eval(w.track(), route.track(), tracer.track_mut(), &source)
.map_err(|e| eco_format!("{e:?}"))
}
fn module_sym(&mut self, path: EcoVec<&str>, module: Module) -> SymbolInfo { fn module_sym(&mut self, path: EcoVec<&str>, module: Module) -> SymbolInfo {
let key = module.name().to_owned(); let key = module.name().to_owned();
let site = Some(self.root); let site = Some(self.root);
@ -271,7 +258,7 @@ impl ScanSymbolCtx<'_> {
site: Option<&FileId>, site: Option<&FileId>,
val: &Value, val: &Value,
) -> SymbolInfo { ) -> SymbolInfo {
let mut head = create_head(self.world, key, val); let mut head = create_head(self.ctx, key, val);
if !matches!(&val, Value::Module(..)) { if !matches!(&val, Value::Module(..)) {
if let Some((span, mod_fid)) = head.span.and_then(Span::id).zip(site) { if let Some((span, mod_fid)) = head.span.and_then(Span::id).zip(site) {
@ -321,7 +308,7 @@ impl ScanSymbolCtx<'_> {
if fid.package() == self.for_spec { if fid.package() == self.for_spec {
let av = self.aliases.entry(fid).or_default(); let av = self.aliases.entry(fid).or_default();
if av.is_empty() { if av.is_empty() {
let m = self.module(fid); let m = self.ctx.module_by_id(fid);
let mut path = path.clone(); let mut path = path.clone();
path.push("-"); path.push("-");
path.push(key); path.push(key);
@ -341,9 +328,9 @@ impl ScanSymbolCtx<'_> {
} }
/// List all symbols in a package. /// List all symbols in a package.
pub fn list_symbols(world: &LspWorld, spec: &PackageInfo) -> StrResult<SymbolsInfo> { pub fn list_symbols(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult<SymbolsInfo> {
let toml_id = get_manifest_id(spec)?; let toml_id = get_manifest_id(spec)?;
let manifest = get_manifest(world, toml_id)?; let manifest = get_manifest(ctx.world(), toml_id)?;
let for_spec = PackageSpec { let for_spec = PackageSpec {
namespace: spec.namespace.clone(), namespace: spec.namespace.clone(),
@ -355,14 +342,17 @@ pub fn list_symbols(world: &LspWorld, spec: &PackageInfo) -> StrResult<SymbolsIn
let entry_point = toml_id.join(&manifest.package.entrypoint); let entry_point = toml_id.join(&manifest.package.entrypoint);
let mut scan_ctx = ScanSymbolCtx { let mut scan_ctx = ScanSymbolCtx {
world, ctx,
root: entry_point, root: entry_point,
for_spec: Some(&for_spec), for_spec: Some(&for_spec),
aliases: &mut aliases, aliases: &mut aliases,
extras: &mut extras, extras: &mut extras,
}; };
let src = scan_ctx.module(entry_point)?; let src = scan_ctx
.ctx
.module_by_id(entry_point)
.map_err(|e| eco_format!("failed to get module by id {entry_point:?}: {e:?}"))?;
let mut symbols = scan_ctx.module_sym(eco_vec![], src); let mut symbols = scan_ctx.module_sym(eco_vec![], src);
let module_uses = aliases let module_uses = aliases
@ -403,7 +393,7 @@ static DOCS_CONVERT_ID: std::sync::LazyLock<Mutex<FileId>> = std::sync::LazyLock
Mutex::new(FileId::new(None, VirtualPath::new("__tinymist_docs__.typ"))) Mutex::new(FileId::new(None, VirtualPath::new("__tinymist_docs__.typ")))
}); });
fn convert_docs(world: &LspWorld, content: &str) -> StrResult<EcoString> { pub(crate) fn convert_docs(world: &LspWorld, content: &str) -> StrResult<EcoString> {
static DOCS_LIB: std::sync::LazyLock<Arc<typlite::scopes::Scopes<typlite::value::Value>>> = static DOCS_LIB: std::sync::LazyLock<Arc<typlite::scopes::Scopes<typlite::value::Value>>> =
std::sync::LazyLock::new(library::lib); std::sync::LazyLock::new(library::lib);
@ -430,7 +420,7 @@ fn convert_docs(world: &LspWorld, content: &str) -> StrResult<EcoString> {
fn identify_docs(kind: &str, content: &str) -> StrResult<Docs> { fn identify_docs(kind: &str, content: &str) -> StrResult<Docs> {
match kind { match kind {
"function" => identify_tidy_func_docs(content).map(Docs::Function), "function" => identify_func_docs(content).map(Docs::Function),
"variable" => identify_tidy_var_docs(content).map(Docs::Variable), "variable" => identify_tidy_var_docs(content).map(Docs::Variable),
"module" => identify_tidy_module_docs(content).map(Docs::Module), "module" => identify_tidy_module_docs(content).map(Docs::Module),
_ => Err(eco_format!("unknown kind {kind}")), _ => Err(eco_format!("unknown kind {kind}")),
@ -578,7 +568,7 @@ pub fn generate_md_docs(
}; };
let mut md = String::new(); let mut md = String::new();
let SymbolsInfo { root, module_uses } = list_symbols(world, spec)?; let SymbolsInfo { root, module_uses } = list_symbols(ctx, spec)?;
log::debug!("module_uses: {module_uses:#?}"); log::debug!("module_uses: {module_uses:#?}");
@ -897,14 +887,14 @@ fn kind_of(val: &Value) -> EcoString {
.into() .into()
} }
fn create_head(world: &LspWorld, k: &str, v: &Value) -> SymbolInfoHead { fn create_head(world: &mut AnalysisContext, k: &str, v: &Value) -> SymbolInfoHead {
let kind = kind_of(v); let kind = kind_of(v);
let (docs, name_range, fid, span) = match v { let (docs, name_range, fid, span) = match v {
Value::Func(f) => { Value::Func(f) => {
let mut span = None; let mut span = None;
let mut name_range = None; let mut name_range = None;
let docs = None.or_else(|| { let docs = None.or_else(|| {
let source = world.source(f.span().id()?).ok()?; let source = world.source_by_id(f.span().id()?).ok()?;
let node = source.find(f.span())?; let node = source.find(f.span())?;
log::debug!("node: {k} -> {:?}", node.parent()); log::debug!("node: {k} -> {:?}", node.parent());
// use parent of params, todo: reliable way to get the def target // use parent of params, todo: reliable way to get the def target

View file

@ -28,7 +28,7 @@ pub struct TidyModuleDocs {
pub docs: String, pub docs: String,
} }
pub fn identify_tidy_func_docs(converted: &str) -> StrResult<TidyFuncDocs> { pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
let lines = converted.lines().collect::<Vec<_>>(); let lines = converted.lines().collect::<Vec<_>>();
let mut matching_return_ty = true; let mut matching_return_ty = true;
@ -213,7 +213,7 @@ mod tests {
use super::TidyParamDocs; use super::TidyParamDocs;
fn func(s: &str) -> String { fn func(s: &str) -> String {
let f = super::identify_tidy_func_docs(s).unwrap(); let f = super::identify_func_docs(s).unwrap();
let mut res = format!(">> docs:\n{}\n<< docs", f.docs); let mut res = format!(">> docs:\n{}\n<< docs", f.docs);
if let Some(t) = f.return_ty { if let Some(t) = f.return_ty {
res.push_str(&format!("\n>>return\n{t}\n<<return")); res.push_str(&format!("\n>>return\n{t}\n<<return"));

View file

@ -11,7 +11,7 @@ use typst::{
text::TextItem, text::TextItem,
}; };
use crate::{AnalysisContext, StatefulRequest, VersionedDocument}; use crate::prelude::*;
/// Span information for some content. /// Span information for some content.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View file

@ -4,4 +4,4 @@ description: "Check on \"(\" (30)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font2.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font2.typ
--- ---
( ⪯ TextFont) ( ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"(\" (33)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font3.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font3.typ
--- ---
( ⪯ TextFont) ( ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"(\" (31)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font4.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font4.typ
--- ---
( ⪯ TextFont) ( ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"(\" (47)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font5.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font5.typ
--- ---
( ⪯ TextFont) ( ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (34)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element.typ
--- ---
( ⪰ ( ⪯ (TextFont | Array<TextFont>)) ⪯ TextFont) ( ⪰ ( ⪯ (TextFont | Array<TextFont>)) ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (31)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element2.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element2.typ
--- ---
( ⪰ ( ⪰ "Test" ⪯ (TextFont | Array<TextFont>)) ⪯ TextFont) ( ⪰ ( ⪰ "Test" ⪯ (TextFont | Array<TextFont>)) ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (34)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element3.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element3.typ
--- ---
( ⪯ TextFont) ( ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (31)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element4.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element4.typ
--- ---
( ⪯ TextFont) ( ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"(\" (56)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external.typ
--- ---
( ⪯ TextFont) ( ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"(\" (59)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external_alias.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external_alias.typ
--- ---
( ⪯ TextFont) ( ⪯ Array<TextFont>)

View file

@ -4,4 +4,4 @@ description: "Check on \"(\" (105)"
expression: literal_type expression: literal_type
input_file: crates/tinymist-query/src/fixtures/post_type_check/user_func.typ input_file: crates/tinymist-query/src/fixtures/post_type_check/user_func.typ
--- ---
( ⪯ TextFont) ( ⪯ Array<TextFont>)

View file

@ -0,0 +1,7 @@
/// #let fn = `(..fn-args) => any`;
///
/// - fn (function, fn): The `fn`.
/// - max-repetitions (int): The `max-repetitions`.
/// - repetitions (int): The `repetitions`.
/// - args (any, fn-args): The `args`.
#let touying-fn-wrapper(fn, max-repetitions: none, repetitions: none, ..args) = none

View file

@ -0,0 +1,17 @@
---
source: crates/tinymist-query/src/analysis.rs
expression: result
input_file: crates/tinymist-query/src/fixtures/type_check/annotation.typ
---
"args" = Args
"fn" = Any
"fn-args" = Any
"max-repetitions" = None
"repetitions" = None
"touying-fn-wrapper" = ((( ⪯ (Type(function) | (...: Any) => Any)), "max-repetitions": ( ⪯ Type(integer)), "repetitions": ( ⪯ Type(integer)), ...: ( ⪯ (Any | Any))) => None).with(..("max-repetitions": None, "repetitions": None) => any)
---
215..233 -> @touying-fn-wrapper
234..236 -> @fn
238..253 -> @max-repetitions
261..272 -> @repetitions
282..286 -> @args

View file

@ -34,7 +34,7 @@ type CompileDriver<C> = CompileDriverImpl<C, tinymist_world::LspCompilerFeat>;
struct WrapWorld<'a>(&'a mut LspWorld); struct WrapWorld<'a>(&'a mut LspWorld);
impl<'a> AnalysisResources for WrapWorld<'a> { impl<'a> AnalysisResources for WrapWorld<'a> {
fn world(&self) -> &dyn typst::World { fn world(&self) -> &LspWorld {
self.0 self.0
} }

View file

@ -25,7 +25,7 @@ use crate::{
pub use tinymist_derive::BindTyCtx; pub use tinymist_derive::BindTyCtx;
pub(crate) use super::{TyCtxMut, TyCtx}; pub(crate) use super::{TyCtx, TyCtxMut};
pub(crate) use crate::adt::interner::Interned; pub(crate) use crate::adt::interner::Interned;
/// A reference to the interned type /// A reference to the interned type
@ -179,7 +179,7 @@ impl fmt::Debug for TypeSigParam<'_> {
match self { match self {
TypeSigParam::Pos(ty) => write!(f, "{ty:?}"), TypeSigParam::Pos(ty) => write!(f, "{ty:?}"),
TypeSigParam::Named(name, ty) => write!(f, "{name:?}: {ty:?}"), TypeSigParam::Named(name, ty) => write!(f, "{name:?}: {ty:?}"),
TypeSigParam::Rest(ty) => write!(f, "...: {ty:?}[]"), TypeSigParam::Rest(ty) => write!(f, "...: {ty:?}"),
} }
} }
} }
@ -548,13 +548,10 @@ impl SigTy {
/// Array constructor /// Array constructor
#[comemo::memoize] #[comemo::memoize]
pub fn array_cons(elem: Ty, anyify: bool) -> Interned<SigTy> { pub fn array_cons(elem: Ty, anyify: bool) -> Interned<SigTy> {
let ret = if anyify { let rest = Ty::Array(Interned::new(elem.clone()));
Ty::Any let ret = if anyify { Ty::Any } else { rest.clone() };
} else {
Ty::Array(Interned::new(elem.clone()))
};
Interned::new(Self { Interned::new(Self {
inputs: Interned::new(vec![elem]), inputs: Interned::new(vec![rest]),
body: Some(ret), body: Some(ret),
names: NameBone::empty(), names: NameBone::empty(),
name_started: 0, name_started: 0,

View file

@ -1,5 +1,6 @@
use std::{collections::HashMap, fmt::Write}; use std::{collections::HashMap, fmt::Write};
use comemo::Tracked;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::Deserialize; use serde::Deserialize;
@ -10,7 +11,7 @@ use typst::{
introspection::MetadataElem, introspection::MetadataElem,
syntax::Span, syntax::Span,
text::{FontInfo, FontStyle}, text::{FontInfo, FontStyle},
Library, Library, World,
}; };
mod tooltip; mod tooltip;
@ -429,7 +430,7 @@ pub fn truncated_doc_repr(value: &Value) -> EcoString {
} }
/// Run a function with a VM instance in the world /// Run a function with a VM instance in the world
pub fn with_vm<T>(world: &dyn typst::World, f: impl FnOnce(&mut typst::eval::Vm) -> T) -> T { pub fn with_vm<T>(world: Tracked<dyn World + '_>, f: impl FnOnce(&mut typst::eval::Vm) -> T) -> T {
use comemo::Track; use comemo::Track;
use typst::engine::*; use typst::engine::*;
use typst::eval::*; use typst::eval::*;
@ -440,7 +441,7 @@ pub fn with_vm<T>(world: &dyn typst::World, f: impl FnOnce(&mut typst::eval::Vm)
let introspector = Introspector::default(); let introspector = Introspector::default();
let mut tracer = Tracer::new(); let mut tracer = Tracer::new();
let engine = Engine { let engine = Engine {
world: world.track(), world,
route: Route::default(), route: Route::default(),
introspector: introspector.track(), introspector: introspector.track(),
locator: &mut locator, locator: &mut locator,

View file

@ -206,7 +206,7 @@ impl CompileHandler {
struct WrapWorld<'a>(&'a LspWorld, &'a PeriscopeRenderer); struct WrapWorld<'a>(&'a LspWorld, &'a PeriscopeRenderer);
impl<'a> AnalysisResources for WrapWorld<'a> { impl<'a> AnalysisResources for WrapWorld<'a> {
fn world(&self) -> &dyn typst::World { fn world(&self) -> &LspWorld {
self.0 self.0
} }

View file

@ -551,17 +551,23 @@ impl LanguageState {
&mut self, &mut self,
mut arguments: Vec<JsonValue>, mut arguments: Vec<JsonValue>,
) -> AnySchedulableResponse { ) -> AnySchedulableResponse {
let handle = self.primary().handle.clone();
let info = get_arg!(arguments[1] as PackageInfo); let info = get_arg!(arguments[1] as PackageInfo);
let snap = self.primary().snapshot().map_err(z_internal_error)?; let snap = self.primary().snapshot().map_err(z_internal_error)?;
just_future(async move { just_future(async move {
let snap = snap.receive().await.map_err(z_internal_error)?; let snap = snap.receive().await.map_err(z_internal_error)?;
let w = snap.world.as_ref(); let w = snap.world.as_ref();
let symbols = tinymist_query::docs::list_symbols(w, &info)
.map_err(map_string_err("failed to list symbols"))
.map_err(z_internal_error)?;
serde_json::to_value(symbols).map_err(|e| internal_error(e.to_string())) let symbols = handle
.run_analysis(w, |a| {
tinymist_query::docs::list_symbols(a, &info)
.map_err(map_string_err("failed to list symbols"))
})
.map_err(internal_error)?
.map_err(internal_error)?;
serde_json::to_value(symbols).map_err(internal_error)
}) })
} }
@ -582,7 +588,8 @@ impl LanguageState {
&mut self, &mut self,
info: PackageInfo, info: PackageInfo,
) -> LspResult<impl Future<Output = LspResult<String>>> { ) -> LspResult<impl Future<Output = LspResult<String>>> {
let handle = self.primary().handle.clone(); let handle: std::sync::Arc<actor::typ_client::CompileHandler> =
self.primary().handle.clone();
let snap = handle.snapshot().map_err(z_internal_error)?; let snap = handle.snapshot().map_err(z_internal_error)?;
Ok(async move { Ok(async move {
let snap = snap.receive().await.map_err(z_internal_error)?; let snap = snap.receive().await.map_err(z_internal_error)?;
@ -609,7 +616,7 @@ impl LanguageState {
let res = handle.run_analysis(w, |a| { let res = handle.run_analysis(w, |a| {
tinymist_query::docs::generate_md_docs(a, w, &info) tinymist_query::docs::generate_md_docs(a, w, &info)
.map_err(map_string_err("failed to list symbols")) .map_err(map_string_err("failed to generate docs"))
.map_err(z_internal_error) .map_err(z_internal_error)
}); });

View file

@ -0,0 +1 @@
#let _fn(T, fn-args: "T[]") = (..fn-args) => any;