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:
Myriad-Dreamin 2024-10-15 21:04:35 +08:00 committed by GitHub
parent 4aeb3747bc
commit c17e0c787e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 312 additions and 107 deletions

View file

@ -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
};

View file

@ -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, &param.types),
ty: self.check_type_strings(&module, &param.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:?}");

View file

@ -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());
}
}

View file

@ -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}")),
}

View file

@ -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"));

View file

@ -1,2 +1,2 @@
#let tmpl2(stroke) = text(stroke: stroke)
#tmpl2(/* position */)
#tmpl2(/* position */)

View file

@ -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

View file

@ -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

View file

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

View file

@ -0,0 +1,3 @@
/// #let args = `array(int)`
/// - args (args): The `args`.
#let sum(..args) = none

View file

@ -0,0 +1,5 @@
/// #let tag-full = `tag("configs", ns: "@preview/touying")`
/// #let tag = `tag("configs")`
///
/// -> tag
#let config-common() = none

View file

@ -0,0 +1,4 @@
/// #let m = `(x, y) => x`;
///
/// -> function, m
#let mapper = (x, f) => x.map(f);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

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_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

View file

@ -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()
}
}

View file

@ -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);

View file

@ -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(..) => {

View file

@ -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()?;