Skip to content

0.2.1#18

Merged
math3usmartins merged 8 commits into
mainfrom
0.2.x
Jun 17, 2026
Merged

0.2.1#18
math3usmartins merged 8 commits into
mainfrom
0.2.x

Conversation

@math3usmartins

Copy link
Copy Markdown
Member

No description provided.

math3usmartins and others added 8 commits June 14, 2026 17:23
Each HttpError<X> monomorphizes into a distinct concrete class (extending
the native exception, implementing the original name as a marker
interface), so a `catch (HttpError<X> $e)` clause is just a type-hint
position that CallSiteRewriter rewrites to the specialized FQN and PHP's
native catch discriminates by concrete class. This worked as emergent
behavior with no dedicated production code; add a fixture + integration
test so a future narrowing of CallSiteRewriter or the scanner's
type-hint detection cannot regress it silently.

Covers specific-specialization discrimination (the textually-first arm is
skipped), bare-marker catch-all, and a union of two specializations, at
runtime plus a rewrite snapshot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The transpiler passes plain PHP through unchanged, so `|>` support hinges
purely on the parser's target version. Keep ApplicationConsole on
createForHostVersion() (supported runtime is ^8.4): the host accepts 8.4
syntax on 8.4 and 8.5 syntax on 8.5, with no need to force a newer grammar.

Lock the pipe operator with an integration test that mirrors the production
wiring and round-trips `|>` (alone and beside a generic specialization).
Tag it `@group php85` + `#[RequiresPhp('>= 8.5.0')]` so it skips on 8.4 and
runs only under an 8.5 runtime.

- Makefile: test/unit excludes php85; new test/unit/php85 runs only it.
- CI: phpunit job is now a matrix (8.4 -> full suite minus php85,
  8.5 -> only php85).
- CONTRIBUTING: document the split and the new target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lets contributors without a local PHP 8.5 run the `@group php85` tests:
`docker compose run --rm php85 make test/unit/php85`. The service builds a
lean php:8.5-cli-alpine image (composer + git/unzip/make, no coverage
drivers since those tests run coverage-off). Verified green on PHP 8.5.7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ntion

Replace the inline-source pipe test with a tracked `pipe_operator` fixture
(Box.xphp generic + Use.xphp exercising a standalone `|>` chain and one
beside a turbofish call site), asserted via SnapshotHash like the other
compile fixtures. The whole-file snapshot locks the emitted bytes -- pipe
operator preserved verbatim, turbofish rewritten to the monomorphized FQN.

Still `@group php85` + `#[RequiresPhp('>= 8.5.0')]`: skips on 8.4, runs on
8.5. Verified green in the php85 container on PHP 8.5.7 (real comparison,
not update mode) and skipping cleanly on the 8.4 host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ic catch + 8.5 pass-through

Correctness fixes (all verified by compiling/running the snippets):
- README: the flagship Collection<T> example used a variadic promoted
  property (`public T ...$items`), which PHP rejects ("Cannot declare
  variadic promoted property") -- the headline snippet never ran.
  Replace with an add()/first() shape that compiles and prints "Alice".
- how-it-works: the on-disk cache path is `<cache>/Generated/...`, not
  `XPHP/Generated/...` (XPHP\Generated\ is only the namespace). Fix the
  prose and the mermaid node; they contradicted the file's own Stage 5.
- pseudo-types: "See also" cited box_generic as using `self` (it uses
  `T`); point at the real self/static fixtures instead.

Coverage gaps:
- New docs/syntax/exceptions.md: generic exceptions and catch by
  specialization (turbofish throw, per-specialization arms, bare marker
  catch, union catch), with a tested fixture reference. Linked from the
  syntax index table and quick-reference card.
- caveats: document that newer-PHP syntax (e.g. the 8.5 pipe operator)
  needs a host runtime that can parse it, since the parser uses the host
  PHP version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lasses

A specialized generic is lifted into a fresh XPHP\Generated\... namespace, but
bare class/interface names that relied on the source file's `use` imports
(extends Countable, implements IteratorAggregate, new ArrayIterator,
catch (SomeException), typed properties/params/returns) were copied verbatim.
In the relocated namespace they resolved to a non-existent
XPHP\Generated\...\<Name> and threw "Interface not found" at autoload time —
silent at compile time, fatal on first load. Only type-param and generic-args
names were ever fully-qualified; plain class references were neither.

Resolve at parse time, swap on the clone:
- XphpSourceParser::resolveAndAttach now tags bare class-name Names in
  class-name positions (extends/implements/new/instanceof/catch/static-access/
  param·return·property·const type hints) with their resolved FQN in a new
  ATTR_RESOLVED_FQN attribute, reusing the existing NamespaceContext. The node
  is not mutated, so emitted user files stay byte-identical. A shouldQualify
  guard skips already-FQ names, generic-marker names, enclosing type-params and
  scalars/self/parent/static; function and const Names are never visited, so
  PHP's global fallback is preserved.
- Specializer fully-qualifies a tagged Name only on the relocated clone, so the
  generated class references every non-generic symbol by FQN and loads cleanly.

Regression coverage: builtin_interface_via_use fixture exercising every position
(built-in extends/implements via use, same-namespace extends, new, catch,
instanceof, class-const fetch, nullable/union/closure-return type hints, an
enum-typed constant) with a runtime verify asserting the specialized class
autoloads and satisfies \Countable/\IteratorAggregate/\Traversable, plus a
no-churn guard proving a non-generic class still emits bare imported names.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bin/xphp bootstrapped Composer with a single hardcoded
`require dirname(__DIR__) . '/vendor/autoload.php'`. That path only exists in a
standalone checkout. When xphp-lang/xphp is installed as a Composer dependency
the package has no vendor/ of its own — the autoloader lives in the consuming
project's vendor/autoload.php, three levels up from bin/ — so `vendor/bin/xphp`
died at startup with "Failed opening required ...". It only worked in the
package's own repo, masking the bug.

Replace the require with the standard multi-candidate bootstrap: probe the
Composer bin-proxy global ($GLOBALS['_composer_autoload_path']), the
dependency path (dirname(__DIR__, 3) . '/autoload.php'), then the standalone
path (dirname(__DIR__) . '/vendor/autoload.php'), requiring the first that
exists; otherwise print a clear message to STDERR and exit(1). The is_string
guard keeps it safe under strict_types, and the standalone candidate is kept
last because it is also the PHAR's only working autoloader path.

Regression coverage: test/Console/BinAutoloadBootstrapTest.php drives the binary
via proc_open with separate stdout/stderr pipes — standalone checkout, a
simulated consumer layout (fake vendor/xphp-lang/xphp/bin/xphp with no nested
vendor/), and the no-autoloader error path asserting exit 1 + the diagnostic on
STDERR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@math3usmartins math3usmartins requested a review from a team June 17, 2026 18:45
@math3usmartins math3usmartins merged commit b39788e into main Jun 17, 2026
4 checks passed
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.

1 participant