Skip to content

Keep StructArrays for struct results whose type is not inferred - #78

Open
oschulz wants to merge 3 commits into
mainfrom
structarrays-noninferred
Open

oschulz wants to merge 3 commits into
mainfrom
structarrays-noninferred

Conversation

@oschulz

@oschulz oschulz commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

Opened by Claude (Opus 5.5, effort level: max), working with @oschulz.

Broadcasts over a StructArray with nested-array columns returned a plain Array for struct-valued results whose type is not inferred concretely, so code that reads fields of the result, like LegendDSP's bl_stats.mean, failed. Such results now take StructArrays' own path again, as in 0.6 before the nested broadcast style existed: Base's copy takes the element type from the values, and a new similar method for these broadcasts decides between a StructArray and a plain Array, also when Base widens the element type.

Fixes #77.

Background

  • The same broadcast across releases: 0.6.6 returned StructArrays for inferred and non-inferred struct results, 1.0.2 to 1.2.0 plain Arrays for all struct results. The StructArrays extension of 1.3.0 restored StructArrays only for results inferred concretely.
  • In LegendDSP's SiPM DSP, minimum.(inters.x; init=0u"s") over µs values infers as Quantity{T,𝐓} where T, so the ifelse and the whole signalstats broadcast infer as Any. Reproduced with RadiationDetectorSignals 0.3.12 and RadiationDetectorDSP 0.2.21: bl_stats is a Vector with 1.4.2 and a StructVector with this PR, with identical values.

Design notes

  • Routing non-inferred results to StructArrays' path, as suggested in StructArrays extension: broadcast returns a plain Array instead of a StructArray when the element type is not inferred concretely #77, is not enough by itself. StructArrays treats every nonempty struct type as a record, Array included since Julia 1.11, so array results would become StructArrays with ref and size columns, and named tuples with varying names or tuples of varying length throw inside StructArrays. The new similar method therefore gives a StructArray only for result types that StructArrays can rebuild exactly from their fields: concrete struct types, and tuple and named tuple types with definite fields. Widened types with parameters that the fields don't determine stay in plain Arrays, as in 1.4.2.
  • Zero-dimensional results that are not inferred are returned unwrapped, as before and as in Base.
  • Block-wise evaluation over disk-backed data joins blocks with different element types into a container allocated the same way, so struct results stay StructArrays there too.

Testing

Full suite on Julia 1.13, except the Reactant test, whose CUDA library does not load on the local aarch64 machine, and the affected files on Julia 1.10. The new tests fail on 1.4.2, with the routing change alone, with all non-concrete struct types accepted, and without unwrapping zero-dimensional results. Codex (gpt-6-astra) reproduced the LegendDSP case and reviewed the change; its two findings, lost type parameters when widening and wrapped zero-dimensional results, are fixed and tested.

Version bumped to 1.4.3.

Broadcasts over StructArrays with nested-array columns sent results whose
element type is not inferred concretely to the nested-array path, which
collects them into a plain Array, struct results included. Before the
nested broadcast style existed (ArraysOfArrays 0.6), StructArrays' own
path returned a StructArray for these. Now only results inferred as a
concrete non-struct type take the nested-array path, all others Base's
copy, which takes the element type from the values and allocates via
similar, also when it widens. A similar method for these broadcasts
allocates a StructArray only for result types that StructArrays can
rebuild from their fields: concrete struct types, and tuple and named
tuple types with definite fields. So arrays, which StructArrays treats as
structs (Array since Julia 1.11), and widened types with parameters that
the fields do not determine stay in plain Arrays. Non-inferred results of
zero-dimensional broadcasts are still returned unwrapped, as Base does.

Block-wise evaluation over disk-backed data joins blocks of different
element types into a container allocated via similar as well, so struct
results stay StructArrays there too.

Fixes #77.

Assisted-By: generative AI
Assisted-By: generative AI
@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.95%. Comparing base (637468c) to head (ad92a28).

Files with missing lines Patch % Lines
ext/ArraysOfArraysStructArraysExt.jl 81.81% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #78      +/-   ##
==========================================
- Coverage   98.08%   97.95%   -0.14%     
==========================================
  Files          17       17              
  Lines        1308     1318      +10     
==========================================
+ Hits         1283     1291       +8     
- Misses         25       27       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
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.

StructArrays extension: broadcast returns a plain Array instead of a StructArray when the element type is not inferred concretely

1 participant