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,
|
||||
dest_filename: &Path,
|
||||
) {
|
||||
use roc_reporting::report::{can_problem, RocDocAllocator, DEFAULT_PALETTE};
|
||||
use roc_reporting::type_error::type_problem;
|
||||
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
|
||||
|
||||
let src = loaded.src;
|
||||
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)>,
|
||||
ext: TypeExt,
|
||||
) -> 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)| {
|
||||
(
|
||||
|
@ -2129,9 +2129,9 @@ mod report_text {
|
|||
|
||||
fn type_problem_to_pretty<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
problem: crate::type_error::Problem,
|
||||
problem: crate::error::r#type::Problem,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use crate::type_error::Problem::*;
|
||||
use crate::error::r#type::Problem::*;
|
||||
|
||||
match problem {
|
||||
FieldTypo(typo, possibilities) => {
|
|
@ -11,5 +11,5 @@
|
|||
// re-enable this when working on performance optimizations than have it block PRs.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
pub mod error;
|
||||
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::{Lowercase, TagName, Uppercase};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
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 = false;
|
||||
|
||||
|
@ -144,541 +146,6 @@ pub const UNDERLINE_CODE: &str = "\u{001b}[4m";
|
|||
|
||||
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
|
||||
pub struct RocDocAllocator<'a> {
|
||||
upstream: BoxAllocator,
|
||||
|
|
|
@ -103,7 +103,7 @@ pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast
|
|||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
|
||||
|
@ -118,12 +118,32 @@ pub struct CanExprOut {
|
|||
pub constraint: Constraint,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseErrOut {
|
||||
pub fail: Fail,
|
||||
pub home: ModuleId,
|
||||
pub interns: Interns,
|
||||
}
|
||||
|
||||
#[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) {
|
||||
Ok(e) => e,
|
||||
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_mono::expr::{Expr, Procs};
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, Report, BLUE_CODE, BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE,
|
||||
GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE, WHITE_CODE, YELLOW_CODE,
|
||||
can_problem, mono_problem, parse_problem, type_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;
|
||||
use roc_types::subs::Subs;
|
||||
use std::path::PathBuf;
|
||||
// use roc_region::all;
|
||||
use crate::helpers::{can_expr, infer_expr, CanExprOut};
|
||||
use roc_parse::parser::Fail;
|
||||
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
||||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||||
use roc_solve::solve;
|
||||
|
||||
|
@ -52,7 +51,7 @@ mod test_reporting {
|
|||
ModuleId,
|
||||
Interns,
|
||||
),
|
||||
Fail,
|
||||
ParseErrOut,
|
||||
> {
|
||||
let CanExprOut {
|
||||
loc_expr,
|
||||
|
@ -112,15 +111,29 @@ mod test_reporting {
|
|||
{
|
||||
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) {
|
||||
Err(fail) => todo!(),
|
||||
Ok((type_problems, can_problems, mono_problems, home, interns)) => {
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
Err(parse_err) => {
|
||||
let ParseErrOut {
|
||||
fail,
|
||||
home,
|
||||
interns,
|
||||
} = parse_err;
|
||||
|
||||
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 alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
for problem in can_problems {
|
||||
let report = can_problem(&alloc, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
|
@ -2690,17 +2703,9 @@ mod test_reporting {
|
|||
),
|
||||
indoc!(
|
||||
r#"
|
||||
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||
|
||||
The `a` type variable is not used in the `Foo` alias definition:
|
||||
|
||||
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.
|
||||
-- PARSE PROBLEM ---------------------------------------------------------------
|
||||
|
||||
Unexpected tokens in front of the `=` symbol:
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue