mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Merge branch 'main' into specialize-exprs
This commit is contained in:
commit
2e96aca0fd
797 changed files with 17394 additions and 12632 deletions
|
@ -64,6 +64,9 @@ const ABILITY_IMPLEMENTATION_NOT_IDENTIFIER: &str = "ABILITY IMPLEMENTATION NOT
|
|||
const DUPLICATE_IMPLEMENTATION: &str = "DUPLICATE IMPLEMENTATION";
|
||||
const UNNECESSARY_IMPLEMENTATIONS: &str = "UNNECESSARY IMPLEMENTATIONS";
|
||||
const INCOMPLETE_ABILITY_IMPLEMENTATION: &str = "INCOMPLETE ABILITY IMPLEMENTATION";
|
||||
const STATEMENT_AFTER_EXPRESSION: &str = "STATEMENT AFTER EXPRESSION";
|
||||
const MISSING_EXCLAMATION: &str = "MISSING EXCLAMATION";
|
||||
const UNNECESSARY_EXCLAMATION: &str = "UNNECESSARY EXCLAMATION";
|
||||
|
||||
pub fn can_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
|
@ -195,7 +198,7 @@ pub fn can_problem<'b>(
|
|||
]),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("Builtins are imported automatically, so you can remove this import."),
|
||||
alloc.reflow("Tip: Learn more about builtins in the tutorial:\n\n<https://www.roc-lang.org/tutorial#builtin-modules>"),
|
||||
alloc.reflow("Tip: Learn more about builtins in the tutorial:\n<https://www.roc-lang.org/tutorial#builtin-modules>"),
|
||||
]);
|
||||
|
||||
title = EXPLICIT_BUILTIN_IMPORT.to_string();
|
||||
|
@ -213,7 +216,7 @@ pub fn can_problem<'b>(
|
|||
alloc.symbol_unqualified(symbol),
|
||||
alloc.reflow(" from the exposing list.")
|
||||
]),
|
||||
alloc.reflow("Tip: Learn more about builtins in the tutorial:\n\n<https://www.roc-lang.org/tutorial#builtin-modules>"),
|
||||
alloc.reflow("Tip: Learn more about builtins in the tutorial:\n<https://www.roc-lang.org/tutorial#builtin-modules>"),
|
||||
]);
|
||||
|
||||
title = EXPLICIT_BUILTIN_IMPORT.to_string();
|
||||
|
@ -1192,7 +1195,7 @@ pub fn can_problem<'b>(
|
|||
doc = alloc.stack([
|
||||
alloc.reflow("This destructure assignment doesn't introduce any new variables:"),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("If you don't need to use the value on the right-hand-side of this assignment, consider removing the assignment. Since Roc is purely functional, assignments that don't introduce variables cannot affect a program's behavior!"),
|
||||
alloc.reflow("If you don't need to use the value on the right-hand side of this assignment, consider removing the assignment. Since effects are not allowed at the top-level, assignments that don't introduce variables cannot affect a program's behavior"),
|
||||
]);
|
||||
title = "UNNECESSARY DEFINITION".to_string();
|
||||
}
|
||||
|
@ -1346,6 +1349,122 @@ pub fn can_problem<'b>(
|
|||
doc = report.doc;
|
||||
title = report.title;
|
||||
}
|
||||
|
||||
Problem::ReturnOutsideOfFunction { region } => {
|
||||
doc = alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow("This "),
|
||||
alloc.keyword("return"),
|
||||
alloc.reflow(" statement doesn't belong to a function:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("I wouldn't know where to return to if I used it!"),
|
||||
]);
|
||||
|
||||
title = "RETURN OUTSIDE OF FUNCTION".to_string();
|
||||
}
|
||||
|
||||
Problem::StatementsAfterReturn { region } => {
|
||||
doc = alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow("This code won't run because it follows a "),
|
||||
alloc.keyword("return"),
|
||||
alloc.reflow(" statement:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.concat([
|
||||
alloc.hint("you can move the "),
|
||||
alloc.keyword("return"),
|
||||
alloc.reflow(
|
||||
" statement below this block to make the code that follows it run.",
|
||||
),
|
||||
]),
|
||||
]);
|
||||
|
||||
title = "UNREACHABLE CODE".to_string();
|
||||
}
|
||||
|
||||
Problem::ReturnAtEndOfFunction { region } => {
|
||||
doc = alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow("This "),
|
||||
alloc.keyword("return"),
|
||||
alloc.reflow(" keyword is redundant:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.concat([
|
||||
alloc.reflow("The last expression in a function is treated like a "),
|
||||
alloc.keyword("return"),
|
||||
alloc.reflow(" statement. You can safely remove "),
|
||||
alloc.keyword("return"),
|
||||
alloc.reflow(" here."),
|
||||
]),
|
||||
]);
|
||||
|
||||
title = "UNNECESSARY RETURN".to_string();
|
||||
}
|
||||
|
||||
Problem::StmtAfterExpr(region) => {
|
||||
doc = alloc.stack([
|
||||
alloc
|
||||
.reflow(r"I just finished parsing an expression with a series of definitions,"),
|
||||
alloc.reflow(
|
||||
r"and this line is indented as if it's intended to be part of that expression:",
|
||||
),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.concat([alloc.reflow(
|
||||
"However, I already saw the final expression in that series of definitions.",
|
||||
)]),
|
||||
alloc.tip().append(
|
||||
alloc.reflow(
|
||||
"An expression like `4`, `\"hello\"`, or `functionCall MyThing` is like `return 4` in other programming languages. To me, it seems like you did `return 4` followed by more code in the lines after, that code would never be executed!"
|
||||
)
|
||||
),
|
||||
alloc.tip().append(
|
||||
alloc.reflow(
|
||||
"If you are working with `Task`, this error can happen if you forgot a `!` somewhere."
|
||||
)
|
||||
)
|
||||
]);
|
||||
|
||||
title = STATEMENT_AFTER_EXPRESSION.to_string();
|
||||
}
|
||||
|
||||
Problem::UnsuffixedEffectfulRecordField(region) => {
|
||||
doc = alloc.stack([
|
||||
alloc.reflow(
|
||||
"The type of this record field is an effectful function, but its name does not indicate so:",
|
||||
),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("Add an exclamation mark at the end, like:"),
|
||||
alloc
|
||||
.parser_suggestion("{ readFile!: Str => Str }")
|
||||
.indent(4),
|
||||
alloc.reflow("This will help readers identify it as a source of effects."),
|
||||
]);
|
||||
|
||||
title = MISSING_EXCLAMATION.to_string();
|
||||
}
|
||||
|
||||
Problem::SuffixedPureRecordField(region) => {
|
||||
doc = alloc.stack([
|
||||
alloc.reflow(
|
||||
"The type of this record field is a pure function, but its name suggests otherwise:",
|
||||
),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc
|
||||
.reflow("The exclamation mark at the end is reserved for effectful functions."),
|
||||
alloc.concat([
|
||||
alloc.hint("Did you mean to use "),
|
||||
alloc.keyword("=>"),
|
||||
alloc.text(" instead of "),
|
||||
alloc.keyword("->"),
|
||||
alloc.text("?"),
|
||||
]),
|
||||
]);
|
||||
|
||||
title = UNNECESSARY_EXCLAMATION.to_string();
|
||||
}
|
||||
};
|
||||
|
||||
Report {
|
||||
|
@ -2075,9 +2194,6 @@ fn pretty_runtime_error<'b>(
|
|||
|
||||
title = SYNTAX_PROBLEM;
|
||||
}
|
||||
RuntimeError::MalformedClosure(_) => {
|
||||
todo!("");
|
||||
}
|
||||
RuntimeError::MalformedSuffixed(_) => {
|
||||
todo!("error for malformed suffix");
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use roc_parse::parser::{ENumber, ESingleQuote, FileError, PList, SyntaxError};
|
||||
use roc_parse::parser::{ENumber, EReturn, ESingleQuote, FileError, PList, SyntaxError};
|
||||
use roc_problem::Severity;
|
||||
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Position, Region};
|
||||
use std::path::PathBuf;
|
||||
|
@ -10,10 +10,19 @@ pub fn parse_problem<'a>(
|
|||
alloc: &'a RocDocAllocator<'a>,
|
||||
lines: &LineInfo,
|
||||
filename: PathBuf,
|
||||
_starting_line: u32,
|
||||
starting_line: u32,
|
||||
parse_problem: FileError<SyntaxError<'a>>,
|
||||
) -> Report<'a> {
|
||||
to_syntax_report(alloc, lines, filename, &parse_problem.problem.problem)
|
||||
to_syntax_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
&parse_problem.problem.problem,
|
||||
lines.convert_line_column(LineColumn {
|
||||
line: starting_line,
|
||||
column: 0,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn note_for_record_type_indent<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> {
|
||||
|
@ -57,6 +66,7 @@ fn to_syntax_report<'a>(
|
|||
lines: &LineInfo,
|
||||
filename: PathBuf,
|
||||
parse_problem: &roc_parse::parser::SyntaxError<'a>,
|
||||
start: Position,
|
||||
) -> Report<'a> {
|
||||
use SyntaxError::*;
|
||||
|
||||
|
@ -148,7 +158,19 @@ fn to_syntax_report<'a>(
|
|||
Position::default(),
|
||||
),
|
||||
Header(header) => to_header_report(alloc, lines, filename, header, Position::default()),
|
||||
_ => todo!("unhandled parse error: {:?}", parse_problem),
|
||||
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
InvalidPattern | BadUtf8 | Todo | ReservedKeyword(_) | NotYetImplemented(_) | Space(_) => {
|
||||
to_unhandled_parse_error_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
format!("{:?}", parse_problem),
|
||||
start,
|
||||
start,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,6 +194,7 @@ enum Node {
|
|||
StringFormat,
|
||||
Dbg,
|
||||
Expect,
|
||||
Return,
|
||||
}
|
||||
|
||||
fn to_expr_report<'a>(
|
||||
|
@ -309,8 +332,11 @@ fn to_expr_report<'a>(
|
|||
alloc.reflow(" instead."),
|
||||
],
|
||||
_ => vec![
|
||||
alloc.reflow("I have no specific suggestion for this operator, "),
|
||||
alloc.reflow("see TODO for the full list of operators in Roc."),
|
||||
alloc.reflow("I have no specific suggestion for this operator, see "),
|
||||
alloc.parser_suggestion(
|
||||
"https://www.roc-lang.org/tutorial#operator-desugaring-table ",
|
||||
),
|
||||
alloc.reflow("for the full list of operators in Roc."),
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -410,6 +436,7 @@ fn to_expr_report<'a>(
|
|||
Node::ListElement => (pos, alloc.text("a list")),
|
||||
Node::Dbg => (pos, alloc.text("a dbg statement")),
|
||||
Node::Expect => (pos, alloc.text("an expect statement")),
|
||||
Node::Return => (pos, alloc.text("a return statement")),
|
||||
Node::RecordConditionalDefault => (pos, alloc.text("record field default")),
|
||||
Node::StringFormat => (pos, alloc.text("a string format")),
|
||||
Node::InsideParens => (pos, alloc.text("some parentheses")),
|
||||
|
@ -642,30 +669,94 @@ fn to_expr_report<'a>(
|
|||
severity,
|
||||
}
|
||||
}
|
||||
EExpr::StmtAfterExpr(pos) => {
|
||||
let surroundings = Region::new(start, *pos);
|
||||
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc
|
||||
.reflow(r"I just finished parsing an expression with a series of definitions,"),
|
||||
alloc.reflow(
|
||||
r"and this line is indented as if it's intended to be part of that expression:",
|
||||
),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
||||
alloc.concat([alloc.reflow(
|
||||
"However, I already saw the final expression in that series of definitions.",
|
||||
)]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
EExpr::Return(EReturn::Return(pos) | EReturn::IndentReturnValue(pos), start) => {
|
||||
to_expr_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
doc,
|
||||
title: "STATEMENT AFTER EXPRESSION".to_string(),
|
||||
severity,
|
||||
}
|
||||
Context::InNode(Node::Return, *start),
|
||||
&EExpr::IndentStart(*pos),
|
||||
*pos,
|
||||
)
|
||||
}
|
||||
_ => todo!("unhandled parse error: {:?}", parse_problem),
|
||||
EExpr::Return(EReturn::ReturnValue(parse_problem, pos), start) => to_expr_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
Context::InNode(Node::Return, *start),
|
||||
parse_problem,
|
||||
*pos,
|
||||
),
|
||||
EExpr::Return(EReturn::Space(parse_problem, pos), _) => {
|
||||
to_space_report(alloc, lines, filename, parse_problem, *pos)
|
||||
}
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
EExpr::End(pos)
|
||||
| EExpr::Dot(pos)
|
||||
| EExpr::Access(pos)
|
||||
| EExpr::UnaryNot(pos)
|
||||
| EExpr::UnaryNegate(pos)
|
||||
| EExpr::Pattern(_, pos)
|
||||
| EExpr::IndentDefBody(pos)
|
||||
| EExpr::IndentEquals(pos)
|
||||
| EExpr::IndentAnnotation(pos)
|
||||
| EExpr::Equals(pos)
|
||||
| EExpr::DoubleColon(pos)
|
||||
| EExpr::MalformedPattern(pos)
|
||||
| EExpr::BackpassComma(pos)
|
||||
| EExpr::BackpassContinue(pos)
|
||||
| EExpr::DbgContinue(pos)
|
||||
| EExpr::Underscore(pos)
|
||||
| EExpr::Crash(pos)
|
||||
| EExpr::Try(pos)
|
||||
| EExpr::UnexpectedTopLevelExpr(pos) => to_unhandled_parse_error_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
format!("{:?}", parse_problem),
|
||||
*pos,
|
||||
start,
|
||||
),
|
||||
EExpr::RecordUpdateOldBuilderField(region)
|
||||
| EExpr::RecordUpdateIgnoredField(region)
|
||||
| EExpr::RecordBuilderOldBuilderField(region) => to_unhandled_parse_error_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
format!("{:?}", parse_problem),
|
||||
region.start(),
|
||||
start,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_unhandled_parse_error_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
lines: &LineInfo,
|
||||
filename: PathBuf,
|
||||
parse_problem_str: String,
|
||||
pos: Position,
|
||||
start: Position,
|
||||
) -> Report<'a> {
|
||||
let severity = Severity::Fatal;
|
||||
let surroundings = Region::new(start, pos);
|
||||
let region = LineColumnRegion::from_pos(lines.convert_pos(pos));
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow("I got stuck while parsing this:"),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region, severity),
|
||||
alloc.reflow("Here's the internal parse problem:"),
|
||||
alloc.string(parse_problem_str).indent(4),
|
||||
alloc.reflow("Unfortunately, I'm not able to provide a more insightful error message for this syntax problem yet. This is considered a bug in the compiler."),
|
||||
alloc.note("If you'd like to contribute to Roc, this would be a good first issue!")
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "UNHANDLED PARSE ERROR".to_string(),
|
||||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -982,7 +1073,6 @@ fn to_str_report<'a>(
|
|||
suggestion("An escaped quote: ", "\\\""),
|
||||
suggestion("An escaped backslash: ", "\\\\"),
|
||||
suggestion("A unicode code point: ", "\\u(00FF)"),
|
||||
suggestion("An interpolated string: ", "$(myVariable)"),
|
||||
])
|
||||
.indent(4),
|
||||
]);
|
||||
|
@ -2153,7 +2243,26 @@ fn to_pattern_report<'a>(
|
|||
&EPattern::NumLiteral(ENumber::End, pos) => {
|
||||
to_malformed_number_literal_report(alloc, lines, filename, pos)
|
||||
}
|
||||
_ => todo!("unhandled parse error: {:?}", parse_problem),
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
EPattern::AsKeyword(pos)
|
||||
| EPattern::AsIdentifier(pos)
|
||||
| EPattern::Underscore(pos)
|
||||
| EPattern::NotAPattern(pos)
|
||||
| EPattern::End(pos)
|
||||
| EPattern::Space(_, pos)
|
||||
| EPattern::IndentStart(pos)
|
||||
| EPattern::IndentEnd(pos)
|
||||
| EPattern::AsIndentStart(pos)
|
||||
| EPattern::AccessorFunction(pos)
|
||||
| EPattern::RecordUpdaterFunction(pos) => to_unhandled_parse_error_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
format!("{:?}", parse_problem),
|
||||
*pos,
|
||||
start,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2562,7 +2671,7 @@ fn to_type_report<'a>(
|
|||
severity,
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
Next::Other(_) | Next::Keyword(_) | Next::Close(_, _) | Next::Token(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2660,8 +2769,23 @@ fn to_type_report<'a>(
|
|||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
_ => todo!("unhandled type parse error: {:?}", &parse_problem),
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
EType::Space(_, pos)
|
||||
| EType::UnderscoreSpacing(pos)
|
||||
| EType::TWildcard(pos)
|
||||
| EType::TInferred(pos)
|
||||
| EType::TEnd(pos)
|
||||
| EType::TWhereBar(pos)
|
||||
| EType::TImplementsClause(pos)
|
||||
| EType::TAbilityImpl(_, pos) => to_unhandled_parse_error_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
format!("{:?}", parse_problem),
|
||||
*pos,
|
||||
start,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3812,8 +3936,14 @@ fn to_provides_report<'a>(
|
|||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
_ => todo!("unhandled parse error {:?}", parse_problem),
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
EProvides::Open(pos) |
|
||||
EProvides::To(pos) |
|
||||
EProvides::IndentPackage(pos) |
|
||||
EProvides::ListStart(pos) |
|
||||
EProvides::Package(_, pos) =>
|
||||
to_unhandled_parse_error_report(alloc, lines, filename, format!("{:?}", parse_problem), pos, start),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3924,7 +4054,13 @@ fn to_exposes_report<'a>(
|
|||
|
||||
EExposes::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos),
|
||||
|
||||
_ => todo!("unhandled `exposes` parsing error {:?}", parse_problem),
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
EExposes::Open(pos) |
|
||||
EExposes::IndentExposes(pos) |
|
||||
EExposes::IndentListStart(pos) |
|
||||
EExposes::ListStart(pos) =>
|
||||
to_unhandled_parse_error_report(alloc, lines, filename, format!("{:?}", parse_problem), pos, start)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4032,8 +4168,28 @@ fn to_imports_report<'a>(
|
|||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
_ => todo!("unhandled parse error {:?}", parse_problem),
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
EImports::Open(pos)
|
||||
| EImports::IndentListStart(pos)
|
||||
| EImports::IndentListEnd(pos)
|
||||
| EImports::ListStart(pos)
|
||||
| EImports::ExposingDot(pos)
|
||||
| EImports::ShorthandDot(pos)
|
||||
| EImports::Shorthand(pos)
|
||||
| EImports::IndentSetStart(pos)
|
||||
| EImports::SetStart(pos)
|
||||
| EImports::SetEnd(pos)
|
||||
| EImports::TypedIdent(pos)
|
||||
| EImports::AsKeyword(pos)
|
||||
| EImports::StrLiteral(pos) => to_unhandled_parse_error_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
format!("{:?}", parse_problem),
|
||||
pos,
|
||||
start,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4158,8 +4314,18 @@ fn to_requires_report<'a>(
|
|||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
_ => todo!("unhandled parse error {:?}", parse_problem),
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
ERequires::IndentRequires(pos)
|
||||
| ERequires::IndentListStart(pos)
|
||||
| ERequires::TypedIdent(_, pos) => to_unhandled_parse_error_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
format!("{:?}", parse_problem),
|
||||
pos,
|
||||
start,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4222,7 +4388,21 @@ fn to_packages_report<'a>(
|
|||
|
||||
EPackages::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos),
|
||||
|
||||
_ => todo!("unhandled parse error {:?}", parse_problem),
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
EPackages::Open(pos)
|
||||
| EPackages::IndentPackages(pos)
|
||||
| EPackages::ListStart(pos)
|
||||
| EPackages::IndentListStart(pos)
|
||||
| EPackages::IndentListEnd(pos)
|
||||
| EPackages::PackageEntry(_, pos) => to_unhandled_parse_error_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
format!("{:?}", parse_problem),
|
||||
pos,
|
||||
start,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4291,7 +4471,16 @@ fn to_space_report<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
_ => todo!("unhandled type parse error: {:?}", &parse_problem),
|
||||
// If you're adding or changing syntax, please handle the case with a
|
||||
// good error message above instead of adding more unhandled cases below.
|
||||
BadInputError::BadUtf8 => to_unhandled_parse_error_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
format!("{:?}", parse_problem),
|
||||
pos,
|
||||
pos,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::error::canonicalize::{to_circular_def_doc, CIRCULAR_DEF};
|
|||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
|
||||
use itertools::EitherOrBoth;
|
||||
use itertools::Itertools;
|
||||
use roc_can::constraint::{ExpectEffectfulReason, FxCallKind, FxSuffixKind};
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
use roc_collections::all::{HumanIndex, MutSet, SendMap};
|
||||
use roc_collections::VecMap;
|
||||
|
@ -314,6 +315,163 @@ pub fn type_problem<'b>(
|
|||
severity,
|
||||
})
|
||||
}
|
||||
FxInPureFunction(fx_call_region, fx_call_kind, ann_region) => {
|
||||
let lines = [
|
||||
describe_fx_call_kind(alloc, fx_call_kind),
|
||||
alloc.region(lines.convert_region(fx_call_region), severity),
|
||||
match ann_region {
|
||||
Some(ann_region) => alloc.stack([
|
||||
alloc.reflow(
|
||||
"However, the type of the enclosing function requires that it's pure:",
|
||||
),
|
||||
alloc.region(lines.convert_region(ann_region), Severity::Warning),
|
||||
alloc.concat([
|
||||
alloc.tip(),
|
||||
alloc.text("Replace "),
|
||||
alloc.keyword("->"),
|
||||
alloc.text(" with "),
|
||||
alloc.keyword("=>"),
|
||||
alloc.text(" to annotate it as effectful."),
|
||||
]),
|
||||
]),
|
||||
None => {
|
||||
alloc.reflow("However, the enclosing function is required to be pure.")
|
||||
}
|
||||
},
|
||||
alloc.reflow("You can still run the program with this error, which can be helpful when you're debugging."),
|
||||
];
|
||||
|
||||
Some(Report {
|
||||
filename,
|
||||
title: "EFFECT IN PURE FUNCTION".to_string(),
|
||||
doc: alloc.stack(lines),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
FxInTopLevel(call_region, fx_call_kind) => {
|
||||
let lines = [
|
||||
describe_fx_call_kind(alloc, fx_call_kind),
|
||||
alloc.region(lines.convert_region(call_region), severity),
|
||||
alloc.reflow("However, it appears in a top-level def instead of a function. If we allowed this, importing this module would produce a side effect."),
|
||||
alloc.concat([
|
||||
alloc.tip(),
|
||||
alloc.reflow("If you don't need any arguments, use an empty record:"),
|
||||
]),
|
||||
alloc.parser_suggestion(" askName! : {} => Str\n askName! = \\{} ->\n Stdout.line! \"What's your name?\"\n Stdin.line! {}"),
|
||||
alloc.reflow("This will allow the caller to control when the effects run."),
|
||||
];
|
||||
|
||||
Some(Report {
|
||||
filename,
|
||||
title: "EFFECT IN TOP-LEVEL".to_string(),
|
||||
doc: alloc.stack(lines),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
ExpectedEffectful(region, ExpectEffectfulReason::Stmt) => {
|
||||
let stack = [
|
||||
alloc.reflow("This statement does not produce any effects:"),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow(
|
||||
"Standalone statements are only useful if they call effectful functions.",
|
||||
),
|
||||
alloc.reflow("Did you forget to use its result? If not, feel free to remove it."),
|
||||
];
|
||||
Some(Report {
|
||||
title: "LEFTOVER STATEMENT".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack(stack),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
ExpectedEffectful(region, ExpectEffectfulReason::Ignored) => {
|
||||
let stack = [
|
||||
alloc.reflow("This assignment doesn't introduce any new variables:"),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("Since it doesn't call any effectful functions, this assignment cannot affect the program's behavior. If you don't need to use the value on the right-hand side, consider removing the assignment.")
|
||||
];
|
||||
|
||||
Some(Report {
|
||||
title: "UNNECESSARY DEFINITION".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack(stack),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
UnsuffixedEffectfulFunction(
|
||||
region,
|
||||
kind @ (FxSuffixKind::Let(symbol) | FxSuffixKind::Pattern(symbol)),
|
||||
) => {
|
||||
let stack = [
|
||||
match kind {
|
||||
FxSuffixKind::Let(_) => alloc
|
||||
.reflow("This function is effectful, but its name does not indicate so:"),
|
||||
FxSuffixKind::Pattern(_) => alloc.reflow(
|
||||
"This is an effectful function, but its name does not indicate so:",
|
||||
),
|
||||
FxSuffixKind::UnsuffixedRecordField => {
|
||||
unreachable!()
|
||||
}
|
||||
},
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("Add an exclamation mark at the end, like:"),
|
||||
alloc
|
||||
.string(format!("{}!", symbol.as_str(alloc.interns)))
|
||||
.indent(4),
|
||||
alloc.reflow("This will help readers identify it as a source of effects."),
|
||||
];
|
||||
Some(Report {
|
||||
title: "MISSING EXCLAMATION".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack(stack),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
UnsuffixedEffectfulFunction(region, FxSuffixKind::UnsuffixedRecordField) => {
|
||||
let stack = [
|
||||
alloc.reflow(
|
||||
"This field's value is an effectful function, but its name does not indicate so:",
|
||||
),
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("Add an exclamation mark at the end, like:"),
|
||||
alloc
|
||||
.parser_suggestion("{ readFile! : File.read! }")
|
||||
.indent(4),
|
||||
alloc.reflow("This will help readers identify it as a source of effects."),
|
||||
];
|
||||
Some(Report {
|
||||
title: "MISSING EXCLAMATION".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack(stack),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
SuffixedPureFunction(region, kind) => {
|
||||
let stack = [
|
||||
match kind {
|
||||
FxSuffixKind::Let(_) => {
|
||||
alloc.reflow("This function is pure, but its name suggests otherwise:")
|
||||
}
|
||||
FxSuffixKind::Pattern(_) => {
|
||||
alloc.reflow("This is a pure function, but its name suggests otherwise:")
|
||||
}
|
||||
FxSuffixKind::UnsuffixedRecordField => {
|
||||
unreachable!()
|
||||
}
|
||||
},
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc
|
||||
.reflow("The exclamation mark at the end is reserved for effectful functions."),
|
||||
alloc.hint("Did you forget to run an effect? Is the type annotation wrong?"),
|
||||
];
|
||||
|
||||
Some(Report {
|
||||
title: "UNNECESSARY EXCLAMATION".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack(stack),
|
||||
severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1645,6 +1803,81 @@ fn to_expr_report<'b>(
|
|||
unimplemented!("record default field is not implemented yet")
|
||||
}
|
||||
Reason::ImportParams(_) => unreachable!(),
|
||||
|
||||
Reason::FunctionOutput => {
|
||||
let problem = alloc.concat([
|
||||
alloc.text("This "),
|
||||
alloc.keyword("return"),
|
||||
alloc.reflow(
|
||||
" statement doesn't match the return type of its enclosing function:",
|
||||
),
|
||||
]);
|
||||
|
||||
let comparison = type_comparison(
|
||||
alloc,
|
||||
found,
|
||||
expected_type,
|
||||
ExpectationContext::Arbitrary,
|
||||
add_category(alloc, alloc.text("It"), &category),
|
||||
alloc.reflow("But I expected the function to have return type:"),
|
||||
None,
|
||||
);
|
||||
|
||||
Report {
|
||||
title: "TYPE MISMATCH".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack([
|
||||
problem,
|
||||
alloc.region_with_subregion(
|
||||
lines.convert_region(region),
|
||||
lines.convert_region(expr_region),
|
||||
severity,
|
||||
),
|
||||
comparison,
|
||||
]),
|
||||
severity,
|
||||
}
|
||||
}
|
||||
|
||||
Reason::Stmt(opt_name) => {
|
||||
let diff = to_diff(alloc, Parens::Unnecessary, found.clone(), expected_type);
|
||||
|
||||
let lines = [
|
||||
match opt_name {
|
||||
None => alloc.reflow("The result of this expression is ignored:"),
|
||||
Some(fn_name) => alloc.concat([
|
||||
alloc.reflow("The result of this call to "),
|
||||
alloc.symbol_qualified(fn_name),
|
||||
alloc.reflow(" is ignored:"),
|
||||
]),
|
||||
},
|
||||
alloc.region(lines.convert_region(region), severity),
|
||||
alloc.reflow("Standalone statements are required to produce an empty record, but the type of this one is:"),
|
||||
alloc.type_block(type_with_able_vars(alloc, diff.left, diff.left_able)),
|
||||
match found {
|
||||
ErrorType::EffectfulFunc | ErrorType::Function(_, _, _, _) => {
|
||||
alloc.hint("Did you forget to call the function?")
|
||||
}
|
||||
_ => alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow("If you still want to ignore it, assign it to "),
|
||||
alloc.keyword("_"),
|
||||
alloc.reflow(", like this:"),
|
||||
]),
|
||||
alloc
|
||||
.parser_suggestion("_ = File.delete! \"data.json\"")
|
||||
.indent(4),
|
||||
])
|
||||
},
|
||||
];
|
||||
|
||||
Report {
|
||||
filename,
|
||||
title: "IGNORED RESULT".to_string(),
|
||||
doc: alloc.stack(lines),
|
||||
severity,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1675,7 +1908,7 @@ fn describe_wanted_function(tipe: &ErrorType) -> DescribedFunction {
|
|||
use ErrorType::*;
|
||||
|
||||
match tipe {
|
||||
Function(args, _, _) => DescribedFunction::Arguments(args.len()),
|
||||
Function(args, _, _, _) => DescribedFunction::Arguments(args.len()),
|
||||
Alias(_, _, actual, AliasKind::Structural) => describe_wanted_function(actual),
|
||||
Alias(_, _, actual, AliasKind::Opaque) => {
|
||||
let tag = if matches!(
|
||||
|
@ -1956,7 +2189,7 @@ fn format_category<'b>(
|
|||
}
|
||||
|
||||
Uniqueness => (
|
||||
alloc.concat([this_is, alloc.text(" an uniqueness attribute")]),
|
||||
alloc.concat([this_is, alloc.text(" a uniqueness attribute")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
Crash => {
|
||||
|
@ -1983,6 +2216,10 @@ fn format_category<'b>(
|
|||
alloc.concat([this_is, alloc.text(" a dbg statement")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
Return => (
|
||||
alloc.concat([text!(alloc, "{}his", t), alloc.reflow(" returns a value")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2523,12 +2760,13 @@ fn to_doc_help<'b>(
|
|||
use ErrorType::*;
|
||||
|
||||
match tipe {
|
||||
Function(args, _, ret) => report_text::function(
|
||||
Function(args, _, fx, ret) => report_text::function(
|
||||
alloc,
|
||||
parens,
|
||||
args.into_iter()
|
||||
.map(|arg| to_doc_help(ctx, gen_usages, alloc, Parens::InFn, arg))
|
||||
.collect(),
|
||||
fx,
|
||||
to_doc_help(ctx, gen_usages, alloc, Parens::InFn, *ret),
|
||||
),
|
||||
Infinite => alloc.text("∞"),
|
||||
|
@ -2551,6 +2789,8 @@ fn to_doc_help<'b>(
|
|||
alloc.type_variable(lowercase)
|
||||
}
|
||||
|
||||
EffectfulFunc => alloc.text("effectful function"),
|
||||
|
||||
Type(symbol, args) => report_text::apply(
|
||||
alloc,
|
||||
parens,
|
||||
|
@ -2745,6 +2985,7 @@ fn count_generated_name_usages<'a>(
|
|||
RigidVar(name) | RigidAbleVar(name, _) => {
|
||||
debug_assert!(!is_generated_name(name));
|
||||
}
|
||||
EffectfulFunc => {}
|
||||
Type(_, tys) => {
|
||||
stack.extend(tys.iter().map(|t| (t, only_unseen)));
|
||||
}
|
||||
|
@ -2765,7 +3006,7 @@ fn count_generated_name_usages<'a>(
|
|||
stack.extend(tags.values().flatten().map(|t| (t, only_unseen)));
|
||||
ext_stack.push((ext, only_unseen));
|
||||
}
|
||||
Function(args, _lset, ret) => {
|
||||
Function(args, _lset, _fx, ret) => {
|
||||
stack.extend(args.iter().map(|t| (t, only_unseen)));
|
||||
stack.push((ret, only_unseen));
|
||||
}
|
||||
|
@ -2940,7 +3181,7 @@ fn to_diff<'b>(
|
|||
}
|
||||
}
|
||||
|
||||
(Function(args1, _, ret1), Function(args2, _, ret2)) => {
|
||||
(Function(args1, _, fx1, ret1), Function(args2, _, fx2, ret2)) => {
|
||||
if args1.len() == args2.len() {
|
||||
let mut status = Status::Similar;
|
||||
let arg_diff = diff_args(alloc, Parens::InFn, args1, args2);
|
||||
|
@ -2948,8 +3189,8 @@ fn to_diff<'b>(
|
|||
status.merge(arg_diff.status);
|
||||
status.merge(ret_diff.status);
|
||||
|
||||
let left = report_text::function(alloc, parens, arg_diff.left, ret_diff.left);
|
||||
let right = report_text::function(alloc, parens, arg_diff.right, ret_diff.right);
|
||||
let left = report_text::function(alloc, parens, arg_diff.left, fx1, ret_diff.left);
|
||||
let right = report_text::function(alloc, parens, arg_diff.right, fx2, ret_diff.right);
|
||||
let mut left_able = arg_diff.left_able;
|
||||
left_able.extend(ret_diff.left_able);
|
||||
let mut right_able = arg_diff.right_able;
|
||||
|
@ -3538,9 +3779,10 @@ fn should_show_diff(t1: &ErrorType, t2: &ErrorType) -> bool {
|
|||
.any(|(p1, p2)| should_show_diff(p1, p2))
|
||||
})
|
||||
}
|
||||
(Function(params1, ret1, l1), Function(params2, ret2, l2)) => {
|
||||
(Function(params1, ret1, fx1, l1), Function(params2, ret2, fx2, l2)) => {
|
||||
if params1.len() != params2.len()
|
||||
|| should_show_diff(ret1, ret2)
|
||||
|| fx1 != fx2
|
||||
|| should_show_diff(l1, l2)
|
||||
{
|
||||
return true;
|
||||
|
@ -3602,8 +3844,10 @@ fn should_show_diff(t1: &ErrorType, t2: &ErrorType) -> bool {
|
|||
| (_, TagUnion(_, _, _))
|
||||
| (RecursiveTagUnion(_, _, _, _), _)
|
||||
| (_, RecursiveTagUnion(_, _, _, _))
|
||||
| (Function(_, _, _), _)
|
||||
| (_, Function(_, _, _)) => true,
|
||||
| (Function(_, _, _, _), _)
|
||||
| (_, Function(_, _, _, _))
|
||||
| (EffectfulFunc, _)
|
||||
| (_, EffectfulFunc) => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4061,7 +4305,7 @@ mod report_text {
|
|||
use crate::report::{Annotation, RocDocAllocator, RocDocBuilder};
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_types::pretty_print::Parens;
|
||||
use roc_types::types::{ErrorType, RecordField, TypeExt};
|
||||
use roc_types::types::{ErrorFunctionFx, ErrorType, RecordField, TypeExt};
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
fn with_parens<'b>(
|
||||
|
@ -4075,11 +4319,15 @@ mod report_text {
|
|||
alloc: &'b RocDocAllocator<'b>,
|
||||
parens: Parens,
|
||||
args: Vec<RocDocBuilder<'b>>,
|
||||
fx: ErrorFunctionFx,
|
||||
ret: RocDocBuilder<'b>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
let function_doc = alloc.concat([
|
||||
alloc.intersperse(args, alloc.reflow(", ")),
|
||||
alloc.reflow(" -> "),
|
||||
match fx {
|
||||
ErrorFunctionFx::Pure => alloc.text(" -> "),
|
||||
ErrorFunctionFx::Effectful => alloc.text(" => "),
|
||||
},
|
||||
ret,
|
||||
]);
|
||||
|
||||
|
@ -4640,12 +4888,12 @@ fn type_problem_to_pretty<'b>(
|
|||
};
|
||||
|
||||
match tipe {
|
||||
Infinite | Error | FlexVar(_) => alloc.nil(),
|
||||
Infinite | Error | FlexVar(_) | EffectfulFunc => alloc.nil(),
|
||||
FlexAbleVar(_, other_abilities) => {
|
||||
rigid_able_vs_different_flex_able(x, abilities, other_abilities)
|
||||
}
|
||||
RigidVar(y) | RigidAbleVar(y, _) => bad_double_rigid(x, y),
|
||||
Function(_, _, _) => rigid_able_vs_concrete(x, alloc.reflow("a function value")),
|
||||
Function(_, _, _, _) => rigid_able_vs_concrete(x, alloc.reflow("a function value")),
|
||||
Record(_, _) => rigid_able_vs_concrete(x, alloc.reflow("a record value")),
|
||||
Tuple(_, _) => rigid_able_vs_concrete(x, alloc.reflow("a tuple value")),
|
||||
TagUnion(_, _, _) | RecursiveTagUnion(_, _, _, _) => {
|
||||
|
@ -4733,8 +4981,9 @@ fn type_problem_to_pretty<'b>(
|
|||
};
|
||||
bad_rigid_var(x, msg)
|
||||
}
|
||||
EffectfulFunc => alloc.reflow("an effectful function"),
|
||||
RigidVar(y) | RigidAbleVar(y, _) => bad_double_rigid(x, y),
|
||||
Function(_, _, _) => bad_rigid_var(x, alloc.reflow("a function value")),
|
||||
Function(_, _, _, _) => bad_rigid_var(x, alloc.reflow("a function value")),
|
||||
Record(_, _) => bad_rigid_var(x, alloc.reflow("a record value")),
|
||||
Tuple(_, _) => bad_rigid_var(x, alloc.reflow("a tuple value")),
|
||||
TagUnion(_, _, _) | RecursiveTagUnion(_, _, _, _) => {
|
||||
|
@ -5275,3 +5524,19 @@ fn pattern_to_doc_help<'b>(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn describe_fx_call_kind<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
kind: FxCallKind,
|
||||
) -> RocDocBuilder<'b> {
|
||||
match kind {
|
||||
FxCallKind::Call(Some(name)) => alloc.concat([
|
||||
alloc.reflow("This call to "),
|
||||
alloc.symbol_qualified(name),
|
||||
alloc.reflow(" might produce an effect:"),
|
||||
]),
|
||||
FxCallKind::Call(None) => alloc.reflow("This expression calls an effectful function:"),
|
||||
FxCallKind::Stmt => alloc.reflow("This statement calls an effectful function:"),
|
||||
FxCallKind::Ignored => alloc.reflow("This ignored def calls an effectful function:"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,9 @@ pub fn pretty_header_with_path(title: &str, path: &Path) -> String {
|
|||
relative_path.to_string()
|
||||
};
|
||||
|
||||
// ensure path conatians only unix slashes
|
||||
let path = path.replace('\\', "/");
|
||||
|
||||
let header = format!(
|
||||
"── {} {} {} {}",
|
||||
title,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue