Improve rendering of type-signatures in docs

- [x] Display function arguments using a newline-multiline strategy
    when the signature gets too long. For example:

    ```
    union_with
      ( left left: AssocList<key, value>
      , right right: AssocList<key, value>
      , with with: fn(key, value, value) -> value
      ) -> AssocList<key, value>
    ```

  - [x] Show type-aliases as type-aliases in signatures; provided
    they've been specified as type annotations. Otherwise, fallback to
    the inferred type.

  - [x] Do not show argument names in signatures, but show labels when
    they're present. This reflects more the original intent behind
    labels (which are meant as public-facing documentation).
This commit is contained in:
KtorZ 2022-12-17 12:45:48 +01:00
parent 579030db36
commit 202678e21e
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
2 changed files with 124 additions and 51 deletions

View File

@ -18,6 +18,7 @@ use crate::{
}; };
const INDENT: isize = 2; const INDENT: isize = 2;
const DOCS_MAX_COLUMNS: isize = 80;
pub fn pretty(writer: &mut String, module: UntypedModule, extra: ModuleExtra, src: &str) { pub fn pretty(writer: &mut String, module: UntypedModule, extra: ModuleExtra, src: &str) {
let intermediate = Intermediate { let intermediate = Intermediate {
@ -347,6 +348,7 @@ impl<'comments> Formatter<'comments> {
} => name } => name
.to_doc() .to_doc()
.append(wrap_args( .append(wrap_args(
false,
args.iter() args.iter()
.map(|a| (self.constant_call_arg(a), a.label.is_some())), .map(|a| (self.constant_call_arg(a), a.label.is_some())),
)) ))
@ -362,6 +364,7 @@ impl<'comments> Formatter<'comments> {
.append(".") .append(".")
.append(name.as_str()) .append(name.as_str())
.append(wrap_args( .append(wrap_args(
false,
args.iter() args.iter()
.map(|a| (self.constant_call_arg(a), a.label.is_some())), .map(|a| (self.constant_call_arg(a), a.label.is_some())),
)) ))
@ -380,6 +383,7 @@ impl<'comments> Formatter<'comments> {
Constant::Tuple { elements, .. } => "#" Constant::Tuple { elements, .. } => "#"
.to_doc() .to_doc()
.append(wrap_args( .append(wrap_args(
false,
elements.iter().map(|e| (self.const_expr(e), false)), elements.iter().map(|e| (self.const_expr(e), false)),
)) ))
.group(), .group(),
@ -451,15 +455,19 @@ impl<'comments> Formatter<'comments> {
.. ..
} => "fn" } => "fn"
.to_doc() .to_doc()
.append(wrap_args(args.iter().map(|t| (self.annotation(t), false)))) .append(wrap_args(
false,
args.iter().map(|t| (self.annotation(t), false)),
))
.group() .group()
.append(" ->") .append(" ->")
.append(break_("", " ").append(self.annotation(retrn)).nest(INDENT)), .append(break_("", " ").append(self.annotation(retrn)).nest(INDENT)),
Annotation::Var { name, .. } => name.to_doc(), Annotation::Var { name, .. } => name.to_doc(),
Annotation::Tuple { elems, .. } => "#" Annotation::Tuple { elems, .. } => "#".to_doc().append(wrap_args(
.to_doc() false,
.append(wrap_args(elems.iter().map(|t| (self.annotation(t), false)))), elems.iter().map(|t| (self.annotation(t), false)),
)),
} }
.group() .group()
} }
@ -519,7 +527,10 @@ impl<'comments> Formatter<'comments> {
.append(keyword) .append(keyword)
.append(" ") .append(" ")
.append(name) .append(name)
.append(wrap_args(args.iter().map(|e| (self.fn_arg(e), false)))); .append(wrap_args(
false,
args.iter().map(|e| (self.fn_arg(e), false)),
));
// Add return annotation // Add return annotation
let head = match return_annotation { let head = match return_annotation {
@ -550,7 +561,7 @@ impl<'comments> Formatter<'comments> {
return_annotation: Option<&'a Annotation>, return_annotation: Option<&'a Annotation>,
body: &'a UntypedExpr, body: &'a UntypedExpr,
) -> Document<'a> { ) -> Document<'a> {
let args = wrap_args(args.iter().map(|e| (self.fn_arg(e), false))).group(); let args = wrap_args(false, args.iter().map(|e| (self.fn_arg(e), false))).group();
let body = match body { let body = match body {
UntypedExpr::When { .. } => self.expr(body).force_break(), UntypedExpr::When { .. } => self.expr(body).force_break(),
_ => self.expr(body), _ => self.expr(body),
@ -780,7 +791,10 @@ impl<'comments> Formatter<'comments> {
UntypedExpr::Tuple { elems, .. } => "#" UntypedExpr::Tuple { elems, .. } => "#"
.to_doc() .to_doc()
.append(wrap_args(elems.iter().map(|e| (self.wrap_expr(e), false)))) .append(wrap_args(
false,
elems.iter().map(|e| (self.wrap_expr(e), false)),
))
.group(), .group(),
}; };
commented(document, comments) commented(document, comments)
@ -844,6 +858,7 @@ impl<'comments> Formatter<'comments> {
_ => name _ => name
.append(wrap_args( .append(wrap_args(
false,
args.iter().map(|a| (self.pattern_call_arg(a), is_record)), args.iter().map(|a| (self.pattern_call_arg(a), is_record)),
)) ))
.group(), .group(),
@ -885,6 +900,7 @@ impl<'comments> Formatter<'comments> {
_ => self _ => self
.expr(fun) .expr(fun)
.append(wrap_args( .append(wrap_args(
false,
args.iter() args.iter()
.map(|a| (self.call_arg(a, needs_curly), needs_curly)), .map(|a| (self.call_arg(a, needs_curly), needs_curly)),
)) ))
@ -932,7 +948,9 @@ impl<'comments> Formatter<'comments> {
let spread_doc = "..".to_doc().append(self.expr(&spread.base)); let spread_doc = "..".to_doc().append(self.expr(&spread.base));
let arg_docs = args.iter().map(|a| (self.record_update_arg(a), true)); let arg_docs = args.iter().map(|a| (self.record_update_arg(a), true));
let all_arg_docs = once((spread_doc, true)).chain(arg_docs); let all_arg_docs = once((spread_doc, true)).chain(arg_docs);
constructor_doc.append(wrap_args(all_arg_docs)).group() constructor_doc
.append(wrap_args(false, all_arg_docs))
.group()
} }
pub fn bin_op<'a>( pub fn bin_op<'a>(
@ -1018,6 +1036,7 @@ impl<'comments> Formatter<'comments> {
// x |> fun(_, 2, 3) // x |> fun(_, 2, 3)
self.expr(fun).append( self.expr(fun).append(
wrap_args( wrap_args(
false,
args.iter() args.iter()
.skip(1) .skip(1)
.map(|a| (self.call_arg(a, false), false)), .map(|a| (self.call_arg(a, false), false)),
@ -1026,8 +1045,9 @@ impl<'comments> Formatter<'comments> {
) )
} else { } else {
// x |> fun(1, _, 3) // x |> fun(1, _, 3)
self.expr(fun) self.expr(fun).append(
.append(wrap_args(args.iter().map(|a| (self.call_arg(a, false), false))).group()) wrap_args(false, args.iter().map(|a| (self.call_arg(a, false), false))).group(),
)
} }
} }
@ -1047,7 +1067,7 @@ impl<'comments> Formatter<'comments> {
} }
_ => self.expr(fun).append( _ => self.expr(fun).append(
wrap_args(args.iter().map(|a| (self.call_arg(a, false), false))).group(), wrap_args(false, args.iter().map(|a| (self.call_arg(a, false), false))).group(),
), ),
}, },
@ -1091,7 +1111,9 @@ impl<'comments> Formatter<'comments> {
constructor constructor
.name .name
.to_doc() .to_doc()
.append(wrap_args(constructor.arguments.iter().map( .append(wrap_args(
false,
constructor.arguments.iter().map(
|RecordConstructorArg { |RecordConstructorArg {
label, label,
annotation, annotation,
@ -1101,7 +1123,9 @@ impl<'comments> Formatter<'comments> {
let arg_comments = self.pop_comments(location.start); let arg_comments = self.pop_comments(location.start);
let arg = match label { let arg = match label {
Some(l) => l.to_doc().append(": ").append(self.annotation(annotation)), Some(l) => {
l.to_doc().append(": ").append(self.annotation(annotation))
}
None => self.annotation(annotation), None => self.annotation(annotation),
}; };
@ -1113,7 +1137,8 @@ impl<'comments> Formatter<'comments> {
label.is_some(), label.is_some(),
) )
}, },
))) ),
))
.group() .group()
}; };
@ -1247,30 +1272,72 @@ impl<'comments> Formatter<'comments> {
&mut self, &mut self,
name: &'a str, name: &'a str,
args: &'a [TypedArg], args: &'a [TypedArg],
return_annotation: &'a Option<Annotation>,
return_type: Arc<Type>, return_type: Arc<Type>,
) -> Document<'a> { ) -> Document<'a> {
let mut printer = tipo::pretty::Printer::new(); let head = name
name.to_doc() .to_doc()
.append(self.docs_fn_args(args, &mut printer)) .append(self.docs_fn_args(false, args))
.append(" -> ".to_doc()) .append(" -> ");
.append(printer.print(&return_type))
let tail = self.type_or_annotation(return_annotation, &return_type);
let doc = head.append(tail.clone()).group();
// Wrap arguments on multi-lines if they are lengthy.
if doc
.clone()
.to_pretty_string(DOCS_MAX_COLUMNS)
.contains('\n')
{
let head = name
.to_doc()
.append(self.docs_fn_args(true, args))
.append(" -> ");
head.append(tail).group()
} else {
doc
}
} }
// Will always print the types, even if they were implicit in the original source // Will always print the types, even if they were implicit in the original source
pub fn docs_fn_args<'a>( pub fn docs_fn_args<'a>(&mut self, multiline: bool, args: &'a [TypedArg]) -> Document<'a> {
if multiline {
line().nest(INDENT).append(wrap_args(
true,
args.iter()
.map(|e| (self.docs_fn_arg(e).append(line()), false)),
))
} else {
wrap_args(false, args.iter().map(|e| (self.docs_fn_arg(e), false)))
}
}
fn docs_fn_arg<'a>(&mut self, arg: &'a Arg<Arc<Type>>) -> Document<'a> {
self.docs_fn_arg_name(&arg.arg_name)
.append(self.type_or_annotation(&arg.annotation, &arg.tipo))
.group()
}
fn docs_fn_arg_name<'a>(&mut self, arg_name: &'a ArgName) -> Document<'a> {
match arg_name {
ArgName::Named { .. } | ArgName::Discard { .. } => "".to_doc(),
ArgName::LabeledDiscard { label, .. } | ArgName::NamedLabeled { label, .. } => {
label.to_doc().append(": ")
}
}
}
// Display type-annotation when available, or fallback to inferred type.
fn type_or_annotation<'a>(
&mut self, &mut self,
args: &'a [TypedArg], annotation: &'a Option<Annotation>,
printer: &mut tipo::pretty::Printer, type_info: &Arc<Type>,
) -> Document<'a> { ) -> Document<'a> {
wrap_args(args.iter().map(|arg| { match annotation {
( Some(a) => self.annotation(a),
arg.arg_name None => tipo::pretty::Printer::new().print(type_info),
.to_doc() }
.append(": ".to_doc().append(printer.print(&arg.tipo)))
.group(),
false,
)
}))
} }
fn wrap_expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { fn wrap_expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> {
@ -1403,7 +1470,10 @@ impl<'comments> Formatter<'comments> {
Pattern::Tuple { elems, .. } => "#" Pattern::Tuple { elems, .. } => "#"
.to_doc() .to_doc()
.append(wrap_args(elems.iter().map(|e| (self.pattern(e), false)))) .append(wrap_args(
false,
elems.iter().map(|e| (self.pattern(e), false)),
))
.group(), .group(),
Pattern::List { elements, tail, .. } => { Pattern::List { elements, tail, .. } => {
@ -1559,7 +1629,7 @@ impl<'a> Documentable<'a> for &'a BinOp {
} }
} }
pub fn wrap_args<'a, I>(args: I) -> Document<'a> pub fn wrap_args<'a, I>(multiline: bool, args: I) -> Document<'a>
where where
I: IntoIterator<Item = (Document<'a>, bool)>, I: IntoIterator<Item = (Document<'a>, bool)>,
{ {
@ -1575,6 +1645,8 @@ where
let (open_broken, open_unbroken, close) = if curly { let (open_broken, open_unbroken, close) = if curly {
(" {", " { ", "}") (" {", " { ", "}")
} else if multiline {
("( ", "( ", ")")
} else { } else {
("(", "(", ")") ("(", "(", ")")
}; };

View File

@ -410,6 +410,7 @@ impl DocFunction {
.docs_fn_signature( .docs_fn_signature(
&func_def.name, &func_def.name,
&func_def.arguments, &func_def.arguments,
&func_def.return_annotation,
func_def.return_type.clone(), func_def.return_type.clone(),
) )
.to_pretty_string(MAX_COLUMNS), .to_pretty_string(MAX_COLUMNS),