Skip to content

Commit 6524eea

Browse files
committed
Warnings for unused components
1 parent e540ae6 commit 6524eea

20 files changed

+643
-30
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
mod toplevel;
2+
mod unused_elements;
23

34
pub use toplevel::TopLevel;
5+
pub use unused_elements::UnusedElementsCheck;
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
use crate::ast_visitor::{Action, Visitor};
2+
use crate::cmdline;
3+
use fil_ast as ast;
4+
use fil_utils::{Diagnostics, Error, GPosIdx};
5+
use std::collections::{HashMap, HashSet};
6+
7+
/// Structure to track usage information for a single component
8+
#[derive(Default, Clone)]
9+
struct ComponentUsage {
10+
// Definitions with their locations
11+
instances: HashMap<String, ast::Loc<fil_utils::Id>>,
12+
invocations: HashMap<String, ast::Loc<fil_utils::Id>>,
13+
let_params: HashMap<String, ast::Loc<fil_utils::Id>>,
14+
sig_params: HashMap<String, ast::Loc<fil_utils::Id>>,
15+
16+
// Usage tracking
17+
invoked_instances: HashSet<String>,
18+
accessed_invocations: HashSet<String>,
19+
referenced_params: HashSet<String>,
20+
}
21+
22+
23+
/// Check for unused elements in AST (instances, invocations, parameters)
24+
pub struct UnusedElementsCheck {
25+
diag: Diagnostics,
26+
current_component: Option<String>,
27+
component_usage: HashMap<String, ComponentUsage>,
28+
warnings_as_errors: bool,
29+
}
30+
31+
impl crate::ast_visitor::Construct for UnusedElementsCheck {
32+
fn from(opts: &cmdline::Opts, _ast: &mut ast::Namespace) -> Self {
33+
UnusedElementsCheck {
34+
diag: Diagnostics::default(),
35+
current_component: None,
36+
component_usage: HashMap::new(),
37+
warnings_as_errors: opts.warnings_as_errors,
38+
}
39+
}
40+
41+
fn clear_data(&mut self) {
42+
// Don't clear component_usage since we need it across components
43+
self.current_component = None;
44+
}
45+
}
46+
47+
impl UnusedElementsCheck {
48+
fn current_usage(&mut self) -> &mut ComponentUsage {
49+
let comp_name = self.current_component.clone().expect("No current component");
50+
self.component_usage.entry(comp_name).or_default()
51+
}
52+
53+
fn extract_invocation_name(&self, port: &ast::Port) -> Option<String> {
54+
match &port.base {
55+
ast::PortRef::Instance { instance, .. } => {
56+
Some(instance.inner().as_ref().to_string())
57+
}
58+
_ => None,
59+
}
60+
}
61+
62+
fn visit_expr_for_params(&mut self, expr: &ast::Expr) {
63+
match expr {
64+
ast::Expr::Abstract(name) => {
65+
let usage = self.current_usage();
66+
usage.referenced_params.insert(name.inner().as_ref().to_string());
67+
}
68+
ast::Expr::ParamAccess { inst: _, param } => {
69+
let usage = self.current_usage();
70+
usage.referenced_params.insert(param.inner().as_ref().to_string());
71+
}
72+
ast::Expr::Op { left, right, .. } => {
73+
self.visit_expr_for_params(left);
74+
self.visit_expr_for_params(right);
75+
}
76+
ast::Expr::If { cond, then, alt } => {
77+
self.visit_order_constraint_for_params(cond);
78+
self.visit_expr_for_params(then);
79+
self.visit_expr_for_params(alt);
80+
}
81+
ast::Expr::App { args, .. } => {
82+
for arg in args {
83+
self.visit_expr_for_params(arg);
84+
}
85+
}
86+
// Base cases: concrete values don't reference parameters
87+
ast::Expr::Concrete(_) => {}
88+
}
89+
}
90+
91+
fn visit_order_constraint_for_params(&mut self, constraint: &ast::OrderConstraint<Box<ast::Expr>>) {
92+
self.visit_expr_for_params(&constraint.left);
93+
self.visit_expr_for_params(&constraint.right);
94+
}
95+
96+
fn visit_port_for_params(&mut self, port: &ast::Port) {
97+
// Visit access expressions in bundle accesses
98+
for access in &port.access {
99+
self.visit_expr_for_params(&access.start);
100+
self.visit_expr_for_params(&access.end);
101+
}
102+
}
103+
104+
fn add_unused_warning(&mut self, element_type: &str, description: &str, loc: GPosIdx) {
105+
let mut warning = Error::unused_element(element_type, description)
106+
.add_note(self.diag.add_info(format!("{} defined here", element_type), loc));
107+
108+
// Promote to error if flag is set
109+
if self.warnings_as_errors {
110+
warning = warning.as_error();
111+
}
112+
113+
self.diag.add_error(warning);
114+
}
115+
116+
fn check_component_unused(&mut self, comp_name: &str) {
117+
// Clone the usage data to avoid borrowing issues
118+
let usage = match self.component_usage.get(comp_name).cloned() {
119+
Some(u) => u,
120+
None => return, // No usage data for this component
121+
};
122+
123+
// Check unused instances
124+
for (inst_name, def_loc) in &usage.instances {
125+
if !usage.invoked_instances.contains(inst_name) {
126+
self.add_unused_warning(
127+
"instance",
128+
"instance is created but never invoked",
129+
def_loc.pos(),
130+
);
131+
}
132+
}
133+
134+
// Check unused invocations (no ports accessed)
135+
for (inv_name, def_loc) in &usage.invocations {
136+
if !usage.accessed_invocations.contains(inv_name) {
137+
self.add_unused_warning(
138+
"invocation",
139+
"output ports on invocation never used",
140+
def_loc.pos(),
141+
);
142+
}
143+
}
144+
145+
// Check unused let parameters
146+
for (param_name, def_loc) in &usage.let_params {
147+
if !usage.referenced_params.contains(param_name) {
148+
self.add_unused_warning(
149+
"parameter",
150+
"parameter is defined but never used",
151+
def_loc.pos(),
152+
);
153+
}
154+
}
155+
156+
// Check unused signature parameters
157+
for (param_name, def_loc) in &usage.sig_params {
158+
if !usage.referenced_params.contains(param_name) {
159+
self.add_unused_warning(
160+
"parameter",
161+
"parameter is defined but never used",
162+
def_loc.pos(),
163+
);
164+
}
165+
}
166+
}
167+
}
168+
169+
impl Visitor for UnusedElementsCheck {
170+
fn name() -> &'static str {
171+
"unused-elements"
172+
}
173+
174+
fn signature(&mut self, sig: &mut ast::Signature) -> Action {
175+
let comp_name = sig.name.inner().as_ref().to_string();
176+
self.current_component = Some(comp_name.clone());
177+
178+
// Record signature parameters
179+
let usage = self.current_usage();
180+
for param in &sig.params {
181+
usage.sig_params.insert(
182+
param.name().as_ref().to_string(),
183+
param.param.clone(),
184+
);
185+
}
186+
187+
// Don't stop traversal - we need to visit the component body
188+
Action::Continue
189+
}
190+
191+
fn instance(&mut self, inst: &mut ast::Instance) -> Action {
192+
let usage = self.current_usage();
193+
usage.instances.insert(
194+
inst.name.inner().as_ref().to_string(),
195+
inst.name.clone(),
196+
);
197+
198+
// Visit parameter expressions for parameter usage
199+
for param_expr in &inst.params {
200+
self.visit_expr_for_params(param_expr.inner());
201+
}
202+
203+
Action::Continue
204+
}
205+
206+
fn invoke(&mut self, inv: &mut ast::Invoke) -> Action {
207+
let usage = self.current_usage();
208+
209+
// Record invocation definition
210+
usage.invocations.insert(
211+
inv.name.inner().as_ref().to_string(),
212+
inv.name.clone(),
213+
);
214+
215+
// Mark the instance as being invoked
216+
usage.invoked_instances.insert(
217+
inv.instance.inner().as_ref().to_string()
218+
);
219+
220+
// Visit port expressions for parameter usage
221+
for port in &inv.ports {
222+
self.visit_port_for_params(port.inner());
223+
}
224+
225+
Action::Continue
226+
}
227+
228+
fn param_let(&mut self, param: &mut ast::ParamLet) -> Action {
229+
let usage = self.current_usage();
230+
usage.let_params.insert(
231+
param.name.inner().as_ref().to_string(),
232+
param.name.clone(),
233+
);
234+
235+
// If the parameter has an expression, visit it for parameter references
236+
if let Some(expr) = &param.expr {
237+
self.visit_expr_for_params(expr);
238+
}
239+
240+
Action::Continue
241+
}
242+
243+
fn connect(&mut self, conn: &mut ast::Connect) -> Action {
244+
// Check if source is from an invocation and mark it as accessed
245+
if let Some(inv_name) = self.extract_invocation_name(conn.src.inner()) {
246+
let usage = self.current_usage();
247+
usage.accessed_invocations.insert(inv_name);
248+
}
249+
250+
// Visit port expressions for parameter usage
251+
self.visit_port_for_params(conn.src.inner());
252+
self.visit_port_for_params(conn.dst.inner());
253+
254+
Action::Continue
255+
}
256+
257+
fn fact(&mut self, fact: &mut ast::Fact) -> Action {
258+
// Visit expressions in facts for parameter usage
259+
for expr in fact.exprs() {
260+
self.visit_expr_for_params(expr);
261+
}
262+
Action::Continue
263+
}
264+
265+
fn exists(&mut self, exists: &mut ast::Exists) -> Action {
266+
// Visit the binding expression for parameter usage
267+
self.visit_expr_for_params(exists.bind.inner());
268+
Action::Continue
269+
}
270+
271+
fn finish(&mut self, namespace: &mut ast::Namespace) {
272+
// Check each component for unused elements
273+
for comp in &namespace.components {
274+
let comp_name = comp.sig.name.inner().as_ref().to_string();
275+
self.check_component_unused(&comp_name);
276+
}
277+
}
278+
279+
fn after_traversal(mut self) -> Option<u64> {
280+
self.diag.report_all()
281+
}
282+
}

crates/filament/src/cmdline.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,12 @@ pub struct Opts {
129129
/// use bitvector encoding for proofs
130130
#[argh(option, long = "solver-bv")]
131131
pub solver_bv: Option<u8>,
132+
133+
/// disable unused element warnings
134+
#[argh(switch, long = "no-warn-unused")]
135+
pub no_warn_unused: bool,
136+
137+
/// treat all warnings as errors
138+
#[argh(switch, long = "warnings-as-errors")]
139+
pub warnings_as_errors: bool,
132140
}

crates/filament/src/main.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ fn collect_known_pass_names() -> Vec<String> {
5858

5959
// AST pass names
6060
add_ast_pass::<ap::TopLevel>(&mut pass_names);
61+
add_ast_pass::<ap::UnusedElementsCheck>(&mut pass_names);
6162

6263
pass_names.sort();
6364
pass_names.dedup();
@@ -124,6 +125,11 @@ fn run(opts: &cmdline::Opts) -> Result<(), u64> {
124125
};
125126

126127
ast_pass_pipeline! { opts, ns; ap::TopLevel };
128+
129+
// Run unused elements check only if warnings are not disabled
130+
if !opts.no_warn_unused {
131+
ast_pass_pipeline! { opts, ns; ap::UnusedElementsCheck };
132+
}
127133

128134
// Set the parameter bindings for the top-level component
129135
if let Some(main) = ns.toplevel() {

0 commit comments

Comments
 (0)