Skip to content

fix(widget): decode array-of-object entryFields into Codable, not String (#5070)#5155

Merged
proggeramlug merged 2 commits into
mainfrom
fix/widget-array-object-decode
Jun 14, 2026
Merged

fix(widget): decode array-of-object entryFields into Codable, not String (#5070)#5155
proggeramlug merged 2 commits into
mainfrom
fix/widget-array-object-decode

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #5070. An iOS-widget perry/widget entryFields member that is an array of objects generated the correct nested-Codable type for the entry-struct field ([<Name><Field>Item]), but the TimelineProvider decode site cast the JSON value to String:

let entry = TopSitesWidgetEntry(date: Date(), sites: entryDict["sites"] as? String ?? "")
//                                                    ^^^ cannot convert String to [TopSitesWidgetSitesItem]

…so the generated Swift didn't compile.

Fix

  • Factor the per-field decode logic out of the two duplicated inline match blocks (native + AppIntent provider paths) into a single entry_field_decode helper.

  • Array-of-object (and other JSON-backed nested) fields now round-trip through JSONSerialization + JSONDecoder into the nested Codable struct(s):

    sites: (entryDict["sites"])
        .flatMap { try? JSONSerialization.data(withJSONObject: $0) }
        .flatMap { try? JSONDecoder().decode([TopSitesSitesItem].self, from: $0) } ?? []
  • Arrays of scalars now cast to [String]/[Double]/[Bool] (previously also fell through to the String cast).

  • Optionals of scalars cast to the matching optional type.

  • Scalars are unchanged.

This also unblocks ForEach(entry.<field>, ...) in render bodies.

Testing

  • New regression test test_timeline_decode_array_of_objects asserts the decode site no longer casts the object array to String and instead decodes into [TopSitesSitesItem].
  • cargo test -p perry-codegen-swiftui — 19 passed.
  • cargo fmt + cargo clippy -p perry-codegen-swiftui clean.

Per maintainer policy for this PR, no version bump or changelog entry is included.

Summary by CodeRabbit

  • Bug Fixes
    • Improved decoding of complex data types in widget timeline entries so arrays and nested objects are correctly generated as typed Codable results instead of being coerced to String.
  • Tests
    • Added regression test coverage to ensure array-of-objects timeline fields are decoded using the proper nested Codable decoding path.
  • Chores
    • Reformatted Android linker command/path construction logic to preserve existing behavior while improving readability, including Windows .cmd handling.

iOS-widget SwiftUI codegen emitted the entry struct field for an
array-of-objects `entryFields` member with the correct nested-Codable
type (`[<Name><Field>Item]`), but the TimelineProvider decode site cast
the JSON value to `String` (`entryDict["x"] as? String ?? ""`), so the
generated Swift failed to compile.

Factor the per-field decode into `entry_field_decode` (shared by both the
native and AppIntent provider paths) and round-trip array/object/optional
fields through JSONSerialization + JSONDecoder into the nested Codable
struct. Scalars and scalar arrays keep their direct `as?` casts. This also
unblocks `ForEach(entry.<field>, ...)` in render bodies.

Fixes #5070
@coderabbitai

coderabbitai Bot commented Jun 14, 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: dfb6ba06-9fa1-4493-9dd6-9b430d48c7b9

📥 Commits

Reviewing files that changed from the base of the PR and between dd7921b and edb99c2.

📒 Files selected for processing (2)
  • crates/perry/src/commands/compile/link/mod.rs
  • crates/perry/src/commands/compile/link/platform_cmd.rs
✅ Files skipped from review due to trivial changes (2)
  • crates/perry/src/commands/compile/link/platform_cmd.rs
  • crates/perry/src/commands/compile/link/mod.rs

📝 Walkthrough

Walkthrough

The Swift emitter gains two new private helpers, entry_field_decode and json_decode_expr, that centralize decode-expression generation for all WidgetFieldType variants. Both emit_native_timeline_provider and emit_app_intent_timeline_provider are updated to call entry_field_decode instead of inline match arms. A regression test verifies the array-of-objects path emits JSONDecoder decoding. The Android link-path construction in two files is reformatted to use explicit multi-line conditionals for the Windows .cmd suffix.

Changes

SwiftUI Entry Field Decode Fix

Layer / File(s) Summary
entry_field_decode and json_decode_expr helpers
crates/perry-codegen-swiftui/src/emit.rs
Adds two functions that generate typed Swift decode expressions for each WidgetFieldType: as? casts with defaults for scalars, direct as? [T] for scalar arrays, JSONSerialization+JSONDecoder into nested […Item].self types for array-of-objects and bare object fields, and optional-returning variants for optional shapes.
Timeline provider wiring and regression test
crates/perry-codegen-swiftui/src/emit.rs
Replaces inline per-field match blocks in emit_native_timeline_provider and emit_app_intent_timeline_provider with single calls to entry_field_decode. Adds test_timeline_decode_array_of_objects asserting that an array-of-objects field produces JSONDecoder().decode([TopSitesWidgetSitesItem].self, …) and that scalar paths remain unchanged.

Android Link Path Formatting

Layer / File(s) Summary
Android NDK clang path construction reformatting
crates/perry/src/commands/compile/link/mod.rs, crates/perry/src/commands/compile/link/platform_cmd.rs
Reformats the host-tag string in link-path construction and the format! expression in clang executable naming to use explicit multi-line if/else blocks instead of inline conditionals for the Windows .cmd suffix; behavior is unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 No more strings where arrays should be,
The rabbit has fixed the decode spree!
JSONDecoder leaps through each nested frame,
[SitesItem].self — now typed, not tamed.
Hop hop hooray, the widgets compile free! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary fix: decoding array-of-object entryFields into Codable types instead of String.
Description check ✅ Passed The PR description is comprehensive and covers Summary, Changes, Related issue, and Test plan sections matching the template requirements.
Linked Issues check ✅ Passed The changes fully address issue #5070 by implementing proper JSON decoding for array-of-object entryFields, enabling ForEach support, and adding regression tests.
Out of Scope Changes check ✅ Passed Changes to link/mod.rs and platform_cmd.rs are formatting-only (rustfmt reformatting), unrelated to #5070 but necessary for CI linting. Main fix in emit.rs directly addresses the linked issue.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/widget-array-object-decode

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

Pre-existing fmt drift from #5154 (Android NDK `.cmd` if-else); rustfmt
1.96.0 expands the single-line if/else. Unrelated to the widget fix but
required to get `cargo fmt --all -- --check` (the lint gate) green.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Added a second commit (style:) running rustfmt over link/mod.rs and platform_cmd.rs. That fmt drift is pre-existing from #5154 (the Android NDK .cmd if/else lands as a single line; rustfmt 1.96.0 expands it) and is unrelated to the widget fix — but cargo fmt --all -- --check runs over the whole tree, so the lint gate is currently red on every PR branched off main until it's reformatted. Happy to drop that commit if main is fixed independently.

@proggeramlug proggeramlug merged commit d985647 into main Jun 14, 2026
15 checks passed
@proggeramlug proggeramlug deleted the fix/widget-array-object-decode branch June 14, 2026 21:33
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.

TS-widget SwiftUI codegen: array-of-object entryFields decoded as String (cannot convert String to [..Item])

1 participant