Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -236,6 +236,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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ members = [
# Build each platform package explicitly for its applicable target.
default-members = [
"crates/perry",
"crates/perry-container-compose",
]

# Aggressive release optimizations for small, fast binaries
Expand Down
35 changes: 35 additions & 0 deletions changelog.d/6837-rust-warnings-to-zero.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
### 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.

- Restore `perry-container-compose` to the workspace default build set. This
keeps the container feature's auto-optimized archive available and satisfies
the workspace invariant exercised by the hermetic test tier.
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 @@ -1084,57 +1084,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
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2221,7 +2221,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
let Some(sites) = spec_facts.call_sites.get(&f.id) else {
continue;
};
let mut reject =
let reject =
|reason: typed_abi::TypedCloneRejectionReason,
records: &mut Vec<crate::native_value::NativeRepRecord>| {
record_typed_clone_rejection(
Expand Down
7 changes: 0 additions & 7 deletions crates/perry-codegen/src/codegen/spec_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,6 @@ pub(crate) struct SpecFnPlan {
pub dispatch: SpecDispatch,
}

impl SpecFnPlan {
/// Phase-2 budget: exactly ONE specialized entry per function (the
/// dominant tuple). Kept as an explicit constant so raising it later is a
/// knob, not a rewrite.
pub(crate) const MAX_ENTRIES_PER_FUNCTION: usize = 1;
}

/// LLVM parameter type for a rep slot.
pub(crate) fn spec_rep_llvm_ty(rep: SpecParamRep) -> LlvmType {
match rep {
Expand Down
50 changes: 45 additions & 5 deletions crates/perry-codegen/src/codegen/typed_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,10 +635,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 Expand Up @@ -922,6 +918,10 @@ fn typed_receiver_chain_fields<'a>(
if link.extends_expr.is_some()
|| link.native_extends.is_some()
|| link.heritage_lexically_shadowed
// A name-only parent is imported. Its authoritative layout is
// source-prefix-resolved elsewhere; this name-keyed map cannot
// disambiguate same-named cross-module classes safely.
|| (link.extends_name.is_some() && link.extends.is_none())
|| (link.extends.is_some() && link.extends_name.is_none())
{
return Err(TypedCloneRejectionReason::ReceiverClassExtends);
Expand Down Expand Up @@ -1891,7 +1891,7 @@ pub(crate) fn lower_typed_i1_body(
#[cfg(test)]
mod tests {
use super::*;
use perry_hir::Param;
use perry_hir::{Class, Param};

fn param(id: u32, name: &str, ty: Type) -> Param {
Param {
Expand Down Expand Up @@ -1928,6 +1928,46 @@ mod tests {
vec![Stmt::Return(Some(expr))]
}

fn class(id: u32, name: &str, extends: Option<u32>, extends_name: Option<&str>) -> Class {
Class {
id,
name: name.to_string(),
type_params: Vec::new(),
extends,
extends_name: extends_name.map(str::to_string),
native_extends: None,
extends_expr: None,
heritage_lexically_shadowed: false,
fields: Vec::new(),
constructor: None,
methods: Vec::new(),
getters: Vec::new(),
setters: Vec::new(),
static_accessor_names: Vec::new(),
static_accessor_fn_ids: Vec::new(),
static_fields: Vec::new(),
static_methods: Vec::new(),
computed_members: Vec::new(),
decorators: Vec::new(),
is_exported: false,
aliases: Vec::new(),
is_nested: false,
alloc_width_hint: 0,
}
}

#[test]
fn receiver_clone_rejects_name_only_imported_parent_chain() {
let parent = class(1, "Parent", None, None);
let child = class(2, "Child", None, Some("Parent"));
let classes = HashMap::from([("Parent".to_string(), &parent)]);

assert!(matches!(
typed_receiver_chain_fields(&classes, &child),
Err(TypedCloneRejectionReason::ReceiverClassExtends)
));
}

#[test]
fn f64_clone_accepts_mixed_raw_params_when_return_expr_is_numeric_safe() {
let f = function(
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
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/collectors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub(crate) use shadow_slots::{
collect_declared_shadow_slots_in_stmts, collect_shadow_slot_clear_points,
};
pub(crate) use spec_abi_sites::{
collect_spec_abi_facts, local_is_reassigned, reassigned_locals, SpecParamRep, SpecTaBinding,
collect_spec_abi_facts, reassigned_locals, SpecParamRep, SpecTaBinding,
};
pub(crate) use this_as_value::{
class_chain_extends_builtin_error, class_chain_has_unmodeled_base, class_uses_this_as_value,
Expand Down
7 changes: 5 additions & 2 deletions crates/perry-codegen/src/collectors/spec_abi_sites.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub(crate) fn reassigned_locals(stmts: &[Stmt]) -> HashSet<u32> {
}

/// Single-id convenience over [`reassigned_locals`].
#[cfg(test)]
pub(crate) fn local_is_reassigned(stmts: &[Stmt], id: u32) -> bool {
reassigned_locals(stmts).contains(&id)
}
Expand Down Expand Up @@ -197,8 +198,10 @@ fn record_expr_use(e: &Expr, depth: u32, scan: &mut ModuleScan) {
receiver,
..
} => {
record_receiver_use(target, depth, scan);
record_receiver_use(receiver, depth, scan);
// Ordinary [[Set]] can invoke proxies/accessors and otherwise
// expose either object, so neither position is length-safe.
record_expr_use(target, depth, scan);
record_expr_use(receiver, depth, scan);
record_expr_use(key, depth, scan);
record_expr_use(value, depth, scan);
}
Expand Down
Loading
Loading