error message for record update and missing module

This commit is contained in:
Folkert 2020-07-14 23:24:30 +02:00
parent 27317110f2
commit 331a8ed5eb
8 changed files with 153 additions and 8 deletions

View file

@ -115,7 +115,11 @@ impl<'a> Env<'a> {
} }
None => Err(RuntimeError::ModuleNotImported { None => Err(RuntimeError::ModuleNotImported {
module_name, module_name,
ident: ident.into(), imported_modules: self
.module_ids
.available_modules()
.map(|string| string.as_ref().into())
.collect(),
region, region,
}), }),
} }

View file

@ -214,10 +214,17 @@ pub fn canonicalize_expr<'a>(
(answer, output) (answer, output)
} else { } else {
panic!( // only (optionally qualified) variables can be updated, not arbitrary expressions
"TODO canonicalize invalid record update (non-Var in update position)\n{:?}",
can_update.value let error = roc_problem::can::RuntimeError::InvalidRecordUpdate {
); region: can_update.region,
};
let answer = Expr::RuntimeError(error.clone());
env.problems.push(Problem::RuntimeError(error));
(answer, Output::default())
} }
} }
ast::Expr::Record { ast::Expr::Record {

View file

@ -346,6 +346,10 @@ impl ModuleIds {
pub fn get_name(&self, id: ModuleId) -> Option<&InlinableString> { pub fn get_name(&self, id: ModuleId) -> Option<&InlinableString> {
self.by_id.get(id.0 as usize) self.by_id.get(id.0 as usize)
} }
pub fn available_modules(&self) -> impl Iterator<Item = &InlinableString> {
self.by_id.iter()
}
} }
/// An ID that is assigned to interned string identifiers within a module. /// An ID that is assigned to interned string identifiers within a module.

View file

@ -110,12 +110,15 @@ pub enum RuntimeError {
}, },
ModuleNotImported { ModuleNotImported {
module_name: InlinableString, module_name: InlinableString,
ident: InlinableString, imported_modules: MutSet<Box<str>>,
region: Region, region: Region,
}, },
InvalidPrecedence(PrecedenceProblem, Region), InvalidPrecedence(PrecedenceProblem, Region),
MalformedIdentifier(Box<str>, Region), MalformedIdentifier(Box<str>, Region),
MalformedClosure(Region), MalformedClosure(Region),
InvalidRecordUpdate {
region: Region,
},
InvalidFloat(FloatErrorKind, Region, Box<str>), InvalidFloat(FloatErrorKind, Region, Box<str>),
InvalidInt(IntErrorKind, Base, Region, Box<str>), InvalidInt(IntErrorKind, Base, Region, Box<str>),
CircularDef(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>), CircularDef(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),

View file

@ -377,7 +377,11 @@ fn pretty_runtime_error<'b>(
todo!("unsupported patterns are currently not parsed!") todo!("unsupported patterns are currently not parsed!")
} }
RuntimeError::ValueNotExposed { .. } => todo!("value not exposed"), RuntimeError::ValueNotExposed { .. } => todo!("value not exposed"),
RuntimeError::ModuleNotImported { .. } => todo!("module not imported"), RuntimeError::ModuleNotImported {
module_name,
imported_modules,
region,
} => module_not_found(alloc, region, &module_name, imported_modules),
RuntimeError::InvalidPrecedence(_, _) => { RuntimeError::InvalidPrecedence(_, _) => {
// do nothing, reported with PrecedenceProblem // do nothing, reported with PrecedenceProblem
unreachable!() unreachable!()
@ -512,6 +516,14 @@ fn pretty_runtime_error<'b>(
hint, hint,
]) ])
} }
RuntimeError::InvalidRecordUpdate { region } => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This expression cannot be updated"),
alloc.reflow(":"),
]),
alloc.region(region),
alloc.reflow("Only variables can be updated with record update syntax."),
]),
RuntimeError::NoImplementation => todo!("no implementation, unreachable"), RuntimeError::NoImplementation => todo!("no implementation, unreachable"),
} }
} }
@ -562,3 +574,49 @@ fn not_found<'b>(
to_details(default_no, default_yes), to_details(default_no, default_yes),
]) ])
} }
fn module_not_found<'b>(
alloc: &'b RocDocAllocator<'b>,
region: roc_region::all::Region,
name: &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("Is there an import missing? Perhaps there is a typo, these names seem close:");
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("The `"),
alloc.string(name.to_string()),
alloc.reflow("` module is not imported:"),
]),
alloc.region(region),
to_details(default_no, default_yes),
])
}

View file

@ -1,3 +1,4 @@
use inlinable_string::InlinableString;
use roc_module::ident::Ident; use roc_module::ident::Ident;
use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::ident::{Lowercase, TagName, Uppercase};
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -297,6 +298,10 @@ impl<'a> RocDocAllocator<'a> {
.annotate(Annotation::Module) .annotate(Annotation::Module)
} }
pub fn inlinable_string(&'a self, s: InlinableString) -> DocBuilder<'a, Self, Annotation> {
self.text(format!("{}", s)).annotate(Annotation::Module)
}
pub fn binop( pub fn binop(
&'a self, &'a self,
content: roc_module::operator::BinOp, content: roc_module::operator::BinOp,

View file

@ -145,7 +145,10 @@ pub fn can_expr_with(
let mut var_store = VarStore::default(); let mut var_store = VarStore::default();
let var = var_store.fresh(); let var = var_store.fresh();
let expected = Expected::NoExpectation(Type::Variable(var)); let expected = Expected::NoExpectation(Type::Variable(var));
let module_ids = ModuleIds::default(); let mut module_ids = ModuleIds::default();
// ensure the Test module is accessible in our tests
module_ids.get_or_insert(&"Test".into());
// Desugar operators (convert them to Apply calls, taking into account // Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization. // operator precedence and associativity rules), before doing other canonicalization.

View file

@ -3395,4 +3395,65 @@ mod test_reporting {
), ),
) )
} }
#[test]
fn invalid_record_update() {
report_problem_as(
indoc!(
r#"
foo = { bar: 3 }
updateNestedRecord = { foo.bar & x: 4 }
example = { age: 42 }
# these should work
y = { Test.example & age: 3 }
x = { example & age: 4 }
{ updateNestedRecord, foo, x, y }
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This expression cannot be updated:
2 updateNestedRecord = { foo.bar & x: 4 }
^^^^^^^
Only variables can be updated with record update syntax.
"#
),
)
}
#[test]
fn module_not_imported() {
report_problem_as(
indoc!(
r#"
Foo.test
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
The `Foo` module is not imported:
1 Foo.test
^^^^^^^^
Is there an import missing? Perhaps there is a typo, these names seem
close:
Bool
Num
Map
Set
"#
),
)
}
} }