Skip to content

Commit 51104fd

Browse files
authored
[move-compiler] Added unused constants warning (#13422)
## Description Added a warning about unused constants. ## Test Plan Added appropriate tests. --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [ ] protocol change - [x] user-visible impact - [ ] breaking change for a client SDKs - [ ] breaking change for FNs (FN binary must upgrade) - [ ] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes When building Move code, new compiler warnings that point to unused constants may appear.
1 parent 937b652 commit 51104fd

File tree

15 files changed

+123
-31
lines changed

15 files changed

+123
-31
lines changed

move-compiler/src/diagnostics/codes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ codes!(
289289
Function: { msg: "unused function", severity: Warning },
290290
StructField: { msg: "unused struct field", severity: Warning },
291291
FunTypeParam: { msg: "unused function type parameter", severity: Warning },
292+
Constant: { msg: "unused constant", severity: Warning },
292293
],
293294
Attributes: [
294295
Duplicate: { msg: "invalid duplicate attribute", severity: NonblockingError },

move-compiler/src/diagnostics/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use crate::{
1111
WellKnownFilterName,
1212
},
1313
shared::{
14-
ast_debug::AstDebug, FILTER_UNUSED_FUNCTION, FILTER_UNUSED_STRUCT_FIELD,
15-
FILTER_UNUSED_TYPE_PARAMETER,
14+
ast_debug::AstDebug, FILTER_UNUSED_CONST, FILTER_UNUSED_FUNCTION,
15+
FILTER_UNUSED_STRUCT_FIELD, FILTER_UNUSED_TYPE_PARAMETER,
1616
},
1717
};
1818
use codespan_reporting::{
@@ -507,6 +507,7 @@ impl UnprefixedWarningFilters {
507507
let unused_fun_info = UnusedItem::Function.into_info();
508508
let unused_field_info = UnusedItem::StructField.into_info();
509509
let unused_fn_tparam_info = UnusedItem::FunTypeParam.into_info();
510+
let unused_const_info = UnusedItem::Constant.into_info();
510511
let filtered_codes = BTreeMap::from([
511512
(
512513
(unused_fun_info.category(), unused_fun_info.code()),
@@ -523,6 +524,10 @@ impl UnprefixedWarningFilters {
523524
),
524525
Some(FILTER_UNUSED_TYPE_PARAMETER),
525526
),
527+
(
528+
(unused_const_info.category(), unused_const_info.code()),
529+
Some(FILTER_UNUSED_CONST),
530+
),
526531
]);
527532
Self::Specified {
528533
categories: BTreeMap::new(),

move-compiler/src/shared/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ pub const FILTER_UNUSED_ATTRIBUTE: &str = "unused_attribute";
151151
pub const FILTER_UNUSED_TYPE_PARAMETER: &str = "unused_type_parameter";
152152
pub const FILTER_UNUSED_FUNCTION: &str = "unused_function";
153153
pub const FILTER_UNUSED_STRUCT_FIELD: &str = "unused_field";
154+
pub const FILTER_UNUSED_CONST: &str = "unused_const";
154155
pub const FILTER_DEAD_CODE: &str = "dead_code";
155156

156157
pub type NamedAddressMap = BTreeMap<Symbol, NumericalAddress>;
@@ -326,6 +327,7 @@ impl CompilationEnv {
326327
},
327328
]),
328329
),
330+
known_code_filter!(FILTER_UNUSED_CONST, UnusedItem::Constant, filter_attr_name),
329331
known_code_filter!(FILTER_DEAD_CODE, UnusedItem::DeadCode, filter_attr_name),
330332
]);
331333

move-compiler/src/typing/core.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use crate::{
66
diag,
77
diagnostics::{codes::NameResolution, Diagnostic},
8-
expansion::ast::{AbilitySet, ModuleIdent, Visibility},
8+
expansion::ast::{AbilitySet, ModuleIdent, ModuleIdent_, Visibility},
99
naming::ast::{
1010
self as N, BuiltinTypeName_, FunctionSignature, StructDefinition, StructTypeParameter,
1111
TParam, TParamID, TVar, Type, TypeName, TypeName_, Type_, Var,
@@ -88,14 +88,14 @@ pub struct Context<'env> {
8888

8989
loop_info: LoopInfo,
9090

91-
/// collects all called functions in the current module
92-
pub called_fns: BTreeSet<Symbol>,
93-
9491
/// collects all friends that should be added over the course of 'public(package)' calls
9592
/// structured as (defining module, new friend, location) where `new friend` is usually the
9693
/// context's current module. Note there may be more than one location in practice, but
9794
/// tracking a single one is sufficient for error reporting.
9895
pub new_friends: BTreeSet<(ModuleIdent, Loc)>,
96+
/// collects all used module members (functions and constants) but it's a superset of these in
97+
/// that it may contain other identifiers that do not in fact represent a function or a constant
98+
pub used_module_members: BTreeMap<ModuleIdent_, BTreeSet<Symbol>>,
9999
}
100100

101101
macro_rules! program_info {
@@ -218,8 +218,8 @@ impl<'env> Context<'env> {
218218
loop_info: LoopInfo(LoopInfo_::NotInLoop),
219219
modules,
220220
env,
221-
called_fns: BTreeSet::new(),
222221
new_friends: BTreeSet::new(),
222+
used_module_members: BTreeMap::new(),
223223
}
224224
}
225225

move-compiler/src/typing/translate.rs

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ use crate::{
1010
diag,
1111
diagnostics::{codes::*, Diagnostic},
1212
editions::Flavor,
13-
expansion::ast::{AttributeName_, Fields, Friend, ModuleIdent, Value_, Visibility},
13+
expansion::ast::{
14+
AttributeName_, AttributeValue_, Attribute_, Attributes, Fields, Friend, ModuleAccess_,
15+
ModuleIdent, ModuleIdent_, Value_, Visibility,
16+
},
1417
naming::ast::{self as N, TParam, TParamID, Type, TypeName_, Type_},
1518
parser::ast::{Ability_, BinOp_, ConstantName, Field, FunctionName, StructName, UnaryOp_},
1619
shared::{
@@ -86,6 +89,10 @@ fn modules(
8689
.expect("ICE compiler added duplicate friends to public(package) friend list");
8790
}
8891

92+
for (_, mident, mdef) in &typed_modules {
93+
gen_unused_warnings(context, mident, mdef);
94+
}
95+
8996
typed_modules
9097
}
9198

@@ -95,8 +102,6 @@ fn module(
95102
mdef: N::ModuleDefinition,
96103
) -> (T::ModuleDefinition, BTreeSet<(ModuleIdent, Loc)>) {
97104
assert!(context.current_script_constants.is_none());
98-
assert!(context.called_fns.is_empty());
99-
assert!(context.new_friends.is_empty());
100105

101106
context.current_module = Some(ident);
102107
let N::ModuleDefinition {
@@ -114,6 +119,7 @@ fn module(
114119
structs
115120
.iter_mut()
116121
.for_each(|(_, _, s)| struct_def(context, s));
122+
process_attributes(context, &attributes);
117123
let constants = nconstants.map(|name, c| constant(context, name, c));
118124
let functions = nfunctions.map(|name, f| function(context, name, f, false));
119125
assert!(context.constraints.is_empty());
@@ -129,12 +135,8 @@ fn module(
129135
constants,
130136
functions,
131137
};
132-
gen_unused_warnings(context, &typed_module);
133138
// get the list of new friends and reset the list.
134139
let new_friends = std::mem::take(&mut context.new_friends);
135-
// reset called functions set so that it's ready to be be populated with values from
136-
// a single module only
137-
context.called_fns.clear();
138140
(typed_module, new_friends)
139141
}
140142

@@ -202,6 +204,7 @@ fn function(
202204
assert!(context.constraints.is_empty());
203205
context.reset_for_module_item();
204206
context.current_function = Some(name);
207+
process_attributes(context, &attributes);
205208
function_signature(context, &signature);
206209
if is_script {
207210
let mk_msg = || {
@@ -301,6 +304,8 @@ fn constant(context: &mut Context, _name: ConstantName, nconstant: N::Constant)
301304
} = nconstant;
302305
context.env.add_warning_filter_scope(warning_filter.clone());
303306

307+
process_attributes(context, &attributes);
308+
304309
// Don't need to add base type constraint, as it is checked in `check_valid_constant::signature`
305310
let mut signature = core::instantiate(context, signature);
306311
check_valid_constant::signature(
@@ -1241,6 +1246,13 @@ fn exp_inner(context: &mut Context, sp!(eloc, ne_): N::Exp) -> T::Exp {
12411246

12421247
NE::Constant(m, c) => {
12431248
let ty = core::make_constant_type(context, eloc, &m, &c);
1249+
if let Some(mident) = m {
1250+
context
1251+
.used_module_members
1252+
.entry(mident.value)
1253+
.or_insert_with(BTreeSet::new)
1254+
.insert(c.value());
1255+
}
12441256
(ty, TE::Constant(m, c))
12451257
}
12461258

@@ -2102,9 +2114,11 @@ fn module_call(
21022114
parameter_types: params_ty_list,
21032115
acquires,
21042116
};
2105-
if context.is_current_module(&m) {
2106-
context.called_fns.insert(f.value());
2107-
}
2117+
context
2118+
.used_module_members
2119+
.entry(m.value)
2120+
.or_insert_with(BTreeSet::new)
2121+
.insert(f.value());
21082122
(ret_ty, T::UnannotatedExp_::ModuleCall(Box::new(call)))
21092123
}
21102124

@@ -2318,12 +2332,38 @@ fn make_arg_types<S: std::fmt::Display, F: Fn() -> S>(
23182332
given
23192333
}
23202334

2335+
//**************************************************************************************************
2336+
// Utils
2337+
//**************************************************************************************************
2338+
2339+
fn process_attributes(context: &mut Context, all_attributes: &Attributes) {
2340+
for (_, _, attr) in all_attributes {
2341+
match &attr.value {
2342+
Attribute_::Name(_) => (),
2343+
Attribute_::Parameterized(_, attrs) => process_attributes(context, attrs),
2344+
Attribute_::Assigned(_, val) => {
2345+
let AttributeValue_::ModuleAccess(mod_access) = &val.value else {
2346+
continue;
2347+
};
2348+
if let ModuleAccess_::ModuleAccess(mident, name) = mod_access.value {
2349+
// conservatively assume that each `ModuleAccess` refers to a constant name
2350+
context
2351+
.used_module_members
2352+
.entry(mident.value)
2353+
.or_insert_with(BTreeSet::new)
2354+
.insert(name.value);
2355+
}
2356+
}
2357+
}
2358+
}
2359+
}
2360+
23212361
//**************************************************************************************************
23222362
// Module-wide warnings
23232363
//**************************************************************************************************
23242364

2325-
/// Generates warnings for unused (private) functions.
2326-
fn gen_unused_warnings(context: &mut Context, mdef: &T::ModuleDefinition) {
2365+
/// Generates warnings for unused (private) functions and unused constants.
2366+
fn gen_unused_warnings(context: &mut Context, mident: &ModuleIdent_, mdef: &T::ModuleDefinition) {
23272367
if !mdef.is_source_module {
23282368
// generate warnings only for modules compiled in this pass rather than for all modules
23292369
// including pre-compiled libraries for which we do not have source code available and
@@ -2336,6 +2376,22 @@ fn gen_unused_warnings(context: &mut Context, mdef: &T::ModuleDefinition) {
23362376
.env
23372377
.add_warning_filter_scope(mdef.warning_filter.clone());
23382378

2379+
for (loc, name, c) in &mdef.constants {
2380+
context
2381+
.env
2382+
.add_warning_filter_scope(c.warning_filter.clone());
2383+
2384+
let members = context.used_module_members.get(mident);
2385+
if members.is_none() || !members.unwrap().contains(name) {
2386+
let msg = format!("The constant '{name}' is never used. Consider removing it.");
2387+
context
2388+
.env
2389+
.add_diag(diag!(UnusedItem::Constant, (loc, msg)))
2390+
}
2391+
2392+
context.env.pop_warning_filter_scope();
2393+
}
2394+
23392395
for (loc, name, fun) in &mdef.functions {
23402396
if fun.attributes.iter().any(|(_, n, _)| {
23412397
n == &AttributeName_::Known(KnownAttribute::Testing(TestingAttribute::Test))
@@ -2350,9 +2406,11 @@ fn gen_unused_warnings(context: &mut Context, mdef: &T::ModuleDefinition) {
23502406
context
23512407
.env
23522408
.add_warning_filter_scope(fun.warning_filter.clone());
2353-
if !context.called_fns.contains(name)
2354-
&& fun.entry.is_none()
2409+
2410+
let members = context.used_module_members.get(mident);
2411+
if fun.entry.is_none()
23552412
&& matches!(fun.visibility, Visibility::Internal)
2413+
&& (members.is_none() || !members.unwrap().contains(name))
23562414
{
23572415
// TODO: postponing handling of friend functions until we decide what to do with them
23582416
// vis-a-vis ideas around package-private
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module 0x42::unused_consts {
2+
const UNUSED: u64 = 42;
3+
}

move-compiler/tests/move_check/typing/unused_const.unused

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
warning[W09011]: unused constant
2+
┌─ tests/move_check/typing/unused_const.move:2:11
3+
4+
2 │ const UNUSED: u64 = 42;
5+
│ ^^^^^^ The constant 'UNUSED' is never used. Consider removing it.
6+
7+
= This warning can be suppressed with '#[allow(unused_const)]' applied to the 'module' or module member ('const', 'fun', or 'struct')
8+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module 0x42::used_consts {
2+
3+
// at this point consts can only be used in functions and annotations (and not, for example, to
4+
// define other constants) so we can only test for that
5+
6+
const USED_IN_FUN: u64 = 42;
7+
const USED_IN_ANNOTATION: u64 = 42;
8+
9+
public fun foo(): u64 {
10+
USED_IN_FUN
11+
}
12+
13+
#[test(p = @42)]
14+
#[expected_failure(abort_code = USED_IN_ANNOTATION)]
15+
public fun baz(p: u64) {
16+
assert!(p > 7, 42);
17+
}
18+
19+
20+
}

move-compiler/tests/move_check/typing/used_const.unused

Whitespace-only changes.

0 commit comments

Comments
 (0)