Merge pull request #2574 from rtfeldman/fix-panic-on-unimported-module

[Bug] Handle unimported modules properly
This commit is contained in:
hafiz 2022-02-26 16:39:22 -05:00 committed by GitHub
commit 1aec997843
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 117 additions and 32 deletions

View file

@ -160,12 +160,17 @@ impl<'a> Env<'a> {
}) })
} }
}, },
None => { None => Err(RuntimeError::ModuleNotImported {
panic!( module_name,
"Module {} exists, but is not recorded in dep_idents", imported_modules: self
module_name .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()) .map(|string| string.as_ref().into())
.collect(), .collect(),
region, region,
module_exists: false,
}), }),
} }
} }

View file

@ -130,7 +130,9 @@ fn make_apply_symbol(
// it was imported but it doesn't expose this ident. // it was imported but it doesn't expose this ident.
env.problem(roc_problem::can::Problem::RuntimeError(problem)); 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))
} }
} }
} }

View file

@ -124,12 +124,17 @@ impl<'a> Env<'a> {
}) })
} }
}, },
None => { None => Err(RuntimeError::ModuleNotImported {
panic!( module_name,
"Module {} exists, but is not recorded in dep_idents", imported_modules: self
module_name .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()) .map(|string| string.as_ref().into())
.collect(), .collect(),
region, region,
module_exists: false,
}), }),
} }
} }

View file

@ -171,10 +171,37 @@ pub enum RuntimeError {
region: Region, region: Region,
exposed_values: Vec<Lowercase>, 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 { ModuleNotImported {
/// The name of the module that was referenced
module_name: ModuleName, module_name: ModuleName,
/// A list of modules which *have* been imported
imported_modules: MutSet<Box<str>>, imported_modules: MutSet<Box<str>>,
/// Where the problem occurred
region: Region, 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), InvalidPrecedence(PrecedenceProblem, Region),
MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region), MalformedIdentifier(Box<str>, roc_parse::ident::BadIdent, Region),

View file

@ -1015,8 +1015,16 @@ fn pretty_runtime_error<'b>(
module_name, module_name,
imported_modules, imported_modules,
region, 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; title = MODULE_NOT_IMPORTED;
} }
@ -1501,34 +1509,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>( fn module_not_found<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
lines: &LineInfo, lines: &LineInfo,
region: roc_region::all::Region, region: roc_region::all::Region,
name: &ModuleName, name: &ModuleName,
options: MutSet<Box<str>>, options: MutSet<Box<str>>,
module_exists: bool,
) -> RocDocBuilder<'b> { ) -> 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 = let mut suggestions =
suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect()); suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect());
suggestions.truncate(4); 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.reflow("Is there an "),
alloc.keyword("import"), alloc.keyword("import"),
alloc.reflow(" or "), alloc.reflow(" or "),
alloc.keyword("exposing"), alloc.keyword("exposing"),
alloc.reflow(" missing up-top"), 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 { } else {
alloc.stack(vec![ 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 alloc
.vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string()))) .vcat(suggestions.into_iter().map(|v| alloc.string(v.to_string())))
.indent(4), .indent(4),
@ -1543,6 +1556,6 @@ fn module_not_found<'b>(
alloc.reflow("` module is not imported:"), alloc.reflow("` module is not imported:"),
]), ]),
alloc.region(lines.convert_region(region)), alloc.region(lines.convert_region(region)),
to_details(default_no, default_yes), details,
]) ])
} }

View file

@ -8274,4 +8274,35 @@ I need all branches in an `if` to have the same type!
), ),
) )
} }
#[test]
fn unimported_modules_reported() {
report_problem_as(
indoc!(
r#"
main : Task.Task {} []
main = "whatever man you don't even know my type"
main
"#
),
indoc!(
r#"
MODULE NOT IMPORTED
The `Task` module is not imported:
1 main : Task.Task {} []
^^^^^^^^^^^^^^^
Is there an import missing? Perhaps there is a typo. Did you mean one
of these?
Test
List
Num
Set
"#
),
)
}
} }