command: Operand carries a source span

Each WHERE-expression Operand now records the byte span of the
terminal it was built from — the precise per-literal highlight
target for an expression WARNING (finishing ADR-0027 §2's
highlight/hint wiring). parse_operand captures MatchedItem::span;
the RowFilter::eq convenience constructor uses Operand::NO_SPAN.

PartialEq is hand-written to ignore the span — it is editor
metadata, so Command equality stays whitespace- and
position-independent, which the Expr test corpus relies on.
No behaviour change; 1100 tests still pass, clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-19 09:20:52 +00:00
parent 39b92a7558
commit 426e80185f
4 changed files with 94 additions and 27 deletions
+49 -5
View File
@@ -257,9 +257,15 @@ impl RowFilter {
#[must_use]
pub fn eq(column: impl Into<String>, value: Value) -> Self {
Self::Where(Expr::Predicate(Predicate::Compare {
left: Operand::Column(column.into()),
left: Operand::Column {
name: column.into(),
span: Operand::NO_SPAN,
},
op: CompareOp::Eq,
right: Operand::Literal(value),
right: Operand::Literal {
value,
span: Operand::NO_SPAN,
},
}))
}
}
@@ -322,10 +328,48 @@ pub enum Predicate {
/// A comparison operand — a column reference or a literal
/// (ADR-0026 §1: operands are never nested expressions).
#[derive(Debug, Clone, PartialEq, Eq)]
///
/// Each operand carries the byte `span` it occupied in the
/// source. The span feeds the precise per-literal WARNING
/// highlight (ADR-0027) and is otherwise editor metadata —
/// `PartialEq` is hand-written to **ignore** it, so two
/// operands are equal when their column / value match
/// regardless of where they were typed. This keeps `Command`
/// equality whitespace- and position-independent (the bulk of
/// the `Expr` test corpus relies on it).
#[derive(Debug, Clone, Eq)]
pub enum Operand {
Column(String),
Literal(Value),
Column { name: String, span: (usize, usize) },
Literal { value: Value, span: (usize, usize) },
}
impl Operand {
/// Span used for operands built without a source position
/// (the [`RowFilter::eq`] convenience constructor).
pub const NO_SPAN: (usize, usize) = (0, 0);
/// The byte range this operand occupied in the source —
/// [`Operand::NO_SPAN`] for programmatically-built operands.
#[must_use]
pub const fn span(&self) -> (usize, usize) {
match self {
Self::Column { span, .. } | Self::Literal { span, .. } => *span,
}
}
}
impl PartialEq for Operand {
/// Compares column name / literal value only — the source
/// `span` is deliberately excluded (see the type docs).
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Column { name: a, .. }, Self::Column { name: b, .. }) => a == b,
(Self::Literal { value: a, .. }, Self::Literal { value: b, .. }) => {
a == b
}
_ => false,
}
}
}
/// The six comparison operators. `<>` and `!=` both parse to