Skip to content

Add unused items pass#29431

Draft
mohammadfawaz wants to merge 1 commit into
masterfrom
mohammadfawaz/unused_items
Draft

Add unused items pass#29431
mohammadfawaz wants to merge 1 commit into
masterfrom
mohammadfawaz/unused_items

Conversation

@mohammadfawaz

@mohammadfawaz mohammadfawaz commented May 19, 2026

Copy link
Copy Markdown
Collaborator

Adds an UnusedItems warning pass to the compiler. Runs after TypeChecking and emits rustc-style dead_code / unused_variables / unused_imports diagnostics under the UNU code prefix.

What's checked

Item Warned?
Function (non-entry, non-@test)
Struct (non-record) ✅ (incl. transitive deadness)
const (any scope)
Import
Local let / param / loop iter / const generic
_-prefixed binding that was read
Record ❌ (public surface)
Struct/record field ❌ (too noisy without dataflow + opt-out)
Mapping ❌ (access via intrinsics/storage ops not tracked)
Storage variable ❌ (same)
Interface / function prototype / record prototype

Fields, mappings, and storage variables share one future unlock: data-flow-aware "is this ever read/written" tracking + an @allow_unused attribute for the legitimate false-positive cases.

Leading-_ rules

Mirroring rustc's _x convention. Whether _ is permitted depends on whether the name reaches the Aleo VM — if it does, snarkVM rejects identifiers that do not start with a letter, so we reject at compile time too. Allowed positions silence the corresponding unused_* warning.

Position Allowed? Effect / rejection
Local let binding Silences unused_variable
Tuple-pattern element Silences unused_variable
Local const Silences unused_const
Top-level / module const Silences unused_const
Loop iteration variable Silences unused_variable
Function parameter (non-entry, non-@test) Silences unused_variable. Param names don't reach the VM (renamed to r0, r1, …).
Const-generic parameter Silences unused_variable. Monomorphized away.
Variant::Fn (free fn) name Silences unused_function. Force-inlined so the name never reaches the VM.
Variant::FinalFn (final fn) name Silences unused_function. Always inlined.
Interface name Interfaces are Leo-only — no rejection, no warning.
Entry-point function name (fn inside program { … }) NameValidation rejects → ENV03711002.
Struct / record name NameValidation rejects → ENV03711002.
Struct / record field name NameValidation rejects → ENV03711002.
Mapping name NameValidation rejects → ENV03711002.
Storage variable name NameValidation rejects → ENV03711002.
Program name (program X.aleo) NameValidation rejects → ENV03711002.
Binding name matching a bare-callable intrinsic (_self_caller, _block_height, etc.) Parser rejects → EPAR0370056. The parser dispatches _self_caller() to the intrinsic before any scope lookup, so a same-named local would be silently shadowed.
@no_inline on a _-prefixed Variant::Fn Type-checker rejects → ETYC0372192. The two annotations directly contradict.

Bonus: reading a _-prefixed local (which would defeat the silencing marker) emits WUNU03714006used binding \x` whose name begins with ```.

Architecture

The pass is structured as three phases, all driven by the standard AstVisitor / UnitVisitor traits:

  1. UseCollector walks the AST once: populates used_imports / used_globals / potential_global_uses, tracks lexical scopes, emits body-level warnings as each scope drains, and records composite member-dependency edges into a local graph.
  2. Pure reachability scan over that dependency graph from "live roots" (records + library top-level structs + any composite directly referenced from user code).
  3. UnusedChecker walks the AST again (without descending into bodies) and emits warnings for unused top-level items and imports.

The two visitors share state via an owned CollectedUses struct passed by reference. NameValidation (a sibling pass that runs before TypeChecking) handles all leading-_ rejections in positions where the name reaches the Aleo VM.

@mohammadfawaz mohammadfawaz self-assigned this May 19, 2026
@codspeed-hq

codspeed-hq Bot commented May 19, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 17 untouched benchmarks


Comparing mohammadfawaz/unused_items (548b070) with master (2b82c79)

Open in CodSpeed

@mohammadfawaz mohammadfawaz force-pushed the mohammadfawaz/unused_items branch 2 times, most recently from c9ad543 to 1d530b0 Compare May 19, 2026 22:14
@mohammadfawaz mohammadfawaz added 🚀 feature A new feature. 🧱 Core Compiler Anything related to the core compiler including parsing, analysis, transforms, codegen, etc. labels May 20, 2026
@mohammadfawaz mohammadfawaz force-pushed the mohammadfawaz/unused_items branch 4 times, most recently from 212cdfa to 5cd4474 Compare May 25, 2026 14:11
@mohammadfawaz mohammadfawaz force-pushed the mohammadfawaz/unused_items branch 4 times, most recently from 81a0e92 to 73fd3ba Compare May 28, 2026 17:11
Introduce a new compiler pass that emits warnings for unused items
(functions, structs, consts, imports, local bindings) modeled on
rustc's `dead_code` and `unused_variables` lints. Also relax the
parser's underscore-prefix rejection for binding-position
identifiers, reject binding names that collide with compiler
intrinsics, and forbid `@no_inline` on functions whose name starts
with `_` (those are force-inlined).

Document the new warnings and the identifier rules under
`documentation/language/`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🧱 Core Compiler Anything related to the core compiler including parsing, analysis, transforms, codegen, etc. 🚀 feature A new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant