Skip to content

[9/15] Three copies of the conformability policy, and two of the floor spelling #130

Description

@mns-nordicals

What the code looks like now

The conformability contract — a statically known mismatch is a compile
error, dims that cannot be compared statically get a statement-level
runtime guard, provably equal dims need nothing, never warn-and-proceed —
is spelled three times, in three files, by three functions that have to be
kept in agreement by hand:

  • guard_conformable_dims() in R/r2f-matrix-blas.R, added by PR 7 for the
    BLAS/LAPACK lowerings. It is the only one that renders a statically known
    extent as the literal rather than size(x).
  • emit_elementwise_size_guard() in R/r2f-operators-helpers.R, used by
    maybe_reshape_vector_matrix() for the elementwise operators. Its
    caller does the check-then-emit dance inline at five separate sites.
  • An inline per-axis guard inside check_ifelse_branch_shape()
    (R/r2f-conditionals.R), whose private ifelse_axis_verdict()
    re-implements check_elementwise_lengths() — minus the zero-length
    policy, which is why an ifelse() branch of statically known length 0
    slips through where an elementwise operand of the same length is
    rejected.

Two smaller duplications sit alongside:

  • check_conformable() (R/r2f-matrix-blas.R) is dims_match()
    (R/r2f-operators-helpers.R) written in list form — same two branches,
    same answers, wrapped in list(ok=, unknown=) so that both call sites
    (solve() routing, the cbind/rbind common-dim check) then have to
    destructure a verdict whose unknown field they immediately collapse
    back to a boolean.
  • The stay-in-the-real-domain floor spelling — aint(x) plus a merge()
    correction, written the way it is because Fortran's FLOOR() returns an
    integer and would overflow on a large double — appears verbatim in the
    floor() handler (R/r2f-math.R) and in double %/%
    (R/r2f-arithmetic.R).

Why it matters

  • A policy fix has to land three times. Each of the earlier PRs in this
    series tightened the conformability rules, and each had to be applied
    once per emitter. A missed emitter fails silently — it keeps compiling,
    just with the old semantics. The ifelse() zero-length hole above is
    exactly that failure, already in the tree.
  • The three emitters have already drifted. They disagree on whether a
    known extent is spelled as a literal or as size(x), on whether a
    zero-length operand is refused, and on whether a NULL hoist is a clean
    error or a crash. None of these differences is intentional.
  • check_conformable() invites the wrong reading. Its
    unknown = TRUE result looks like it defers to a runtime guard, but
    neither call site can emit one — a cbind/rbind output dim has to be
    known to declare the variable at all. Both sites turn unknown into the
    same compile error, which is what dims_match() already means.

Expected behavior

No behavior change — this is a maintainability issue. The conformability
policy should be spelled once, in one function, that every enforcement
point calls: the elementwise operators, ifelse(), and the BLAS/LAPACK
lowerings. check_conformable() should be its dims_match() self at both
call sites, and the real-domain floor spelling should live in one place.

The combinatorial conformability grid added earlier in this series
(QUICKR_FULL_GRID=1) is what makes such a change safe to attempt: it
pins values, typeof(), shapes, and the documented compile/runtime errors
for every (mode, shape, operator) cell against plain R.

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