use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use roc_module::ident::TagName; use roc_region::all::LineInfo; use std::path::PathBuf; use ven_pretty::DocAllocator; pub fn mono_problem<'b>( alloc: &'b RocDocAllocator<'b>, lines: &LineInfo, filename: PathBuf, problem: roc_mono::ir::MonoProblem, ) -> Report<'b> { use roc_exhaustive::Context::*; use roc_exhaustive::Error::*; use roc_mono::ir::MonoProblem::*; match problem { PatternProblem(Incomplete(region, context, missing)) => match context { BadArg => { let doc = alloc.stack([ alloc.reflow("This pattern does not cover all the possibilities:"), alloc.region(lines.convert_region(region)), alloc.reflow("Other possibilities include:"), unhandled_patterns_to_doc_block(alloc, missing), alloc.concat([ alloc.reflow( "I would have to crash if I saw one of those! \ So rather than pattern matching in function arguments, put a ", ), alloc.keyword("when"), alloc.reflow(" in the function body to account for all possibilities."), ]), ]); Report { filename, title: "UNSAFE PATTERN".to_string(), doc, severity: Severity::RuntimeError, } } BadDestruct => { let doc = alloc.stack([ alloc.reflow("This pattern does not cover all the possibilities:"), alloc.region(lines.convert_region(region)), alloc.reflow("Other possibilities include:"), unhandled_patterns_to_doc_block(alloc, missing), alloc.concat([ alloc.reflow( "I would have to crash if I saw one of those! \ You can use a binding to deconstruct a value if there is only ONE possibility. \ Use a " ), alloc.keyword("when"), alloc.reflow(" to account for all possibilities."), ]), ]); Report { filename, title: "UNSAFE PATTERN".to_string(), doc, severity: Severity::RuntimeError, } } BadCase => { let doc = alloc.stack([ alloc.concat([ alloc.reflow("This "), alloc.keyword("when"), alloc.reflow(" does not cover all the possibilities:"), ]), alloc.region(lines.convert_region(region)), alloc.reflow("Other possibilities include:"), unhandled_patterns_to_doc_block(alloc, missing), alloc.reflow( "I would have to crash if I saw one of those! \ Add branches for them!", ), // alloc.hint().append(alloc.reflow("or use a hole.")), ]); Report { filename, title: "UNSAFE PATTERN".to_string(), doc, severity: Severity::RuntimeError, } } }, PatternProblem(Redundant { overall_region, branch_region, index, }) => { let doc = alloc.stack([ alloc.concat([ alloc.reflow("The "), alloc.string(index.ordinal()), alloc.reflow(" pattern is redundant:"), ]), alloc.region_with_subregion( lines.convert_region(overall_region), lines.convert_region(branch_region), ), alloc.reflow( "Any value of this shape will be handled by \ a previous pattern, so this one should be removed.", ), ]); Report { filename, title: "REDUNDANT PATTERN".to_string(), doc, severity: Severity::Warning, } } } } pub fn unhandled_patterns_to_doc_block<'b>( alloc: &'b RocDocAllocator<'b>, patterns: Vec, ) -> RocDocBuilder<'b> { alloc .vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v))) .indent(4) .annotate(Annotation::TypeBlock) } fn pattern_to_doc<'b>( alloc: &'b RocDocAllocator<'b>, pattern: roc_exhaustive::Pattern, ) -> RocDocBuilder<'b> { pattern_to_doc_help(alloc, pattern, false) } const AFTER_TAG_INDENT: &str = " "; fn pattern_to_doc_help<'b>( alloc: &'b RocDocAllocator<'b>, pattern: roc_exhaustive::Pattern, in_type_param: bool, ) -> RocDocBuilder<'b> { use roc_exhaustive::Literal::*; use roc_exhaustive::Pattern::*; use roc_exhaustive::RenderAs; match pattern { Anything => alloc.text("_"), Literal(l) => match l { Int(i) => alloc.text(i.to_string()), U128(i) => alloc.text(i.to_string()), Bit(true) => alloc.text("True"), Bit(false) => alloc.text("False"), Byte(b) => alloc.text(b.to_string()), Float(f) => alloc.text(f.to_string()), Decimal(d) => alloc.text(d.to_string()), Str(s) => alloc.string(s.into()), }, Ctor(union, tag_id, args) => { match union.render_as { RenderAs::Guard => { // #Guard debug_assert_eq!( union.alternatives[tag_id.0 as usize].name, TagName::Global("#Guard".into()) ); debug_assert!(args.len() == 2); let tag = pattern_to_doc_help(alloc, args[1].clone(), in_type_param); alloc.concat([ tag, alloc.text(AFTER_TAG_INDENT), alloc.text("(note the lack of an "), alloc.keyword("if"), alloc.text(" clause)"), ]) } RenderAs::Record(field_names) => { let mut arg_docs = Vec::with_capacity(args.len()); for (label, v) in field_names.into_iter().zip(args.into_iter()) { match &v { Anything => { arg_docs.push(alloc.text(label.to_string())); } Literal(_) | Ctor(_, _, _) => { arg_docs.push( alloc .text(label.to_string()) .append(alloc.reflow(": ")) .append(pattern_to_doc_help(alloc, v, false)), ); } } } alloc .text("{ ") .append(alloc.intersperse(arg_docs, alloc.reflow(", "))) .append(" }") } RenderAs::Tag | RenderAs::Opaque => { let has_args = !args.is_empty(); let arg_docs = args .into_iter() .map(|v| pattern_to_doc_help(alloc, v, true)); let tag = &union.alternatives[tag_id.0 as usize]; let tag_name = match union.render_as { RenderAs::Tag => alloc.tag_name(tag.name.clone()), RenderAs::Opaque => match tag.name { TagName::Private(opaque) => alloc.wrapped_opaque_name(opaque), _ => unreachable!(), }, _ => unreachable!(), }; // We assume the alternatives are sorted. If not, this assert will trigger debug_assert!(tag_id == tag.tag_id); let docs = std::iter::once(tag_name).chain(arg_docs); if in_type_param && has_args { alloc .text("(") .append(alloc.intersperse(docs, alloc.space())) .append(")") } else { alloc.intersperse(docs, alloc.space()) } } } } } }