mirror of
https://github.com/roc-lang/roc.git
synced 2025-11-26 13:52:15 +00:00
Report unused imports in modules
This commit is contained in:
parent
5dc51ce444
commit
92aa0912ea
8 changed files with 72 additions and 27 deletions
|
|
@ -122,7 +122,7 @@ pub struct ExposedModuleTypes {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
pub module_id: ModuleId,
|
pub module_id: ModuleId,
|
||||||
pub exposed_imports: MutMap<Symbol, Variable>,
|
pub exposed_imports: MutMap<Symbol, Region>,
|
||||||
pub exposed_symbols: VecSet<Symbol>,
|
pub exposed_symbols: VecSet<Symbol>,
|
||||||
pub referenced_values: VecSet<Symbol>,
|
pub referenced_values: VecSet<Symbol>,
|
||||||
pub referenced_types: VecSet<Symbol>,
|
pub referenced_types: VecSet<Symbol>,
|
||||||
|
|
@ -145,8 +145,7 @@ pub struct ModuleOutput {
|
||||||
pub aliases: MutMap<Symbol, Alias>,
|
pub aliases: MutMap<Symbol, Alias>,
|
||||||
pub rigid_variables: RigidVariables,
|
pub rigid_variables: RigidVariables,
|
||||||
pub declarations: Declarations,
|
pub declarations: Declarations,
|
||||||
pub exposed_imports: MutMap<Symbol, Variable>,
|
pub exposed_imports: MutMap<Symbol, Region>,
|
||||||
pub lookups: Vec<(Symbol, Variable, Region)>,
|
|
||||||
pub problems: Vec<Problem>,
|
pub problems: Vec<Problem>,
|
||||||
pub referenced_values: VecSet<Symbol>,
|
pub referenced_values: VecSet<Symbol>,
|
||||||
pub referenced_types: VecSet<Symbol>,
|
pub referenced_types: VecSet<Symbol>,
|
||||||
|
|
@ -277,7 +276,6 @@ pub fn canonicalize_module_defs<'a>(
|
||||||
let mut can_exposed_imports = MutMap::default();
|
let mut can_exposed_imports = MutMap::default();
|
||||||
let mut scope = Scope::new(home, exposed_ident_ids, imported_abilities_state);
|
let mut scope = Scope::new(home, exposed_ident_ids, imported_abilities_state);
|
||||||
let mut env = Env::new(arena, home, dep_idents, module_ids);
|
let mut env = Env::new(arena, home, dep_idents, module_ids);
|
||||||
let num_deps = dep_idents.len();
|
|
||||||
|
|
||||||
for (name, alias) in aliases.into_iter() {
|
for (name, alias) in aliases.into_iter() {
|
||||||
scope.add_alias(
|
scope.add_alias(
|
||||||
|
|
@ -301,7 +299,6 @@ pub fn canonicalize_module_defs<'a>(
|
||||||
// rules multiple times unnecessarily.
|
// rules multiple times unnecessarily.
|
||||||
crate::operator::desugar_defs(arena, loc_defs);
|
crate::operator::desugar_defs(arena, loc_defs);
|
||||||
|
|
||||||
let mut lookups = Vec::with_capacity(num_deps);
|
|
||||||
let mut rigid_variables = RigidVariables::default();
|
let mut rigid_variables = RigidVariables::default();
|
||||||
|
|
||||||
// Exposed values are treated like defs that appear before any others, e.g.
|
// Exposed values are treated like defs that appear before any others, e.g.
|
||||||
|
|
@ -319,20 +316,13 @@ pub fn canonicalize_module_defs<'a>(
|
||||||
let first_char = ident.as_inline_str().as_str().chars().next().unwrap();
|
let first_char = ident.as_inline_str().as_str().chars().next().unwrap();
|
||||||
|
|
||||||
if first_char.is_lowercase() {
|
if first_char.is_lowercase() {
|
||||||
// this is a value definition
|
|
||||||
let expr_var = var_store.fresh();
|
|
||||||
|
|
||||||
match scope.import(ident, symbol, region) {
|
match scope.import(ident, symbol, region) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
// Add an entry to exposed_imports using the current module's name
|
// Add an entry to exposed_imports using the current module's name
|
||||||
// as the key; e.g. if this is the Foo module and we have
|
// as the key; e.g. if this is the Foo module and we have
|
||||||
// exposes [Bar.{ baz }] then insert Foo.baz as the key, so when
|
// exposes [Bar.{ baz }] then insert Foo.baz as the key, so when
|
||||||
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
|
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
|
||||||
can_exposed_imports.insert(symbol, expr_var);
|
can_exposed_imports.insert(symbol, region);
|
||||||
|
|
||||||
// This will be used during constraint generation,
|
|
||||||
// to add the usual Lookup constraint as if this were a normal def.
|
|
||||||
lookups.push((symbol, expr_var, region));
|
|
||||||
}
|
}
|
||||||
Err((_shadowed_symbol, _region)) => {
|
Err((_shadowed_symbol, _region)) => {
|
||||||
panic!("TODO gracefully handle shadowing in imports.")
|
panic!("TODO gracefully handle shadowing in imports.")
|
||||||
|
|
@ -795,7 +785,6 @@ pub fn canonicalize_module_defs<'a>(
|
||||||
problems: env.problems,
|
problems: env.problems,
|
||||||
symbols_from_requires,
|
symbols_from_requires,
|
||||||
pending_derives,
|
pending_derives,
|
||||||
lookups,
|
|
||||||
loc_expects,
|
loc_expects,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1984,13 +1984,16 @@ fn report_unused_imported_modules<'a>(
|
||||||
constrained_module: &ConstrainedModule,
|
constrained_module: &ConstrainedModule,
|
||||||
) {
|
) {
|
||||||
let mut unused_imported_modules = constrained_module.imported_modules.clone();
|
let mut unused_imported_modules = constrained_module.imported_modules.clone();
|
||||||
|
let mut unused_imports = constrained_module.module.exposed_imports.clone();
|
||||||
|
|
||||||
for symbol in constrained_module.module.referenced_values.iter() {
|
for symbol in constrained_module.module.referenced_values.iter() {
|
||||||
unused_imported_modules.remove(&symbol.module_id());
|
unused_imported_modules.remove(&symbol.module_id());
|
||||||
|
unused_imports.remove(&symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
for symbol in constrained_module.module.referenced_types.iter() {
|
for symbol in constrained_module.module.referenced_types.iter() {
|
||||||
unused_imported_modules.remove(&symbol.module_id());
|
unused_imported_modules.remove(&symbol.module_id());
|
||||||
|
unused_imports.remove(&symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
let existing = match state.module_cache.can_problems.entry(module_id) {
|
let existing = match state.module_cache.can_problems.entry(module_id) {
|
||||||
|
|
@ -2000,9 +2003,15 @@ fn report_unused_imported_modules<'a>(
|
||||||
|
|
||||||
for (unused, region) in unused_imported_modules.drain() {
|
for (unused, region) in unused_imported_modules.drain() {
|
||||||
if !unused.is_builtin() {
|
if !unused.is_builtin() {
|
||||||
existing.push(roc_problem::can::Problem::UnusedImport(unused, region));
|
existing.push(roc_problem::can::Problem::UnusedModuleImport(
|
||||||
|
unused, region,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (unused, region) in unused_imports.drain() {
|
||||||
|
existing.push(roc_problem::can::Problem::UnusedImport(unused, region));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extend_header_with_builtin(header: &mut ModuleHeader, module: ModuleId) {
|
fn extend_header_with_builtin(header: &mut ModuleHeader, module: ModuleId) {
|
||||||
|
|
@ -3539,7 +3548,7 @@ fn send_header<'a>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
|
let mut imported: Vec<(QualifiedModuleName, Vec<Loc<Ident>>, Region)> =
|
||||||
Vec::with_capacity(imports.len());
|
Vec::with_capacity(imports.len());
|
||||||
let mut imported_modules: MutMap<ModuleId, Region> = MutMap::default();
|
let mut imported_modules: MutMap<ModuleId, Region> = MutMap::default();
|
||||||
let mut scope_size = 0;
|
let mut scope_size = 0;
|
||||||
|
|
@ -3611,7 +3620,11 @@ fn send_header<'a>(
|
||||||
// to the same symbols as the ones we're using here.
|
// to the same symbols as the ones we're using here.
|
||||||
let ident_ids = ident_ids_by_module.get_or_insert(module_id);
|
let ident_ids = ident_ids_by_module.get_or_insert(module_id);
|
||||||
|
|
||||||
for ident in exposed_idents {
|
for Loc {
|
||||||
|
region,
|
||||||
|
value: ident,
|
||||||
|
} in exposed_idents
|
||||||
|
{
|
||||||
let ident_id = ident_ids.get_or_insert(ident.as_str());
|
let ident_id = ident_ids.get_or_insert(ident.as_str());
|
||||||
let symbol = Symbol::new(module_id, ident_id);
|
let symbol = Symbol::new(module_id, ident_id);
|
||||||
|
|
||||||
|
|
@ -3750,7 +3763,7 @@ fn send_header_two<'a>(
|
||||||
let declared_name: ModuleName = "".into();
|
let declared_name: ModuleName = "".into();
|
||||||
let mut symbols_from_requires = Vec::with_capacity(requires.len());
|
let mut symbols_from_requires = Vec::with_capacity(requires.len());
|
||||||
|
|
||||||
let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
|
let mut imported: Vec<(QualifiedModuleName, Vec<Loc<Ident>>, Region)> =
|
||||||
Vec::with_capacity(imports.len());
|
Vec::with_capacity(imports.len());
|
||||||
let mut imported_modules: MutMap<ModuleId, Region> = MutMap::default();
|
let mut imported_modules: MutMap<ModuleId, Region> = MutMap::default();
|
||||||
|
|
||||||
|
|
@ -3835,7 +3848,11 @@ fn send_header_two<'a>(
|
||||||
// to the same symbols as the ones we're using here.
|
// to the same symbols as the ones we're using here.
|
||||||
let ident_ids = ident_ids_by_module.get_or_insert(module_id);
|
let ident_ids = ident_ids_by_module.get_or_insert(module_id);
|
||||||
|
|
||||||
for ident in exposed_idents {
|
for Loc {
|
||||||
|
region,
|
||||||
|
value: ident,
|
||||||
|
} in exposed_idents
|
||||||
|
{
|
||||||
let ident_id = ident_ids.get_or_insert(ident.as_str());
|
let ident_id = ident_ids.get_or_insert(ident.as_str());
|
||||||
let symbol = Symbol::new(module_id, ident_id);
|
let symbol = Symbol::new(module_id, ident_id);
|
||||||
|
|
||||||
|
|
@ -4657,7 +4674,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
|
||||||
Ok(Msg::Parsed(parsed))
|
Ok(Msg::Parsed(parsed))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>, Vec<Ident>) {
|
fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>, Vec<Loc<Ident>>) {
|
||||||
use roc_parse::header::ImportsEntry::*;
|
use roc_parse::header::ImportsEntry::*;
|
||||||
|
|
||||||
match entry {
|
match entry {
|
||||||
|
|
@ -4665,7 +4682,7 @@ fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>
|
||||||
let mut exposed = Vec::with_capacity(exposes.len());
|
let mut exposed = Vec::with_capacity(exposes.len());
|
||||||
|
|
||||||
for loc_entry in exposes.iter() {
|
for loc_entry in exposes.iter() {
|
||||||
exposed.push(ident_from_exposed(&loc_entry.value));
|
exposed.push(loc_entry.map(ident_from_exposed));
|
||||||
}
|
}
|
||||||
|
|
||||||
let qualified_module_name = QualifiedModuleName {
|
let qualified_module_name = QualifiedModuleName {
|
||||||
|
|
@ -4680,7 +4697,7 @@ fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>
|
||||||
let mut exposed = Vec::with_capacity(exposes.len());
|
let mut exposed = Vec::with_capacity(exposes.len());
|
||||||
|
|
||||||
for loc_entry in exposes.iter() {
|
for loc_entry in exposes.iter() {
|
||||||
exposed.push(ident_from_exposed(&loc_entry.value));
|
exposed.push(loc_entry.map(ident_from_exposed));
|
||||||
}
|
}
|
||||||
|
|
||||||
let qualified_module_name = QualifiedModuleName {
|
let qualified_module_name = QualifiedModuleName {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ pub enum ShadowKind {
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Problem {
|
pub enum Problem {
|
||||||
UnusedDef(Symbol, Region),
|
UnusedDef(Symbol, Region),
|
||||||
UnusedImport(ModuleId, Region),
|
UnusedImport(Symbol, Region),
|
||||||
|
UnusedModuleImport(ModuleId, Region),
|
||||||
ExposedButNotDefined(Symbol),
|
ExposedButNotDefined(Symbol),
|
||||||
UnknownGeneratesWith(Loc<Ident>),
|
UnknownGeneratesWith(Loc<Ident>),
|
||||||
/// First symbol is the name of the closure with that argument
|
/// First symbol is the name of the closure with that argument
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ pub fn helper(
|
||||||
for problem in can_problems.into_iter() {
|
for problem in can_problems.into_iter() {
|
||||||
// Ignore "unused" problems
|
// Ignore "unused" problems
|
||||||
match problem {
|
match problem {
|
||||||
UnusedDef(_, _) | UnusedArgument(_, _, _, _) | UnusedImport(_, _) => {
|
UnusedDef(_, _) | UnusedArgument(_, _, _, _) | UnusedModuleImport(_, _) => {
|
||||||
delayed_errors.push(problem);
|
delayed_errors.push(problem);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ fn create_llvm_module<'a>(
|
||||||
// Ignore "unused" problems
|
// Ignore "unused" problems
|
||||||
UnusedDef(_, _)
|
UnusedDef(_, _)
|
||||||
| UnusedArgument(_, _, _, _)
|
| UnusedArgument(_, _, _, _)
|
||||||
| UnusedImport(_, _)
|
| UnusedModuleImport(_, _)
|
||||||
| RuntimeError(_)
|
| RuntimeError(_)
|
||||||
| UnsupportedPattern(_, _)
|
| UnsupportedPattern(_, _)
|
||||||
| ExposedButNotDefined(_) => {
|
| ExposedButNotDefined(_) => {
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,24 @@ pub fn can_problem<'b>(
|
||||||
title = UNUSED_DEF.to_string();
|
title = UNUSED_DEF.to_string();
|
||||||
severity = Severity::Warning;
|
severity = Severity::Warning;
|
||||||
}
|
}
|
||||||
Problem::UnusedImport(module_id, region) => {
|
Problem::UnusedImport(symbol, region) => {
|
||||||
|
doc = alloc.stack([
|
||||||
|
alloc.concat([
|
||||||
|
alloc.symbol_qualified(symbol),
|
||||||
|
alloc.reflow(" is not used in this module."),
|
||||||
|
]),
|
||||||
|
alloc.region(lines.convert_region(region)),
|
||||||
|
alloc.concat([
|
||||||
|
alloc.reflow("Since "),
|
||||||
|
alloc.symbol_qualified(symbol),
|
||||||
|
alloc.reflow(" isn't used, you don't need to import it."),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
title = UNUSED_IMPORT.to_string();
|
||||||
|
severity = Severity::Warning;
|
||||||
|
}
|
||||||
|
Problem::UnusedModuleImport(module_id, region) => {
|
||||||
doc = alloc.stack([
|
doc = alloc.stack([
|
||||||
alloc.concat([
|
alloc.concat([
|
||||||
alloc.reflow("Nothing from "),
|
alloc.reflow("Nothing from "),
|
||||||
|
|
|
||||||
|
|
@ -10718,6 +10718,27 @@ All branches in an `if` must have the same type!
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test_report!(
|
||||||
|
unused_value_import,
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app "test" imports [List.{ concat }] provides [main] to "./platform"
|
||||||
|
|
||||||
|
main = ""
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
@r###"
|
||||||
|
── UNUSED IMPORT ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
|
||||||
|
`List.concat` is not used in this module.
|
||||||
|
|
||||||
|
1│ app "test" imports [List.{ concat }] provides [main] to "./platform"
|
||||||
|
^^^^^^
|
||||||
|
|
||||||
|
Since `List.concat` isn't used, you don't need to import it.
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
test_report!(
|
test_report!(
|
||||||
unnecessary_builtin_module_import,
|
unnecessary_builtin_module_import,
|
||||||
indoc!(
|
indoc!(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
app "args"
|
app "args"
|
||||||
packages { pf: "cli-platform/main.roc" }
|
packages { pf: "cli-platform/main.roc" }
|
||||||
imports [pf.Stdout, pf.Arg, pf.Program.{ Program }]
|
imports [pf.Stdout, pf.Arg, pf.Program.{ Program, exitCode }]
|
||||||
provides [main] to pf
|
provides [main] to pf
|
||||||
|
|
||||||
main : Program
|
main : Program
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue