From 9fe08cafd01f0f6b9349cc90017c127682f2e6c9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 11 Aug 2023 20:59:40 -0400 Subject: [PATCH] Render docs for abilities, tuples, and `as` --- crates/compiler/load_internal/src/docs.rs | 71 ++++++++- crates/compiler/parse/src/ast.rs | 7 + crates/docs/src/lib.rs | 174 +++++++++++++++------- 3 files changed, 199 insertions(+), 53 deletions(-) diff --git a/crates/compiler/load_internal/src/docs.rs b/crates/compiler/load_internal/src/docs.rs index 55db7563b3..b84f701bf2 100644 --- a/crates/compiler/load_internal/src/docs.rs +++ b/crates/compiler/load_internal/src/docs.rs @@ -54,11 +54,30 @@ pub enum TypeAnnotation { fields: Vec, extension: Box, }, + Tuple { + elems: Vec, + extension: Box, + }, Ability { members: Vec, }, Wildcard, NoTypeAnn, + Where { + ann: Box, + implements: Vec, + }, + As { + ann: Box, + name: String, + vars: Vec, + }, +} + +#[derive(Debug, Clone)] +pub struct ImplementsClause { + pub name: String, + pub abilities: Vec, } #[derive(Debug, Clone)] @@ -540,7 +559,57 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> } } ast::TypeAnnotation::Wildcard => TypeAnnotation::Wildcard, - _ => NoTypeAnn, + ast::TypeAnnotation::As(loc_ann, _comments, type_header) => TypeAnnotation::As { + ann: Box::new(type_to_docs(in_func_type_ann, loc_ann.value)), + name: type_header.name.value.to_string(), + vars: type_header + .vars + .iter() + .filter_map(|loc_pattern| match loc_pattern.value { + ast::Pattern::Identifier(ident) => Some(ident.to_string()), + _ => None, + }) + .collect(), + }, + ast::TypeAnnotation::Tuple { elems, ext } => { + let mut doc_elems = Vec::new(); + + for loc_ann in elems.items { + doc_elems.push(type_to_docs(in_func_type_ann, loc_ann.value)); + } + + let extension = match ext { + None => NoTypeAnn, + Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value), + }; + + TypeAnnotation::Tuple { + elems: doc_elems, + extension: Box::new(extension), + } + } + ast::TypeAnnotation::Where(loc_ann, implements) => TypeAnnotation::Where { + ann: Box::new(type_to_docs(in_func_type_ann, loc_ann.value)), + implements: implements + .iter() + .map(|clause| { + let abilities = clause + .value + .abilities + .iter() + .map(|ability| type_to_docs(in_func_type_ann, ability.value)) + .collect(); + + ImplementsClause { + name: clause.value.var.value.item().to_string(), + abilities, + } + }) + .collect(), + }, + ast::TypeAnnotation::Malformed(_) | ast::TypeAnnotation::Inferred => { + TypeAnnotation::NoTypeAnn + } } } diff --git a/crates/compiler/parse/src/ast.rs b/crates/compiler/parse/src/ast.rs index d97d2ce8dd..2cafc3522a 100644 --- a/crates/compiler/parse/src/ast.rs +++ b/crates/compiler/parse/src/ast.rs @@ -38,6 +38,13 @@ impl<'a, T> Spaced<'a, T> { } } } + + pub fn item(&self) -> &T { + match self { + Spaced::Item(answer) => answer, + Spaced::SpaceBefore(next, _spaces) | Spaced::SpaceAfter(next, _spaces) => next.item(), + } + } } impl<'a, T: Debug> Debug for Spaced<'a, T> { diff --git a/crates/docs/src/lib.rs b/crates/docs/src/lib.rs index cbf6fb0251..a3ff1bf596 100644 --- a/crates/docs/src/lib.rs +++ b/crates/docs/src/lib.rs @@ -11,6 +11,7 @@ use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threadin use roc_module::symbol::{Interns, Symbol}; use roc_packaging::cache::{self, RocCacheDir}; use roc_parse::ident::{parse_ident, Accessor, Ident}; +use roc_parse::keyword; use roc_parse::state::State; use roc_region::all::Region; use std::fs; @@ -626,6 +627,7 @@ fn type_annotation_to_html( TypeAnnotation::Function { args, output } => { let mut paren_is_open = false; let mut peekable_args = args.iter().peekable(); + while let Some(arg) = peekable_args.next() { if is_multiline { if !should_be_multiline(arg) { @@ -638,8 +640,7 @@ fn type_annotation_to_html( paren_is_open = true; } - let child_needs_parens = - matches!(arg, TypeAnnotation::Function { args: _, output: _ }); + let child_needs_parens = matches!(arg, TypeAnnotation::Function { .. }); type_annotation_to_html(indent_level, buf, arg, child_needs_parens); if peekable_args.peek().is_some() { @@ -676,77 +677,146 @@ fn type_annotation_to_html( } TypeAnnotation::NoTypeAnn => {} TypeAnnotation::Wildcard => buf.push('*'), + TypeAnnotation::Tuple { elems, extension } => { + let elems_len = elems.len(); + let tuple_indent = indent_level + 1; + + if is_multiline { + new_line(buf); + indent(buf, tuple_indent); + } + + buf.push('('); + + if is_multiline { + new_line(buf); + } + + let next_indent_level = tuple_indent + 1; + + for (index, elem) in elems.iter().enumerate() { + if is_multiline { + indent(buf, next_indent_level); + } + + type_annotation_to_html(next_indent_level, buf, elem, false); + + if is_multiline { + if index < (elems_len - 1) { + buf.push(','); + } + + new_line(buf); + } + } + + if is_multiline { + indent(buf, tuple_indent); + } + + buf.push(')'); + + type_annotation_to_html(indent_level, buf, extension, true); + } + TypeAnnotation::Where { ann, implements } => { + type_annotation_to_html(indent_level, buf, ann, false); + + new_line(buf); + indent(buf, indent_level + 1); + + buf.push_str(keyword::WHERE); + + let multiline_implements = implements + .iter() + .any(|imp| imp.abilities.iter().any(should_be_multiline)); + + for (index, imp) in implements.iter().enumerate() { + if index != 0 { + buf.push(','); + } + + if multiline_implements { + new_line(buf); + indent(buf, indent_level + 2); + } else { + buf.push(' ') + } + + buf.push_str(&imp.name); + buf.push(' '); + buf.push_str(keyword::IMPLEMENTS); + buf.push(' '); + + for (index, ability) in imp.abilities.iter().enumerate() { + if index != 0 { + buf.push_str(" & "); + } + + type_annotation_to_html(indent_level, buf, ability, false); + } + } + } + TypeAnnotation::As { ann, name, vars } => { + type_annotation_to_html(indent_level, buf, ann, true); + buf.push(' '); + buf.push_str(name); + + for var in vars { + buf.push(' '); + buf.push_str(var); + } + } } } fn should_be_multiline(type_ann: &TypeAnnotation) -> bool { match type_ann { TypeAnnotation::TagUnion { tags, extension } => { - let mut is_multiline = should_be_multiline(extension) || tags.len() > 1; - - for tag in tags { - for value in &tag.values { - if is_multiline { - break; - } - is_multiline = should_be_multiline(value); - } - } - - is_multiline + tags.len() > 1 + || should_be_multiline(extension) + || tags + .iter() + .any(|tag| tag.values.iter().any(should_be_multiline)) } TypeAnnotation::Function { args, output } => { - let mut is_multiline = should_be_multiline(output) || args.len() > 2; - - for arg in args { - if is_multiline { - break; - } - - is_multiline = should_be_multiline(arg); - } - - is_multiline + args.len() > 2 || should_be_multiline(output) || args.iter().any(should_be_multiline) } TypeAnnotation::ObscuredTagUnion => false, TypeAnnotation::ObscuredRecord => false, TypeAnnotation::BoundVariable(_) => false, - TypeAnnotation::Apply { parts, .. } => { - let mut is_multiline = false; - - for part in parts { - is_multiline = should_be_multiline(part); - - if is_multiline { - break; - } - } - - is_multiline - } + TypeAnnotation::Apply { parts, .. } => parts.iter().any(should_be_multiline), TypeAnnotation::Record { fields, extension } => { - let mut is_multiline = should_be_multiline(extension) || fields.len() > 1; - - for field in fields { - if is_multiline { - break; - } - match field { + fields.len() > 1 + || should_be_multiline(extension) + || fields.iter().any(|field| match field { RecordField::RecordField { type_annotation, .. - } => is_multiline = should_be_multiline(type_annotation), + } => should_be_multiline(type_annotation), RecordField::OptionalField { type_annotation, .. - } => is_multiline = should_be_multiline(type_annotation), - RecordField::LabelOnly { .. } => {} - } - } - - is_multiline + } => should_be_multiline(type_annotation), + RecordField::LabelOnly { .. } => false, + }) } TypeAnnotation::Ability { .. } => true, TypeAnnotation::Wildcard => false, TypeAnnotation::NoTypeAnn => false, + TypeAnnotation::Tuple { elems, extension } => { + elems.len() > 1 + || should_be_multiline(extension) + || elems.iter().any(should_be_multiline) + } + TypeAnnotation::Where { ann, implements } => { + should_be_multiline(ann) + || implements + .iter() + .any(|imp| imp.abilities.iter().any(should_be_multiline)) + } + TypeAnnotation::As { + ann, + name: _, + vars: _, + } => should_be_multiline(ann), } }