mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 17:58:17 +00:00
feat: check variable type annotations in docstring (#681)
* feat: check variable type annotations in docstring * dev: rollback playground changes * dev: refactor a bit
This commit is contained in:
parent
4aeb3747bc
commit
c17e0c787e
22 changed files with 312 additions and 107 deletions
|
@ -483,14 +483,15 @@ impl<'w> AnalysisContext<'w> {
|
|||
|
||||
pub(crate) fn compute_docstring(
|
||||
&self,
|
||||
fid: TypstFileId,
|
||||
docs: String,
|
||||
kind: DocStringKind,
|
||||
) -> Option<Arc<DocString>> {
|
||||
let h = hash128(&(&docs, &kind));
|
||||
let h = hash128(&(&fid, &docs, &kind));
|
||||
let res = if let Some(res) = self.analysis.caches.docstrings.get(&h) {
|
||||
res.clone()
|
||||
} else {
|
||||
let res = crate::analysis::tyck::compute_docstring(self, docs, kind).map(Arc::new);
|
||||
let res = crate::analysis::tyck::compute_docstring(self, fid, docs, kind).map(Arc::new);
|
||||
self.analysis.caches.docstrings.insert(h, res.clone());
|
||||
res
|
||||
};
|
||||
|
|
|
@ -4,7 +4,8 @@ use reflexo::TakeAs;
|
|||
use typst::foundations::{IntoValue, Module, Str, Type};
|
||||
|
||||
use crate::{
|
||||
docs::{convert_docs, identify_func_docs, DocStringKind},
|
||||
adt::snapshot_map::SnapshotMap,
|
||||
docs::{convert_docs, identify_func_docs, identify_var_docs, DocStringKind},
|
||||
syntax::{find_docs_of, get_non_strict_def_target},
|
||||
};
|
||||
|
||||
|
@ -13,19 +14,38 @@ use super::*;
|
|||
const DOC_VARS: u64 = 0;
|
||||
|
||||
impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||
pub fn check_closure_docs(&mut self, root: &LinkedNode) -> Option<DocString> {
|
||||
pub fn check_func_docs(&mut self, root: &LinkedNode) -> Option<DocString> {
|
||||
let closure = root.cast::<ast::Closure>()?;
|
||||
let documenting_id = closure
|
||||
.name()
|
||||
.and_then(|n| self.get_def_id(n.span(), &to_ident_ref(root, n)?))?;
|
||||
|
||||
self.check_docstring(root, DocStringKind::Function, documenting_id)
|
||||
}
|
||||
|
||||
pub fn check_var_docs(&mut self, root: &LinkedNode) -> Option<DocString> {
|
||||
let lb = root.cast::<ast::LetBinding>()?;
|
||||
let first = lb.kind().bindings();
|
||||
let documenting_id = first
|
||||
.first()
|
||||
.and_then(|n| self.get_def_id(n.span(), &to_ident_ref(root, *n)?))?;
|
||||
|
||||
self.check_docstring(root, DocStringKind::Variable, documenting_id)
|
||||
}
|
||||
|
||||
pub fn check_docstring(
|
||||
&mut self,
|
||||
root: &LinkedNode,
|
||||
kind: DocStringKind,
|
||||
base_id: DefId,
|
||||
) -> Option<DocString> {
|
||||
// todo: cache docs capture
|
||||
// use parent of params, todo: reliable way to get the def target
|
||||
let def = get_non_strict_def_target(root.clone())?;
|
||||
let docs = find_docs_of(&self.source, def)?;
|
||||
|
||||
let documenting_id = closure
|
||||
.name()
|
||||
.and_then(|n| self.get_def_id(n.span(), &to_ident_ref(root, n)?))?;
|
||||
let docstring = self.ctx.compute_docstring(docs, DocStringKind::Function)?;
|
||||
Some(docstring.take().rename_based_on(documenting_id, self))
|
||||
let docstring = self.ctx.compute_docstring(root.span().id()?, docs, kind)?;
|
||||
Some(docstring.take().rename_based_on(base_id, self))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,30 +114,36 @@ pub(crate) struct VarDoc {
|
|||
|
||||
pub(crate) fn compute_docstring(
|
||||
ctx: &AnalysisContext,
|
||||
fid: TypstFileId,
|
||||
docs: String,
|
||||
kind: DocStringKind,
|
||||
) -> Option<DocString> {
|
||||
let checker = DocsChecker {
|
||||
fid,
|
||||
ctx,
|
||||
vars: HashMap::new(),
|
||||
docs_scope: HashMap::new(),
|
||||
globals: HashMap::default(),
|
||||
locals: SnapshotMap::default(),
|
||||
next_id: 0,
|
||||
};
|
||||
match kind {
|
||||
DocStringKind::Function => checker.check_closure_docs(docs),
|
||||
DocStringKind::Function => checker.check_func_docs(docs),
|
||||
DocStringKind::Variable => checker.check_var_docs(docs),
|
||||
}
|
||||
}
|
||||
|
||||
struct DocsChecker<'a, 'w> {
|
||||
fid: TypstFileId,
|
||||
ctx: &'a AnalysisContext<'w>,
|
||||
/// The typing on definitions
|
||||
vars: HashMap<DefId, TypeVarBounds>,
|
||||
docs_scope: HashMap<EcoString, Option<Ty>>,
|
||||
globals: HashMap<EcoString, Option<Ty>>,
|
||||
locals: SnapshotMap<EcoString, Ty>,
|
||||
next_id: u32,
|
||||
}
|
||||
|
||||
impl<'a, 'w> DocsChecker<'a, 'w> {
|
||||
pub fn check_closure_docs(mut self, docs: String) -> Option<DocString> {
|
||||
pub fn check_func_docs(mut self, docs: String) -> Option<DocString> {
|
||||
let converted = convert_docs(self.ctx.world(), &docs).ok()?;
|
||||
let converted = identify_func_docs(&converted).ok()?;
|
||||
let module = self.ctx.module_by_str(docs)?;
|
||||
|
@ -128,7 +154,7 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
|||
param.name,
|
||||
VarDoc {
|
||||
_docs: Some(param.docs),
|
||||
ty: self.check_doc_types(&module, ¶m.types),
|
||||
ty: self.check_type_strings(&module, ¶m.types),
|
||||
_default: param.default,
|
||||
},
|
||||
);
|
||||
|
@ -136,7 +162,7 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
|||
|
||||
let res_ty = converted
|
||||
.return_ty
|
||||
.and_then(|ty| self.check_doc_types(&module, &ty));
|
||||
.and_then(|ty| self.check_type_strings(&module, &ty));
|
||||
|
||||
Some(DocString {
|
||||
docs: Some(converted.docs),
|
||||
|
@ -146,10 +172,37 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
|||
})
|
||||
}
|
||||
|
||||
fn check_doc_types(&mut self, m: &Module, strs: &str) -> Option<Ty> {
|
||||
pub fn check_var_docs(mut self, docs: String) -> Option<DocString> {
|
||||
let converted = convert_docs(self.ctx.world(), &docs).ok()?;
|
||||
let converted = identify_var_docs(&converted).ok()?;
|
||||
let module = self.ctx.module_by_str(docs)?;
|
||||
|
||||
let res_ty = converted
|
||||
.return_ty
|
||||
.and_then(|ty| self.check_type_strings(&module, &ty));
|
||||
|
||||
Some(DocString {
|
||||
docs: Some(converted.docs),
|
||||
var_bounds: self.vars,
|
||||
vars: HashMap::new(),
|
||||
res_ty,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_var(&mut self, name: StrRef) -> Ty {
|
||||
self.next_id += 1;
|
||||
let encoded = DefId(self.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.vars.insert(encoded, bounds);
|
||||
var
|
||||
}
|
||||
|
||||
fn check_type_strings(&mut self, m: &Module, strs: &str) -> 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 {
|
||||
let Some(ty) = self.check_type_ident(m, name) else {
|
||||
continue;
|
||||
};
|
||||
types.push(ty);
|
||||
|
@ -158,7 +211,7 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
|||
Some(Ty::from_types(types.into_iter()))
|
||||
}
|
||||
|
||||
fn check_doc_type_ident(&mut self, m: &Module, name: &str) -> Option<Ty> {
|
||||
fn check_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(
|
||||
[
|
||||
|
@ -215,11 +268,13 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
|||
});
|
||||
|
||||
let builtin_ty = TYPE_REPRS.get(name).cloned();
|
||||
builtin_ty.or_else(|| self.check_doc_type_anno(m, name))
|
||||
builtin_ty
|
||||
.or_else(|| self.locals.get(name).cloned())
|
||||
.or_else(|| self.check_type_annotation(m, name))
|
||||
}
|
||||
|
||||
fn check_doc_type_anno(&mut self, m: &Module, name: &str) -> Option<Ty> {
|
||||
if let Some(v) = self.docs_scope.get(name) {
|
||||
fn check_type_annotation(&mut self, m: &Module, name: &str) -> Option<Ty> {
|
||||
if let Some(v) = self.globals.get(name) {
|
||||
return v.clone();
|
||||
}
|
||||
|
||||
|
@ -230,69 +285,85 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
|||
let text = annotated.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());
|
||||
let ret = self.check_type_expr(m, exprs.next()?);
|
||||
self.globals.insert(name.into(), ret.clone());
|
||||
ret
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_var(&mut self, name: StrRef) -> Ty {
|
||||
self.next_id += 1;
|
||||
let encoded = DefId(self.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.vars.insert(encoded, bounds);
|
||||
var
|
||||
}
|
||||
|
||||
fn check_doc_type_expr(&mut self, m: &Module, s: ast::Expr) -> Option<Ty> {
|
||||
fn check_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::Ident(i) => self.check_type_ident(m, i.get().as_str()),
|
||||
ast::Expr::FuncCall(c) => match c.callee() {
|
||||
ast::Expr::Ident(i) => {
|
||||
let name = i.get().as_str();
|
||||
match name {
|
||||
"array" => Some({
|
||||
let ast::Arg::Pos(pos) = c.args().items().next()? else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Ty::Array(self.check_type_expr(m, pos)?.into())
|
||||
}),
|
||||
"tag" => Some({
|
||||
let ast::Arg::Pos(ast::Expr::Str(s)) = c.args().items().next()? else {
|
||||
return None;
|
||||
};
|
||||
let pkg_id = PackageId::try_from(self.fid).ok();
|
||||
Ty::Builtin(BuiltinTy::Tag(s.get().into(), pkg_id.map(From::from)))
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
ast::Expr::Closure(c) => {
|
||||
log::debug!("check doc closure annotation: {c:?}");
|
||||
let mut pos = vec![];
|
||||
let mut named = BTreeMap::new();
|
||||
let mut rest = None;
|
||||
let snap = self.locals.snapshot();
|
||||
|
||||
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.docs_scope.insert(name, Some(rest_ty.clone()));
|
||||
rest = Some(rest_ty);
|
||||
let sig = None.or_else(|| {
|
||||
for param in c.params().children() {
|
||||
match param {
|
||||
ast::Param::Pos(ast::Pattern::Normal(ast::Expr::Ident(i))) => {
|
||||
let name = i.get().clone();
|
||||
let base_ty = self.generate_var(name.as_str().into());
|
||||
self.locals.insert(name, base_ty.clone());
|
||||
pos.push(base_ty);
|
||||
}
|
||||
ast::Param::Pos(_) => {
|
||||
pos.push(Ty::Any);
|
||||
}
|
||||
ast::Param::Named(e) => {
|
||||
let exp = self.check_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.generate_var(name.as_str().into());
|
||||
self.locals.insert(name, 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();
|
||||
let body = self.check_type_expr(m, c.body())?;
|
||||
let sig = SigTy::new(pos, named, rest, Some(body)).into();
|
||||
|
||||
Some(Ty::Func(sig))
|
||||
Some(Ty::Func(sig))
|
||||
});
|
||||
|
||||
self.locals.rollback_to(snap);
|
||||
sig
|
||||
}
|
||||
ast::Expr::Dict(d) => {
|
||||
log::debug!("check doc dict annotation: {d:?}");
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::{collections::BTreeMap, sync::LazyLock};
|
||||
|
||||
use ecow::eco_vec;
|
||||
use typst::{
|
||||
foundations::Value,
|
||||
syntax::{
|
||||
|
@ -371,11 +372,11 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
}
|
||||
|
||||
fn check_closure(&mut self, root: LinkedNode<'_>) -> Option<Ty> {
|
||||
let dostring = self.check_closure_docs(&root);
|
||||
let dostring = dostring.as_ref().unwrap_or(&EMPTY_DOCSTRING);
|
||||
let docstring = self.check_func_docs(&root);
|
||||
let docstring = docstring.as_ref().unwrap_or(&EMPTY_DOCSTRING);
|
||||
let closure: ast::Closure = root.cast()?;
|
||||
|
||||
log::debug!("check closure: {:?} -> {dostring:#?}", closure.name());
|
||||
log::debug!("check closure: {:?} -> {docstring:#?}", closure.name());
|
||||
|
||||
let mut pos = vec![];
|
||||
let mut named = BTreeMap::new();
|
||||
|
@ -385,13 +386,13 @@ 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, dostring, root.clone()));
|
||||
pos.push(self.check_pattern(pattern, Ty::Any, docstring, 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())?)?;
|
||||
if let Some(annotated) = dostring.var_ty(name.as_str()) {
|
||||
if let Some(annotated) = docstring.var_ty(name.as_str()) {
|
||||
self.constrain(&v, annotated);
|
||||
}
|
||||
// todo: this is less efficient than v.lbs.push(exp), we may have some idea to
|
||||
|
@ -405,7 +406,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
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)?)?;
|
||||
if let Some(annotated) = dostring.var_ty(e.get().as_str()) {
|
||||
if let Some(annotated) = docstring.var_ty(e.get().as_str()) {
|
||||
self.constrain(&v, annotated);
|
||||
}
|
||||
self.constrain(&exp, &v);
|
||||
|
@ -417,7 +418,16 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
}
|
||||
|
||||
let body = self.check_expr_in(closure.body().span(), root);
|
||||
let _ = dostring.res_ty;
|
||||
// let res_ty = docstring.res_ty.clone().unwrap_or(body);
|
||||
let res_ty = if let Some(annotated) = &docstring.res_ty {
|
||||
self.constrain(&body, annotated);
|
||||
Ty::Let(Interned::new(TypeBounds {
|
||||
lbs: eco_vec![body],
|
||||
ubs: eco_vec![annotated.clone()],
|
||||
}))
|
||||
} else {
|
||||
body
|
||||
};
|
||||
|
||||
let named: Vec<(Interned<str>, Ty)> = named.into_iter().collect();
|
||||
|
||||
|
@ -432,7 +442,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
self.weaken(rest);
|
||||
}
|
||||
|
||||
let sig = SigTy::new(pos, named, rest, Some(body)).into();
|
||||
let sig = SigTy::new(pos, named, rest, Some(res_ty)).into();
|
||||
let sig = Ty::Func(sig);
|
||||
if defaults.is_empty() {
|
||||
return Some(sig);
|
||||
|
@ -462,13 +472,19 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
// todo lbs is the lexical signature.
|
||||
}
|
||||
ast::LetBindingKind::Normal(pattern) => {
|
||||
// let _name = let_binding.name().get().to_string();
|
||||
let docstring = self.check_var_docs(&root);
|
||||
let docstring = docstring.as_ref().unwrap_or(&EMPTY_DOCSTRING);
|
||||
|
||||
let value = let_binding
|
||||
.init()
|
||||
.map(|init| self.check_expr_in(init.span(), root.clone()))
|
||||
.unwrap_or_else(|| Ty::Builtin(BuiltinTy::Infer));
|
||||
if let Some(annotated) = &docstring.res_ty {
|
||||
self.constrain(&value, annotated);
|
||||
}
|
||||
let value = docstring.res_ty.clone().unwrap_or(value);
|
||||
|
||||
self.check_pattern(pattern, value, &EMPTY_DOCSTRING, root.clone());
|
||||
self.check_pattern(pattern, value, docstring, root.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@ impl From<(PathBuf, PackageSpec)> for PackageInfo {
|
|||
pub enum DocStringKind {
|
||||
/// A docstring for a function.
|
||||
Function,
|
||||
/// A docstring for a variable.
|
||||
Variable,
|
||||
}
|
||||
|
||||
/// Docs about a symbol.
|
||||
|
@ -428,7 +430,7 @@ pub(crate) fn convert_docs(world: &LspWorld, content: &str) -> StrResult<EcoStri
|
|||
fn identify_docs(kind: &str, content: &str) -> StrResult<Docs> {
|
||||
match kind {
|
||||
"function" => identify_func_docs(content).map(Docs::Function),
|
||||
"variable" => identify_tidy_var_docs(content).map(Docs::Variable),
|
||||
"variable" => identify_var_docs(content).map(Docs::Variable),
|
||||
"module" => identify_tidy_module_docs(content).map(Docs::Module),
|
||||
_ => Err(eco_format!("unknown kind {kind}")),
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn identify_tidy_var_docs(converted: &str) -> StrResult<TidyVarDocs> {
|
||||
pub fn identify_var_docs(converted: &str) -> StrResult<TidyVarDocs> {
|
||||
let lines = converted.lines().collect::<Vec<_>>();
|
||||
|
||||
let mut return_ty = None;
|
||||
|
@ -232,7 +232,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn var(s: &str) -> String {
|
||||
let f = super::identify_tidy_var_docs(s).unwrap();
|
||||
let f = super::identify_var_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"));
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#let tmpl2(stroke) = text(stroke: stroke)
|
||||
#tmpl2(/* position */)
|
||||
#tmpl2(/* position */)
|
|
@ -0,0 +1,6 @@
|
|||
/// #let fn = `('T, x: 'T, y: 'T) => 'T`
|
||||
/// #let args = `resultOf(fn)`
|
||||
///
|
||||
/// - fn (function, fn): The `fn`.
|
||||
/// - args (args, args): The `args`.
|
||||
#let fold(fn, ..args) = none
|
|
@ -1,7 +1,5 @@
|
|||
/// #let fn = `(..fn-args) => any`;
|
||||
///
|
||||
/// - fn (function, fn): The `fn`.
|
||||
/// - fn (function): The `fn`.
|
||||
/// - max-repetitions (int): The `max-repetitions`.
|
||||
/// - repetitions (int): The `repetitions`.
|
||||
/// - args (any, fn-args): The `args`.
|
||||
/// - args (any): The `args`.
|
||||
#let touying-fn-wrapper(fn, max-repetitions: none, repetitions: none, ..args) = none
|
|
@ -0,0 +1,5 @@
|
|||
/// #let fn = `(..args) => any`;
|
||||
///
|
||||
/// - fn (function, fn): The `fn`.
|
||||
/// - args (any): The `args`.
|
||||
#let fn-wrapper(fn, ..args) = none
|
|
@ -0,0 +1,3 @@
|
|||
/// #let args = `array(int)`
|
||||
/// - args (args): The `args`.
|
||||
#let sum(..args) = none
|
|
@ -0,0 +1,5 @@
|
|||
/// #let tag-full = `tag("configs", ns: "@preview/touying")`
|
||||
/// #let tag = `tag("configs")`
|
||||
///
|
||||
/// -> tag
|
||||
#let config-common() = none
|
|
@ -0,0 +1,4 @@
|
|||
/// #let m = `(x, y) => x`;
|
||||
///
|
||||
/// -> function, m
|
||||
#let mapper = (x, f) => x.map(f);
|
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
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
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
expression: result
|
||||
input_file: crates/tinymist-query/src/fixtures/type_check/annotation_fn.typ
|
||||
---
|
||||
"args" = Args
|
||||
"fn" = Any
|
||||
"max-repetitions" = None
|
||||
"repetitions" = None
|
||||
"touying-fn-wrapper" = ((( ⪯ Type(function)), "max-repetitions": ( ⪯ Type(integer)), "repetitions": ( ⪯ Type(integer)), ...: ( ⪯ Any)) => None).with(..("max-repetitions": None, "repetitions": None) => any)
|
||||
---
|
||||
162..180 -> @touying-fn-wrapper
|
||||
181..183 -> @fn
|
||||
185..200 -> @max-repetitions
|
||||
208..219 -> @repetitions
|
||||
229..233 -> @args
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
expression: result
|
||||
input_file: crates/tinymist-query/src/fixtures/type_check/annotation_fn2.typ
|
||||
---
|
||||
"args" = Args
|
||||
"args" = Any
|
||||
"fn" = Any
|
||||
"fn-wrapper" = (( ⪯ (Type(function) | (...: Any) => Any)), ...: ( ⪯ Any)) => None
|
||||
---
|
||||
107..117 -> @fn-wrapper
|
||||
118..120 -> @fn
|
||||
124..128 -> @args
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
expression: result
|
||||
input_file: crates/tinymist-query/src/fixtures/type_check/annotation_sum.typ
|
||||
---
|
||||
"args" = Args
|
||||
"sum" = (...: ( ⪯ Array<Type(integer)>)) => None
|
||||
---
|
||||
65..68 -> @sum
|
||||
71..75 -> @args
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
expression: result
|
||||
input_file: crates/tinymist-query/src/fixtures/type_check/annotation_tag.typ
|
||||
---
|
||||
"config-common" = () => None
|
||||
---
|
||||
113..126 -> @config-common
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
expression: result
|
||||
input_file: crates/tinymist-query/src/fixtures/type_check/annotation_var.typ
|
||||
---
|
||||
"f" = Any
|
||||
"mapper" = (Type(function) | (Any, Any) => Any)
|
||||
"x" = Any
|
||||
"x" = Any
|
||||
"y" = Any
|
||||
---
|
||||
56..62 -> @mapper
|
||||
66..67 -> @x
|
||||
69..70 -> @f
|
||||
75..76 -> @x
|
||||
75..83 -> Any
|
||||
81..82 -> @f
|
|
@ -1,5 +1,6 @@
|
|||
use core::fmt;
|
||||
|
||||
use crate::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::RegexSet;
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
|
@ -165,6 +166,33 @@ pub enum BuiltinSig<'a> {
|
|||
TupleAt(&'a Ty),
|
||||
}
|
||||
|
||||
/// A package identifier.
|
||||
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct PackageId {
|
||||
pub namespace: StrRef,
|
||||
pub name: StrRef,
|
||||
}
|
||||
|
||||
impl fmt::Debug for PackageId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "@{}/{}", self.namespace, self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TypstFileId> for PackageId {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: TypstFileId) -> Result<Self, Self::Error> {
|
||||
let Some(spec) = value.package() else {
|
||||
return Err(());
|
||||
};
|
||||
Ok(PackageId {
|
||||
namespace: spec.namespace.as_str().into(),
|
||||
name: spec.name.as_str().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum BuiltinTy {
|
||||
Clause,
|
||||
|
@ -195,6 +223,7 @@ pub enum BuiltinTy {
|
|||
Outset,
|
||||
Radius,
|
||||
|
||||
Tag(StrRef, Option<Interned<PackageId>>),
|
||||
Type(typst::foundations::Type),
|
||||
Element(typst::foundations::Element),
|
||||
Path(PathPreference),
|
||||
|
@ -230,6 +259,13 @@ impl fmt::Debug for BuiltinTy {
|
|||
BuiltinTy::Radius => write!(f, "Radius"),
|
||||
BuiltinTy::Type(ty) => write!(f, "Type({})", ty.long_name()),
|
||||
BuiltinTy::Element(e) => e.fmt(f),
|
||||
BuiltinTy::Tag(tag, id) => {
|
||||
if let Some(id) = id {
|
||||
write!(f, "Tag({tag:?}) of {id:?}")
|
||||
} else {
|
||||
write!(f, "Tag({tag:?})")
|
||||
}
|
||||
}
|
||||
BuiltinTy::Path(p) => write!(f, "Path({p:?})"),
|
||||
}
|
||||
}
|
||||
|
@ -270,8 +306,8 @@ impl BuiltinTy {
|
|||
BuiltinTy::Type(builtin).literally()
|
||||
}
|
||||
|
||||
pub(crate) fn describe(&self) -> &'static str {
|
||||
match self {
|
||||
pub(crate) fn describe(&self) -> String {
|
||||
let res = match self {
|
||||
BuiltinTy::Clause => "any",
|
||||
BuiltinTy::Undef => "any",
|
||||
BuiltinTy::Content => "content",
|
||||
|
@ -299,6 +335,13 @@ impl BuiltinTy {
|
|||
BuiltinTy::Radius => "radius",
|
||||
BuiltinTy::Type(ty) => ty.short_name(),
|
||||
BuiltinTy::Element(ty) => ty.name(),
|
||||
BuiltinTy::Tag(name, id) => {
|
||||
return if let Some(id) = id {
|
||||
format!("tag {name} of {id:?}")
|
||||
} else {
|
||||
format!("tag {name}")
|
||||
}
|
||||
}
|
||||
BuiltinTy::Path(s) => match s {
|
||||
PathPreference::None => "[any]",
|
||||
PathPreference::Special => "[any]",
|
||||
|
@ -314,7 +357,9 @@ impl BuiltinTy {
|
|||
PathPreference::RawTheme => "[theme]",
|
||||
PathPreference::RawSyntax => "[syntax]",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
res.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,15 +18,15 @@ use typst::{
|
|||
syntax::{ast, Span, SyntaxKind, SyntaxNode},
|
||||
};
|
||||
|
||||
use super::PackageId;
|
||||
use crate::{
|
||||
adt::{interner::impl_internable, snapshot_map},
|
||||
analysis::BuiltinTy,
|
||||
};
|
||||
|
||||
pub use tinymist_derive::BindTyCtx;
|
||||
|
||||
pub(crate) use super::{TyCtx, TyCtxMut};
|
||||
pub(crate) use crate::adt::interner::Interned;
|
||||
pub use tinymist_derive::BindTyCtx;
|
||||
|
||||
/// A reference to the interned type
|
||||
pub(crate) type TyRef = Interned<Ty>;
|
||||
|
@ -1123,6 +1123,7 @@ impl_internable!(IfTy,);
|
|||
impl_internable!(Vec<Ty>,);
|
||||
impl_internable!(TypeBounds,);
|
||||
impl_internable!(NameBone,);
|
||||
impl_internable!(PackageId,);
|
||||
impl_internable!((Ty, Ty),);
|
||||
|
||||
struct RefDebug<'a>(&'a Ty);
|
||||
|
|
|
@ -157,7 +157,7 @@ impl TypeDescriber {
|
|||
return b.to_string();
|
||||
}
|
||||
Ty::Builtin(b) => {
|
||||
return b.describe().to_string();
|
||||
return b.describe();
|
||||
}
|
||||
Ty::Value(v) => return v.val.repr().to_string(),
|
||||
Ty::Field(..) => {
|
||||
|
|
|
@ -742,6 +742,7 @@ fn type_completion(
|
|||
BuiltinTy::Content => return None,
|
||||
BuiltinTy::Infer => return None,
|
||||
BuiltinTy::FlowNone => return None,
|
||||
BuiltinTy::Tag(..) => return None,
|
||||
|
||||
BuiltinTy::Path(p) => {
|
||||
let source = ctx.ctx.source_by_id(ctx.root.span().id()?).ok()?;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue