1use core::fmt;
2use std::collections::{BTreeMap, HashMap};
3use std::hash::{Hash, Hasher};
4use std::sync::{Arc, OnceLock};
5
6use ecow::{EcoString, eco_format};
7use serde::{Deserialize, Serialize};
8
9use super::tidy::*;
10use crate::syntax::DeclExpr;
11use crate::ty::{Interned, ParamAttrs, ParamTy, StrRef, Ty, TypeVarBounds};
12use crate::upstream::plain_docs_sentence;
13
14#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
16pub enum DocTextKind {
17 Plain,
19 Official,
21}
22
23#[derive(Debug, Clone)]
25pub struct DocText {
26 raw: EcoString,
27 kind: DocTextKind,
28 resolved: OnceLock<EcoString>,
29}
30
31impl DocText {
32 pub fn plain(raw: EcoString) -> Self {
34 Self {
35 raw,
36 kind: DocTextKind::Plain,
37 resolved: OnceLock::new(),
38 }
39 }
40
41 pub fn official(raw: EcoString) -> Self {
43 Self {
44 raw,
45 kind: DocTextKind::Official,
46 resolved: OnceLock::new(),
47 }
48 }
49
50 pub fn raw(&self) -> &EcoString {
52 &self.raw
53 }
54
55 pub fn kind(&self) -> DocTextKind {
57 self.kind
58 }
59
60 pub fn get_or_init(
62 &self,
63 convert_official: impl FnOnce(&EcoString) -> EcoString,
64 ) -> &EcoString {
65 match self.kind {
66 DocTextKind::Plain => &self.raw,
67 DocTextKind::Official => self.resolved.get_or_init(|| convert_official(&self.raw)),
68 }
69 }
70}
71
72impl PartialEq for DocText {
73 fn eq(&self, other: &Self) -> bool {
74 self.kind == other.kind && self.raw == other.raw
75 }
76}
77
78impl Eq for DocText {}
79
80impl PartialOrd for DocText {
81 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
82 Some(self.cmp(other))
83 }
84}
85
86impl Ord for DocText {
87 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
88 self.kind
89 .cmp(&other.kind)
90 .then_with(|| self.raw.cmp(&other.raw))
91 }
92}
93
94impl Hash for DocText {
95 fn hash<H: Hasher>(&self, state: &mut H) {
96 self.kind.hash(state);
97 self.raw.hash(state);
98 }
99}
100
101#[derive(Debug, Clone, Default)]
103pub struct DocString {
104 pub docs: Option<EcoString>,
106 pub var_bounds: HashMap<DeclExpr, TypeVarBounds>,
108 pub vars: BTreeMap<StrRef, VarDoc>,
110 pub res_ty: Option<Ty>,
112}
113
114impl DocString {
115 pub fn as_var(&self) -> VarDoc {
117 VarDoc {
118 docs: self.docs.clone().unwrap_or_default(),
119 ty: self.res_ty.clone(),
120 }
121 }
122
123 pub fn get_var(&self, name: &StrRef) -> Option<&VarDoc> {
125 self.vars.get(name)
126 }
127
128 pub fn var_ty(&self, name: &StrRef) -> Option<&Ty> {
130 self.get_var(name).and_then(|v| v.ty.as_ref())
131 }
132}
133
134#[derive(Debug, Clone, Default)]
136pub struct VarDoc {
137 pub docs: EcoString,
139 pub ty: Option<Ty>,
141}
142
143impl VarDoc {
144 pub fn to_untyped(&self) -> Arc<UntypedDefDocs> {
146 Arc::new(UntypedDefDocs::Variable(VarDocsT {
147 docs: self.docs.clone(),
148 return_ty: (),
149 def_docs: OnceLock::new(),
150 }))
151 }
152}
153
154type TypeRepr = Option<(
155 EcoString,
156 EcoString,
157 EcoString,
158)>;
159
160pub type UntypedDefDocs = DefDocsT<()>;
162pub type DefDocs = DefDocsT<TypeRepr>;
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167#[serde(tag = "kind")]
168pub enum DefDocsT<T> {
169 #[serde(rename = "func")]
171 Function(Box<SignatureDocsT<T>>),
172 #[serde(rename = "var")]
174 Variable(VarDocsT<T>),
175 #[serde(rename = "module")]
177 Module(TidyModuleDocs),
178 #[serde(rename = "plain")]
180 Plain {
181 docs: EcoString,
183 },
184}
185
186impl<T> DefDocsT<T> {
187 pub fn docs(&self) -> &EcoString {
189 match self {
190 Self::Function(docs) => &docs.docs,
191 Self::Variable(docs) => &docs.docs,
192 Self::Module(docs) => &docs.docs,
193 Self::Plain { docs } => docs,
194 }
195 }
196}
197
198impl DefDocs {
199 pub fn hover_docs(&self) -> EcoString {
201 match self {
202 DefDocs::Function(docs) => docs.hover_docs().clone(),
203 _ => plain_docs_sentence(self.docs()),
204 }
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct SignatureDocsT<T> {
211 pub docs: EcoString,
213 pub pos: Vec<ParamDocsT<T>>,
215 pub named: BTreeMap<Interned<str>, ParamDocsT<T>>,
217 pub rest: Option<ParamDocsT<T>>,
219 pub ret_ty: T,
221 #[serde(skip)]
223 pub hover_docs: OnceLock<EcoString>,
224}
225
226impl SignatureDocsT<TypeRepr> {
227 pub fn hover_docs(&self) -> &EcoString {
229 self.hover_docs
230 .get_or_init(|| plain_docs_sentence(&format!("{}", SigHoverDocs(self))))
231 }
232}
233
234struct SigHoverDocs<'a>(&'a SignatureDocs);
235
236impl fmt::Display for SigHoverDocs<'_> {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 let docs = self.0;
239 let base_docs = docs.docs.trim();
240
241 if !base_docs.is_empty() {
242 f.write_str(base_docs)?;
243 }
244
245 fn write_param_docs(
246 f: &mut fmt::Formatter<'_>,
247 docs: &ParamDocsT<TypeRepr>,
248 kind: &str,
249 is_first: &mut bool,
250 ) -> fmt::Result {
251 if *is_first {
252 *is_first = false;
253 write!(f, "\n\n## {}\n\n", docs.name)?;
254 } else {
255 write!(f, "\n\n## {} ({kind})\n\n", docs.name)?;
256 }
257
258 if let Some(t) = &docs.cano_type {
260 write!(f, "```typc\ntype: {}\n```\n\n", t.2)?;
261 }
262
263 f.write_str(docs.docs.trim())?;
264
265 Ok(())
266 }
267
268 if !docs.pos.is_empty() {
269 f.write_str("\n\n# Positional Parameters")?;
270
271 let mut is_first = true;
272 for pos_docs in &docs.pos {
273 write_param_docs(f, pos_docs, "positional", &mut is_first)?;
274 }
275 }
276
277 if docs.rest.is_some() {
278 f.write_str("\n\n# Rest Parameters")?;
279
280 let mut is_first = true;
281 if let Some(rest) = &docs.rest {
282 write_param_docs(f, rest, "spread right", &mut is_first)?;
283 }
284 }
285
286 if !docs.named.is_empty() {
287 f.write_str("\n\n# Named Parameters")?;
288
289 let mut is_first = true;
290 for named_docs in docs.named.values() {
291 write_param_docs(f, named_docs, "named", &mut is_first)?;
292 }
293 }
294
295 Ok(())
296 }
297}
298
299pub type UntypedSignatureDocs = SignatureDocsT<()>;
301pub type SignatureDocs = SignatureDocsT<TypeRepr>;
303
304impl SignatureDocs {
305 pub fn print(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
307 let mut is_first = true;
308 let mut write_sep = |f: &mut dyn std::fmt::Write| {
309 if is_first {
310 is_first = false;
311 return f.write_str("\n ");
312 }
313 f.write_str(",\n ")
314 };
315
316 f.write_char('(')?;
317 for pos_docs in &self.pos {
318 write_sep(f)?;
319 f.write_str(&pos_docs.name)?;
320 if let Some(t) = &pos_docs.cano_type {
321 write!(f, ": {}", t.0)?;
322 }
323 }
324 if let Some(rest) = &self.rest {
325 write_sep(f)?;
326 f.write_str("..")?;
327 f.write_str(&rest.name)?;
328 if let Some(t) = &rest.cano_type {
329 write!(f, ": {}", t.0)?;
330 }
331 }
332
333 if !self.named.is_empty() {
334 let mut name_prints = vec![];
335 for v in self.named.values() {
336 let ty = v.cano_type.as_ref().map(|t| &t.0);
337 name_prints.push((v.name.clone(), ty, v.default.clone()))
338 }
339 name_prints.sort();
340 for (name, ty, val) in name_prints {
341 write_sep(f)?;
342 let val = val.as_deref().unwrap_or("any");
343 let mut default = val.trim();
344 if default.starts_with('{') && default.ends_with('}') && default.len() > 30 {
345 default = "{ .. }"
346 }
347 if default.starts_with('`') && default.ends_with('`') && default.len() > 30 {
348 default = "raw"
349 }
350 if default.starts_with('[') && default.ends_with(']') && default.len() > 30 {
351 default = "content"
352 }
353 f.write_str(&name)?;
354 if let Some(ty) = ty {
355 write!(f, ": {ty}")?;
356 }
357 if default.contains('\n') {
358 write!(f, " = {}", default.replace("\n", "\n "))?;
359 } else {
360 write!(f, " = {default}")?;
361 }
362 }
363 }
364 if !is_first {
365 f.write_str(",\n")?;
366 }
367 f.write_char(')')?;
368
369 Ok(())
370 }
371}
372
373pub type UntypedVarDocs = VarDocsT<()>;
375pub type VarDocs = VarDocsT<Option<(EcoString, EcoString, EcoString)>>;
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
380pub struct VarDocsT<T> {
381 pub docs: EcoString,
383 pub return_ty: T,
385 #[serde(skip)]
387 pub def_docs: OnceLock<String>,
388}
389
390impl VarDocs {
391 pub fn def_docs(&self) -> &String {
393 self.def_docs
394 .get_or_init(|| plain_docs_sentence(&self.docs).into())
395 }
396}
397
398pub type TypelessParamDocs = ParamDocsT<()>;
400pub type ParamDocs = ParamDocsT<TypeRepr>;
402
403pub trait DocTextResolver {
405 fn resolve_doc_text(&mut self, docs: &DocText) -> EcoString;
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize, Default)]
411pub struct ParamDocsT<T> {
412 pub name: Interned<str>,
414 pub docs: EcoString,
416 pub cano_type: T,
418 pub default: Option<EcoString>,
420 #[serde(flatten)]
422 pub attrs: ParamAttrs,
423}
424
425impl ParamDocs {
426 pub fn new(ctx: &mut impl DocTextResolver, param: &ParamTy, ty: Option<&Ty>) -> Self {
428 let docs = param
429 .docs
430 .as_ref()
431 .map(|docs| ctx.resolve_doc_text(docs))
432 .unwrap_or_default();
433 Self {
434 name: param.name.as_ref().into(),
435 docs,
436 cano_type: format_ty(ty.or(Some(¶m.ty))),
437 default: param.default.clone(),
438 attrs: param.attrs,
439 }
440 }
441}
442
443pub fn format_ty(ty: Option<&Ty>) -> TypeRepr {
445 let ty = ty?;
446 let short = ty.repr().unwrap_or_else(|| "any".into());
447 let long = eco_format!("{ty:?}");
448 let value = ty.value_repr().unwrap_or_else(|| "".into());
449
450 Some((short, long, value))
451}
452
453pub fn format_ty_short(ty: Option<&Ty>) -> TypeRepr {
455 let ty = ty?;
456 let short = ty.repr().unwrap_or_else(|| "any".into());
457 Some((short.clone(), short.clone(), short))
458}