Skip to content

Fix infer_type_of_indexed handling of MultiIndex - #1667

Merged
seantalts merged 3 commits into
stan-dev:masterfrom
seantalts:fix/infer-type-multiindex
Aug 16, 2026
Merged

Fix infer_type_of_indexed handling of MultiIndex#1667
seantalts merged 3 commits into
stan-dev:masterfrom
seantalts:fix/infer-type-multiindex

Conversation

@seantalts

@seantalts seantalts commented Aug 16, 2026

Copy link
Copy Markdown
Member

Expr.Helpers.infer_type_of_indexed mis-types multi-indexing. vector[MultiIndex] returns real (it falls through to the scalar arm), matrix[MultiIndex] returns vector (it is grouped with the Single arms), and matrix[MultiIndex, Single] matches no arm and raises an internal error. The rule is that Single reduces a dimension while All, Upfrom, Between, and MultiIndex preserve it.

The bug is latent today because codegen ignores the type metadata. It matters for anything that trusts it: Memory_patterns.matrix_set stops recursing at non-Eigen types, so an expression wrongly typed real can leave a variable out of the SoA demotion set. I hit this while working on #1666, where a compiler-built gather typed real produced C++ that assigned an SoA value into an AoS slice and did not compile. Ast_to_Mir.copy_indices also feeds arbitrary index lists into this function and could hit the internal error.

The fix moves MultiIndex into the dimension-preserving arm and adds [MultiIndex, Single] to the matrix arms next to [All, Single]. New cases in the inline expect test cover vector, row vector, matrix, matrix-then-column, and arrays. No other test output changes.

Submission Checklist

  • Run unit tests
  • Documentation
    • OR, no user-facing changes were made

Release notes

Fixed the inferred type of multi-indexed expressions in the compiler's middle intermediate representation.

Copyright and Licensing

By submitting this pull request, the copyright holder is agreeing to
license the submitted work under the BSD 3-clause license (https://opensource.org/licenses/BSD-3-Clause)

I used AI but understand the code and think it makes sense.

infer_type_of_indexed mis-typed multi-indexing in two arms:

* UVector, [MultiIndex _] (likewise URowVector and the complex
  variants) fell through to the scalar arm and returned UReal.
  Indexing a vector with an int array yields a vector.
* UMatrix, [MultiIndex _] was grouped with the Single arms and
  returned UVector. A multi-index selects multiple rows, so the
  result is UMatrix. Same for UComplexMatrix.

In addition, UMatrix, [MultiIndex _; Single _] matched no arm at all
and raised an internal error instead of returning UVector the way
[All; Single _] does.

The rule is that Single reduces a dimension while All, Upfrom,
Between, and MultiIndex all preserve it. MultiIndex now joins the
All/Upfrom/Between arm for single indices, and the matrix pair arms
gain [MultiIndex _; Single _] alongside [All; Single _].

Codegen ignores this type metadata, which is why the bug was latent,
but Memory_patterns trusts it for the SoA/AoS analysis: a gather
expression typed real makes matrix_set stop recursing, the gathered
variable is left out of the demotion set, and the generated C++ can
end up assigning an SoA value into an AoS slice, which does not
compile.
@seantalts
seantalts marked this pull request as ready for review August 16, 2026 16:22
@nhuurre

nhuurre commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Thanks. It seems lines 224, 226, and 227 implicitly rely on the wildcard index matching only Single _ because all others were already handled on line 209. (which was not the case for MultiIndex _ as you discovered)

I'd be more confident that this fixes all the corner cases if the final internal error arm was an exhaustive match instead of wildcard. I believe it should be

    | ( (UInt | UReal | UComplex | UTuple _ | UFun _ | UMathLibraryFunction)
      , _ :: _ )
     |(UVector | URowVector | UComplexVector | UComplexRowVector), _ :: _ :: _
     |(UMatrix | UComplexMatrix), _ :: _ :: _ :: _ ->
        ICE.(internal_errorf "Can't index %t" [UnsizedType.pp $ ut])
        [@coverage off]

@codecov

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.09091% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.32%. Comparing base (b96c001) to head (99e8b21).
⚠️ Report is 26 commits behind head on master.

Files with missing lines Patch % Lines
src/middle/Expr.ml 59.09% 9 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1667   +/-   ##
=======================================
  Coverage   92.31%   92.32%           
=======================================
  Files          67       67           
  Lines        9972     9978    +6     
=======================================
+ Hits         9206     9212    +6     
  Misses        766      766           
Files with missing lines Coverage Δ
src/middle/Expr.ml 84.21% <59.09%> (+0.74%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The wildcard internal error arm hid the assumption that a lone index
wildcard means Single, which is how the MultiIndex bug went unnoticed.
The final arm now lists the invalid shapes and the compiler checks that
every combination is handled. That check forced two arms the wildcard
used to swallow: a matrix with a Single row index and a range column
index is a row vector, and a matrix with two range indices is a matrix.
@seantalts

Copy link
Copy Markdown
Member Author

Done. Making it exhaustive also forced two arms the wildcard used to swallow: matrix with a Single row and a range column is a row_vector, and matrix with two range indices is a matrix. Both used to hit the internal error. Added test cases for those.

Comment thread src/middle/Expr.ml Outdated
Comment on lines +218 to +219
| ( UMatrix
, ([(All | Upfrom _ | Between _ | MultiIndex _); Single _] | [Single _]) )

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This looks like it covers both x[ : ,1] and x[1]. But x[1] is the same as x[1, : ]. I believe this is a pre-existing bug. Matrix[Single] should be a row vector.

matrix[Single] returned UVector, but x[1] is x[1, :], a row vector.
Same for complex matrices. The changed answers show up in the typed MIR
metadata and the inline expect test. No generated code changes.
@seantalts
seantalts enabled auto-merge August 16, 2026 19:20
@seantalts
seantalts merged commit 66f569f into stan-dev:master Aug 16, 2026
1 check passed
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.

2 participants