feat: incorporated with static function signature analysis (#688)

* feat: finished function signature analysis

* dev: update snapshot

* dev: broken snapshot
This commit is contained in:
Myriad-Dreamin 2024-10-16 15:53:43 +08:00 committed by GitHub
parent a3f100e7cb
commit 02bbdbf8d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 429 additions and 270 deletions

View file

@ -19,7 +19,7 @@ use std::{
fmt::{self, Debug, Display},
hash::{BuildHasherDefault, Hash, Hasher},
ops::Deref,
sync::OnceLock,
sync::{LazyLock, OnceLock},
};
use dashmap::{DashMap, SharedValue};
@ -95,6 +95,13 @@ impl Interned<str> {
}
}
static EMPTY: LazyLock<Interned<str>> = LazyLock::new(|| Interned::new_str(""));
impl Default for Interned<str> {
fn default() -> Self {
EMPTY.clone()
}
}
impl From<&str> for Interned<str> {
fn from(s: &str) -> Self {
Interned::new_str(s)
@ -249,6 +256,32 @@ impl PartialEq for Interned<str> {
impl Eq for Interned<str> {}
impl serde::Serialize for Interned<str> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.arc.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for Interned<str> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct StrVisitor;
impl<'de> serde::de::Visitor<'de> for StrVisitor {
type Value = Interned<str>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
Ok(Interned::new_str(v))
}
}
deserializer.deserialize_str(StrVisitor)
}
}
impl<T: Internable + ?Sized> Hash for Interned<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
// NOTE: Cast disposes vtable pointer / slice/str length.

View file

@ -73,7 +73,7 @@ pub fn analyze_call_no_cache(
callee_node: LinkedNode,
args: ast::Args<'_>,
) -> Option<CallInfo> {
let signature = analyze_signature(ctx, SignatureTarget::Syntax(source, callee_node))?;
let signature = analyze_signature(ctx, SignatureTarget::SyntaxFast(source, callee_node))?;
trace!("got signature {signature:?}");
let mut info = CallInfo {

View file

@ -16,10 +16,9 @@ use typst::syntax::{package::PackageSpec, Span, VirtualPath};
use typst::{model::Document, text::Font};
use typst_shim::syntax::LinkedNodeExt;
use crate::adt::interner::Interned;
use crate::analysis::{
analyze_bib, analyze_dyn_signature, analyze_expr_, analyze_import_, post_type_check, BibInfo,
DefUseInfo, DefinitionLink, DocString, IdentRef, ImportInfo, PathPreference, SigTy, Signature,
analyze_bib, analyze_expr_, analyze_import_, analyze_signature, post_type_check, BibInfo,
DefUseInfo, DefinitionLink, DocString, IdentRef, ImportInfo, PathPreference, Signature,
SignatureTarget, Ty, TypeScheme,
};
use crate::docs::{DocStringKind, SignatureDocs};
@ -460,8 +459,14 @@ impl<'w> AnalysisContext<'w> {
Some(self.to_lsp_range(position, &source))
}
pub(crate) fn signature_dyn(&mut self, func: Func) -> Signature {
log::debug!("check runtime func {func:?}");
analyze_signature(self, SignatureTarget::Runtime(func)).unwrap()
}
/// Get the signature of a function.
pub fn signature(&self, func: &SignatureTarget) -> Option<Signature> {
pub fn get_signature(&self, func: &SignatureTarget) -> Option<Signature> {
match func {
SignatureTarget::Def(source, r) => {
// todo: check performance on peeking signature source frequently
@ -472,6 +477,15 @@ impl<'w> AnalysisContext<'w> {
.get(&hash128(&cache_key))
.and_then(|slot| (cache_key.1 == slot.2).then_some(slot.3.clone()))
}
SignatureTarget::SyntaxFast(source, node) => {
// todo: check performance on peeking signature source frequently
let cache_key = (source, node.offset(), true);
self.analysis
.caches
.static_signatures
.get(&hash128(&cache_key))
.and_then(|slot| (cache_key.1 == slot.2).then_some(slot.3.clone()))
}
SignatureTarget::Syntax(source, node) => {
// todo: check performance on peeking signature source frequently
let cache_key = (source, node.offset());
@ -507,6 +521,16 @@ impl<'w> AnalysisContext<'w> {
});
slot.3.clone()
}
SignatureTarget::SyntaxFast(source, node) => {
let cache_key = (source, node.offset(), true);
self.analysis
.caches
.static_signatures
.entry(hash128(&cache_key))
.or_insert_with(|| (self.lifetime, cache_key.0, cache_key.1, compute()))
.3
.clone()
}
SignatureTarget::Syntax(source, node) => {
let cache_key = (source, node.offset());
self.analysis
@ -537,7 +561,7 @@ impl<'w> AnalysisContext<'w> {
runtime_fn: &Value,
) -> Option<SignatureDocs> {
let def_use = self.def_use(source.clone())?;
let ty_chk = self.type_check(source.clone())?;
let ty_chk = self.type_check(source)?;
crate::docs::signature_docs(self, Some(&(def_use, ty_chk)), def_ident, runtime_fn, None)
}
@ -560,7 +584,7 @@ impl<'w> AnalysisContext<'w> {
}
/// Get the type check information of a source file.
pub(crate) fn type_check(&mut self, source: Source) -> Option<Arc<TypeScheme>> {
pub(crate) fn type_check(&mut self, source: &Source) -> Option<Arc<TypeScheme>> {
let fid = source.id();
if let Some(res) = self.caches.modules.entry(fid).or_default().type_check() {
@ -574,7 +598,7 @@ impl<'w> AnalysisContext<'w> {
let res = if let Some(res) = self.analysis.caches.type_check.get(&h) {
res.1.clone()
} else {
let res = crate::analysis::type_check(self, source);
let res = crate::analysis::type_check(self, source.clone());
self.analysis
.caches
.type_check
@ -691,18 +715,13 @@ impl<'w> AnalysisContext<'w> {
self.type_of_span(rr.span())
}
pub(crate) fn type_of_func(&mut self, func: &Func) -> Option<Interned<SigTy>> {
log::debug!("check runtime func {func:?}");
Some(analyze_dyn_signature(self, func.clone()).type_sig())
}
pub(crate) fn user_type_of_ident(
&mut self,
source: &Source,
def_fid: TypstFileId,
def_ident: &IdentRef,
) -> Option<Ty> {
let ty_chk = self.type_check(source.clone())?;
let ty_chk = self.type_check(source)?;
let def_use = self.def_use(source.clone())?;
let (def_id, _) = def_use.get_def(def_fid, def_ident)?;
@ -721,14 +740,17 @@ impl<'w> AnalysisContext<'w> {
pub(crate) fn type_of_span(&mut self, s: Span) -> Option<Ty> {
let id = s.id()?;
let source = self.source_by_id(id).ok()?;
let ty_chk = self.type_check(source)?;
ty_chk.type_of_span(s)
self.type_of_span_(&source, s)
}
pub(crate) fn type_of_span_(&mut self, source: &Source, s: Span) -> Option<Ty> {
self.type_check(source)?.type_of_span(s)
}
pub(crate) fn literal_type_of_node(&mut self, k: LinkedNode) -> Option<Ty> {
let id = k.span().id()?;
let source = self.source_by_id(id).ok()?;
let ty_chk = self.type_check(source.clone())?;
let ty_chk = self.type_check(&source)?;
post_type_check(self, &ty_chk, k.clone()).or_else(|| ty_chk.type_of_span(k.span()))
}

View file

@ -145,7 +145,7 @@ impl<'a, 'w> TyCtxMut for PostTypeCheckWorker<'a, 'w> {
}
fn type_of_func(&mut self, func: &Func) -> Option<Interned<SigTy>> {
self.ctx.type_of_func(func)
Some(self.ctx.signature_dyn(func.clone()).type_sig())
}
}

View file

@ -4,6 +4,8 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use serde::{Deserialize, Serialize};
use typst::foundations::ParamInfo;
use typst::syntax::Source;
use typst::{
foundations::{Closure, Func, Value},
@ -12,17 +14,71 @@ use typst::{
LinkedNode, SyntaxKind,
},
};
use typst_shim::syntax::LinkedNodeExt;
use typst_shim::utils::LazyHash;
use super::{IdentRef, StrRef};
use crate::adt::interner::Interned;
use crate::analysis::resolve_callee;
use crate::syntax::{get_def_target, get_deref_target, DefTarget};
use crate::ty::SigTy;
use crate::analysis::{resolve_callee, AnalysisContext};
use crate::docs::UntypedSymbolDocs;
use crate::syntax::get_non_strict_def_target;
use crate::ty::{SigTy, Ty};
use crate::upstream::truncated_repr;
use crate::AnalysisContext;
use super::{find_definition, DefinitionLink, IdentRef, LexicalKind, LexicalVarKind, StrRef, Ty};
/// Describes a function parameter.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub struct ParamAttrs {
/// Is the parameter positional?
pub positional: bool,
/// Is the parameter named?
///
/// Can be true even if `positional` is true if the parameter can be given
/// in both variants.
pub named: bool,
/// Can the parameter be given any number of times?
pub variadic: bool,
/// Is the parameter settable with a set rule?
pub settable: bool,
}
impl ParamAttrs {
pub(crate) fn positional() -> ParamAttrs {
ParamAttrs {
positional: true,
named: false,
variadic: false,
settable: false,
}
}
pub(crate) fn named() -> ParamAttrs {
ParamAttrs {
positional: false,
named: true,
variadic: false,
settable: false,
}
}
pub(crate) fn variadic() -> ParamAttrs {
ParamAttrs {
positional: true,
named: false,
variadic: true,
settable: false,
}
}
}
impl From<&ParamInfo> for ParamAttrs {
fn from(param: &ParamInfo) -> Self {
ParamAttrs {
positional: param.positional,
named: param.named,
variadic: param.variadic,
settable: param.settable,
}
}
}
/// Describes a function parameter.
#[derive(Debug, Clone)]
@ -35,17 +91,8 @@ pub struct ParamSpec {
pub default: Option<EcoString>,
/// The type of the parameter.
pub ty: Ty,
/// Is the parameter positional?
pub positional: bool,
/// Is the parameter named?
///
/// Can be true even if `positional` is true if the parameter can be given
/// in both variants.
pub named: bool,
/// Can the parameter be given any number of times?
pub variadic: bool,
/// Is the parameter settable with a set rule?
pub settable: bool,
/// The attributes of the parameter.
pub attrs: ParamAttrs,
}
/// Describes a function signature.
@ -90,7 +137,7 @@ pub struct PrimarySignature {
pub param_specs: Vec<ParamSpec>,
/// Whether the function has fill, stroke, or size parameters.
pub has_fill_or_size_or_stroke: bool,
/// The signature type.
/// The associated signature type.
pub(crate) sig_ty: Interned<SigTy>,
_broken: bool,
}
@ -168,48 +215,126 @@ pub enum SignatureTarget<'a> {
/// A static node without knowing the function at runtime.
Def(Source, IdentRef),
/// A static node without knowing the function at runtime.
SyntaxFast(Source, LinkedNode<'a>),
/// A static node without knowing the function at runtime.
Syntax(Source, LinkedNode<'a>),
/// A function that is known at runtime.
Runtime(Func),
}
pub(crate) fn analyze_dyn_signature(ctx: &mut AnalysisContext, func: Func) -> Signature {
ctx.compute_signature(SignatureTarget::Runtime(func.clone()), || {
Signature::Primary(analyze_dyn_signature_inner(func))
})
}
pub(crate) fn analyze_signature(
ctx: &mut AnalysisContext,
callee_node: SignatureTarget,
) -> Option<Signature> {
if let Some(sig) = ctx.signature(&callee_node) {
if let Some(sig) = ctx.get_signature(&callee_node) {
return Some(sig);
}
analyze_type_signature(ctx, &callee_node).or_else(|| analyze_dyn_signature(ctx, &callee_node))
}
fn analyze_type_signature(
ctx: &mut AnalysisContext,
callee_node: &SignatureTarget<'_>,
) -> Option<Signature> {
let (type_info, ty) = match callee_node {
SignatureTarget::Def(..) => None,
SignatureTarget::SyntaxFast(source, node) | SignatureTarget::Syntax(source, node) => {
let type_info = ctx.type_check(source)?;
let ty = type_info.type_of_span(node.span())?;
Some((type_info, ty))
}
SignatureTarget::Runtime(f) => {
let source = ctx.source_by_id(f.span().id()?).ok()?;
let node = source.find(f.span())?;
let def = get_non_strict_def_target(node.parent()?.clone())?;
let type_info = ctx.type_check(&source)?;
let ty = type_info.type_of_span(def.node().span())?;
Some((type_info, ty))
}
}?;
let type_var = ty.sources().into_iter().next()?;
let sig_ty = ty.sig_repr(true)?;
let docstring = match type_info.var_docs.get(&type_var.def).map(|x| x.as_ref()) {
Some(UntypedSymbolDocs::Function(sig)) => sig,
_ => return None,
};
let mut param_specs = Vec::new();
let mut has_fill_or_size_or_stroke = false;
let mut _broken = false;
for (doc, ty) in docstring.pos.iter().zip(sig_ty.positional_params()) {
let default = doc.default.clone();
let ty = ty.clone();
let name = doc.name.clone();
if matches!(name.as_ref(), "fill" | "stroke" | "size") {
has_fill_or_size_or_stroke = true;
}
param_specs.push(ParamSpec {
name,
docs: Some(doc.docs.clone()),
default,
ty,
attrs: ParamAttrs::positional(),
});
}
for (name, ty) in sig_ty.named_params() {
let doc = docstring.named.get(name).unwrap();
let default = doc.default.clone();
let ty = ty.clone();
if matches!(name.as_ref(), "fill" | "stroke" | "size") {
has_fill_or_size_or_stroke = true;
}
param_specs.push(ParamSpec {
name: name.clone(),
docs: Some(doc.docs.clone()),
default,
ty,
attrs: ParamAttrs::named(),
});
}
if let Some(doc) = docstring.rest.as_ref() {
let default = doc.default.clone();
param_specs.push(ParamSpec {
name: doc.name.clone(),
docs: Some(doc.docs.clone()),
default,
ty: sig_ty.rest_param().cloned().unwrap_or(Ty::Any),
attrs: ParamAttrs::variadic(),
});
}
Some(Signature::Primary(Arc::new(PrimarySignature {
docs: Some(docstring.docs.clone()),
param_specs,
has_fill_or_size_or_stroke,
sig_ty,
_broken,
})))
}
fn analyze_dyn_signature(
ctx: &mut AnalysisContext,
callee_node: &SignatureTarget<'_>,
) -> Option<Signature> {
let func = match callee_node {
SignatureTarget::Def(..) => todo!(),
SignatureTarget::Syntax(source, node) => {
let _ = resolve_callee_v2;
let _ = source;
// let res = resolve_callee_v2(ctx, node)?;
// let func = match res {
// TryResolveCalleeResult::Syntax(lnk) => {
// println!("Syntax {:?}", lnk.name);
// return analyze_static_signature(ctx, source, lnk);
// }
// TryResolveCalleeResult::Runtime(func) => func,
// };
let func = resolve_callee(ctx, &node)?;
SignatureTarget::Def(..) => return None,
SignatureTarget::SyntaxFast(..) => return None,
SignatureTarget::Syntax(_, node) => {
let func = resolve_callee(ctx, node)?;
log::debug!("got function {func:?}");
func
}
SignatureTarget::Runtime(func) => func,
SignatureTarget::Runtime(func) => func.clone(),
};
use typst::foundations::func::Repr;
@ -241,81 +366,12 @@ pub(crate) fn analyze_signature(
if with_stack.is_empty() {
return Some(Signature::Primary(signature));
}
Some(Signature::Partial(Arc::new(PartialSignature {
signature,
with_stack,
})))
}
// fn analyze_static_signature(
// ctx: &mut AnalysisContext<'_>,
// source: Source,
// lnk: DefinitionLink,
// ) -> Option<Signature> {
// let def_at = lnk.def_at?;
// let def_source = if def_at.0 == source.id() {
// source.clone()
// } else {
// ctx.source_by_id(def_at.0).ok()?
// };
// let root = LinkedNode::new(def_source.root());
// let def_node = root.leaf_at_compat(def_at.1.start + 1)?;
// let def_node = get_def_target(def_node)?;
// let def_node = match def_node {
// DefTarget::Let(node) => node,
// DefTarget::Import(_) => return None,
// };
// println!("def_node {def_node:?}");
// None
// }
#[allow(dead_code)]
enum TryResolveCalleeResult {
Syntax(DefinitionLink),
Runtime(Func),
}
/// Resolve a callee expression to a function but prefer to keep static.
fn resolve_callee_v2(
ctx: &mut AnalysisContext,
callee: LinkedNode,
) -> Option<TryResolveCalleeResult> {
let source = ctx.source_by_id(callee.span().id()?).ok()?;
let node = source.find(callee.span())?;
let cursor = node.offset();
let deref_target = get_deref_target(node, cursor)?;
let def = find_definition(ctx, source.clone(), None, deref_target)?;
if let LexicalKind::Var(LexicalVarKind::Function) = def.kind {
if let Some(Value::Func(f)) = def.value {
return Some(TryResolveCalleeResult::Runtime(f));
}
}
if let Some(def_at) = &def.def_at {
let def_source = if def_at.0 == source.id() {
source.clone()
} else {
ctx.source_by_id(def_at.0).ok()?
};
let _t = ctx.type_check(source)?;
let root = LinkedNode::new(def_source.root());
let def_node = root.leaf_at_compat(def_at.1.start + 1)?;
let def_node = get_def_target(def_node)?;
let _def_node = match def_node {
DefTarget::Let(node) => node,
DefTarget::Import(_) => return None,
};
}
Some(TryResolveCalleeResult::Syntax(def))
}
fn analyze_dyn_signature_inner(func: Func) -> Arc<PrimarySignature> {
let mut pos_tys = vec![];
let mut named_tys = Vec::new();
@ -326,37 +382,26 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc<PrimarySignature> {
let mut rest_spec = None;
let mut broken = false;
let mut has_fill = false;
let mut has_stroke = false;
let mut has_size = false;
let mut has_fill_or_size_or_stroke = false;
let mut add_param = |param: ParamSpec| {
let name = param.name.clone();
if param.named {
match param.name.as_ref() {
"fill" => {
has_fill = true;
}
"stroke" => {
has_stroke = true;
}
"size" => {
has_size = true;
}
_ => {}
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.variadic {
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.positional {
} 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);
@ -377,10 +422,7 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc<PrimarySignature> {
docs: Some(p.docs.into()),
default: p.default.map(|d| truncated_repr(&d())),
ty: Ty::from_param_site(&func, p),
positional: p.positional,
named: p.named,
variadic: p.variadic,
settable: p.settable,
attrs: p.into(),
});
}
@ -400,7 +442,7 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc<PrimarySignature> {
Arc::new(PrimarySignature {
docs: func.docs().map(From::from),
param_specs,
has_fill_or_size_or_stroke: has_fill || has_stroke || has_size,
has_fill_or_size_or_stroke,
sig_ty: sig_ty.into(),
_broken: broken,
})
@ -424,10 +466,7 @@ fn analyze_closure_signature(c: Arc<LazyHash<Closure>>, add_param: &mut impl FnM
docs: None,
default: None,
ty: Ty::Any,
positional: true,
named: false,
variadic: false,
settable: false,
attrs: ParamAttrs::positional(),
});
}
// todo: pattern
@ -438,10 +477,7 @@ fn analyze_closure_signature(c: Arc<LazyHash<Closure>>, add_param: &mut impl FnM
docs: Some(eco_format!("Default value: {expr}")),
default: Some(expr),
ty: Ty::Any,
positional: false,
named: true,
variadic: false,
settable: true,
attrs: ParamAttrs::named(),
});
}
ast::Param::Spread(n) => {
@ -451,10 +487,7 @@ fn analyze_closure_signature(c: Arc<LazyHash<Closure>>, add_param: &mut impl FnM
docs: None,
default: None,
ty: Ty::Any,
positional: true,
named: false,
variadic: true,
settable: false,
attrs: ParamAttrs::variadic(),
});
}
}

View file

@ -93,7 +93,7 @@ impl<'a, 'w> TyCtxMut for TypeChecker<'a, 'w> {
}
fn type_of_func(&mut self, func: &Func) -> Option<Interned<SigTy>> {
self.ctx.type_of_func(func)
Some(self.ctx.signature_dyn(func.clone()).type_sig())
}
}
@ -167,7 +167,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
let source = self.ctx.source_by_id(def_id).ok()?;
let ext_def_use_info = self.ctx.def_use(source.clone())?;
let ext_type_info = self.ctx.type_check(source)?;
let ext_type_info = self.ctx.type_check(&source)?;
let (ext_def_id, _) = ext_def_use_info.get_def(
def_id,
&IdentRef {

View file

@ -14,15 +14,6 @@ use super::*;
const DOC_VARS: u64 = 0;
impl<'a, 'w> TypeChecker<'a, 'w> {
pub fn check_func_docs(&mut self, root: &LinkedNode) -> Option<Arc<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<Arc<DocString>> {
let lb = root.cast::<ast::LetBinding>()?;
let first = lb.kind().bindings();
@ -45,9 +36,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
let docs = find_docs_of(&self.source, def)?;
let docstring = self.ctx.compute_docstring(root.span().id()?, docs, kind)?;
let res = Arc::new(docstring.take().rename_based_on(base_id, self));
self.info.var_docs.insert(base_id, res.clone());
Some(res)
Some(Arc::new(docstring.take().rename_based_on(base_id, self)))
}
}

View file

@ -12,9 +12,14 @@ use typst::{
};
use super::*;
use crate::{adt::interner::Interned, ty::*};
use crate::{
adt::interner::Interned,
docs::{DocStringKind, SignatureDocsT, TypelessParamDocs, UntypedSymbolDocs},
ty::*,
};
static EMPTY_DOCSTRING: LazyLock<DocString> = LazyLock::new(DocString::default);
static EMPTY_VAR_DOC: LazyLock<VarDoc> = LazyLock::new(VarDoc::default);
impl<'a, 'w> TypeChecker<'a, 'w> {
pub(crate) fn check_syntax(&mut self, root: LinkedNode) -> Option<Ty> {
@ -374,12 +379,20 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
}
fn check_closure(&mut self, root: LinkedNode<'_>) -> Option<Ty> {
let docstring = self.check_func_docs(&root);
let docstring = docstring.as_deref().unwrap_or(&EMPTY_DOCSTRING);
let closure: ast::Closure = root.cast()?;
let def_id = closure
.name()
.and_then(|n| self.get_def_id(n.span(), &to_ident_ref(&root, n)?))?;
let docstring = self.check_docstring(&root, DocStringKind::Function, def_id);
let docstring = docstring.as_deref().unwrap_or(&EMPTY_DOCSTRING);
log::debug!("check closure: {:?} -> {docstring:#?}", closure.name());
let mut pos_docs = vec![];
let mut named_docs = BTreeMap::new();
let mut rest_docs = None;
let mut pos = vec![];
let mut named = BTreeMap::new();
let mut defaults = BTreeMap::new();
@ -389,6 +402,18 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
match param {
ast::Param::Pos(pattern) => {
pos.push(self.check_pattern(pattern, Ty::Any, docstring, root.clone()));
if let ast::Pattern::Normal(ast::Expr::Ident(ident)) = pattern {
let name = ident.get().into();
let param_doc = docstring.get_var(&name).unwrap_or(&EMPTY_VAR_DOC);
pos_docs.push(TypelessParamDocs {
name,
docs: param_doc.docs.clone().unwrap_or_default(),
cano_type: (),
default: param_doc.default.clone(),
attrs: ParamAttrs::positional(),
});
}
}
ast::Param::Named(e) => {
let name = e.name().get().into();
@ -401,7 +426,19 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
// optimize it, so I put a todo here.
self.constrain(&exp, &v);
named.insert(name.clone(), v);
defaults.insert(name, exp);
defaults.insert(name.clone(), exp);
let param_doc = docstring.get_var(&name).unwrap_or(&EMPTY_VAR_DOC);
named_docs.insert(
name.clone(),
TypelessParamDocs {
name: name.clone(),
docs: param_doc.docs.clone().unwrap_or_default(),
cano_type: (),
default: param_doc.default.clone(),
attrs: ParamAttrs::named(),
},
);
}
// todo: spread left/right
ast::Param::Spread(a) => {
@ -413,14 +450,35 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
}
self.constrain(&exp, &v);
rest = Some(v);
let param_doc = docstring
.get_var(&e.get().as_str().into())
.unwrap_or(&EMPTY_VAR_DOC);
rest_docs = Some(TypelessParamDocs {
name: e.get().as_str().into(),
docs: param_doc.docs.clone().unwrap_or_default(),
cano_type: (),
default: param_doc.default.clone(),
attrs: ParamAttrs::variadic(),
});
}
// todo: ..(args)
}
}
}
self.info.var_docs.insert(
def_id,
Arc::new(UntypedSymbolDocs::Function(Box::new(SignatureDocsT {
docs: docstring.docs.clone().unwrap_or_default(),
pos: pos_docs,
named: named_docs,
rest: rest_docs,
ret_ty: (),
}))),
);
let body = self.check_expr_in(closure.body().span(), root);
// 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 {

View file

@ -113,7 +113,7 @@ impl StatefulRequest for CompletionRequest {
Some(DerefTarget::Normal(SyntaxKind::Str, cano_expr)) => {
let parent = cano_expr.parent()?;
if matches!(parent.kind(), SyntaxKind::Named | SyntaxKind::Args) {
let ty_chk = ctx.type_check(source.clone());
let ty_chk = ctx.type_check(&source);
if let Some(ty_chk) = ty_chk {
let ty = ty_chk.type_of_span(cano_expr.span());
log::debug!("check string ty: {ty:?}");

View file

@ -116,7 +116,7 @@ pub fn package_docs(
let file_id = fid?;
let src = world.source(file_id).ok()?;
let def_use = ctx.def_use(src.clone())?;
let ty_chck = ctx.type_check(src)?;
let ty_chck = ctx.type_check(&src)?;
Some((def_use, ty_chck))
});
let type_info = type_info.as_ref();

View file

@ -14,9 +14,10 @@ use typst::{
};
use super::tidy::*;
use crate::analysis::{analyze_dyn_signature, ParamSpec};
use crate::analysis::{ParamAttrs, ParamSpec};
use crate::docs::library;
use crate::syntax::IdentRef;
use crate::ty::Interned;
use crate::{ty::Ty, AnalysisContext};
type TypeRepr = Option<(/* short */ String, /* long */ String)>;
@ -54,13 +55,18 @@ impl fmt::Display for DocStringKind {
}
}
/// Documentation about a symbol (without type information).
pub type UntypedSymbolDocs = SymbolDocsT<()>;
/// Documentation about a symbol.
pub type SymbolDocs = SymbolDocsT<TypeRepr>;
/// Documentation about a symbol.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum SymbolDocs {
pub enum SymbolDocsT<T> {
/// Documentation about a function.
#[serde(rename = "func")]
Function(Box<SignatureDocs>),
Function(Box<SignatureDocsT<T>>),
/// Documentation about a variable.
#[serde(rename = "var")]
Variable(TidyVarDocs),
@ -75,14 +81,14 @@ pub enum SymbolDocs {
},
}
impl SymbolDocs {
impl<T> SymbolDocsT<T> {
/// Get the markdown representation of the documentation.
pub fn docs(&self) -> &str {
pub fn docs(&self) -> &EcoString {
match self {
Self::Function(docs) => docs.docs.as_str(),
Self::Variable(docs) => docs.docs.as_str(),
Self::Module(docs) => docs.docs.as_str(),
Self::Plain { docs } => docs.as_str(),
Self::Function(docs) => &docs.docs,
Self::Variable(docs) => &docs.docs,
Self::Module(docs) => &docs.docs,
Self::Plain { docs } => docs,
}
}
}
@ -123,21 +129,22 @@ pub(crate) fn symbol_docs(
/// Describes a primary function signature.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignatureDocs {
pub struct SignatureDocsT<T> {
/// Documentation for the function.
pub docs: EcoString,
// pub return_ty: Option<EcoString>,
// pub params: Vec<TidyParamDocs>,
/// The positional parameters.
pub pos: Vec<ParamDocs>,
pub pos: Vec<ParamDocsT<T>>,
/// The named parameters.
pub named: BTreeMap<String, ParamDocs>,
pub named: BTreeMap<Interned<str>, ParamDocsT<T>>,
/// The rest parameter.
pub rest: Option<ParamDocs>,
pub rest: Option<ParamDocsT<T>>,
/// The return type.
pub ret_ty: TypeRepr,
pub ret_ty: T,
}
/// Documentation about a signature.
pub type SignatureDocs = SignatureDocsT<TypeRepr>;
impl fmt::Display for SignatureDocs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut is_first = true;
@ -197,41 +204,35 @@ impl fmt::Display for SignatureDocs {
}
}
/// 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)]
pub struct ParamDocs {
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ParamDocsT<T> {
/// The parameter's name.
pub name: String,
pub name: Interned<str>,
/// Documentation for the parameter.
pub docs: EcoString,
/// Inferred type of the parameter.
pub cano_type: TypeRepr,
pub cano_type: T,
/// The parameter's default name as value.
pub default: Option<EcoString>,
/// Is the parameter positional?
pub positional: bool,
/// Is the parameter named?
///
/// Can be true even if `positional` is true if the parameter can be given
/// in both variants.
pub named: bool,
/// Can the parameter be given any number of times?
pub variadic: bool,
/// Is the parameter settable with a set rule?
pub settable: bool,
/// The attribute of the parameter.
#[serde(flatten)]
pub attrs: ParamAttrs,
}
impl ParamDocs {
fn new(param: &ParamSpec, ty: Option<&Ty>, doc_ty: Option<&mut ShowTypeRepr>) -> Self {
Self {
name: param.name.as_ref().to_owned(),
name: param.name.as_ref().into(),
docs: param.docs.clone().unwrap_or_default(),
cano_type: format_ty(ty.or(Some(&param.ty)), doc_ty),
default: param.default.clone(),
positional: param.positional,
named: param.named,
variadic: param.variadic,
settable: param.settable,
attrs: param.attrs,
}
}
}
@ -276,7 +277,7 @@ pub(crate) fn signature_docs(
}
}
let sig = analyze_dyn_signature(ctx, func.clone());
let sig = ctx.signature_dyn(func.clone());
let def_id = type_info.and_then(|(def_use, _)| {
let def_fid = func.span().id()?;
let (def_id, _) = def_use.get_def(def_fid, def_ident?)?;
@ -313,7 +314,7 @@ pub(crate) fn signature_docs(
let named = named_in
.map(|(param, ty)| {
(
param.name.as_ref().to_owned(),
param.name.clone(),
ParamDocs::new(param, ty, doc_ty.as_mut()),
)
})
@ -323,7 +324,7 @@ pub(crate) fn signature_docs(
let ret_ty = format_ty(ret_in, doc_ty.as_mut());
Some(SignatureDocs {
docs: docstring.and_then(|x| x.docs.clone()).unwrap_or_default(),
docs: docstring.map(|x| x.docs().clone()).unwrap_or_default(),
pos,
named,
rest,

View file

@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs
expression: CallSnapshot(result.as_deref())
input_file: crates/tinymist-query/src/fixtures/call_info/builtin.typ
---
1 -> CallParamInfo { kind: Positional, is_content_block: false, param_name: "angle" }
<nil>

View file

@ -3,6 +3,4 @@ source: crates/tinymist-query/src/analysis.rs
expression: CallSnapshot(result.as_deref())
input_file: crates/tinymist-query/src/fixtures/call_info/builtin_poly.typ
---
255 -> CallParamInfo { kind: Positional, is_content_block: false, param_name: "red" }
255 -> CallParamInfo { kind: Positional, is_content_block: false, param_name: "green" }
255 -> CallParamInfo { kind: Positional, is_content_block: false, param_name: "blue" }
<nil>

View file

@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs
expression: CallSnapshot(result.as_deref())
input_file: crates/tinymist-query/src/fixtures/call_info/builtin_poly2.typ
---
"#fff" -> CallParamInfo { kind: Positional, is_content_block: false, param_name: "red" }
<nil>

View file

@ -3,5 +3,4 @@ source: crates/tinymist-query/src/analysis.rs
expression: CallSnapshot(result.as_deref())
input_file: crates/tinymist-query/src/fixtures/call_info/user_named.typ
---
y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param_name: "y" }
1 -> CallParamInfo { kind: Positional, is_content_block: false, param_name: "x" }
<nil>

View file

@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs
expression: CallSnapshot(result.as_deref())
input_file: crates/tinymist-query/src/fixtures/call_info/user_named_with.typ
---
1 -> CallParamInfo { kind: Positional, is_content_block: false, param_name: "x" }
<nil>

View file

@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs
expression: CallSnapshot(result.as_deref())
input_file: crates/tinymist-query/src/fixtures/call_info/user_named_with2.typ
---
y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param_name: "y" }
<nil>

View file

@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs
expression: CallSnapshot(result.as_deref())
input_file: crates/tinymist-query/src/fixtures/call_info/user_with.typ
---
1 -> CallParamInfo { kind: Positional, is_content_block: false, param_name: "y" }
<nil>

View file

@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs
expression: CallSnapshot(result.as_deref())
input_file: crates/tinymist-query/src/fixtures/call_info/user_with2.typ
---
<nil>

View file

@ -3,15 +3,8 @@ 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

@ -4,17 +4,12 @@ expression: result
input_file: crates/tinymist-query/src/fixtures/type_check/tuple_map.typ
---
"a" = (1, )
"b" = (Type(string), )
"f" = (( ⪯ (Type(bytes) | Type(float) | Type(integer) | Type(label) | Type(string) | Type(type) | Type(version)))) => Type(string)
"x" = Any
"b" = (Any, )
"f" = Undef
---
5..6 -> @a
20..21 -> @f
24..25 -> @x
29..32 -> Type(string)
29..35 -> Type(string)
33..34 -> @x
42..43 -> @b
46..47 -> @a
46..54 -> (Type(string), )
46..54 -> (Any, )
52..53 -> @f

View file

@ -3,7 +3,7 @@ use typst_shim::syntax::LinkedNodeExt;
use crate::{
adt::interner::Interned,
analysis::{analyze_dyn_signature, find_definition},
analysis::find_definition,
prelude::*,
syntax::{get_check_target, get_deref_target, CheckTarget, ParamTarget},
DocTooltip, LspParamInfo, SemanticRequest,
@ -62,7 +62,7 @@ impl SemanticRequest for SignatureHelpRequest {
function = &inner.0;
}
let sig = analyze_dyn_signature(ctx, function.clone());
let sig = ctx.signature_dyn(function.clone());
let pos = sig.primary().pos();
let named = sig.primary().named();
let rest = sig.primary().rest();
@ -91,7 +91,7 @@ impl SemanticRequest for SignatureHelpRequest {
let mut real_offset = 0;
let focus_name = OnceCell::new();
for (i, (param, ty)) in pos.chain(named).chain(rest).enumerate() {
if is_set && !param.settable {
if is_set && !param.attrs.settable {
continue;
}

View file

@ -10,6 +10,43 @@ impl Ty {
matches!(self, Ty::Union(_) | Ty::Let(_) | Ty::Var(_))
}
/// Get the sources of the given type.
pub fn sources(&self) -> Vec<Interned<TypeVar>> {
let mut results = vec![];
fn collect(ty: &Ty, results: &mut Vec<Interned<TypeVar>>) {
use Ty::*;
match ty {
Any | Boolean(_) | If(..) | Builtin(..) | Value(..) => {}
Dict(..) | Array(..) | Tuple(..) | Func(..) | Args(..) => {}
Unary(..) | Binary(..) => {}
Field(ty) => {
collect(&ty.field, results);
}
Union(ty) => {
for ty in ty.iter() {
collect(ty, results);
}
}
Let(ty) => {
for ty in ty.ubs.iter() {
collect(ty, results);
}
for ty in ty.lbs.iter() {
collect(ty, results);
}
}
Var(ty) => {
results.push(ty.clone());
}
With(ty) => collect(&ty.sig, results),
Select(ty) => collect(&ty.ty, results),
}
}
collect(self, &mut results);
results
}
/// Profile the bounds of the given type.
pub fn bounds(&self, pol: bool, checker: &mut impl BoundChecker) {
BoundCheckContext.ty(self, pol, checker);

View file

@ -21,7 +21,8 @@ use typst::{
use super::PackageId;
use crate::{
adt::{interner::impl_internable, snapshot_map},
analysis::{BuiltinTy, DocString},
analysis::BuiltinTy,
docs::UntypedSymbolDocs,
};
pub(crate) use super::{TyCtx, TyCtxMut};
@ -960,7 +961,7 @@ pub struct TypeScheme {
/// The typing on definitions
pub vars: HashMap<DefId, TypeVarBounds>,
/// The checked documentation of definitions
pub var_docs: HashMap<DefId, Arc<DocString>>,
pub var_docs: HashMap<DefId, Arc<UntypedSymbolDocs>>,
/// The local binding of the type variable
pub local_binds: snapshot_map::SnapshotMap<DefId, Ty>,
/// The typing on syntax structures

View file

@ -12,7 +12,7 @@ use typst::visualize::Color;
use super::{Completion, CompletionContext, CompletionKind};
use crate::adt::interner::Interned;
use crate::analysis::{analyze_dyn_signature, resolve_call_target, BuiltinTy, PathPreference, Ty};
use crate::analysis::{resolve_call_target, BuiltinTy, PathPreference, Ty};
use crate::syntax::{param_index_at_leaf, CheckTarget};
use crate::upstream::complete::complete_code;
use crate::upstream::plain_docs_sentence;
@ -61,7 +61,7 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
let types = (|| {
let id = self.root.span().id()?;
let src = self.ctx.source_by_id(id).ok()?;
self.ctx.type_check(src)
self.ctx.type_check(&src)
})();
let types = types.as_ref();
@ -462,7 +462,7 @@ fn describe_value(ctx: &mut AnalysisContext, v: &Value) -> EcoString {
f = &with_f.0;
}
let sig = analyze_dyn_signature(ctx, f.clone());
let sig = ctx.signature_dyn(f.clone());
sig.primary()
.ty()
.describe()
@ -580,7 +580,7 @@ pub fn param_completions<'a>(
let pos_index =
param_index_at_leaf(&ctx.leaf, &func, args).map(|i| if this.is_some() { i + 1 } else { i });
let signature = analyze_dyn_signature(ctx.ctx, func.clone());
let signature = ctx.ctx.signature_dyn(func.clone());
let leaf_type = ctx.ctx.literal_type_of_node(ctx.leaf.clone());
log::debug!("pos_param_completion_by_type: {:?}", leaf_type);
@ -601,7 +601,7 @@ pub fn param_completions<'a>(
log::debug!("pos_param_completion_to: {:?}", pos);
if let Some(pos) = pos {
if set && !pos.settable {
if set && !pos.attrs.settable {
break 'pos_check;
}
@ -609,7 +609,7 @@ pub fn param_completions<'a>(
doc = Some(plain_docs_sentence(docs));
}
if pos.positional {
if pos.attrs.positional {
type_completion(ctx, &pos.ty, doc.as_deref());
}
}
@ -627,10 +627,10 @@ pub fn param_completions<'a>(
}
log::debug!(
"pos_named_param_completion_to({set:?}): {name:?} {:?}",
param.settable
param.attrs.settable
);
if set && !param.settable {
if set && !param.attrs.settable {
continue;
}
@ -640,7 +640,7 @@ pub fn param_completions<'a>(
.clone()
};
if param.named {
if param.attrs.named {
let compl = Completion {
kind: CompletionKind::Field,
label: param.name.as_ref().into(),
@ -679,7 +679,7 @@ pub fn param_completions<'a>(
ctx.completions.push(compl);
}
if param.positional {
if param.attrs.positional {
type_completion(ctx, &param.ty, docs().as_deref());
}
}
@ -986,14 +986,14 @@ pub fn named_param_value_completions<'a>(
func = f.0.clone();
}
let signature = analyze_dyn_signature(ctx.ctx, func.clone());
let signature = ctx.ctx.signature_dyn(func.clone());
let primary_sig = signature.primary();
let Some(param) = primary_sig.get_named(name) else {
return;
};
if !param.named {
if !param.attrs.named {
return;
}