mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
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:
parent
08e6b79dca
commit
7b3317dbb6
18 changed files with 334 additions and 122 deletions
|
@ -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]
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ interface Hash
|
|||
hashUnordered,
|
||||
] imports []
|
||||
|
||||
import Bool exposing [Bool, isEq]
|
||||
import Bool exposing [Bool]
|
||||
import List
|
||||
import Str
|
||||
import Num exposing [
|
||||
|
|
|
@ -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
|
||||
##
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -3,7 +3,7 @@ interface Primary
|
|||
imports []
|
||||
|
||||
import Dep1
|
||||
import Dep2 exposing [two]
|
||||
import Dep2
|
||||
import Dep3Blah exposing [bar]
|
||||
import Res
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ interface Primary
|
|||
imports []
|
||||
|
||||
import Dep1
|
||||
import Dep2 exposing [two]
|
||||
import Dep2
|
||||
import Dep3 exposing [bar]
|
||||
import Res
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ interface WithBuiltins
|
|||
imports []
|
||||
|
||||
import Dep1
|
||||
import Dep2 exposing [two]
|
||||
import Dep2
|
||||
|
||||
floatTest = Num.maxF64
|
||||
|
||||
|
|
|
@ -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![
|
||||
|
|
|
@ -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([
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue