Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
688fb47
chore(warnings): sweep machine-fixable warnings via cargo fix --all-t…
TheHypnoo Jul 25, 2026
ebdf7da
chore(warnings): drop 150 redundant `unsafe` blocks
TheHypnoo Jul 25, 2026
1acaf64
chore(warnings): delete two self-declared extern "C" HTTP symbols
TheHypnoo Jul 25, 2026
59c3db8
chore(warnings): clear 101 dead_code warnings
TheHypnoo Jul 25, 2026
e75d38f
chore(warnings): clear naming, visibility, unreachable and must-use w…
TheHypnoo Jul 25, 2026
3fcd0b2
chore(warnings): clear the last 65 warnings — host scope is now clean
TheHypnoo Jul 25, 2026
75f4744
chore(warnings): clean the reduced-feature scope too
TheHypnoo Jul 25, 2026
322f0ca
ci: gate rustc warnings with -D warnings
TheHypnoo Jul 25, 2026
3009179
docs(changelog): key the warnings-sweep fragment to #6837
TheHypnoo Jul 25, 2026
b402ec6
chore(warnings): fix float_literal_f32_fallback under rustc 1.97.1
TheHypnoo Jul 25, 2026
50c6d45
chore(warnings): silence the four warnings that only appear on Linux
TheHypnoo Jul 25, 2026
b2825cb
fix(runtime): restore `use std::path::Path` on non-unix targets
TheHypnoo Jul 25, 2026
36a521c
fix: address warnings sweep review
Jul 29, 2026
f3e67e5
chore: merge main into PR branch
TheHypnoo Jul 29, 2026
34a2516
fix(warnings): clean regressions after main sync
TheHypnoo Jul 29, 2026
f0978de
fix(workspace): restore container compose default build
Jul 30, 2026
8e28811
fix(warnings): merge main and address review findings
TheHypnoo Jul 30, 2026
fb3c40a
Merge remote-tracking branch 'origin/main' into chore/warnings-sweep-405
TheHypnoo Jul 30, 2026
f0e9972
Merge remote-tracking branch 'origin/main' into chore/warnings-sweep-405
TheHypnoo Jul 30, 2026
d0e3325
Merge remote-tracking branch 'origin/main' into chore/warnings-sweep-405
TheHypnoo Jul 30, 2026
e513467
Merge remote-tracking branch 'origin/main' into maint/pr-6837
Jul 30, 2026
4f744af
Merge remote-tracking branch 'origin/main' into maint/pr-6837
Jul 30, 2026
658303d
Merge remote-tracking branch 'origin/main' into chore/warnings-sweep-405
TheHypnoo Jul 30, 2026
9e85116
Merge remote-tracking branch 'origin/chore/warnings-sweep-405' into c…
TheHypnoo Jul 30, 2026
88f591b
fix(gc): poll after allocating loop controls
TheHypnoo Jul 30, 2026
673a010
Merge remote-tracking branch 'origin/main' into chore/warnings-sweep-405
TheHypnoo Jul 30, 2026
38bb229
Merge remote-tracking branch 'origin/main' into chore/warnings-sweep-405
TheHypnoo Jul 30, 2026
a2f6abb
fix(warnings): remove redundant iterator unsafe block
TheHypnoo Jul 30, 2026
f4f3b5c
Merge remote-tracking branch 'origin/main' into chore/warnings-sweep-405
TheHypnoo Jul 30, 2026
7ee9ece
Merge remote-tracking branch 'origin/main' into maint/pr-6837
Jul 30, 2026
040b35e
fix(warnings): remove redundant iterator unsafe block
Jul 30, 2026
7c42057
Merge remote-tracking branch 'origin/chore/warnings-sweep-405' into m…
Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,73 @@ jobs:
cargo clippy "${cargo_args[@]}"
fi

# ---------------------------------------------------------------------------
# rustc warnings gate
#
# `cargo check` with `-D warnings`, so a PR cannot add a rustc warning. This
# is deliberately separate from the clippy job above: clippy's own warn-level
# lints are informational here, while rustc's are not.
#
# Both legs are needed because they compile different code. `perry` depends on
# perry-runtime with `default-features = false`, so the product leg sees a
# runtime with regex-engine, diagnostics and temporal off, where items the
# workspace leg finds live are dead. The workspace leg passes `--all-targets`
# so test and bench targets count too — without it, test-only code drifts.
#
# perry-ui-macos is in the excluded scope (this runs on ubuntu), so its
# warnings are not gated here.
# ---------------------------------------------------------------------------
rustc-warnings:
name: Warnings (${{ matrix.scope }})
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
scope: [product, host-compatible]
env:
RUSTFLAGS: -D warnings
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "false"
SCCACHE_DIR: ${{ github.workspace }}/.sccache
SCCACHE_CACHE_SIZE: "12G"
CARGO_INCREMENTAL: "0"
steps:
- uses: actions/checkout@v7

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install sccache
uses: mozilla-actions/sccache-action@v0.0.10

- name: Cache sccache objects
uses: actions/cache@v6
with:
path: ${{ github.workspace }}/.sccache
key: sccache-${{ runner.os }}-perry-${{ github.job }}-${{ github.run_id }}
restore-keys: |
sccache-${{ runner.os }}-perry-

- uses: Swatinem/rust-cache@v2
with:
shared-key: "${{ runner.os }}-perry"
save-if: ${{ github.ref == 'refs/heads/main' }}

- name: Check for rustc warnings
run: |
if [[ "${{ matrix.scope }}" == "product" ]]; then
cargo check -p perry --bins
else
mapfile -t excluded < <(python3 scripts/workspace_architecture.py \
--print-excluded-scope host-compatible)
cargo_args=(--workspace --all-targets)
for package in "${excluded[@]}"; do
cargo_args+=(--exclude "$package")
done
cargo check "${cargo_args[@]}"
fi

# ---------------------------------------------------------------------------
# API docs drift gate (#465)
#
Expand Down
31 changes: 31 additions & 0 deletions changelog.d/6837-rust-warnings-to-zero.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
### Fixed

- **Rust warnings: 492 → 0, and a CI gate so they stay there.** A clean
`cargo check --workspace --all-targets` on `main` emitted 492 warnings
(150 `unused_unsafe`, 121 `dead_code`, 96 `unused_imports`, 31
`unreachable_patterns`, and eleven smaller families). Nothing in CI gated
them: the `lint` job runs only `cargo fmt --check`, and the `clippy` job
exits non-zero only on deny-level lints. Four scopes now report zero —
`--workspace --all-targets`, `-p perry --bins`, `-p perry-runtime
--no-default-features`, and the four-crate runtime/stdlib build — and a new
`rustc-warnings` job runs `cargo check` with `-D warnings` over the product
and host-compatible scopes.

Three findings the sweep turned up, each fixed rather than silenced:

- `js_node_http_res_write` and `js_node_http_res_end` were declared in an
`extern "C"` block in `perry-ext-http-server` for symbols that crate
defines itself. A local declaration of a symbol you also define is never
checked against the definition — the defect class that shipped an ABI
mismatch in #6646. Both signatures matched; the declarations are gone and
the remaining ~62 in that block are tracked separately.
- `duplex_allow_half_open_defaults_true_and_honors_false_option` had no
`#[test]` attribute, so it had never run.
- `test_seed_class_parent_closure_root` existed twice, both writing the same
`CLASS_PARENT_CLOSURES` static.

Eleven warnings appeared only under the reduced feature set `perry` selects
(`default-features = false` on perry-runtime), where regex-engine,
diagnostics and temporal are off. Those items are gated at the item, not
suppressed. The cross-host UI crates (ios/tvos/watchOS/visionos/android/
windows/gtk4) cannot be checked from a macOS or Linux host and are untouched.
1 change: 0 additions & 1 deletion crates/perry-codegen-arkts/src/tests/containers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// Section, string + number formatting, and the perry/media drain glue.
use super::*;

#[test]
// ----- Phase 2 v12: Tabs / Modal / Menu / Grid -----
#[test]
fn tabs_emits_tabcontent_per_spec() {
Expand Down
4 changes: 2 additions & 2 deletions crates/perry-codegen-arkts/src/tests/widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ fn animation_modifier_maps_curve_string_to_curve_enum() {
}

#[test]
fn shadow_modifier_maps_blur_to_radius_offsets_to_offsetXY() {
fn shadow_modifier_maps_blur_to_radius_offsets_to_offset_xy() {
let mut m = empty_module();
m.init.push(app_with_body(nmc(
"Text",
Expand Down Expand Up @@ -518,7 +518,7 @@ fn inline_style_border_combines_color_and_width() {
}

#[test]
fn text_with_id_string_is_NOT_treated_as_style() {
fn text_with_id_string_is_not_treated_as_style() {
// Text("Count: 0", "counter") — second string arg is the reactive
// id, NOT a style object. extract_style_object returns None for
// String args, so the v3.2 reactive path still wins.
Expand Down
1 change: 0 additions & 1 deletion crates/perry-codegen-wasm/src/emit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ use wasm_encoder::{
use closures::{collect_closures_from_expr, collect_closures_from_stmts};
// `f64_const_bits` is held alive for future use (matches the pre-split
// `#[allow(dead_code)]` annotation on its original definition).
use constants::f64_const_bits;
use constants::{
f64_const, EnumResolvedValue, STRING_TAG, TAG_FALSE, TAG_NULL, TAG_TRUE, TAG_UNDEFINED,
};
Expand Down
2 changes: 0 additions & 2 deletions crates/perry-codegen-wasm/src/emit/runtime_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
//!
//! Pure code-movement from `mod.rs`.

use super::*;

/// Import function indices (must match the order imports are added)
/// Most fields are unused directly but their indices define the WASM import order.
#[derive(Clone, Copy)]
Expand Down
2 changes: 0 additions & 2 deletions crates/perry-codegen-wasm/src/emit/ui_method_map.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! `map_ui_method`: maps perry/ui and perry/system method names to bridge
//! function names. Pure code-movement from `mod.rs`.

use super::*;

/// Map perry/ui and perry/system method names to bridge function names.
/// Mirrors the mapping in perry-codegen-js's emit_ui_method_call.
pub(super) fn map_ui_method(method: &str, class_name: Option<&str>) -> &'static str {
Expand Down
51 changes: 0 additions & 51 deletions crates/perry-codegen/src/codegen/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -985,57 +985,6 @@ pub(super) fn init_static_fields_late(
Ok(())
}

/// Returns true if `stmt` contains, at any nesting depth (through
/// if/while/do-while/for/labeled/try/switch bodies), an `Expr(
/// StaticMethodCall)` invoking the (`class_name`, `method_name`) pair —
/// the shape HIR lowering emits at the class-decl position for each
/// `__perry_static_init_*` synthetic method. Used by
/// `init_static_fields_late` to skip per-(class, block) pairs that
/// have already been invoked inline. (#2278)
///
/// Must recurse: a class declared inside `try { class C { static {...}
/// } }` (test262 static-init-abrupt.js wraps its whole class this way)
/// lowers its inline `StaticMethodCall` into the `Try`'s `body`, not at
/// `hir.init`'s top level. A shallow top-level-only scan missed it, so
/// this late fallback re-invoked the block a second time — outside the
/// user's `try`, so a throwing block's second run surfaced as an
/// uncaught exception instead of staying silently absent.
fn init_calls_static_block(stmt: &perry_hir::Stmt, class_name: &str, method_name: &str) -> bool {
use perry_hir::Stmt;
let any_calls = |stmts: &[Stmt]| {
stmts
.iter()
.any(|s| init_calls_static_block(s, class_name, method_name))
};
match stmt {
Stmt::Expr(perry_hir::Expr::StaticMethodCall {
class_name: c,
method_name: m,
..
}) => c == class_name && m == method_name,
Stmt::If {
then_branch,
else_branch,
..
} => any_calls(then_branch) || else_branch.as_ref().is_some_and(|b| any_calls(b)),
Stmt::While { body, .. } | Stmt::DoWhile { body, .. } | Stmt::For { body, .. } => {
any_calls(body)
}
Stmt::Labeled { body, .. } => init_calls_static_block(body, class_name, method_name),
Stmt::Try {
body,
catch,
finally,
} => {
any_calls(body)
|| catch.as_ref().is_some_and(|c| any_calls(&c.body))
|| finally.as_ref().is_some_and(|f| any_calls(f))
}
Stmt::Switch { cases, .. } => cases.iter().any(|case| any_calls(&case.body)),
_ => false,
}
}

/// #5989: collect every `(class, method)` invoked via a `StaticMethodCall`
/// ANYWHERE in the module — module init, top-level function bodies, and
/// (crucially) recursively inside nested closures. `init_calls_static_block`
Expand Down
4 changes: 0 additions & 4 deletions crates/perry-codegen/src/codegen/typed_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,10 +614,6 @@ pub(crate) fn typed_i1_closure_rejection_reason_with_types(
typed_i1_body_rejection_reason(body, locals)
}

pub(crate) fn typed_i32_closure_rejection_reason(expr: &Expr) -> Option<TypedCloneRejectionReason> {
typed_i32_closure_rejection_reason_with_types(expr, &HashMap::new())
}

pub(crate) fn typed_i32_closure_rejection_reason_with_types(
expr: &Expr,
module_local_types: &HashMap<u32, Type>,
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/collectors/i64_emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn emit_i64_function(llmod: &mut crate::module::LlModule, f: &Function, i64_
cx.f.block_mut(cx.cur).unwrap().ret(I64, "0");
}
}
struct I64Cx<'a> {
pub(crate) struct I64Cx<'a> {
f: &'a mut crate::function::LlFunction,
cur: usize,
locals: std::collections::HashMap<u32, String>,
Expand Down
34 changes: 17 additions & 17 deletions crates/perry-codegen/src/ext_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,23 +586,23 @@ const FFI_REGISTRY: &[(&str, OwnerKind)] = &[
/// optimization horizon worth measuring.
static USED_PROVIDERS: Mutex<Option<HashSet<OwnerKind>>> = Mutex::new(None);

/// Per-module capture buffer, active only between [`begin_module_capture`]
/// and [`take_module_capture`] on the same thread.
///
/// Why a thread-local rather than another field on [`USED_PROVIDERS`]:
/// the object cache (`crates/perry/src/commands/compile/object_cache.rs`)
/// needs to know which registry symbols *this one module* emitted, so it
/// can persist them next to the module's cached `.o` and replay them on a
/// later cache hit. [`USED_PROVIDERS`] is process-wide and rayon compiles
/// many modules concurrently, so it cannot attribute a symbol to a module.
/// `perry-codegen` itself never uses rayon and `compile_module` runs start
/// to finish on its caller's worker thread, so a thread-local scoped
/// around that one call captures exactly this module's emissions.
///
/// We record the matched symbol *names* rather than [`OwnerKind`]s: replay
/// re-runs them through [`record_ffi_call`], so the symbol→owner mapping is
/// always the one in today's table, never a stale routing decision baked
/// into a cache entry written by an older perry.
// Per-module capture buffer, active only between [`begin_module_capture`]
// and [`take_module_capture`] on the same thread.
//
// Why a thread-local rather than another field on [`USED_PROVIDERS`]:
// the object cache (`crates/perry/src/commands/compile/object_cache.rs`)
// needs to know which registry symbols *this one module* emitted, so it
// can persist them next to the module's cached `.o` and replay them on a
// later cache hit. [`USED_PROVIDERS`] is process-wide and rayon compiles
// many modules concurrently, so it cannot attribute a symbol to a module.
// `perry-codegen` itself never uses rayon and `compile_module` runs start
// to finish on its caller's worker thread, so a thread-local scoped
// around that one call captures exactly this module's emissions.
//
// We record the matched symbol *names* rather than [`OwnerKind`]s: replay
// re-runs them through [`record_ffi_call`], so the symbol→owner mapping is
// always the one in today's table, never a stale routing decision baked
// into a cache entry written by an older perry.
thread_local! {
static MODULE_CAPTURE: RefCell<Option<HashSet<&'static str>>> = const { RefCell::new(None) };
}
Expand Down
16 changes: 0 additions & 16 deletions crates/perry-codegen/src/lower_call/early_branches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@ fn typed_i1_closure_signature_note(reps: &[crate::codegen::TypedParamRep]) -> St
}
}

fn typed_string_closure_signature_note(arg_count: usize) -> String {
if arg_count <= 1 {
"typed_signature=string(i64 closure, string)->string".to_string()
} else {
"typed_signature=string(i64 closure, string, ...)->string".to_string()
}
}

fn typed_closure_signature_note(ret: &str, reps: &[crate::codegen::TypedParamRep]) -> String {
let first = reps.first().map(|rep| rep.label()).unwrap_or("void");
if reps.len() <= 1 {
Expand All @@ -48,14 +40,6 @@ fn typed_closure_signature_note(ret: &str, reps: &[crate::codegen::TypedParamRep
}
}

fn typed_i32_closure_signature_note(arg_count: usize) -> String {
if arg_count <= 1 {
"typed_signature=i32(i64 closure, i32)->i32".to_string()
} else {
"typed_signature=i32(i64 closure, i32, ...)->i32".to_string()
}
}

fn is_async_dispose_symbol_index(index: &Expr) -> bool {
let Expr::SymbolFor(symbol_name) = index else {
return false;
Expand Down
8 changes: 0 additions & 8 deletions crates/perry-codegen/src/lower_call/func_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,6 @@ fn typed_i1_signature_note(reps: &[crate::codegen::TypedParamRep]) -> String {
}
}

fn typed_i32_signature_note(arg_count: usize) -> String {
match arg_count {
0 => "typed_signature=i32()->i32".to_string(),
1 => "typed_signature=i32(i32)->i32".to_string(),
_ => "typed_signature=i32(i32, ...)->i32".to_string(),
}
}

fn typed_signature_note(
ret: &str,
reps: &[crate::codegen::TypedParamRep],
Expand Down
16 changes: 0 additions & 16 deletions crates/perry-codegen/src/lower_call/method_override.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,6 @@ fn typed_i1_method_signature_note(reps: &[crate::codegen::TypedParamRep]) -> Str
}
}

fn typed_i32_method_signature_note(arg_count: usize) -> String {
if arg_count <= 1 {
"typed_signature=i32(i32)->i32".to_string()
} else {
"typed_signature=i32(i32, ...)->i32".to_string()
}
}

fn typed_string_method_signature_note(arg_count: usize) -> String {
if arg_count <= 1 {
"typed_signature=string(string)->string".to_string()
} else {
"typed_signature=string(string, ...)->string".to_string()
}
}

fn typed_method_signature_note(ret: &str, reps: &[crate::codegen::TypedParamRep]) -> String {
let first = reps.first().map(|rep| rep.label()).unwrap_or("void");
if reps.len() <= 1 {
Expand Down
Loading