Update unused warnings for inline imports

Now that imports can be limited to smaller scopes than the entire module,
unused import warnings need to work like unused def warnings.

This commit moves unused import warnings discovery and reporting from load
to canonicalization where we can track their usage per scope.

This also fixes a longstanding bug where unused exposed names from an import
were not reported if they were only used in a qualified manner.
This commit is contained in:
Agus Zubiaga 2024-01-11 18:09:01 -03:00
parent 08e6b79dca
commit 7b3317dbb6
No known key found for this signature in database
18 changed files with 334 additions and 122 deletions

View file

@ -35,7 +35,7 @@ import Bool exposing [Bool, Eq]
import Result exposing [Result]
import List
import Str
import Num exposing [Nat, U64, F32, U32, U8, I8]
import Num exposing [Nat, U64, F32, U32, U8]
import Hash exposing [Hasher, Hash]
import Inspect exposing [Inspect, Inspector, InspectFormatter]

View file

@ -23,7 +23,7 @@ interface Hash
hashUnordered,
] imports []
import Bool exposing [Bool, isEq]
import Bool exposing [Bool]
import List
import Str
import Num exposing [

View file

@ -75,7 +75,7 @@ interface List
import Bool exposing [Bool, Eq]
import Result exposing [Result]
import Num exposing [Nat, Num, Int]
import Num exposing [Nat, Num]
## ## Types
##

View file

@ -28,7 +28,7 @@ interface Set
import List
import Bool exposing [Bool, Eq]
import Dict exposing [Dict]
import Dict
import Num exposing [Nat]
import Hash exposing [Hash, Hasher]
import Inspect exposing [Inspect, Inspector, InspectFormatter]

View file

@ -139,7 +139,7 @@ interface Str
]
imports []
import Bool exposing [Bool, Eq]
import Bool exposing [Bool]
import Result exposing [Result]
import List
import Num exposing [Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec]

View file

@ -10,11 +10,11 @@ interface TotallyNotJson
import List
import Str
import Result exposing [Result]
import Encode exposing [Encoder, EncoderFormatting, appendWith]
import Result
import Encode exposing [EncoderFormatting, appendWith]
import Decode exposing [DecoderFormatting, DecodeResult]
import Num exposing [U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Nat, Dec]
import Bool exposing [Bool, Eq]
import Num exposing [U8, U16, U64, F32, F64, Nat, Dec]
import Bool exposing [Bool]
## An opaque type with the `EncoderFormatting` and
## `DecoderFormatting` abilities.

View file

@ -1,5 +1,5 @@
use crate::env::Env;
use crate::procedure::References;
use crate::procedure::{QualifiedReference, References};
use crate::scope::{PendingAbilitiesInScope, Scope};
use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet};
use roc_module::ident::{Ident, Lowercase, TagName};
@ -17,10 +17,43 @@ use roc_types::types::{
pub struct Annotation {
pub typ: Type,
pub introduced_variables: IntroducedVariables,
pub references: VecSet<Symbol>,
pub references: AnnotationReferences,
pub aliases: VecMap<Symbol, Alias>,
}
#[derive(Clone, Debug)]
pub struct AnnotationReferences {
symbols: VecSet<Symbol>,
qualified: Vec<QualifiedReference>,
}
impl AnnotationReferences {
pub fn new() -> Self {
Self {
symbols: Default::default(),
qualified: Default::default(),
}
}
pub fn insert(&mut self, symbol: Symbol, qualified: QualifiedReference) {
if !self.symbols.insert(symbol) {
self.qualified.push(qualified);
}
}
pub fn insert_lookups(&self, references: &mut References) {
for (symbol, qualified) in self.symbols.iter().zip(&self.qualified) {
references.insert_type_lookup(*symbol, *qualified);
}
}
}
impl Default for AnnotationReferences {
fn default() -> Self {
Self::new()
}
}
impl Annotation {
pub fn add_to(
&self,
@ -28,9 +61,7 @@ impl Annotation {
references: &mut References,
introduced_variables: &mut IntroducedVariables,
) {
for symbol in self.references.iter() {
references.insert_type_lookup(*symbol);
}
self.references.insert_lookups(references);
introduced_variables.union(&self.introduced_variables);
@ -291,7 +322,7 @@ pub(crate) fn canonicalize_annotation(
annotation_for: AnnotationFor,
) -> Annotation {
let mut introduced_variables = IntroducedVariables::default();
let mut references = VecSet::default();
let mut references = AnnotationReferences::new();
let mut aliases = VecMap::default();
let (annotation, region) = match annotation {
@ -381,13 +412,17 @@ pub(crate) fn make_apply_symbol(
scope: &mut Scope,
module_name: &str,
ident: &str,
references: &mut AnnotationReferences,
) -> Result<Symbol, Type> {
if module_name.is_empty() {
// Since module_name was empty, this is an unqualified type.
// Look it up in scope!
match scope.lookup_str(ident, region) {
Ok(symbol) => Ok(symbol),
Ok(symbol) => {
references.insert(symbol, QualifiedReference::Unqualified);
Ok(symbol)
}
Err(problem) => {
env.problem(roc_problem::can::Problem::RuntimeError(problem));
@ -396,7 +431,10 @@ pub(crate) fn make_apply_symbol(
}
} else {
match env.qualified_lookup(scope, module_name, ident, region) {
Ok(symbol) => Ok(symbol),
Ok(symbol) => {
references.insert(symbol, QualifiedReference::Qualified);
Ok(symbol)
}
Err(problem) => {
// Either the module wasn't imported, or
// it was imported but it doesn't expose this ident.
@ -537,7 +575,7 @@ fn can_annotation_help(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut AnnotationReferences,
) -> Type {
use roc_parse::ast::TypeAnnotation::*;
@ -580,15 +618,14 @@ fn can_annotation_help(
Type::Function(args, Box::new(closure), Box::new(ret))
}
Apply(module_name, ident, type_arguments) => {
let symbol = match make_apply_symbol(env, region, scope, module_name, ident) {
let symbol = match make_apply_symbol(env, region, scope, module_name, ident, references)
{
Err(problem) => return problem,
Ok(symbol) => symbol,
};
let mut args = Vec::new();
references.insert(symbol);
if scope.abilities_store.is_ability(symbol) {
let fresh_ty_var = find_fresh_var_name(introduced_variables);
@ -744,7 +781,7 @@ fn can_annotation_help(
let mut vars = Vec::with_capacity(loc_vars.len());
let mut lowercase_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(loc_vars.len());
references.insert(symbol);
references.insert(symbol, QualifiedReference::Unqualified);
for loc_var in *loc_vars {
let var = match loc_var.value {
@ -1055,7 +1092,7 @@ fn canonicalize_has_clause(
introduced_variables: &mut IntroducedVariables,
clause: &Loc<roc_parse::ast::ImplementsClause<'_>>,
pending_abilities_in_scope: &PendingAbilitiesInScope,
references: &mut VecSet<Symbol>,
references: &mut AnnotationReferences,
) -> Result<(), Type> {
let Loc {
region,
@ -1078,7 +1115,7 @@ fn canonicalize_has_clause(
{
let ability = match ability {
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
let symbol = make_apply_symbol(env, region, scope, module_name, ident)?;
let symbol = make_apply_symbol(env, region, scope, module_name, ident, references)?;
// Ability defined locally, whose members we are constructing right now...
if !pending_abilities_in_scope.contains_key(&symbol)
@ -1096,7 +1133,6 @@ fn canonicalize_has_clause(
}
};
references.insert(ability);
let already_seen = can_abilities.insert(ability);
if already_seen {
@ -1130,7 +1166,7 @@ fn can_extension_type(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut AnnotationReferences,
opt_ext: &Option<&Loc<TypeAnnotation>>,
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
) -> (Type, ExtImplicitOpenness) {
@ -1333,7 +1369,7 @@ fn can_assigned_fields<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut AnnotationReferences,
) -> SendMap<Lowercase, RecordField<Type>> {
use roc_parse::ast::AssignedField::*;
use roc_types::types::RecordField::*;
@ -1448,7 +1484,7 @@ fn can_assigned_tuple_elems(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut AnnotationReferences,
) -> VecMap<usize, Type> {
let mut elem_types = VecMap::with_capacity(elems.len());
@ -1482,7 +1518,7 @@ fn can_tags<'a>(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut VecMap<Symbol, Alias>,
references: &mut VecSet<Symbol>,
references: &mut AnnotationReferences,
) -> Vec<(TagName, Vec<Type>)> {
let mut tag_types = Vec::with_capacity(tags.len());

View file

@ -6,6 +6,7 @@ use crate::annotation::canonicalize_annotation;
use crate::annotation::find_type_def_symbols;
use crate::annotation::make_apply_symbol;
use crate::annotation::AnnotationFor;
use crate::annotation::AnnotationReferences;
use crate::annotation::IntroducedVariables;
use crate::annotation::OwnedNamedOrAble;
use crate::derive;
@ -358,9 +359,7 @@ fn canonicalize_alias<'a>(
);
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.insert_type_lookup(symbol);
}
can_ann.references.insert_lookups(&mut output.references);
let mut can_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
@ -704,6 +703,8 @@ fn canonicalize_opaque<'a>(
AliasKind::Opaque,
)?;
let mut references = AnnotationReferences::new();
let mut derived_defs = Vec::new();
if let Some(has_abilities) = has_abilities {
let has_abilities = has_abilities.value.collection();
@ -722,7 +723,8 @@ fn canonicalize_opaque<'a>(
// Op := {} has [Eq]
let (ability, members) = match ability.value {
ast::TypeAnnotation::Apply(module_name, ident, []) => {
match make_apply_symbol(env, region, scope, module_name, ident) {
match make_apply_symbol(env, region, scope, module_name, ident, &mut references)
{
Ok(ability) => {
let opt_members = scope
.abilities_store
@ -915,6 +917,8 @@ fn canonicalize_opaque<'a>(
}
}
references.insert_lookups(&mut output.references);
Ok(CanonicalizedOpaque {
opaque_def: alias,
derived_defs,
@ -929,7 +933,12 @@ pub(crate) fn canonicalize_defs<'a>(
scope: &mut Scope,
loc_defs: &'a mut roc_parse::ast::Defs<'a>,
pattern_type: PatternType,
) -> (CanDefs, Output, MutMap<Symbol, Region>) {
) -> (
CanDefs,
Output,
MutMap<Symbol, Region>,
Vec<IntroducedImport>,
) {
// Canonicalizing defs while detecting shadowing involves a multi-step process:
//
// 1. Go through each of the patterns.
@ -979,6 +988,7 @@ pub(crate) fn canonicalize_defs<'a>(
env,
var_store,
value_def,
region,
scope,
&pending_abilities_in_scope,
&mut output,
@ -1035,7 +1045,12 @@ fn canonicalize_value_defs<'a>(
pattern_type: PatternType,
mut aliases: VecMap<Symbol, Alias>,
mut symbols_introduced: MutMap<Symbol, Region>,
) -> (CanDefs, Output, MutMap<Symbol, Region>) {
) -> (
CanDefs,
Output,
MutMap<Symbol, Region>,
Vec<IntroducedImport>,
) {
// Canonicalize all the patterns, record shadowing problems, and store
// the ast::Expr values in pending_exprs for further canonicalization
// once we've finished assembling the entire scope.
@ -1045,6 +1060,8 @@ fn canonicalize_value_defs<'a>(
let mut pending_expect_fx = Vec::with_capacity(value_defs.len());
let mut pending_ingested_files = Vec::with_capacity(value_defs.len());
let mut imports_introduced = Vec::with_capacity(value_defs.len());
for loc_pending_def in value_defs {
match loc_pending_def.value {
PendingValue::Def(pending_def) => {
@ -1064,7 +1081,9 @@ fn canonicalize_value_defs<'a>(
PendingValue::ExpectFx(pending_expect) => {
pending_expect_fx.push(pending_expect);
}
PendingValue::ModuleImport => { /* nothing to do */ }
PendingValue::ModuleImport(introduced_import) => {
imports_introduced.push(introduced_import);
}
PendingValue::IngestedFileImport(pending_ingested_file) => {
pending_ingested_files.push(pending_ingested_file);
}
@ -1181,7 +1200,7 @@ fn canonicalize_value_defs<'a>(
aliases,
};
(can_defs, output, symbols_introduced)
(can_defs, output, symbols_introduced, imports_introduced)
}
struct CanonicalizedTypeDefs<'a> {
@ -1395,9 +1414,9 @@ fn resolve_abilities(
);
// Record all the annotation's references in output.references.lookups
for symbol in member_annot.references {
output.references.insert_type_lookup(symbol);
}
member_annot
.references
.insert_lookups(&mut output.references);
// What variables in the annotation are bound to the parent ability, and what variables
// are bound to some other ability?
@ -2470,7 +2489,7 @@ pub fn can_defs_with_return<'a>(
loc_defs: &'a mut Defs<'a>,
loc_ret: &'a Loc<ast::Expr<'a>>,
) -> (Expr, Output) {
let (unsorted, defs_output, symbols_introduced) = canonicalize_defs(
let (unsorted, defs_output, symbols_introduced, imports_introduced) = canonicalize_defs(
env,
Output::default(),
var_store,
@ -2504,6 +2523,8 @@ pub fn can_defs_with_return<'a>(
}
}
report_unused_imports(imports_introduced, &output.references, env, scope);
let mut loc_expr: Loc<Expr> = ret_expr;
for declaration in declarations.into_iter().rev() {
@ -2513,6 +2534,27 @@ pub fn can_defs_with_return<'a>(
(loc_expr.value, output)
}
pub fn report_unused_imports(
imports_introduced: Vec<IntroducedImport>,
references: &References,
env: &mut Env<'_>,
scope: &mut Scope,
) {
for import in imports_introduced {
if references.has_module_lookup(import.module_id) {
for (symbol, region) in import.exposed_symbols {
if !references.has_unqualified_type_or_value_lookup(symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
{
env.problem(Problem::UnusedImport(symbol, region));
}
}
} else {
env.problem(Problem::UnusedModuleImport(import.module_id, import.region));
}
}
}
fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Loc<Expr> {
match decl {
Declaration::Declare(def) => {
@ -2760,7 +2802,7 @@ enum PendingValue<'a> {
Dbg(PendingExpectOrDbg<'a>),
Expect(PendingExpectOrDbg<'a>),
ExpectFx(PendingExpectOrDbg<'a>),
ModuleImport,
ModuleImport(IntroducedImport),
IngestedFileImport(ast::IngestedFileImport<'a>),
SignatureDefMismatch,
}
@ -2770,10 +2812,18 @@ struct PendingExpectOrDbg<'a> {
preceding_comment: Region,
}
pub struct IntroducedImport {
module_id: ModuleId,
region: Region,
exposed_symbols: Vec<(Symbol, Region)>,
}
#[allow(clippy::too_many_arguments)]
fn to_pending_value_def<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
def: &'a ast::ValueDef<'a>,
region: Region,
scope: &mut Scope,
pending_abilities_in_scope: &PendingAbilitiesInScope,
output: &mut Output,
@ -2896,14 +2946,20 @@ fn to_pending_value_def<'a>(
scope.import_module(module_id);
let mut exposed_symbols;
match module_import.exposed {
None => {}
None => {
exposed_symbols = Vec::new();
}
Some(exposed_kw) => {
let exposed_ids = env
.dep_idents
.get(&module_id)
.expect("Module id should have been added in load");
exposed_symbols = Vec::with_capacity(exposed_kw.item.len());
for loc_name in exposed_kw.item.items {
let exposed_name = loc_name.value.item();
let name = exposed_name.as_str();
@ -2912,6 +2968,7 @@ fn to_pending_value_def<'a>(
match exposed_ids.get_id(name) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, ident_id);
exposed_symbols.push((symbol, loc_name.region));
match scope.import_symbol(ident, symbol, loc_name.region) {
Ok(()) => {}
@ -2943,7 +3000,11 @@ fn to_pending_value_def<'a>(
}
}
PendingValue::ModuleImport
PendingValue::ModuleImport(IntroducedImport {
module_id,
region,
exposed_symbols,
})
}
IngestedFileImport(module_import) => PendingValue::IngestedFileImport(*module_import),
}

View file

@ -8,7 +8,7 @@ use crate::num::{
int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumBound,
};
use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern, PermitShadows};
use crate::procedure::References;
use crate::procedure::{QualifiedReference, References};
use crate::scope::Scope;
use crate::traverse::{walk_expr, Visitor};
use roc_collections::soa::Index;
@ -882,7 +882,9 @@ pub fn canonicalize_expr<'a>(
}
Ok((name, opaque_def)) => {
let argument = Box::new(args.pop().unwrap());
output.references.insert_type_lookup(name);
output
.references
.insert_type_lookup(name, QualifiedReference::Unqualified);
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
@ -1193,7 +1195,9 @@ pub fn canonicalize_expr<'a>(
}
Ok((name, opaque_def)) => {
let mut output = Output::default();
output.references.insert_type_lookup(name);
output
.references
.insert_type_lookup(name, QualifiedReference::Unqualified);
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
@ -1877,7 +1881,9 @@ fn canonicalize_var_lookup(
// Look it up in scope!
match scope.lookup_str(ident, region) {
Ok(symbol) => {
output.references.insert_value_lookup(symbol);
output
.references
.insert_value_lookup(symbol, QualifiedReference::Unqualified);
if scope.abilities_store.is_ability_member_name(symbol) {
AbilityMember(
@ -1900,7 +1906,9 @@ fn canonicalize_var_lookup(
// Look it up in the env!
match env.qualified_lookup(scope, module_name, ident, region) {
Ok(symbol) => {
output.references.insert_value_lookup(symbol);
output
.references
.insert_value_lookup(symbol, QualifiedReference::Qualified);
if scope.abilities_store.is_ability_member_name(symbol) {
AbilityMember(

View file

@ -1,6 +1,6 @@
use crate::abilities::{AbilitiesStore, ImplKey, PendingAbilitiesStore, ResolvedImpl};
use crate::annotation::{canonicalize_annotation, AnnotationFor};
use crate::def::{canonicalize_defs, Def};
use crate::annotation::{canonicalize_annotation, AnnotationFor, AnnotationReferences};
use crate::def::{canonicalize_defs, report_unused_imports, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env;
use crate::expr::{
@ -127,7 +127,6 @@ pub struct Module {
pub exposed_imports: MutMap<Symbol, Region>,
pub exposed_symbols: VecSet<Symbol>,
pub referenced_values: VecSet<Symbol>,
pub referenced_types: VecSet<Symbol>,
/// all aliases. `bool` indicates whether it is exposed
pub aliases: MutMap<Symbol, (bool, Alias)>,
pub rigid_variables: RigidVariables,
@ -152,7 +151,6 @@ pub struct ModuleOutput {
pub exposed_symbols: VecSet<Symbol>,
pub problems: Vec<Problem>,
pub referenced_values: VecSet<Symbol>,
pub referenced_types: VecSet<Symbol>,
pub symbols_from_requires: Vec<(Loc<Symbol>, Loc<Type>)>,
pub pending_derives: PendingDerives,
pub scope: Scope,
@ -363,7 +361,7 @@ pub fn canonicalize_module_defs<'a>(
}
}
let (defs, output, symbols_introduced) = canonicalize_defs(
let (defs, output, symbols_introduced, imports_introduced) = canonicalize_defs(
&mut env,
Output::default(),
var_store,
@ -389,6 +387,8 @@ pub fn canonicalize_module_defs<'a>(
}
}
report_unused_imports(imports_introduced, &output.references, &mut env, &mut scope);
for named in output.introduced_variables.named {
rigid_variables.named.insert(named.variable, named.name);
}
@ -404,18 +404,15 @@ pub fn canonicalize_module_defs<'a>(
}
let mut referenced_values = VecSet::default();
let mut referenced_types = VecSet::default();
// Gather up all the symbols that were referenced across all the defs' lookups.
referenced_values.extend(output.references.value_lookups().copied());
referenced_types.extend(output.references.type_lookups().copied());
// Gather up all the symbols that were referenced across all the defs' calls.
referenced_values.extend(output.references.calls().copied());
// Gather up all the symbols that were referenced from other modules.
referenced_values.extend(env.qualified_value_lookups.iter().copied());
referenced_types.extend(env.qualified_type_lookups.iter().copied());
// NOTE previously we inserted builtin defs into the list of defs here
// this is now done later, in file.rs.
@ -539,7 +536,7 @@ pub fn canonicalize_module_defs<'a>(
let annotation = crate::annotation::Annotation {
typ: def_annotation.signature,
introduced_variables: def_annotation.introduced_variables,
references: Default::default(),
references: AnnotationReferences::new(),
aliases: Default::default(),
};
@ -597,7 +594,7 @@ pub fn canonicalize_module_defs<'a>(
let annotation = crate::annotation::Annotation {
typ: def_annotation.signature,
introduced_variables: def_annotation.introduced_variables,
references: Default::default(),
references: AnnotationReferences::new(),
aliases: Default::default(),
};
@ -694,14 +691,12 @@ pub fn canonicalize_module_defs<'a>(
// Incorporate any remaining output.lookups entries into references.
referenced_values.extend(output.references.value_lookups().copied());
referenced_types.extend(output.references.type_lookups().copied());
// Incorporate any remaining output.calls entries into references.
referenced_values.extend(output.references.calls().copied());
// Gather up all the symbols that were referenced from other modules.
referenced_values.extend(env.qualified_value_lookups.iter().copied());
referenced_types.extend(env.qualified_type_lookups.iter().copied());
let mut fix_closures_no_capture_symbols = VecSet::default();
let mut fix_closures_closure_captures = VecMap::default();
@ -797,7 +792,6 @@ pub fn canonicalize_module_defs<'a>(
rigid_variables,
declarations,
referenced_values,
referenced_types,
exposed_imports: can_exposed_imports,
problems: env.problems,
symbols_from_requires,

View file

@ -446,7 +446,10 @@ pub fn canonicalize_pattern<'a>(
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
output.references.insert_type_lookup(opaque);
output.references.insert_type_lookup(
opaque,
crate::procedure::QualifiedReference::Unqualified,
);
Pattern::UnwrappedOpaque {
whole_var: var_store.fresh(),

View file

@ -1,6 +1,6 @@
use crate::expr::Expr;
use crate::pattern::Pattern;
use roc_module::symbol::Symbol;
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
@ -46,6 +46,22 @@ impl ReferencesBitflags {
const TYPE_LOOKUP: Self = ReferencesBitflags(2);
const CALL: Self = ReferencesBitflags(4);
const BOUND: Self = ReferencesBitflags(8);
const QUALIFIED: Self = ReferencesBitflags(16);
}
#[derive(Copy, Clone, Debug)]
pub enum QualifiedReference {
Unqualified,
Qualified,
}
impl QualifiedReference {
fn flags(&self, flags: ReferencesBitflags) -> ReferencesBitflags {
match self {
Self::Unqualified => flags,
Self::Qualified => ReferencesBitflags(flags.0 | ReferencesBitflags::QUALIFIED.0),
}
}
}
#[derive(Clone, Debug, Default)]
@ -108,12 +124,12 @@ impl References {
}
}
pub fn insert_value_lookup(&mut self, symbol: Symbol) {
self.insert(symbol, ReferencesBitflags::VALUE_LOOKUP);
pub fn insert_value_lookup(&mut self, symbol: Symbol, qualified: QualifiedReference) {
self.insert(symbol, qualified.flags(ReferencesBitflags::VALUE_LOOKUP));
}
pub fn insert_type_lookup(&mut self, symbol: Symbol) {
self.insert(symbol, ReferencesBitflags::TYPE_LOOKUP);
pub fn insert_type_lookup(&mut self, symbol: Symbol, qualified: QualifiedReference) {
self.insert(symbol, qualified.flags(ReferencesBitflags::TYPE_LOOKUP));
}
pub fn insert_bound(&mut self, symbol: Symbol) {
@ -178,7 +194,24 @@ impl References {
false
}
pub fn has_unqualified_type_or_value_lookup(&self, symbol: Symbol) -> bool {
let mask = ReferencesBitflags::VALUE_LOOKUP.0 | ReferencesBitflags::TYPE_LOOKUP.0;
let it = self.symbols.iter().zip(self.bitflags.iter());
for (a, b) in it {
if *a == symbol && b.0 & mask > 0 && b.0 & ReferencesBitflags::QUALIFIED.0 == 0 {
return true;
}
}
false
}
pub fn references_type_def(&self, symbol: Symbol) -> bool {
self.has_type_lookup(symbol)
}
pub fn has_module_lookup(&self, module_id: ModuleId) -> bool {
self.symbols.iter().any(|sym| sym.module_id() == module_id)
}
}

View file

@ -2142,44 +2142,6 @@ macro_rules! debug_check_ir {
};
}
/// Report modules that are imported, but from which nothing is used
fn report_unused_imported_modules(
state: &mut State<'_>,
module_id: ModuleId,
constrained_module: &ConstrainedModule,
) {
// [modules-revamp] TODO: take expr-level into account
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() {
unused_imported_modules.remove(&symbol.module_id());
unused_imports.remove(symbol);
}
for symbol in constrained_module.module.referenced_types.iter() {
unused_imported_modules.remove(&symbol.module_id());
unused_imports.remove(symbol);
}
let existing = match state.module_cache.can_problems.entry(module_id) {
Vacant(entry) => entry.insert(std::vec::Vec::new()),
Occupied(entry) => entry.into_mut(),
};
for (unused, region) in unused_imported_modules.drain() {
if !unused.is_builtin() {
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_module_with_builtin_import(module: &mut ParsedModule, module_id: ModuleId) {
module
.package_qualified_imported_modules
@ -2514,8 +2476,6 @@ fn update<'a>(
state.module_cache.documentation.insert(module_id, docs);
}
report_unused_imported_modules(&mut state, module_id, &constrained_module);
state
.module_cache
.aliases
@ -5145,7 +5105,6 @@ fn canonicalize_and_constrain<'a>(
exposed_imports: module_output.exposed_imports,
exposed_symbols: module_output.exposed_symbols,
referenced_values: module_output.referenced_values,
referenced_types: module_output.referenced_types,
aliases,
rigid_variables: module_output.rigid_variables,
abilities_store: module_output.scope.abilities_store,

View file

@ -3,7 +3,7 @@ interface Primary
imports []
import Dep1
import Dep2 exposing [two]
import Dep2
import Dep3Blah exposing [bar]
import Res

View file

@ -3,7 +3,7 @@ interface Primary
imports []
import Dep1
import Dep2 exposing [two]
import Dep2
import Dep3 exposing [bar]
import Res

View file

@ -3,7 +3,7 @@ interface WithBuiltins
imports []
import Dep1
import Dep2 exposing [two]
import Dep2
floatTest = Num.maxF64

View file

@ -217,10 +217,21 @@ fn load_fixture(
let home = loaded_module.module_id;
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
let (filepath, src) = loaded_module.sources.get(&home).unwrap();
let can_problems = loaded_module.can_problems.remove(&home).unwrap_or_default();
if !can_problems.is_empty() {
panic!(
"{}",
format_can_problems(
can_problems,
home,
&loaded_module.interns,
filepath.clone(),
src,
)
);
}
assert!(loaded_module
.type_problems
.remove(&home)
@ -453,7 +464,7 @@ fn import_inside_def() {
}
#[test]
#[should_panic(expected = "LookupNotInScope")]
#[should_panic(expected = "UNRECOGNIZED NAME")]
fn exposed_used_outside_scope() {
let subs_by_module = Default::default();
load_fixture(
@ -464,7 +475,7 @@ fn exposed_used_outside_scope() {
}
#[test]
#[should_panic(expected = "ModuleNotImported")]
#[should_panic(expected = "MODULE NOT IMPORTED")]
fn import_used_outside_scope() {
let subs_by_module = Default::default();
load_fixture(
@ -915,9 +926,9 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
UNUSED IMPORT tmp/opaque_wrapped_unwrapped_outside_defining_module/Main
UNUSED IMPORT in tmp/opaque_wrapped_unwrapped_outside_defining_module/Main
Nothing from Age is used in this module.
Age is imported but not used.
3 import Age exposing [Age]
^^^^^^^^^^^^^^^^^^^^^^^^^
@ -930,6 +941,114 @@ fn opaque_wrapped_unwrapped_outside_defining_module() {
);
}
#[test]
fn unused_imports() {
let modules = vec![
(
"Dep1",
indoc!(
r#"
interface Dep1 exposes [one] imports []
one = 1
"#
),
),
(
"Dep2",
indoc!(
r#"
interface Dep2 exposes [two] imports []
two = 2
"#
),
),
(
"Dep3",
indoc!(
r#"
interface Dep3 exposes [Three, three] imports []
Three : [Three]
three = Three
"#
),
),
(
"Main",
indoc!(
r#"
interface Main exposes [usedModule, unusedModule, unusedExposed, usingThreeValue] imports []
import Dep1
import Dep3 exposing [Three]
usedModule =
import Dep2
Dep2.two
unusedModule =
import Dep2
2
unusedExposed =
import Dep2 exposing [two]
2
usingThreeValue =
Dep3.three
"#
),
),
];
let err = multiple_modules("unused_imports", modules).unwrap_err();
assert_eq!(
err,
indoc!(
r"
UNUSED IMPORT in tmp/unused_imports/Main
Dep2 is imported but not used.
11 import Dep2
^^^^^^^^^^^
Since Dep2 isn't used, you don't need to import it.
UNUSED IMPORT in tmp/unused_imports/Main
Dep2 is imported but not used.
15 import Dep2 exposing [two]
^^^^^^^^^^^^^^^^^^^^^^^^^^
Since Dep2 isn't used, you don't need to import it.
UNUSED IMPORT in tmp/unused_imports/Main
Dep1 is imported but not used.
3 import Dep1
^^^^^^^^^^^
Since Dep1 isn't used, you don't need to import it.
UNUSED IMPORT in tmp/unused_imports/Main
`Dep3.Three` is not used in this module.
4 import Dep3 exposing [Three]
^^^^^
Since `Dep3.Three` isn't used, you don't need to import it.
"
),
"\n{}",
err
)
}
#[test]
fn issue_2863_module_type_does_not_exist() {
let modules = vec![

View file

@ -107,9 +107,8 @@ pub fn can_problem<'b>(
Problem::UnusedModuleImport(module_id, region) => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("Nothing from "),
alloc.module(module_id),
alloc.reflow(" is used in this module."),
alloc.reflow(" is imported but not used."),
]),
alloc.region(lines.convert_region(region)),
alloc.concat([