mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
parse error infrastructure
This commit is contained in:
parent
e21cdfc689
commit
719ef5b70e
10 changed files with 646 additions and 568 deletions
|
@ -156,8 +156,7 @@ fn gen(
|
||||||
target: Triple,
|
target: Triple,
|
||||||
dest_filename: &Path,
|
dest_filename: &Path,
|
||||||
) {
|
) {
|
||||||
use roc_reporting::report::{can_problem, RocDocAllocator, DEFAULT_PALETTE};
|
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
|
||||||
use roc_reporting::type_error::type_problem;
|
|
||||||
|
|
||||||
let src = loaded.src;
|
let src = loaded.src;
|
||||||
let home = loaded.module_id;
|
let home = loaded.module_id;
|
||||||
|
|
397
compiler/reporting/src/error/canonicalize.rs
Normal file
397
compiler/reporting/src/error/canonicalize.rs
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
use roc_collections::all::MutSet;
|
||||||
|
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||||
|
use roc_problem::can::{Problem, RuntimeError};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||||
|
use ven_pretty::DocAllocator;
|
||||||
|
|
||||||
|
pub fn can_problem<'b>(
|
||||||
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
|
filename: PathBuf,
|
||||||
|
problem: Problem,
|
||||||
|
) -> Report<'b> {
|
||||||
|
let doc = match problem {
|
||||||
|
Problem::UnusedDef(symbol, region) => {
|
||||||
|
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) => {
|
||||||
|
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)) => alloc
|
||||||
|
.stack(vec![
|
||||||
|
if left_bin_op.value == right_bin_op.value {
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("Using more than one "),
|
||||||
|
alloc.binop(left_bin_op.value),
|
||||||
|
alloc.reflow(concat!(
|
||||||
|
" like this requires parentheses,",
|
||||||
|
" to clarify how things should be grouped.",
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("Using "),
|
||||||
|
alloc.binop(left_bin_op.value),
|
||||||
|
alloc.reflow(" and "),
|
||||||
|
alloc.binop(right_bin_op.value),
|
||||||
|
alloc.reflow(concat!(
|
||||||
|
" together requires parentheses, ",
|
||||||
|
"to clarify how they should be grouped."
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
alloc.region(region),
|
||||||
|
]),
|
||||||
|
Problem::UnsupportedPattern(pattern_type, region) => {
|
||||||
|
use roc_parse::pattern::PatternType::*;
|
||||||
|
|
||||||
|
let this_thing = match pattern_type {
|
||||||
|
TopLevelDef => "a top-level definition:",
|
||||||
|
DefExpr => "a value definition:",
|
||||||
|
FunctionArg => "function arguments:",
|
||||||
|
WhenBranch => unreachable!("all patterns are allowed in a When"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let suggestion = vec![
|
||||||
|
alloc.reflow(
|
||||||
|
"Patterns like this don't cover all possible shapes of the input type. Use a ",
|
||||||
|
),
|
||||||
|
alloc.keyword("when"),
|
||||||
|
alloc.reflow(" ... "),
|
||||||
|
alloc.keyword("is"),
|
||||||
|
alloc.reflow(" instead."),
|
||||||
|
];
|
||||||
|
|
||||||
|
alloc.stack(vec![
|
||||||
|
alloc
|
||||||
|
.reflow("This pattern is not allowed in ")
|
||||||
|
.append(alloc.reflow(this_thing)),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.concat(suggestion),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
Problem::ShadowingInAnnotation {
|
||||||
|
original_region,
|
||||||
|
shadow,
|
||||||
|
} => pretty_runtime_error(
|
||||||
|
alloc,
|
||||||
|
RuntimeError::Shadowing {
|
||||||
|
original_region,
|
||||||
|
shadow,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Problem::CyclicAlias(symbol, region, others) => {
|
||||||
|
let (doc, title) = crate::error::r#type::cyclic_alias(alloc, symbol, region, others);
|
||||||
|
|
||||||
|
return Report {
|
||||||
|
filename,
|
||||||
|
title,
|
||||||
|
doc,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Problem::PhantomTypeArgument {
|
||||||
|
alias,
|
||||||
|
variable_region,
|
||||||
|
variable_name,
|
||||||
|
} => alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("The "),
|
||||||
|
alloc.type_variable(variable_name),
|
||||||
|
alloc.reflow(" type variable is not used in the "),
|
||||||
|
alloc.symbol_unqualified(alias),
|
||||||
|
alloc.reflow(" alias definition:"),
|
||||||
|
]),
|
||||||
|
alloc.region(variable_region),
|
||||||
|
alloc.reflow("Roc does not allow unused type parameters!"),
|
||||||
|
// TODO add link to this guide section
|
||||||
|
alloc.hint().append(alloc.reflow(
|
||||||
|
"If you want an unused type parameter (a so-called \"phantom type\"), \
|
||||||
|
read the guide section on phantom data.",
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
Problem::DuplicateRecordFieldValue {
|
||||||
|
field_name,
|
||||||
|
field_region,
|
||||||
|
record_region,
|
||||||
|
replaced_region,
|
||||||
|
} => alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This record defines the "),
|
||||||
|
alloc.record_field(field_name.clone()),
|
||||||
|
alloc.reflow(" field twice!"),
|
||||||
|
]),
|
||||||
|
alloc.region_all_the_things(
|
||||||
|
record_region,
|
||||||
|
replaced_region,
|
||||||
|
field_region,
|
||||||
|
Annotation::Error,
|
||||||
|
),
|
||||||
|
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||||
|
alloc.region_all_the_things(
|
||||||
|
record_region,
|
||||||
|
field_region,
|
||||||
|
field_region,
|
||||||
|
Annotation::TypoSuggestion,
|
||||||
|
),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("For clarity, remove the previous "),
|
||||||
|
alloc.record_field(field_name),
|
||||||
|
alloc.reflow(" definitions from this record."),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Problem::DuplicateRecordFieldType {
|
||||||
|
field_name,
|
||||||
|
field_region,
|
||||||
|
record_region,
|
||||||
|
replaced_region,
|
||||||
|
} => alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This record type defines the "),
|
||||||
|
alloc.record_field(field_name.clone()),
|
||||||
|
alloc.reflow(" field twice!"),
|
||||||
|
]),
|
||||||
|
alloc.region_all_the_things(
|
||||||
|
record_region,
|
||||||
|
replaced_region,
|
||||||
|
field_region,
|
||||||
|
Annotation::Error,
|
||||||
|
),
|
||||||
|
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||||
|
alloc.region_all_the_things(
|
||||||
|
record_region,
|
||||||
|
field_region,
|
||||||
|
field_region,
|
||||||
|
Annotation::TypoSuggestion,
|
||||||
|
),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("For clarity, remove the previous "),
|
||||||
|
alloc.record_field(field_name),
|
||||||
|
alloc.reflow(" definitions from this record type."),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Problem::DuplicateTag {
|
||||||
|
tag_name,
|
||||||
|
tag_union_region,
|
||||||
|
tag_region,
|
||||||
|
replaced_region,
|
||||||
|
} => alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This tag union type defines the "),
|
||||||
|
alloc.tag_name(tag_name.clone()),
|
||||||
|
alloc.reflow(" tag twice!"),
|
||||||
|
]),
|
||||||
|
alloc.region_all_the_things(
|
||||||
|
tag_union_region,
|
||||||
|
replaced_region,
|
||||||
|
tag_region,
|
||||||
|
Annotation::Error,
|
||||||
|
),
|
||||||
|
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
||||||
|
alloc.region_all_the_things(
|
||||||
|
tag_union_region,
|
||||||
|
tag_region,
|
||||||
|
tag_region,
|
||||||
|
Annotation::TypoSuggestion,
|
||||||
|
),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("For clarity, remove the previous "),
|
||||||
|
alloc.tag_name(tag_name),
|
||||||
|
alloc.reflow(" definitions from this tag union type."),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
||||||
|
};
|
||||||
|
|
||||||
|
Report {
|
||||||
|
title: "SYNTAX PROBLEM".to_string(),
|
||||||
|
filename,
|
||||||
|
doc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(mut idents, regions) => {
|
||||||
|
let first = idents.remove(0);
|
||||||
|
|
||||||
|
if idents.is_empty() {
|
||||||
|
alloc
|
||||||
|
.reflow("The ")
|
||||||
|
.append(alloc.ident(first.value.clone()))
|
||||||
|
.append(alloc.reflow(
|
||||||
|
" value is defined directly in terms of itself, causing an infinite loop.",
|
||||||
|
))
|
||||||
|
// TODO "are you trying to mutate a variable?
|
||||||
|
// TODO hint?
|
||||||
|
} else {
|
||||||
|
alloc.stack(vec![
|
||||||
|
alloc
|
||||||
|
.reflow("The ")
|
||||||
|
.append(alloc.ident(first.value.clone()))
|
||||||
|
.append(
|
||||||
|
alloc.reflow(" definition is causing a very tricky infinite loop:"),
|
||||||
|
),
|
||||||
|
alloc.region(regions[0].0),
|
||||||
|
alloc
|
||||||
|
.reflow("The ")
|
||||||
|
.append(alloc.ident(first.value.clone()))
|
||||||
|
.append(alloc.reflow(
|
||||||
|
" value depends on itself through the following chain of definitions:",
|
||||||
|
)),
|
||||||
|
crate::report::cycle(
|
||||||
|
alloc,
|
||||||
|
4,
|
||||||
|
alloc.ident(first.value),
|
||||||
|
idents
|
||||||
|
.into_iter()
|
||||||
|
.map(|ident| alloc.ident(ident.value))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
),
|
||||||
|
// TODO hint?
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||||
|
// UnsupportedPattern(Region),
|
||||||
|
// UnrecognizedFunctionName(Located<InlinableString>),
|
||||||
|
// SymbolNotExposed {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn not_found<'b>(
|
||||||
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
|
region: roc_region::all::Region,
|
||||||
|
name: &str,
|
||||||
|
thing: &'b str,
|
||||||
|
options: MutSet<Box<str>>,
|
||||||
|
) -> RocDocBuilder<'b> {
|
||||||
|
use crate::error::r#type::suggest;
|
||||||
|
|
||||||
|
let mut suggestions = suggest::sort(name, options.iter().map(|v| v.as_ref()).collect());
|
||||||
|
suggestions.truncate(4);
|
||||||
|
|
||||||
|
let default_no = alloc.concat(vec![
|
||||||
|
alloc.reflow("Is there an "),
|
||||||
|
alloc.keyword("import"),
|
||||||
|
alloc.reflow(" or "),
|
||||||
|
alloc.keyword("exposing"),
|
||||||
|
alloc.reflow(" missing up-top"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let default_yes = alloc.reflow("these names seem close though:");
|
||||||
|
|
||||||
|
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
||||||
|
if suggestions.is_empty() {
|
||||||
|
no_suggestion_details
|
||||||
|
} else {
|
||||||
|
alloc.stack(vec![
|
||||||
|
yes_suggestion_details,
|
||||||
|
alloc
|
||||||
|
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
||||||
|
.indent(4),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("I cannot find a `"),
|
||||||
|
alloc.string(name.to_string()),
|
||||||
|
alloc.reflow("` "),
|
||||||
|
alloc.reflow(thing),
|
||||||
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
to_details(default_no, default_yes),
|
||||||
|
])
|
||||||
|
}
|
4
compiler/reporting/src/error/mod.rs
Normal file
4
compiler/reporting/src/error/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod canonicalize;
|
||||||
|
pub mod mono;
|
||||||
|
pub mod parse;
|
||||||
|
pub mod r#type;
|
147
compiler/reporting/src/error/mono.rs
Normal file
147
compiler/reporting/src/error/mono.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use ven_pretty::DocAllocator;
|
||||||
|
|
||||||
|
pub fn mono_problem<'b>(
|
||||||
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
|
filename: PathBuf,
|
||||||
|
problem: roc_mono::expr::MonoProblem,
|
||||||
|
) -> Report<'b> {
|
||||||
|
use roc_mono::expr::MonoProblem::*;
|
||||||
|
use roc_mono::pattern::Context::*;
|
||||||
|
use roc_mono::pattern::Error::*;
|
||||||
|
|
||||||
|
match problem {
|
||||||
|
PatternProblem(Incomplete(region, context, missing)) => match context {
|
||||||
|
BadArg => {
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.reflow("Other possibilities include:"),
|
||||||
|
unhandled_patterns_to_doc_block(alloc, missing),
|
||||||
|
alloc.concat(vec![
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BadDestruct => {
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.reflow("This pattern does not cover all the possibilities:"),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.reflow("Other possibilities include:"),
|
||||||
|
unhandled_patterns_to_doc_block(alloc, missing),
|
||||||
|
alloc.concat(vec![
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BadCase => {
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This "),
|
||||||
|
alloc.keyword("when"),
|
||||||
|
alloc.reflow(" does not cover all the possibilities:"),
|
||||||
|
]),
|
||||||
|
alloc.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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PatternProblem(Redundant {
|
||||||
|
overall_region,
|
||||||
|
branch_region,
|
||||||
|
index,
|
||||||
|
}) => {
|
||||||
|
let doc = alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("The "),
|
||||||
|
alloc.string(index.ordinal()),
|
||||||
|
alloc.reflow(" pattern is redundant:"),
|
||||||
|
]),
|
||||||
|
alloc.region_with_subregion(overall_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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unhandled_patterns_to_doc_block<'b>(
|
||||||
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
|
patterns: Vec<roc_mono::pattern::Pattern>,
|
||||||
|
) -> 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_mono::pattern::Pattern,
|
||||||
|
) -> RocDocBuilder<'b> {
|
||||||
|
use roc_mono::pattern::Literal::*;
|
||||||
|
use roc_mono::pattern::Pattern::*;
|
||||||
|
|
||||||
|
match pattern {
|
||||||
|
Anything => alloc.text("_"),
|
||||||
|
Literal(l) => match l {
|
||||||
|
Int(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()),
|
||||||
|
Str(s) => alloc.string(s.into()),
|
||||||
|
},
|
||||||
|
Ctor(_, tag_name, args) => {
|
||||||
|
let arg_docs = args.into_iter().map(|v| pattern_to_doc(alloc, v));
|
||||||
|
|
||||||
|
let docs = std::iter::once(alloc.tag_name(tag_name)).chain(arg_docs);
|
||||||
|
|
||||||
|
alloc.intersperse(docs, alloc.space())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
compiler/reporting/src/error/parse.rs
Normal file
39
compiler/reporting/src/error/parse.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use roc_parse::parser::{Fail, FailReason};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::report::{Report, RocDocAllocator};
|
||||||
|
use ven_pretty::DocAllocator;
|
||||||
|
|
||||||
|
pub fn parse_problem<'b>(
|
||||||
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
|
filename: PathBuf,
|
||||||
|
problem: Fail,
|
||||||
|
) -> Report<'b> {
|
||||||
|
use FailReason::*;
|
||||||
|
|
||||||
|
match problem.reason {
|
||||||
|
ArgumentsBeforeEquals => {
|
||||||
|
let doc = alloc.text("Unexpected tokens in front of the `=` symbol:");
|
||||||
|
|
||||||
|
Report {
|
||||||
|
filename,
|
||||||
|
doc,
|
||||||
|
title: "PARSE PROBLEM".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
//
|
||||||
|
// Unexpected(char, Region),
|
||||||
|
// OutdentedTooFar,
|
||||||
|
// ConditionFailed,
|
||||||
|
// LineTooLong(u32 /* which line was too long */),
|
||||||
|
// TooManyLines,
|
||||||
|
// Eof(Region),
|
||||||
|
// InvalidPattern,
|
||||||
|
// ReservedKeyword(Region),
|
||||||
|
// ArgumentsBeforeEquals,
|
||||||
|
//}
|
||||||
|
todo!("unhandled parse error: {:?}", other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1951,7 +1951,7 @@ mod report_text {
|
||||||
fs: Vec<(Lowercase, ErrorType)>,
|
fs: Vec<(Lowercase, ErrorType)>,
|
||||||
ext: TypeExt,
|
ext: TypeExt,
|
||||||
) -> RocDocBuilder<'b> {
|
) -> RocDocBuilder<'b> {
|
||||||
use crate::type_error::{ext_to_doc, to_doc};
|
use crate::error::r#type::{ext_to_doc, to_doc};
|
||||||
|
|
||||||
let entry_to_doc = |(name, tipe): (Lowercase, ErrorType)| {
|
let entry_to_doc = |(name, tipe): (Lowercase, ErrorType)| {
|
||||||
(
|
(
|
||||||
|
@ -2129,9 +2129,9 @@ mod report_text {
|
||||||
|
|
||||||
fn type_problem_to_pretty<'b>(
|
fn type_problem_to_pretty<'b>(
|
||||||
alloc: &'b RocDocAllocator<'b>,
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
problem: crate::type_error::Problem,
|
problem: crate::error::r#type::Problem,
|
||||||
) -> RocDocBuilder<'b> {
|
) -> RocDocBuilder<'b> {
|
||||||
use crate::type_error::Problem::*;
|
use crate::error::r#type::Problem::*;
|
||||||
|
|
||||||
match problem {
|
match problem {
|
||||||
FieldTypo(typo, possibilities) => {
|
FieldTypo(typo, possibilities) => {
|
|
@ -11,5 +11,5 @@
|
||||||
// re-enable this when working on performance optimizations than have it block PRs.
|
// re-enable this when working on performance optimizations than have it block PRs.
|
||||||
#![allow(clippy::large_enum_variant)]
|
#![allow(clippy::large_enum_variant)]
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
pub mod type_error;
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use roc_collections::all::MutSet;
|
|
||||||
use roc_module::ident::Ident;
|
use roc_module::ident::Ident;
|
||||||
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
||||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
|
||||||
use roc_problem::can::{Problem, RuntimeError};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
|
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated};
|
||||||
|
|
||||||
|
pub use crate::error::canonicalize::can_problem;
|
||||||
|
pub use crate::error::mono::mono_problem;
|
||||||
|
pub use crate::error::parse::parse_problem;
|
||||||
|
pub use crate::error::r#type::type_problem;
|
||||||
|
|
||||||
// const IS_WINDOWS: bool = std::env::consts::OS == "windows";
|
// const IS_WINDOWS: bool = std::env::consts::OS == "windows";
|
||||||
const IS_WINDOWS: bool = false;
|
const IS_WINDOWS: bool = false;
|
||||||
|
|
||||||
|
@ -144,541 +146,6 @@ pub const UNDERLINE_CODE: &str = "\u{001b}[4m";
|
||||||
|
|
||||||
pub const RESET_CODE: &str = "\u{001b}[0m";
|
pub const RESET_CODE: &str = "\u{001b}[0m";
|
||||||
|
|
||||||
pub fn mono_problem<'b>(
|
|
||||||
alloc: &'b RocDocAllocator<'b>,
|
|
||||||
filename: PathBuf,
|
|
||||||
problem: roc_mono::expr::MonoProblem,
|
|
||||||
) -> Report<'b> {
|
|
||||||
use roc_mono::expr::MonoProblem::*;
|
|
||||||
use roc_mono::pattern::Context::*;
|
|
||||||
use roc_mono::pattern::Error::*;
|
|
||||||
|
|
||||||
match problem {
|
|
||||||
PatternProblem(Incomplete(region, context, missing)) => match context {
|
|
||||||
BadArg => {
|
|
||||||
let doc = alloc.stack(vec![
|
|
||||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
|
||||||
alloc.region(region),
|
|
||||||
alloc.reflow("Other possibilities include:"),
|
|
||||||
unhandled_patterns_to_doc_block(alloc, missing),
|
|
||||||
alloc.concat(vec![
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BadDestruct => {
|
|
||||||
let doc = alloc.stack(vec![
|
|
||||||
alloc.reflow("This pattern does not cover all the possibilities:"),
|
|
||||||
alloc.region(region),
|
|
||||||
alloc.reflow("Other possibilities include:"),
|
|
||||||
unhandled_patterns_to_doc_block(alloc, missing),
|
|
||||||
alloc.concat(vec![
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BadCase => {
|
|
||||||
let doc = alloc.stack(vec![
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("This "),
|
|
||||||
alloc.keyword("when"),
|
|
||||||
alloc.reflow(" does not cover all the possibilities:"),
|
|
||||||
]),
|
|
||||||
alloc.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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
PatternProblem(Redundant {
|
|
||||||
overall_region,
|
|
||||||
branch_region,
|
|
||||||
index,
|
|
||||||
}) => {
|
|
||||||
let doc = alloc.stack(vec![
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("The "),
|
|
||||||
alloc.string(index.ordinal()),
|
|
||||||
alloc.reflow(" pattern is redundant:"),
|
|
||||||
]),
|
|
||||||
alloc.region_with_subregion(overall_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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unhandled_patterns_to_doc_block<'b>(
|
|
||||||
alloc: &'b RocDocAllocator<'b>,
|
|
||||||
patterns: Vec<roc_mono::pattern::Pattern>,
|
|
||||||
) -> 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_mono::pattern::Pattern,
|
|
||||||
) -> RocDocBuilder<'b> {
|
|
||||||
use roc_mono::pattern::Literal::*;
|
|
||||||
use roc_mono::pattern::Pattern::*;
|
|
||||||
// Anything,
|
|
||||||
// Literal(Literal),
|
|
||||||
// Ctor(Union, TagName, std::vec::Vec<Pattern>),
|
|
||||||
match pattern {
|
|
||||||
Anything => alloc.text("_"),
|
|
||||||
Literal(l) => match l {
|
|
||||||
Int(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()),
|
|
||||||
Str(s) => alloc.string(s.into()),
|
|
||||||
},
|
|
||||||
Ctor(_, tag_name, args) => {
|
|
||||||
let arg_docs = args.into_iter().map(|v| pattern_to_doc(alloc, v));
|
|
||||||
|
|
||||||
let docs = std::iter::once(alloc.tag_name(tag_name)).chain(arg_docs);
|
|
||||||
|
|
||||||
alloc.intersperse(docs, alloc.space())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn can_problem<'b>(
|
|
||||||
alloc: &'b RocDocAllocator<'b>,
|
|
||||||
filename: PathBuf,
|
|
||||||
problem: Problem,
|
|
||||||
) -> Report<'b> {
|
|
||||||
let doc = match problem {
|
|
||||||
Problem::UnusedDef(symbol, region) => {
|
|
||||||
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) => {
|
|
||||||
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)) => alloc
|
|
||||||
.stack(vec![
|
|
||||||
if left_bin_op.value == right_bin_op.value {
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("Using more than one "),
|
|
||||||
alloc.binop(left_bin_op.value),
|
|
||||||
alloc.reflow(concat!(
|
|
||||||
" like this requires parentheses,",
|
|
||||||
" to clarify how things should be grouped.",
|
|
||||||
)),
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("Using "),
|
|
||||||
alloc.binop(left_bin_op.value),
|
|
||||||
alloc.reflow(" and "),
|
|
||||||
alloc.binop(right_bin_op.value),
|
|
||||||
alloc.reflow(concat!(
|
|
||||||
" together requires parentheses, ",
|
|
||||||
"to clarify how they should be grouped."
|
|
||||||
)),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
alloc.region(region),
|
|
||||||
]),
|
|
||||||
Problem::UnsupportedPattern(pattern_type, region) => {
|
|
||||||
use roc_parse::pattern::PatternType::*;
|
|
||||||
|
|
||||||
let this_thing = match pattern_type {
|
|
||||||
TopLevelDef => "a top-level definition:",
|
|
||||||
DefExpr => "a value definition:",
|
|
||||||
FunctionArg => "function arguments:",
|
|
||||||
WhenBranch => unreachable!("all patterns are allowed in a When"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let suggestion = vec![
|
|
||||||
alloc.reflow(
|
|
||||||
"Patterns like this don't cover all possible shapes of the input type. Use a ",
|
|
||||||
),
|
|
||||||
alloc.keyword("when"),
|
|
||||||
alloc.reflow(" ... "),
|
|
||||||
alloc.keyword("is"),
|
|
||||||
alloc.reflow(" instead."),
|
|
||||||
];
|
|
||||||
|
|
||||||
alloc.stack(vec![
|
|
||||||
alloc
|
|
||||||
.reflow("This pattern is not allowed in ")
|
|
||||||
.append(alloc.reflow(this_thing)),
|
|
||||||
alloc.region(region),
|
|
||||||
alloc.concat(suggestion),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
Problem::ShadowingInAnnotation {
|
|
||||||
original_region,
|
|
||||||
shadow,
|
|
||||||
} => pretty_runtime_error(
|
|
||||||
alloc,
|
|
||||||
RuntimeError::Shadowing {
|
|
||||||
original_region,
|
|
||||||
shadow,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Problem::CyclicAlias(symbol, region, others) => {
|
|
||||||
let (doc, title) = crate::type_error::cyclic_alias(alloc, symbol, region, others);
|
|
||||||
|
|
||||||
return Report {
|
|
||||||
filename,
|
|
||||||
title,
|
|
||||||
doc,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Problem::PhantomTypeArgument {
|
|
||||||
alias,
|
|
||||||
variable_region,
|
|
||||||
variable_name,
|
|
||||||
} => alloc.stack(vec![
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("The "),
|
|
||||||
alloc.type_variable(variable_name),
|
|
||||||
alloc.reflow(" type variable is not used in the "),
|
|
||||||
alloc.symbol_unqualified(alias),
|
|
||||||
alloc.reflow(" alias definition:"),
|
|
||||||
]),
|
|
||||||
alloc.region(variable_region),
|
|
||||||
alloc.reflow("Roc does not allow unused type parameters!"),
|
|
||||||
// TODO add link to this guide section
|
|
||||||
alloc.hint().append(alloc.reflow(
|
|
||||||
"If you want an unused type parameter (a so-called \"phantom type\"), \
|
|
||||||
read the guide section on phantom data.",
|
|
||||||
)),
|
|
||||||
]),
|
|
||||||
Problem::DuplicateRecordFieldValue {
|
|
||||||
field_name,
|
|
||||||
field_region,
|
|
||||||
record_region,
|
|
||||||
replaced_region,
|
|
||||||
} => alloc.stack(vec![
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("This record defines the "),
|
|
||||||
alloc.record_field(field_name.clone()),
|
|
||||||
alloc.reflow(" field twice!"),
|
|
||||||
]),
|
|
||||||
alloc.region_all_the_things(
|
|
||||||
record_region,
|
|
||||||
replaced_region,
|
|
||||||
field_region,
|
|
||||||
Annotation::Error,
|
|
||||||
),
|
|
||||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
|
||||||
alloc.region_all_the_things(
|
|
||||||
record_region,
|
|
||||||
field_region,
|
|
||||||
field_region,
|
|
||||||
Annotation::TypoSuggestion,
|
|
||||||
),
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("For clarity, remove the previous "),
|
|
||||||
alloc.record_field(field_name),
|
|
||||||
alloc.reflow(" definitions from this record."),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
Problem::DuplicateRecordFieldType {
|
|
||||||
field_name,
|
|
||||||
field_region,
|
|
||||||
record_region,
|
|
||||||
replaced_region,
|
|
||||||
} => alloc.stack(vec![
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("This record type defines the "),
|
|
||||||
alloc.record_field(field_name.clone()),
|
|
||||||
alloc.reflow(" field twice!"),
|
|
||||||
]),
|
|
||||||
alloc.region_all_the_things(
|
|
||||||
record_region,
|
|
||||||
replaced_region,
|
|
||||||
field_region,
|
|
||||||
Annotation::Error,
|
|
||||||
),
|
|
||||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
|
||||||
alloc.region_all_the_things(
|
|
||||||
record_region,
|
|
||||||
field_region,
|
|
||||||
field_region,
|
|
||||||
Annotation::TypoSuggestion,
|
|
||||||
),
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("For clarity, remove the previous "),
|
|
||||||
alloc.record_field(field_name),
|
|
||||||
alloc.reflow(" definitions from this record type."),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
Problem::DuplicateTag {
|
|
||||||
tag_name,
|
|
||||||
tag_union_region,
|
|
||||||
tag_region,
|
|
||||||
replaced_region,
|
|
||||||
} => alloc.stack(vec![
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("This tag union type defines the "),
|
|
||||||
alloc.tag_name(tag_name.clone()),
|
|
||||||
alloc.reflow(" tag twice!"),
|
|
||||||
]),
|
|
||||||
alloc.region_all_the_things(
|
|
||||||
tag_union_region,
|
|
||||||
replaced_region,
|
|
||||||
tag_region,
|
|
||||||
Annotation::Error,
|
|
||||||
),
|
|
||||||
alloc.reflow("In the rest of the program, I will only use the latter definition:"),
|
|
||||||
alloc.region_all_the_things(
|
|
||||||
tag_union_region,
|
|
||||||
tag_region,
|
|
||||||
tag_region,
|
|
||||||
Annotation::TypoSuggestion,
|
|
||||||
),
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("For clarity, remove the previous "),
|
|
||||||
alloc.tag_name(tag_name),
|
|
||||||
alloc.reflow(" definitions from this tag union type."),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
|
|
||||||
};
|
|
||||||
|
|
||||||
Report {
|
|
||||||
title: "SYNTAX PROBLEM".to_string(),
|
|
||||||
filename,
|
|
||||||
doc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn not_found<'b>(
|
|
||||||
alloc: &'b RocDocAllocator<'b>,
|
|
||||||
region: roc_region::all::Region,
|
|
||||||
name: &str,
|
|
||||||
thing: &'b str,
|
|
||||||
options: MutSet<Box<str>>,
|
|
||||||
) -> RocDocBuilder<'b> {
|
|
||||||
use crate::type_error::suggest;
|
|
||||||
|
|
||||||
let mut suggestions = suggest::sort(name, options.iter().map(|v| v.as_ref()).collect());
|
|
||||||
suggestions.truncate(4);
|
|
||||||
|
|
||||||
let default_no = alloc.concat(vec![
|
|
||||||
alloc.reflow("Is there an "),
|
|
||||||
alloc.keyword("import"),
|
|
||||||
alloc.reflow(" or "),
|
|
||||||
alloc.keyword("exposing"),
|
|
||||||
alloc.reflow(" missing up-top"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let default_yes = alloc.reflow("these names seem close though:");
|
|
||||||
|
|
||||||
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
|
||||||
if suggestions.is_empty() {
|
|
||||||
no_suggestion_details
|
|
||||||
} else {
|
|
||||||
alloc.stack(vec![
|
|
||||||
yes_suggestion_details,
|
|
||||||
alloc
|
|
||||||
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
|
||||||
.indent(4),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
alloc.stack(vec![
|
|
||||||
alloc.concat(vec![
|
|
||||||
alloc.reflow("I cannot find a `"),
|
|
||||||
alloc.string(name.to_string()),
|
|
||||||
alloc.reflow("` "),
|
|
||||||
alloc.reflow(thing),
|
|
||||||
]),
|
|
||||||
alloc.region(region),
|
|
||||||
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."#;
|
|
||||||
|
|
||||||
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(mut idents, regions) => {
|
|
||||||
let first = idents.remove(0);
|
|
||||||
|
|
||||||
if idents.is_empty() {
|
|
||||||
alloc
|
|
||||||
.reflow("The ")
|
|
||||||
.append(alloc.ident(first.value.clone()))
|
|
||||||
.append(alloc.reflow(
|
|
||||||
" value is defined directly in terms of itself, causing an infinite loop.",
|
|
||||||
))
|
|
||||||
// TODO "are you trying to mutate a variable?
|
|
||||||
// TODO hint?
|
|
||||||
} else {
|
|
||||||
alloc.stack(vec![
|
|
||||||
alloc
|
|
||||||
.reflow("The ")
|
|
||||||
.append(alloc.ident(first.value.clone()))
|
|
||||||
.append(
|
|
||||||
alloc.reflow(" definition is causing a very tricky infinite loop:"),
|
|
||||||
),
|
|
||||||
alloc.region(regions[0].0),
|
|
||||||
alloc
|
|
||||||
.reflow("The ")
|
|
||||||
.append(alloc.ident(first.value.clone()))
|
|
||||||
.append(alloc.reflow(
|
|
||||||
" value depends on itself through the following chain of definitions:",
|
|
||||||
)),
|
|
||||||
cycle(
|
|
||||||
alloc,
|
|
||||||
4,
|
|
||||||
alloc.ident(first.value),
|
|
||||||
idents
|
|
||||||
.into_iter()
|
|
||||||
.map(|ident| alloc.ident(ident.value))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
),
|
|
||||||
// TODO hint?
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
|
||||||
// UnsupportedPattern(Region),
|
|
||||||
// UnrecognizedFunctionName(Located<InlinableString>),
|
|
||||||
// SymbolNotExposed {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// define custom allocator struct so we can `impl RocDocAllocator` custom helpers
|
// define custom allocator struct so we can `impl RocDocAllocator` custom helpers
|
||||||
pub struct RocDocAllocator<'a> {
|
pub struct RocDocAllocator<'a> {
|
||||||
upstream: BoxAllocator,
|
upstream: BoxAllocator,
|
||||||
|
|
|
@ -103,7 +103,7 @@ pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn can_expr(expr_str: &str) -> Result<CanExprOut, Fail> {
|
pub fn can_expr(expr_str: &str) -> Result<CanExprOut, ParseErrOut> {
|
||||||
can_expr_with(&Bump::new(), test_home(), expr_str)
|
can_expr_with(&Bump::new(), test_home(), expr_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,12 +118,32 @@ pub struct CanExprOut {
|
||||||
pub constraint: Constraint,
|
pub constraint: Constraint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParseErrOut {
|
||||||
|
pub fail: Fail,
|
||||||
|
pub home: ModuleId,
|
||||||
|
pub interns: Interns,
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> Result<CanExprOut, Fail> {
|
pub fn can_expr_with(
|
||||||
|
arena: &Bump,
|
||||||
|
home: ModuleId,
|
||||||
|
expr_str: &str,
|
||||||
|
) -> Result<CanExprOut, ParseErrOut> {
|
||||||
let loc_expr = match parse_loc_with(&arena, expr_str) {
|
let loc_expr = match parse_loc_with(&arena, expr_str) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(fail) => {
|
Err(fail) => {
|
||||||
return Err(fail);
|
let interns = Interns {
|
||||||
|
module_ids: ModuleIds::default(),
|
||||||
|
all_ident_ids: MutMap::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return Err(ParseErrOut {
|
||||||
|
fail,
|
||||||
|
interns,
|
||||||
|
home,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,16 +14,15 @@ mod test_reporting {
|
||||||
use roc_module::symbol::{Interns, ModuleId};
|
use roc_module::symbol::{Interns, ModuleId};
|
||||||
use roc_mono::expr::{Expr, Procs};
|
use roc_mono::expr::{Expr, Procs};
|
||||||
use roc_reporting::report::{
|
use roc_reporting::report::{
|
||||||
can_problem, mono_problem, Report, BLUE_CODE, BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE,
|
can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
|
||||||
GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE, WHITE_CODE, YELLOW_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;
|
use roc_types::pretty_print::name_all_type_vars;
|
||||||
use roc_types::subs::Subs;
|
use roc_types::subs::Subs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
// use roc_region::all;
|
// use roc_region::all;
|
||||||
use crate::helpers::{can_expr, infer_expr, CanExprOut};
|
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
||||||
use roc_parse::parser::Fail;
|
|
||||||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||||||
use roc_solve::solve;
|
use roc_solve::solve;
|
||||||
|
|
||||||
|
@ -52,7 +51,7 @@ mod test_reporting {
|
||||||
ModuleId,
|
ModuleId,
|
||||||
Interns,
|
Interns,
|
||||||
),
|
),
|
||||||
Fail,
|
ParseErrOut,
|
||||||
> {
|
> {
|
||||||
let CanExprOut {
|
let CanExprOut {
|
||||||
loc_expr,
|
loc_expr,
|
||||||
|
@ -112,15 +111,29 @@ mod test_reporting {
|
||||||
{
|
{
|
||||||
use ven_pretty::DocAllocator;
|
use ven_pretty::DocAllocator;
|
||||||
|
|
||||||
|
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||||
|
|
||||||
|
let filename = filename_from_string(r"\code\proj\Main.roc");
|
||||||
|
|
||||||
match infer_expr_help(src) {
|
match infer_expr_help(src) {
|
||||||
Err(fail) => todo!(),
|
Err(parse_err) => {
|
||||||
Ok((type_problems, can_problems, mono_problems, home, interns)) => {
|
let ParseErrOut {
|
||||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
fail,
|
||||||
|
home,
|
||||||
|
interns,
|
||||||
|
} = parse_err;
|
||||||
|
|
||||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||||
|
|
||||||
let filename = filename_from_string(r"\code\proj\Main.roc");
|
let doc = parse_problem(&alloc, filename, fail);
|
||||||
|
|
||||||
|
callback(doc.pretty(&alloc).append(alloc.line()), buf)
|
||||||
|
}
|
||||||
|
Ok((type_problems, can_problems, mono_problems, home, interns)) => {
|
||||||
let mut reports = Vec::new();
|
let mut reports = Vec::new();
|
||||||
|
|
||||||
|
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||||
|
|
||||||
for problem in can_problems {
|
for problem in can_problems {
|
||||||
let report = can_problem(&alloc, filename.clone(), problem.clone());
|
let report = can_problem(&alloc, filename.clone(), problem.clone());
|
||||||
reports.push(report);
|
reports.push(report);
|
||||||
|
@ -2690,17 +2703,9 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
-- PARSE PROBLEM ---------------------------------------------------------------
|
||||||
|
|
||||||
The `a` type variable is not used in the `Foo` alias definition:
|
Unexpected tokens in front of the `=` symbol:
|
||||||
|
|
||||||
1 ┆ Foo a : [ Foo ]
|
|
||||||
┆ ^
|
|
||||||
|
|
||||||
Roc does not allow unused type parameters!
|
|
||||||
|
|
||||||
Hint: If you want an unused type parameter (a so-called "phantom
|
|
||||||
type"), read the guide section on phantom data.
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue