mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24:45 +00:00
🐛️ Handle unimported modules properly
helpful error, not panic! Closes #2422
This commit is contained in:
parent
b5b26eabc9
commit
4d10c22442
7 changed files with 93 additions and 32 deletions
|
@ -160,12 +160,17 @@ impl<'a> Env<'a> {
|
|||
})
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!(
|
||||
"Module {} exists, but is not recorded in dep_idents",
|
||||
module_name
|
||||
)
|
||||
}
|
||||
None => Err(RuntimeError::ModuleNotImported {
|
||||
module_name,
|
||||
imported_modules: self
|
||||
.dep_idents
|
||||
.keys()
|
||||
.filter_map(|module_id| self.module_ids.get_name(*module_id))
|
||||
.map(|module_name| module_name.as_ref().into())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: true,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -177,6 +182,7 @@ impl<'a> Env<'a> {
|
|||
.map(|string| string.as_ref().into())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,9 @@ fn make_apply_symbol(
|
|||
// it was imported but it doesn't expose this ident.
|
||||
env.problem(roc_problem::can::Problem::RuntimeError(problem));
|
||||
|
||||
Err(Type::Erroneous(Problem::UnrecognizedIdent((*ident).into())))
|
||||
// A failed import should have already been reported through
|
||||
// roc_can::env::Env::qualified_lookup's checks
|
||||
Err(Type::Erroneous(Problem::SolvedTypeError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,12 +124,17 @@ impl<'a> Env<'a> {
|
|||
})
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!(
|
||||
"Module {} exists, but is not recorded in dep_idents",
|
||||
module_name
|
||||
)
|
||||
}
|
||||
None => Err(RuntimeError::ModuleNotImported {
|
||||
module_name,
|
||||
imported_modules: self
|
||||
.dep_idents
|
||||
.keys()
|
||||
.filter_map(|module_id| self.module_ids.get_name(*module_id))
|
||||
.map(|module_name| module_name.as_ref().into())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: true,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +146,7 @@ impl<'a> Env<'a> {
|
|||
.map(|string| string.as_ref().into())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3227,6 +3227,8 @@ fn canonicalize_and_constrain<'a>(
|
|||
|
||||
module_timing.canonicalize = canonicalize_end.duration_since(canonicalize_start).unwrap();
|
||||
|
||||
use roc_can::module::ModuleOutput;
|
||||
use std::process;
|
||||
match canonicalized {
|
||||
Ok(module_output) => {
|
||||
// Generate documentation information
|
||||
|
|
|
@ -8629,6 +8629,11 @@ fn match_on_lambda_set<'a>(
|
|||
env.arena.alloc(result),
|
||||
)
|
||||
}
|
||||
Layout::Struct([]) => {
|
||||
// This is a lambda set with no associated lambdas, often produced as a result
|
||||
// of a runtime error at another point in the code.
|
||||
Stmt::RuntimeError("Cannot have a lambda set with zero variants")
|
||||
}
|
||||
Layout::Struct(fields) => {
|
||||
let function_symbol = lambda_set.set[0].0;
|
||||
|
||||
|
|
|
@ -161,10 +161,37 @@ pub enum RuntimeError {
|
|||
region: Region,
|
||||
exposed_values: Vec<Lowercase>,
|
||||
},
|
||||
/// A module was referenced, but hasn't been imported anywhere in the program
|
||||
///
|
||||
/// An example would be:
|
||||
/// ```roc
|
||||
/// app "hello"
|
||||
/// packages { pf: "platform" }
|
||||
/// imports [ pf.Stdout]
|
||||
/// provides [ main ] to pf
|
||||
///
|
||||
/// main : Task.Task {} [] // Task isn't imported!
|
||||
/// main = Stdout.line "I'm a Roc application!"
|
||||
/// ```
|
||||
ModuleNotImported {
|
||||
/// The name of the module that was referenced
|
||||
module_name: ModuleName,
|
||||
/// A list of modules which *have* been imported
|
||||
imported_modules: MutSet<Box<str>>,
|
||||
/// Where the problem occured
|
||||
region: Region,
|
||||
/// Whether or not the module exists at all
|
||||
///
|
||||
/// This is used to suggest that the user import the module, as opposed to fix a
|
||||
/// typo in the spelling. For example, if the user typed `Task`, and the platform
|
||||
/// exposes a `Task` module that hasn't been imported, we can sugguest that they
|
||||
/// add the import statement.
|
||||
///
|
||||
/// On the other hand, if the user typed `Tesk`, they might want to check their
|
||||
/// spelling.
|
||||
///
|
||||
/// If unsure, this should be set to `false`
|
||||
module_exists: bool,
|
||||
},
|
||||
InvalidPrecedence(PrecedenceProblem, Region),
|
||||
MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region),
|
||||
|
|
|
@ -999,8 +999,16 @@ fn pretty_runtime_error<'b>(
|
|||
module_name,
|
||||
imported_modules,
|
||||
region,
|
||||
module_exists,
|
||||
} => {
|
||||
doc = module_not_found(alloc, lines, region, &module_name, imported_modules);
|
||||
doc = module_not_found(
|
||||
alloc,
|
||||
lines,
|
||||
region,
|
||||
&module_name,
|
||||
imported_modules,
|
||||
module_exists,
|
||||
);
|
||||
|
||||
title = MODULE_NOT_IMPORTED;
|
||||
}
|
||||
|
@ -1414,34 +1422,39 @@ fn not_found<'b>(
|
|||
])
|
||||
}
|
||||
|
||||
/// Generate a message informing the user that a module was referenced, but not found
|
||||
///
|
||||
/// See [`roc_problem::can::ModuleNotImported`]
|
||||
fn module_not_found<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
lines: &LineInfo,
|
||||
region: roc_region::all::Region,
|
||||
name: &ModuleName,
|
||||
options: MutSet<Box<str>>,
|
||||
module_exists: bool,
|
||||
) -> RocDocBuilder<'b> {
|
||||
// If the module exists, sugguest that the user import it
|
||||
let details = if module_exists {
|
||||
// TODO: Maybe give an example of how to do that
|
||||
alloc.reflow("Did you mean to import it?")
|
||||
} else {
|
||||
// If the module might not exist, sugguest that it's a typo
|
||||
let mut suggestions =
|
||||
suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect());
|
||||
suggestions.truncate(4);
|
||||
|
||||
let default_no = alloc.concat(vec![
|
||||
if suggestions.is_empty() {
|
||||
// We don't have any recommended spelling corrections
|
||||
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. Did you mean one of these?");
|
||||
|
||||
let to_details = |no_suggestion_details, yes_suggestion_details| {
|
||||
if suggestions.is_empty() {
|
||||
no_suggestion_details
|
||||
])
|
||||
} else {
|
||||
alloc.stack(vec![
|
||||
yes_suggestion_details,
|
||||
alloc.reflow("Is there an import missing? Perhaps there is a typo. Did you mean one of these?"),
|
||||
alloc
|
||||
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
|
||||
.indent(4),
|
||||
|
@ -1456,6 +1469,6 @@ fn module_not_found<'b>(
|
|||
alloc.reflow("` module is not imported:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region)),
|
||||
to_details(default_no, default_yes),
|
||||
details,
|
||||
])
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue