Skip to content

Return self from every Expression builder; fold Eq/Gt into Comparison - #103

Merged
MidnightDesign merged 1 commit into
masterfrom
92-expression-builder-methods-return-internal-concrete-types-instead-of-self-fold-eqgt-into-comparison
Jul 24, 2026
Merged

Return self from every Expression builder; fold Eq/Gt into Comparison#103
MidnightDesign merged 1 commit into
masterfrom
92-expression-builder-methods-return-internal-concrete-types-instead-of-self-fold-eqgt-into-comparison

Conversation

@MidnightDesign

Copy link
Copy Markdown
Contributor

Closes #92. Scoped for the 0.3.0 breaking release.

Problem

The fluent builder on Expression is our most-used public @api surface, but nine of its fifteen combinators declared a class-level @internal node as their return type while the other six returned self. So the surface both leaked internal symbols and contradicted itself — eq returned Eq but its sibling neq returned self; gt returned Gt but lt/gte/lte returned self.

Separately, Eq and Gt were BC shims: @internal final subclasses of Comparison that added nothing but a fixed constructor. They only still existed because Expression::eq()/gt() named them as return types before Comparison existed.

Changes

  • All fifteen Expression builder combinators now return self. The rationale that was spelled out on not()'s docblock is now the class-level rule, so it moved to the class docblock and the per-method note is gone.
  • Expr::eq()/gt() return Comparison and construct through the shared comparison() helper.
  • Eq and Gt are removed; they fold into Comparison, which is now final (all six operators are plain instances of it). Comparison::equals() already matches on self, so a former Eq/Gt value and a plain Comparison compare equal — behavior is preserved.
  • Docblocks on Comparison and BinaryOperator updated to drop the Eq/Gt references and the "four of six" framing.
  • One test's data-provider name-builder discriminated closures from expressions with is_callable(), which only worked because the builders returned final concrete nodes. With self (abstract Expression, possibly-callable via a hypothetical __invoke subclass) PHPStan widened $expr() to mixed. Reordered it to check instanceof Expression first, mirroring the consuming test method.
  • Added an UPGRADING note for the break.

BC impact

Structural break — return-type narrowing on public methods plus two class removals. The Roave checker will flag it. Correct for a 0.MINOR bump; frozen after 1.0.0, at which point we'd be stuck publishing Eq/Add/… as permanent public types.

Checks

cs-check, psalm, phpstan, check-deps, phpunit (1152 tests), and infection on changed lines (100% MSI) all pass locally.

🤖 Generated with Claude Code

The fluent builder on Expression is public @api, but nine of its fifteen
combinators declared an @internal concrete node (Eq, Add, Call, ...) as
their return type while the other six returned self. The surface both
leaked internal symbols and contradicted itself — eq returned Eq but its
sibling neq returned self. Every combinator now returns self.

Once eq() and gt() no longer name Eq and Gt, those two classes have no
reason to exist: they were @internal final subclasses of Comparison that
added only a fixed constructor. They fold into Comparison, which is now
final since all six operators are plain instances of it. Comparison::equals
already matches on self, so a former Eq/Gt value and a plain Comparison
compare equal — behavior is preserved.

Structural BC break (return-type narrowing on public methods + class
removals); correct for the 0.3.0 breaking release.

Closes #92

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PbPoW5wtovYbi3R6z62fWT
@MidnightDesign
MidnightDesign merged commit a19673c into master Jul 24, 2026
25 checks passed
@MidnightDesign
MidnightDesign deleted the 92-expression-builder-methods-return-internal-concrete-types-instead-of-self-fold-eqgt-into-comparison branch July 24, 2026 08:44
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.

Expression builder methods return @internal concrete types instead of self; fold Eq/Gt into Comparison

1 participant