Skip to content

fix(core): reject transient reuse after persistent! and clean up leaked PHP type errors - #2229

Merged
Chemaclass merged 1 commit into
mainfrom
fix/clojure-suite-clean-errors
May 29, 2026
Merged

Chemaclass merged 1 commit into
mainfrom
fix/clojure-suite-clean-errors

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

The nightly clojure-test-suite CI job (informational, continue-on-error) runs the Clojure-compatibility suite against Phel. Triaging its failures (#2223) surfaced a handful of genuine Phel bugs rather than intentional leniency: transients could be reused after persistent!, and several bad-type inputs leaked raw PHP TypeError / deprecation text instead of a clean Phel exception.

Companion PR phel-lang/clojure-test-suite#20 marks the intentional divergences as :phel; this PR fixes the real bugs.

💡 Goal

Match Clojure where it makes sense without making Phel worse: enforce the transient/persistent contract and surface clean errors, while keeping Phel's deliberate lenient behaviours (nil-punning, polymorphic conj/assoc over a transient accumulator) intact.

🔖 Changes

  • Transient lifecycle guard. New Phel\Lang\Collections\TransientStateTrait: persistent() invalidates the transient, and every mutator checks it first. conj! / assoc! / dissoc! / disj! / pop! — or a second persistent! — after persistent! now throw RuntimeException ("Transient used after persistent! call") instead of silently mutating structure shared with the persisted value. Applied to TransientVector, TransientMapWrapper (hash/array maps + hash-sets) and TransientSortedMap (sorted maps + sets via delegation).
  • Persistent ops on transients stay lenient by design. conj / assoc / dissoc still accept transients — the fast into / group-by paths reduce them over a transient accumulator, so this polymorphism is load-bearing. (Clojure throws here; kept as a :phel divergence in the suite.)
  • nan? / NaN? on nil returns false cleanly instead of leaking is_nan(): Passing null ... is deprecated.
  • take lazy (2-arg) form rejects a non-integer count with a clean InvalidArgumentException instead of leaking Seq::take(): Argument #1 ($n) must be of type int.
  • SequenceGenerator::toIterable() throws a clean InvalidArgumentException for a non-seqable value instead of leaking PHP's Return value must be of type Traversable|array TypeError (covers remove, select-keys and other for / generator consumers).
  • Tests: TransientVectorTest invalidation cases; new tests/phel/core/transient-safety.phel and tests/phel/core/clean-type-errors.phel. CHANGELOG.md + src/php/Lang/CLAUDE.md updated.

Relates to #2223.

Full composer test green; the clojure-test-suite run drops to 0 failures (with #20).

@Chemaclass Chemaclass added the bug Something isn't working label May 29, 2026
@Chemaclass Chemaclass self-assigned this May 29, 2026
…ed PHP type errors

Surfaced by the clojure-test-suite CI job (#2223).

- Transients now guard their lifecycle: a shared TransientStateTrait
  invalidates the collection in persistent(), so conj!/assoc!/dissoc!/
  disj!/pop! (or a second persistent!) after persistent! throw instead of
  silently mutating structure shared with the persisted value. Applied to
  TransientVector, TransientMapWrapper (hash/array maps + hash-sets) and
  TransientSortedMap (sorted maps + sets). Persistent ops on transients stay
  lenient by design: the fast into/group-by paths reduce conj/assoc over a
  transient accumulator.
- nan?/NaN? on nil returns false without leaking is_nan() deprecation.
- take's lazy 2-arg form rejects a non-integer count with a clean
  InvalidArgumentException instead of leaking Seq::take() TypeError.
- SequenceGenerator::toIterable() throws a clean InvalidArgumentException for
  a non-seqable value instead of leaking PHP's iterable return-type TypeError
  (covers remove, select-keys and other for/generator consumers).
@Chemaclass
Chemaclass force-pushed the fix/clojure-suite-clean-errors branch from 59ff326 to 69eeb0f Compare May 29, 2026 20:28
@Chemaclass
Chemaclass merged commit f7724fd into main May 29, 2026
13 checks passed
@Chemaclass
Chemaclass deleted the fix/clojure-suite-clean-errors branch May 29, 2026 20:38
Chemaclass added a commit that referenced this pull request May 29, 2026
The suite now runs 0 failures against main (#2229 fixed the real
bugs; phel-lang/clojure-test-suite#20 marked the intentional :phel
divergences), so drop the soft-gate:

- add a pull_request trigger so the job reports on PRs
- remove continue-on-error (failures now fail the job)
- pin the suite checkout to the merged suite SHA (7d3db18b) for reproducible
  PR/push runs; the nightly cron keeps tracking main to surface upstream drift

Relates to #2223.
Chemaclass added a commit that referenced this pull request May 29, 2026
Condense the Performance and Fixed bullets to terse one-liners, keep
every PR ref, and cite the actual merge PRs (#2224/#2226/#2228/#2229)
instead of the umbrella issue #2223.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant