| Age | Commit message (Collapse) | Author |
|
A proxy stub declares none of the real class's instance properties, and the
`__get`/`__set` forwarders were emitted only for classes that declare a public
one. Every other property access therefore got PHP's own answer for an
undeclared property — null on a read, a dynamic property on a write — so plugin
code reading state the entity holds ran on with null and failed somewhere else
entirely, or not at all.
Emit the forwarders on every root stub, add `__isset`/`__unset` alongside them
so `isset()` cannot answer false silently either, and serve all four from one
dispatcher that answers the state the Rust-side entity exposes and rejects every
other name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
AliasPackage, CompleteAliasPackage and RootAliasPackage now have generated
proxy stubs, so a package handed to a plugin no longer has to be a real
package: it crosses as the stub matching its concrete variant, answers
getAliasOf / setRootPackageAlias / isRootPackageAlias /
hasSelfVersionRequires, and can be constructed from plugin code.
The setters RootPackageInterface declares are routed through that interface
for every root package instead of through the base Package state. Only the
alias variant needs it -- RootAliasPackage overrides all nine to write
through to the package it aliases -- but a real RootPackage delegates to the
same base state either way, so both take one path.
An alias of an alias has no representation here, so narrowing the
constructor argument to a real package is an explicit error rather than a
silent demotion.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
A materialized value used to cross as a constructor call: the class name,
the arguments, and any post-construction setter. Describing a real
instance that way needed a ReflectionProperty read for every field the
class exposes no getter for, and state no constructor takes (an unset
pretty string, a Link built without a pretty constraint) had no faithful
call to describe it at all.
The value now crosses as the object record serialize() writes for it,
which unserialize() revives without running a constructor, so both sides
transfer the state itself instead of a recipe for rebuilding it. The PHP
half keeps only the class list (also the allowed_classes list of every
frame payload) and the UTC rebasing of dates; describe(), build() and the
reflection are gone.
The wire codec gains O: records (PluginValue::PhpObject) and r: back
references, whose resolution reproduces PHP's numbering of every value in
a payload; a cyclic object graph and a PHP reference (R:) are rejected.
Two behaviours change with it: a date crosses carrying timezone_type 3
"UTC" rather than a +00:00 offset, which is what ArrayLoader builds a
release date as, and a Link subclass crosses as a P-table entity instead
of being silently downgraded to a plain Link.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The link getters and setters and the release date accessors were explicit
errors for every non-empty value, because an immutable value has no entity
to point a handle at. They now cross as materialized values: the descriptor
names the real class and the constructor arguments, and each side builds a
genuine instance of its own.
The semver constraint a link holds is encoded structurally rather than
re-parsed from its string form, so the pretty strings and the conjunctive
flag survive the crossing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
An installer that extends LibraryInstaller reaches for the Composer object
graph in ways the proxy did not answer: the download manager and the config,
the writable repository methods, a React promise as its own return value, and
`new Package(...)` from its supports() path.
* `Composer::getConfig`/`getDownloadManager` are dispatched, and both classes
become generated proxy stubs. Their reads and mutators answer from the Rust
entity; the surfaces needing stubs of their own (ConfigSourceInterface,
DownloaderInterface) stay explicit errors.
* The download manager's futures are driven to completion and handed back as
already-settled React promises, since PHP declares a non-nullable
PromiseInterface there. A promise a plugin returns is drained the same way:
settled yields its value, rejected re-raises, pending is an explicit error.
* Proxy stubs now carry the real class's constructor and ask the Rust side to
allocate the entity; reviving a stub for an existing entity binds the handle
without running it. Classes Rust cannot build name themselves in the error.
* The package proxy covers `Package`'s own setters, `CompletePackage`'s
metadata, `RootPackage`'s root-only state, and `BasePackage::$id`.
Link values and release dates still have no wire image, so the methods
carrying them remain explicit errors.
|
|
A plugin can now hand an InstallerInterface implementation to
InstallationManager::addInstaller across the wire, and a legacy
composer-installer package is loaded as one; both are backed by a
PhpInstallerProxy forwarding the whole installer contract to the entity in
the PHP worker. An installer returning a real promise is an explicit error
until promises can cross the boundary.
InstallationManager takes installers as shared handles instead of boxes, so
the object identity removeInstaller and PluginManager's registeredPlugins
compare against survives registration, and holds them in a RefCell:
Installer::run keeps a shared borrow of the manager for the whole run, and a
plugin activated inside it registers its installer from there. The type
cache keys on the installer itself, like upstream, so re-entrant
registration cannot leave a stale index behind. InstallerInterface::supports
is fallible for the same reason getCapabilities and getCommands are: it
answers over RPC.
Cloning a proxy stub clones the Rust-side entity and rebinds the copy to the
fresh handle. Previously only the classes declaring __clone got a throwing
body, and the rest let two stubs share (and twice release) one handle.
Package entities answer with AnyPackage::dup, which already carries
BasePackage::__clone and the RootAliasPackage override; the others are an
explicit error.
The package proxy covers the whole PackageInterface surface; only the link
maps and the release date still lack a wire image for their value objects.
PluginManager gains a test-only seam for the reported Plugin API version,
and the three PluginInstallerTest cases that need it are ported.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
A same-FQCN Composer\Console\Application, hand-written under the new
php/runtime/ tree, hosts CommandProvider commands inside the PHP worker:
PhpCommandProxy overrides run() and forwards the stringified input, so the
real Symfony machinery binds, validates and executes against the live
command object, while help/list render Rust-side from a definition read
back at construction. Reverse \Shirabe\RustCommandStub rows let a plugin
command invoke built-in commands back in the Rust process, keeping every
command on the side whose helper set it was written for.
Composer\EventDispatcher\Event moves from a generated stub to a dual-mode
runtime class: the real BaseCommand::initialize constructs a
PreCommandRunEvent natively in the worker, which a proxy-only constructor
guard rejected. Its PRE_COMMAND_RUN dispatch reaches a new EventDispatcher
stub whose dispatch supports the observably-no-op no-listener case and
fails explicitly otherwise. The stub generator now accepts runtime-provided
classes as stub bases (never as targets) and cross-checks the Application
handoff property table against the real class.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Port Application::getPluginCommands: resolve the local composer with
plugins force-enabled, fall back to Factory::createGlobal, and collect
commands from CommandProvider capability adapters. PhpCommandProxy now
mirrors name/description/aliases/hidden over RPC so `list` output
matches upstream; input definitions remain TODO(plugin).
PhpClass::php_class_name returns an owned String because PHP-backed
proxies only know their class at runtime (the override-skip warning
prints get_class). CommandProvider::getCommands hands out shared
Rc<RefCell> handles since the commands are stored in the application.
Factory::createGlobal now propagates createConfig errors instead of
swallowing them, and Application::getComposer only catches the
exception classes upstream catches, so a ParsingException reaches
doRun's GithubActionError path as in Composer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
getPluginCapability was a no-op returning None. Now it runs the real
flow: class_exists in the worker, new $capabilityClass($ctorArgs) with
the plugin's own phandle spliced in as $ctorArgs['plugin'], both
instanceof checks answered by is_a in the child, and a per-interface
Rust adapter over the resulting entity (CommandProvider and the plain
Capability marker; anything else is an explicit error).
getPluginCapabilities now propagates errors instead of swallowing them.
Capable::get_capabilities widens from IndexMap<String, String> to
IndexMap<String, PhpMixed>: upstream casts the return with (array) and
validates only the queried key, so the narrow type both rejected maps
Composer accepts and made the invalidImplementationClassNames data
provider unrepresentable. The old code also returned ' 0 ' (trimmed to
the falsy '0') as a valid class name where upstream throws; the
rewritten validation follows upstream's empty/is_string/trim sequence.
CommandProvider::get_commands returns BaseCommand adapters whose name is
read back over RPC after the PHP constructor ran configure(); executing
one still needs the PHP-side Symfony Application and stays an explicit
error. The is_array / instanceof BaseCommand checks that upstream's
Application::getPluginCommands performs on the raw getCommands value
live in the adapter, because Vec<Box<dyn BaseCommand>> asserts every
element up front.
Ports testCommandProviderCapability (plugin-v8 end to end against the
real worker) and testQueryingWithInvalidCapabilityClassNameThrows (all
eight provider cases); the two tests that pass a PHPUnit mock plugin
into PHP stay ignored — a Rust-native mock has no PHP-side entity to
cross the boundary as $ctorArgs['plugin'].
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Widen the R table beyond Composer/IO with InstallationManager,
RepositoryManager, repository and package entities, interned by pointer
identity through a shared register_entity. Script events answer
getComposer/getIO, Composer hands out its graph getters, repositories list
their packages as per-variant stubs, package getters cover the
extension-installer surface (getRequires only while empty: the Link
snapshot encoding does not exist yet), and getInstallPath resolves its
package argument back through the R table. Everything else stays an
explicit error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Wire removePlugin to EventDispatcher::removeListener now that subscriber
listeners carry the plugin's P-table handle: PHP's $candidate[0] ===
$listener identity check maps to phandle equality on Callable::PhpMethod.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Wires the addPlugin subscriber branch end to end: EventSubscriberInterface
and Capable become fallible and dyn-compatible (their sole implementor is
the PHP plugin proxy, which answers getSubscribedEvents over RPC), listeners
register as Callable::PhpMethod and are invoked with a per-call event handle,
and the R table now drops entries when a child-side stub destructs. The R
table keeps its IndexMap with monotonically increasing handles, so released
handles are never reused and no generation counter is needed.
Upstream has no subscriber-plugin test, so the path is covered by a
Shirabe-owned fixture exercising all three getSubscribedEvents shapes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Replace the hand-written proxy stubs under crates/shirabe-php-rpc/php/stubs
with output of scripts/plugin-stub-generator, a deterministic emitter that
derives every stub from the Composer checkout and the classifier report.
Anything it cannot faithfully proxy (by-ref/variadic parameters, magic
methods, public properties, diverging omitted overrides, stale stub files,
a STUB_FILES entry missing in lib.rs) fails generation instead of degrading
silently, so future Composer releases surface new members as explicit
errors rather than silent gaps.
Regenerating the stubs also normalizes the hand-written inconsistencies
(uniform guarded constructors, import-based type spellings) and fixes real
gaps the review of the generated diff uncovered: the BaseIO authentication
methods now carry the real class's untyped signatures, and the previously
missing ConsoleIO::sanitize is materialized together with its private
static helper. A cargo test runs generate-stubs --check to keep the
committed stubs, the generator and the embedded list from drifting apart.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
InstallationManager::execute now takes &self (the mock recorder moved
into a RefCell) and its callers hold only shared borrows: plugin
registration inside a batch re-enters the same manager handle through
Composer::getInstallationManager()->getInstallPath(), which panicked
on the RefCell re-borrow under the &mut shape — the same re-entrancy
the repository side already fixed, unreachable from the ported tests
because they call PluginInstaller::install directly like PHPUnit does.
The worker-side InstalledVersions mirror now matches the full tail of
FilesystemRepository::write: unconditional reload plus the
reflection-based selfDir/installedIsLocalDir restore. The previous
class_exists(false) guard rested on a lazy-load assumption that does
not hold in the worker (its real ClassLoader only knows the Composer
checkout's vendor dir, so a later lazy load would read the checkout's
installed.php, not the project's); the mirror is now skipped only when
the class is not autoloadable at all, i.e. no plugin runtime and hence
no observer code. Boot-time seeding stays TODO(plugin).
Also from the review: registered_plugins entries are removed only
after the deactivate/uninstall loop (PHP unsets last, and a throw must
leave the entry observable); extra.class keeps associative-array
values and fails loudly on non-strings instead of silently dropping
them; the two discarded write() results now propagate (they carry the
reload-push failure); register_package's allow-plugins skip message is
DEBUG like the addPlugin side; the loader-eviction divergence of
REGISTERED_LOADERS and the lossy UTF-8 spots carry searchable
markers; the test-only proxy downcast follows the __ naming rule; the
R-table dispatch clones the entity out instead of holding the table
borrow across the handler; the IO/PartialComposer stubs turn a
plugin-side `new NullIO()` into an explicit error instead of an
ArgumentCountError; and the empty() emulation covers float 0.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Implement the remainder of PluginManager::registerPackage: the plugin
autoload map is built by the ported createLoader/parseAutoloads and
served to the worker over the existing reverse-RPC autoloader, files
entries go through a composerRequire-equivalent glue call, and
already-defined classes take the upstream _composer_tmp rename/eval
path. Instantiation uses the new NewObject/CallPhpMethod lanes backed
by a P table in the worker; PhpPluginProxy adapts the resulting handle
to PluginInterface, with $composer/$io exposed to plugin callbacks via
an R table (unsupported methods stay explicit errors). Hand-written
proxy stubs cover Composer, PartialComposer and the IO hierarchy, and
the stub autoloader is re-prepended after loading the Composer PHP
runtime so its vendor autoloader cannot shadow proxied FQCNs.
FilesystemRepository::write now mirrors InstalledVersions::reload into
a running worker (class_exists-guarded, so an unloaded class keeps its
upstream lazy-load behavior), removing the previously undefined
observation window.
The installer pipeline passes the installed repository as a shared
handle instead of a long-lived `&mut dyn`: plugin registration runs
inside InstallationManager::execute and re-enters the same local
repository through the RepositoryManager, which would panic on the
RefCell re-borrow under the old shape.
PluginInterface lifecycle methods now take an owned ComposerHandle
(plugins retain $composer past the call) and return anyhow::Result
(PHP plugin code may throw); the plugin list uses shared ownership so
the identity comparison of removePlugin survives the dual storage in
registeredPlugins, matching PHP reference semantics.
Ports the activate/upgrade/uninstall tests of PluginInstallerTest,
serialized across the shared worker process whose persistent class
table is exactly what exercises the rename path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Implement the two script execution paths that previously stopped at
todo!(): a Class::method listener is invoked as CallStaticMethod with the
event crossing the boundary as a proxy-stub handle, and a Command-class
listener runs inside a throwaway bare Symfony Application hosted by the
worker via a generated snippet, its BufferedOutput written back through
the dispatcher's IO. makeAutoloader is ported for real (canonical-package
hash, setDevMode, buildPackageMap/parseAutoloads/createLoader), and the
class_exists/is_callable/is_a/defined guards now query the worker, whose
script autoloader resolves classes by asking the Rust-side ClassLoader
over the reverse channel. EventInterface gains as_any (the IOInterface
downcast pattern) so the concrete event type is reachable behind the
trait object. Application::do_run now registers ScriptAliasCommand
entries as typed commands, unblocking the run-script --list/alias tests;
the dev-mode-to-generator test is ported with local mockall mocks. The
remaining ignored tests carry re-verified reasons: the listener methods
live on the PHPUnit test class itself (unloadable in the worker), or the
test needs live import of a user PHP Command class into the Application.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
IOInterface::ask/select now return anyhow::Result<PhpMixed>, and
ConsoleIO::ask_question forwards QuestionHelper errors (validator
failures, MissingInputException) instead of collapsing them with
.expect(). In PHP these exceptions propagate from QuestionHelper
through ConsoleIO to the caller, so callers such as UpdateCommand's
interactive package selection must be able to observe them; the
MissingInputException is wrapped with its concrete type preserved so
Application's ExceptionInterface downcast keeps working. All call
sites now propagate with `?` (Perforce::query_p4_user becomes
Result-returning: PHP declares it void but exceptions still escape),
and the previously ignored
test_interactive_mode_throws_if_no_package_entered passes.
ask_confirmation/ask_and_hide_answer still collapse errors; extending
propagation to them is left as TODO(phase-c) pending a decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
regex::Regex::clone() does not share the underlying meta engine's
search-cache pool, so every fresh clone pays a ~10us warmup cost on
its first use. Two changes together eliminate this across nearly all
preg_* call sites:
- A php_regex! macro resolves PHP-style patterns to a per-call-site
&'static regex::Regex (via regex-macro's LazyLock), applied at the
majority of call sites throughout the codebase.
- Call sites still passing dynamic pattern strings go through
PATTERN_CACHE, which now stores Arc<(Regex, bool)> and hands out
Arc::clone()s instead of cloning the Regex itself.
PregPattern::resolve() returns a ResolvedPattern enum (Arc or
'static reference) rather than an owned Regex, so neither path ever
clones the Regex proper.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
|
|
|
|
Composer/PartialComposer exposed its RepositoryManager, InstallationManager,
EventDispatcher, Locker, DownloadManager, AutoloadGenerator and ArchiveManager
as concrete types, but Composer's public setters (setDownloadManager() etc.)
let plugins swap in subclasses. Introduce a *Interface trait per manager and
store each as Rc<RefCell<dyn ...Interface>> so a replacement is honored.
Only Composer's slots and the sinks fed from its accessors become trait
objects; managers injected concretely at construction keep their concrete
references, matching PHP semantics. Fluent setters on the affected classes now
return () and Locker::update_hash is de-generified to a boxed FnOnce so the
traits stay object-safe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
|
|
The List and Array variants of PhpMixed boxed their elements
unnecessarily. Store PhpMixed values directly and update all callers
accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
PluginManager::get_plugins cloned each plugin through a Rust-only
`clone_box` stub (`todo!()`). PHP's getPlugins() returns `$this->plugins`
directly and the objects are shared by reference, so borrow the stored
instances (`&[Box<dyn PluginInterface>]`) instead of cloning. The sole
caller is adjusted and `PluginInterface::clone_box` is removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
|
|
The Preg methods panic on PCRE failure (per the file header rationale),
so their anyhow::Result wrappers never carried an Err.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Wire up ConsoleIO with HelperSet/QuestionHelper, register the
ErrorHandler with the IO instance, and fall back to a default output
in run(). Replace resolved phase-b TODOs across the console, command,
io, factory, installer, dependency_resolver, and util modules; reclassify
the remaining blockers (typed Symfony command registry, stdin resource
caching) as phase-c.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
Extract a superclass-trait EventInterface from the base Event.
pool_builder's PrePoolCreateEvent stays deferred: its constructor needs
owned, non-cloneable Request and repository boxes the builder only holds
by reference (owned-payload blocker). The event is plugin-only, so its
construction is re-tagged TODO(plugin).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
Align the Symfony namespace mapping with the documented convention
(symfony::component::X -> symfony::X) and remove now-unused console
stub files. Update all import paths across the workspace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
Replace TODO(phase-b) placeholders (todo!() and commented-out code)
with real implementations:
- Share JsonFile via Rc<RefCell<JsonFile>> so JsonConfigSource and the
owning command can hold the same instance (base_config_command,
config_command, repository_command, require_command, create_project,
remove_command, factory)
- Change InstallerInterface methods (is_installed, download, prepare,
cleanup, get_install_path) to &mut self so initialize_vendor_dir can
run, propagated to all installer implementations
- Pass io/config/filesystem/process by clone instead of moving or
stubbing (auth_helper, svn_driver, curl_downloader, library_installer)
- Make TransportException Clone and store it by value in VcsRepository
- Clone operations in Transaction sort, root_aliases/temporary_constraints
in RepositorySet::create_pool, and share CompletePackage via handle in
PlatformRepository
- Wire up set_option, set_requires/set_dev_requires, installation manager
setters, BumpCommand::set_composer, and clean_backups/set_local_phar
|
|
Composer does not use its own deprecated APIs internally, so drop their
Rust ports: whole deprecated classes, methods, constants, and field.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
Convert InputInterface and OutputInterface parameters from &dyn/&mut dyn
references to Rc<RefCell<dyn ...>> shared ownership across the command,
console, and IO layers, matching the Phase C shared-ownership approach
already used for IOInterface.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
Change RepositoryInterface and WritableRepositoryInterface read methods
(find_package, find_packages, get_packages, load_packages, search,
get_providers, get_canonical_packages) to take &mut self and return
anyhow::Result, so lazy-loading repositories such as ComposerRepository
can perform fallible I/O and mutate internal state on access. Update all
implementors and call sites to propagate the Result and pass mutable
references.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
|
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
PHP packages have reference semantics, so introduce shared-ownership
handles over an AnyPackage enum (PackageInterfaceHandle and friends)
and replace Box<dyn PackageInterface> throughout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Replace the dyn ConstraintInterface trait objects with an AnyConstraint
enum closing over its four implementors (Simple, Multi, MatchAll,
MatchNone), mirroring the earlier Rule enum conversion. Rename
constraint.rs to simple_constraint.rs to match the renamed Constraint
type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Model PHP's `Composer extends PartialComposer` as a PartialOrFullComposer
enum and merge partial_composer.rs into composer.rs. Introduce
ComposerHandle / PartialComposerHandle (plus their Weak variants) so the
graph can be shared, and build it at once with Rc::new_cyclic in the
factory to resolve the back-reference cycles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
|
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
|