diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-28 22:43:11 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-28 22:43:43 +0900 |
| commit | eea4efe87e455742ec17881ee93d8095925e8516 (patch) | |
| tree | 6d242f4fdd0bf32f0494a6fbbd62bce9ed6e1dc7 /crates/shirabe/src/repository | |
| parent | cc5d73c05a0abca2eebcc8a6afa0b1543ee49850 (diff) | |
| download | php-shirabe-eea4efe87e455742ec17881ee93d8095925e8516.tar.gz php-shirabe-eea4efe87e455742ec17881ee93d8095925e8516.tar.zst php-shirabe-eea4efe87e455742ec17881ee93d8095925e8516.zip | |
refactor(repository): introduce Rc<RefCell<_>> handles for repositories
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/repository')
19 files changed, 423 insertions, 100 deletions
diff --git a/crates/shirabe/src/repository/array_repository.rs b/crates/shirabe/src/repository/array_repository.rs index 6467c14..3a05513 100644 --- a/crates/shirabe/src/repository/array_repository.rs +++ b/crates/shirabe/src/repository/array_repository.rs @@ -2,6 +2,7 @@ use std::any::Any; use std::cell::RefCell; +use std::rc::Weak; use anyhow::Result; use indexmap::IndexMap; @@ -17,7 +18,7 @@ use crate::package::version::StabilityFilter; use crate::package::version::VersionParser; use crate::repository::{ AbandonedInfo, FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, - SearchResult, + RepositoryInterfaceHandle, RepositoryInterfaceWeakHandle, SearchResult, }; /// A repository implementation that simply stores packages in an array @@ -29,6 +30,10 @@ pub struct ArrayRepository { /// @var ?array<BasePackage> indexed by package unique name and used to cache hasPackage calls pub(crate) package_map: RefCell<Option<IndexMap<String, BasePackageHandle>>>, + + /// Weak reference to the outermost repository handle wrapping this `ArrayRepository`, + /// injected via `set_self_handle`. Used to wire package -> repository back-references. + self_weak: RefCell<Option<RepositoryInterfaceWeakHandle>>, } impl ArrayRepository { @@ -37,6 +42,7 @@ impl ArrayRepository { let this = Self { packages: RefCell::new(None), package_map: RefCell::new(None), + self_weak: RefCell::new(None), }; for package in packages { this.add_package(package)?; @@ -49,8 +55,12 @@ impl ArrayRepository { if self.packages.borrow().is_none() { self.initialize(); } - // TODO(phase-b): pass a reference to self, not a clone - package.set_repository(todo!("self as Box<dyn RepositoryInterface>"))?; + // PHP: $package->setRepository($this). The back-reference is wired only once this + // repository (or its outermost wrapper) has been put behind a RepositoryInterfaceHandle; + // packages added during construction are wired later in set_self_handle. + if let Some(handle) = self.self_weak.borrow().as_ref().and_then(Weak::upgrade) { + package.set_repository(RepositoryInterfaceHandle::from_rc(handle))?; + } let aliased_package: Option<PackageHandle> = package.as_alias().map(|alias| alias.get_alias_of()); @@ -59,13 +69,13 @@ impl ArrayRepository { .borrow_mut() .as_mut() .unwrap() - .push(package.into()); + .push(package.clone()); if let Some(aliased_package) = aliased_package { // PHP: if ($aliasedPackage->getRepository() === null) $this->addPackage($aliasedPackage); - // TODO(phase-c): the handle does not expose get_repository (a `RefCell`-borrowed - // back-reference); this needs repository back-references on handles. - let _ = aliased_package; + if aliased_package.get_repository().is_none() { + self.add_package(aliased_package.into())?; + } } // invalidate package map cache @@ -438,4 +448,23 @@ impl RepositoryInterface for ArrayRepository { fn as_any(&self) -> &dyn std::any::Any { self } + + fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + *self.self_weak.borrow_mut() = Some(weak.clone()); + // Wire back-references for packages added before this repository was wrapped in a handle + // (e.g. those passed to ArrayRepository::new). Reads the field directly to avoid + // triggering lazy initialization in wrapper repositories. + if let Some(handle) = weak.upgrade() { + let handle = RepositoryInterfaceHandle::from_rc(handle); + let pkgs: Vec<BasePackageHandle> = self + .packages + .borrow() + .as_ref() + .map(|v| v.clone()) + .unwrap_or_default(); + for pkg in pkgs { + let _ = pkg.set_repository(handle.clone()); + } + } + } } diff --git a/crates/shirabe/src/repository/composer_repository.rs b/crates/shirabe/src/repository/composer_repository.rs index ddf8e31..9931a73 100644 --- a/crates/shirabe/src/repository/composer_repository.rs +++ b/crates/shirabe/src/repository/composer_repository.rs @@ -38,6 +38,8 @@ use crate::repository::ConfigurableRepositoryInterface; use crate::repository::LoadPackagesResult; use crate::repository::PlatformRepository; use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; +use crate::repository::RepositoryInterfaceWeakHandle; use crate::repository::RepositorySecurityException; use crate::repository::{PartialOrSecurityAdvisory, SecurityAdvisoryResult}; use crate::repository::{SEARCH_FULLTEXT, SEARCH_VENDOR}; @@ -78,6 +80,9 @@ pub struct ProviderListingEntry { #[derive(Debug)] pub struct ComposerRepository { inner: ArrayRepository, + /// Weak reference to the outermost repository handle wrapping this `ComposerRepository`, + /// injected via `set_self_handle`. Used to wire package -> repository back-references. + self_weak: std::cell::RefCell<Option<RepositoryInterfaceWeakHandle>>, /// @phpstan-var array{url: string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} repo_config: IndexMap<String, PhpMixed>, options: IndexMap<String, PhpMixed>, @@ -274,6 +279,7 @@ impl ComposerRepository { let mut this = Self { inner: inner?, + self_weak: std::cell::RefCell::new(None), repo_config, options, url, @@ -1745,13 +1751,20 @@ impl ComposerRepository { versions_to_load.values().cloned().collect(); let loaded_packages = self.create_packages_flat(versions_to_load_vec, packages_source)?; let uids: Vec<String> = versions_to_load.keys().cloned().collect(); + let self_handle = self.self_handle(); for (index, package) in loaded_packages.into_iter().enumerate() { - // TODO(phase-c): wire the repository back-reference onto the shared package handle. + if let Some(h) = self_handle.as_ref() { + package.set_repository(h.clone())?; + } let uid = &uids[index]; if let Some(alias) = package.as_alias() { let aliased = alias.get_alias_of(); + if let Some(h) = self_handle.as_ref() { + let aliased_pkg: PackageInterfaceHandle = aliased.clone().into(); + aliased_pkg.set_repository(h.clone())?; + } result.insert(uid.clone(), aliased.into()); result.insert(format!("{}-alias", uid), package); } else { @@ -1784,6 +1797,21 @@ impl ComposerRepository { self.inner.add_package(package.into()); } + /// Forwards the outermost handle's weak to the inner `ArrayRepository` so that packages added + /// through `add_package` get the back-reference to this `ComposerRepository` wrapper. + pub fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + *self.self_weak.borrow_mut() = Some(weak.clone()); + self.inner.set_self_handle(weak); + } + + fn self_handle(&self) -> Option<RepositoryInterfaceHandle> { + self.self_weak + .borrow() + .as_ref() + .and_then(std::rc::Weak::upgrade) + .map(RepositoryInterfaceHandle::from_rc) + } + /// @param packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only packages matching it will be loaded fn load_async_packages( &mut self, @@ -1976,11 +2004,18 @@ impl ComposerRepository { let loaded_packages: Vec<BasePackageHandle> = ComposerRepository::create_packages_static(versions_to_load, packages_source)?; + let self_handle = self.self_handle(); for package in loaded_packages.into_iter() { - // TODO(phase-c): wire the repository back-reference onto the shared package handle. + if let Some(h) = self_handle.as_ref() { + package.set_repository(h.clone())?; + } let hash_c = package.ptr_id().to_string(); if let Some(alias) = package.as_alias() { let aliased = alias.get_alias_of(); + if let Some(h) = self_handle.as_ref() { + let aliased_pkg: PackageInterfaceHandle = aliased.clone().into(); + aliased_pkg.set_repository(h.clone())?; + } let aliased_hash = aliased.ptr_id().to_string(); if !packages.contains_key(&aliased_hash) { packages.insert(aliased_hash, aliased.into()); diff --git a/crates/shirabe/src/repository/composite_repository.rs b/crates/shirabe/src/repository/composite_repository.rs index 12f1837..6c167f2 100644 --- a/crates/shirabe/src/repository/composite_repository.rs +++ b/crates/shirabe/src/repository/composite_repository.rs @@ -8,16 +8,17 @@ use shirabe_semver::constraint::AnyConstraint; use crate::package::BasePackageHandle; use crate::package::PackageInterfaceHandle; use crate::repository::{ - FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, + RepositoryInterfaceHandle, SearchResult, }; #[derive(Debug)] pub struct CompositeRepository { - repositories: Vec<Box<dyn RepositoryInterface>>, + repositories: Vec<RepositoryInterfaceHandle>, } impl CompositeRepository { - pub fn new(repositories: Vec<Box<dyn RepositoryInterface>>) -> Self { + pub fn new(repositories: Vec<RepositoryInterfaceHandle>) -> Self { let mut this = Self { repositories: vec![], }; @@ -27,7 +28,7 @@ impl CompositeRepository { this } - pub fn get_repositories(&self) -> &Vec<Box<dyn RepositoryInterface>> { + pub fn get_repositories(&self) -> &Vec<RepositoryInterfaceHandle> { &self.repositories } @@ -39,10 +40,17 @@ impl CompositeRepository { } } - pub fn add_repository(&mut self, repository: Box<dyn RepositoryInterface>) { - if let Some(composite) = repository.as_any().downcast_ref::<CompositeRepository>() { - for repo in composite.get_repositories() { - self.repositories.push(repo.clone_box()); + pub fn add_repository(&mut self, repository: RepositoryInterfaceHandle) { + let nested: Option<Vec<RepositoryInterfaceHandle>> = { + let repo_ref = repository.borrow(); + repo_ref + .as_any() + .downcast_ref::<CompositeRepository>() + .map(|composite| composite.get_repositories().clone()) + }; + if let Some(nested) = nested { + for repo in nested { + self.repositories.push(repo); } } else { self.repositories.push(repository); @@ -163,6 +171,6 @@ impl RepositoryInterface for CompositeRepository { } fn as_any(&self) -> &dyn std::any::Any { - todo!() + self } } diff --git a/crates/shirabe/src/repository/filesystem_repository.rs b/crates/shirabe/src/repository/filesystem_repository.rs index 8232ecf..a721b4f 100644 --- a/crates/shirabe/src/repository/filesystem_repository.rs +++ b/crates/shirabe/src/repository/filesystem_repository.rs @@ -78,6 +78,10 @@ impl FilesystemRepository { self.dev_mode } + pub fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } + pub fn get_repo_name(&self) -> String { format!("file ({})", self.file.get_path()) } diff --git a/crates/shirabe/src/repository/filter_repository.rs b/crates/shirabe/src/repository/filter_repository.rs index 2a339f5..ecf1f0f 100644 --- a/crates/shirabe/src/repository/filter_repository.rs +++ b/crates/shirabe/src/repository/filter_repository.rs @@ -5,7 +5,8 @@ use crate::package::PackageInterfaceHandle; use crate::package::base_package::{self}; use crate::repository::{AdvisoryProviderInterface, SecurityAdvisoryResult}; use crate::repository::{ - FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, + RepositoryInterfaceHandle, SearchResult, }; use anyhow::Result; use indexmap::IndexMap; @@ -18,12 +19,12 @@ pub struct FilterRepository { only: Option<String>, exclude: Option<String>, canonical: bool, - repo: Box<dyn RepositoryInterface>, + repo: RepositoryInterfaceHandle, } impl FilterRepository { pub fn new( - repo: Box<dyn RepositoryInterface>, + repo: RepositoryInterfaceHandle, options: IndexMap<String, PhpMixed>, ) -> Result<Self> { let mut only: Option<String> = None; @@ -126,8 +127,8 @@ impl FilterRepository { }) } - pub fn get_repository(&self) -> &dyn RepositoryInterface { - self.repo.as_ref() + pub fn get_repository(&self) -> RepositoryInterfaceHandle { + self.repo.clone() } fn is_allowed(&self, name: &str) -> bool { @@ -254,7 +255,13 @@ impl RepositoryInterface for FilterRepository { } fn as_advisory_provider(&self) -> Option<&dyn AdvisoryProviderInterface> { - self.repo.as_advisory_provider() + // FilterRepository is itself an advisory provider (it filters), but only meaningfully so + // when the wrapped repository provides advisories. + if self.repo.borrow().as_advisory_provider().is_some() { + Some(self) + } else { + None + } } fn as_any(&self) -> &dyn std::any::Any { @@ -264,7 +271,8 @@ impl RepositoryInterface for FilterRepository { impl AdvisoryProviderInterface for FilterRepository { fn has_security_advisories(&self) -> bool { - if let Some(advisory_repo) = self.repo.as_advisory_provider() { + let repo = self.repo.borrow(); + if let Some(advisory_repo) = repo.as_advisory_provider() { advisory_repo.has_security_advisories() } else { false @@ -276,7 +284,8 @@ impl AdvisoryProviderInterface for FilterRepository { mut package_constraint_map: IndexMap<String, AnyConstraint>, allow_partial_advisories: bool, ) -> anyhow::Result<SecurityAdvisoryResult> { - if let Some(advisory_repo) = self.repo.as_advisory_provider() { + let repo = self.repo.borrow(); + if let Some(advisory_repo) = repo.as_advisory_provider() { package_constraint_map.retain(|name, _| self.is_allowed(name)); advisory_repo.get_security_advisories(package_constraint_map, allow_partial_advisories) } else { diff --git a/crates/shirabe/src/repository/handle.rs b/crates/shirabe/src/repository/handle.rs new file mode 100644 index 0000000..f7a4104 --- /dev/null +++ b/crates/shirabe/src/repository/handle.rs @@ -0,0 +1,174 @@ +//! Shared handle over `RepositoryInterface`. + +use std::cell::{Ref, RefCell, RefMut}; +use std::rc::{Rc, Weak}; + +use indexmap::IndexMap; +use shirabe_php_shim::Countable; +use shirabe_semver::constraint::AnyConstraint; + +use crate::package::BasePackageHandle; +use crate::package::PackageInterfaceHandle; +use crate::repository::{ + FindPackageConstraint, InstalledRepositoryInterface, LoadPackagesResult, ProviderInfo, + RepositoryInterface, SearchResult, WritableRepositoryInterface, +}; + +/// Shared reference to a repository. Corresponds to PHP `RepositoryInterface`. +#[derive(Debug, Clone)] +pub struct RepositoryInterfaceHandle(Rc<RefCell<dyn RepositoryInterface>>); + +/// Weak back-reference held by packages to the repository that owns them. +pub type RepositoryInterfaceWeakHandle = Weak<RefCell<dyn RepositoryInterface>>; + +impl RepositoryInterfaceHandle { + /// Wraps a concrete repository in a shared handle and injects its own weak reference so that + /// `add_package` can wire package -> repository back-references (PHP `setRepository($this)`). + pub fn new<T: RepositoryInterface + 'static>(repository: T) -> Self { + let rc: Rc<RefCell<dyn RepositoryInterface>> = Rc::new(RefCell::new(repository)); + rc.borrow().set_self_handle(Rc::downgrade(&rc)); + Self(rc) + } + + pub fn from_rc(rc: Rc<RefCell<dyn RepositoryInterface>>) -> Self { + Self(rc) + } + + pub fn as_rc(&self) -> &Rc<RefCell<dyn RepositoryInterface>> { + &self.0 + } + + pub fn downgrade(&self) -> RepositoryInterfaceWeakHandle { + Rc::downgrade(&self.0) + } + + pub fn borrow(&self) -> Ref<'_, dyn RepositoryInterface> { + self.0.borrow() + } + + pub fn borrow_mut(&self) -> RefMut<'_, dyn RepositoryInterface> { + self.0.borrow_mut() + } + + /// PHP `===` (reference identity). + pub fn ptr_eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } + + /// Stable identity usable as a map key (PHP `spl_object_hash`). + pub fn ptr_id(&self) -> usize { + Rc::as_ptr(&self.0) as *const () as usize + } + + /// PHP `instanceof T` for a concrete repository type. Keeps the `RefCell` borrow internal. + pub fn is<T: RepositoryInterface + 'static>(&self) -> bool { + self.0.borrow().as_any().is::<T>() + } + + pub fn count(&self) -> i64 { + self.0.borrow().count() + } + + pub fn get_repo_name(&self) -> String { + self.0.borrow().get_repo_name() + } + + pub fn get_packages(&self) -> Vec<BasePackageHandle> { + self.0.borrow().get_packages() + } + + pub fn has_package(&self, package: PackageInterfaceHandle) -> bool { + self.0.borrow().has_package(package) + } + + pub fn find_package( + &self, + name: &str, + constraint: FindPackageConstraint, + ) -> Option<BasePackageHandle> { + self.0.borrow().find_package(name, constraint) + } + + pub fn find_packages( + &self, + name: &str, + constraint: Option<FindPackageConstraint>, + ) -> Vec<BasePackageHandle> { + self.0.borrow().find_packages(name, constraint) + } + + pub fn load_packages( + &self, + package_name_map: IndexMap<String, Option<AnyConstraint>>, + acceptable_stabilities: IndexMap<String, i64>, + stability_flags: IndexMap<String, i64>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, + ) -> LoadPackagesResult { + self.0.borrow().load_packages( + package_name_map, + acceptable_stabilities, + stability_flags, + already_loaded, + ) + } + + pub fn search(&self, query: String, mode: i64, r#type: Option<String>) -> Vec<SearchResult> { + self.0.borrow().search(query, mode, r#type) + } + + pub fn get_providers(&self, package_name: String) -> IndexMap<String, ProviderInfo> { + self.0.borrow().get_providers(package_name) + } + + // --- InstalledRepositoryInterface helpers (valid only when the wrapped repository is one) --- + + pub fn is_fresh(&self) -> bool { + self.0 + .borrow() + .as_installed_repository_interface() + .map_or(false, |r| r.is_fresh()) + } + + pub fn get_dev_mode(&self) -> Option<bool> { + self.0 + .borrow() + .as_installed_repository_interface() + .and_then(|r| r.get_dev_mode()) + } + + pub fn get_canonical_packages(&self) -> Vec<PackageInterfaceHandle> { + self.0 + .borrow() + .as_installed_repository_interface() + .map(|r| r.get_canonical_packages()) + .unwrap_or_default() + } + + pub fn get_dev_package_names(&self) -> Vec<String> { + self.0 + .borrow() + .as_installed_repository_interface() + .map(|r| r.get_dev_package_names().clone()) + .unwrap_or_default() + } + + pub fn set_dev_package_names(&self, dev_package_names: Vec<String>) { + if let Some(r) = self.0.borrow_mut().as_installed_repository_interface_mut() { + r.set_dev_package_names(dev_package_names); + } + } +} + +impl PartialEq for RepositoryInterfaceHandle { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for RepositoryInterfaceHandle {} + +impl std::hash::Hash for RepositoryInterfaceHandle { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.ptr_id().hash(state); + } +} diff --git a/crates/shirabe/src/repository/installed_array_repository.rs b/crates/shirabe/src/repository/installed_array_repository.rs index ca2d8c7..1520497 100644 --- a/crates/shirabe/src/repository/installed_array_repository.rs +++ b/crates/shirabe/src/repository/installed_array_repository.rs @@ -133,7 +133,18 @@ impl RepositoryInterface for InstalledArrayRepository { fn as_advisory_provider(&self) -> Option<&dyn AdvisoryProviderInterface> { None } + fn as_installed_repository_interface(&self) -> Option<&dyn InstalledRepositoryInterface> { + Some(self) + } + fn as_installed_repository_interface_mut( + &mut self, + ) -> Option<&mut dyn InstalledRepositoryInterface> { + Some(self) + } fn as_any(&self) -> &dyn std::any::Any { todo!() } + fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/installed_filesystem_repository.rs b/crates/shirabe/src/repository/installed_filesystem_repository.rs index 352d86f..73a9f1a 100644 --- a/crates/shirabe/src/repository/installed_filesystem_repository.rs +++ b/crates/shirabe/src/repository/installed_filesystem_repository.rs @@ -143,7 +143,18 @@ impl RepositoryInterface for InstalledFilesystemRepository { fn as_advisory_provider(&self) -> Option<&dyn AdvisoryProviderInterface> { None } + fn as_installed_repository_interface(&self) -> Option<&dyn InstalledRepositoryInterface> { + Some(self) + } + fn as_installed_repository_interface_mut( + &mut self, + ) -> Option<&mut dyn InstalledRepositoryInterface> { + Some(self) + } fn as_any(&self) -> &dyn std::any::Any { todo!() } + fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/installed_repository.rs b/crates/shirabe/src/repository/installed_repository.rs index 486d4bd..b857bc2 100644 --- a/crates/shirabe/src/repository/installed_repository.rs +++ b/crates/shirabe/src/repository/installed_repository.rs @@ -16,7 +16,8 @@ use crate::repository::LockArrayRepository; use crate::repository::PlatformRepository; use crate::repository::RootPackageRepository; use crate::repository::{ - FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, + RepositoryInterfaceHandle, SearchResult, }; pub enum NeedleInput { @@ -36,7 +37,7 @@ pub struct InstalledRepository { } impl InstalledRepository { - pub fn new(repositories: Vec<Box<dyn RepositoryInterface>>) -> Self { + pub fn new(repositories: Vec<RepositoryInterfaceHandle>) -> Self { let mut this = Self { inner: CompositeRepository::new(vec![]), }; @@ -381,25 +382,21 @@ impl InstalledRepository { results } - pub fn add_repository( - &mut self, - repository: Box<dyn RepositoryInterface>, - ) -> anyhow::Result<()> { - // TODO(phase-b): cannot Any::is::<dyn InstalledRepositoryInterface>; replace with a - // dedicated downcast/marker method on RepositoryInterface. - if repository.as_any().is::<LockArrayRepository>() - || repository.as_any().is::<RootPackageRepository>() - || repository.as_any().is::<PlatformRepository>() + pub fn add_repository(&mut self, repository: RepositoryInterfaceHandle) -> anyhow::Result<()> { + if repository.is::<LockArrayRepository>() + || repository.is::<RootPackageRepository>() + || repository.is::<PlatformRepository>() { self.inner.add_repository(repository); return Ok(()); } + let type_name = std::any::type_name_of_val(&*repository.borrow()).to_string(); + let repo_name = repository.get_repo_name(); Err(anyhow::anyhow!(LogicException { message: format!( "An InstalledRepository can not contain a repository of type {} ({})", - std::any::type_name_of_val(&*repository), - repository.get_repo_name(), + type_name, repo_name, ), code: 0, })) diff --git a/crates/shirabe/src/repository/lock_array_repository.rs b/crates/shirabe/src/repository/lock_array_repository.rs index cbc8564..f9b64a7 100644 --- a/crates/shirabe/src/repository/lock_array_repository.rs +++ b/crates/shirabe/src/repository/lock_array_repository.rs @@ -5,7 +5,8 @@ use crate::package::PackageInterfaceHandle; use crate::repository::ArrayRepository; use crate::repository::CanonicalPackagesTrait; use crate::repository::{ - FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, + RepositoryInterfaceWeakHandle, SearchResult, }; use indexmap::IndexMap; use shirabe_php_shim::Countable; @@ -89,4 +90,8 @@ impl RepositoryInterface for LockArrayRepository { fn as_any(&self) -> &dyn std::any::Any { self } + + fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/mod.rs b/crates/shirabe/src/repository/mod.rs index 6f037d0..d678764 100644 --- a/crates/shirabe/src/repository/mod.rs +++ b/crates/shirabe/src/repository/mod.rs @@ -7,6 +7,7 @@ pub mod composite_repository; pub mod configurable_repository_interface; pub mod filesystem_repository; pub mod filter_repository; +pub mod handle; pub mod installed_array_repository; pub mod installed_filesystem_repository; pub mod installed_repository; @@ -39,6 +40,7 @@ pub use composite_repository::*; pub use configurable_repository_interface::*; pub use filesystem_repository::*; pub use filter_repository::*; +pub use handle::*; pub use installed_array_repository::*; pub use installed_filesystem_repository::*; pub use installed_repository::*; diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index 1b95ff7..b8c4f52 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -2012,4 +2012,8 @@ impl crate::repository::RepositoryInterface for PlatformRepository { fn as_any(&self) -> &dyn std::any::Any { self } + + fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/repository_factory.rs b/crates/shirabe/src/repository/repository_factory.rs index 908704c..1159da1 100644 --- a/crates/shirabe/src/repository/repository_factory.rs +++ b/crates/shirabe/src/repository/repository_factory.rs @@ -12,7 +12,7 @@ use crate::factory::Factory; use crate::io::IOInterface; use crate::json::JsonFile; use crate::repository::FilesystemRepository; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; use crate::repository::RepositoryManager; use crate::util::HttpDownloader; use crate::util::ProcessExecutor; @@ -102,7 +102,7 @@ impl RepositoryFactory { repository: &str, allow_filesystem: bool, rm: Option<&mut RepositoryManager>, - ) -> anyhow::Result<Box<dyn RepositoryInterface>> { + ) -> anyhow::Result<RepositoryInterfaceHandle> { let repo_config = Self::config_from_string(io.clone(), config, repository, allow_filesystem)?; Self::create_repo(io, config, repo_config, rm) @@ -113,7 +113,7 @@ impl RepositoryFactory { config: &std::rc::Rc<std::cell::RefCell<Config>>, repo_config: IndexMap<String, PhpMixed>, rm: Option<&mut RepositoryManager>, - ) -> anyhow::Result<Box<dyn RepositoryInterface>> { + ) -> anyhow::Result<RepositoryInterfaceHandle> { let mut owned_rm; let rm = if let Some(rm) = rm { rm @@ -145,7 +145,7 @@ impl RepositoryFactory { io: Option<std::rc::Rc<std::cell::RefCell<dyn IOInterface>>>, config: Option<std::rc::Rc<std::cell::RefCell<Config>>>, rm: Option<&mut RepositoryManager>, - ) -> anyhow::Result<IndexMap<String, Box<dyn RepositoryInterface>>> { + ) -> anyhow::Result<IndexMap<String, RepositoryInterfaceHandle>> { let config = match config { Some(c) => c, None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_config(None, None)?)), @@ -233,7 +233,7 @@ impl RepositoryFactory { pub fn default_repos_with_default_manager( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - ) -> anyhow::Result<IndexMap<String, Box<dyn RepositoryInterface>>> { + ) -> anyhow::Result<IndexMap<String, RepositoryInterfaceHandle>> { let config = std::rc::Rc::new(std::cell::RefCell::new(Factory::create_config( Some(io.clone()), None, @@ -247,8 +247,8 @@ impl RepositoryFactory { fn create_repos( rm: &mut RepositoryManager, repo_configs: Vec<PhpMixed>, - ) -> anyhow::Result<IndexMap<String, Box<dyn RepositoryInterface>>> { - let mut repo_map: IndexMap<String, Box<dyn RepositoryInterface>> = IndexMap::new(); + ) -> anyhow::Result<IndexMap<String, RepositoryInterfaceHandle>> { + let mut repo_map: IndexMap<String, RepositoryInterfaceHandle> = IndexMap::new(); for (index, repo) in repo_configs.into_iter().enumerate() { match &repo { @@ -290,7 +290,7 @@ impl RepositoryFactory { .to_string(); // TODO(phase-b): FilesystemRepository does not yet implement // RepositoryInterface; once it does, construct it from JsonFile here. - let created: Box<dyn RepositoryInterface> = + let created: RepositoryInterfaceHandle = todo!("FilesystemRepository as dyn RepositoryInterface"); repo_map.insert(name, created); } else { @@ -323,7 +323,7 @@ impl RepositoryFactory { pub fn generate_repository_name( index: &PhpMixed, repo: &IndexMap<String, PhpMixed>, - existing_repos: &IndexMap<String, Box<dyn RepositoryInterface>>, + existing_repos: &IndexMap<String, RepositoryInterfaceHandle>, ) -> String { let mut name = match index { PhpMixed::Int(_) => { @@ -344,7 +344,7 @@ impl RepositoryFactory { fn generate_repository_name_indexed( index: usize, repo: &IndexMap<String, PhpMixed>, - existing_repos: &IndexMap<String, Box<dyn RepositoryInterface>>, + existing_repos: &IndexMap<String, RepositoryInterfaceHandle>, ) -> String { let mut name = if let Some(url) = repo.get("url").and_then(|v| v.as_string()) { Preg::replace("{^https?://}i", "", url).unwrap_or_else(|_| url.to_string()) diff --git a/crates/shirabe/src/repository/repository_interface.rs b/crates/shirabe/src/repository/repository_interface.rs index 316f509..21914f6 100644 --- a/crates/shirabe/src/repository/repository_interface.rs +++ b/crates/shirabe/src/repository/repository_interface.rs @@ -93,8 +93,23 @@ pub trait RepositoryInterface: Countable + std::fmt::Debug { None } + fn as_installed_repository_interface_mut( + &mut self, + ) -> Option<&mut dyn crate::repository::InstalledRepositoryInterface> { + None + } + fn as_any(&self) -> &dyn std::any::Any; + /// Injects this repository's own weak handle so that `add_package` can wire package -> + /// repository back-references (PHP `setRepository($this)`). Called once when the repository is + /// wrapped in a [`RepositoryInterfaceHandle`](crate::repository::RepositoryInterfaceHandle). + /// Wrapper repositories forward the same weak (the outermost handle) to their inner + /// `ArrayRepository`. + fn set_self_handle(&self, weak: crate::repository::RepositoryInterfaceWeakHandle) { + let _ = weak; + } + fn clone_box(&self) -> Box<dyn RepositoryInterface> { todo!() } diff --git a/crates/shirabe/src/repository/repository_manager.rs b/crates/shirabe/src/repository/repository_manager.rs index 72c3643..dd69731 100644 --- a/crates/shirabe/src/repository/repository_manager.rs +++ b/crates/shirabe/src/repository/repository_manager.rs @@ -10,15 +10,14 @@ use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::package::PackageInterfaceHandle; use crate::repository::FilterRepository; -use crate::repository::InstalledRepositoryInterface; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; use crate::util::HttpDownloader; use crate::util::ProcessExecutor; #[derive(Debug)] pub struct RepositoryManager { - local_repository: Option<Box<dyn InstalledRepositoryInterface>>, - repositories: Vec<Box<dyn RepositoryInterface>>, + local_repository: Option<RepositoryInterfaceHandle>, + repositories: Vec<RepositoryInterfaceHandle>, repository_classes: IndexMap<String, String>, io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, config: std::rc::Rc<std::cell::RefCell<Config>>, @@ -62,7 +61,7 @@ impl RepositoryManager { name, crate::repository::FindPackageConstraint::Constraint(constraint.clone()), ) { - return Some(package.clone().into()); + return Some(package.into()); } } None @@ -81,17 +80,17 @@ impl RepositoryManager { constraint.clone(), )), ) { - packages.push(p.clone().into()); + packages.push(p); } } packages } - pub fn add_repository(&mut self, repository: Box<dyn RepositoryInterface>) { + pub fn add_repository(&mut self, repository: RepositoryInterfaceHandle) { self.repositories.push(repository); } - pub fn prepend_repository(&mut self, repository: Box<dyn RepositoryInterface>) { + pub fn prepend_repository(&mut self, repository: RepositoryInterfaceHandle) { self.repositories.insert(0, repository); } @@ -100,7 +99,7 @@ impl RepositoryManager { r#type: &str, config: IndexMap<String, PhpMixed>, name: Option<&str>, - ) -> anyhow::Result<Box<dyn RepositoryInterface>> { + ) -> anyhow::Result<RepositoryInterfaceHandle> { if !self.repository_classes.contains_key(r#type) { return Err(InvalidArgumentException { message: format!("Repository type is not registered: {}", r#type), @@ -140,7 +139,10 @@ impl RepositoryManager { let repository = self.create_repository_by_class(&class, cleaned_config)?; if let Some(filter_config) = filter_config { - return Ok(Box::new(FilterRepository::new(repository, filter_config)?)); + return Ok(RepositoryInterfaceHandle::new(FilterRepository::new( + repository, + filter_config, + )?)); } Ok(repository) @@ -150,7 +152,7 @@ impl RepositoryManager { &self, _class: &str, _config: IndexMap<String, PhpMixed>, - ) -> anyhow::Result<Box<dyn RepositoryInterface>> { + ) -> anyhow::Result<RepositoryInterfaceHandle> { todo!("Phase B: dynamic class instantiation by class name") } @@ -159,15 +161,15 @@ impl RepositoryManager { .insert(r#type.to_string(), class.to_string()); } - pub fn get_repositories(&self) -> &Vec<Box<dyn RepositoryInterface>> { + pub fn get_repositories(&self) -> &Vec<RepositoryInterfaceHandle> { &self.repositories } - pub fn set_local_repository(&mut self, repository: Box<dyn InstalledRepositoryInterface>) { + pub fn set_local_repository(&mut self, repository: RepositoryInterfaceHandle) { self.local_repository = Some(repository); } - pub fn get_local_repository(&self) -> &dyn InstalledRepositoryInterface { - self.local_repository.as_ref().unwrap().as_ref() + pub fn get_local_repository(&self) -> RepositoryInterfaceHandle { + self.local_repository.clone().unwrap() } } diff --git a/crates/shirabe/src/repository/repository_set.rs b/crates/shirabe/src/repository/repository_set.rs index 3a81eda..f4a4057 100644 --- a/crates/shirabe/src/repository/repository_set.rs +++ b/crates/shirabe/src/repository/repository_set.rs @@ -35,7 +35,7 @@ use crate::repository::InstalledRepositoryInterface; use crate::repository::LockArrayRepository; use crate::repository::PlatformRepository; use crate::repository::{AdvisoryProviderInterface, PartialOrSecurityAdvisory}; -use crate::repository::{FindPackageConstraint, RepositoryInterface}; +use crate::repository::{FindPackageConstraint, RepositoryInterface, RepositoryInterfaceHandle}; #[derive(Debug, Clone)] pub struct RootAliasEntry { @@ -66,7 +66,7 @@ pub struct RepositorySet { pub(crate) root_references: IndexMap<String, String>, /// @var RepositoryInterface[] - pub(crate) repositories: Vec<Box<dyn RepositoryInterface>>, + pub(crate) repositories: Vec<RepositoryInterfaceHandle>, /// @var int[] array of stability => BasePackage::STABILITY_* value /// @phpstan-var array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*> @@ -170,7 +170,7 @@ impl RepositorySet { /// repository the search for that package ends, and following repos will not be consulted. /// /// @param RepositoryInterface $repo A package repository - pub fn add_repository(&mut self, repo: Box<dyn RepositoryInterface>) -> Result<()> { + pub fn add_repository(&mut self, repo: RepositoryInterfaceHandle) -> Result<()> { if self.locked { return Err(RuntimeException { message: "Pool has already been created from this repository set, it cannot be modified anymore.".to_string(), @@ -179,17 +179,15 @@ impl RepositorySet { .into()); } - let repos: Vec<Box<dyn RepositoryInterface>> = - if let Some(composite) = repo.as_any().downcast_ref::<CompositeRepository>() { - // TODO(phase-b): clone composite.get_repositories() — Box<dyn RepositoryInterface> cloning - composite - .get_repositories() - .iter() - .map(|r| r.clone_box()) - .collect() + let repos: Vec<RepositoryInterfaceHandle> = { + let repo_ref = repo.borrow(); + if let Some(composite) = repo_ref.as_any().downcast_ref::<CompositeRepository>() { + composite.get_repositories().clone() } else { + drop(repo_ref); vec![repo] - }; + } + }; for repo in repos { self.repositories.push(repo); @@ -379,7 +377,8 @@ impl RepositorySet { for repository in &self.repositories { // TODO(phase-b): use anyhow::Result<Result<T, E>> to model PHP try/catch let attempt: Result<()> = (|| -> Result<()> { - let Some(advisory_repo) = repository.as_advisory_provider() else { + let repo_ref = repository.borrow(); + let Some(advisory_repo) = repo_ref.as_advisory_provider() else { return Ok(()); }; if !advisory_repo.has_security_advisories() { @@ -480,11 +479,11 @@ impl RepositorySet { pool_builder.set_allowed_types(allowed_types); for repo in &self.repositories { - let is_installed = repo.as_installed_repository_interface().is_some() - || repo - .as_any() - .downcast_ref::<InstalledRepository>() - .is_some(); + let is_installed = { + let repo_ref = repo.borrow(); + repo_ref.as_installed_repository_interface().is_some() + || repo_ref.as_any().is::<InstalledRepository>() + }; if is_installed && !self.allow_installed_repositories { return Err(LogicException { message: "The pool can not accept packages from an installed repository" @@ -507,11 +506,11 @@ impl RepositorySet { /// Create a pool for dependency resolution from the packages in this repository set. pub fn create_pool_with_all_packages(&mut self) -> Result<Pool> { for repo in &self.repositories { - let is_installed = repo.as_installed_repository_interface().is_some() - || repo - .as_any() - .downcast_ref::<InstalledRepository>() - .is_some(); + let is_installed = { + let repo_ref = repo.borrow(); + repo_ref.as_installed_repository_interface().is_some() + || repo_ref.as_any().is::<InstalledRepository>() + }; if is_installed && !self.allow_installed_repositories { return Err(LogicException { message: "The pool can not accept packages from an installed repository" diff --git a/crates/shirabe/src/repository/repository_utils.rs b/crates/shirabe/src/repository/repository_utils.rs index 667cea2..4522039 100644 --- a/crates/shirabe/src/repository/repository_utils.rs +++ b/crates/shirabe/src/repository/repository_utils.rs @@ -3,7 +3,7 @@ use crate::package::Link; use crate::repository::CompositeRepository; use crate::repository::FilterRepository; -use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceHandle; use indexmap::IndexMap; use std::any::Any; @@ -39,23 +39,31 @@ impl RepositoryUtils { } pub fn flatten_repositories( - repo: Box<dyn RepositoryInterface>, + repo: RepositoryInterfaceHandle, unwrap_filter_repos: bool, - ) -> Vec<Box<dyn RepositoryInterface>> { - let repo: Box<dyn RepositoryInterface> = if unwrap_filter_repos { - if let Some(filter_repo) = repo.as_any().downcast_ref::<FilterRepository>() { - filter_repo.get_repository().clone_box() - } else { - repo - } + ) -> Vec<RepositoryInterfaceHandle> { + let repo: RepositoryInterfaceHandle = if unwrap_filter_repos { + let unwrapped = { + let r = repo.borrow(); + r.as_any() + .downcast_ref::<FilterRepository>() + .map(|filter_repo| filter_repo.get_repository()) + }; + unwrapped.unwrap_or(repo) } else { repo }; - if let Some(composite_repo) = repo.as_any().downcast_ref::<CompositeRepository>() { + let nested = { + let r = repo.borrow(); + r.as_any() + .downcast_ref::<CompositeRepository>() + .map(|composite_repo| composite_repo.get_repositories().clone()) + }; + if let Some(nested) = nested { let mut repos = Vec::new(); - for r in composite_repo.get_repositories() { - for r2 in Self::flatten_repositories(r.clone_box(), unwrap_filter_repos) { + for r in nested { + for r2 in Self::flatten_repositories(r, unwrap_filter_repos) { repos.push(r2); } } diff --git a/crates/shirabe/src/repository/root_package_repository.rs b/crates/shirabe/src/repository/root_package_repository.rs index cb01a7c..22ca437 100644 --- a/crates/shirabe/src/repository/root_package_repository.rs +++ b/crates/shirabe/src/repository/root_package_repository.rs @@ -4,6 +4,7 @@ use crate::package::BasePackageHandle; use crate::package::PackageInterfaceHandle; use crate::package::RootPackageInterfaceHandle; use crate::repository::ArrayRepository; +use crate::repository::RepositoryInterfaceWeakHandle; use crate::repository::{ProviderInfo, RepositoryInterface, SearchResult}; use indexmap::IndexMap; @@ -85,4 +86,8 @@ impl RepositoryInterface for RootPackageRepository { fn as_any(&self) -> &dyn std::any::Any { self } + + fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } } diff --git a/crates/shirabe/src/repository/writable_array_repository.rs b/crates/shirabe/src/repository/writable_array_repository.rs index 282f3c0..ac9df8f 100644 --- a/crates/shirabe/src/repository/writable_array_repository.rs +++ b/crates/shirabe/src/repository/writable_array_repository.rs @@ -3,6 +3,7 @@ use crate::installer::InstallationManager; use crate::repository::ArrayRepository; use crate::repository::RepositoryInterface; +use crate::repository::RepositoryInterfaceWeakHandle; use anyhow::Result; use shirabe_php_shim::Countable; @@ -52,6 +53,10 @@ impl WritableArrayRepository { self.inner.add_package(package) } + pub fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) { + self.inner.set_self_handle(weak); + } + pub fn remove_package( &mut self, package: crate::package::PackageInterfaceHandle, |
