refactor: move expr and ty defs to analysis crate (#1633)

This commit is contained in:
Myriad-Dreamin 2025-04-08 05:50:55 +08:00 committed by GitHub
parent 72e33e461d
commit ac506dcc31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 968 additions and 890 deletions

18
Cargo.lock generated
View file

@ -4086,15 +4086,31 @@ dependencies = [
name = "tinymist-analysis"
version = "0.13.10"
dependencies = [
"comemo",
"dashmap",
"ecow",
"ena",
"hashbrown 0.14.5",
"if_chain",
"insta",
"itertools 0.13.0",
"log",
"lsp-types",
"parking_lot",
"regex",
"rpds",
"rustc-hash 2.1.1",
"serde",
"serde_yaml",
"strum",
"tinymist-derive",
"tinymist-std",
"tinymist-world",
"toml",
"triomphe",
"typst",
"typst-shim",
"unscanny",
]
[[package]]
@ -4226,7 +4242,6 @@ dependencies = [
"dirs",
"ecow",
"ena",
"hashbrown 0.14.5",
"hex",
"if_chain",
"indexmap 2.8.0",
@ -4255,7 +4270,6 @@ dependencies = [
"tinymist-std",
"tinymist-world",
"toml",
"triomphe",
"ttf-parser",
"typlite",
"typst",

View file

@ -13,13 +13,30 @@ repository.workspace = true
rust-version.workspace = true
[dependencies]
comemo.workspace = true
dashmap.workspace = true
ecow.workspace = true
ena.workspace = true
hashbrown.workspace = true
if_chain.workspace = true
itertools.workspace = true
log.workspace = true
lsp-types.workspace = true
parking_lot.workspace = true
regex.workspace = true
rpds.workspace = true
rustc-hash.workspace = true
serde.workspace = true
serde_yaml.workspace = true
strum.workspace = true
toml.workspace = true
tinymist-derive.workspace = true
tinymist-std.workspace = true
tinymist-world.workspace = true
toml.workspace = true
triomphe.workspace = true
typst.workspace = true
typst-shim.workspace = true
unscanny.workspace = true
[dev-dependencies]
insta.workspace = true

View file

@ -341,8 +341,7 @@ impl<T: Display + Internable + ?Sized> Display for Interned<T> {
}
}
pub(crate) static MAPS: Mutex<EcoVec<(&'static str, usize, Arc<AllocStats>)>> =
Mutex::new(EcoVec::new());
pub static MAPS: Mutex<EcoVec<(&'static str, usize, Arc<AllocStats>)>> = Mutex::new(EcoVec::new());
pub struct InternStorage<T: ?Sized> {
alloc: OnceLock<Arc<AllocStats>>,
@ -407,6 +406,6 @@ macro_rules! _impl_internable {
}
pub use crate::_impl_internable as impl_internable;
use crate::analysis::AllocStats;
use crate::stats::AllocStats;
impl_internable!(str,);

View file

@ -0,0 +1,4 @@
#![allow(missing_docs)]
pub mod interner;
pub mod snapshot_map;

View file

@ -0,0 +1,6 @@
#![allow(missing_docs)]
mod def;
pub use def::*;
mod tidy;
pub use tidy::*;

View file

@ -0,0 +1,296 @@
use core::fmt;
use std::collections::BTreeMap;
use std::sync::OnceLock;
use ecow::{eco_format, EcoString};
use serde::{Deserialize, Serialize};
use super::tidy::*;
use crate::ty::{Interned, ParamAttrs, ParamTy, Ty};
use crate::upstream::plain_docs_sentence;
type TypeRepr = Option<(
/* short */ EcoString,
/* long */ EcoString,
/* value */ EcoString,
)>;
/// Documentation about a definition (without type information).
pub type UntypedDefDocs = DefDocsT<()>;
/// Documentation about a definition.
pub type DefDocs = DefDocsT<TypeRepr>;
/// Documentation about a definition.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum DefDocsT<T> {
/// Documentation about a function.
#[serde(rename = "func")]
Function(Box<SignatureDocsT<T>>),
/// Documentation about a variable.
#[serde(rename = "var")]
Variable(VarDocsT<T>),
/// Documentation about a module.
#[serde(rename = "module")]
Module(TidyModuleDocs),
/// Other kinds of documentation.
#[serde(rename = "plain")]
Plain {
/// The content of the documentation.
docs: EcoString,
},
}
impl<T> DefDocsT<T> {
/// Get the markdown representation of the documentation.
pub fn docs(&self) -> &EcoString {
match self {
Self::Function(docs) => &docs.docs,
Self::Variable(docs) => &docs.docs,
Self::Module(docs) => &docs.docs,
Self::Plain { docs } => docs,
}
}
}
impl DefDocs {
/// Get full documentation for the signature.
pub fn hover_docs(&self) -> EcoString {
match self {
DefDocs::Function(docs) => docs.hover_docs().clone(),
_ => plain_docs_sentence(self.docs()),
}
}
}
/// Describes a primary function signature.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignatureDocsT<T> {
/// Documentation for the function.
pub docs: EcoString,
/// The positional parameters.
pub pos: Vec<ParamDocsT<T>>,
/// The named parameters.
pub named: BTreeMap<Interned<str>, ParamDocsT<T>>,
/// The rest parameter.
pub rest: Option<ParamDocsT<T>>,
/// The return type.
pub ret_ty: T,
/// The full documentation for the signature.
#[serde(skip)]
pub hover_docs: OnceLock<EcoString>,
}
impl SignatureDocsT<TypeRepr> {
/// Get full documentation for the signature.
pub fn hover_docs(&self) -> &EcoString {
self.hover_docs
.get_or_init(|| plain_docs_sentence(&format!("{}", SigHoverDocs(self))))
}
}
struct SigHoverDocs<'a>(&'a SignatureDocs);
impl fmt::Display for SigHoverDocs<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let docs = self.0;
let base_docs = docs.docs.trim();
if !base_docs.is_empty() {
f.write_str(base_docs)?;
}
fn write_param_docs(
f: &mut fmt::Formatter<'_>,
docs: &ParamDocsT<TypeRepr>,
kind: &str,
is_first: &mut bool,
) -> fmt::Result {
if *is_first {
*is_first = false;
write!(f, "\n\n## {}\n\n", docs.name)?;
} else {
write!(f, "\n\n## {} ({kind})\n\n", docs.name)?;
}
// p.cano_type.0
if let Some(t) = &docs.cano_type {
write!(f, "```typc\ntype: {}\n```\n\n", t.2)?;
}
f.write_str(docs.docs.trim())?;
Ok(())
}
if !docs.pos.is_empty() {
f.write_str("\n\n# Positional Parameters")?;
let mut is_first = true;
for pos_docs in &docs.pos {
write_param_docs(f, pos_docs, "positional", &mut is_first)?;
}
}
if docs.rest.is_some() {
f.write_str("\n\n# Rest Parameters")?;
let mut is_first = true;
if let Some(rest) = &docs.rest {
write_param_docs(f, rest, "spread right", &mut is_first)?;
}
}
if !docs.named.is_empty() {
f.write_str("\n\n# Named Parameters")?;
let mut is_first = true;
for named_docs in docs.named.values() {
write_param_docs(f, named_docs, "named", &mut is_first)?;
}
}
Ok(())
}
}
/// Documentation about a signature.
pub type UntypedSignatureDocs = SignatureDocsT<()>;
/// Documentation about a signature.
pub type SignatureDocs = SignatureDocsT<TypeRepr>;
impl SignatureDocs {
/// Get the markdown representation of the documentation.
pub fn print(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
let mut is_first = true;
let mut write_sep = |f: &mut dyn std::fmt::Write| {
if is_first {
is_first = false;
return f.write_str("\n ");
}
f.write_str(",\n ")
};
f.write_char('(')?;
for pos_docs in &self.pos {
write_sep(f)?;
f.write_str(&pos_docs.name)?;
if let Some(t) = &pos_docs.cano_type {
write!(f, ": {}", t.0)?;
}
}
if let Some(rest) = &self.rest {
write_sep(f)?;
f.write_str("..")?;
f.write_str(&rest.name)?;
if let Some(t) = &rest.cano_type {
write!(f, ": {}", t.0)?;
}
}
if !self.named.is_empty() {
let mut name_prints = vec![];
for v in self.named.values() {
let ty = v.cano_type.as_ref().map(|t| &t.0);
name_prints.push((v.name.clone(), ty, v.default.clone()))
}
name_prints.sort();
for (name, ty, val) in name_prints {
write_sep(f)?;
let val = val.as_deref().unwrap_or("any");
let mut default = val.trim();
if default.starts_with('{') && default.ends_with('}') && default.len() > 30 {
default = "{ .. }"
}
if default.starts_with('`') && default.ends_with('`') && default.len() > 30 {
default = "raw"
}
if default.starts_with('[') && default.ends_with(']') && default.len() > 30 {
default = "content"
}
f.write_str(&name)?;
if let Some(ty) = ty {
write!(f, ": {ty}")?;
}
if default.contains('\n') {
write!(f, " = {}", default.replace("\n", "\n "))?;
} else {
write!(f, " = {default}")?;
}
}
}
if !is_first {
f.write_str(",\n")?;
}
f.write_char(')')?;
Ok(())
}
}
/// Documentation about a variable (without type information).
pub type UntypedVarDocs = VarDocsT<()>;
/// Documentation about a variable.
pub type VarDocs = VarDocsT<Option<(EcoString, EcoString, EcoString)>>;
/// Describes a primary pattern binding.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VarDocsT<T> {
/// Documentation for the pattern binding.
pub docs: EcoString,
/// The inferred type of the pattern binding source.
pub return_ty: T,
/// Cached documentation for the definition.
#[serde(skip)]
pub def_docs: OnceLock<String>,
}
impl VarDocs {
/// Get the markdown representation of the documentation.
pub fn def_docs(&self) -> &String {
self.def_docs
.get_or_init(|| plain_docs_sentence(&self.docs).into())
}
}
/// Documentation about a parameter (without type information).
pub type TypelessParamDocs = ParamDocsT<()>;
/// Documentation about a parameter.
pub type ParamDocs = ParamDocsT<TypeRepr>;
/// Describes a function parameter.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ParamDocsT<T> {
/// The parameter's name.
pub name: Interned<str>,
/// Documentation for the parameter.
pub docs: EcoString,
/// Inferred type of the parameter.
pub cano_type: T,
/// The parameter's default name as value.
pub default: Option<EcoString>,
/// The attribute of the parameter.
#[serde(flatten)]
pub attrs: ParamAttrs,
}
impl ParamDocs {
pub fn new(param: &ParamTy, ty: Option<&Ty>) -> Self {
Self {
name: param.name.as_ref().into(),
docs: param.docs.clone().unwrap_or_default(),
cano_type: format_ty(ty.or(Some(&param.ty))),
default: param.default.clone(),
attrs: param.attrs,
}
}
}
pub fn format_ty(ty: Option<&Ty>) -> TypeRepr {
let ty = ty?;
let short = ty.repr().unwrap_or_else(|| "any".into());
let long = eco_format!("{ty:?}");
let value = ty.value_repr().unwrap_or_else(|| "".into());
Some((short, long, value))
}

View file

@ -1,8 +1,19 @@
//! Tinymist Analysis
pub mod adt;
pub mod docs;
pub mod location;
mod prelude;
mod sig;
pub mod stats;
pub mod syntax;
pub mod ty;
pub mod upstream;
pub use sig::*;
pub use track_values::*;
mod prelude;
mod track_values;
/// Completely disabled log
#[macro_export]

View file

@ -0,0 +1,377 @@
//! Analysis of function signatures.
use core::fmt;
use std::collections::BTreeMap;
use std::sync::Arc;
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use typst::foundations::{Closure, Func};
use typst::syntax::ast::AstNode;
use typst::syntax::{ast, SyntaxKind};
use typst::utils::LazyHash;
// use super::{BoundChecker, Definition};
use crate::ty::{InsTy, ParamTy, SigTy, StrRef, Ty};
use crate::ty::{Interned, ParamAttrs};
use crate::upstream::truncated_repr;
// use crate::upstream::truncated_repr;
/// Describes a function signature.
#[derive(Debug, Clone)]
pub enum Signature {
/// A primary function signature.
Primary(Arc<PrimarySignature>),
/// A partially applied function signature.
Partial(Arc<PartialSignature>),
}
impl Signature {
/// Returns the primary signature if it is one.
pub fn primary(&self) -> &Arc<PrimarySignature> {
match self {
Signature::Primary(sig) => sig,
Signature::Partial(sig) => &sig.signature,
}
}
/// Returns the with bindings of the signature.
pub fn bindings(&self) -> &[ArgsInfo] {
match self {
Signature::Primary(_) => &[],
Signature::Partial(sig) => &sig.with_stack,
}
}
/// Returns the all parameters of the signature.
pub fn params(&self) -> impl Iterator<Item = (&Interned<ParamTy>, Option<&Ty>)> {
let primary = self.primary().params();
// todo: with stack
primary
}
/// Returns the type of the signature.
pub fn type_sig(&self) -> Interned<SigTy> {
let primary = self.primary().sig_ty.clone();
// todo: with stack
primary
}
/// Returns the shift applied to the signature.
pub fn param_shift(&self) -> usize {
match self {
Signature::Primary(_) => 0,
Signature::Partial(sig) => sig
.with_stack
.iter()
.map(|ws| ws.items.len())
.sum::<usize>(),
}
}
}
/// Describes a primary function signature.
#[derive(Debug, Clone)]
pub struct PrimarySignature {
/// The documentation of the function
pub docs: Option<EcoString>,
/// The documentation of the parameter.
pub param_specs: Vec<Interned<ParamTy>>,
/// Whether the function has fill, stroke, or size parameters.
pub has_fill_or_size_or_stroke: bool,
/// The associated signature type.
pub sig_ty: Interned<SigTy>,
/// Whether the signature is broken.
pub _broken: bool,
}
impl PrimarySignature {
/// Returns the number of positional parameters of the function.
pub fn pos_size(&self) -> usize {
self.sig_ty.name_started as usize
}
/// Returns the positional parameters of the function.
pub fn pos(&self) -> &[Interned<ParamTy>] {
&self.param_specs[..self.pos_size()]
}
/// Returns the positional parameters of the function.
pub fn get_pos(&self, offset: usize) -> Option<&Interned<ParamTy>> {
self.pos().get(offset)
}
/// Returns the named parameters of the function.
pub fn named(&self) -> &[Interned<ParamTy>] {
&self.param_specs[self.pos_size()..self.pos_size() + self.sig_ty.names.names.len()]
}
/// Returns the named parameters of the function.
pub fn get_named(&self, name: &StrRef) -> Option<&Interned<ParamTy>> {
self.named().get(self.sig_ty.names.find(name)?)
}
/// Returns the name of the rest parameter of the function.
pub fn has_spread_right(&self) -> bool {
self.sig_ty.spread_right
}
/// Returns the rest parameter of the function.
pub fn rest(&self) -> Option<&Interned<ParamTy>> {
self.has_spread_right()
.then(|| &self.param_specs[self.pos_size() + self.sig_ty.names.names.len()])
}
/// Returns the all parameters of the function.
pub fn params(&self) -> impl Iterator<Item = (&Interned<ParamTy>, Option<&Ty>)> {
let pos = self.pos();
let named = self.named();
let rest = self.rest();
let type_sig = &self.sig_ty;
let pos = pos
.iter()
.enumerate()
.map(|(idx, pos)| (pos, type_sig.pos(idx)));
let named = named.iter().map(|x| (x, type_sig.named(&x.name)));
let rest = rest.into_iter().map(|x| (x, type_sig.rest_param()));
pos.chain(named).chain(rest)
}
}
/// Describes a function argument instance
#[derive(Debug, Clone)]
pub struct ArgInfo {
/// The argument's name.
pub name: Option<StrRef>,
/// The argument's term.
pub term: Option<Ty>,
}
/// Describes a function argument list.
#[derive(Debug, Clone)]
pub struct ArgsInfo {
/// The arguments.
pub items: EcoVec<ArgInfo>,
}
/// Describes a function signature that is already partially applied.
#[derive(Debug, Clone)]
pub struct PartialSignature {
/// The positional parameters.
pub signature: Arc<PrimarySignature>,
/// The stack of `fn.with(..)` calls.
pub with_stack: EcoVec<ArgsInfo>,
}
/// Gets the signature of a function.
#[comemo::memoize]
pub fn func_signature(func: Func) -> Signature {
use typst::foundations::func::Repr;
let mut with_stack = eco_vec![];
let mut func = func;
while let Repr::With(with) = func.inner() {
let (inner, args) = with.as_ref();
with_stack.push(ArgsInfo {
items: args
.items
.iter()
.map(|arg| ArgInfo {
name: arg.name.clone().map(From::from),
term: Some(Ty::Value(InsTy::new(arg.value.v.clone()))),
})
.collect(),
});
func = inner.clone();
}
let mut pos_tys = vec![];
let mut named_tys = Vec::new();
let mut rest_ty = None;
let mut named_specs = BTreeMap::new();
let mut param_specs = Vec::new();
let mut rest_spec = None;
let mut broken = false;
let mut has_fill_or_size_or_stroke = false;
let mut add_param = |param: Interned<ParamTy>| {
let name = param.name.clone();
if param.attrs.named {
if matches!(name.as_ref(), "fill" | "stroke" | "size") {
has_fill_or_size_or_stroke = true;
}
named_tys.push((name.clone(), param.ty.clone()));
named_specs.insert(name.clone(), param.clone());
}
if param.attrs.variadic {
if rest_ty.is_some() {
broken = true;
} else {
rest_ty = Some(param.ty.clone());
rest_spec = Some(param);
}
} else if param.attrs.positional {
// todo: we have some params that are both positional and named
pos_tys.push(param.ty.clone());
param_specs.push(param);
}
};
let ret_ty = match func.inner() {
Repr::With(..) => unreachable!(),
Repr::Closure(closure) => {
analyze_closure_signature(closure.clone(), &mut add_param);
None
}
Repr::Element(..) | Repr::Native(..) | Repr::Plugin(..) => {
for param in func.params().unwrap_or_default() {
add_param(Interned::new(ParamTy {
name: param.name.into(),
docs: Some(param.docs.into()),
default: param.default.map(|default| truncated_repr(&default())),
ty: Ty::from_param_site(&func, param),
attrs: param.into(),
}));
}
func.returns().map(|r| Ty::from_return_site(&func, r))
}
};
let sig_ty = SigTy::new(pos_tys.into_iter(), named_tys, None, rest_ty, ret_ty);
for name in &sig_ty.names.names {
let Some(param) = named_specs.get(name) else {
continue;
};
param_specs.push(param.clone());
}
if let Some(doc) = rest_spec {
param_specs.push(doc);
}
let signature = Arc::new(PrimarySignature {
docs: func.docs().map(From::from),
param_specs,
has_fill_or_size_or_stroke,
sig_ty: sig_ty.into(),
_broken: broken,
});
log::trace!("got signature {signature:?}");
if with_stack.is_empty() {
return Signature::Primary(signature);
}
Signature::Partial(Arc::new(PartialSignature {
signature,
with_stack,
}))
}
fn analyze_closure_signature(
closure: Arc<LazyHash<Closure>>,
add_param: &mut impl FnMut(Interned<ParamTy>),
) {
log::trace!("closure signature for: {:?}", closure.node.kind());
let closure = &closure.node;
let closure_ast = match closure.kind() {
SyntaxKind::Closure => closure.cast::<ast::Closure>().unwrap(),
_ => return,
};
for param in closure_ast.params().children() {
match param {
ast::Param::Pos(pos) => {
let name = format!("{}", PatternDisplay(&pos));
add_param(Interned::new(ParamTy {
name: name.as_str().into(),
docs: None,
default: None,
ty: Ty::Any,
attrs: ParamAttrs::positional(),
}));
}
// todo: pattern
ast::Param::Named(named) => {
let default = unwrap_parens(named.expr()).to_untyped().clone().into_text();
add_param(Interned::new(ParamTy {
name: named.name().get().into(),
docs: Some(eco_format!("Default value: {default}")),
default: Some(default),
ty: Ty::Any,
attrs: ParamAttrs::named(),
}));
}
ast::Param::Spread(spread) => {
let sink = spread.sink_ident().map(|sink| sink.as_str());
add_param(Interned::new(ParamTy {
name: sink.unwrap_or_default().into(),
docs: None,
default: None,
ty: Ty::Any,
attrs: ParamAttrs::variadic(),
}));
}
}
}
}
struct PatternDisplay<'a>(&'a ast::Pattern<'a>);
impl fmt::Display for PatternDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
ast::Pattern::Normal(ast::Expr::Ident(ident)) => f.write_str(ident.as_str()),
ast::Pattern::Normal(_) => f.write_str("?"), // unreachable?
ast::Pattern::Placeholder(_) => f.write_str("_"),
ast::Pattern::Parenthesized(paren_expr) => {
write!(f, "{}", PatternDisplay(&paren_expr.pattern()))
}
ast::Pattern::Destructuring(destructing) => {
write!(f, "(")?;
let mut first = true;
for item in destructing.items() {
if first {
first = false;
} else {
write!(f, ", ")?;
}
match item {
ast::DestructuringItem::Pattern(pos) => {
write!(f, "{}", PatternDisplay(&pos))?
}
ast::DestructuringItem::Named(named) => write!(
f,
"{}: {}",
named.name().as_str(),
unwrap_parens(named.expr()).to_untyped().text()
)?,
ast::DestructuringItem::Spread(spread) => write!(
f,
"..{}",
spread
.sink_ident()
.map(|sink| sink.as_str())
.unwrap_or_default()
)?,
}
}
write!(f, ")")?;
Ok(())
}
}
}
}
fn unwrap_parens(mut expr: ast::Expr) -> ast::Expr {
while let ast::Expr::Parenthesized(paren_expr) = expr {
expr = paren_expr.expr();
}
expr
}

View file

@ -0,0 +1,80 @@
//! Tinymist Analysis Statistics
use std::sync::atomic::{AtomicUsize, Ordering};
/// Statistics about the allocation
#[derive(Debug, Default)]
pub struct AllocStats {
/// The number of allocated objects.
pub allocated: AtomicUsize,
/// The number of dropped objects.
pub dropped: AtomicUsize,
}
impl AllocStats {
/// increment the statistics.
pub fn increment(&self) {
self.allocated.fetch_add(1, Ordering::Relaxed);
}
/// decrement the statistics.
pub fn decrement(&self) {
self.dropped.fetch_add(1, Ordering::Relaxed);
}
/// Report the statistics of the allocation.
pub fn report() -> String {
let maps = crate::adt::interner::MAPS.lock().clone();
let mut data = Vec::new();
for (name, sz, map) in maps {
let allocated = map.allocated.load(std::sync::atomic::Ordering::Relaxed);
let dropped = map.dropped.load(std::sync::atomic::Ordering::Relaxed);
let alive = allocated.saturating_sub(dropped);
data.push((name, sz * alive, allocated, dropped, alive));
}
// sort by total
data.sort_by(|x, y| y.4.cmp(&x.4));
// format to html
let mut html = String::new();
html.push_str(r#"<div>
<style>
table.alloc-stats { width: 100%; border-collapse: collapse; }
table.alloc-stats th, table.alloc-stats td { border: 1px solid black; padding: 8px; text-align: center; }
table.alloc-stats th.name-column, table.alloc-stats td.name-column { text-align: left; }
table.alloc-stats tr:nth-child(odd) { background-color: rgba(242, 242, 242, 0.8); }
@media (prefers-color-scheme: dark) {
table.alloc-stats tr:nth-child(odd) { background-color: rgba(50, 50, 50, 0.8); }
}
</style>
<table class="alloc-stats"><tr><th class="name-column">Name</th><th>Alive</th><th>Allocated</th><th>Dropped</th><th>Size</th></tr>"#);
for (name, sz, allocated, dropped, alive) in data {
html.push_str("<tr>");
html.push_str(&format!(r#"<td class="name-column">{name}</td>"#));
html.push_str(&format!("<td>{alive}</td>"));
html.push_str(&format!("<td>{allocated}</td>"));
html.push_str(&format!("<td>{dropped}</td>"));
html.push_str(&format!("<td>{}</td>", human_size(sz)));
html.push_str("</tr>");
}
html.push_str("</table>");
html.push_str("</div>");
html
}
}
fn human_size(size: usize) -> String {
let units = ["B", "KB", "MB", "GB", "TB"];
let mut unit = 0;
let mut size = size as f64;
while size >= 768.0 && unit < units.len() {
size /= 1024.0;
unit += 1;
}
format!("{:.2} {}", size, units[unit])
}

View file

@ -2,9 +2,17 @@
//!
//! This module must hide all **AST details** from the rest of the codebase.
// todo: remove this
#![allow(missing_docs)]
pub mod import;
pub use import::*;
pub mod comment;
pub use comment::*;
pub mod matcher;
pub use matcher::*;
pub mod def;
pub use def::*;
pub(crate) mod repr;
use repr::*;

View file

@ -3,6 +3,7 @@ use std::{collections::BTreeMap, ops::Range};
use serde::{Deserialize, Serialize};
use tinymist_derive::DeclEnum;
use tinymist_std::DefId;
use tinymist_world::package::PackageSpec;
use typst::{
foundations::{Element, Func, Module, Type, Value},
@ -11,7 +12,6 @@ use typst::{
use crate::{
adt::interner::impl_internable,
analysis::SharedContext,
prelude::*,
ty::{InsTy, Interned, SelectTy, Ty, TypeVar},
};
@ -73,13 +73,13 @@ pub enum Expr {
}
impl Expr {
pub(crate) fn repr(&self) -> EcoString {
pub fn repr(&self) -> EcoString {
let mut s = EcoString::new();
let _ = ExprDescriber::new(&mut s).write_expr(self);
s
}
pub(crate) fn span(&self) -> Span {
pub fn span(&self) -> Span {
match self {
Expr::Decl(decl) => decl.span(),
Expr::Select(select) => select.span,
@ -88,7 +88,7 @@ impl Expr {
}
}
pub(crate) fn file_id(&self) -> Option<TypstFileId> {
pub fn file_id(&self) -> Option<TypstFileId> {
match self {
Expr::Decl(decl) => decl.file_id(),
_ => self.span().id(),
@ -409,7 +409,7 @@ impl Decl {
})
}
pub(crate) fn is_def(&self) -> bool {
pub fn is_def(&self) -> bool {
matches!(
self,
Self::Func(..)
@ -452,25 +452,6 @@ impl Decl {
}
}
/// Gets name range of the declaration.
pub fn name_range(&self, ctx: &SharedContext) -> Option<Range<usize>> {
if let Decl::BibEntry(decl) = self {
return Some(decl.at.1.clone());
}
if !self.is_def() {
return None;
}
let span = self.span();
if let Some(range) = span.range() {
return Some(range.clone());
}
let src = ctx.source_by_id(self.file_id()?).ok()?;
src.range(span)
}
/// Gets full range of the declaration.
pub fn full_range(&self) -> Option<Range<usize>> {
if let Decl::BibEntry(decl) = self {
@ -595,8 +576,8 @@ impl fmt::Debug for SpannedDecl {
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct NameRangeDecl {
name: Interned<str>,
at: Box<(TypstFileId, Range<usize>, Option<Range<usize>>)>,
pub name: Interned<str>,
pub at: Box<(TypstFileId, Range<usize>, Option<Range<usize>>)>,
}
impl NameRangeDecl {
@ -724,7 +705,7 @@ impl fmt::Display for Pattern {
}
impl Pattern {
pub(crate) fn repr(&self) -> EcoString {
pub fn repr(&self) -> EcoString {
let mut s = EcoString::new();
let _ = ExprDescriber::new(&mut s).write_pattern(self);
s

View file

@ -109,7 +109,7 @@ impl PathPreference {
}
impl Ty {
pub(crate) fn from_cast_info(ty: &CastInfo) -> Ty {
pub fn from_cast_info(ty: &CastInfo) -> Ty {
match &ty {
CastInfo::Any => Ty::Any,
CastInfo::Value(val, doc) => Ty::Value(InsTy::new_doc(val.clone(), *doc)),
@ -120,7 +120,7 @@ impl Ty {
}
}
pub(crate) fn from_param_site(func: &Func, param: &ParamInfo) -> Ty {
pub fn from_param_site(func: &Func, param: &ParamInfo) -> Ty {
use typst::foundations::func::Repr;
match func.inner() {
Repr::Element(..) | Repr::Native(..) | Repr::Plugin(..) => {

View file

@ -1,4 +1,6 @@
use crate::analysis::func_signature;
use typst::syntax::Span;
use crate::func_signature;
use super::*;

View file

@ -17,10 +17,9 @@ use typst::{
syntax::{ast, FileId, Span, SyntaxKind, SyntaxNode},
};
use super::{BoundPred, PackageId};
use super::{BoundPred, BuiltinTy, PackageId};
use crate::{
adt::{interner::impl_internable, snapshot_map},
analysis::BuiltinTy,
docs::UntypedDefDocs,
syntax::{DeclExpr, UnaryOp},
};
@ -142,7 +141,7 @@ impl Ty {
matches!(self, Ty::Dict(..))
}
pub(crate) fn union(lhs: Option<Ty>, rhs: Option<Ty>) -> Option<Ty> {
pub fn union(lhs: Option<Ty>, rhs: Option<Ty>) -> Option<Ty> {
Some(match (lhs, rhs) {
(Some(lhs), Some(rhs)) => Ty::from_types([lhs, rhs].into_iter()),
(Some(ty), None) | (None, Some(ty)) => ty,
@ -231,11 +230,11 @@ impl Ty {
}
}
pub(crate) fn satisfy<T: TyCtx>(&self, ctx: &T, f: impl FnMut(&Ty, bool)) {
pub fn satisfy<T: TyCtx>(&self, ctx: &T, f: impl FnMut(&Ty, bool)) {
self.bounds(true, &mut BoundPred::new(ctx, f));
}
pub(crate) fn is_content<T: TyCtx>(&self, ctx: &T) -> bool {
pub fn is_content<T: TyCtx>(&self, ctx: &T) -> bool {
let mut res = false;
self.satisfy(ctx, |ty: &Ty, _pol| {
res = res || {
@ -561,7 +560,7 @@ pub struct ParamAttrs {
}
impl ParamAttrs {
pub(crate) fn positional() -> ParamAttrs {
pub fn positional() -> ParamAttrs {
ParamAttrs {
positional: true,
named: false,
@ -570,7 +569,7 @@ impl ParamAttrs {
}
}
pub(crate) fn named() -> ParamAttrs {
pub fn named() -> ParamAttrs {
ParamAttrs {
positional: false,
named: true,
@ -579,7 +578,7 @@ impl ParamAttrs {
}
}
pub(crate) fn variadic() -> ParamAttrs {
pub fn variadic() -> ParamAttrs {
ParamAttrs {
positional: true,
named: false,
@ -835,7 +834,7 @@ impl SigTy {
})
}
pub(crate) fn with_body(mut self, res_ty: Ty) -> Self {
pub fn with_body(mut self, res_ty: Ty) -> Self {
self.body = Some(res_ty);
self
}
@ -964,7 +963,7 @@ impl SigTy {
pub fn matches<'a>(
&'a self,
args: &'a SigTy,
with: Option<&'a Vec<Interned<crate::analysis::SigTy>>>,
with: Option<&'a Vec<Interned<SigTy>>>,
) -> impl Iterator<Item = (&'a Ty, &'a Ty)> + 'a {
let with_len = with
.map(|w| w.iter().map(|w| w.positional_params().len()).sum::<usize>())

View file

@ -3,11 +3,8 @@ use tinymist_std::hash::hash128;
use tinymist_world::vfs::WorkspaceResolver;
use typst::foundations::Repr;
use crate::{
analysis::{is_plain_value, term_value},
ty::prelude::*,
upstream::truncated_repr_,
};
use super::{is_plain_value, term_value};
use crate::{ty::prelude::*, upstream::truncated_repr_};
impl Ty {
/// Describe the given type.

View file

@ -1,8 +1,11 @@
//! Types and type operations for Typst.
#![allow(missing_docs)]
mod apply;
mod bound;
mod builtin;
mod convert;
mod def;
mod describe;
mod iface;
@ -13,14 +16,16 @@ mod sig;
mod simplify;
mod subst;
pub(crate) use apply::*;
pub(crate) use bound::*;
pub(crate) use builtin::*;
pub use apply::*;
pub use bound::*;
pub use builtin::*;
pub use convert::*;
pub use def::*;
pub(crate) use iface::*;
pub(crate) use mutate::*;
pub(crate) use select::*;
pub(crate) use sig::*;
pub use iface::*;
pub use mutate::*;
pub use select::*;
pub use sig::*;
use typst::foundations::{self, Func, Module, Value};
use typst::syntax::FileId;

View file

@ -1,3 +1,5 @@
//! Functions from typst-ide
use std::{collections::HashMap, fmt::Write, sync::LazyLock};
use comemo::Tracked;
@ -354,6 +356,7 @@ pub(crate) fn urlify(title: &str) -> EcoString {
.collect()
}
/// Get the route of a value.
pub fn route_of_value(val: &Value) -> Option<&'static String> {
// ROUTE_MAPS.get(&CatKey::Func(k.clone()))
let key = match val {
@ -396,6 +399,7 @@ pub fn summarize_font_family<'a>(variants: impl Iterator<Item = &'a FontInfo>) -
detail
}
/// Get the representation but truncated to a certain size.
pub fn truncated_repr_<const SZ_LIMIT: usize>(value: &Value) -> EcoString {
use typst::foundations::Repr;
@ -416,6 +420,7 @@ pub fn truncated_repr_<const SZ_LIMIT: usize>(value: &Value) -> EcoString {
}
}
/// Get the representation but truncated to a certain size.
pub fn truncated_repr(value: &Value) -> EcoString {
const _10MB: usize = 100 * 1024 * 1024;
truncated_repr_::<_10MB>(value)

View file

@ -12,7 +12,7 @@ use typst_shim::syntax::LinkedNodeExt;
use typst_shim::utils::{round_2, Numeric};
use super::{plain_docs_sentence, summarize_font_family, truncated_repr};
use crate::analysis::analyze_expr;
use crate::analyze_expr;
/// Describe the item under the cursor.
///

View file

@ -14,43 +14,33 @@ rust-version.workspace = true
[dependencies]
anyhow.workspace = true
comemo.workspace = true
dirs.workspace = true
regex.workspace = true
yaml-rust2.workspace = true
base64.workspace = true
biblatex.workspace = true
serde_yaml.workspace = true
itertools.workspace = true
strum.workspace = true
log.workspace = true
serde.workspace = true
serde_json.workspace = true
parking_lot.workspace = true
comemo.workspace = true
dashmap.workspace = true
dirs.workspace = true
ena.workspace = true
toml.workspace = true
walkdir.workspace = true
indexmap.workspace = true
ecow.workspace = true
siphasher.workspace = true
rpds.workspace = true
rayon.workspace = true
typst.workspace = true
typst-shim.workspace = true
lsp-types.workspace = true
if_chain.workspace = true
itertools.workspace = true
indexmap.workspace = true
log.workspace = true
lsp-types.workspace = true
parking_lot.workspace = true
percent-encoding.workspace = true
unscanny.workspace = true
ttf-parser.workspace = true
rayon.workspace = true
regex.workspace = true
rpds.workspace = true
rust_iso639.workspace = true
rust_iso3166.workspace = true
dashmap.workspace = true
rustc-hash.workspace = true
hashbrown.workspace = true
triomphe.workspace = true
base64.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
siphasher.workspace = true
strum.workspace = true
toml.workspace = true
ttf-parser.workspace = true
typlite.workspace = true
tinymist-world = { workspace = true }
tinymist-project = { workspace = true, features = ["lsp"] }
@ -58,6 +48,12 @@ tinymist-analysis.workspace = true
tinymist-derive.workspace = true
tinymist-std.workspace = true
tinymist-l10n.workspace = true
typst.workspace = true
typst-shim.workspace = true
unscanny.workspace = true
walkdir.workspace = true
yaml-rust2.workspace = true
[dev-dependencies]
insta.workspace = true

View file

@ -1,3 +1,2 @@
pub mod interner;
pub mod revision;
pub mod snapshot_map;
pub use tinymist_analysis::adt::*;

View file

@ -34,8 +34,6 @@ mod tyck;
pub(crate) use crate::ty::*;
pub(crate) use post_tyck::*;
pub(crate) use tyck::*;
pub mod track_values;
pub use track_values::*;
mod prelude;
mod global;

View file

@ -1,7 +1,7 @@
//! Hybrid analysis for function calls.
use super::prelude::*;
use super::{Signature, StrRef};
use super::Signature;
use crate::analysis::{analyze_signature, PrimarySignature, SignatureTarget};
/// Describes kind of a parameter.

View file

@ -10,6 +10,7 @@ use lsp_types::InsertTextFormat;
use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};
use tinymist_analysis::syntax::{bad_completion_cursor, BadCompletionCursor};
use tinymist_analysis::{analyze_labels, func_signature, DynLabel};
use tinymist_derive::BindTyCtx;
use tinymist_project::LspWorld;
use tinymist_std::path::unix_slash;
@ -27,9 +28,7 @@ use typst_shim::{syntax::LinkedNodeExt, utils::hash128};
use unscanny::Scanner;
use crate::adt::interner::Interned;
use crate::analysis::{
analyze_labels, func_signature, BuiltinTy, DynLabel, LocalContext, PathPreference, Ty,
};
use crate::analysis::{BuiltinTy, LocalContext, PathPreference, Ty};
use crate::completion::{
Completion, CompletionCommand, CompletionContextKey, CompletionItem, CompletionKind,
EcoTextEdit, ParsedSnippet, PostfixSnippet, PostfixSnippetScope, PrefixSnippet,

View file

@ -55,6 +55,31 @@ impl Definition {
}
}
trait HasNameRange {
/// Gets name range of the item.
fn name_range(&self, ctx: &SharedContext) -> Option<Range<usize>>;
}
impl HasNameRange for Decl {
fn name_range(&self, ctx: &SharedContext) -> Option<Range<usize>> {
if let Decl::BibEntry(decl) = self {
return Some(decl.at.1.clone());
}
if !self.is_def() {
return None;
}
let span = self.span();
if let Some(range) = span.range() {
return Some(range.clone());
}
let src = ctx.source_by_id(self.file_id()?).ok()?;
src.range(span)
}
}
// todo: field definition
/// Finds the definition of a symbol.
pub fn definition(

View file

@ -8,6 +8,9 @@ use comemo::{Track, Tracked};
use lsp_types::Url;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use tinymist_analysis::stats::AllocStats;
use tinymist_analysis::ty::term_value;
use tinymist_analysis::{analyze_expr_, analyze_import_};
use tinymist_project::LspWorld;
use tinymist_std::hash::{hash128, FxDashMap};
use tinymist_std::typst::TypstDocument;
@ -24,10 +27,9 @@ use typst_shim::eval::{eval_compat, Eval};
use crate::adt::revision::{RevisionLock, RevisionManager, RevisionManagerLike, RevisionSlot};
use crate::analysis::prelude::*;
use crate::analysis::{
analyze_expr_, analyze_import_, analyze_signature, bib_info, definition, post_type_check,
AllocStats, AnalysisStats, BibInfo, CompletionFeat, Definition, PathPreference, QueryStatGuard,
SemanticTokenCache, SemanticTokenContext, SemanticTokens, Signature, SignatureTarget, Ty,
TypeInfo,
analyze_signature, bib_info, definition, post_type_check, AnalysisStats, BibInfo,
CompletionFeat, Definition, PathPreference, QueryStatGuard, SemanticTokenCache,
SemanticTokenContext, SemanticTokens, Signature, SignatureTarget, Ty, TypeInfo,
};
use crate::docs::{DefDocs, TidyModuleDocs};
use crate::syntax::{
@ -147,7 +149,7 @@ impl Analysis {
/// Report the statistics of the allocation.
pub fn report_alloc_stats(&self) -> String {
AllocStats::report(self)
AllocStats::report()
}
/// Get configured trigger suggest command.
@ -781,7 +783,7 @@ impl SharedContext {
return cached;
}
let res = crate::analysis::term_value(val);
let res = term_value(val);
self.analysis
.caches

View file

@ -1,6 +1,6 @@
//! Infer more than the principal type of some expression.
use hashbrown::HashSet;
use std::collections::HashSet;
use tinymist_derive::BindTyCtx;
use super::{prelude::*, DynTypeBounds, ParamAttrs, ParamTy, SharedContext};

View file

@ -1,4 +1,3 @@
pub use core::fmt;
pub use std::collections::{BTreeMap, HashMap};
pub use std::hash::{Hash, Hasher};
pub use std::ops::Range;
@ -14,7 +13,7 @@ pub use typst::World;
pub use typst_shim::syntax::LinkedNodeExt;
pub use typst_shim::utils::LazyHash;
pub(crate) use super::StrRef;
pub(crate) use super::{LocalContext, ToFunc};
pub(crate) use crate::adt::interner::Interned;
pub use crate::ty::Ty;
pub(crate) use crate::StrRef;

View file

@ -1,5 +1,6 @@
//! Semantic tokens (highlighting) support for LSP.
use std::collections::HashMap;
use std::{
num::NonZeroUsize,
ops::Range,
@ -7,7 +8,6 @@ use std::{
sync::{Arc, OnceLock},
};
use hashbrown::HashMap;
use lsp_types::SemanticToken;
use lsp_types::{SemanticTokenModifier, SemanticTokenType};
use parking_lot::Mutex;

View file

@ -1,163 +1,19 @@
//! Analysis of function signatures.
use itertools::Either;
use tinymist_analysis::{func_signature, ArgInfo, ArgsInfo, PartialSignature};
use tinymist_derive::BindTyCtx;
use typst::foundations::Closure;
use super::{
prelude::*, BoundChecker, Definition, DocSource, ParamTy, SharedContext, SigTy, SigWithTy,
TypeInfo, TypeVar,
};
use super::{prelude::*, Definition, SharedContext};
use crate::analysis::PostTypeChecker;
use crate::docs::{UntypedDefDocs, UntypedSignatureDocs, UntypedVarDocs};
use crate::syntax::classify_def_loosely;
use crate::ty::{DynTypeBounds, ParamAttrs};
use crate::ty::{InsTy, TyCtx};
use crate::upstream::truncated_repr;
use crate::ty::{
BoundChecker, DocSource, DynTypeBounds, ParamAttrs, ParamTy, SigWithTy, TyCtx, TypeInfo,
TypeVar,
};
/// Describes a function signature.
#[derive(Debug, Clone)]
pub enum Signature {
/// A primary function signature.
Primary(Arc<PrimarySignature>),
/// A partially applied function signature.
Partial(Arc<PartialSignature>),
}
impl Signature {
/// Returns the primary signature if it is one.
pub fn primary(&self) -> &Arc<PrimarySignature> {
match self {
Signature::Primary(sig) => sig,
Signature::Partial(sig) => &sig.signature,
}
}
/// Returns the with bindings of the signature.
pub fn bindings(&self) -> &[ArgsInfo] {
match self {
Signature::Primary(_) => &[],
Signature::Partial(sig) => &sig.with_stack,
}
}
/// Returns the all parameters of the function.
pub(crate) fn params(&self) -> impl Iterator<Item = (&Interned<ParamTy>, Option<&Ty>)> {
let primary = self.primary().params();
// todo: with stack
primary
}
pub(crate) fn type_sig(&self) -> Interned<SigTy> {
let primary = self.primary().sig_ty.clone();
// todo: with stack
primary
}
pub(crate) fn param_shift(&self) -> usize {
match self {
Signature::Primary(_) => 0,
Signature::Partial(sig) => sig
.with_stack
.iter()
.map(|ws| ws.items.len())
.sum::<usize>(),
}
}
}
/// Describes a primary function signature.
#[derive(Debug, Clone)]
pub struct PrimarySignature {
/// The documentation of the function
pub docs: Option<EcoString>,
/// The documentation of the parameter.
pub param_specs: Vec<Interned<ParamTy>>,
/// Whether the function has fill, stroke, or size parameters.
pub has_fill_or_size_or_stroke: bool,
/// The associated signature type.
pub(crate) sig_ty: Interned<SigTy>,
_broken: bool,
}
impl PrimarySignature {
/// Returns the number of positional parameters of the function.
pub fn pos_size(&self) -> usize {
self.sig_ty.name_started as usize
}
/// Returns the positional parameters of the function.
pub fn pos(&self) -> &[Interned<ParamTy>] {
&self.param_specs[..self.pos_size()]
}
/// Returns the positional parameters of the function.
pub fn get_pos(&self, offset: usize) -> Option<&Interned<ParamTy>> {
self.pos().get(offset)
}
/// Returns the named parameters of the function.
pub fn named(&self) -> &[Interned<ParamTy>] {
&self.param_specs[self.pos_size()..self.pos_size() + self.sig_ty.names.names.len()]
}
/// Returns the named parameters of the function.
pub fn get_named(&self, name: &StrRef) -> Option<&Interned<ParamTy>> {
self.named().get(self.sig_ty.names.find(name)?)
}
/// Returns the name of the rest parameter of the function.
pub fn has_spread_right(&self) -> bool {
self.sig_ty.spread_right
}
/// Returns the rest parameter of the function.
pub fn rest(&self) -> Option<&Interned<ParamTy>> {
self.has_spread_right()
.then(|| &self.param_specs[self.pos_size() + self.sig_ty.names.names.len()])
}
/// Returns the all parameters of the function.
pub fn params(&self) -> impl Iterator<Item = (&Interned<ParamTy>, Option<&Ty>)> {
let pos = self.pos();
let named = self.named();
let rest = self.rest();
let type_sig = &self.sig_ty;
let pos = pos
.iter()
.enumerate()
.map(|(idx, pos)| (pos, type_sig.pos(idx)));
let named = named.iter().map(|x| (x, type_sig.named(&x.name)));
let rest = rest.into_iter().map(|x| (x, type_sig.rest_param()));
pos.chain(named).chain(rest)
}
}
/// Describes a function argument instance
#[derive(Debug, Clone)]
pub struct ArgInfo {
/// The argument's name.
pub name: Option<StrRef>,
/// The argument's term.
pub term: Option<Ty>,
}
/// Describes a function argument list.
#[derive(Debug, Clone)]
pub struct ArgsInfo {
/// The arguments.
pub items: EcoVec<ArgInfo>,
}
/// Describes a function signature that is already partially applied.
#[derive(Debug, Clone)]
pub struct PartialSignature {
/// The positional parameters.
pub signature: Arc<PrimarySignature>,
/// The stack of `fn.with(..)` calls.
pub with_stack: EcoVec<ArgsInfo>,
}
pub use tinymist_analysis::{PrimarySignature, Signature};
/// The language object that the signature is being analyzed for.
#[derive(Debug, Clone)]
@ -475,216 +331,3 @@ fn analyze_dyn_signature(
Some(func_signature(func))
}
/// Gets the signature of a function.
#[comemo::memoize]
pub fn func_signature(func: Func) -> Signature {
use typst::foundations::func::Repr;
let mut with_stack = eco_vec![];
let mut func = func;
while let Repr::With(with) = func.inner() {
let (inner, args) = with.as_ref();
with_stack.push(ArgsInfo {
items: args
.items
.iter()
.map(|arg| ArgInfo {
name: arg.name.clone().map(From::from),
term: Some(Ty::Value(InsTy::new(arg.value.v.clone()))),
})
.collect(),
});
func = inner.clone();
}
let mut pos_tys = vec![];
let mut named_tys = Vec::new();
let mut rest_ty = None;
let mut named_specs = BTreeMap::new();
let mut param_specs = Vec::new();
let mut rest_spec = None;
let mut broken = false;
let mut has_fill_or_size_or_stroke = false;
let mut add_param = |param: Interned<ParamTy>| {
let name = param.name.clone();
if param.attrs.named {
if matches!(name.as_ref(), "fill" | "stroke" | "size") {
has_fill_or_size_or_stroke = true;
}
named_tys.push((name.clone(), param.ty.clone()));
named_specs.insert(name.clone(), param.clone());
}
if param.attrs.variadic {
if rest_ty.is_some() {
broken = true;
} else {
rest_ty = Some(param.ty.clone());
rest_spec = Some(param);
}
} else if param.attrs.positional {
// todo: we have some params that are both positional and named
pos_tys.push(param.ty.clone());
param_specs.push(param);
}
};
let ret_ty = match func.inner() {
Repr::With(..) => unreachable!(),
Repr::Closure(closure) => {
analyze_closure_signature(closure.clone(), &mut add_param);
None
}
Repr::Element(..) | Repr::Native(..) | Repr::Plugin(..) => {
for param in func.params().unwrap_or_default() {
add_param(Interned::new(ParamTy {
name: param.name.into(),
docs: Some(param.docs.into()),
default: param.default.map(|default| truncated_repr(&default())),
ty: Ty::from_param_site(&func, param),
attrs: param.into(),
}));
}
func.returns().map(|r| Ty::from_return_site(&func, r))
}
};
let sig_ty = SigTy::new(pos_tys.into_iter(), named_tys, None, rest_ty, ret_ty);
for name in &sig_ty.names.names {
let Some(param) = named_specs.get(name) else {
continue;
};
param_specs.push(param.clone());
}
if let Some(doc) = rest_spec {
param_specs.push(doc);
}
let signature = Arc::new(PrimarySignature {
docs: func.docs().map(From::from),
param_specs,
has_fill_or_size_or_stroke,
sig_ty: sig_ty.into(),
_broken: broken,
});
log::trace!("got signature {signature:?}");
if with_stack.is_empty() {
return Signature::Primary(signature);
}
Signature::Partial(Arc::new(PartialSignature {
signature,
with_stack,
}))
}
fn analyze_closure_signature(
closure: Arc<LazyHash<Closure>>,
add_param: &mut impl FnMut(Interned<ParamTy>),
) {
log::trace!("closure signature for: {:?}", closure.node.kind());
let closure = &closure.node;
let closure_ast = match closure.kind() {
SyntaxKind::Closure => closure.cast::<ast::Closure>().unwrap(),
_ => return,
};
for param in closure_ast.params().children() {
match param {
ast::Param::Pos(pos) => {
let name = format!("{}", PatternDisplay(&pos));
add_param(Interned::new(ParamTy {
name: name.as_str().into(),
docs: None,
default: None,
ty: Ty::Any,
attrs: ParamAttrs::positional(),
}));
}
// todo: pattern
ast::Param::Named(named) => {
let default = unwrap_parens(named.expr()).to_untyped().clone().into_text();
add_param(Interned::new(ParamTy {
name: named.name().get().into(),
docs: Some(eco_format!("Default value: {default}")),
default: Some(default),
ty: Ty::Any,
attrs: ParamAttrs::named(),
}));
}
ast::Param::Spread(spread) => {
let sink = spread.sink_ident().map(|sink| sink.as_str());
add_param(Interned::new(ParamTy {
name: sink.unwrap_or_default().into(),
docs: None,
default: None,
ty: Ty::Any,
attrs: ParamAttrs::variadic(),
}));
}
}
}
}
struct PatternDisplay<'a>(&'a ast::Pattern<'a>);
impl fmt::Display for PatternDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
ast::Pattern::Normal(ast::Expr::Ident(ident)) => f.write_str(ident.as_str()),
ast::Pattern::Normal(_) => f.write_str("?"), // unreachable?
ast::Pattern::Placeholder(_) => f.write_str("_"),
ast::Pattern::Parenthesized(paren_expr) => {
write!(f, "{}", PatternDisplay(&paren_expr.pattern()))
}
ast::Pattern::Destructuring(destructing) => {
write!(f, "(")?;
let mut first = true;
for item in destructing.items() {
if first {
first = false;
} else {
write!(f, ", ")?;
}
match item {
ast::DestructuringItem::Pattern(pos) => {
write!(f, "{}", PatternDisplay(&pos))?
}
ast::DestructuringItem::Named(named) => write!(
f,
"{}: {}",
named.name().as_str(),
unwrap_parens(named.expr()).to_untyped().text()
)?,
ast::DestructuringItem::Spread(spread) => write!(
f,
"..{}",
spread
.sink_ident()
.map(|sink| sink.as_str())
.unwrap_or_default()
)?,
}
}
write!(f, ")")?;
Ok(())
}
}
}
}
fn unwrap_parens(mut expr: ast::Expr) -> ast::Expr {
while let ast::Expr::Parenthesized(paren_expr) = expr {
expr = paren_expr.expr();
}
expr
}

View file

@ -1,16 +1,11 @@
//! Statistics about the analyzers
use std::{
sync::{atomic::AtomicUsize, Arc},
time::Duration,
};
use std::{sync::Arc, time::Duration};
use parking_lot::Mutex;
use tinymist_std::hash::FxDashMap;
use typst::syntax::FileId;
use super::Analysis;
#[derive(Clone)]
pub(crate) struct QueryStatBucketData {
pub query: u64,
@ -117,84 +112,3 @@ table.analysis-stats tr:nth-child(odd) { background-color: rgba(242, 242, 242, 0
html
}
}
/// Statistics about the allocation
#[derive(Debug, Default)]
pub struct AllocStats {
/// The number of allocated objects.
pub allocated: AtomicUsize,
/// The number of dropped objects.
pub dropped: AtomicUsize,
}
impl AllocStats {
/// increment the statistics.
pub fn increment(&self) {
self.allocated
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
/// decrement the statistics.
pub fn decrement(&self) {
self.dropped
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
impl AllocStats {
/// Report the statistics of the allocation.
pub fn report(_a: &Analysis) -> String {
let maps = crate::adt::interner::MAPS.lock().clone();
let mut data = Vec::new();
for (name, sz, map) in maps {
let allocated = map.allocated.load(std::sync::atomic::Ordering::Relaxed);
let dropped = map.dropped.load(std::sync::atomic::Ordering::Relaxed);
let alive = allocated.saturating_sub(dropped);
data.push((name, sz * alive, allocated, dropped, alive));
}
// sort by total
data.sort_by(|x, y| y.4.cmp(&x.4));
// format to html
let mut html = String::new();
html.push_str(r#"<div>
<style>
table.alloc-stats { width: 100%; border-collapse: collapse; }
table.alloc-stats th, table.alloc-stats td { border: 1px solid black; padding: 8px; text-align: center; }
table.alloc-stats th.name-column, table.alloc-stats td.name-column { text-align: left; }
table.alloc-stats tr:nth-child(odd) { background-color: rgba(242, 242, 242, 0.8); }
@media (prefers-color-scheme: dark) {
table.alloc-stats tr:nth-child(odd) { background-color: rgba(50, 50, 50, 0.8); }
}
</style>
<table class="alloc-stats"><tr><th class="name-column">Name</th><th>Alive</th><th>Allocated</th><th>Dropped</th><th>Size</th></tr>"#);
for (name, sz, allocated, dropped, alive) in data {
html.push_str("<tr>");
html.push_str(&format!(r#"<td class="name-column">{name}</td>"#));
html.push_str(&format!("<td>{alive}</td>"));
html.push_str(&format!("<td>{allocated}</td>"));
html.push_str(&format!("<td>{dropped}</td>"));
html.push_str(&format!("<td>{}</td>", human_size(sz)));
html.push_str("</tr>");
}
html.push_str("</table>");
html.push_str("</div>");
html
}
}
fn human_size(size: usize) -> String {
let units = ["B", "KB", "MB", "GB", "TB"];
let mut unit = 0;
let mut size = size as f64;
while size >= 768.0 && unit < units.len() {
size /= 1024.0;
unit += 1;
}
format!("{:.2} {}", size, units[unit])
}

View file

@ -15,13 +15,11 @@ use crate::{
};
mod apply;
mod convert;
mod docs;
mod select;
mod syntax;
pub(crate) use apply::*;
pub(crate) use convert::*;
pub(crate) use select::*;
#[derive(Default)]

View file

@ -1,10 +1,10 @@
use serde::{Deserialize, Serialize};
use tinymist_analysis::analyze_expr;
use tinymist_world::ShadowApi;
use typst::foundations::{Bytes, IntoValue, StyleChain};
use typst_shim::syntax::LinkedNodeExt;
use crate::{
analysis::analyze_expr,
prelude::*,
syntax::{interpret_mode_at, InterpretMode},
};

View file

@ -2,7 +2,7 @@ use ecow::EcoString;
use lsp_types::InsertTextFormat;
use serde::{Deserialize, Serialize};
use crate::ty::Interned;
use crate::StrRef;
use super::LspRange;
@ -284,11 +284,11 @@ pub struct LspCompletionCommand {
/// The title of command.
pub title: EcoString,
/// The identifier of the actual command handler.
pub command: Interned<str>,
pub command: StrRef,
}
impl From<Interned<str>> for LspCompletionCommand {
fn from(command: Interned<str>) -> Self {
impl From<StrRef> for LspCompletionCommand {
fn from(command: StrRef) -> Self {
Self {
title: EcoString::default(),
command,

View file

@ -1,14 +1,14 @@
use std::collections::HashSet;
use std::sync::OnceLock;
use ecow::{eco_format, EcoString};
use hashbrown::HashSet;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize};
use strum::IntoEnumIterator;
use crate::adt::interner::Interned;
use crate::prelude::*;
use crate::syntax::{InterpretMode, SurroundingSyntax};
use crate::ty::Interned;
/// This is the poorman's type filter, which is less powerful but more steady.
#[derive(Debug, Clone, Serialize, Deserialize)]

View file

@ -1,303 +1,11 @@
use core::fmt;
use std::collections::BTreeMap;
use std::sync::OnceLock;
use ecow::{eco_format, EcoString};
use serde::{Deserialize, Serialize};
use tinymist_analysis::docs::{format_ty, ParamDocs, SignatureDocs, VarDocs};
use tinymist_analysis::ty::DocSource;
use tinymist_analysis::Signature;
use typst::syntax::Span;
use super::tidy::*;
use crate::analysis::{ParamAttrs, ParamTy, Signature};
use crate::prelude::*;
use crate::ty::Ty;
use crate::ty::{DocSource, Interned};
use crate::upstream::plain_docs_sentence;
type TypeRepr = Option<(
/* short */ EcoString,
/* long */ EcoString,
/* value */ EcoString,
)>;
/// Documentation about a definition (without type information).
pub type UntypedDefDocs = DefDocsT<()>;
/// Documentation about a definition.
pub type DefDocs = DefDocsT<TypeRepr>;
/// Documentation about a definition.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum DefDocsT<T> {
/// Documentation about a function.
#[serde(rename = "func")]
Function(Box<SignatureDocsT<T>>),
/// Documentation about a variable.
#[serde(rename = "var")]
Variable(VarDocsT<T>),
/// Documentation about a module.
#[serde(rename = "module")]
Module(TidyModuleDocs),
/// Other kinds of documentation.
#[serde(rename = "plain")]
Plain {
/// The content of the documentation.
docs: EcoString,
},
}
impl<T> DefDocsT<T> {
/// Get the markdown representation of the documentation.
pub fn docs(&self) -> &EcoString {
match self {
Self::Function(docs) => &docs.docs,
Self::Variable(docs) => &docs.docs,
Self::Module(docs) => &docs.docs,
Self::Plain { docs } => docs,
}
}
}
impl DefDocs {
/// Get full documentation for the signature.
pub fn hover_docs(&self) -> EcoString {
match self {
DefDocs::Function(docs) => docs.hover_docs().clone(),
_ => plain_docs_sentence(self.docs()),
}
}
}
/// Describes a primary function signature.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignatureDocsT<T> {
/// Documentation for the function.
pub docs: EcoString,
/// The positional parameters.
pub pos: Vec<ParamDocsT<T>>,
/// The named parameters.
pub named: BTreeMap<Interned<str>, ParamDocsT<T>>,
/// The rest parameter.
pub rest: Option<ParamDocsT<T>>,
/// The return type.
pub ret_ty: T,
/// The full documentation for the signature.
#[serde(skip)]
pub hover_docs: OnceLock<EcoString>,
}
impl SignatureDocsT<TypeRepr> {
/// Get full documentation for the signature.
pub fn hover_docs(&self) -> &EcoString {
self.hover_docs
.get_or_init(|| plain_docs_sentence(&format!("{}", SigHoverDocs(self))))
}
}
struct SigHoverDocs<'a>(&'a SignatureDocs);
impl fmt::Display for SigHoverDocs<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let docs = self.0;
let base_docs = docs.docs.trim();
if !base_docs.is_empty() {
f.write_str(base_docs)?;
}
fn write_param_docs(
f: &mut fmt::Formatter<'_>,
docs: &ParamDocsT<TypeRepr>,
kind: &str,
is_first: &mut bool,
) -> fmt::Result {
if *is_first {
*is_first = false;
write!(f, "\n\n## {}\n\n", docs.name)?;
} else {
write!(f, "\n\n## {} ({kind})\n\n", docs.name)?;
}
// p.cano_type.0
if let Some(t) = &docs.cano_type {
write!(f, "```typc\ntype: {}\n```\n\n", t.2)?;
}
f.write_str(docs.docs.trim())?;
Ok(())
}
if !docs.pos.is_empty() {
f.write_str("\n\n# Positional Parameters")?;
let mut is_first = true;
for pos_docs in &docs.pos {
write_param_docs(f, pos_docs, "positional", &mut is_first)?;
}
}
if docs.rest.is_some() {
f.write_str("\n\n# Rest Parameters")?;
let mut is_first = true;
if let Some(rest) = &docs.rest {
write_param_docs(f, rest, "spread right", &mut is_first)?;
}
}
if !docs.named.is_empty() {
f.write_str("\n\n# Named Parameters")?;
let mut is_first = true;
for named_docs in docs.named.values() {
write_param_docs(f, named_docs, "named", &mut is_first)?;
}
}
Ok(())
}
}
/// Documentation about a signature.
pub type UntypedSignatureDocs = SignatureDocsT<()>;
/// Documentation about a signature.
pub type SignatureDocs = SignatureDocsT<TypeRepr>;
impl SignatureDocs {
/// Get the markdown representation of the documentation.
pub fn print(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
let mut is_first = true;
let mut write_sep = |f: &mut dyn std::fmt::Write| {
if is_first {
is_first = false;
return f.write_str("\n ");
}
f.write_str(",\n ")
};
f.write_char('(')?;
for pos_docs in &self.pos {
write_sep(f)?;
f.write_str(&pos_docs.name)?;
if let Some(t) = &pos_docs.cano_type {
write!(f, ": {}", t.0)?;
}
}
if let Some(rest) = &self.rest {
write_sep(f)?;
f.write_str("..")?;
f.write_str(&rest.name)?;
if let Some(t) = &rest.cano_type {
write!(f, ": {}", t.0)?;
}
}
if !self.named.is_empty() {
let mut name_prints = vec![];
for v in self.named.values() {
let ty = v.cano_type.as_ref().map(|t| &t.0);
name_prints.push((v.name.clone(), ty, v.default.clone()))
}
name_prints.sort();
for (name, ty, val) in name_prints {
write_sep(f)?;
let val = val.as_deref().unwrap_or("any");
let mut default = val.trim();
if default.starts_with('{') && default.ends_with('}') && default.len() > 30 {
default = "{ .. }"
}
if default.starts_with('`') && default.ends_with('`') && default.len() > 30 {
default = "raw"
}
if default.starts_with('[') && default.ends_with(']') && default.len() > 30 {
default = "content"
}
f.write_str(&name)?;
if let Some(ty) = ty {
write!(f, ": {ty}")?;
}
if default.contains('\n') {
write!(f, " = {}", default.replace("\n", "\n "))?;
} else {
write!(f, " = {default}")?;
}
}
}
if !is_first {
f.write_str(",\n")?;
}
f.write_char(')')?;
Ok(())
}
}
/// Documentation about a variable (without type information).
pub type UntypedVarDocs = VarDocsT<()>;
/// Documentation about a variable.
pub type VarDocs = VarDocsT<Option<(EcoString, EcoString, EcoString)>>;
/// Describes a primary pattern binding.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VarDocsT<T> {
/// Documentation for the pattern binding.
pub docs: EcoString,
/// The inferred type of the pattern binding source.
pub return_ty: T,
/// Cached documentation for the definition.
#[serde(skip)]
pub def_docs: OnceLock<String>,
}
impl VarDocs {
/// Get the markdown representation of the documentation.
pub fn def_docs(&self) -> &String {
self.def_docs
.get_or_init(|| plain_docs_sentence(&self.docs).into())
}
}
/// Documentation about a parameter (without type information).
pub type TypelessParamDocs = ParamDocsT<()>;
/// Documentation about a parameter.
pub type ParamDocs = ParamDocsT<TypeRepr>;
/// Describes a function parameter.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ParamDocsT<T> {
/// The parameter's name.
pub name: Interned<str>,
/// Documentation for the parameter.
pub docs: EcoString,
/// Inferred type of the parameter.
pub cano_type: T,
/// The parameter's default name as value.
pub default: Option<EcoString>,
/// The attribute of the parameter.
#[serde(flatten)]
pub attrs: ParamAttrs,
}
impl ParamDocs {
fn new(param: &ParamTy, ty: Option<&Ty>) -> Self {
Self {
name: param.name.as_ref().into(),
docs: param.docs.clone().unwrap_or_default(),
cano_type: format_ty(ty.or(Some(&param.ty))),
default: param.default.clone(),
attrs: param.attrs,
}
}
}
fn format_ty(ty: Option<&Ty>) -> TypeRepr {
let ty = ty?;
let short = ty.repr().unwrap_or_else(|| "any".into());
let long = eco_format!("{ty:?}");
let value = ty.value_repr().unwrap_or_else(|| "".into());
Some((short, long, value))
}
use crate::LocalContext;
pub(crate) fn var_docs(ctx: &mut LocalContext, pos: Span) -> Option<VarDocs> {
let source = ctx.source_by_id(pos.id()?).ok()?;

View file

@ -4,16 +4,15 @@ mod convert;
mod def;
mod module;
mod package;
mod tidy;
use tinymist_std::path::unix_slash;
use typst::syntax::FileId;
pub(crate) use convert::convert_docs;
pub use def::*;
pub(crate) use def::*;
pub use module::*;
pub use package::*;
pub(crate) use tidy::*;
pub use tinymist_analysis::docs::*;
fn file_id_repr(fid: FileId) -> String {
if let Some(spec) = fid.package() {

View file

@ -10,10 +10,10 @@ use typst::diag::StrResult;
use typst::syntax::package::PackageSpec;
use typst::syntax::FileId;
use crate::adt::interner::Interned;
use crate::docs::file_id_repr;
use crate::package::{get_manifest_id, PackageInfo};
use crate::syntax::{Decl, DefKind, Expr, ExprInfo};
use crate::ty::Interned;
use crate::LocalContext;
use super::DefDocs;

View file

@ -1,4 +1,4 @@
use hashbrown::HashSet;
use std::collections::HashSet;
use crate::{
prelude::*,

View file

@ -46,7 +46,7 @@ pub mod docs;
pub mod package;
pub mod syntax;
pub mod testing;
pub mod ty;
pub use tinymist_analysis::{ty, upstream};
/// The physical position in a document.
pub type FramePosition = typst::layout::Position;
@ -81,15 +81,17 @@ mod semantic_tokens_delta;
mod semantic_tokens_full;
mod signature_help;
mod symbol;
mod upstream;
mod will_rename_files;
mod workspace_label;
use typst::syntax::Source;
use tinymist_analysis::log_debug_ct;
use tinymist_analysis::{adt::interner::Interned, log_debug_ct};
use tinymist_project::LspComputeGraph;
/// A reference to the interned string
pub(crate) type StrRef = Interned<str>;
/// A request handler with given syntax information.
pub trait SyntaxRequest {
/// The response type of the request.

View file

@ -1,5 +1,6 @@
use std::sync::OnceLock;
use tinymist_analysis::adt::interner::Interned;
use tinymist_std::typst::TypstDocument;
use typst::syntax::Span;
@ -7,7 +8,7 @@ use crate::{
analysis::{Definition, SearchCtx},
prelude::*,
syntax::{get_index_info, RefExpr, SyntaxClass},
ty::Interned,
StrRef,
};
/// The [`textDocument/references`] request is sent from the client to the
@ -73,7 +74,7 @@ struct ReferencesWorker<'a> {
ctx: SearchCtx<'a>,
references: Vec<LspLocation>,
def: Definition,
module_path: OnceLock<Interned<str>>,
module_path: OnceLock<StrRef>,
}
impl ReferencesWorker<'_> {
@ -148,7 +149,7 @@ impl ReferencesWorker<'_> {
}
// todo: references of package
fn module_path(&self) -> &Interned<str> {
fn module_path(&self) -> &StrRef {
self.module_path.get_or_init(|| {
self.def
.decl

View file

@ -9,13 +9,13 @@ use typst::{
syntax::Span,
};
use crate::adt::interner::Interned;
use crate::{
analysis::{get_link_exprs, LinkObject, LinkTarget},
find_references,
prelude::*,
prepare_renaming,
syntax::{first_ancestor_expr, get_index_info, node_ancestors, Decl, RefExpr, SyntaxClass},
ty::Interned,
};
/// The [`textDocument/rename`] request is sent from the client to the server to

View file

@ -7,16 +7,13 @@ use std::{
use ecow::eco_format;
use typst::foundations::{IntoValue, Module, Str, Type};
use crate::{adt::interner::Interned, StrRef};
use crate::{adt::snapshot_map::SnapshotMap, analysis::SharedContext};
use crate::{
adt::snapshot_map::SnapshotMap,
analysis::SharedContext,
docs::{convert_docs, identify_pat_docs, identify_tidy_module_docs, UntypedDefDocs, VarDocsT},
prelude::*,
syntax::{Decl, DefKind},
ty::{
BuiltinTy, DynTypeBounds, InsTy, Interned, PackageId, SigTy, StrRef, Ty, TypeVar,
TypeVarBounds,
},
ty::{BuiltinTy, DynTypeBounds, InsTy, PackageId, SigTy, Ty, TypeVar, TypeVarBounds},
};
use super::DeclExpr;
@ -35,6 +32,7 @@ pub struct DocString {
}
impl DocString {
/// Gets the docstring as a variable doc
pub fn as_var(&self) -> VarDoc {
VarDoc {
docs: self.docs.clone().unwrap_or_default(),

View file

@ -1,9 +1,12 @@
#![allow(missing_docs)]
use std::ops::DerefMut;
use parking_lot::Mutex;
use rpds::RedBlackTreeMapSync;
use rustc_hash::FxHashMap;
use std::ops::Deref;
use tinymist_analysis::adt::interner::Interned;
use tinymist_std::hash::hash128;
use typst::{
foundations::{Element, NativeElement, Type, Value},
@ -17,7 +20,7 @@ use crate::{
analysis::{QueryStatGuard, SharedContext},
prelude::*,
syntax::{find_module_level_docs, resolve_id_by_path, DefKind},
ty::{BuiltinTy, InsTy, Interned, Ty},
ty::{BuiltinTy, InsTy, Ty},
};
use super::{compute_docstring, def::*, DocCommentMatcher, DocString, InterpretMode};

View file

@ -2,12 +2,8 @@
//!
//! This module must hide all **AST details** from the rest of the codebase.
// todo: remove this
#![allow(missing_docs)]
pub use tinymist_analysis::syntax::comment::*;
pub use tinymist_analysis::syntax::import::*;
pub use tinymist_analysis::syntax::matcher::*;
pub(crate) mod lexical_hierarchy;
pub use lexical_hierarchy::*;
pub(crate) mod module;
@ -16,9 +12,6 @@ pub(crate) mod expr;
pub use expr::*;
pub(crate) mod docs;
pub use docs::*;
pub(crate) mod def;
pub use def::*;
pub(crate) mod repr;
use repr::*;
pub(crate) mod index;
pub use index::*;
pub use tinymist_analysis::syntax::*;