Skip to content

feat(promql): lower unary negation as multiply-by-minus-one (#36) - #102

Merged
zzylol merged 1 commit into
mainfrom
feat/36-unary-negation
Jul 6, 2026
Merged

zzylol merged 1 commit into
mainfrom
feat/36-unary-negation

Conversation

@zzylol

@zzylol zzylol commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Closes #36.

Problem

PromQL unary negation (-expr) was rejected outright: -rate(m[5m]), -some_metric, sum(-m), a - -b all errored. The root cause was that the L2 PromQL path had no scalar/negate node to express "multiply this vector by -1" — negation is effectively x * -1, which needs a scalar operand.

Fix

That scalar operand landed in #35 (QueryExpr::Scalar + scalar BinaryOp operands), so this is now a small, localized change to the one rejection site in the PromQL walker:

  • Vector operandexpr * -1: a Mul BinaryOp of the (label-preserving) vector against Scalar(-1), no vector match. Mul is commutative, so operand order carries no hazard, and the existing L3 BinaryOp schema rule already follows the non-scalar side — so negation is label-preserving with no extra handling.
  • Constant-foldable operand (-(10*1024*1024)) → a negated Scalar leaf, matching how bare literals already fold.

Because the aggregate and binary walkers already recurse through the shared walk (from the #27 nesting work), negation composes everywhere for free: inside aggregate arguments (sum(-m)), binary/set ops (a - -b, -a or -b), and nested (- -m).

Tests

The five cases the old unary_negation_is_rejected__GAP test pinned as rejected now lower correctly. That test is replaced by:

  • unary_negation_lowers_as_multiply_by_minus_one — all five original cases lower; the root -some_metric is checked structurally (Scan * Scalar(-1), no vector match, [ts, value] schema preserved), and sum(-m) is verified to nest the negation under the aggregate.
  • unary_negation_of_constant_folds_to_scalar and double_unary_negation_nests — the fold and nesting edge cases.
  • Two exact-tree e2e pins in binary_op.rs (q36_unary_negation_is_multiply_by_minus_one, q36_sum_of_negation_nests).
  • Moved unary negation from "Rejected cleanly" to "Lowers" in the docs/promql-lowering.md nesting-contract table; scrubbed the two stale "negation rejected" references in promql.rs / error.rs.

The full PromQL corpus suite (awesome-prometheus-alerts + promql corpora) still passes — no rejection-count assertion shifted.

Notes

  • Front-end crate only; independent of any other open work.
  • Verified: cargo test --workspace — 340 passing, 0 failures; cargo clippy --all-targets clean; production source (promql.rs, error.rs) has zero net-new cargo fmt drift vs main.

🤖 Generated with Claude Code

`-expr` was rejected outright — the L2 PromQL path had no way to express
"sign-flip every sample". Now that a scalar operand exists (#35),
negation lowers as `expr * -1`: a `Mul` BinaryOp of the vector against
`Scalar(-1)`, with no vector match. A constant-foldable operand
(`-(10*1024*1024)`) collapses to a negated `Scalar` leaf instead. `Mul`
is commutative, so operand order carries no hazard, and the L3 BinaryOp
schema rule already takes the vector side — negation is label-preserving
for free.

Negation composes with everything: it lowers inside aggregate arguments
(`sum(-m)`), binary ops (`a - -b`), set ops (`-a or -b`), and nests
(`- -m`), because the aggregate/binary walkers already recurse through
the shared `walk`.

The five cases the old `unary_negation_is_rejected__GAP` test pinned as
rejected now lower correctly; that test is replaced by
`unary_negation_lowers_as_multiply_by_minus_one` plus constant-fold and
double-negation coverage, two exact-tree e2e pins, and a row in the
nesting-contract table in docs/promql-lowering.md.

Closes #36

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zzylol
zzylol merged commit 4959f3b into main Jul 6, 2026
1 check passed
@zzylol
zzylol deleted the feat/36-unary-negation branch July 6, 2026 00:49
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.

Unary negation rejected in PromQL lowering (no scalar/negate node)

1 participant