Skip to content

refactor(hir): split widget_decl.rs under the 2000-line file-size cap#6531

Merged
proggeramlug merged 1 commit into
mainfrom
fix/split-widget-decl-lower
Jul 17, 2026
Merged

refactor(hir): split widget_decl.rs under the 2000-line file-size cap#6531
proggeramlug merged 1 commit into
mainfrom
fix/split-widget-decl-lower

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Problem

crates/perry-hir/src/lower/widget_decl.rs reached 2372 lines, over the CI check_file_size.sh cap (2000). #6513 (reloadPolicy through all three widget backends) added +378 lines and tipped it over. The lint job's "File size limit" step was skipped while the benchmark-freshness step failed first, so it slipped onto main; once freshness passes it surfaces and reddens lint on every PR.

Fix

Pure mechanical split, no logic change — into the foo.rs + foo/ submodule layout the repo already uses (cf. object_cache.rs + object_cache/object_cache_tests.rs):

  • widget_decl/reactive_animate.rscollect_state_value_reads + try_desugar_reactive_animate (the reactive-animation desugaring). try_desugar_reactive_animate is re-exported from widget_decl.rs as pub(crate) so lower/mod.rs's pub(crate) use widget_decl::* still resolves it for expr_call/prescans.rs. (pub(super) couldn't be re-exported across the new module boundary — E0364.)
  • widget_decl/tests.rs — the #[cfg(test)] module.

widget_decl.rs drops from 2372 → 1665 lines.

Verification

  • cargo check -p perry-hir compiles.
  • cargo fmt -p perry-hir -- --check clean.
  • The 18 moved widget tests pass (cargo test -p perry-hir --lib widget_decl::tests).
  • ./scripts/check_file_size.sh → "OK: no Rust source files exceed 2000 lines."

Together with #6528 (benchmark-freshness gate), this greens the repo-wide lint job.

Summary by CodeRabbit

  • New Features

    • Added reactive animations for widget opacity and position (e.g., animateOpacity / animatePosition) that automatically update when referenced state values change.
    • Animations re-evaluate their inputs on state changes when the call shape is supported.
  • Tests

    • Expanded lowering coverage for widget field types, formatting/text content parsing, reload-policy extraction (including nested control flow), and array syntax/typing.
    • Added validation for invalid or non-literal reload-policy configurations.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4409bff3-f1b1-41aa-b7d9-ea82c1a282e3

📥 Commits

Reviewing files that changed from the base of the PR and between 9644471 and 0a48673.

📒 Files selected for processing (3)
  • crates/perry-hir/src/lower/widget_decl.rs
  • crates/perry-hir/src/lower/widget_decl/reactive_animate.rs
  • crates/perry-hir/src/lower/widget_decl/tests.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/perry-hir/src/lower/widget_decl.rs
  • crates/perry-hir/src/lower/widget_decl/tests.rs
  • crates/perry-hir/src/lower/widget_decl/reactive_animate.rs

📝 Walkthrough

Walkthrough

The changes add reactive lowering for state-dependent opacity and position animations, expose it through widget_decl, and move inline tests into a dedicated module with expanded coverage for widget declaration parsing.

Changes

Widget lowering updates

Layer / File(s) Summary
Reactive animation desugaring
crates/perry-hir/src/lower/widget_decl.rs, crates/perry-hir/src/lower/widget_decl/reactive_animate.rs
State .value reads in supported animateOpacity and animatePosition calls are lowered into initial animations plus stateOnChange re-invocations.
Widget lowering test module
crates/perry-hir/src/lower/widget_decl.rs, crates/perry-hir/src/lower/widget_decl/tests.rs
Inline tests are moved to tests.rs, with coverage for field types, value specs, formatting, text content, reload policies, and array syntax.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LoweringContext
  participant try_desugar_reactive_animate
  participant animateOpacityOrPosition
  participant stateOnChange
  LoweringContext->>try_desugar_reactive_animate: lower animation call
  try_desugar_reactive_animate->>animateOpacityOrPosition: emit initial animation
  try_desugar_reactive_animate->>stateOnChange: register State value subscribers
  stateOnChange->>animateOpacityOrPosition: re-lower and re-invoke on change
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately describes the main refactor of splitting widget_decl.rs.
Description check ✅ Passed The description covers the problem, fix, and verification well, though it doesn't follow the template headings exactly and omits explicit Related issue/Checklist sections.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/split-widget-decl-lower

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-hir/src/lower/widget_decl/reactive_animate.rs`:
- Around line 21-86: The collect_state_value_reads function documents support
for object literals and assignment expressions but currently skips both. Add
recursive traversal for ast::Expr::Object property value expressions and
ast::Expr::Assign assignment operands, preserving existing deduplication and
handling nested expressions such as object member reads; add regression coverage
confirming stateOnChange subscribers are generated for these cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e8024c25-2570-4c12-aaa3-e68ff8ebd848

📥 Commits

Reviewing files that changed from the base of the PR and between 23f1f35 and 9644471.

📒 Files selected for processing (3)
  • crates/perry-hir/src/lower/widget_decl.rs
  • crates/perry-hir/src/lower/widget_decl/reactive_animate.rs
  • crates/perry-hir/src/lower/widget_decl/tests.rs

Comment on lines +21 to +86
/// Covers the expression shapes most commonly found in animation arguments:
/// ternaries, binary/logical ops, parens, template literals, unary,
/// assignment RHS, call args, array/object literals, and member reads. The
/// catch-all silently skips unhandled shapes — worst case, a state read
/// inside an exotic expression just won't trigger reactivity (same
/// conservative failure mode as #104's template walker).
fn collect_state_value_reads(ctx: &LoweringContext, expr: &ast::Expr, out: &mut Vec<String>) {
match expr {
ast::Expr::Member(member) => {
// `<ident>.value` where ident is a registered State.
if let ast::MemberProp::Ident(prop) = &member.prop {
if prop.sym.as_ref() == "value" {
if let ast::Expr::Ident(obj) = member.obj.as_ref() {
let name = obj.sym.to_string();
if matches!(
ctx.lookup_native_instance(&name),
Some(("perry/ui", "State"))
) && !out.contains(&name)
{
out.push(name);
return;
}
}
}
}
collect_state_value_reads(ctx, member.obj.as_ref(), out);
}
ast::Expr::Paren(p) => collect_state_value_reads(ctx, &p.expr, out),
ast::Expr::Cond(c) => {
collect_state_value_reads(ctx, &c.test, out);
collect_state_value_reads(ctx, &c.cons, out);
collect_state_value_reads(ctx, &c.alt, out);
}
ast::Expr::Bin(b) => {
collect_state_value_reads(ctx, &b.left, out);
collect_state_value_reads(ctx, &b.right, out);
}
ast::Expr::Unary(u) => collect_state_value_reads(ctx, &u.arg, out),
ast::Expr::Tpl(t) => {
for e in &t.exprs {
collect_state_value_reads(ctx, e, out);
}
}
ast::Expr::Call(c) => {
if let ast::Callee::Expr(ce) = &c.callee {
collect_state_value_reads(ctx, ce, out);
}
for a in &c.args {
collect_state_value_reads(ctx, &a.expr, out);
}
}
ast::Expr::Array(a) => {
for el in a.elems.iter().flatten() {
collect_state_value_reads(ctx, &el.expr, out);
}
}
ast::Expr::Seq(s) => {
for e in &s.exprs {
collect_state_value_reads(ctx, e, out);
}
}
ast::Expr::TsNonNull(n) => collect_state_value_reads(ctx, &n.expr, out),
ast::Expr::TsAs(a) => collect_state_value_reads(ctx, &a.expr, out),
ast::Expr::TsTypeAssertion(a) => collect_state_value_reads(ctx, &a.expr, out),
_ => {}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Traverse the expression shapes promised by this collector.

Object and Assign are documented as supported but fall into _ => {}. Expressions such as ({ target: state.value }).target therefore receive only the initial animation and no stateOnChange subscriber. Add recursive handling for these shapes and regression tests, or use an exhaustive AST visitor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-hir/src/lower/widget_decl/reactive_animate.rs` around lines 21 -
86, The collect_state_value_reads function documents support for object literals
and assignment expressions but currently skips both. Add recursive traversal for
ast::Expr::Object property value expressions and ast::Expr::Assign assignment
operands, preserving existing deduplication and handling nested expressions such
as object member reads; add regression coverage confirming stateOnChange
subscribers are generated for these cases.

widget_decl.rs reached 2372 lines — #6513's reloadPolicy work pushed it
over the CI `check_file_size.sh` cap, reddening the lint job's file-size
step on every PR (previously masked behind the benchmark-freshness step).
Pure mechanical split, no logic change:

- widget_decl/reactive_animate.rs — collect_state_value_reads +
  try_desugar_reactive_animate (reactive-animation desugaring).
  try_desugar_reactive_animate is re-exported from widget_decl.rs as
  pub(crate) so lower/mod.rs's `pub(crate) use widget_decl::*` still
  resolves it for expr_call/prescans.rs.
- widget_decl/tests.rs — the #[cfg(test)] module.

widget_decl.rs drops to 1665 lines. perry-hir compiles, fmt --check is
clean, the 18 moved tests pass, and check_file_size.sh is green.
@proggeramlug
proggeramlug force-pushed the fix/split-widget-decl-lower branch from 9644471 to 0a48673 Compare July 17, 2026 13:32
@proggeramlug
proggeramlug merged commit 2a83a57 into main Jul 17, 2026
25 checks passed
@proggeramlug
proggeramlug deleted the fix/split-widget-decl-lower branch July 17, 2026 14:24
proggeramlug pushed a commit that referenced this pull request Jul 17, 2026
Version + CHANGELOG for the three fixes that green the repo-wide lint job:

- #6528 (v0.5.1261): version-invariant Cargo.toml fingerprint in the
  public-baseline freshness gate.
- #6531 (v0.5.1262): split widget_decl.rs under the 2000-line file-size cap.
- #6394/#6526 (v0.5.1263): key the object cache on all codegen-affecting
  env vars.

Bumps [workspace.package] 0.5.1260 -> 0.5.1263. The version bump no longer
invalidates the benchmark freshness gate (fixed by #6528).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant