feat: merge func and variable docs parser (#812)

* feat: merge func and variable docs parser

* test: update snapshot
This commit is contained in:
Myriad-Dreamin 2024-11-14 20:35:21 +08:00 committed by GitHub
parent 7ab125bbc2
commit c128f633d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 80 additions and 141 deletions

View file

@ -410,7 +410,7 @@ impl LocalContext {
match def.decl.kind() {
DefKind::Function => {
let sig = self.sig_of_def(def.clone())?;
let docs = crate::docs::sig_docs(&sig, None)?;
let docs = crate::docs::sig_docs(&sig)?;
Some(DefDocs::Function(Box::new(docs)))
}
DefKind::Struct | DefKind::Constant | DefKind::Variable => {

View file

@ -13,8 +13,11 @@ use crate::ty::Ty;
use crate::ty::{DocSource, Interned};
use crate::upstream::plain_docs_sentence;
type TypeRepr = Option<(/* short */ String, /* long */ String)>;
type ShowTypeRepr<'a> = &'a mut dyn FnMut(Option<&Ty>) -> TypeRepr;
type TypeRepr = Option<(
/* short */ String,
/* long */ String,
/* value */ String,
)>;
/// Documentation about a definition (without type information).
pub type UntypedDefDocs = DefDocsT<()>;
@ -86,13 +89,13 @@ 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!("{}", SigDefDocs(self))))
.get_or_init(|| plain_docs_sentence(&format!("{}", SigHoverDocs(self))))
}
}
struct SigDefDocs<'a>(&'a SignatureDocs);
struct SigHoverDocs<'a>(&'a SignatureDocs);
impl fmt::Display for SigDefDocs<'_> {
impl fmt::Display for SigHoverDocs<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let docs = self.0;
let base_docs = docs.docs.trim();
@ -213,6 +216,31 @@ impl SignatureDocs {
}
}
/// Documentation about a variable (without type information).
pub type UntypedVarDocs = VarDocsT<()>;
/// Documentation about a variable.
pub type VarDocs = VarDocsT<Option<(String, String, String)>>;
/// 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.
@ -235,24 +263,24 @@ pub struct ParamDocsT<T> {
}
impl ParamDocs {
fn new(param: &ParamSpec, ty: Option<&Ty>, doc_ty: Option<&mut ShowTypeRepr>) -> Self {
fn new(param: &ParamSpec, 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)), doc_ty),
cano_type: format_ty(ty.or(Some(&param.ty))),
default: param.default.clone(),
attrs: param.attrs,
}
}
}
fn format_ty(ty: Option<&Ty>, doc_ty: Option<&mut ShowTypeRepr>) -> TypeRepr {
match doc_ty {
Some(doc_ty) => doc_ty(ty),
None => ty
.and_then(|ty| ty.repr())
.map(|short| (short, format!("{ty:?}"))),
}
fn format_ty(ty: Option<&Ty>) -> TypeRepr {
let ty = ty?;
let short = ty.repr().unwrap_or_else(|| "any".to_owned());
let long = format!("{ty:?}");
let value = ty.value_repr().unwrap_or_else(|| "".to_owned());
Some((short, long, value))
}
pub(crate) fn var_docs(ctx: &mut LocalContext, pos: Span) -> Option<VarDocs> {
@ -266,7 +294,7 @@ pub(crate) fn var_docs(ctx: &mut LocalContext, pos: Span) -> Option<VarDocs> {
log::info!("check variable docs of ty: {ty:?} => {srcs:?}");
let doc_source = srcs.into_iter().next()?;
let return_ty = ty.describe().map(|short| (short, format!("{ty:?}")));
let return_ty = format_ty(Some(&ty));
match doc_source {
DocSource::Var(var) => {
let docs = type_info
@ -291,7 +319,7 @@ pub(crate) fn var_docs(ctx: &mut LocalContext, pos: Span) -> Option<VarDocs> {
}
}
pub(crate) fn sig_docs(sig: &Signature, mut doc_ty: Option<ShowTypeRepr>) -> Option<SignatureDocs> {
pub(crate) fn sig_docs(sig: &Signature) -> Option<SignatureDocs> {
let type_sig = sig.type_sig().clone();
let pos_in = sig
@ -310,19 +338,14 @@ pub(crate) fn sig_docs(sig: &Signature, mut doc_ty: Option<ShowTypeRepr>) -> Opt
let ret_in = type_sig.body.as_ref();
let pos = pos_in
.map(|(param, ty)| ParamDocs::new(param, ty, doc_ty.as_mut()))
.map(|(param, ty)| ParamDocs::new(param, ty))
.collect();
let named = named_in
.map(|(param, ty)| {
(
param.name.clone(),
ParamDocs::new(param, ty, doc_ty.as_mut()),
)
})
.map(|(param, ty)| (param.name.clone(), ParamDocs::new(param, ty)))
.collect();
let rest = rest_in.map(|(param, ty)| ParamDocs::new(param, ty, doc_ty.as_mut()));
let rest = rest_in.map(|(param, ty)| ParamDocs::new(param, ty));
let ret_ty = format_ty(ret_in, doc_ty.as_mut());
let ret_ty = format_ty(ret_in);
Some(SignatureDocs {
docs: sig.primary().docs.clone().unwrap_or_default(),

View file

@ -196,7 +196,7 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
{
let _ = writeln!(md, "<!-- begin:param {} -->", param.name);
let ty = match &param.cano_type {
Some((short, _)) => short,
Some((short, _, _)) => short,
None => "unknown",
};
let _ = writeln!(

View file

@ -1,12 +1,8 @@
use std::sync::OnceLock;
use ecow::EcoString;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use typst::diag::StrResult;
use crate::upstream::plain_docs_sentence;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TidyParamDocs {
pub name: EcoString,
@ -16,38 +12,18 @@ pub struct TidyParamDocs {
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TidyFuncDocs {
pub struct TidyPatDocs {
pub docs: EcoString,
pub return_ty: Option<EcoString>,
pub params: Vec<TidyParamDocs>,
}
/// Documentation about a variable (without type information).
pub type UntypedVarDocs = VarDocsT<()>;
/// Documentation about a variable.
pub type VarDocs = VarDocsT<Option<(String, String)>>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VarDocsT<T> {
pub docs: EcoString,
pub return_ty: T,
#[serde(skip)]
pub def_docs: OnceLock<String>,
}
impl VarDocs {
pub fn def_docs(&self) -> &String {
self.def_docs
.get_or_init(|| plain_docs_sentence(&self.docs).into())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TidyModuleDocs {
pub docs: EcoString,
}
pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
pub fn identify_pat_docs(converted: &str) -> StrResult<TidyPatDocs> {
let lines = converted.lines().collect::<Vec<_>>();
let mut matching_return_ty = true;
@ -153,54 +129,13 @@ pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
};
params.reverse();
Ok(TidyFuncDocs {
Ok(TidyPatDocs {
docs,
return_ty,
params,
})
}
pub fn identify_var_docs(converted: EcoString) -> StrResult<VarDocs> {
let lines = converted.lines().collect::<Vec<_>>();
let mut return_ty = None;
let mut break_line = None;
let mut i = lines.len();
loop {
if i == 0 {
break;
}
i -= 1;
let line = lines[i];
if line.is_empty() {
continue;
}
let Some(w) = line.trim_start().strip_prefix("->") else {
break_line = Some(i + 1);
break;
};
// todo: convert me
return_ty = Some((w.trim().into(), String::new()));
break_line = Some(i);
break;
}
let docs = match break_line {
Some(line_no) => (lines[..line_no]).iter().copied().join("\n").into(),
None => converted,
};
Ok(VarDocs {
docs,
return_ty,
def_docs: OnceLock::new(),
})
}
pub fn identify_tidy_module_docs(docs: EcoString) -> StrResult<TidyModuleDocs> {
Ok(TidyModuleDocs { docs })
}
@ -236,7 +171,7 @@ mod tests {
use super::TidyParamDocs;
fn func(s: &str) -> String {
let f = super::identify_func_docs(s).unwrap();
let f = super::identify_pat_docs(s).unwrap();
let mut res = format!(">> docs:\n{}\n<< docs", f.docs);
if let Some(t) = f.return_ty {
res.push_str(&format!("\n>>return\n{t}\n<<return"));
@ -254,10 +189,10 @@ mod tests {
}
fn var(s: &str) -> String {
let f = super::identify_var_docs(s.into()).unwrap();
let f = super::identify_pat_docs(s).unwrap();
let mut res = format!(">> docs:\n{}\n<< docs", f.docs);
if let Some(t) = f.return_ty {
res.push_str(&format!("\n>>return\n{}\n<<return", t.0));
res.push_str(&format!("\n>>return\n{t}\n<<return"));
}
res
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/annotate_docs_error.typ
---
{
"contents": "```typc\nlet speaker-note(\n note,\n mode: str = \"typ\",\n setting: (any) => any = Closure(..),\n) = none;\n```\n\n---\nSpeaker notes are a way to add additional information to your slides that is not visible to the audience. This can be useful for providing additional context or reminders to yourself.\n\n ## Example\n\n ```typ\n#speaker-note[This is a speaker note]\n\n```\n```\nRender Error\ncompiling node: error: unknown variable: speaker-note at \"/__render__.typ\":201..213\nHint: if you meant to use subtraction, try adding spaces around the minus sign: \\`speaker - note\\`\n\n```\n\n## Parameters\n\n@positional `note`\n\n@named `mode`\n\n@named `setting`",
"contents": "```typc\nlet speaker-note(\n note: any,\n mode: str = \"typ\",\n setting: (any) => any = Closure(..),\n) = none;\n```\n\n---\nSpeaker notes are a way to add additional information to your slides that is not visible to the audience. This can be useful for providing additional context or reminders to yourself.\n\n ## Example\n\n ```typ\n#speaker-note[This is a speaker note]\n\n```\n```\nRender Error\ncompiling node: error: unknown variable: speaker-note at \"/__render__.typ\":201..213\nHint: if you meant to use subtraction, try adding spaces around the minus sign: \\`speaker - note\\`\n\n```\n\n## Parameters\n\n@positional `note`\n\n@named `mode`\n\n@named `setting`",
"range": "11:20:11:32"
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/annotate_ret.typ
---
{
"contents": "```typc\nlet _delayed-wrapper(\n body,\n) = content;\n```\n\n---\n## Parameters\n\n@positional `body`",
"contents": "```typc\nlet _delayed-wrapper(\n body: any,\n) = content;\n```\n\n---\n## Parameters\n\n@positional `body`",
"range": "6:20:6:36"
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/param.typ
---
{
"contents": "```typc\nlet param;\n```\n\n---\nThe `parameter`.",
"contents": "```typc\nlet param = any;\n```\n\n---\nThe `parameter`.",
"range": "3:25:3:30"
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/value_repr.typ
---
{
"contents": "```typc\nlet f(\n x,\n y,\n z,\n w01: int = 1,\n w02: str = \"test\",\n w03: any = 1 + 2,\n w04 = Label(test),\n w05: function = box,\n w06: any = list.item,\n w07: content = Expr(..),\n w08: any = Expr(..),\n w09: any = 1 + 2,\n w10: array = (\n 1,\n 2,\n ),\n w11: array = (),\n w12: dict = (:),\n w13: dict = (a: 1),\n w14: dict = (a: box),\n w15: dict = (a: list.item),\n) = int;\n```\n\n---\n## Parameters\n\n@positional `x`\n\n@positional `y`\n\n@positional `z`\n\n@named `w01`\n\n@named `w02`\n\n@named `w03`\n\n@named `w04`\n\n@named `w05`\n\n@named `w06`\n\n@named `w07`\n\n@named `w08`\n\n@named `w09`\n\n@named `w10`\n\n@named `w11`\n\n@named `w12`\n\n@named `w13`\n\n@named `w14`\n\n@named `w15`",
"contents": "```typc\nlet f(\n x: any,\n y: any,\n z: any,\n w01: int = 1,\n w02: str = \"test\",\n w03: any = 1 + 2,\n w04: any = Label(test),\n w05: function = box,\n w06: any = list.item,\n w07: content = Expr(..),\n w08: any = Expr(..),\n w09: any = 1 + 2,\n w10: array = (\n 1,\n 2,\n ),\n w11: array = (),\n w12: dict = (:),\n w13: dict = (a: 1),\n w14: dict = (a: box),\n w15: dict = (a: list.item),\n) = int;\n```\n\n---\n## Parameters\n\n@positional `x`\n\n@positional `y`\n\n@positional `z`\n\n@named `w01`\n\n@named `w02`\n\n@named `w03`\n\n@named `w04`\n\n@named `w05`\n\n@named `w06`\n\n@named `w07`\n\n@named `w08`\n\n@named `w09`\n\n@named `w10`\n\n@named `w11`\n\n@named `w12`\n\n@named `w13`\n\n@named `w14`\n\n@named `w15`",
"range": "23:20:23:21"
}

View file

@ -272,8 +272,8 @@ fn link_tooltip(
Some(HoverContents::Array(results))
}
fn push_result_ty(name: &str, ty_repr: Option<&(String, String)>, type_doc: &mut String) {
let Some((short, _)) = ty_repr else {
fn push_result_ty(name: &str, ty_repr: Option<&(String, String, String)>, type_doc: &mut String) {
let Some((short, _, _)) = ty_repr else {
return;
};
if short == name {

View file

@ -10,10 +10,7 @@ use typst::foundations::{IntoValue, Module, Str, Type};
use crate::{
adt::snapshot_map::SnapshotMap,
analysis::SharedContext,
docs::{
convert_docs, identify_func_docs, identify_tidy_module_docs, identify_var_docs,
UntypedDefDocs, VarDocsT,
},
docs::{convert_docs, identify_pat_docs, identify_tidy_module_docs, UntypedDefDocs, VarDocsT},
prelude::*,
syntax::{Decl, DefKind},
ty::{BuiltinTy, Interned, PackageId, SigTy, StrRef, Ty, TypeBounds, TypeVar, TypeVarBounds},
@ -87,13 +84,11 @@ pub(crate) fn compute_docstring(
locals: SnapshotMap::default(),
next_id: 0,
};
use DefKind::*;
match kind {
DefKind::Function => checker.check_func_docs(docs),
DefKind::Variable => checker.check_var_docs(docs),
DefKind::Module => checker.check_module_docs(docs),
DefKind::Constant => None,
DefKind::Struct => None,
DefKind::Reference => None,
Function | Variable => checker.check_pat_docs(docs),
Module => checker.check_module_docs(docs),
Constant | Struct | Reference => None,
}
}
@ -110,9 +105,9 @@ struct DocsChecker<'a> {
static EMPTY_MODULE: LazyLock<Module> =
LazyLock::new(|| Module::new("stub", typst::foundations::Scope::new()));
impl<'a> DocsChecker<'a> {
pub fn check_func_docs(mut self, docs: String) -> Option<DocString> {
pub fn check_pat_docs(mut self, docs: String) -> Option<DocString> {
let converted =
convert_docs(self.ctx, &docs).and_then(|converted| identify_func_docs(&converted));
convert_docs(self.ctx, &docs).and_then(|converted| identify_pat_docs(&converted));
let converted = match Self::fallback_docs(converted, &docs) {
Ok(c) => c,
@ -145,29 +140,6 @@ impl<'a> DocsChecker<'a> {
})
}
pub fn check_var_docs(mut self, docs: String) -> Option<DocString> {
let converted = convert_docs(self.ctx, &docs).and_then(identify_var_docs);
let converted = match Self::fallback_docs(converted, &docs) {
Ok(c) => c,
Err(e) => return Some(e),
};
let module = self.ctx.module_by_str(docs);
let module = module.as_ref().unwrap_or(EMPTY_MODULE.deref());
let res_ty = converted
.return_ty
.and_then(|ty| self.check_type_strings(module, &ty.0));
Some(DocString {
docs: Some(converted.docs),
var_bounds: self.vars,
vars: BTreeMap::new(),
res_ty,
})
}
pub fn check_module_docs(self, docs: String) -> Option<DocString> {
let converted = convert_docs(self.ctx, &docs).and_then(identify_tidy_module_docs);

View file

@ -21,6 +21,15 @@ impl Ty {
worker.describe_root(self)
}
/// Describe available value instances of the given type.
pub fn value_repr(&self) -> Option<String> {
let mut worker = TypeDescriber {
repr: true,
..Default::default()
};
worker.describe_root(self)
}
/// Describe the given type.
pub fn describe(&self) -> Option<String> {
let mut worker = TypeDescriber::default();