Skip to content

Releases: nerima-lisp/cl-cc-php

Release list

v0.1.1

Choose a tag to compare

@github-actions github-actions released this 31 Jul 05:10
v0.1.1
6f472d7

Changed

  • t/runtime-builtins-io-objects-php85-test.lisp grew back over the
    500-line limit (511 lines) as later work in this session's own
    history added more tests to it. Its six Dom\Element/
    Dom\ParentNode/Dom\HTMLDocument tests split out to a new
    t/runtime-builtins-io-dom-php85-test.lisp — the same size-driven,
    topic-scoped split already applied once before in this file's history
    to pull out runtime-builtins-io-uri-php85-test.lisp. A fresh sweep
    of every file's current line count (wc -l src/*.lisp t/*.lisp,
    since several files grew substantially across this session's edits)
    found this as the only file over the limit; no src/ file is over
    500 lines (runtime-builtins-core.lisp sits exactly at it).
  • Every sbcl --script invocation in flake.nix (checks.default,
    packages.coverage, and apps.test/nix run .#test) now runs under
    timeout (600s for the plain test suite and the interactive nix run
    entry point, 900s for coverage's slower force-recompile-then-run), so a
    hang — an infinite loop, a VM/fiber deadlock — fails the build loudly
    instead of running until CI's own outer timeout eventually kills it.
    These were the only unguarded command invocations in the repository: the
    GitHub Actions workflows already carry timeout-minutes on every job, and
    there is no other place this codebase shells out to a subprocess.
  • json_validate (%php-json-validate) now delegates to
    cl-json-kit's
    json-kit:parse, a dependency-free, RFC 8259-conformance-tested JSON
    reader (the full JSONTestSuite corpus: 95/95 must-accept, 188/188
    must-reject), instead of a hand-rolled strict parser added earlier in this
    same series of changes. That hand-rolled version already regression-tested
    clean, but a purpose-built, conformance-tested library is strictly more
    correct with less code to maintain — e.g. it correctly rejects a leading
    zero before more digits ("01"), a real RFC 8259 rule the hand-rolled
    digit-scanner missed. This is cl-cc-php's first adoption of a
    nerima-lisp sibling package as a genuine runtime dependency (cl-weave,
    cl-prolog, and cl-parser-kit are test-only): it is now a :depends-on
    of the shipped cl-cc-php system, not just cl-cc-php/test, wired into
    flake.nix/run-tests.lisp/coverage.lisp the same way the cl-cc
    subsystems are, and documented in docs/src/installation.md's dependency
    table. %php-json-decode/%php-json-encode deliberately still use the
    original hand-rolled reader/writer: PHP's json_decode needs values
    translated into this runtime's own %php-array/+php-null+/PHP-boolean
    representation, which is a real, non-trivial mapping — not the "weird
    unnecessary adapter" this project avoids — and migrating it is a larger,
    separately-scoped change than json_validate's zero-value-model swap.

Fixed

  • final class Foo { ... } had the exact same gap abstract class
    did (see below): no registered top-level statement parser of its
    own, so it fell through to generic expression-statement parsing and
    errored on the bare final keyword. Found by re-checking the
    abstract-class fix's own investigation notes, which had already
    identified final as sharing the same gap without fixing it at the
    time. Fixed the same way, registering a :final statement parser
    mirroring :abstract's. As with abstract, this only makes the
    class parse — nothing tracks "is this class final" past parsing, so
    extending a final class is not rejected either, confirmed by a test
    documenting that current behavior rather than implementing the
    enforcement.
  • An enum using a trait (enum S { use Greetable; case A; }) failed
    outright when a merged-in method was actually called. Found
    immediately after the class-trait fix above by applying the same
    "does this related feature have the same class of gap" check to
    enums — t/parser-class-test.lisp's
    php-parser-enum-implements-methods-traits-and-constants test
    combines enum ... { use HasLabels; ... } but, like the class-trait
    tests before the fix above, only checks the parser's raw AST shape.
    Two separate, non-obvious causes, found by reading the actual error
    at each step rather than assuming the first fix covered enums too:
    (1) %php-merge-all-trait-members's original (dolist (stmt stmts) (when (ast-defclass-p stmt) ...)) walk never found an enum's
    ast-defclass at all — %php-parse-classlike wraps an enum's class
    definition in an ast-progn alongside a %php-enum-finalize call,
    unlike a plain class, whose ast-defclass is a bare top-level form —
    so the merge silently never ran for any enum. Fixed by also checking
    one level into an ast-progn's forms. (2) Once the merge did run, a
    merged-in method still failed: enum methods dispatch through the
    shared enum class object and need :allocation :class, not the
    ordinary per-instance :allocation :instance a trait method carries
    from where it was declared — %php-parse-classlike already applies
    this fixup to methods declared directly in an enum body, but a method
    merged in later, by a separate pass, never went through that code
    path. Fixed by applying the same fixup to merged-in methods when the
    target is an enum.
  • abstract class Foo { abstract function m(): T; ... } failed to parse
    at all — PHP parse error: unexpected keyword :ABSTRACT in expression. :readonly has its own top-level statement parser
    (readonly class Foo {...}, src/parser-class.lisp) that requires
    class to immediately follow and delegates to
    %php-parse-class-decl; :abstract (and :final) had no such
    registration, so a leading abstract before class fell through to
    generic expression-statement parsing and errored on the bare keyword.
    Member-level abstract function ...; inside a class/trait/interface
    body already worked (%php-parse-visibility-modifiers already
    recognized :abstract there) — only the class-level modifier itself
    was missing. Found while investigating whether abstract classes work
    at all (no test anywhere exercised the class-level abstract
    modifier before this). Fixed by registering a :abstract statement
    parser mirroring :readonly's exact pattern. Not fixed, and
    confirmed as a separate, real gap by the same investigation:

    instantiating an abstract class directly (new Shape() where Shape
    is abstract) is not rejected — nothing currently tracks "is this
    class abstract" as metadata past parsing, and no instantiation-time
    check exists to consult it, unlike real PHP's fatal "Cannot
    instantiate abstract class" error. Documented via a test asserting
    the current (unenforced) behavior rather than implemented, given the
    additional scope (tracking abstractness through to whatever lowers
    new) this investigation did not cover.
  • PHP trait methods did not actually work at all. The simplest
    possible case — trait Greetable { function greet() { return 'hi'; } } class C { use Greetable; } (new C())->greet() — failed outright
    with The slot CL-CC/PHP::GREET is missing from the object of class CL-CC/PHP::C. Every trait test before this fix either checked the
    parser's raw AST shape directly (t/parser-trait-test.lisp, which
    never compiled or ran anything) or used empty traits and checked
    only class_uses() reflection (which did work — trait names were
    always tracked correctly). None ever called an actual trait method,
    which is the entire point of a trait. Root cause: use TraitA;
    inside a class body produced only an internal :PHP-TRAIT-USE marker
    slot-def (%php-parse-use-trait-stmt, src/parser-trait.lisp) that
    nothing in class lowering ever consulted — confirmed with grep,
    there was no reference to :php-trait-use/:php-trait-names
    anywhere outside parser-trait.lisp, and *php-trait-registry*
    (trait name → member list, populated by trait declarations) had no
    reader at all. Fixed with a new whole-program pass,
    %php-merge-all-trait-members (run from parse-php-source, after
    every top-level form — including every trait declaration — is
    parsed, so a class using a trait declared later in the same file
    still resolves correctly): it replaces each class's trait-use marker
    with the real member slot-defs of the traits it names. A member name
    defined by only one used trait is copied in directly. A member name
    defined by more than one used trait is resolved by a matching
    insteadof clause (TraitA::member insteadof TraitB; keeps TraitA's
    version); a collision insteadof does not resolve now signals a
    clear error — matching real PHP's fatal "has not been applied,
    because there are collisions" — instead of silently picking whichever
    trait happened to be listed first, which is what would have happened
    with no conflict handling at all. t/parser-trait-test.lisp's ten
    parser-level tests (which check that use ... { insteadof/as }
    syntax parses into the correct raw metadata) needed updating to read
    that metadata from *php-trait-applications* instead of the now-
    merged-away marker slot-def — their original intent (verify parsing
    captured the right insteadof/alias data) is unchanged, only the
    channel they read it through. as aliasing is also implemented, by
    %php-apply-trait-aliases: TraitA::method as alias; adds a renamed
    copy of the resolved member under the alias name — purely additive,
    the original name stays callable too, matching real PHP — and
    method as protected; (visibility only, no rename) replaces the
    merged member's :php-modifiers visibility keyword in place. Both
    verified end-to-end. (Not verified: whether the changed visibility is
    actually enforced against external callers — that is a separate
    mechanism this investigation did not check.)
  • Prefix ++/-- on a non-assignable operand (anything besides a
    `$v...
Read more