mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 22:34:45 +00:00
error message for record update and missing module
This commit is contained in:
parent
27317110f2
commit
331a8ed5eb
8 changed files with 153 additions and 8 deletions
|
@ -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,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 */)>),
|
||||||
|
|
|
@ -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),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue