Skip to content

Plate-local alias in symbolic UDF return size emits an out-of-scope declaration #60

Open

Description

@nsiccha

Problem

A plate cell may bind an input to a local name before calling a helper whose declared vector return length depends on that input. The local alias causes the emitted outer storage declaration to refer to a name outside its scope. The same helper call with the input passed directly emits ragged per-cell storage and compiles.

Verified at StanBlocks commit 75d9feb41f6f9148799c4c1f3236aacd5bdfdf7c with Julia 1.10.12.

Minimal reproducer

using StanBlocks

StanBlocks.@deffun begin
    alias_probe_cell(reads::int[n])::vector[max(reads)] = rep_vector(1.0, max(reads))
end

data = (; reads=[[1, 2], [1, 2, 3]], y=[1.0, 1.0])

alias_model = StanBlocks.@slic data begin
    out ~ plate(reads, y; outer=(2,)) do reads, y
        local_reads = reads
        conc = alias_probe_cell(local_reads)
        y ~ normal(conc[1], 1.0)
        conc[2]
    end
end

direct_model = StanBlocks.@slic data begin
    out ~ plate(reads, y; outer=(2,)) do reads, y
        conc = alias_probe_cell(reads)
        y ~ normal(conc[1], 1.0)
        conc[2]
    end
end

for (label, model) in (("alias", alias_model), ("direct", direct_model))
    result = StanBlocks.stanc_check(StanBlocks.stan_code(model); warn_pedantic=false)
    println(label, ": ", result.ok)
end

Observed output: alias: false, direct: true. The alias case emits matrix[max(out_local_reads), 2] out_conc; in transformed data, then stanc reports Identifier "out_local_reads" not in scope. The direct case emits per-cell lengths and a ragged out_conc__pl_mem_1 carrier and passes stanc.

Expected: the local alias retains the same per-cell return length as its input, with valid ragged storage. The two calls have the same helper arguments and model meaning.

This occurs in a pure StanBlocks model; no application-specific code or data are required.

Activity

  1. nsiccha commented on Oct 12, 2026

    @nsiccha
    OwnerAuthor

    Fixed on devibe by 3896a56. That commit builds on the per-cell size inference from f711046 and adds a follow-up that keeps the outer input sizes when plate parameters shadow the data names, as do reads, y does above.

    At that commit, with Julia 1.10.12, the reproducer above prints alias: true and direct: true. The alias case now emits per-cell lengths and ragged storage, like the direct case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions