Merge branch 'main' into specialize-exprs

This commit is contained in:
Agus Zubiaga 2024-11-23 01:48:51 -03:00
commit 2e96aca0fd
No known key found for this signature in database
797 changed files with 17394 additions and 12632 deletions

View file

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

View file

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

View file

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

View file

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