parse error infrastructure

This commit is contained in:
Folkert 2020-04-15 21:10:14 +02:00
parent e21cdfc689
commit 719ef5b70e
10 changed files with 646 additions and 568 deletions

View file

@ -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;

View 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),
])
}

View file

@ -0,0 +1,4 @@
pub mod canonicalize;
pub mod mono;
pub mod parse;
pub mod r#type;

View 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())
}
}
}

View 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)
}
}
}

View file

@ -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) => {

View file

@ -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;

View file

@ -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,

View file

@ -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,
});
}
};

View file

@ -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:
"#
),
)