You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[3/15] Apply R's type-promotion lattice: promoted result modes, operand casts, and a narrowing-reassignment error (t-kalinowski#139)
* Report and apply promoted result modes in operators and constructors
reduce_promoted_mode() implemented R's mode lattice correctly but was
only used for c()/reduction output declarations; conform() reported the
first non-scalar operand's mode, and no operand was ever cast. Since
`<-` copies the reported mode verbatim into the target's declaration,
mixed integer/double arithmetic silently truncated (x + 0.5 on integer
x returned integers), and every context where Fortran requires uniform
argument types -- array constructors, min/max, modulo -- failed at the
gfortran stage for mixed modes. Logical operands could not participate
in arithmetic or comparisons at all.
Centralize the mechanism in two helpers and wire it through:
- cast_to_mode(x, mode, context): the one place cast spellings live.
Handles integer->double, logical->integer/double (booleanizing or
relabeling bind(c) integer-backed logicals as needed), and errors
cleanly on casts it cannot spell. bind_cast_value() folds into it.
- promote_operands(args, context): join + cast for uniform-type
contexts (c(), multi-arg min/max/sum/prod, modulo).
- conform() now reports the lattice join; the lattice gains complex as
its top so complex results no longer depend on operand order.
- ^ always returns double like R, casting the base but keeping integer
exponents integer (real ** int is exact and defined for negative
bases, matching R_pow); the parenthesized exponent fixes the
non-standard `x ** -1_c_int` spelling.
- Arithmetic and comparisons cast logical operands to integer
(R: TRUE + TRUE is 2L), including unary +/-.
Snapshot churn: heat-diffusion's dx^2/dy^2 gain the base cast and
exponent parens; all runtime results are unchanged there.
* Error when reassignment would narrow a variable's mode
R re-types a binding on assignment (x <- x + 0.5 promotes an integer x
to double); Fortran cannot re-type a variable, so quickr kept the
declared mode and the assignment silently truncated the value back into
it -- quick() returned 2 where R returns 2.5, with no diagnostic.
check_assignment_compatible() only checked rank, not mode.
Add check_reassignment_narrowing(): reassigning a value whose mode sits
above the target's on the lattice (logical < integer < double <
complex) is now a compile-time error naming the variable and both
modes. Same-mode reassignment and assigning a lower-mode value into a
wider variable (x_dbl <- x_dbl + 1L) are unaffected.
* Compute double %/% in the real domain
Fortran FLOOR() returns a default-kind integer, so the double branch of
%/% silently overflowed large quotients: 1e20 %/% 3 came back as
-2147483648 instead of ~3.33e19. The sibling floor() handler already
avoids this with aint() plus a merge() adjustment in real arithmetic;
use the same expression here, hoisting the quotient so it is evaluated
once instead of three times.
* Extend the narrowing check to subassignment and superassignment
check_reassignment_narrowing() only guarded whole-variable `<-`, so
`x[1L] <- 2.5` on a declared-integer x silently truncated to 2L where R
promotes the whole vector to double -- the same contract hole the
reassignment check was added to close. `x <<- val` and `x[i] <<- val`
inside local closures had the identical gap.
Run the same check against the base variable in the `[<-`, `<<-`, and
`[<<-` handlers.
* Treat logicals as integers in abs() and single-arg reductions
The logical-as-integer arithmetic rule was spelled inline at each call
site, and two sites missed it: abs() passed logical expressions straight
to Fortran's abs(), and the single-argument reduction path emitted
sum((x /= 0)) / minval((x /= 0)) / etc. -- all gfortran type errors,
where R returns integer results (abs(TRUE) is 1L, sum(TRUE) is 1L).
Name the rule once as arith_join_mode() and use it in abs(), reduce_arg,
and the existing inline spellings (unary +/-, %%, multi-arg reductions,
promote_arith_pair), so the next consumer cannot miss it.
* Spell the mode lattice once: shared mode_lattice + mode_rank()
The narrowing check hand-spelled the promotion order that
reduce_promoted_mode() encoded as an if-chain, so the two rules this
branch ties together -- what a join promotes to, and what a
reassignment may not narrow from -- could drift apart. One lattice
constant and a mode_rank() helper now back both: the join takes the
highest rank present (modes outside the lattice are ignored, as the
old chain did), the narrowing check compares ranks. No behavior
change.
* Format with air
* Use result unpacking for more compact and readable code.
* Create snapshots of assignmetn narrowing errors
0 commit comments