use core::fmt;
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, OnceLock};
use ecow::{eco_format, EcoString};
use serde::{Deserialize, Serialize};
use super::tidy::*;
use crate::syntax::DeclExpr;
use crate::ty::{Interned, ParamAttrs, ParamTy, StrRef, Ty, TypeVarBounds};
use crate::upstream::plain_docs_sentence;
#[derive(Debug, Clone, Default)]
pub struct DocString {
pub docs: Option<EcoString>,
pub var_bounds: HashMap<DeclExpr, TypeVarBounds>,
pub vars: BTreeMap<StrRef, VarDoc>,
pub res_ty: Option<Ty>,
}
impl DocString {
pub fn as_var(&self) -> VarDoc {
VarDoc {
docs: self.docs.clone().unwrap_or_default(),
ty: self.res_ty.clone(),
}
}
pub fn get_var(&self, name: &StrRef) -> Option<&VarDoc> {
self.vars.get(name)
}
pub fn var_ty(&self, name: &StrRef) -> Option<&Ty> {
self.get_var(name).and_then(|v| v.ty.as_ref())
}
}
#[derive(Debug, Clone, Default)]
pub struct VarDoc {
pub docs: EcoString,
pub ty: Option<Ty>,
}
impl VarDoc {
pub fn to_untyped(&self) -> Arc<UntypedDefDocs> {
Arc::new(UntypedDefDocs::Variable(VarDocsT {
docs: self.docs.clone(),
return_ty: (),
def_docs: OnceLock::new(),
}))
}
}
type TypeRepr = Option<(
EcoString,
EcoString,
EcoString,
)>;
pub type UntypedDefDocs = DefDocsT<()>;
pub type DefDocs = DefDocsT<TypeRepr>;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum DefDocsT<T> {
#[serde(rename = "func")]
Function(Box<SignatureDocsT<T>>),
#[serde(rename = "var")]
Variable(VarDocsT<T>),
#[serde(rename = "module")]
Module(TidyModuleDocs),
#[serde(rename = "plain")]
Plain {
docs: EcoString,
},
}
impl<T> DefDocsT<T> {
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 {
pub fn hover_docs(&self) -> EcoString {
match self {
DefDocs::Function(docs) => docs.hover_docs().clone(),
_ => plain_docs_sentence(self.docs()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignatureDocsT<T> {
pub docs: EcoString,
pub pos: Vec<ParamDocsT<T>>,
pub named: BTreeMap<Interned<str>, ParamDocsT<T>>,
pub rest: Option<ParamDocsT<T>>,
pub ret_ty: T,
#[serde(skip)]
pub hover_docs: OnceLock<EcoString>,
}
impl SignatureDocsT<TypeRepr> {
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)?;
}
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(())
}
}
pub type UntypedSignatureDocs = SignatureDocsT<()>;
pub type SignatureDocs = SignatureDocsT<TypeRepr>;
impl SignatureDocs {
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(())
}
}
pub type UntypedVarDocs = VarDocsT<()>;
pub type VarDocs = VarDocsT<Option<(EcoString, EcoString, EcoString)>>;
#[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())
}
}
pub type TypelessParamDocs = ParamDocsT<()>;
pub type ParamDocs = ParamDocsT<TypeRepr>;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ParamDocsT<T> {
pub name: Interned<str>,
pub docs: EcoString,
pub cano_type: T,
pub default: Option<EcoString>,
#[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(¶m.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))
}