1use core::fmt;
2use std::collections::{BTreeMap, HashMap};
3use std::sync::{Arc, OnceLock};
4
5use ecow::{EcoString, eco_format};
6use serde::{Deserialize, Serialize};
7
8use super::tidy::*;
9use crate::syntax::DeclExpr;
10use crate::ty::{Interned, ParamAttrs, ParamTy, StrRef, Ty, TypeVarBounds};
11use crate::upstream::plain_docs_sentence;
12
13#[derive(Debug, Clone, Default)]
15pub struct DocString {
16 pub docs: Option<EcoString>,
18 pub var_bounds: HashMap<DeclExpr, TypeVarBounds>,
20 pub vars: BTreeMap<StrRef, VarDoc>,
22 pub res_ty: Option<Ty>,
24}
25
26impl DocString {
27 pub fn as_var(&self) -> VarDoc {
29 VarDoc {
30 docs: self.docs.clone().unwrap_or_default(),
31 ty: self.res_ty.clone(),
32 }
33 }
34
35 pub fn get_var(&self, name: &StrRef) -> Option<&VarDoc> {
37 self.vars.get(name)
38 }
39
40 pub fn var_ty(&self, name: &StrRef) -> Option<&Ty> {
42 self.get_var(name).and_then(|v| v.ty.as_ref())
43 }
44}
45
46#[derive(Debug, Clone, Default)]
48pub struct VarDoc {
49 pub docs: EcoString,
51 pub ty: Option<Ty>,
53}
54
55impl VarDoc {
56 pub fn to_untyped(&self) -> Arc<UntypedDefDocs> {
58 Arc::new(UntypedDefDocs::Variable(VarDocsT {
59 docs: self.docs.clone(),
60 return_ty: (),
61 def_docs: OnceLock::new(),
62 }))
63 }
64}
65
66type TypeRepr = Option<(
67 EcoString,
68 EcoString,
69 EcoString,
70)>;
71
72pub type UntypedDefDocs = DefDocsT<()>;
74pub type DefDocs = DefDocsT<TypeRepr>;
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(tag = "kind")]
80pub enum DefDocsT<T> {
81 #[serde(rename = "func")]
83 Function(Box<SignatureDocsT<T>>),
84 #[serde(rename = "var")]
86 Variable(VarDocsT<T>),
87 #[serde(rename = "module")]
89 Module(TidyModuleDocs),
90 #[serde(rename = "plain")]
92 Plain {
93 docs: EcoString,
95 },
96}
97
98impl<T> DefDocsT<T> {
99 pub fn docs(&self) -> &EcoString {
101 match self {
102 Self::Function(docs) => &docs.docs,
103 Self::Variable(docs) => &docs.docs,
104 Self::Module(docs) => &docs.docs,
105 Self::Plain { docs } => docs,
106 }
107 }
108}
109
110impl DefDocs {
111 pub fn hover_docs(&self) -> EcoString {
113 match self {
114 DefDocs::Function(docs) => docs.hover_docs().clone(),
115 _ => plain_docs_sentence(self.docs()),
116 }
117 }
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct SignatureDocsT<T> {
123 pub docs: EcoString,
125 pub pos: Vec<ParamDocsT<T>>,
127 pub named: BTreeMap<Interned<str>, ParamDocsT<T>>,
129 pub rest: Option<ParamDocsT<T>>,
131 pub ret_ty: T,
133 #[serde(skip)]
135 pub hover_docs: OnceLock<EcoString>,
136}
137
138impl SignatureDocsT<TypeRepr> {
139 pub fn hover_docs(&self) -> &EcoString {
141 self.hover_docs
142 .get_or_init(|| plain_docs_sentence(&format!("{}", SigHoverDocs(self))))
143 }
144}
145
146struct SigHoverDocs<'a>(&'a SignatureDocs);
147
148impl fmt::Display for SigHoverDocs<'_> {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 let docs = self.0;
151 let base_docs = docs.docs.trim();
152
153 if !base_docs.is_empty() {
154 f.write_str(base_docs)?;
155 }
156
157 fn write_param_docs(
158 f: &mut fmt::Formatter<'_>,
159 docs: &ParamDocsT<TypeRepr>,
160 kind: &str,
161 is_first: &mut bool,
162 ) -> fmt::Result {
163 if *is_first {
164 *is_first = false;
165 write!(f, "\n\n## {}\n\n", docs.name)?;
166 } else {
167 write!(f, "\n\n## {} ({kind})\n\n", docs.name)?;
168 }
169
170 if let Some(t) = &docs.cano_type {
172 write!(f, "```typc\ntype: {}\n```\n\n", t.2)?;
173 }
174
175 f.write_str(docs.docs.trim())?;
176
177 Ok(())
178 }
179
180 if !docs.pos.is_empty() {
181 f.write_str("\n\n# Positional Parameters")?;
182
183 let mut is_first = true;
184 for pos_docs in &docs.pos {
185 write_param_docs(f, pos_docs, "positional", &mut is_first)?;
186 }
187 }
188
189 if docs.rest.is_some() {
190 f.write_str("\n\n# Rest Parameters")?;
191
192 let mut is_first = true;
193 if let Some(rest) = &docs.rest {
194 write_param_docs(f, rest, "spread right", &mut is_first)?;
195 }
196 }
197
198 if !docs.named.is_empty() {
199 f.write_str("\n\n# Named Parameters")?;
200
201 let mut is_first = true;
202 for named_docs in docs.named.values() {
203 write_param_docs(f, named_docs, "named", &mut is_first)?;
204 }
205 }
206
207 Ok(())
208 }
209}
210
211pub type UntypedSignatureDocs = SignatureDocsT<()>;
213pub type SignatureDocs = SignatureDocsT<TypeRepr>;
215
216impl SignatureDocs {
217 pub fn print(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
219 let mut is_first = true;
220 let mut write_sep = |f: &mut dyn std::fmt::Write| {
221 if is_first {
222 is_first = false;
223 return f.write_str("\n ");
224 }
225 f.write_str(",\n ")
226 };
227
228 f.write_char('(')?;
229 for pos_docs in &self.pos {
230 write_sep(f)?;
231 f.write_str(&pos_docs.name)?;
232 if let Some(t) = &pos_docs.cano_type {
233 write!(f, ": {}", t.0)?;
234 }
235 }
236 if let Some(rest) = &self.rest {
237 write_sep(f)?;
238 f.write_str("..")?;
239 f.write_str(&rest.name)?;
240 if let Some(t) = &rest.cano_type {
241 write!(f, ": {}", t.0)?;
242 }
243 }
244
245 if !self.named.is_empty() {
246 let mut name_prints = vec![];
247 for v in self.named.values() {
248 let ty = v.cano_type.as_ref().map(|t| &t.0);
249 name_prints.push((v.name.clone(), ty, v.default.clone()))
250 }
251 name_prints.sort();
252 for (name, ty, val) in name_prints {
253 write_sep(f)?;
254 let val = val.as_deref().unwrap_or("any");
255 let mut default = val.trim();
256 if default.starts_with('{') && default.ends_with('}') && default.len() > 30 {
257 default = "{ .. }"
258 }
259 if default.starts_with('`') && default.ends_with('`') && default.len() > 30 {
260 default = "raw"
261 }
262 if default.starts_with('[') && default.ends_with(']') && default.len() > 30 {
263 default = "content"
264 }
265 f.write_str(&name)?;
266 if let Some(ty) = ty {
267 write!(f, ": {ty}")?;
268 }
269 if default.contains('\n') {
270 write!(f, " = {}", default.replace("\n", "\n "))?;
271 } else {
272 write!(f, " = {default}")?;
273 }
274 }
275 }
276 if !is_first {
277 f.write_str(",\n")?;
278 }
279 f.write_char(')')?;
280
281 Ok(())
282 }
283}
284
285pub type UntypedVarDocs = VarDocsT<()>;
287pub type VarDocs = VarDocsT<Option<(EcoString, EcoString, EcoString)>>;
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct VarDocsT<T> {
293 pub docs: EcoString,
295 pub return_ty: T,
297 #[serde(skip)]
299 pub def_docs: OnceLock<String>,
300}
301
302impl VarDocs {
303 pub fn def_docs(&self) -> &String {
305 self.def_docs
306 .get_or_init(|| plain_docs_sentence(&self.docs).into())
307 }
308}
309
310pub type TypelessParamDocs = ParamDocsT<()>;
312pub type ParamDocs = ParamDocsT<TypeRepr>;
314
315#[derive(Debug, Clone, Serialize, Deserialize, Default)]
317pub struct ParamDocsT<T> {
318 pub name: Interned<str>,
320 pub docs: EcoString,
322 pub cano_type: T,
324 pub default: Option<EcoString>,
326 #[serde(flatten)]
328 pub attrs: ParamAttrs,
329}
330
331impl ParamDocs {
332 pub fn new(param: &ParamTy, ty: Option<&Ty>) -> Self {
334 Self {
335 name: param.name.as_ref().into(),
336 docs: param.docs.clone().unwrap_or_default(),
337 cano_type: format_ty(ty.or(Some(¶m.ty))),
338 default: param.default.clone(),
339 attrs: param.attrs,
340 }
341 }
342}
343
344pub fn format_ty(ty: Option<&Ty>) -> TypeRepr {
346 let ty = ty?;
347 let short = ty.repr().unwrap_or_else(|| "any".into());
348 let long = eco_format!("{ty:?}");
349 let value = ty.value_repr().unwrap_or_else(|| "".into());
350
351 Some((short, long, value))
352}