mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-24 05:06:41 +00:00
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:
parent
c8e11e92f1
commit
3ed401740e
26 changed files with 407 additions and 99 deletions
|
|
@ -1,13 +1,16 @@
|
|||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::{collections::HashSet, ops::Deref};
|
||||
|
||||
use comemo::Tracked;
|
||||
use comemo::{Track, Tracked};
|
||||
use once_cell::sync::OnceCell;
|
||||
use reflexo::hash::{hash128, FxDashMap};
|
||||
use reflexo::{debug_loc::DataSource, ImmutPath};
|
||||
use typst::diag::{eco_format, FileError, FileResult, PackageError};
|
||||
use typst::eval::Eval;
|
||||
use typst::foundations::{self, Bytes, Func, Styles};
|
||||
use tinymist_world::LspWorld;
|
||||
use tinymist_world::DETACHED_ENTRY;
|
||||
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::syntax::{package::PackageSpec, Span, VirtualPath};
|
||||
use typst::{model::Document, text::Font};
|
||||
|
|
@ -148,7 +151,7 @@ impl ModuleAnalysisCache {
|
|||
/// The resources for analysis.
|
||||
pub trait AnalysisResources {
|
||||
/// Get the world surface for Typst compiler.
|
||||
fn world(&self) -> &dyn World;
|
||||
fn world(&self) -> &LspWorld;
|
||||
|
||||
/// Resolve the real path for a package spec.
|
||||
fn resolve(&self, spec: &PackageSpec) -> Result<Arc<Path>, PackageError>;
|
||||
|
|
@ -226,7 +229,7 @@ impl<'w> AnalysisContext<'w> {
|
|||
}
|
||||
|
||||
/// Get the world surface for Typst compiler.
|
||||
pub fn world(&self) -> &'w dyn World {
|
||||
pub fn world(&self) -> &'w LspWorld {
|
||||
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.
|
||||
pub fn path_for_id(&self, id: TypstFileId) -> Result<PathBuf, FileError> {
|
||||
if id.vpath().as_rootless_path() == Path::new("-") {
|
||||
|
|
@ -319,19 +335,6 @@ impl<'w> AnalysisContext<'w> {
|
|||
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.
|
||||
pub fn source_by_path(&mut self, p: &Path) -> FileResult<Source> {
|
||||
// todo: source in packages
|
||||
|
|
@ -339,6 +342,31 @@ impl<'w> AnalysisContext<'w> {
|
|||
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.
|
||||
pub fn deref_syntax_at<'s>(
|
||||
&mut self,
|
||||
|
|
@ -517,7 +545,7 @@ impl<'w> AnalysisContext<'w> {
|
|||
pub fn import_info(&mut self, source: Source) -> Option<Arc<ImportInfo>> {
|
||||
use comemo::Track;
|
||||
let w = self.resources.world();
|
||||
let w = w.track();
|
||||
let w = (w as &dyn World).track();
|
||||
|
||||
let token = &self.analysis.workers.import;
|
||||
token.enter(|| import_info(w, source))
|
||||
|
|
@ -582,13 +610,13 @@ impl<'w> AnalysisContext<'w> {
|
|||
) -> Option<Arc<BibInfo>> {
|
||||
use comemo::Track;
|
||||
let w = self.resources.world();
|
||||
let w = w.track();
|
||||
let w = (w as &dyn World).track();
|
||||
|
||||
bib_info(w, span, bib_paths.collect())
|
||||
}
|
||||
|
||||
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> {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use ecow::EcoString;
|
||||
use once_cell::sync::Lazy;
|
||||
use reflexo::vector::ir::DefId;
|
||||
use typst::{
|
||||
|
|
@ -43,6 +44,9 @@ pub(crate) fn type_check(ctx: &mut AnalysisContext, source: Source) -> Option<Ar
|
|||
def_use_info,
|
||||
info: &mut info,
|
||||
externals: HashMap::new(),
|
||||
docs_scope: HashMap::new(),
|
||||
documenting_id: None,
|
||||
generated: HashMap::new(),
|
||||
mode: InterpretMode::Markup,
|
||||
};
|
||||
let lnk = LinkedNode::new(source.root());
|
||||
|
|
@ -70,6 +74,9 @@ struct TypeChecker<'a, 'w> {
|
|||
def_use_info: Arc<DefUseInfo>,
|
||||
|
||||
info: &'a mut TypeScheme,
|
||||
docs_scope: HashMap<EcoString, Option<Ty>>,
|
||||
documenting_id: Option<DefId>,
|
||||
generated: HashMap<DefId, u32>,
|
||||
externals: HashMap<DefId, Option<Ty>>,
|
||||
mode: InterpretMode,
|
||||
}
|
||||
|
|
@ -106,11 +113,25 @@ impl<'a, 'w> TypeChecker<'a, '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> {
|
||||
let def_id = self
|
||||
.def_use_info
|
||||
.get_ref(&r)
|
||||
.or_else(|| Some(self.def_use_info.get_def(s.id()?, &r)?.0))?;
|
||||
let def_id = self.get_def_id(s, &r)?;
|
||||
|
||||
// todo: false positive of clippy
|
||||
#[allow(clippy::map_entry)]
|
||||
|
|
@ -134,6 +155,13 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
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> {
|
||||
if let Some(ty) = self.externals.get(&def_id) {
|
||||
return ty.clone();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Type checking on source file
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::{collections::BTreeMap, sync::LazyLock};
|
||||
|
||||
use typst::{
|
||||
foundations::Value,
|
||||
foundations::{IntoValue, Module, Str, Type, Value},
|
||||
syntax::{
|
||||
ast::{self, AstNode},
|
||||
LinkedNode, SyntaxKind,
|
||||
|
|
@ -11,7 +11,21 @@ use typst::{
|
|||
};
|
||||
|
||||
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> {
|
||||
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> {
|
||||
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 named = BTreeMap::new();
|
||||
|
|
@ -381,22 +436,32 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
for param in closure.params().children() {
|
||||
match param {
|
||||
ast::Param::Pos(pattern) => {
|
||||
pos.push(self.check_pattern(pattern, Ty::Any, root.clone()));
|
||||
pos.push(self.check_pattern(pattern, Ty::Any, ¶m_docs, root.clone()));
|
||||
}
|
||||
ast::Param::Named(e) => {
|
||||
let name = e.name().get();
|
||||
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 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
|
||||
// optimize it, so I put a todo here.
|
||||
self.constrain(&exp, &v);
|
||||
named.insert(e.name().into(), v);
|
||||
defaults.insert(e.name().into(), exp);
|
||||
named.insert(name.into(), v);
|
||||
defaults.insert(name.into(), exp);
|
||||
}
|
||||
// todo: spread left/right
|
||||
ast::Param::Spread(a) => {
|
||||
if let Some(e) = a.sink_ident() {
|
||||
let exp = Ty::Builtin(BuiltinTy::Args);
|
||||
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);
|
||||
rest = Some(v);
|
||||
}
|
||||
|
|
@ -456,7 +521,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
.map(|init| self.check_expr_in(init.span(), root.clone()))
|
||||
.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))
|
||||
}
|
||||
|
||||
fn check_pattern(&mut self, pattern: ast::Pattern<'_>, value: Ty, root: LinkedNode<'_>) -> Ty {
|
||||
self.check_pattern_(pattern, value, root)
|
||||
fn check_pattern(
|
||||
&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))
|
||||
}
|
||||
|
||||
|
|
@ -577,19 +648,180 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
&mut self,
|
||||
pattern: ast::Pattern<'_>,
|
||||
value: Ty,
|
||||
param_docs: &ParamDocs,
|
||||
root: LinkedNode<'_>,
|
||||
) -> Option<Ty> {
|
||||
Some(match pattern {
|
||||
ast::Pattern::Normal(ast::Expr::Ident(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);
|
||||
v
|
||||
}
|
||||
ast::Pattern::Normal(_) => 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
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ use std::ops::Range;
|
|||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use comemo::Track;
|
||||
use ecow::{eco_vec, EcoString, EcoVec};
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Itertools;
|
||||
|
|
@ -19,14 +18,12 @@ use serde::{Deserialize, Serialize};
|
|||
use tinymist_world::base::{EntryState, ShadowApi, TaskInputs};
|
||||
use tinymist_world::LspWorld;
|
||||
use typst::diag::{eco_format, StrResult};
|
||||
use typst::engine::Route;
|
||||
use typst::eval::Tracer;
|
||||
use typst::foundations::{Bytes, Module, Value};
|
||||
use typst::syntax::package::{PackageManifest, PackageSpec};
|
||||
use typst::syntax::{FileId, Span, VirtualPath};
|
||||
use typst::World;
|
||||
|
||||
use self::tidy::*;
|
||||
pub(crate) use self::tidy::*;
|
||||
use crate::analysis::analyze_dyn_signature;
|
||||
use crate::syntax::{find_docs_of, get_non_strict_def_target, IdentRef};
|
||||
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()))
|
||||
}
|
||||
|
||||
struct ScanSymbolCtx<'a> {
|
||||
world: &'a LspWorld,
|
||||
struct ScanSymbolCtx<'a, 'w> {
|
||||
ctx: &'a mut AnalysisContext<'w>,
|
||||
for_spec: Option<&'a PackageSpec>,
|
||||
aliases: &'a mut HashMap<FileId, Vec<String>>,
|
||||
extras: &'a mut Vec<SymbolInfo>,
|
||||
root: FileId,
|
||||
}
|
||||
|
||||
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:?}"))
|
||||
}
|
||||
|
||||
impl ScanSymbolCtx<'_, '_> {
|
||||
fn module_sym(&mut self, path: EcoVec<&str>, module: Module) -> SymbolInfo {
|
||||
let key = module.name().to_owned();
|
||||
let site = Some(self.root);
|
||||
|
|
@ -271,7 +258,7 @@ impl ScanSymbolCtx<'_> {
|
|||
site: Option<&FileId>,
|
||||
val: &Value,
|
||||
) -> 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 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 {
|
||||
let av = self.aliases.entry(fid).or_default();
|
||||
if av.is_empty() {
|
||||
let m = self.module(fid);
|
||||
let m = self.ctx.module_by_id(fid);
|
||||
let mut path = path.clone();
|
||||
path.push("-");
|
||||
path.push(key);
|
||||
|
|
@ -341,9 +328,9 @@ impl ScanSymbolCtx<'_> {
|
|||
}
|
||||
|
||||
/// 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 manifest = get_manifest(world, toml_id)?;
|
||||
let manifest = get_manifest(ctx.world(), toml_id)?;
|
||||
|
||||
let for_spec = PackageSpec {
|
||||
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 mut scan_ctx = ScanSymbolCtx {
|
||||
world,
|
||||
ctx,
|
||||
root: entry_point,
|
||||
for_spec: Some(&for_spec),
|
||||
aliases: &mut aliases,
|
||||
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 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")))
|
||||
});
|
||||
|
||||
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>>> =
|
||||
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> {
|
||||
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),
|
||||
"module" => identify_tidy_module_docs(content).map(Docs::Module),
|
||||
_ => Err(eco_format!("unknown kind {kind}")),
|
||||
|
|
@ -578,7 +568,7 @@ pub fn generate_md_docs(
|
|||
};
|
||||
|
||||
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:#?}");
|
||||
|
||||
|
|
@ -897,14 +887,14 @@ fn kind_of(val: &Value) -> EcoString {
|
|||
.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 (docs, name_range, fid, span) = match v {
|
||||
Value::Func(f) => {
|
||||
let mut span = None;
|
||||
let mut name_range = None;
|
||||
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())?;
|
||||
log::debug!("node: {k} -> {:?}", node.parent());
|
||||
// use parent of params, todo: reliable way to get the def target
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub struct TidyModuleDocs {
|
|||
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 mut matching_return_ty = true;
|
||||
|
|
@ -213,7 +213,7 @@ mod tests {
|
|||
use super::TidyParamDocs;
|
||||
|
||||
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);
|
||||
if let Some(t) = f.return_ty {
|
||||
res.push_str(&format!("\n>>return\n{t}\n<<return"));
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use typst::{
|
|||
text::TextItem,
|
||||
};
|
||||
|
||||
use crate::{AnalysisContext, StatefulRequest, VersionedDocument};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Span information for some content.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"(\" (30)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font2.typ
|
||||
---
|
||||
( ⪯ TextFont)
|
||||
( ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"(\" (33)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font3.typ
|
||||
---
|
||||
( ⪯ TextFont)
|
||||
( ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"(\" (31)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font4.typ
|
||||
---
|
||||
( ⪯ TextFont)
|
||||
( ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"(\" (47)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font5.typ
|
||||
---
|
||||
( ⪯ TextFont)
|
||||
( ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (34)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element.typ
|
||||
---
|
||||
( ⪰ ( ⪯ (TextFont | Array<TextFont>)) ⪯ TextFont)
|
||||
( ⪰ ( ⪯ (TextFont | Array<TextFont>)) ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (31)"
|
|||
expression: literal_type
|
||||
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>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (34)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element3.typ
|
||||
---
|
||||
( ⪯ TextFont)
|
||||
( ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (31)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element4.typ
|
||||
---
|
||||
( ⪯ TextFont)
|
||||
( ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"(\" (56)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external.typ
|
||||
---
|
||||
( ⪯ TextFont)
|
||||
( ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"(\" (59)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external_alias.typ
|
||||
---
|
||||
( ⪯ TextFont)
|
||||
( ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ description: "Check on \"(\" (105)"
|
|||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/user_func.typ
|
||||
---
|
||||
( ⪯ TextFont)
|
||||
( ⪯ Array<TextFont>)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -34,7 +34,7 @@ type CompileDriver<C> = CompileDriverImpl<C, tinymist_world::LspCompilerFeat>;
|
|||
struct WrapWorld<'a>(&'a mut LspWorld);
|
||||
|
||||
impl<'a> AnalysisResources for WrapWorld<'a> {
|
||||
fn world(&self) -> &dyn typst::World {
|
||||
fn world(&self) -> &LspWorld {
|
||||
self.0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use crate::{
|
|||
|
||||
pub use tinymist_derive::BindTyCtx;
|
||||
|
||||
pub(crate) use super::{TyCtxMut, TyCtx};
|
||||
pub(crate) use super::{TyCtx, TyCtxMut};
|
||||
pub(crate) use crate::adt::interner::Interned;
|
||||
|
||||
/// A reference to the interned type
|
||||
|
|
@ -179,7 +179,7 @@ impl fmt::Debug for TypeSigParam<'_> {
|
|||
match self {
|
||||
TypeSigParam::Pos(ty) => write!(f, "{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
|
||||
#[comemo::memoize]
|
||||
pub fn array_cons(elem: Ty, anyify: bool) -> Interned<SigTy> {
|
||||
let ret = if anyify {
|
||||
Ty::Any
|
||||
} else {
|
||||
Ty::Array(Interned::new(elem.clone()))
|
||||
};
|
||||
let rest = Ty::Array(Interned::new(elem.clone()));
|
||||
let ret = if anyify { Ty::Any } else { rest.clone() };
|
||||
Interned::new(Self {
|
||||
inputs: Interned::new(vec![elem]),
|
||||
inputs: Interned::new(vec![rest]),
|
||||
body: Some(ret),
|
||||
names: NameBone::empty(),
|
||||
name_started: 0,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::{collections::HashMap, fmt::Write};
|
||||
|
||||
use comemo::Tracked;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Deserialize;
|
||||
|
|
@ -10,7 +11,7 @@ use typst::{
|
|||
introspection::MetadataElem,
|
||||
syntax::Span,
|
||||
text::{FontInfo, FontStyle},
|
||||
Library,
|
||||
Library, World,
|
||||
};
|
||||
|
||||
mod tooltip;
|
||||
|
|
@ -429,7 +430,7 @@ pub fn truncated_doc_repr(value: &Value) -> EcoString {
|
|||
}
|
||||
|
||||
/// 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 typst::engine::*;
|
||||
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 mut tracer = Tracer::new();
|
||||
let engine = Engine {
|
||||
world: world.track(),
|
||||
world,
|
||||
route: Route::default(),
|
||||
introspector: introspector.track(),
|
||||
locator: &mut locator,
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ impl CompileHandler {
|
|||
struct WrapWorld<'a>(&'a LspWorld, &'a PeriscopeRenderer);
|
||||
|
||||
impl<'a> AnalysisResources for WrapWorld<'a> {
|
||||
fn world(&self) -> &dyn typst::World {
|
||||
fn world(&self) -> &LspWorld {
|
||||
self.0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -551,17 +551,23 @@ impl LanguageState {
|
|||
&mut self,
|
||||
mut arguments: Vec<JsonValue>,
|
||||
) -> AnySchedulableResponse {
|
||||
let handle = self.primary().handle.clone();
|
||||
let info = get_arg!(arguments[1] as PackageInfo);
|
||||
|
||||
let snap = self.primary().snapshot().map_err(z_internal_error)?;
|
||||
just_future(async move {
|
||||
let snap = snap.receive().await.map_err(z_internal_error)?;
|
||||
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,
|
||||
info: PackageInfo,
|
||||
) -> 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)?;
|
||||
Ok(async move {
|
||||
let snap = snap.receive().await.map_err(z_internal_error)?;
|
||||
|
|
@ -609,7 +616,7 @@ impl LanguageState {
|
|||
|
||||
let res = handle.run_analysis(w, |a| {
|
||||
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)
|
||||
});
|
||||
|
||||
|
|
|
|||
1
syntaxes/textmate/tests/unit/bugs/code-mode.typ
Normal file
1
syntaxes/textmate/tests/unit/bugs/code-mode.typ
Normal file
|
|
@ -0,0 +1 @@
|
|||
#let _fn(T, fn-args: "T[]") = (..fn-args) => any;
|
||||
Loading…
Add table
Add a link
Reference in a new issue