mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
a working state again
This commit is contained in:
parent
3187138084
commit
931567ac4d
3 changed files with 455 additions and 756 deletions
|
@ -1,21 +1,13 @@
|
|||
use crate::report::ReportText::{BinOp, Concat, Module, Region, Value};
|
||||
use bumpalo::Bump;
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_solve::solve;
|
||||
use roc_types::pretty_print::content_to_string;
|
||||
use roc_types::subs::{Content, Subs};
|
||||
use roc_types::types::{write_error_type, ErrorType};
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
|
||||
|
||||
const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#;
|
||||
|
||||
/// A textual report.
|
||||
pub struct Report<'b> {
|
||||
pub title: String,
|
||||
|
@ -96,159 +88,109 @@ pub const DEFAULT_PALETTE: Palette = Palette {
|
|||
typo_suggestion: GREEN_CODE,
|
||||
};
|
||||
|
||||
pub const RED_CODE: &str = "\u{001b}[31m";
|
||||
pub const WHITE_CODE: &str = "\u{001b}[37m";
|
||||
pub const BLUE_CODE: &str = "\u{001b}[34m";
|
||||
pub const YELLOW_CODE: &str = "\u{001b}[33m";
|
||||
pub const GREEN_CODE: &str = "\u{001b}[42m";
|
||||
pub const CYAN_CODE: &str = "\u{001b}[36m";
|
||||
pub const MAGENTA_CODE: &str = "\u{001b}[35m";
|
||||
|
||||
pub const BOLD_CODE: &str = "\u{001b}[1m";
|
||||
|
||||
pub const UNDERLINE_CODE: &str = "\u{001b}[4m";
|
||||
|
||||
pub const RESET_CODE: &str = "\u{001b}[0m";
|
||||
|
||||
pub fn can_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
problem: Problem,
|
||||
) -> Report<'b> {
|
||||
let mut texts = Vec::new();
|
||||
match problem {
|
||||
let doc = match problem {
|
||||
Problem::UnusedDef(symbol, region) => {
|
||||
texts.push(Value(symbol));
|
||||
texts.push(plain_text(" is not used anywhere in your code."));
|
||||
texts.push(Region(region));
|
||||
texts.push(plain_text("If you didn't intend on using "));
|
||||
texts.push(Value(symbol));
|
||||
texts.push(plain_text(
|
||||
" then remove it so future readers of your code don't wonder why it is there.",
|
||||
));
|
||||
}
|
||||
Problem::UnusedImport(module_id, region) => {
|
||||
texts.push(plain_text("Nothing from "));
|
||||
texts.push(Module(module_id));
|
||||
texts.push(plain_text(" is used in this module."));
|
||||
texts.push(Region(region));
|
||||
texts.push(plain_text("Since "));
|
||||
texts.push(Module(module_id));
|
||||
texts.push(plain_text(" isn't used, you don't need to import it."));
|
||||
let line =
|
||||
r#" then remove it so future readers of your code don't wonder why it is there."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.symbol_unqualified(symbol)
|
||||
.append(alloc.reflow(" is not used anywhere in your code.")),
|
||||
alloc.region(region),
|
||||
alloc
|
||||
.reflow("If you didn't intend on using ")
|
||||
.append(alloc.symbol_unqualified(symbol))
|
||||
.append(alloc.reflow(line)),
|
||||
])
|
||||
}
|
||||
Problem::UnusedImport(module_id, region) => alloc.concat(vec![
|
||||
alloc.reflow("Nothing from "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" is used in this module."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("Since "),
|
||||
alloc.module(module_id),
|
||||
alloc.reflow(" isn't used, you don't need to import it."),
|
||||
]),
|
||||
Problem::UnusedArgument(closure_symbol, argument_symbol, region) => {
|
||||
texts.push(Value(closure_symbol));
|
||||
texts.push(plain_text(" doesn't use "));
|
||||
texts.push(Value(argument_symbol));
|
||||
texts.push(plain_text("."));
|
||||
texts.push(Region(region));
|
||||
texts.push(plain_text("If you don't need "));
|
||||
texts.push(Value(argument_symbol));
|
||||
texts.push(plain_text(
|
||||
", then you can just remove it. However, if you really do need ",
|
||||
));
|
||||
texts.push(Value(argument_symbol));
|
||||
texts.push(plain_text(" as an argument of "));
|
||||
texts.push(Value(closure_symbol));
|
||||
texts.push(plain_text(", prefix it with an underscore, like this: \"_"));
|
||||
texts.push(Value(argument_symbol));
|
||||
texts.push(plain_text("\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."));
|
||||
let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used.";
|
||||
|
||||
alloc.concat(vec![
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(" doesn't use "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow("."),
|
||||
alloc.region(region),
|
||||
alloc.reflow("If you don't need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(", then you can just remove it. However, if you really do need "),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(" as an argument of "),
|
||||
alloc.symbol_unqualified(closure_symbol),
|
||||
alloc.reflow(", prefix it with an underscore, like this: \"_"),
|
||||
alloc.symbol_unqualified(argument_symbol),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => {
|
||||
if left_bin_op.value == right_bin_op.value {
|
||||
texts.push(plain_text("Using more than one "));
|
||||
texts.push(BinOp(left_bin_op.value));
|
||||
texts.push(plain_text(
|
||||
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => alloc
|
||||
.stack(vec![
|
||||
if left_bin_op.value == right_bin_op.value {
|
||||
alloc.reflow("Using more than one ")
|
||||
.append(alloc.binop(left_bin_op.value))
|
||||
.append(alloc.reflow(
|
||||
" like this requires parentheses, to clarify how things should be grouped.",
|
||||
))
|
||||
} else {
|
||||
texts.push(plain_text("Using "));
|
||||
texts.push(BinOp(left_bin_op.value));
|
||||
texts.push(plain_text(" and "));
|
||||
texts.push(BinOp(right_bin_op.value));
|
||||
texts.push(plain_text(
|
||||
} else {
|
||||
(alloc.reflow("Using "))
|
||||
.append(alloc.binop(left_bin_op.value))
|
||||
.append(alloc.reflow(" and "))
|
||||
.append(alloc.binop(right_bin_op.value))
|
||||
.append(alloc.reflow(
|
||||
" together requires parentheses, to clarify how they should be grouped.",
|
||||
))
|
||||
}
|
||||
texts.push(Region(region));
|
||||
}
|
||||
},
|
||||
alloc.region(region),
|
||||
]),
|
||||
Problem::UnsupportedPattern(_pattern_type, _region) => {
|
||||
panic!("TODO implement unsupported pattern report")
|
||||
}
|
||||
Problem::ShadowingInAnnotation {
|
||||
original_region,
|
||||
shadow,
|
||||
} => {
|
||||
texts.push(ReportText::RuntimeError(RuntimeError::Shadowing {
|
||||
} => pretty_runtime_error(
|
||||
alloc,
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
}));
|
||||
}
|
||||
Problem::RuntimeError(runtime_error) => {
|
||||
texts.push(ReportText::RuntimeError(runtime_error));
|
||||
}
|
||||
},
|
||||
),
|
||||
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
||||
};
|
||||
|
||||
Report {
|
||||
title: "SYNTAX PROBLEM".to_string(),
|
||||
filename,
|
||||
doc: alloc.nil(),
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty_runtime_error<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
runtime_error: RuntimeError,
|
||||
) -> RocDocBuilder<'b> {
|
||||
match runtime_error {
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
} => {
|
||||
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.text("The ")
|
||||
.append(alloc.ident(shadow.value))
|
||||
.append(alloc.reflow(" name is first defined here:")),
|
||||
alloc.region(original_region),
|
||||
alloc.reflow("But then it's defined a second time here:"),
|
||||
alloc.region(shadow.region),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
|
||||
RuntimeError::LookupNotInScope(loc_name, options) => {
|
||||
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
|
||||
}
|
||||
RuntimeError::CircularDef(idents, _regions) => match idents.first() {
|
||||
Some(ident) => alloc.stack(vec![alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(ident.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||
))]),
|
||||
None => alloc.nil(),
|
||||
},
|
||||
other => {
|
||||
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
// UnsupportedPattern(Region),
|
||||
// UnrecognizedFunctionName(Located<InlinableString>),
|
||||
// ValueNotExposed {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// ModuleNotImported {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// InvalidPrecedence(PrecedenceProblem, Region),
|
||||
// MalformedIdentifier(Box<str>, Region),
|
||||
// MalformedClosure(Region),
|
||||
// FloatOutsideRange(Box<str>),
|
||||
// IntOutsideRange(Box<str>),
|
||||
// InvalidHex(std::num::ParseIntError, Box<str>),
|
||||
// InvalidOctal(std::num::ParseIntError, Box<str>),
|
||||
// InvalidBinary(std::num::ParseIntError, Box<str>),
|
||||
// QualifiedPatternIdent(InlinableString),
|
||||
// CircularDef(
|
||||
// Vec<Located<Ident>>,
|
||||
// Vec<(Region /* pattern */, Region /* expr */)>,
|
||||
// ),
|
||||
//
|
||||
// /// When the author specifies a type annotation but no implementation
|
||||
// NoImplementation,
|
||||
todo!("TODO implement run time error reporting for {:?}", other)
|
||||
}
|
||||
doc,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,152 +235,82 @@ fn not_found<'b>(
|
|||
to_details(default_no, default_yes),
|
||||
])
|
||||
}
|
||||
fn pretty_runtime_error<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
runtime_error: RuntimeError,
|
||||
) -> RocDocBuilder<'b> {
|
||||
match runtime_error {
|
||||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
} => {
|
||||
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ReportText {
|
||||
/// A value. Render it qualified unless it was defined in the current module.
|
||||
Value(Symbol),
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
.text("The ")
|
||||
.append(alloc.ident(shadow.value))
|
||||
.append(alloc.reflow(" name is first defined here:")),
|
||||
alloc.region(original_region),
|
||||
alloc.reflow("But then it's defined a second time here:"),
|
||||
alloc.region(shadow.region),
|
||||
alloc.reflow(line),
|
||||
])
|
||||
}
|
||||
|
||||
/// An identifier, should probably be rendered the same way as a symbol.
|
||||
Name(Ident),
|
||||
|
||||
/// A module,
|
||||
Module(ModuleId),
|
||||
|
||||
/// A type. Render it using roc_types::pretty_print for now, but maybe
|
||||
/// do something fancier later.
|
||||
Type(Content),
|
||||
|
||||
TypeProblem(crate::type_error::Problem),
|
||||
RuntimeError(RuntimeError),
|
||||
TypeError(solve::TypeError),
|
||||
|
||||
ErrorTypeInline(ErrorType),
|
||||
ErrorTypeBlock(Box<ReportText>),
|
||||
|
||||
/// Plain text
|
||||
Plain(Box<str>),
|
||||
|
||||
/// Emphasized text (might be bold, italics, a different color, etc)
|
||||
EmText(Box<str>),
|
||||
|
||||
/// A global tag rendered as code (e.g. a monospace font, or with backticks around it).
|
||||
GlobalTag(Box<str>),
|
||||
|
||||
/// A private tag rendered as code (e.g. a monospace font, or with backticks around it).
|
||||
PrivateTag(Symbol),
|
||||
|
||||
/// A record field name rendered as code (e.g. a monospace font, or with backticks around it).
|
||||
RecordField(Box<str>),
|
||||
|
||||
/// A language keyword like `if`, rendered as code (e.g. a monospace font, or with backticks around it).
|
||||
Keyword(Box<str>),
|
||||
|
||||
/// A region in the original source
|
||||
Region(roc_region::all::Region),
|
||||
|
||||
/// A URL, which should be rendered as a hyperlink.
|
||||
Url(Box<str>),
|
||||
|
||||
/// The documentation for this symbol.
|
||||
Docs(Symbol),
|
||||
|
||||
BinOp(roc_parse::operator::BinOp),
|
||||
|
||||
/// Many ReportText that should be concatenated together.
|
||||
Concat(Vec<ReportText>),
|
||||
|
||||
/// Many ReportText that each get separate lines
|
||||
Stack(Vec<ReportText>),
|
||||
|
||||
Intersperse {
|
||||
separator: Box<ReportText>,
|
||||
items: Vec<ReportText>,
|
||||
},
|
||||
|
||||
Indent(usize, Box<ReportText>),
|
||||
}
|
||||
|
||||
pub fn plain_text(str: &str) -> ReportText {
|
||||
ReportText::Plain(Box::from(str))
|
||||
}
|
||||
|
||||
pub fn name(ident: Ident) -> ReportText {
|
||||
ReportText::Name(ident)
|
||||
}
|
||||
|
||||
pub fn em_text(str: &str) -> ReportText {
|
||||
ReportText::EmText(Box::from(str))
|
||||
}
|
||||
|
||||
pub fn tag_name_text(tag_name: TagName) -> ReportText {
|
||||
match tag_name {
|
||||
TagName::Private(symbol) => ReportText::PrivateTag(symbol),
|
||||
TagName::Global(uppercase) => global_tag_text(uppercase.as_str()),
|
||||
RuntimeError::LookupNotInScope(loc_name, options) => {
|
||||
not_found(alloc, loc_name.region, &loc_name.value, "value", options)
|
||||
}
|
||||
RuntimeError::CircularDef(idents, _regions) => match idents.first() {
|
||||
Some(ident) => alloc.stack(vec![alloc
|
||||
.reflow("The ")
|
||||
.append(alloc.ident(ident.value.clone()))
|
||||
.append(alloc.reflow(
|
||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||
))]),
|
||||
None => alloc.nil(),
|
||||
},
|
||||
other => {
|
||||
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
// UnsupportedPattern(Region),
|
||||
// UnrecognizedFunctionName(Located<InlinableString>),
|
||||
// alloc.symbol_unqualified(NotExposed {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// ModuleNotImported {
|
||||
// module_name: InlinableString,
|
||||
// ident: InlinableString,
|
||||
// region: Region,
|
||||
// },
|
||||
// InvalidPrecedence(PrecedenceProblem, Region),
|
||||
// MalformedIdentifier(Box<str>, Region),
|
||||
// MalformedClosure(Region),
|
||||
// FloatOutsideRange(Box<str>),
|
||||
// IntOutsideRange(Box<str>),
|
||||
// InvalidHex(std::num::ParseIntError, Box<str>),
|
||||
// InvalidOctal(std::num::ParseIntError, Box<str>),
|
||||
// InvalidBinary(std::num::ParseIntError, Box<str>),
|
||||
// QualifiedPatternIdent(InlinableString),
|
||||
// CircularDef(
|
||||
// Vec<Located<Ident>>,
|
||||
// Vec<(Region /* pattern */, Region /* expr */)>,
|
||||
// ),
|
||||
//
|
||||
// /// When the author specifies a type annotation but no implementation
|
||||
// NoImplementation,
|
||||
todo!("TODO implement run time error reporting for {:?}", other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn private_tag_text(symbol: Symbol) -> ReportText {
|
||||
ReportText::PrivateTag(symbol)
|
||||
}
|
||||
|
||||
pub fn global_tag_text(str: &str) -> ReportText {
|
||||
ReportText::GlobalTag(Box::from(str))
|
||||
}
|
||||
|
||||
pub fn record_field_text(str: &str) -> ReportText {
|
||||
ReportText::RecordField(Box::from(str))
|
||||
}
|
||||
|
||||
pub fn keyword_text(str: &str) -> ReportText {
|
||||
ReportText::Keyword(Box::from(str))
|
||||
}
|
||||
|
||||
pub fn error_type_inline(err: ErrorType) -> ReportText {
|
||||
ReportText::ErrorTypeInline(err)
|
||||
}
|
||||
|
||||
pub fn url(str: &str) -> ReportText {
|
||||
ReportText::Url(Box::from(str))
|
||||
}
|
||||
|
||||
pub fn concat(values: Vec<ReportText>) -> ReportText {
|
||||
ReportText::Concat(values)
|
||||
}
|
||||
|
||||
pub fn separate(values: Vec<ReportText>) -> ReportText {
|
||||
// TODO I think this should be a possibly-breaking space
|
||||
intersperse(plain_text(" "), values)
|
||||
}
|
||||
|
||||
pub fn intersperse(separator: ReportText, items: Vec<ReportText>) -> ReportText {
|
||||
ReportText::Intersperse {
|
||||
separator: Box::new(separator),
|
||||
items,
|
||||
}
|
||||
}
|
||||
|
||||
pub const RED_CODE: &str = "\u{001b}[31m";
|
||||
pub const WHITE_CODE: &str = "\u{001b}[37m";
|
||||
pub const BLUE_CODE: &str = "\u{001b}[34m";
|
||||
pub const YELLOW_CODE: &str = "\u{001b}[33m";
|
||||
pub const GREEN_CODE: &str = "\u{001b}[42m";
|
||||
pub const CYAN_CODE: &str = "\u{001b}[36m";
|
||||
pub const MAGENTA_CODE: &str = "\u{001b}[35m";
|
||||
|
||||
pub const BOLD_CODE: &str = "\u{001b}[1m";
|
||||
|
||||
pub const UNDERLINE_CODE: &str = "\u{001b}[4m";
|
||||
|
||||
pub const RESET_CODE: &str = "\u{001b}[0m";
|
||||
|
||||
// define custom allocator struct so we can `impl RocDocAllocator` custom helpers
|
||||
pub struct RocDocAllocator<'a> {
|
||||
upstream: BoxAllocator,
|
||||
subs: &'a mut Subs,
|
||||
home: ModuleId,
|
||||
src_lines: &'a [&'a str],
|
||||
interns: &'a Interns,
|
||||
pub home: ModuleId,
|
||||
pub interns: &'a Interns,
|
||||
}
|
||||
|
||||
pub type RocDocBuilder<'b> = DocBuilder<'b, RocDocAllocator<'b>, Annotation>;
|
||||
|
@ -469,15 +341,9 @@ where
|
|||
}
|
||||
|
||||
impl<'a> RocDocAllocator<'a> {
|
||||
pub fn new(
|
||||
subs: &'a mut Subs,
|
||||
src_lines: &'a [&'a str],
|
||||
home: ModuleId,
|
||||
interns: &'a Interns,
|
||||
) -> Self {
|
||||
pub fn new(src_lines: &'a [&'a str], home: ModuleId, interns: &'a Interns) -> Self {
|
||||
RocDocAllocator {
|
||||
upstream: BoxAllocator,
|
||||
subs,
|
||||
home,
|
||||
src_lines,
|
||||
interns,
|
||||
|
@ -516,6 +382,12 @@ impl<'a> RocDocAllocator<'a> {
|
|||
self.string(content.to_owned()).annotate(Annotation::Alias)
|
||||
}
|
||||
|
||||
pub fn type_variable(&'a self, content: Lowercase) -> DocBuilder<'a, Self, Annotation> {
|
||||
// currently not annotated
|
||||
self.string(content.to_string())
|
||||
.annotate(Annotation::TypeVariable)
|
||||
}
|
||||
|
||||
pub fn tag_name(&'a self, tn: TagName) -> DocBuilder<'a, Self, Annotation> {
|
||||
match tn {
|
||||
TagName::Global(uppercase) => self.global_tag_name(uppercase),
|
||||
|
@ -569,11 +441,24 @@ impl<'a> RocDocAllocator<'a> {
|
|||
self.text(format!("{}", uppercase))
|
||||
.annotate(Annotation::GlobalTag)
|
||||
}
|
||||
|
||||
pub fn record_field(&'a self, lowercase: Lowercase) -> DocBuilder<'a, Self, Annotation> {
|
||||
self.text(format!(".{}", lowercase))
|
||||
.annotate(Annotation::RecordField)
|
||||
}
|
||||
|
||||
pub fn module(&'a self, module_id: ModuleId) -> DocBuilder<'a, Self, Annotation> {
|
||||
self.text(format!("{}", self.interns.module_name(module_id)))
|
||||
.annotate(Annotation::Module)
|
||||
}
|
||||
|
||||
pub fn binop(
|
||||
&'a self,
|
||||
content: roc_parse::operator::BinOp,
|
||||
) -> DocBuilder<'a, Self, Annotation> {
|
||||
self.text(content.to_string()).annotate(Annotation::BinOp)
|
||||
}
|
||||
|
||||
pub fn type_block(
|
||||
&'a self,
|
||||
content: DocBuilder<'a, Self, Annotation>,
|
||||
|
@ -581,6 +466,12 @@ impl<'a> RocDocAllocator<'a> {
|
|||
content.annotate(Annotation::TypeBlock).indent(4)
|
||||
}
|
||||
|
||||
pub fn hint(&'a self) -> DocBuilder<'a, Self, Annotation> {
|
||||
self.text("Hint:")
|
||||
.append(self.softline())
|
||||
.annotate(Annotation::Hint)
|
||||
}
|
||||
|
||||
pub fn region(&'a self, region: roc_region::all::Region) -> DocBuilder<'a, Self, Annotation> {
|
||||
let max_line_number_length = (region.end_line + 1).to_string().len();
|
||||
let indent = 2;
|
||||
|
@ -645,6 +536,10 @@ impl<'a> RocDocAllocator<'a> {
|
|||
.append(rest_of_line);
|
||||
|
||||
result = result.append(source_line);
|
||||
|
||||
if i != region.end_line {
|
||||
result = result.append(self.line())
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
|
@ -748,6 +643,7 @@ where
|
|||
self.write_str("<")?;
|
||||
}
|
||||
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
|
||||
| TypeVariable
|
||||
if !self.in_type_block =>
|
||||
{
|
||||
self.write_str("`")?;
|
||||
|
@ -775,6 +671,7 @@ where
|
|||
self.write_str(">")?;
|
||||
}
|
||||
GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion
|
||||
| TypeVariable
|
||||
if !self.in_type_block =>
|
||||
{
|
||||
self.write_str("`")?;
|
||||
|
@ -878,366 +775,3 @@ where
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ReportText {
|
||||
/// General idea: this function puts all the characters in. Any styling (emphasis, colors,
|
||||
/// monospace font, etc) is done in the CiWrite and ColorWrite `RenderAnnotated` instances.
|
||||
pub fn pretty<'b>(
|
||||
self,
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
) -> DocBuilder<'b, RocDocAllocator<'b>, Annotation> {
|
||||
use ReportText::*;
|
||||
|
||||
match self {
|
||||
Url(url) => alloc.text(url.into_string()).annotate(Annotation::Url),
|
||||
Plain(string) => alloc
|
||||
.text(string.into_string())
|
||||
.annotate(Annotation::PlainText),
|
||||
EmText(string) => alloc
|
||||
.text(string.into_string())
|
||||
.annotate(Annotation::Emphasized),
|
||||
Keyword(string) => alloc
|
||||
.text(string.into_string())
|
||||
.annotate(Annotation::Keyword),
|
||||
GlobalTag(string) => alloc
|
||||
.text(string.into_string())
|
||||
.annotate(Annotation::GlobalTag),
|
||||
RecordField(string) => alloc
|
||||
.text(format!(".{}", string))
|
||||
.annotate(Annotation::RecordField),
|
||||
PrivateTag(symbol) => alloc.private_tag_name(symbol),
|
||||
Value(symbol) => alloc.symbol_foreign_qualified(symbol),
|
||||
|
||||
Module(module_id) => alloc
|
||||
.text(format!("{}", alloc.interns.module_name(module_id)))
|
||||
.annotate(Annotation::Module),
|
||||
Type(content) => match content {
|
||||
Content::FlexVar(_) | Content::RigidVar(_) => alloc
|
||||
.text(content_to_string(
|
||||
content,
|
||||
alloc.subs,
|
||||
alloc.home,
|
||||
alloc.interns,
|
||||
))
|
||||
.annotate(Annotation::TypeVariable),
|
||||
|
||||
Content::Structure(_) => alloc
|
||||
.text(content_to_string(
|
||||
content,
|
||||
alloc.subs,
|
||||
alloc.home,
|
||||
alloc.interns,
|
||||
))
|
||||
.annotate(Annotation::Structure),
|
||||
|
||||
Content::Alias(_, _, _) => alloc
|
||||
.text(content_to_string(
|
||||
content,
|
||||
alloc.subs,
|
||||
alloc.home,
|
||||
alloc.interns,
|
||||
))
|
||||
.annotate(Annotation::Alias),
|
||||
|
||||
Content::Error => alloc.text(content_to_string(
|
||||
content,
|
||||
alloc.subs,
|
||||
alloc.home,
|
||||
alloc.interns,
|
||||
)),
|
||||
},
|
||||
ErrorTypeInline(error_type) => alloc
|
||||
.nil()
|
||||
.append(alloc.hardline())
|
||||
.append(
|
||||
alloc
|
||||
.text(write_error_type(alloc.home, alloc.interns, error_type))
|
||||
.indent(4),
|
||||
)
|
||||
.append(alloc.hardline()),
|
||||
|
||||
ErrorTypeBlock(error_type) => alloc
|
||||
.nil()
|
||||
.append(alloc.hardline())
|
||||
.append(
|
||||
error_type
|
||||
.pretty(alloc)
|
||||
.indent(4)
|
||||
.annotate(Annotation::TypeBlock),
|
||||
)
|
||||
.append(alloc.hardline()),
|
||||
|
||||
Indent(n, nested) => {
|
||||
let rest = nested.pretty(alloc);
|
||||
alloc.nil().append(rest).indent(n)
|
||||
}
|
||||
Docs(_) => {
|
||||
panic!("TODO implment docs");
|
||||
}
|
||||
Concat(report_texts) => {
|
||||
alloc.concat(report_texts.into_iter().map(|rep| rep.pretty(alloc)))
|
||||
}
|
||||
Stack(report_texts) => alloc
|
||||
.intersperse(
|
||||
report_texts.into_iter().map(|rep| (rep.pretty(alloc))),
|
||||
alloc.hardline(),
|
||||
)
|
||||
.append(alloc.hardline()),
|
||||
Intersperse { separator, items } => alloc.intersperse(
|
||||
items
|
||||
.into_iter()
|
||||
.map(|rep| (rep.pretty(alloc)))
|
||||
.collect::<Vec<_>>(),
|
||||
separator.pretty(alloc),
|
||||
),
|
||||
BinOp(bin_op) => alloc.text(bin_op.to_string()).annotate(Annotation::BinOp),
|
||||
Region(region) => {
|
||||
let max_line_number_length = (region.end_line + 1).to_string().len();
|
||||
let indent = 2;
|
||||
|
||||
let body = if region.start_line == region.end_line {
|
||||
let i = region.start_line;
|
||||
|
||||
let line_number_string = (i + 1).to_string();
|
||||
let line_number = line_number_string;
|
||||
let this_line_number_length = line_number.len();
|
||||
|
||||
let line = alloc.src_lines[i as usize];
|
||||
let rest_of_line = if line.trim().is_empty() {
|
||||
alloc.nil()
|
||||
} else {
|
||||
alloc
|
||||
.nil()
|
||||
.append(alloc.text(line).indent(2))
|
||||
.annotate(Annotation::CodeBlock)
|
||||
};
|
||||
|
||||
let source_line = alloc
|
||||
.line()
|
||||
.append(
|
||||
alloc
|
||||
.text(" ".repeat(max_line_number_length - this_line_number_length)),
|
||||
)
|
||||
.append(alloc.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(alloc.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(rest_of_line);
|
||||
|
||||
let highlight_line = alloc
|
||||
.line()
|
||||
.append(alloc.text(" ".repeat(max_line_number_length)))
|
||||
.append(alloc.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(
|
||||
alloc
|
||||
.text(" ".repeat(region.start_col as usize))
|
||||
.indent(indent),
|
||||
)
|
||||
.append(
|
||||
alloc
|
||||
.text("^".repeat((region.end_col - region.start_col) as usize))
|
||||
.annotate(Annotation::Error),
|
||||
);
|
||||
|
||||
source_line.append(highlight_line)
|
||||
} else {
|
||||
let mut result = alloc.nil();
|
||||
for i in region.start_line..=region.end_line {
|
||||
let line_number_string = (i + 1).to_string();
|
||||
let line_number = line_number_string;
|
||||
let this_line_number_length = line_number.len();
|
||||
|
||||
let line = alloc.src_lines[i as usize];
|
||||
let rest_of_line = if !line.trim().is_empty() {
|
||||
alloc
|
||||
.text(line)
|
||||
.annotate(Annotation::CodeBlock)
|
||||
.indent(indent)
|
||||
} else {
|
||||
alloc.nil()
|
||||
};
|
||||
|
||||
let source_line =
|
||||
alloc
|
||||
.line()
|
||||
.append(alloc.text(
|
||||
" ".repeat(max_line_number_length - this_line_number_length),
|
||||
))
|
||||
.append(alloc.text(line_number).annotate(Annotation::LineNumber))
|
||||
.append(alloc.text(" ┆").annotate(Annotation::GutterBar))
|
||||
.append(alloc.text(">").annotate(Annotation::Error))
|
||||
.append(rest_of_line);
|
||||
|
||||
result = result.append(source_line);
|
||||
}
|
||||
|
||||
result
|
||||
};
|
||||
alloc
|
||||
.nil()
|
||||
.append(alloc.line())
|
||||
.append(body)
|
||||
.append(alloc.line())
|
||||
.append(alloc.line())
|
||||
}
|
||||
Name(ident) => alloc
|
||||
.text(format!("{}", ident.as_inline_str()))
|
||||
.annotate(Annotation::Symbol),
|
||||
TypeProblem(problem) => Self::type_problem_to_pretty(alloc, problem),
|
||||
RuntimeError(problem) => pretty_runtime_error(alloc, problem),
|
||||
TypeError(problem) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_problem_to_pretty<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
problem: crate::type_error::Problem,
|
||||
) -> DocBuilder<'b, RocDocAllocator<'b>, Annotation> {
|
||||
use crate::type_error::suggest;
|
||||
use crate::type_error::Problem::*;
|
||||
|
||||
match problem {
|
||||
FieldTypo(typo, possibilities) => {
|
||||
let suggestions = suggest::sort(typo.as_str(), possibilities);
|
||||
|
||||
match suggestions.get(0) {
|
||||
None => alloc.nil(),
|
||||
Some(nearest) => {
|
||||
let typo_str = format!("{}", typo);
|
||||
let nearest_str = format!("{}", nearest);
|
||||
|
||||
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
||||
let suggestion =
|
||||
alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
||||
|
||||
let hint1 = Self::hint(alloc)
|
||||
.append(alloc.reflow("Seems like a record field typo. Maybe "))
|
||||
.append(found)
|
||||
.append(alloc.reflow(" should be "))
|
||||
.append(suggestion)
|
||||
.append(alloc.text("?"));
|
||||
|
||||
let hint2 = Self::hint(alloc).append(alloc.reflow(ADD_ANNOTATIONS));
|
||||
|
||||
hint1
|
||||
.append(alloc.line())
|
||||
.append(alloc.line())
|
||||
.append(hint2)
|
||||
}
|
||||
}
|
||||
}
|
||||
FieldsMissing(missing) => match missing.split_last() {
|
||||
None => alloc.nil(),
|
||||
Some((f1, [])) => Self::hint(alloc)
|
||||
.append(alloc.reflow("Looks like the "))
|
||||
.append(f1.as_str().to_owned())
|
||||
.append(alloc.reflow(" field is missing.")),
|
||||
Some((last, init)) => {
|
||||
let separator = alloc.reflow(", ");
|
||||
|
||||
Self::hint(alloc)
|
||||
.append(alloc.reflow("Looks like the "))
|
||||
.append(
|
||||
alloc
|
||||
.intersperse(init.iter().map(|v| v.as_str().to_owned()), separator),
|
||||
)
|
||||
.append(alloc.reflow(" and "))
|
||||
.append(alloc.text(last.as_str().to_owned()))
|
||||
.append(alloc.reflow(" fields are missing."))
|
||||
}
|
||||
},
|
||||
TagTypo(typo, possibilities_tn) => {
|
||||
let possibilities = possibilities_tn
|
||||
.into_iter()
|
||||
.map(|tag_name| tag_name.into_string(alloc.interns, alloc.home))
|
||||
.collect();
|
||||
let typo_str = format!("{}", typo.into_string(alloc.interns, alloc.home));
|
||||
let suggestions = suggest::sort(&typo_str, possibilities);
|
||||
|
||||
match suggestions.get(0) {
|
||||
None => alloc.nil(),
|
||||
Some(nearest) => {
|
||||
let nearest_str = format!("{}", nearest);
|
||||
|
||||
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
||||
let suggestion =
|
||||
alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
||||
|
||||
let hint1 = Self::hint(alloc)
|
||||
.append(alloc.reflow("Seems like a tag typo. Maybe "))
|
||||
.append(found)
|
||||
.append(" should be ")
|
||||
.append(suggestion)
|
||||
.append(alloc.text("?"));
|
||||
|
||||
let hint2 = Self::hint(alloc).append(alloc.reflow(ADD_ANNOTATIONS));
|
||||
|
||||
hint1
|
||||
.append(alloc.line())
|
||||
.append(alloc.line())
|
||||
.append(hint2)
|
||||
}
|
||||
}
|
||||
}
|
||||
ArityMismatch(found, expected) => {
|
||||
let line = if found < expected {
|
||||
format!(
|
||||
"It looks like it takes too few arguments. I was expecting {} more.",
|
||||
expected - found
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"It looks like it takes too many arguments. I'm seeing {} extra.",
|
||||
found - expected
|
||||
)
|
||||
};
|
||||
|
||||
Self::hint(alloc).append(line)
|
||||
}
|
||||
|
||||
BadRigidVar(x, tipe) => {
|
||||
use ErrorType::*;
|
||||
|
||||
let bad_rigid_var = |name: &str, a_thing| {
|
||||
let text = format!(
|
||||
r#"The type annotation uses the type variable `{}` to say that this definition can produce any type of value. But in the body I see that it will only produce {} of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general?"#,
|
||||
name, a_thing
|
||||
);
|
||||
|
||||
Self::hint(alloc).append(alloc.string(text))
|
||||
};
|
||||
|
||||
let bad_double_rigid = |a, b| {
|
||||
let text = format!(
|
||||
r#"Your type annotation uses {} and {} as separate type variables. Your code seems to be saying they are the same though. Maybe they should be the same your type annotation? Maybe your code uses them in a weird way?"#,
|
||||
a, b
|
||||
);
|
||||
|
||||
Self::hint(alloc).append(alloc.string(text))
|
||||
};
|
||||
|
||||
match tipe {
|
||||
Infinite | Error | FlexVar(_) => alloc.nil(),
|
||||
RigidVar(y) => bad_double_rigid(x.as_str(), y.as_str()),
|
||||
Function(_, _) => bad_rigid_var(x.as_str(), "a function value"),
|
||||
Record(_, _) => bad_rigid_var(x.as_str(), "a record value"),
|
||||
TagUnion(_, _) | RecursiveTagUnion(_, _, _) => {
|
||||
bad_rigid_var(x.as_str(), "a tag value")
|
||||
}
|
||||
Alias(symbol, _, _) | Type(symbol, _) => bad_rigid_var(
|
||||
x.as_str(),
|
||||
&format!("a {} value", symbol.ident_string(alloc.interns)),
|
||||
),
|
||||
Boolean(_) => bad_rigid_var(x.as_str(), "a uniqueness attribute value"),
|
||||
}
|
||||
}
|
||||
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn hint<'b>(alloc: &'b RocDocAllocator<'b>) -> DocBuilder<'b, RocDocAllocator<'b>, Annotation> {
|
||||
alloc
|
||||
.text("Hint:")
|
||||
.append(alloc.softline())
|
||||
.annotate(Annotation::Hint)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,11 @@ use roc_types::pretty_print::Parens;
|
|||
use roc_types::types::{Category, ErrorType, PatternCategory, Reason, TypeExt};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::report::{Report, RocDocAllocator, RocDocBuilder};
|
||||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#;
|
||||
|
||||
pub fn type_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
filename: PathBuf,
|
||||
|
@ -60,7 +62,7 @@ fn report_mismatch<'b>(
|
|||
problem: RocDocBuilder<'b>,
|
||||
this_is: RocDocBuilder<'b>,
|
||||
instead_of: RocDocBuilder<'b>,
|
||||
further_details: RocDocBuilder<'b>,
|
||||
further_details: Option<RocDocBuilder<'b>>,
|
||||
) -> Report<'b> {
|
||||
let lines = vec![
|
||||
problem,
|
||||
|
@ -142,13 +144,13 @@ fn to_expr_report<'b>(
|
|||
expected_type,
|
||||
add_category(alloc, alloc.text("It is"), &category),
|
||||
alloc.text("But you are trying to use it as:"),
|
||||
alloc.nil()
|
||||
None,
|
||||
);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "TYPE MISMATCH".to_string(),
|
||||
doc: alloc.concat(vec![
|
||||
doc: alloc.stack(vec![
|
||||
alloc.text("This expression is used in an unexpected way:"),
|
||||
alloc.region(expr_region),
|
||||
comparison,
|
||||
|
@ -163,7 +165,7 @@ fn to_expr_report<'b>(
|
|||
alloc.concat(vec![alloc.text("the "), doc.clone()]),
|
||||
alloc.concat(vec![alloc.text(" on "), doc]),
|
||||
),
|
||||
None => (alloc.text("this"), alloc.text("")),
|
||||
None => (alloc.text("this"), alloc.nil()),
|
||||
};
|
||||
|
||||
// TODO special-case 2-branch if
|
||||
|
@ -201,7 +203,7 @@ fn to_expr_report<'b>(
|
|||
on_name_text,
|
||||
alloc.text(" says it should be:"),
|
||||
]),
|
||||
alloc.nil()
|
||||
None,
|
||||
);
|
||||
|
||||
Report {
|
||||
|
@ -220,7 +222,8 @@ fn to_expr_report<'b>(
|
|||
alloc.text("This "),
|
||||
alloc.keyword("if"),
|
||||
alloc.text(" condition needs to be a "),
|
||||
alloc.type_str("Bool"), alloc.text(":"),
|
||||
alloc.type_str("Bool"),
|
||||
alloc.text(":"),
|
||||
]);
|
||||
|
||||
report_bad_type(
|
||||
|
@ -237,7 +240,8 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
alloc.text("But I need every "),
|
||||
alloc.keyword("if"),
|
||||
alloc.text(" condition to evaluate to a "),
|
||||
alloc.type_str("Bool"), alloc.text("—either "),
|
||||
alloc.type_str("Bool"),
|
||||
alloc.text("—either "),
|
||||
alloc.global_tag_name("True".into()),
|
||||
alloc.text(" or "),
|
||||
alloc.global_tag_name("False".into()),
|
||||
|
@ -311,11 +315,12 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
alloc.keyword("then"),
|
||||
alloc.text(" branch has the type:"),
|
||||
]),
|
||||
Some(
|
||||
alloc.concat(vec![
|
||||
alloc.text("I need all branches in an "),
|
||||
alloc.keyword("if"),
|
||||
alloc.text(" to have the same type!"),
|
||||
]),
|
||||
])),
|
||||
),
|
||||
_ => {
|
||||
let ith = int_to_ordinal(index);
|
||||
|
@ -334,11 +339,11 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
)),
|
||||
alloc.string(format!("The {} branch is", ith)),
|
||||
alloc.text("But all the previous branches have type:"),
|
||||
alloc.concat(vec![
|
||||
Some(alloc.concat(vec![
|
||||
alloc.text("I need all branches in an "),
|
||||
alloc.keyword("if"),
|
||||
alloc.text(" to have the same type!"),
|
||||
]),
|
||||
])),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -361,11 +366,11 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
]),
|
||||
alloc.string(format!("The {} branch is", ith)),
|
||||
alloc.text("But all the previous branches have type:"),
|
||||
alloc.concat(vec![
|
||||
Some(alloc.concat(vec![
|
||||
alloc.text("I need all branches of a "),
|
||||
alloc.keyword("when"),
|
||||
alloc.text(" to have the same type!"),
|
||||
]),
|
||||
])),
|
||||
)
|
||||
}
|
||||
Reason::ElemInList { index } => {
|
||||
|
@ -387,7 +392,7 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
)),
|
||||
alloc.string(format!("The {} element is", ith)),
|
||||
alloc.text("But all the previous elements in the list have type:"),
|
||||
alloc.text("I need all elements of a list to have the same type!"),
|
||||
Some(alloc.text("I need all elements of a list to have the same type!")),
|
||||
)
|
||||
}
|
||||
Reason::RecordUpdateValue(field) => report_mismatch(
|
||||
|
@ -405,13 +410,13 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
]),
|
||||
alloc.concat(vec![
|
||||
alloc.text("You are trying to update "),
|
||||
alloc.record_field(field.to_owned()),
|
||||
alloc.record_field(field),
|
||||
alloc.text(" to be"),
|
||||
]),
|
||||
alloc.text("But it should be:"),
|
||||
alloc.text(
|
||||
Some(alloc.text(
|
||||
r#"Record update syntax does not allow you to change the type of fields. You can achieve that with record literal syntax."#,
|
||||
),
|
||||
)),
|
||||
),
|
||||
Reason::FnCall { name, arity } => match count_arguments(&found) {
|
||||
0 => {
|
||||
|
@ -443,7 +448,7 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
Report {
|
||||
filename,
|
||||
title: "TOO MANY ARGS".to_string(),
|
||||
doc: alloc.concat(lines),
|
||||
doc: alloc.stack(lines),
|
||||
}
|
||||
}
|
||||
n => {
|
||||
|
@ -477,7 +482,7 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
Report {
|
||||
filename,
|
||||
title: "TOO MANY ARGS".to_string(),
|
||||
doc: alloc.concat(lines),
|
||||
doc: alloc.stack(lines),
|
||||
}
|
||||
} else {
|
||||
let lines = vec![
|
||||
|
@ -502,7 +507,7 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
Report {
|
||||
filename,
|
||||
title: "TOO FEW ARGS".to_string(),
|
||||
doc: alloc.concat(lines),
|
||||
doc: alloc.stack(lines),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -534,7 +539,7 @@ alloc.type_str("Bool"), alloc.text(":"),
|
|||
this_function,
|
||||
alloc.string(format!(" needs the {} argument to be:", ith)),
|
||||
]),
|
||||
alloc.text(""),
|
||||
None,
|
||||
)
|
||||
}
|
||||
other => {
|
||||
|
@ -572,7 +577,7 @@ fn type_comparison<'b>(
|
|||
expected: ErrorType,
|
||||
i_am_seeing: RocDocBuilder<'b>,
|
||||
instead_of: RocDocBuilder<'b>,
|
||||
context_hints: RocDocBuilder<'b>,
|
||||
context_hints: Option<RocDocBuilder<'b>>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
let comparison = to_comparison(alloc, actual, expected);
|
||||
|
||||
|
@ -581,10 +586,13 @@ fn type_comparison<'b>(
|
|||
comparison.actual,
|
||||
instead_of,
|
||||
comparison.expected,
|
||||
context_hints,
|
||||
];
|
||||
|
||||
lines.extend(problems_to_hint(comparison.problems));
|
||||
if context_hints.is_some() {
|
||||
lines.push(alloc.concat(context_hints));
|
||||
}
|
||||
|
||||
lines.extend(problems_to_hint(alloc, comparison.problems));
|
||||
|
||||
alloc.stack(lines)
|
||||
}
|
||||
|
@ -600,7 +608,7 @@ fn lone_type<'b>(
|
|||
|
||||
let mut lines = vec![i_am_seeing, comparison.actual, further_details];
|
||||
|
||||
lines.extend(problems_to_hint(comparison.problems));
|
||||
lines.extend(problems_to_hint(alloc, comparison.problems));
|
||||
|
||||
alloc.stack(lines)
|
||||
}
|
||||
|
@ -689,7 +697,7 @@ fn to_pattern_report<'b>(
|
|||
|
||||
match expected {
|
||||
PExpected::NoExpectation(expected_type) => {
|
||||
let doc = alloc.concat(vec![
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.text("This pattern is being used in an unexpected way:"),
|
||||
alloc.region(expr_region),
|
||||
pattern_type_comparision(
|
||||
|
@ -804,7 +812,7 @@ fn pattern_type_comparision<'b>(
|
|||
comparison.expected,
|
||||
];
|
||||
|
||||
lines.extend(problems_to_hint(comparison.problems));
|
||||
lines.extend(problems_to_hint(alloc, comparison.problems));
|
||||
lines.extend(reason_hints);
|
||||
|
||||
alloc.stack(lines)
|
||||
|
@ -876,13 +884,15 @@ pub enum Problem {
|
|||
BadRigidVar(Lowercase, ErrorType),
|
||||
}
|
||||
|
||||
fn problems_to_hint<'b>(mut problems: Vec<Problem>) -> Option<RocDocBuilder<'b>> {
|
||||
fn problems_to_hint<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
mut problems: Vec<Problem>,
|
||||
) -> Option<RocDocBuilder<'b>> {
|
||||
if problems.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let problem = problems.remove(problems.len() - 1);
|
||||
// Some(RocDocBuilder<'b>::TypeProblem(problem))
|
||||
todo!()
|
||||
Some(type_problem_to_pretty(alloc, problem))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1071,7 +1081,7 @@ pub fn to_doc<'b>(
|
|||
report_text::tag_union(
|
||||
alloc,
|
||||
tags.into_iter()
|
||||
.map(|(k, v)| (alloc.tag_name(k.clone()), v))
|
||||
.map(|(k, v)| (alloc.tag_name(k), v))
|
||||
.collect(),
|
||||
ext_to_doc(alloc, ext),
|
||||
)
|
||||
|
@ -1095,7 +1105,7 @@ pub fn to_doc<'b>(
|
|||
alloc,
|
||||
to_doc(alloc, Parens::Unnecessary, *rec_var),
|
||||
tags.into_iter()
|
||||
.map(|(k, v)| (alloc.tag_name(k.clone()), v))
|
||||
.map(|(k, v)| (alloc.tag_name(k), v))
|
||||
.collect(),
|
||||
ext_to_doc(alloc, ext),
|
||||
)
|
||||
|
@ -1236,9 +1246,7 @@ fn to_diff<'b>(
|
|||
let right = to_doc(alloc, Parens::Unnecessary, type2);
|
||||
|
||||
let problems = match pair {
|
||||
(RigidVar(x), other) | (other, RigidVar(x)) => {
|
||||
vec![Problem::BadRigidVar(x.clone(), other.clone())]
|
||||
}
|
||||
(RigidVar(x), other) | (other, RigidVar(x)) => vec![Problem::BadRigidVar(x, other)],
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
|
@ -1432,7 +1440,7 @@ fn diff_tag_union<'b>(
|
|||
|
||||
Diff {
|
||||
left: (field.clone(), alloc.tag_name(field.clone()), diff.left),
|
||||
right: (field.clone(), alloc.tag_name(field.clone()), diff.right),
|
||||
right: (field.clone(), alloc.tag_name(field), diff.right),
|
||||
status: diff.status,
|
||||
}
|
||||
};
|
||||
|
@ -1441,7 +1449,7 @@ fn diff_tag_union<'b>(
|
|||
field.clone(),
|
||||
alloc.tag_name(field.clone()),
|
||||
// TODO add spaces between args
|
||||
args.into_iter()
|
||||
args.iter()
|
||||
.map(|arg| to_doc(alloc, Parens::Unnecessary, arg.clone()))
|
||||
.collect(),
|
||||
)
|
||||
|
@ -1757,3 +1765,158 @@ mod report_text {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn type_problem_to_pretty<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
problem: crate::type_error::Problem,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::type_error::Problem::*;
|
||||
|
||||
match problem {
|
||||
FieldTypo(typo, possibilities) => {
|
||||
let suggestions = suggest::sort(typo.as_str(), possibilities);
|
||||
|
||||
match suggestions.get(0) {
|
||||
None => alloc.nil(),
|
||||
Some(nearest) => {
|
||||
let typo_str = format!("{}", typo);
|
||||
let nearest_str = format!("{}", nearest);
|
||||
|
||||
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
||||
let suggestion = alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
||||
|
||||
let hint1 = alloc
|
||||
.hint()
|
||||
.append(alloc.reflow("Seems like a record field typo. Maybe "))
|
||||
.append(found)
|
||||
.append(alloc.reflow(" should be "))
|
||||
.append(suggestion)
|
||||
.append(alloc.text("?"));
|
||||
|
||||
let hint2 = alloc.hint().append(alloc.reflow(ADD_ANNOTATIONS));
|
||||
|
||||
hint1
|
||||
.append(alloc.line())
|
||||
.append(alloc.line())
|
||||
.append(hint2)
|
||||
}
|
||||
}
|
||||
}
|
||||
FieldsMissing(missing) => match missing.split_last() {
|
||||
None => alloc.nil(),
|
||||
Some((f1, [])) => alloc
|
||||
.hint()
|
||||
.append(alloc.reflow("Looks like the "))
|
||||
.append(f1.as_str().to_owned())
|
||||
.append(alloc.reflow(" field is missing.")),
|
||||
Some((last, init)) => {
|
||||
let separator = alloc.reflow(", ");
|
||||
|
||||
alloc
|
||||
.hint()
|
||||
.append(alloc.reflow("Looks like the "))
|
||||
.append(
|
||||
alloc.intersperse(init.iter().map(|v| v.as_str().to_owned()), separator),
|
||||
)
|
||||
.append(alloc.reflow(" and "))
|
||||
.append(alloc.text(last.as_str().to_owned()))
|
||||
.append(alloc.reflow(" fields are missing."))
|
||||
}
|
||||
},
|
||||
TagTypo(typo, possibilities_tn) => {
|
||||
let possibilities = possibilities_tn
|
||||
.into_iter()
|
||||
.map(|tag_name| tag_name.into_string(alloc.interns, alloc.home))
|
||||
.collect();
|
||||
let typo_str = format!("{}", typo.into_string(alloc.interns, alloc.home));
|
||||
let suggestions = suggest::sort(&typo_str, possibilities);
|
||||
|
||||
match suggestions.get(0) {
|
||||
None => alloc.nil(),
|
||||
Some(nearest) => {
|
||||
let nearest_str = format!("{}", nearest);
|
||||
|
||||
let found = alloc.text(typo_str).annotate(Annotation::Typo);
|
||||
let suggestion = alloc.text(nearest_str).annotate(Annotation::TypoSuggestion);
|
||||
|
||||
let hint1 = alloc
|
||||
.hint()
|
||||
.append(alloc.reflow("Seems like a tag typo. Maybe "))
|
||||
.append(found)
|
||||
.append(" should be ")
|
||||
.append(suggestion)
|
||||
.append(alloc.text("?"));
|
||||
|
||||
let hint2 = alloc.hint().append(alloc.reflow(ADD_ANNOTATIONS));
|
||||
|
||||
hint1
|
||||
.append(alloc.line())
|
||||
.append(alloc.line())
|
||||
.append(hint2)
|
||||
}
|
||||
}
|
||||
}
|
||||
ArityMismatch(found, expected) => {
|
||||
let line = if found < expected {
|
||||
format!(
|
||||
"It looks like it takes too few arguments. I was expecting {} more.",
|
||||
expected - found
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"It looks like it takes too many arguments. I'm seeing {} extra.",
|
||||
found - expected
|
||||
)
|
||||
};
|
||||
|
||||
alloc.hint().append(line)
|
||||
}
|
||||
|
||||
BadRigidVar(x, tipe) => {
|
||||
use ErrorType::*;
|
||||
|
||||
let bad_rigid_var = |name: Lowercase, a_thing| {
|
||||
alloc
|
||||
.hint()
|
||||
.append(alloc.reflow("The type annotation uses the type variable "))
|
||||
.append(alloc.type_variable(name))
|
||||
.append(alloc.reflow(" to say that this definition can produce any type of value. But in the body I see that it will only produce "))
|
||||
.append(a_thing)
|
||||
.append(alloc.reflow(" of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general?"))
|
||||
};
|
||||
|
||||
let bad_double_rigid = |a, b| {
|
||||
let line = r#" as separate type variables. Your code seems to be saying they are the same though. Maybe they should be the same your type annotation? Maybe your code uses them in a weird way?"#;
|
||||
|
||||
alloc
|
||||
.hint()
|
||||
.append(alloc.reflow("Your type annotation uses "))
|
||||
.append(alloc.type_variable(a))
|
||||
.append(alloc.reflow(" and "))
|
||||
.append(alloc.type_variable(b))
|
||||
.append(alloc.reflow(line))
|
||||
};
|
||||
|
||||
match tipe {
|
||||
Infinite | Error | FlexVar(_) => alloc.nil(),
|
||||
RigidVar(y) => bad_double_rigid(x, y),
|
||||
Function(_, _) => bad_rigid_var(x, alloc.reflow("a function value")),
|
||||
Record(_, _) => bad_rigid_var(x, alloc.reflow("a record value")),
|
||||
TagUnion(_, _) | RecursiveTagUnion(_, _, _) => {
|
||||
bad_rigid_var(x, alloc.reflow("a tag value"))
|
||||
}
|
||||
Alias(symbol, _, _) | Type(symbol, _) => bad_rigid_var(
|
||||
x,
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("a "),
|
||||
alloc.symbol_unqualified(symbol),
|
||||
alloc.reflow(" value"),
|
||||
]),
|
||||
),
|
||||
Boolean(_) => bad_rigid_var(x, alloc.reflow("a uniqueness attribute value")),
|
||||
}
|
||||
}
|
||||
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,8 @@ mod test_reporting {
|
|||
use crate::helpers::test_home;
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_reporting::report::{
|
||||
can_problem, em_text, plain_text, url, Report, ReportText, BLUE_CODE, BOLD_CODE, CYAN_CODE,
|
||||
DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
|
||||
WHITE_CODE, YELLOW_CODE,
|
||||
can_problem, Report, BLUE_CODE, BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE,
|
||||
MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE, WHITE_CODE, YELLOW_CODE,
|
||||
};
|
||||
use roc_reporting::type_error::type_problem;
|
||||
use roc_types::pretty_print::name_all_type_vars;
|
||||
|
@ -23,11 +22,8 @@ mod test_reporting {
|
|||
// use roc_region::all;
|
||||
use crate::helpers::{can_expr, infer_expr, CanExprOut};
|
||||
use roc_reporting::report;
|
||||
use roc_reporting::report::ReportText::{Concat, Module, Region, Type, Value};
|
||||
use roc_reporting::report::RocDocAllocator;
|
||||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||||
use roc_solve::solve;
|
||||
use roc_types::subs::Content::{FlexVar, RigidVar, Structure};
|
||||
use roc_types::subs::FlatType::EmptyRecord;
|
||||
use std::fmt::Write;
|
||||
|
||||
fn filename_from_string(str: &str) -> PathBuf {
|
||||
|
@ -37,11 +33,10 @@ mod test_reporting {
|
|||
return filename;
|
||||
}
|
||||
|
||||
// use roc_problem::can;
|
||||
fn to_simple_report<'b>(text: ReportText) -> Report<'b> {
|
||||
fn to_simple_report<'b>(doc: RocDocBuilder<'b>) -> Report<'b> {
|
||||
Report {
|
||||
title: "".to_string(),
|
||||
doc: todo!(),
|
||||
doc: doc,
|
||||
filename: filename_from_string(r"\code\proj\Main.roc"),
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +46,6 @@ mod test_reporting {
|
|||
) -> (
|
||||
Vec<solve::TypeError>,
|
||||
Vec<roc_problem::can::Problem>,
|
||||
Subs,
|
||||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
|
@ -76,11 +70,11 @@ mod test_reporting {
|
|||
|
||||
name_all_type_vars(var, &mut subs);
|
||||
|
||||
(unify_problems, can_problems, subs, home, interns)
|
||||
(unify_problems, can_problems, home, interns)
|
||||
}
|
||||
|
||||
fn report_problem_as(src: &str, expected_rendering: &str) {
|
||||
let (type_problems, can_problems, mut subs, home, interns) = infer_expr_help(src);
|
||||
let (type_problems, can_problems, home, interns) = infer_expr_help(src);
|
||||
|
||||
let mut buf: String = String::new();
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
|
@ -88,7 +82,7 @@ mod test_reporting {
|
|||
match can_problems.first() {
|
||||
None => {}
|
||||
Some(problem) => {
|
||||
let alloc = RocDocAllocator::new(&mut subs, &src_lines, home, &interns);
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
let report = can_problem(
|
||||
&alloc,
|
||||
filename_from_string(r"\code\proj\Main.roc"),
|
||||
|
@ -104,7 +98,7 @@ mod test_reporting {
|
|||
|
||||
let mut it = type_problems.into_iter().peekable();
|
||||
while let Some(problem) = it.next() {
|
||||
let alloc = RocDocAllocator::new(&mut subs, &src_lines, home, &interns);
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
let report = type_problem(
|
||||
&alloc,
|
||||
filename_from_string(r"\code\proj\Main.roc"),
|
||||
|
@ -125,7 +119,7 @@ mod test_reporting {
|
|||
}
|
||||
|
||||
fn color_report_problem_as(src: &str, expected_rendering: &str) {
|
||||
let (type_problems, can_problems, mut subs, home, interns) = infer_expr_help(src);
|
||||
let (type_problems, can_problems, home, interns) = infer_expr_help(src);
|
||||
|
||||
let mut buf: String = String::new();
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
|
@ -133,7 +127,7 @@ mod test_reporting {
|
|||
match can_problems.first() {
|
||||
None => {}
|
||||
Some(problem) => {
|
||||
let alloc = RocDocAllocator::new(&mut subs, &src_lines, home, &interns);
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
let report = can_problem(
|
||||
&alloc,
|
||||
filename_from_string(r"\code\proj\Main.roc"),
|
||||
|
@ -149,7 +143,7 @@ mod test_reporting {
|
|||
|
||||
let mut it = type_problems.into_iter().peekable();
|
||||
while let Some(problem) = it.next() {
|
||||
let alloc = RocDocAllocator::new(&mut subs, &src_lines, home, &interns);
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
let report = type_problem(
|
||||
&alloc,
|
||||
filename_from_string(r"\code\proj\Main.roc"),
|
||||
|
@ -162,7 +156,13 @@ mod test_reporting {
|
|||
}
|
||||
}
|
||||
|
||||
assert_eq!(buf, expected_rendering);
|
||||
if !buf.is_empty() {
|
||||
write!(buf, "\n").unwrap();
|
||||
}
|
||||
|
||||
let readable = human_readable(&buf);
|
||||
|
||||
assert_eq!(readable, expected_rendering);
|
||||
}
|
||||
|
||||
fn human_readable(str: &str) -> String {
|
||||
|
@ -199,7 +199,9 @@ mod test_reporting {
|
|||
2 ┆ y = 2
|
||||
┆ ^
|
||||
|
||||
If you didn't intend on using `y` then remove it so future readers of your code don't wonder why it is there."#
|
||||
If you didn't intend on using `y` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -232,7 +234,8 @@ mod test_reporting {
|
|||
┆ ^
|
||||
|
||||
Since these variables have the same name, it's easy to use the wrong
|
||||
one on accident. Give one of them a new name."#
|
||||
one on accident. Give one of them a new name.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -267,7 +270,8 @@ mod test_reporting {
|
|||
┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Since these variables have the same name, it's easy to use the wrong
|
||||
one on accident. Give one of them a new name."#
|
||||
one on accident. Give one of them a new name.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -355,11 +359,11 @@ mod test_reporting {
|
|||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
Using != and == together requires parentheses, to clarify how they should be grouped.
|
||||
Using != and == together requires parentheses, to clarify how they
|
||||
should be grouped.
|
||||
|
||||
3 ┆ if selectedId != thisId == adminsId then
|
||||
┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -385,12 +389,12 @@ mod test_reporting {
|
|||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
Using more than one == like this requires parentheses, to clarify how things should be grouped.
|
||||
Using more than one == like this requires parentheses, to clarify how
|
||||
things should be grouped.
|
||||
|
||||
2 ┆> 1
|
||||
3 ┆> == 2
|
||||
4 ┆> == 3
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -461,16 +465,20 @@ mod test_reporting {
|
|||
"#
|
||||
);
|
||||
|
||||
let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src);
|
||||
let (_type_problems, _can_problems, home, interns) = infer_expr_help(src);
|
||||
|
||||
let mut buf = String::new();
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
|
||||
let alloc = RocDocAllocator::new(&mut subs, &src_lines, home, &interns);
|
||||
to_simple_report(Value(
|
||||
interns.symbol(test_home(), "activityIndicatorLarge".into()),
|
||||
))
|
||||
.render_color_terminal(&mut buf, &alloc, &DEFAULT_PALETTE);
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
let symbol = interns.symbol(test_home(), "activityIndicatorLarge".into());
|
||||
|
||||
to_simple_report(alloc.symbol_unqualified(symbol)).render_color_terminal(
|
||||
&mut buf,
|
||||
&alloc,
|
||||
&DEFAULT_PALETTE,
|
||||
);
|
||||
|
||||
assert_eq!(human_readable(&buf), "<blue>activityIndicatorLarge<reset>");
|
||||
}
|
||||
|
@ -486,14 +494,14 @@ mod test_reporting {
|
|||
"#
|
||||
);
|
||||
|
||||
let (_type_problems, _can_problems, mut subs, home, mut interns) = infer_expr_help(src);
|
||||
let (_type_problems, _can_problems, home, mut interns) = infer_expr_help(src);
|
||||
|
||||
let mut buf = String::new();
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let module_id = interns.module_id(&"Util.Int".into());
|
||||
|
||||
let alloc = RocDocAllocator::new(&mut subs, &src_lines, home, &interns);
|
||||
to_simple_report(Module(module_id)).render_color_terminal(
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
to_simple_report(alloc.module(module_id)).render_color_terminal(
|
||||
&mut buf,
|
||||
&alloc,
|
||||
&DEFAULT_PALETTE,
|
||||
|
@ -515,13 +523,19 @@ mod test_reporting {
|
|||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
I cannot find a `theAdmin` value
|
||||
|
||||
<cyan>1<reset><magenta> ┆<reset><red>><reset> <white>isDisabled = \user -> user.isAdmin<reset>
|
||||
<cyan>2<reset><magenta> ┆<reset><red>><reset>
|
||||
<cyan>3<reset><magenta> ┆<reset><red>><reset> <white>theAdmin<reset>
|
||||
<cyan>4<reset><magenta> ┆<reset><red>><reset> <white> |> isDisabled<reset>
|
||||
<cyan>3<reset><magenta> ┆<reset><white> theAdmin<reset>
|
||||
<magenta> ┆<reset> <red>^^^^^^^^<reset>
|
||||
|
||||
these names seem close though:
|
||||
|
||||
Num
|
||||
Set
|
||||
Result
|
||||
Int
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -872,7 +886,8 @@ mod test_reporting {
|
|||
parts of the type that repeat something already printed out
|
||||
infinitely.
|
||||
|
||||
∞ -> a"#
|
||||
∞ -> a
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -900,7 +915,8 @@ mod test_reporting {
|
|||
parts of the type that repeat something already printed out
|
||||
infinitely.
|
||||
|
||||
List ∞ -> a"#
|
||||
List ∞ -> a
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -935,7 +951,6 @@ mod test_reporting {
|
|||
|
||||
{ foo : Int }
|
||||
|
||||
|
||||
Hint: Seems like a record field typo. Maybe `bar` should be `foo`?
|
||||
|
||||
Hint: Can more type annotations be added? Type annotations always help
|
||||
|
@ -974,7 +989,6 @@ mod test_reporting {
|
|||
|
||||
[ Green, Red ]
|
||||
|
||||
|
||||
Hint: Seems like a tag typo. Maybe `Blue` should be `Red`?
|
||||
|
||||
Hint: Can more type annotations be added? Type annotations always help
|
||||
|
@ -1013,7 +1027,6 @@ mod test_reporting {
|
|||
|
||||
[ Green Bool, Red Int ]
|
||||
|
||||
|
||||
Hint: Seems like a tag typo. Maybe `Blue` should be `Red`?
|
||||
|
||||
Hint: Can more type annotations be added? Type annotations always help
|
||||
|
@ -1051,8 +1064,6 @@ mod test_reporting {
|
|||
But the type annotation on `x` says it should be:
|
||||
|
||||
Int
|
||||
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -1087,8 +1098,6 @@ mod test_reporting {
|
|||
But the type annotation on `x` says it should be:
|
||||
|
||||
Int
|
||||
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -1121,8 +1130,6 @@ mod test_reporting {
|
|||
But the type annotation on `x` says it should be:
|
||||
|
||||
Int -> Int
|
||||
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -1148,7 +1155,8 @@ mod test_reporting {
|
|||
4 ┆ x 3
|
||||
┆ ^
|
||||
|
||||
Are there any missing commas? Or missing parentheses?"#
|
||||
Are there any missing commas? Or missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1173,7 +1181,8 @@ mod test_reporting {
|
|||
4 ┆ f 1 2
|
||||
┆ ^
|
||||
|
||||
Are there any missing commas? Or missing parentheses?"#
|
||||
Are there any missing commas? Or missing parentheses?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1198,7 +1207,8 @@ mod test_reporting {
|
|||
4 ┆ f 1
|
||||
┆ ^
|
||||
|
||||
Roc does not allow functions to be partially applied. Use a closure to make partial application explicit."#
|
||||
Roc does not allow functions to be partially applied. Use a closure to make partial application explicit.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1289,7 +1299,6 @@ mod test_reporting {
|
|||
But the expression between `when` and `is` has the type:
|
||||
|
||||
{ foo : Num a }
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -1319,7 +1328,8 @@ mod test_reporting {
|
|||
Bool
|
||||
Int
|
||||
Num
|
||||
Map"#
|
||||
Map
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1383,7 +1393,6 @@ mod test_reporting {
|
|||
But the expression between `when` and `is` has the type:
|
||||
|
||||
{ foo : Num a }
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -1416,8 +1425,6 @@ mod test_reporting {
|
|||
But you are trying to use it as:
|
||||
|
||||
[ Foo a ]b
|
||||
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -1450,8 +1457,6 @@ mod test_reporting {
|
|||
But the type annotation says it should be:
|
||||
|
||||
{ x : Int }
|
||||
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -1485,7 +1490,6 @@ mod test_reporting {
|
|||
|
||||
{ a : Int, b : Float, c : Bool }
|
||||
|
||||
|
||||
Hint: Looks like the c and a fields are missing.
|
||||
"#
|
||||
),
|
||||
|
@ -1520,8 +1524,7 @@ mod test_reporting {
|
|||
|
||||
a, b -> a
|
||||
|
||||
|
||||
Hint: Your type annotation uses a and b as separate type variables.
|
||||
Hint: Your type annotation uses `a` and `b` as separate type variables.
|
||||
Your code seems to be saying they are the same though. Maybe they
|
||||
should be the same your type annotation? Maybe your code uses them in
|
||||
a weird way?
|
||||
|
@ -1558,10 +1561,9 @@ mod test_reporting {
|
|||
|
||||
Bool -> msg
|
||||
|
||||
|
||||
Hint: The type annotation uses the type variable `msg` to say that
|
||||
this definition can produce any type of value. But in the body I see
|
||||
that it will only produce a tag value of a single specific type. Maybe
|
||||
Hint: The type annotation uses the type variable `msg` to say that this
|
||||
definition can produce any type of value. But in the body I see that
|
||||
it will only produce a tag value of a single specific type. Maybe
|
||||
change the type annotation to be more specific? Maybe change the code
|
||||
to be more general?
|
||||
"#
|
||||
|
@ -1597,10 +1599,9 @@ mod test_reporting {
|
|||
|
||||
msg
|
||||
|
||||
|
||||
Hint: The type annotation uses the type variable `msg` to say that
|
||||
this definition can produce any type of value. But in the body I see
|
||||
that it will only produce a Int value of a single specific type. Maybe
|
||||
Hint: The type annotation uses the type variable `msg` to say that this
|
||||
definition can produce any type of value. But in the body I see that
|
||||
it will only produce a `Int` value of a single specific type. Maybe
|
||||
change the type annotation to be more specific? Maybe change the code
|
||||
to be more general?
|
||||
"#
|
||||
|
@ -1634,7 +1635,8 @@ mod test_reporting {
|
|||
f
|
||||
Int
|
||||
Num
|
||||
Map"#
|
||||
Map
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1663,10 +1665,11 @@ mod test_reporting {
|
|||
3 ┆ ok = 3
|
||||
┆ ^^
|
||||
|
||||
If you didn't intend on using `ok` then remove it so future readers of your code don't wonder why it is there.
|
||||
If you didn't intend on using `ok` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
|
||||
-- TYPE MISMATCH ---------------------------------------------------------------
|
||||
|
||||
|
||||
Something is off with the body of the `f` definition:
|
||||
|
||||
2 ┆> f = \_ ->
|
||||
|
@ -1681,8 +1684,6 @@ mod test_reporting {
|
|||
But the type annotation on `f` says it should be:
|
||||
|
||||
Bool -> Int
|
||||
|
||||
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -1706,7 +1707,8 @@ mod test_reporting {
|
|||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
The `f` value is defined directly in terms of itself, causing an
|
||||
infinite loop."#
|
||||
infinite loop.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue