diff options
Diffstat (limited to 'crates/shirabe/src')
92 files changed, 3312 insertions, 2080 deletions
diff --git a/crates/shirabe/src/advisory/auditor.rs b/crates/shirabe/src/advisory/auditor.rs index 5418fac..8ac6d0c 100644 --- a/crates/shirabe/src/advisory/auditor.rs +++ b/crates/shirabe/src/advisory/auditor.rs @@ -15,8 +15,9 @@ use crate::advisory::SecurityAdvisory; use crate::io::ConsoleIO; use crate::io::IOInterface; use crate::json::JsonFile; -use crate::package::CompletePackageInterface; +use crate::package::CompletePackageInterfaceHandle; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::base_package; use crate::package::base_package::BasePackage; use crate::repository::PartialOrSecurityAdvisory; @@ -73,7 +74,7 @@ impl Auditor { &self, io: &mut dyn IOInterface, repo_set: &RepositorySet, - packages: Vec<Box<dyn PackageInterface>>, + packages: Vec<PackageInterfaceHandle>, format: &str, warning_only: bool, ignore_list: IndexMap<String, Option<String>>, @@ -109,12 +110,12 @@ impl Auditor { let mut abandoned_count: i64 = 0; let affected_packages_count = advisories.len() as i64; - let abandoned_packages: Vec<Box<dyn CompletePackageInterface>>; + let abandoned_packages: Vec<CompletePackageInterfaceHandle>; if abandoned == Self::ABANDONED_IGNORE { abandoned_packages = vec![]; } else { // TODO(phase-b): $packages reused here; see note above - abandoned_packages = self.filter_abandoned_packages(&[], &ignore_abandoned); + abandoned_packages = self.filter_abandoned_packages(&[], &ignore_abandoned)?; if abandoned == Self::ABANDONED_FAIL { abandoned_count = abandoned_packages.len() as i64; } @@ -144,11 +145,8 @@ impl Auditor { let abandoned_map = array_reduce( &abandoned_packages, |mut carry: IndexMap<String, Option<String>>, - package: &Box<dyn CompletePackageInterface>| { - carry.insert( - package.get_pretty_name().to_string(), - package.get_replacement_package().map(|s| s.to_string()), - ); + package: &CompletePackageInterfaceHandle| { + carry.insert(package.get_pretty_name(), package.get_replacement_package()); carry }, IndexMap::new(), @@ -284,9 +282,9 @@ impl Auditor { /// @return array<CompletePackageInterface> pub fn filter_abandoned_packages( &self, - packages: &[Box<dyn PackageInterface>], + packages: &[PackageInterfaceHandle], ignore_abandoned: &IndexMap<String, Option<String>>, - ) -> Vec<Box<dyn CompletePackageInterface>> { + ) -> anyhow::Result<Vec<CompletePackageInterfaceHandle>> { let mut filter: Option<String> = None; if ignore_abandoned.len() != 0 { filter = Some(base_package::package_names_to_regexp( @@ -295,16 +293,19 @@ impl Auditor { )); } - // PHP: array_filter($packages, fn($pkg) => $pkg instanceof CompletePackageInterface && $pkg->isAbandoned() && ($filter === null || !Preg::isMatch($filter, $pkg->getName()))) - // TODO(phase-b): downcast Box<dyn PackageInterface> -> Box<dyn CompletePackageInterface> - let _ = packages; - let _ = filter; - let _ = |pkg: &Box<dyn PackageInterface>| -> bool { - // pkg instanceof CompletePackageInterface && pkg.is_abandoned() && (filter.is_none() || !Preg::is_match(filter.as_ref().unwrap(), pkg.get_name())) - let _ = Preg::is_match("", ""); - false - }; - vec![] + // PHP: array_filter($packages, fn(PackageInterface $pkg) => $pkg instanceof CompletePackageInterface && $pkg->isAbandoned() && ($filter === null || !Preg::isMatch($filter, $pkg->getName()))) + let mut result: Vec<CompletePackageInterfaceHandle> = vec![]; + for pkg in packages { + let Some(pkg) = pkg.as_complete() else { + continue; + }; + if pkg.is_abandoned() + && (filter.is_none() || !Preg::is_match(filter.as_ref().unwrap(), &pkg.get_name())?) + { + result.push(pkg); + } + } + Ok(result) } /// @phpstan-param array<string, array<PartialOrSecurityAdvisory|SecurityAdvisory>> $allAdvisories @@ -577,7 +578,7 @@ impl Auditor { fn output_abandoned_packages( &self, io: &mut dyn IOInterface, - packages: &[Box<dyn CompletePackageInterface>], + packages: &[CompletePackageInterfaceHandle], format: &str, ) -> Result<()> { io.write_error(&sprintf( @@ -673,10 +674,10 @@ impl Auditor { // upcast to PackageInterface (e.g. via an as_package_interface() trait method) fn get_package_name_with_link_for_complete( &self, - package: &Box<dyn CompletePackageInterface>, + package: &CompletePackageInterfaceHandle, ) -> String { let _ = package; - // PackageInfo::get_view_source_or_homepage_url(package as &dyn PackageInterface) + // PackageInfo::get_view_source_or_homepage_url(package.as_rc().borrow().as_package_interface()) String::new() } diff --git a/crates/shirabe/src/autoload/autoload_generator.rs b/crates/shirabe/src/autoload/autoload_generator.rs index 5c9f008..3786011 100644 --- a/crates/shirabe/src/autoload/autoload_generator.rs +++ b/crates/shirabe/src/autoload/autoload_generator.rs @@ -27,9 +27,9 @@ use crate::installer::InstallationManager; use crate::io::IOInterface; use crate::io::NullIO; use crate::json::JsonFile; -use crate::package::AliasPackage; use crate::package::Locker; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::RootPackageInterface; use crate::repository::InstalledRepositoryInterface; use crate::script::ScriptEvents; @@ -267,7 +267,7 @@ impl AutoloadGenerator { let autoloads = self.parse_autoloads( package_map .iter() - .map(|(p, s)| (p.clone_package_box(), s.clone())) + .map(|(p, s)| (p.clone(), s.clone())) .collect(), root_package, filtered_dev_packages, @@ -741,20 +741,24 @@ impl AutoloadGenerator { &self, installation_manager: &mut InstallationManager, root_package: &dyn RootPackageInterface, - packages: Vec<Box<dyn PackageInterface>>, - ) -> anyhow::Result<Vec<(Box<dyn PackageInterface>, Option<String>)>> { + packages: Vec<PackageInterfaceHandle>, + ) -> anyhow::Result<Vec<(PackageInterfaceHandle, Option<String>)>> { // build package => install path map - let mut package_map: Vec<(Box<dyn PackageInterface>, Option<String>)> = vec![( - root_package.clone_as_package_interface(), - Some(String::new()), - )]; + // TODO(phase-c): the root package needs to be available here as a shared handle; + // a borrowed &dyn RootPackageInterface cannot be lifted into a PackageInterfaceHandle. + let _ = root_package; + let root_package_handle: PackageInterfaceHandle = + todo!("root package handle for build_package_map"); + let mut package_map: Vec<(PackageInterfaceHandle, Option<String>)> = + vec![(root_package_handle, Some(String::new()))]; for package in packages { - if package.as_alias_package().is_some() { + if package.as_alias().is_some() { continue; } - self.validate_package(&*package)?; - let install_path = installation_manager.get_install_path(&*package); + self.validate_package(package.as_rc().borrow().as_package_interface())?; + let install_path = installation_manager + .get_install_path(package.as_rc().borrow().as_package_interface()); package_map.push((package, install_path)); } @@ -795,7 +799,7 @@ impl AutoloadGenerator { /// Compiles an ordered list of namespace => path mappings pub fn parse_autoloads( &self, - package_map: Vec<(Box<dyn PackageInterface>, Option<String>)>, + package_map: Vec<(PackageInterfaceHandle, Option<String>)>, root_package: &dyn RootPackageInterface, filtered_dev_packages: PhpMixed, ) -> IndexMap<String, PhpMixed> { @@ -812,7 +816,7 @@ impl AutoloadGenerator { .unwrap_or_default(); package_map .into_iter() - .filter(|item| !dev_list.contains(&item.0.get_name().to_string())) + .filter(|item| !dev_list.contains(&item.0.get_name())) .collect() } else if filtered_dev_packages.as_bool() == Some(true) { self.filter_package_map(package_map, root_package) @@ -932,7 +936,7 @@ impl AutoloadGenerator { pub(crate) fn get_include_paths_file( &self, - package_map: &Vec<(Box<dyn PackageInterface>, Option<String>)>, + package_map: &Vec<(PackageInterfaceHandle, Option<String>)>, filesystem: &Filesystem, base_path: &str, vendor_path: &str, @@ -1083,7 +1087,7 @@ impl AutoloadGenerator { pub(crate) fn get_platform_check( &self, - package_map: &Vec<(Box<dyn PackageInterface>, Option<String>)>, + package_map: &Vec<(PackageInterfaceHandle, Option<String>)>, check_platform: PhpMixed, dev_package_names: &Vec<String>, ) -> Option<String> { @@ -1625,7 +1629,7 @@ impl AutoloadGenerator { pub(crate) fn parse_autoloads_type( &self, - package_map: &Vec<(Box<dyn PackageInterface>, Option<String>)>, + package_map: &Vec<(PackageInterfaceHandle, Option<String>)>, r#type: &str, root_package: &dyn RootPackageInterface, ) -> IndexMap<String, Box<PhpMixed>> { @@ -1794,7 +1798,10 @@ impl AutoloadGenerator { if r#type == "files" { autoloads.insert( - self.get_file_identifier(&**package, &path_str), + self.get_file_identifier( + package.as_rc().borrow().as_package_interface(), + &path_str, + ), Box::new(PhpMixed::String(relative_path)), ); continue; @@ -1830,17 +1837,17 @@ impl AutoloadGenerator { /// Filters out dev-dependencies pub(crate) fn filter_package_map( &self, - package_map: Vec<(Box<dyn PackageInterface>, Option<String>)>, + package_map: Vec<(PackageInterfaceHandle, Option<String>)>, root_package: &dyn RootPackageInterface, - ) -> Vec<(Box<dyn PackageInterface>, Option<String>)> { - let mut packages: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); + ) -> Vec<(PackageInterfaceHandle, Option<String>)> { + let mut packages: IndexMap<String, PackageInterfaceHandle> = IndexMap::new(); let mut include: IndexMap<String, bool> = IndexMap::new(); let mut replaced_by: IndexMap<String, String> = IndexMap::new(); for item in &package_map { let package = &item.0; - let name = package.get_name().to_string(); - packages.insert(name.clone(), package.clone_package_box()); + let name = package.get_name(); + packages.insert(name.clone(), package.clone()); for (_k, replace) in &package.get_replaces() { replaced_by.insert(replace.get_target().to_string(), name.clone()); } @@ -1849,7 +1856,7 @@ impl AutoloadGenerator { // Recursive walk emulating PHP's by-reference closure capture. fn add( package: &dyn PackageInterface, - packages: &IndexMap<String, Box<dyn PackageInterface>>, + packages: &IndexMap<String, PackageInterfaceHandle>, include: &mut IndexMap<String, bool>, replaced_by: &IndexMap<String, String>, ) { @@ -1861,7 +1868,12 @@ impl AutoloadGenerator { if !include.contains_key(&target) { include.insert(target.clone(), true); if let Some(p) = packages.get(&target) { - add(&**p, packages, include, replaced_by); + add( + p.as_rc().borrow().as_package_interface(), + packages, + include, + replaced_by, + ); } } } @@ -1892,29 +1904,29 @@ impl AutoloadGenerator { /// Packages of equal weight are sorted alphabetically pub(crate) fn sort_package_map( &self, - package_map: Vec<(Box<dyn PackageInterface>, Option<String>)>, - ) -> Vec<(Box<dyn PackageInterface>, Option<String>)> { - let mut packages: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); + package_map: Vec<(PackageInterfaceHandle, Option<String>)>, + ) -> Vec<(PackageInterfaceHandle, Option<String>)> { + let mut packages: IndexMap<String, PackageInterfaceHandle> = IndexMap::new(); let mut paths: IndexMap<String, Option<String>> = IndexMap::new(); for item in &package_map { let (package, path) = item; - let name = package.get_name().to_string(); - packages.insert(name.clone(), package.clone_package_box()); + let name = package.get_name(); + packages.insert(name.clone(), package.clone()); paths.insert(name, path.clone()); } let sorted_packages = PackageSorter::sort_packages( - packages.values().map(|p| p.clone_package_box()).collect(), + packages.values().map(|p| p.clone()).collect(), IndexMap::new(), ); - let mut sorted_package_map: Vec<(Box<dyn PackageInterface>, Option<String>)> = vec![]; + let mut sorted_package_map: Vec<(PackageInterfaceHandle, Option<String>)> = vec![]; for package in sorted_packages { - let name = package.get_name().to_string(); + let name = package.get_name(); sorted_package_map.push(( - packages.get(&name).unwrap().clone_package_box(), + packages.get(&name).unwrap().clone(), paths.get(&name).cloned().flatten(), )); } diff --git a/crates/shirabe/src/command/archive_command.rs b/crates/shirabe/src/command/archive_command.rs index 0813df6..1b21242 100644 --- a/crates/shirabe/src/command/archive_command.rs +++ b/crates/shirabe/src/command/archive_command.rs @@ -1,7 +1,5 @@ //! ref: composer/src/Composer/Command/ArchiveCommand.php -use std::any::Any; - use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; @@ -16,8 +14,6 @@ use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::factory::Factory; use crate::io::IOInterface; -use crate::package::BasePackage; -use crate::package::CompletePackageInterface; use crate::package::archiver::ArchiveManager; use crate::package::version::VersionParser; use crate::package::version::VersionSelector; @@ -192,17 +188,18 @@ impl ArchiveCommand { &owned_archive_manager }; - let package = if let Some(name) = package_name { - match self.select_package(io, &name, version.as_deref())? { - Some(p) => p, - None => return Ok(1), - } - } else { - let _rc = self.require_composer(None, None)?; - crate::command::composer_full(&_rc) - .get_package() - .clone_box() - }; + let package: crate::package::CompletePackageInterfaceHandle = + if let Some(name) = package_name { + match self.select_package(io, &name, version.as_deref())? { + Some(p) => p, + None => return Ok(1), + } + } else { + let _rc = self.require_composer(None, None)?; + // TODO(phase-c): composer.get_package() returns &dyn RootPackageInterface, not a + // handle, so it cannot be shared as a CompletePackageInterfaceHandle yet. + todo!("share composer.get_package() as a CompletePackageInterfaceHandle") + }; io.write_error(&format!( "<info>Creating the archive into \"{}\".</info>", @@ -211,13 +208,7 @@ impl ArchiveCommand { // TODO(phase-b): ArchiveManager.archive needs &mut self and &mut CompletePackageInterface; // current composer.get_archive_manager() returns &ArchiveManager. Needs RefCell wrapper. let _ = archive_manager; - let _ = ( - package.as_ref(), - format, - dest, - file_name.as_deref(), - ignore_filters, - ); + let _ = (&package, format, dest, file_name.as_deref(), ignore_filters); let package_path: String = todo!("ArchiveManager.archive call"); let fs = Filesystem::new(None); let short_path = @@ -239,7 +230,7 @@ impl ArchiveCommand { io: &mut dyn IOInterface, package_name: &str, version: Option<&str>, - ) -> Result<Option<Box<dyn CompletePackageInterface>>> { + ) -> Result<Option<crate::package::CompletePackageInterfaceHandle>> { io.write_error("<info>Searching for the specified package.</info>"); let mut version = version.map(|v| v.to_string()); @@ -323,7 +314,7 @@ impl ArchiveCommand { None, shirabe_php_shim::PhpMixed::Bool(true), )?; - let p = best.unwrap_or_else(|| packages.into_iter().next().unwrap()); + let p = best.unwrap_or_else(|| packages.into_iter().next().unwrap().into()); io.write_error(&format!( "<info>Found multiple matches, selected {}.</info>", @@ -333,7 +324,8 @@ impl ArchiveCommand { io.write_error("<comment>Please use a more specific constraint to pick a different package.</comment>"); p } else if packages.len() == 1 { - let p = packages.into_iter().next().unwrap(); + let p: crate::package::PackageInterfaceHandle = + packages.into_iter().next().unwrap().into(); io.write_error(&format!( "<info>Found an exact match {}.</info>", p.get_pretty_string() @@ -347,10 +339,18 @@ impl ArchiveCommand { return Ok(None); }; - // TODO(phase-b): instanceof CompletePackageInterface / BasePackage runtime - // checks require downcast support that BasePackage trait does not yet expose. - let _ = &package; - todo!("convert Box<dyn BasePackage> into Box<dyn CompletePackageInterface>") + let Some(complete) = package.as_complete() else { + return Err(LogicException { + message: format!( + "Expected a CompletePackageInterface instance but found {}", + get_debug_type(&shirabe_php_shim::PhpMixed::Null) + ), + code: 0, + } + .into()); + }; + + Ok(Some(complete)) } } diff --git a/crates/shirabe/src/command/audit_command.rs b/crates/shirabe/src/command/audit_command.rs index f4f27e9..d3dadbd 100644 --- a/crates/shirabe/src/command/audit_command.rs +++ b/crates/shirabe/src/command/audit_command.rs @@ -6,7 +6,6 @@ use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::PartialComposerHandle; use crate::console::input::InputOption; use crate::io::IOInterface; -use crate::package::PackageInterface; use crate::repository::CanonicalPackagesTrait; use crate::repository::InstalledRepository; use crate::repository::RepositoryInterface; @@ -146,7 +145,7 @@ impl AuditCommand { &self, composer: &PartialComposerHandle, input: &dyn InputInterface, - ) -> Result<Vec<Box<dyn PackageInterface>>> { + ) -> Result<Vec<crate::package::PackageInterfaceHandle>> { let mut composer = crate::command::composer_full_mut(composer); if input.get_option("locked").as_bool().unwrap_or(false) { let locker = composer.get_locker().clone(); diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs index f1ff863..8ea10b5 100644 --- a/crates/shirabe/src/command/base_dependency_command.rs +++ b/crates/shirabe/src/command/base_dependency_command.rs @@ -53,7 +53,7 @@ pub trait BaseDependencyCommand: BaseCommand { let mut repos: Vec<Box<dyn RepositoryInterface>> = vec![]; repos.push(Box::new(RootPackageRepository::new( - composer.get_package().clone_box(), + composer.get_package().clone(), ))); if input.get_option("locked").as_bool().unwrap_or(false) { @@ -158,7 +158,7 @@ pub trait BaseDependencyCommand: BaseCommand { FindPackageConstraint::String(text_constraint.clone()), ) { installed_repo.add_repository(Box::new( - InstalledArrayRepository::new_with_packages(vec![r#match.clone_box()])?, + InstalledArrayRepository::new_with_packages(vec![r#match.into()])?, ))?; } else if PlatformRepository::is_platform_package(&needle) { let parser = VersionParser::new(); @@ -170,9 +170,9 @@ pub trait BaseDependencyCommand: BaseCommand { .to_string(); let temp_platform_pkg = Package::new(needle.clone(), version.clone(), version); installed_repo.add_repository(Box::new( - InstalledArrayRepository::new_with_packages(vec![Box::new( - temp_platform_pkg, - )])?, + InstalledArrayRepository::new_with_packages(vec![ + crate::package::PackageHandle::from_package(temp_platform_pkg).into(), + ])?, ))?; } } else { @@ -270,9 +270,9 @@ pub trait BaseDependencyCommand: BaseCommand { self.init_styles(output); let root = &packages[0]; let description = root - .as_complete_package_interface() + .as_complete() .and_then(|c| c.get_description()) - .unwrap_or(""); + .unwrap_or_default(); self.get_io().write(&format!( "<info>{}</info> {} {}", root.get_pretty_name(), @@ -335,7 +335,9 @@ pub trait BaseDependencyCommand: BaseCommand { } else { package.get_pretty_version().to_string() }; - let package_url = PackageInfo::get_view_source_or_homepage_url(&*package); + let package_url = PackageInfo::get_view_source_or_homepage_url( + package.as_rc().borrow().as_package_interface(), + ); let name_with_link = match &package_url { Some(url) => format!( "<href={}>{}</>", @@ -410,7 +412,9 @@ pub trait BaseDependencyCommand: BaseCommand { } else { package.get_pretty_version().to_string() }; - let package_url = PackageInfo::get_view_source_or_homepage_url(&**package); + let package_url = PackageInfo::get_view_source_or_homepage_url( + package.as_rc().borrow().as_package_interface(), + ); let name_with_link = match &package_url { Some(url) => format!( "<href={}>{}</>", diff --git a/crates/shirabe/src/command/bump_command.rs b/crates/shirabe/src/command/bump_command.rs index 0b9727f..3299f85 100644 --- a/crates/shirabe/src/command/bump_command.rs +++ b/crates/shirabe/src/command/bump_command.rs @@ -251,13 +251,14 @@ impl BumpCommand { None => continue, Some(p) => p, }; - while let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { - // TODO(phase-b): get_alias_of returns &dyn BasePackage; cloning into Box - // requires clone_box on BasePackage applied to a borrowed ref. - package = alias.get_alias_of().clone_box(); + while let Some(alias) = package.as_alias() { + package = alias.get_alias_of().into(); } - let bumped = bumper.bump_requirement(link.get_constraint(), package.as_ref())?; + let bumped = bumper.bump_requirement( + link.get_constraint(), + package.as_rc().borrow().as_package_interface(), + )?; if bumped == current_constraint { continue; diff --git a/crates/shirabe/src/command/check_platform_reqs_command.rs b/crates/shirabe/src/command/check_platform_reqs_command.rs index 74ecfc3..5d886e7 100644 --- a/crates/shirabe/src/command/check_platform_reqs_command.rs +++ b/crates/shirabe/src/command/check_platform_reqs_command.rs @@ -113,7 +113,7 @@ impl CheckPlatformReqsCommand { } } - let root_pkg_repo = RootPackageRepository::new(composer.get_package().clone_box()); + let root_pkg_repo = RootPackageRepository::new(composer.get_package().clone()); let installed_repo = InstalledRepository::new(vec![installed_repo_base, Box::new(root_pkg_repo)]); @@ -148,7 +148,7 @@ impl CheckPlatformReqsCommand { let mut req_results: Vec<CheckResult> = vec![]; 'candidates: for candidate in &candidates { let candidate_constraint: Option<AnyConstraint> = - if candidate.get_name() == require { + if candidate.get_name() == *require { let c = SimpleConstraint::new( "=".to_string(), candidate.get_version().to_string(), @@ -178,7 +178,7 @@ impl CheckPlatformReqsCommand { for link in links { if !link.get_constraint().matches(&candidate_constraint) { req_results.push(CheckResult { - platform_package: if candidate.get_name() == require { + platform_package: if candidate.get_name() == *require { candidate.get_pretty_name().to_string() } else { require.clone() @@ -186,7 +186,7 @@ impl CheckPlatformReqsCommand { version: candidate_constraint.get_pretty_string().to_string(), link: Some(link.clone()), status: "<error>failed</error>".to_string(), - provider: if candidate.get_name() == require { + provider: if candidate.get_name() == *require { String::new() } else { format!( @@ -200,7 +200,7 @@ impl CheckPlatformReqsCommand { } results.push(CheckResult { - platform_package: if candidate.get_name() == require { + platform_package: if candidate.get_name() == *require { candidate.get_pretty_name().to_string() } else { require.clone() @@ -208,7 +208,7 @@ impl CheckPlatformReqsCommand { version: candidate_constraint.get_pretty_string().to_string(), link: None, status: "<info>success</info>".to_string(), - provider: if candidate.get_name() == require { + provider: if candidate.get_name() == *require { String::new() } else { format!( diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index d4b4a1a..e0cb3d4 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -31,7 +31,6 @@ use crate::installer::ProjectInstaller; use crate::installer::SuggestedPackagesReporter; use crate::io::IOInterface; use crate::json::JsonFile; -use crate::package::AliasPackage; use crate::package::version::VersionParser; use crate::package::version::VersionSelector; use crate::package::{STABILITIES, SUPPORTED_LINK_TYPES}; @@ -511,7 +510,7 @@ impl CreateProjectCommand { config_source.add_link( r#type, link.get_target(), - package.get_pretty_version(), + &package.get_pretty_version(), ); } } @@ -873,14 +872,10 @@ impl CreateProjectCommand { } // avoid displaying 9999999-dev as version if default-branch was selected - // TODO(phase-b): `$package instanceof AliasPackage` downcast and reassigning - // `package` to its alias-of requires Rc<dyn PackageInterface> sharing. Skipped. - let package_as_alias: Option<&AliasPackage> = None; - if package_as_alias.is_some() - && package.get_pretty_version() == VersionParser::DEFAULT_BRANCH_ALIAS - { - // package = package_as_alias.unwrap().get_alias_of(); - todo!("phase-b: reassigning package to alias_of needs Rc-shared ownership"); + if let Some(alias) = package.as_alias() { + if package.get_pretty_version() == VersionParser::DEFAULT_BRANCH_ALIAS { + package = alias.get_alias_of().into(); + } } io.write_error(&format!( @@ -896,12 +891,8 @@ impl CreateProjectCommand { io.write_error("<info>Plugins have been disabled.</info>"); } - // TODO(phase-b): `$package instanceof AliasPackage` downcast and reassigning - // `package` to its alias-of requires Rc<dyn PackageInterface> sharing. Skipped. - let package_as_alias: Option<&AliasPackage> = None; - if let Some(_alias) = package_as_alias { - // package = alias.get_alias_of(); - todo!("phase-b: reassigning package to alias_of needs Rc-shared ownership"); + if let Some(alias) = package.as_alias() { + package = alias.get_alias_of().into(); } let dm = composer.get_download_manager(); @@ -917,7 +908,7 @@ impl CreateProjectCommand { let mut installed_repo = InstalledArrayRepository::new()?; im.execute( &mut installed_repo, - vec![Box::new(InstallOperation::new(package.clone_package_box()))], + vec![Box::new(InstallOperation::new(package.clone()))], true, true, false, @@ -928,7 +919,7 @@ impl CreateProjectCommand { // TODO(phase-b): self.suggested_packages_reporter is on the outer scope via &self // self.suggested_packages_reporter.add_suggestions_from_package(&*package); - let installed_from_vcs = "source" == package.get_installation_source().unwrap_or(""); + let installed_from_vcs = package.get_installation_source().as_deref() == Some("source"); io.write_error(&format!("<info>Created project in {}</info>", directory)); chdir(&directory); @@ -942,7 +933,7 @@ impl CreateProjectCommand { Platform::clear_env("COMPOSER"); } - Platform::put_env("COMPOSER_ROOT_VERSION", package.get_pretty_version()); + Platform::put_env("COMPOSER_ROOT_VERSION", &package.get_pretty_version()); // once the root project is fully initialized, we do not need to wipe everything on user abort anymore even if it happens during deps install if let Some(handler) = signal_handler { diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs index 7a16699..fe57e5c 100644 --- a/crates/shirabe/src/command/diagnose_command.rs +++ b/crates/shirabe/src/command/diagnose_command.rs @@ -173,7 +173,7 @@ impl DiagnoseCommand { ) .unwrap(); let mut php_version = php_pkg.get_pretty_version().to_string(); - if let Some(cp) = php_pkg.as_complete_package_interface() { + if let Some(cp) = php_pkg.as_complete() { if str_contains(&cp.get_description().unwrap_or_default(), "overridden") { php_version = format!( "{} - {}", @@ -928,7 +928,7 @@ impl DiagnoseCommand { normalized_version, version.clone(), ); - packages.push(Box::new(root_pkg)); + packages.push(crate::package::RootPackageHandle::from_root_package(root_pkg).into()); } let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new(); repo_config.insert("type".to_string(), PhpMixed::String("composer".to_string())); diff --git a/crates/shirabe/src/command/fund_command.rs b/crates/shirabe/src/command/fund_command.rs index 82c0c0a..f829342 100644 --- a/crates/shirabe/src/command/fund_command.rs +++ b/crates/shirabe/src/command/fund_command.rs @@ -67,14 +67,14 @@ impl FundCommand { let mut packages_to_load: IndexMap<String, Option<AnyConstraint>> = IndexMap::new(); let mut packages_to_load_names: indexmap::IndexSet<String> = indexmap::IndexSet::new(); for package in repo.get_packages() { - if package.as_any().downcast_ref::<AliasPackage>().is_some() { + if package.as_alias().is_some() { continue; } packages_to_load.insert( - package.get_name().to_string(), + package.get_name(), Some(MatchAllConstraint::new(None).into()), ); - packages_to_load_names.insert(package.get_name().to_string()); + packages_to_load_names.insert(package.get_name()); } // load all packages dev versions in parallel @@ -87,15 +87,15 @@ impl FundCommand { // collect funding data from default branches for (_, package) in &result.packages { - if package.as_any().downcast_ref::<AliasPackage>().is_none() { + if package.as_alias().is_none() { // TODO: check for CompleteAliasPackage as well - if let Some(complete_pkg) = package.as_any().downcast_ref::<CompletePackage>() { + if let Some(complete_pkg) = package.as_complete() { if complete_pkg.is_default_branch() && !complete_pkg.get_funding().is_empty() - && packages_to_load_names.contains(complete_pkg.get_name()) + && packages_to_load_names.contains(&complete_pkg.get_name()) { - Self::insert_funding_data(&mut fundings, complete_pkg)?; - packages_to_load_names.shift_remove(complete_pkg.get_name()); + Self::insert_funding_data(&mut fundings, &complete_pkg)?; + packages_to_load_names.shift_remove(&complete_pkg.get_name()); } } } @@ -103,15 +103,14 @@ impl FundCommand { // collect funding from installed packages if none was found in the default branch above for package in repo.get_packages() { - if package.as_any().downcast_ref::<AliasPackage>().is_some() - || !packages_to_load_names.contains(package.get_name()) + if package.as_alias().is_some() || !packages_to_load_names.contains(&package.get_name()) { continue; } // TODO: check for CompleteAliasPackage as well - if let Some(complete_pkg) = package.as_any().downcast_ref::<CompletePackage>() { + if let Some(complete_pkg) = package.as_complete() { if !complete_pkg.get_funding().is_empty() { - Self::insert_funding_data(&mut fundings, complete_pkg)?; + Self::insert_funding_data(&mut fundings, &complete_pkg)?; } } } @@ -172,10 +171,12 @@ impl FundCommand { fn insert_funding_data( fundings: &mut IndexMap<String, IndexMap<String, Vec<String>>>, - package: &CompletePackage, + package: &crate::package::CompletePackageInterfaceHandle, ) -> Result<()> { let pretty_name = package.get_pretty_name(); - let (vendor, package_name) = pretty_name.split_once('/').unwrap_or(("", pretty_name)); + let (vendor, package_name) = pretty_name + .split_once('/') + .unwrap_or(("", pretty_name.as_str())); for funding_option in package.get_funding() { let url_val = funding_option.get("url").and_then(|v| v.as_string()); diff --git a/crates/shirabe/src/command/home_command.rs b/crates/shirabe/src/command/home_command.rs index e7fc6b5..673039a 100644 --- a/crates/shirabe/src/command/home_command.rs +++ b/crates/shirabe/src/command/home_command.rs @@ -9,7 +9,7 @@ use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::console::input::InputArgument; use crate::console::input::InputOption; use crate::io::IOInterface; -use crate::package::CompletePackageInterface; +use crate::package::CompletePackageInterfaceHandle; use crate::package::PackageInterface; use crate::package::RootPackageInterface; use crate::repository::RepositoryFactory; @@ -106,7 +106,7 @@ impl HomeCommand { 'repos: for repo in &repos { for package in repo.find_packages(&package_name, None) { package_exists = true; - if let Some(complete_pkg) = package.as_complete_package_interface() { + if let Some(complete_pkg) = package.as_complete() { if self.handle_package(complete_pkg, show_homepage, show_only) { handled = true; break 'repos; @@ -139,7 +139,7 @@ impl HomeCommand { fn handle_package( &mut self, - package: &dyn CompletePackageInterface, + package: CompletePackageInterfaceHandle, show_homepage: bool, show_only: bool, ) -> bool { @@ -208,7 +208,7 @@ impl HomeCommand { let composer = crate::command::composer_full(&composer); let mut repos: Vec<Box<dyn RepositoryInterface>> = vec![]; repos.push(Box::new(RootPackageRepository::new( - composer.get_package().clone_box(), + composer.get_package().clone(), ))); // TODO(phase-b): get_local_repository / get_repositories return shared refs; needs Rc<dyn ...> migration return Ok(repos); diff --git a/crates/shirabe/src/command/licenses_command.rs b/crates/shirabe/src/command/licenses_command.rs index 961f70b..2bcecd5 100644 --- a/crates/shirabe/src/command/licenses_command.rs +++ b/crates/shirabe/src/command/licenses_command.rs @@ -113,7 +113,11 @@ impl LicensesCommand { if input.get_option("no-dev").as_bool().unwrap_or(false) { RepositoryUtils::filter_required_packages( &repo.get_packages(), - composer.get_package(), + composer + .get_package() + .as_rc() + .borrow() + .as_package_interface(), false, vec![], ) @@ -123,11 +127,8 @@ impl LicensesCommand { }; let _ = composer.get_package(); - // TODO(phase-b): convert BasePackage trait objects to PackageInterface for sorting. - let pkg_pi: Vec<Box<dyn crate::package::PackageInterface>> = packages - .into_iter() - .map(|p| p.clone_package_box()) - .collect(); + let pkg_pi: Vec<crate::package::PackageInterfaceHandle> = + packages.into_iter().map(|p| p.into()).collect(); let packages = PackageSorter::sort_packages_alphabetically(pkg_pi); let io = self.get_io(); @@ -158,7 +159,9 @@ impl LicensesCommand { PhpMixed::String("Licenses".to_string()), ]); for package in &packages { - let link = PackageInfo::get_view_source_or_homepage_url(package.as_ref()); + let link = PackageInfo::get_view_source_or_homepage_url( + package.as_rc().borrow().as_package_interface(), + ); let name = if let Some(link) = link { format!( "<href={}>{}</>", @@ -168,9 +171,7 @@ impl LicensesCommand { } else { package.get_pretty_name().to_string() }; - let pkg_licenses = if let Some(complete_pkg) = - package.as_any().downcast_ref::<CompletePackage>() - { + let pkg_licenses = if let Some(complete_pkg) = package.as_complete_package() { complete_pkg.get_license() } else { vec![] @@ -199,9 +200,7 @@ impl LicensesCommand { let mut dependencies: IndexMap<String, IndexMap<String, PhpMixed>> = IndexMap::new(); for package in &packages { - let pkg_licenses = if let Some(complete_pkg) = - package.as_any().downcast_ref::<CompletePackage>() - { + let pkg_licenses = if let Some(complete_pkg) = package.as_complete_package() { complete_pkg.get_license() } else { vec![] @@ -268,9 +267,7 @@ impl LicensesCommand { "summary" => { let mut used_licenses: IndexMap<String, i64> = IndexMap::new(); for package in &packages { - let mut licenses = if let Some(complete_pkg) = - package.as_any().downcast_ref::<CompletePackage>() - { + let mut licenses = if let Some(complete_pkg) = package.as_complete_package() { complete_pkg.get_license() } else { vec![] diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs index fa25fa3..a268469 100644 --- a/crates/shirabe/src/command/package_discovery_trait.rs +++ b/crates/shirabe/src/command/package_discovery_trait.rs @@ -574,7 +574,10 @@ pub trait PackageDiscoveryTrait { message: sprintf( &format!( "Package %s has requirements incompatible with your PHP version, PHP extensions and Composer version{}", - self.get_platform_exception_details(&*candidate, platform_repo), + self.get_platform_exception_details( + candidate.as_rc().borrow().as_package_interface(), + platform_repo, + ), ), &[PhpMixed::String(name.to_string())], ), @@ -610,8 +613,10 @@ pub trait PackageDiscoveryTrait { message: format!( "Package {} exists in {} and {} which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo's packages are not installable. See https://getcomposer.org/repoprio for details and assistance.", name, - all_repos_package.get_repository().unwrap().get_repo_name(), - package.get_repository().unwrap().get_repo_name(), + // TODO(phase-c): the originating repository names need the handle's + // repository back-reference (phase-c handoff item #1). + "a higher priority repository", + "a lower priority repository", ), code: 0, } @@ -666,7 +671,10 @@ pub trait PackageDiscoveryTrait { message: sprintf( &format!( "Could not find package %s in any version matching your PHP version, PHP extensions and Composer version{}%s", - self.get_platform_exception_details(&*candidate, platform_repo), + self.get_platform_exception_details( + candidate.as_rc().borrow().as_package_interface(), + platform_repo, + ), ), &[ PhpMixed::String(name.to_string()), @@ -772,7 +780,9 @@ pub trait PackageDiscoveryTrait { if fixed { package.get_pretty_version().to_string() } else { - version_selector.find_recommended_require_version(&*package)? + version_selector.find_recommended_require_version( + package.as_rc().borrow().as_package_interface(), + )? }, )) } @@ -798,9 +808,7 @@ pub trait PackageDiscoveryTrait { Ok(r) => r, Err(e) => { // PHP: if ($e instanceof \LogicException) throw $e; - // TODO(phase-b): downcast to LogicException - let is_logic: bool = todo!("e instanceof LogicException"); - if is_logic { + if e.downcast_ref::<LogicException>().is_some() { return Err(e); } @@ -891,7 +899,7 @@ pub trait PackageDiscoveryTrait { let mut platform_pkg_version = platform_pkg.get_pretty_version().to_string(); let platform_extra = platform_pkg.get_extra(); let has_config_platform = platform_extra.contains_key("config.platform"); - let is_complete = platform_pkg.as_complete_package_interface().is_some(); + let is_complete = platform_pkg.as_complete().is_some(); if has_config_platform && is_complete { // TODO(phase-b): platform_pkg.get_description() via CompletePackageInterface platform_pkg_version = format!( diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index d66a830..1e3114b 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -16,9 +16,6 @@ use crate::dependency_resolver::Transaction; use crate::dependency_resolver::operation::InstallOperation; use crate::dependency_resolver::operation::UninstallOperation; use crate::io::IOInterface; -use crate::package::AliasPackage; -use crate::package::BasePackage; -use crate::package::PackageInterface; use crate::package::base_package; use crate::plugin::CommandEvent; use crate::plugin::PluginEvents; @@ -73,7 +70,7 @@ impl ReinstallCommand { let repository_manager = composer.get_repository_manager().clone(); let repository_manager = repository_manager.borrow(); let local_repo = repository_manager.get_local_repository(); - let mut packages_to_reinstall: Vec<Box<dyn crate::package::PackageInterface>> = vec![]; + let mut packages_to_reinstall: Vec<crate::package::PackageInterfaceHandle> = vec![]; let mut package_names_to_reinstall: Vec<String> = vec![]; let type_option = input.get_option("type"); @@ -100,8 +97,8 @@ impl ReinstallCommand { }) .unwrap_or_default(); for package in local_repo.get_canonical_packages() { - if filter_types.contains(&package.get_type().to_string()) { - package_names_to_reinstall.push(package.get_name().to_string()); + if filter_types.contains(&package.get_type()) { + package_names_to_reinstall.push(package.get_name()); packages_to_reinstall.push(package); } } @@ -126,9 +123,9 @@ impl ReinstallCommand { let pattern_regexp = base_package::package_name_to_regexp(pattern); let mut matched = false; for package in local_repo.get_canonical_packages() { - if Preg::is_match(&pattern_regexp, package.get_name()).unwrap_or(false) { + if Preg::is_match(&pattern_regexp, &package.get_name()).unwrap_or(false) { matched = true; - package_names_to_reinstall.push(package.get_name().to_string()); + package_names_to_reinstall.push(package.get_name()); packages_to_reinstall.push(package); } } @@ -149,14 +146,12 @@ impl ReinstallCommand { } let present_packages = local_repo.get_packages(); - let result_packages: Vec<Box<dyn PackageInterface>> = present_packages - .iter() - .map(|p| p.clone_package_box()) - .collect(); - let present_packages: Vec<Box<dyn PackageInterface>> = present_packages + let result_packages: Vec<crate::package::PackageInterfaceHandle> = + present_packages.iter().map(|p| p.clone().into()).collect(); + let present_packages: Vec<crate::package::PackageInterfaceHandle> = present_packages .into_iter() - .filter(|package| !package_names_to_reinstall.contains(&package.get_name().to_string())) - .map(|p| p.clone_package_box()) + .filter(|package| !package_names_to_reinstall.contains(&package.get_name())) + .map(|p| p.into()) .collect(); let transaction = Transaction::new(present_packages, result_packages); @@ -165,24 +160,19 @@ impl ReinstallCommand { let mut install_order = indexmap::IndexMap::new(); for (index, op) in install_operations.iter().enumerate() { if let Some(install_op) = op.as_any().downcast_ref::<InstallOperation>() { - if install_op - .get_package() - .as_any() - .downcast_ref::<AliasPackage>() - .is_none() - { - install_order.insert(install_op.get_package().get_name().to_string(), index); + if install_op.get_package().as_alias().is_none() { + install_order.insert(install_op.get_package().get_name(), index); } } } uninstall_operations.sort_by(|a, b| { let a_order = install_order - .get(a.get_package().get_name()) + .get(&a.get_package().get_name()) .copied() .unwrap_or(0); let b_order = install_order - .get(b.get_package().get_name()) + .get(&b.get_package().get_name()) .copied() .unwrap_or(0); b_order.cmp(&a_order) diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index 15bff24..f0dce6b 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -27,10 +27,7 @@ use crate::installer::InstallerEvents; use crate::io::IOInterface; use crate::json::JsonFile; use crate::json::JsonManipulator; -use crate::package::AliasPackage; -use crate::package::CompletePackageInterface; -use crate::package::PackageInterface; -use crate::package::base_package::{self, BasePackage}; +use crate::package::base_package; use crate::package::loader::ArrayLoader; use crate::package::loader::RootPackageLoader; use crate::package::version::VersionParser; @@ -338,16 +335,16 @@ impl RequireCommand { continue; } - // TODO(phase-b): find_packages returns Vec<Box<dyn BasePackage>> but - // get_most_current_version expects Vec<Box<dyn PackageInterface>>; needs trait - // upcasting once Rust supports it stably or an adapter. - let _ = self.get_repos().find_packages(name, None); - let pkg: Option<Box<dyn PackageInterface>> = - PackageSorter::get_most_current_version(todo!( - "convert Vec<Box<dyn BasePackage>> to Vec<Box<dyn PackageInterface>>" - )); - // TODO(phase-b): instanceof CompletePackageInterface downcast - let pkg_as_complete: Option<&dyn CompletePackageInterface> = None; + let found_packages: Vec<crate::package::PackageInterfaceHandle> = self + .get_repos() + .find_packages(name, None) + .into_iter() + .map(|p| p.into()) + .collect(); + let pkg: Option<crate::package::PackageInterfaceHandle> = + PackageSorter::get_most_current_version(found_packages); + let pkg_as_complete: Option<crate::package::CompletePackageInterfaceHandle> = + pkg.as_ref().and_then(|p| p.as_complete()); if let Some(pkg_complete) = pkg_as_complete { let lowered: Vec<String> = array_map(|s: &String| strtolower(s), &pkg_complete.get_keywords()); @@ -713,8 +710,8 @@ impl RequireCommand { .map(|(k, v)| (k.clone(), PhpMixed::String(v.clone()))) .collect(); let new_links = loader.parse_links( - root_package.get_name(), - root_package.get_pretty_version(), + &root_package.get_name(), + &root_package.get_pretty_version(), base_package::SUPPORTED_LINK_TYPES .get(require_key) .map(|t| t.method) @@ -742,7 +739,7 @@ impl RequireCommand { ); let _ = RootPackageLoader::extract_stability_flags( requirements, - root_package.get_minimum_stability(), + &root_package.get_minimum_stability(), root_package.get_stability_flags().clone(), ); // unset($stabilityFlags, $references); @@ -962,12 +959,8 @@ impl RequireCommand { package_name, crate::repository::FindPackageConstraint::String("*".to_string()), ); - // TODO(phase-b): `$package instanceof AliasPackage` downcast - let package_as_alias: Option<&AliasPackage> = None; - while let Some(_alias) = package_as_alias { - // TODO(phase-b): get_alias_of returns &dyn BasePackage; clone is not available - // and BasePackage is not PackageInterface (the latter is a super-trait). - package = todo!("upcast alias.get_alias_of() to Box<dyn BasePackage>"); + while let Some(alias) = package.as_ref().and_then(|p| p.as_alias()) { + package = Some(alias.get_alias_of().into()); } let package = match package { @@ -976,18 +969,13 @@ impl RequireCommand { }; if fixed { - requirements.insert( - package_name.clone(), - package.get_pretty_version().to_string(), - ); + requirements.insert(package_name.clone(), package.get_pretty_version()); } else { - // TODO(phase-b): trait upcast from &dyn BasePackage to &dyn PackageInterface - // is not yet stable in Rust; use explicit as_package_interface() when available. - let pkg_as_pi: &dyn PackageInterface = - todo!("upcast &dyn BasePackage to &dyn PackageInterface"); requirements.insert( package_name.clone(), - version_selector.find_recommended_require_version(pkg_as_pi)?, + version_selector.find_recommended_require_version( + package.as_rc().borrow().as_package_interface(), + )?, ); } self.get_io().write_error3( @@ -1057,7 +1045,7 @@ impl RequireCommand { { let stability_flags = RootPackageLoader::extract_stability_flags( &requirements, - composer.get_package().get_minimum_stability(), + &composer.get_package().get_minimum_stability(), IndexMap::new(), ); let stability_flags_clone = stability_flags.clone(); diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs index 8826421..c7c2f9d 100644 --- a/crates/shirabe/src/command/show_command.rs +++ b/crates/shirabe/src/command/show_command.rs @@ -23,7 +23,6 @@ use crate::dependency_resolver::PolicyInterface; use crate::filter::platform_requirement_filter::PlatformRequirementFilterInterface; use crate::io::IOInterface; use crate::json::JsonFile; -use crate::package::BasePackage; use crate::package::CompletePackageInterface; use crate::package::Link; use crate::package::PackageInterface; @@ -200,7 +199,7 @@ impl ShowCommand { let mut locked_repo: Option<Box<dyn RepositoryInterface>> = None; // The single-package $package binding from PHP gets surfaced here. - let mut single_package: Option<Box<dyn CompletePackageInterface>> = None; + let mut single_package: Option<crate::package::CompletePackageInterfaceHandle> = None; let mut versions_map: IndexMap<String, String> = IndexMap::new(); let installed_repo: Box<InstalledRepository>; let repos: Box<dyn RepositoryInterface>; @@ -210,11 +209,12 @@ impl ShowCommand { && input.get_option("locked").as_bool() != Some(true) { let _rc = self.require_composer(None, None)?; - let package = crate::command::composer_full(&_rc) - .get_package() - .clone_box(); + // TODO(phase-c): composer.get_package() returns &dyn RootPackageInterface, not a + // RootPackageInterfaceHandle, so it cannot be shared into RootPackageRepository::new yet. + let package: crate::package::RootPackageInterfaceHandle = + todo!("share composer.get_package() as a RootPackageInterfaceHandle"); if input.get_option("name-only").as_bool() == Some(true) { - self.get_io().write(package.get_name()); + self.get_io().write(&package.get_name()); return Ok(0); } @@ -226,13 +226,13 @@ impl ShowCommand { .into()); } installed_repo = Box::new(InstalledRepository::new(vec![Box::new( - RootPackageRepository::new(package.clone_box()), + RootPackageRepository::new(package.clone()), )])); repos = Box::new(InstalledRepository::new(vec![Box::new( - RootPackageRepository::new(package.clone_box()), + RootPackageRepository::new(package.clone()), )])); - // TODO(phase-b): need to convert Box<dyn BasePackage> to Box<dyn CompletePackageInterface> - single_package = todo!("convert package to Box<dyn CompletePackageInterface>"); + // TODO(phase-c): need to convert the root package handle to a CompletePackageInterfaceHandle + single_package = todo!("convert package to CompletePackageInterfaceHandle"); } else if input.get_option("platform").as_bool() == Some(true) { installed_repo = Box::new(InstalledRepository::new(vec![Box::new( make_platform_repo()?, @@ -382,7 +382,9 @@ impl ShowCommand { let root_repo: Box<dyn RepositoryInterface> = if input.get_option("self").as_bool() == Some(true) { - Box::new(RootPackageRepository::new(root_pkg.clone_box())) + Box::new(RootPackageRepository::new( + composer_local.get_package().clone(), + )) } else { Box::new(InstalledArrayRepository::new()?) }; @@ -394,14 +396,12 @@ impl ShowCommand { .get_packages(); let packages = RepositoryUtils::filter_required_packages( &local_packages, - root_pkg as &dyn PackageInterface, + root_pkg.as_rc().borrow().as_package_interface(), false, Vec::new(), ); - let cloned: Vec<Box<dyn PackageInterface>> = packages - .into_iter() - .map(|p| p.clone_package_box()) - .collect(); + let cloned: Vec<crate::package::PackageInterfaceHandle> = + packages.into_iter().map(|p| p.into()).collect(); installed_repo = Box::new(InstalledRepository::new(vec![ root_repo.clone_box(), Box::new(InstalledArrayRepository::new_with_packages(cloned)?), @@ -470,10 +470,7 @@ impl ShowCommand { // show single package or single version if let Some(ref pkg) = single_package { - versions_map.insert( - pkg.get_pretty_version().to_string(), - pkg.get_version().to_string(), - ); + versions_map.insert(pkg.get_pretty_version(), pkg.get_version()); } else if let Some(ref pf) = package_filter { if !pf.contains('*') { let (matched_package, vers) = @@ -482,7 +479,7 @@ impl ShowCommand { if let Some(ref pkg) = matched_package { if input.get_option("direct").as_bool() == Some(true) { if !in_array( - PhpMixed::String(pkg.get_name().to_string()), + PhpMixed::String(pkg.get_name()), &PhpMixed::List( self.get_root_requires() .into_iter() @@ -547,11 +544,13 @@ impl ShowCommand { let mut exit_code: i64 = 0; if input.get_option("tree").as_bool() == Some(true) { + let package_ref = package.as_rc().borrow(); let array_tree = self.generate_package_tree( - package.as_package_interface(), + package_ref.as_package_interface(), &*installed_repo, &*repos, ); + drop(package_ref); if format == "json" { let mut wrapper: IndexMap<String, PhpMixed> = IndexMap::new(); @@ -577,10 +576,11 @@ impl ShowCommand { return Ok(exit_code); } - let mut latest_package: Option<Box<dyn PackageInterface>> = None; + let mut latest_package: Option<crate::package::PackageInterfaceHandle> = None; if input.get_option("latest").as_bool() == Some(true) { + let package_ref = package.as_rc().borrow(); latest_package = self.find_latest_package( - package.as_package_interface(), + package_ref.as_package_interface(), composer.as_ref().unwrap(), &platform_repo, input.get_option("major-only").as_bool().unwrap_or(false), @@ -600,13 +600,13 @@ impl ShowCommand { && (latest_package .as_ref() .unwrap() - .as_complete_package_interface() + .as_complete() .map_or(true, |c| !c.is_abandoned())) { exit_code = 1; } if input.get_option("path").as_bool() == Some(true) { - self.get_io().write_no_newline(package.get_name()); + self.get_io().write_no_newline(&package.get_name()); let path = { let composer_ref = composer.as_ref().unwrap(); // TODO(phase-b): get_installation_manager wants &mut Composer; PHP shares @@ -625,21 +625,25 @@ impl ShowCommand { return Ok(exit_code); } + let package_ref = package.as_rc().borrow(); + let package_dyn = package_ref + .as_complete_package_interface() + .expect("single_package is a CompletePackageInterface"); + let latest_ref = latest_package.as_ref().map(|p| p.as_rc().borrow()); + let latest_dyn: Option<&dyn PackageInterface> = + latest_ref.as_ref().map(|r| r.as_package_interface()); if format == "json" { self.print_package_info_as_json( - &**package, + package_dyn, &versions_map, &*installed_repo, - latest_package.as_deref(), + latest_dyn, )?; } else { - self.print_package_info( - &**package, - &versions_map, - &*installed_repo, - latest_package.as_deref(), - )?; + self.print_package_info(package_dyn, &versions_map, &*installed_repo, latest_dyn)?; } + drop(latest_ref); + drop(package_ref); return Ok(exit_code); } @@ -656,7 +660,7 @@ impl ShowCommand { let mut array_tree: Vec<IndexMap<String, PhpMixed>> = Vec::new(); for package in packages.iter() { if in_array( - PhpMixed::String(package.get_name().to_string()), + PhpMixed::String(package.get_name()), &PhpMixed::List( root_requires .iter() @@ -665,8 +669,9 @@ impl ShowCommand { ), true, ) { + let package_ref = package.as_rc().borrow(); array_tree.push(self.generate_package_tree( - &**package, + package_ref.as_package_interface(), &*installed_repo, &*repos, )); @@ -741,28 +746,28 @@ impl ShowCommand { for package in repo.get_packages() { let existing = packages .get(&type_owned) - .and_then(|m| m.get(package.get_name())); + .and_then(|m| m.get(&package.get_name())); let need_replace = match existing { None => true, Some(PackageOrName::Name(_)) => true, Some(PackageOrName::Pkg(existing)) => { - version_compare(existing.get_version(), package.get_version(), "<") + version_compare(&existing.get_version(), &package.get_version(), "<") } }; if need_replace { - let mut p: Box<dyn PackageInterface> = package.clone_box(); - while let Some(alias) = p.as_alias_package() { - p = alias.get_alias_of().clone_box(); + let mut p: crate::package::PackageInterfaceHandle = package.clone().into(); + while let Some(alias) = p.as_alias() { + p = alias.get_alias_of().into(); } let matches_filter = match &package_filter_regex { None => true, - Some(r) => Preg::is_match(r, p.get_name())?, + Some(r) => Preg::is_match(r, &p.get_name())?, }; if matches_filter { let matches_list = match &package_list_filter { None => true, Some(list) => in_array( - PhpMixed::String(p.get_name().to_string()), + PhpMixed::String(p.get_name()), &PhpMixed::List( list.iter() .map(|s| Box::new(PhpMixed::String(s.clone()))) @@ -775,7 +780,7 @@ impl ShowCommand { packages .entry(type_owned.clone()) .or_insert_with(IndexMap::new) - .insert(p.get_name().to_string(), PackageOrName::Pkg(p)); + .insert(p.get_name(), PackageOrName::Pkg(p)); } } } @@ -785,7 +790,7 @@ impl ShowCommand { packages .entry(type_owned.clone()) .or_insert_with(IndexMap::new) - .insert(name.clone(), PackageOrName::Pkg(p.clone_package_box())); + .insert(name.clone(), PackageOrName::Pkg(p.clone().into())); } } } @@ -809,7 +814,8 @@ impl ShowCommand { "{^(?:%s)$}iD", ); let indent = if show_all_types { " " } else { "" }; - let mut latest_packages: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); + let mut latest_packages: IndexMap<String, crate::package::PackageInterfaceHandle> = + IndexMap::new(); let mut exit_code: i64 = 0; let mut view_data: IndexMap<String, Vec<IndexMap<String, PhpMixed>>> = IndexMap::new(); let mut view_meta_data: IndexMap<String, ViewMetaData> = IndexMap::new(); @@ -835,10 +841,11 @@ impl ShowCommand { if show_latest && *show_version { for package_or_name in type_packages.values() { if let PackageOrName::Pkg(package) = package_or_name { - if !Preg::is_match(&ignored_packages_regex, package.get_pretty_name())? + if !Preg::is_match(&ignored_packages_regex, &package.get_pretty_name())? { + let package_ref = package.as_rc().borrow(); let latest = self.find_latest_package( - &**package, + package_ref.as_package_interface(), composer.as_ref().unwrap(), &platform_repo, show_major_only, @@ -846,12 +853,12 @@ impl ShowCommand { show_patch_only, &*platform_req_filter, )?; + drop(package_ref); if latest.is_none() { continue; } - latest_packages - .insert(package.get_pretty_name().to_string(), latest.unwrap()); + latest_packages.insert(package.get_pretty_name(), latest.unwrap()); } } } @@ -885,9 +892,9 @@ impl ShowCommand { let mut package_view_data: IndexMap<String, PhpMixed> = IndexMap::new(); if let PackageOrName::Pkg(package) = package_or_name { let latest_package = if show_latest - && latest_packages.contains_key(package.get_pretty_name()) + && latest_packages.contains_key(&package.get_pretty_name()) { - latest_packages.get(package.get_pretty_name()) + latest_packages.get(&package.get_pretty_name()) } else { None }; @@ -896,9 +903,7 @@ impl ShowCommand { let mut package_is_up_to_date = if let Some(latest) = latest_package { latest.get_full_pretty_version(true, 0) == package.get_full_pretty_version(true, 0) - && latest - .as_complete_package_interface() - .map_or(true, |c| !c.is_abandoned()) + && latest.as_complete().map_or(true, |c| !c.is_abandoned()) } else { false }; @@ -906,7 +911,7 @@ impl ShowCommand { package_is_up_to_date = package_is_up_to_date || (latest_package.is_none() && show_major_only); let package_is_ignored = - Preg::is_match(&ignored_packages_regex, package.get_pretty_name())?; + Preg::is_match(&ignored_packages_regex, &package.get_pretty_name())?; if input.get_option("outdated").as_bool() == Some(true) && (package_is_up_to_date || package_is_ignored) { @@ -921,12 +926,12 @@ impl ShowCommand { package_view_data.insert( "name".to_string(), - PhpMixed::String(package.get_pretty_name().to_string()), + PhpMixed::String(package.get_pretty_name()), ); package_view_data.insert( "direct-dependency".to_string(), PhpMixed::Bool(in_array( - PhpMixed::String(package.get_name().to_string()), + PhpMixed::String(package.get_name()), &PhpMixed::List( self.get_root_requires() .into_iter() @@ -940,9 +945,9 @@ impl ShowCommand { { package_view_data.insert( "homepage".to_string(), - match package.as_complete_package_interface() { + match package.as_complete() { Some(c) => match c.get_homepage() { - Some(h) => PhpMixed::String(h.to_string()), + Some(h) => PhpMixed::String(h), None => PhpMixed::Null, }, None => PhpMixed::Null, @@ -950,7 +955,9 @@ impl ShowCommand { ); package_view_data.insert( "source".to_string(), - match PackageInfo::get_view_source_url(&**package) { + match PackageInfo::get_view_source_url( + package.as_rc().borrow().as_package_interface(), + ) { Some(s) => PhpMixed::String(s), None => PhpMixed::Null, }, @@ -958,8 +965,7 @@ impl ShowCommand { } name_length = name_length.max(package.get_pretty_name().len()); if write_version { - let mut version_str = - package.get_full_pretty_version(true, 0).to_string(); + let mut version_str = package.get_full_pretty_version(true, 0); if format == "text" { version_str = version_str.trim_start_matches('v').to_string(); } @@ -995,13 +1001,19 @@ impl ShowCommand { } if write_latest && latest_package.is_some() { let latest = latest_package.unwrap(); - let mut latest_version_str = - latest.get_full_pretty_version(true, 0).to_string(); + let mut latest_version_str = latest.get_full_pretty_version(true, 0); if format == "text" { latest_version_str = latest_version_str.trim_start_matches('v').to_string(); } - let update_status = Self::get_update_status(&**latest, &**package); + let latest_ref = latest.as_rc().borrow(); + let package_ref = package.as_rc().borrow(); + let update_status = Self::get_update_status( + latest_ref.as_package_interface(), + package_ref.as_package_interface(), + ); + drop(package_ref); + drop(latest_ref); latest_length = latest_length.max(latest_version_str.len()); package_view_data .insert("latest".to_string(), PhpMixed::String(latest_version_str)); @@ -1033,10 +1045,10 @@ impl ShowCommand { latest_length = latest_length.max("[none matched]".len()); } if write_description { - if let Some(c) = package.as_complete_package_interface() { + if let Some(c) = package.as_complete() { package_view_data.insert( "description".to_string(), - PhpMixed::String(c.get_description().unwrap_or("").to_string()), + PhpMixed::String(c.get_description().unwrap_or_default()), ); } } @@ -1061,7 +1073,7 @@ impl ShowCommand { let mut package_is_abandoned: PhpMixed = PhpMixed::Bool(false); if let Some(latest) = latest_package { - if let Some(c) = latest.as_complete_package_interface() { + if let Some(c) = latest.as_complete() { if c.is_abandoned() { let replacement_package_name = c.get_replacement_package(); let replacement = if let Some(ref rp) = replacement_package_name @@ -1080,7 +1092,7 @@ impl ShowCommand { PhpMixed::String(package_warning), ); package_is_abandoned = match replacement_package_name { - Some(rp) => PhpMixed::String(rp.to_string()), + Some(rp) => PhpMixed::String(rp), None => PhpMixed::Bool(true), }; } @@ -1482,7 +1494,7 @@ impl ShowCommand { name: &str, version: PhpMixed, ) -> anyhow::Result<( - Option<Box<dyn CompletePackageInterface>>, + Option<crate::package::CompletePackageInterfaceHandle>, IndexMap<String, String>, )> { let name = strtolower(name); @@ -1507,7 +1519,7 @@ impl ShowCommand { repository_set.allow_installed_repositories(true); repository_set.add_repository(repos.clone_box())?; - let mut matched_package: Option<Box<dyn PackageInterface>> = None; + let mut matched_package: Option<crate::package::PackageInterfaceHandle> = None; let mut versions: IndexMap<String, String> = IndexMap::new(); let mut pool = if PlatformRepository::is_platform_package(&name) { repository_set.create_pool_with_all_packages()? @@ -1518,33 +1530,32 @@ impl ShowCommand { let mut literals: Vec<i64> = Vec::new(); for package in matches.iter() { // avoid showing the 9999999-dev alias if the default branch has no branch-alias set - let mut p: Box<dyn PackageInterface> = package.clone_box(); - if let Some(alias) = p.as_alias_package() { + let mut p: crate::package::PackageInterfaceHandle = package.clone().into(); + if let Some(alias) = p.as_alias() { if p.get_version() == VersionParser::DEFAULT_BRANCH_ALIAS { - p = alias.get_alias_of().clone_box(); + p = alias.get_alias_of().into(); } } // select an exact match if it is in the installed repo and no specific version was required - if version.is_null() && installed_repo.has_package(&*p) { - matched_package = Some(p.clone_package_box()); + if version.is_null() + && installed_repo.has_package(p.as_rc().borrow().as_package_interface()) + { + matched_package = Some(p.clone()); } - versions.insert( - p.get_pretty_version().to_string(), - p.get_version().to_string(), - ); + versions.insert(p.get_pretty_version(), p.get_version()); literals.push(p.get_id()); } // select preferred package according to policy rules if matched_package.is_none() && !literals.is_empty() { let preferred = policy.select_preferred_packages(&pool, literals.clone(), None); - matched_package = Some(pool.literal_to_package(preferred[0]).clone_package_box()); + matched_package = Some(pool.literal_to_package(preferred[0]).into()); } if let Some(ref mp) = matched_package { - if mp.as_complete_package_interface().is_none() { + if mp.as_complete().is_none() { return Err(LogicException { message: format!( "ShowCommand::getPackage can only work with CompletePackageInterface, but got {}", @@ -1556,10 +1567,8 @@ impl ShowCommand { } } - // TODO(phase-b): need a Box<dyn PackageInterface> -> Box<dyn CompletePackageInterface> - // conversion. PHP relies on duck typing; placeholder None. - let _ = matched_package; - Ok((None, versions)) + let matched_package = matched_package.and_then(|mp| mp.as_complete()); + Ok((matched_package, versions)) } /// Prints package info. @@ -1760,7 +1769,7 @@ impl ShowCommand { let installed_packages = installed_repo.find_packages(package.get_name(), None); if !installed_packages.is_empty() { for installed_package in installed_packages.iter() { - let installed_version = installed_package.get_pretty_version().to_string(); + let installed_version = installed_package.get_pretty_version(); let key_map: IndexMap<String, String> = versions_keys .iter() .map(|v| (v.clone(), v.clone())) @@ -2556,7 +2565,7 @@ impl ShowCommand { minor_only: bool, patch_only: bool, platform_req_filter: &dyn PlatformRequirementFilterInterface, - ) -> anyhow::Result<Option<Box<dyn PackageInterface>>> { + ) -> anyhow::Result<Option<crate::package::PackageInterfaceHandle>> { // find the latest version allowed in this repo set let name = package.get_name(); // TODO(phase-b): VersionSelector::new wants RepositorySet by value, but get_repository_set @@ -2564,7 +2573,7 @@ impl ShowCommand { let _ = self.get_repository_set(composer)?; let composer_ref = crate::command::composer_full(composer); let placeholder_rs = RepositorySet::new( - composer_ref.get_package().get_minimum_stability(), + &composer_ref.get_package().get_minimum_stability(), composer_ref.get_package().get_stability_flags().clone(), Vec::new(), IndexMap::new(), @@ -2676,8 +2685,8 @@ impl ShowCommand { PhpMixed::Bool(true), )?; while let Some(ref c) = candidate { - if let Some(alias) = c.as_alias_package() { - candidate = Some(alias.get_alias_of().clone_box()); + if let Some(alias) = c.as_alias() { + candidate = Some(alias.get_alias_of().into()); } else { break; } @@ -2694,7 +2703,7 @@ impl ShowCommand { if self.repository_set.is_none() { // TODO(phase-b): RepositorySet::with_stability_and_flags — using new() placeholder. let mut rs = RepositorySet::new( - composer.get_package().get_minimum_stability(), + &composer.get_package().get_minimum_stability(), composer.get_package().get_stability_flags().clone(), Vec::new(), IndexMap::new(), @@ -2757,7 +2766,7 @@ impl ShowCommand { #[derive(Debug)] pub enum PackageOrName { - Pkg(Box<dyn PackageInterface>), + Pkg(crate::package::PackageInterfaceHandle), Name(String), } diff --git a/crates/shirabe/src/command/status_command.rs b/crates/shirabe/src/command/status_command.rs index 190884a..69c36a1 100644 --- a/crates/shirabe/src/command/status_command.rs +++ b/crates/shirabe/src/command/status_command.rs @@ -120,18 +120,19 @@ impl StatusCommand { let target_dir = composer .get_installation_manager() .borrow_mut() - .get_install_path(package.as_ref()); + .get_install_path(package.as_rc().borrow().as_package_interface()); let target_dir = match target_dir { Some(d) => d, None => continue, }; // TODO(phase-b): downloader borrow lifetime tied to dm.borrow() temporary; restructure later. let dm_borrow = dm.borrow(); - let downloader: &dyn crate::downloader::DownloaderInterface = - match dm_borrow.get_downloader_for_package(package.as_ref())? { - Some(d) => d, - None => continue, - }; + let downloader: &dyn crate::downloader::DownloaderInterface = match dm_borrow + .get_downloader_for_package(package.as_rc().borrow().as_package_interface())? + { + Some(d) => d, + None => continue, + }; // TODO(phase-b): isinstance checks using ChangeReportInterface/VcsCapableDownloaderInterface/DvcsDownloaderInterface if let Some(change_reporter) = downloader.as_change_report_interface() { @@ -142,16 +143,20 @@ impl StatusCommand { ); } - if let Some(changes) = - change_reporter.get_local_changes(package.as_ref(), &target_dir)? - { + if let Some(changes) = change_reporter.get_local_changes( + package.as_rc().borrow().as_package_interface(), + &target_dir, + )? { errors.insert(target_dir.clone(), changes); } } if let Some(vcs_downloader) = downloader.as_vcs_capable_downloader_interface() { if vcs_downloader - .get_vcs_reference(package.as_ref(), target_dir.clone()) + .get_vcs_reference( + package.as_rc().borrow().as_package_interface(), + target_dir.clone(), + ) .is_some() { let previous_ref = match package.get_installation_source().as_deref() { @@ -160,8 +165,10 @@ impl StatusCommand { _ => None, }; - let current_version = - guesser.guess_version(&dumper.dump(package.as_ref()), &target_dir)?; + let current_version = guesser.guess_version( + &dumper.dump(package.as_rc().borrow().as_package_interface()), + &target_dir, + )?; if let (Some(prev_ref), Some(cur_version)) = (&previous_ref, ¤t_version) { if cur_version.commit.as_deref() != Some(prev_ref.as_str()) @@ -195,9 +202,10 @@ impl StatusCommand { } if let Some(dvcs_downloader) = downloader.as_dvcs_downloader_interface() { - if let Some(unpushed) = - dvcs_downloader.get_unpushed_changes(package.as_ref(), target_dir.clone()) - { + if let Some(unpushed) = dvcs_downloader.get_unpushed_changes( + package.as_rc().borrow().as_package_interface(), + target_dir.clone(), + ) { unpushed_changes.insert(target_dir, unpushed); } } diff --git a/crates/shirabe/src/command/suggests_command.rs b/crates/shirabe/src/command/suggests_command.rs index 6b617b1..8166c1e 100644 --- a/crates/shirabe/src/command/suggests_command.rs +++ b/crates/shirabe/src/command/suggests_command.rs @@ -47,9 +47,12 @@ impl SuggestsCommand { let composer = self.require_composer(None, None)?; let mut composer = crate::command::composer_full_mut(&composer); - let mut installed_repos: Vec<Box<dyn RepositoryInterface>> = vec![Box::new( - RootPackageRepository::new(composer.get_package().clone_box()), - )]; + // TODO(phase-c): composer.get_package() returns &dyn RootPackageInterface, not a + // RootPackageInterfaceHandle, so it cannot be shared into RootPackageRepository::new yet. + let root_package_handle: crate::package::RootPackageInterfaceHandle = + todo!("share composer.get_package() as a RootPackageInterfaceHandle"); + let mut installed_repos: Vec<Box<dyn RepositoryInterface>> = + vec![Box::new(RootPackageRepository::new(root_package_handle))]; if composer.get_locker().borrow_mut().is_locked() { // TODO(phase-b): get_platform_overrides returns IndexMap<String, String>; PlatformRepository::new expects IndexMap<String, PhpMixed> @@ -93,24 +96,16 @@ impl SuggestsCommand { let filter = input.get_argument("packages"); let mut packages = RepositoryInterface::get_packages(&installed_repo); - // TODO(phase-b): composer.get_package() returns &dyn RootPackageInterface; pushing into Vec<Box<dyn BasePackage>> requires conversion - let root_pkg_as_base: Box<dyn crate::package::BasePackage> = - todo!("convert RootPackageInterface to Box<dyn BasePackage>"); + // TODO(phase-c): composer.get_package() returns &dyn RootPackageInterface, not a handle, + // so it cannot be shared into the package list yet. + let root_pkg_as_base: crate::package::BasePackageHandle = + todo!("share composer.get_package() as a BasePackageHandle"); packages.push(root_pkg_as_base); for package in &packages { - if !empty(&filter) - && !in_array( - PhpMixed::String(package.get_name().to_string()), - &filter, - false, - ) - { + if !empty(&filter) && !in_array(PhpMixed::String(package.get_name()), &filter, false) { continue; } - // TODO(phase-b): add_suggestions_from_package expects &dyn PackageInterface; BasePackage is a separate trait - reporter.add_suggestions_from_package(todo!( - "convert Box<dyn BasePackage> to &dyn PackageInterface" - )); + reporter.add_suggestions_from_package(package.as_rc().borrow().as_package_interface()); } let mut mode = SuggestedPackagesReporter::MODE_BY_PACKAGE; diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs index 4096272..06984e2 100644 --- a/crates/shirabe/src/command/update_command.rs +++ b/crates/shirabe/src/command/update_command.rs @@ -24,7 +24,6 @@ use crate::console::input::InputOption; use crate::dependency_resolver::request::{self, Request, UpdateAllowTransitiveDeps}; use crate::installer::Installer; use crate::io::IOInterface; -use crate::package::BasePackage; use crate::package::loader::RootPackageLoader; use crate::package::version::VersionParser; use crate::package::version::VersionSelector; @@ -162,7 +161,7 @@ impl UpdateCommand { RootPackageLoader::extract_references(&reqs, root_package.get_references().clone()); let stability_flags = RootPackageLoader::extract_stability_flags( &reqs, - root_package.get_minimum_stability(), + &root_package.get_minimum_stability(), root_package.get_stability_flags().clone(), ); let _ = references; @@ -222,7 +221,7 @@ impl UpdateCommand { } let matches = Preg::is_match_with_indexed_captures( r"{^(\d+\.\d+\.\d+)}", - package.get_version(), + &package.get_version(), )?; let Some(matches) = matches else { continue; @@ -231,18 +230,18 @@ impl UpdateCommand { "~{}", matches.get(1).cloned().unwrap_or_default() ))?; - if temporary_constraints.contains_key(package.get_name()) { + if temporary_constraints.contains_key(&package.get_name()) { let existing = temporary_constraints - .get(package.get_name()) + .get(&package.get_name()) .map(|c| c.clone()) .unwrap(); temporary_constraints.insert( - package.get_name().to_string(), + package.get_name(), // TODO(phase-b): MultiConstraint::create signature todo!("MultiConstraint::create([existing, constraint], true)"), ); } else { - temporary_constraints.insert(package.get_name().to_string(), constraint); + temporary_constraints.insert(package.get_name(), constraint); } } } @@ -493,10 +492,9 @@ impl UpdateCommand { io_interface::NORMAL, ); let mut autocompleter_values: IndexMap<String, String> = IndexMap::new(); - // TODO(phase-b): unify return types — CanonicalPackagesTrait returns - // Vec<Box<dyn PackageInterface>> while RepositoryInterface::get_packages - // returns Vec<Box<dyn BasePackage>>. Use only the locker branch for now. - let installed_packages: Vec<Box<dyn crate::package::PackageInterface>> = + // TODO(phase-c): wire the non-locked branch through get_local_repository().get_packages() + // (returns Vec<BasePackageHandle>); only the locker branch is populated for now. + let installed_packages: Vec<crate::package::PackageInterfaceHandle> = if composer_ref.get_locker().borrow_mut().is_locked() { CanonicalPackagesTrait::get_packages( &composer_ref @@ -515,7 +513,7 @@ impl UpdateCommand { let mut version_selector = self.create_version_selector(composer)?; for package in &installed_packages { if let Some(filter) = &filter { - if !Preg::is_match(filter, package.get_name()).unwrap_or(false) { + if !Preg::is_match(filter, &package.get_name()).unwrap_or(false) { continue; } } @@ -525,7 +523,7 @@ impl UpdateCommand { // TODO(phase-b): derive from stabilityFlags / minimum_stability let stability: &str = "stable"; let latest_version = version_selector.find_best_candidate( - package.get_name(), + &package.get_name(), constraint, stability, None, @@ -538,7 +536,7 @@ impl UpdateCommand { if let Some(latest) = latest_version { if package.get_version() != latest.get_version() || latest.is_dev() { autocompleter_values.insert( - package.get_name().to_string(), + package.get_name(), format!( "<comment>{}</comment> => <comment>{}</comment>", current_version, @@ -622,7 +620,7 @@ impl UpdateCommand { fn create_version_selector(&self, composer: &PartialComposerHandle) -> Result<VersionSelector> { let composer = crate::command::composer_full(composer); let mut repository_set = RepositorySet::new( - composer.get_package().get_minimum_stability(), + &composer.get_package().get_minimum_stability(), composer.get_package().get_stability_flags().clone(), // TODO(phase-b): collect root aliases from composer.get_package().get_aliases() Vec::new(), diff --git a/crates/shirabe/src/command/validate_command.rs b/crates/shirabe/src/command/validate_command.rs index 60514a1..814f09f 100644 --- a/crates/shirabe/src/command/validate_command.rs +++ b/crates/shirabe/src/command/validate_command.rs @@ -216,7 +216,7 @@ impl ValidateCommand { let path = composer .get_installation_manager() .borrow_mut() - .get_install_path(package.as_ref()); + .get_install_path(package.as_rc().borrow().as_package_interface()); let path = match path { Some(p) => p, None => continue, @@ -229,7 +229,7 @@ impl ValidateCommand { self.output_result( io, - package.get_pretty_name(), + &package.get_pretty_name(), &mut dep_errors, &mut dep_warnings, check_publish, diff --git a/crates/shirabe/src/composer.rs b/crates/shirabe/src/composer.rs index 2da4639..fe700cf 100644 --- a/crates/shirabe/src/composer.rs +++ b/crates/shirabe/src/composer.rs @@ -9,7 +9,7 @@ use crate::downloader::DownloadManager; use crate::event_dispatcher::EventDispatcher; use crate::installer::InstallationManager; use crate::package::archiver::ArchiveManager; -use crate::package::{Locker, RootPackageInterface}; +use crate::package::{Locker, RootPackageInterfaceHandle}; use crate::plugin::PluginManager; use crate::repository::RepositoryManager; use crate::util::r#loop::Loop; @@ -35,7 +35,7 @@ pub fn get_version() -> String { #[derive(Debug, Default)] pub struct PartialComposer { global: bool, - package: Option<Box<dyn RootPackageInterface>>, + package: Option<RootPackageInterfaceHandle>, r#loop: Option<std::rc::Rc<std::cell::RefCell<Loop>>>, repository_manager: Option<std::rc::Rc<std::cell::RefCell<RepositoryManager>>>, installation_manager: Option<std::rc::Rc<std::cell::RefCell<InstallationManager>>>, @@ -44,12 +44,12 @@ pub struct PartialComposer { } impl PartialComposer { - pub fn set_package(&mut self, package: Box<dyn RootPackageInterface>) { + pub fn set_package(&mut self, package: RootPackageInterfaceHandle) { self.package = Some(package); } - pub fn get_package(&self) -> &dyn RootPackageInterface { - self.package.as_deref().unwrap() + pub fn get_package(&self) -> &RootPackageInterfaceHandle { + self.package.as_ref().unwrap() } pub fn set_config(&mut self, config: std::rc::Rc<std::cell::RefCell<Config>>) { @@ -193,11 +193,11 @@ impl Composer { &mut self.partial } - pub fn set_package(&mut self, package: Box<dyn crate::package::RootPackageInterface>) { + pub fn set_package(&mut self, package: RootPackageInterfaceHandle) { self.partial.set_package(package); } - pub fn get_package(&self) -> &dyn crate::package::RootPackageInterface { + pub fn get_package(&self) -> &RootPackageInterfaceHandle { self.partial.get_package() } @@ -316,14 +316,14 @@ impl PartialOrFullComposer { } } - pub fn set_package(&mut self, package: Box<dyn crate::package::RootPackageInterface>) { + pub fn set_package(&mut self, package: RootPackageInterfaceHandle) { match self { Self::Full(full) => full.set_package(package), Self::Partial(partial) => partial.set_package(package), } } - pub fn get_package(&self) -> &dyn crate::package::RootPackageInterface { + pub fn get_package(&self) -> &RootPackageInterfaceHandle { match self { Self::Full(full) => full.get_package(), Self::Partial(partial) => partial.get_package(), diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs index 4bf9197..0b04e26 100644 --- a/crates/shirabe/src/console/application.rs +++ b/crates/shirabe/src/console/application.rs @@ -639,14 +639,18 @@ impl Application { // TODO(phase-b): build_package_map needs &mut InstallationManager // but get_composer returns &Composer; skip until shared ownership is settled. let package_map: Vec<( - Box<dyn crate::package::PackageInterface>, + crate::package::PackageInterfaceHandle, Option<String>, )> = todo!( "build_package_map requires &mut InstallationManager" ); let map = generator.parse_autoloads( package_map, - &*root_package, + root_package + .as_rc() + .borrow() + .as_root_package_interface() + .unwrap(), PhpMixed::Bool(false), ); diff --git a/crates/shirabe/src/dependency_resolver/default_policy.rs b/crates/shirabe/src/dependency_resolver/default_policy.rs index cc33b81..0c6b2fc 100644 --- a/crates/shirabe/src/dependency_resolver/default_policy.rs +++ b/crates/shirabe/src/dependency_resolver/default_policy.rs @@ -1,6 +1,5 @@ //! ref: composer/src/Composer/DependencyResolver/DefaultPolicy.php -use std::any::Any; use std::cell::RefCell; use indexmap::IndexMap; @@ -10,9 +9,8 @@ use shirabe_semver::constraint::SimpleConstraint; use crate::dependency_resolver::PolicyInterface; use crate::dependency_resolver::Pool; -use crate::package::AliasPackage; -use crate::package::PackageInterface; -use crate::package::{BasePackage, STABILITIES}; +use crate::package::BasePackageHandle; +use crate::package::STABILITIES; use crate::util::Platform; #[derive(Debug)] @@ -46,14 +44,14 @@ impl DefaultPolicy { pub fn compare_by_priority( &self, pool: &Pool, - a: &dyn BasePackage, - b: &dyn BasePackage, + a: &BasePackageHandle, + b: &BasePackageHandle, required_package: Option<String>, ignore_replace: bool, ) -> i64 { - if PackageInterface::get_name(a) == PackageInterface::get_name(b) { - let a_aliased = a.as_any().downcast_ref::<AliasPackage>().is_some(); - let b_aliased = b.as_any().downcast_ref::<AliasPackage>().is_some(); + if a.get_name() == b.get_name() { + let a_aliased = a.as_alias().is_some(); + let b_aliased = b.as_alias().is_some(); if a_aliased && !b_aliased { return -1; } @@ -73,10 +71,8 @@ impl DefaultPolicy { if let Some(ref required_package) = required_package { if let Some(pos) = required_package.find('/') { let required_vendor = &required_package[..pos]; - let a_is_same_vendor = - PackageInterface::get_name(a).starts_with(required_vendor); - let b_is_same_vendor = - PackageInterface::get_name(b).starts_with(required_vendor); + let a_is_same_vendor = a.get_name().starts_with(required_vendor); + let b_is_same_vendor = b.get_name().starts_with(required_vendor); if b_is_same_vendor != a_is_same_vendor { return if a_is_same_vendor { -1 } else { 1 }; } @@ -112,7 +108,7 @@ impl DefaultPolicy { .iter() .copied() .filter(|&literal| { - pool.literal_to_package(literal).get_version() == preferred_version + pool.literal_to_package(literal).get_version() == *preferred_version }) .collect(); if !best_literals.is_empty() { @@ -129,10 +125,10 @@ impl DefaultPolicy { continue; } let package = pool.literal_to_package(literal); - if self.version_compare(package, best_package, operator) { + if self.version_compare(&package, &best_package, operator) { best_package = package; best_literals = vec![literal]; - } else if self.version_compare(package, best_package, "==") { + } else if self.version_compare(&package, &best_package, "==") { best_literals.push(literal); } } @@ -144,7 +140,7 @@ impl DefaultPolicy { for &literal in &literals { let package = pool.literal_to_package(literal); - if let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() { + if let Some(alias_pkg) = package.as_alias() { if alias_pkg.is_root_package_alias() { has_local_alias = true; break; @@ -159,7 +155,7 @@ impl DefaultPolicy { let mut selected = vec![]; for &literal in &literals { let package = pool.literal_to_package(literal); - if let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() { + if let Some(alias_pkg) = package.as_alias() { if alias_pkg.is_root_package_alias() { selected.push(literal); } @@ -168,9 +164,9 @@ impl DefaultPolicy { selected } - pub(crate) fn replaces(&self, source: &dyn BasePackage, target: &dyn BasePackage) -> bool { + pub(crate) fn replaces(&self, source: &BasePackageHandle, target: &BasePackageHandle) -> bool { for link in source.get_replaces().values() { - if link.get_target() == target.get_name() { + if link.get_target() == target.get_name().as_str() { return true; } } @@ -181,8 +177,8 @@ impl DefaultPolicy { impl PolicyInterface for DefaultPolicy { fn version_compare( &self, - a: &dyn PackageInterface, - b: &dyn PackageInterface, + a: &BasePackageHandle, + b: &BasePackageHandle, operator: &str, ) -> bool { if self.prefer_stable { @@ -267,8 +263,8 @@ impl PolicyInterface for DefaultPolicy { } let result = self.compare_by_priority( pool, - pool.literal_to_package(a), - pool.literal_to_package(b), + &pool.literal_to_package(a), + &pool.literal_to_package(b), required_package.clone(), true, ); @@ -300,8 +296,8 @@ impl PolicyInterface for DefaultPolicy { } let result = self.compare_by_priority( pool, - pool.literal_to_package(a), - pool.literal_to_package(b), + &pool.literal_to_package(a), + &pool.literal_to_package(b), required_package.clone(), false, ); diff --git a/crates/shirabe/src/dependency_resolver/local_repo_transaction.rs b/crates/shirabe/src/dependency_resolver/local_repo_transaction.rs index b4aa4a4..7b60522 100644 --- a/crates/shirabe/src/dependency_resolver/local_repo_transaction.rs +++ b/crates/shirabe/src/dependency_resolver/local_repo_transaction.rs @@ -14,9 +14,9 @@ impl LocalRepoTransaction { locked_repository: &dyn RepositoryInterface, local_repository: &dyn InstalledRepositoryInterface, ) -> Self { - // TODO(phase-b): RepositoryInterface::get_packages returns Box<dyn BasePackage> - // but Transaction::new wants Box<dyn PackageInterface>. Upcast each via PackageInterface - // trait once a `into_package_interface` helper is added. + // TODO(phase-c): RepositoryInterface::get_packages yields BasePackageHandle; widen each to + // PackageInterfaceHandle (via .into()) and feed them to Transaction::new once the repository + // getters expose handles here. let _ = (locked_repository, local_repository); Self { inner: Transaction::new(Vec::new(), Vec::new()), diff --git a/crates/shirabe/src/dependency_resolver/lock_transaction.rs b/crates/shirabe/src/dependency_resolver/lock_transaction.rs index 14900d1..09d4571 100644 --- a/crates/shirabe/src/dependency_resolver/lock_transaction.rs +++ b/crates/shirabe/src/dependency_resolver/lock_transaction.rs @@ -1,34 +1,30 @@ //! ref: composer/src/Composer/DependencyResolver/LockTransaction.php -use std::any::Any; - use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; use crate::dependency_resolver::Decisions; use crate::dependency_resolver::Pool; use crate::dependency_resolver::Transaction; -use crate::package::AliasPackage; -use crate::package::Package; -use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; #[derive(Debug)] pub struct LockTransaction { inner: Transaction, /// packages in current lock file, platform repo or otherwise present /// Indexed by spl_object_hash - pub(crate) present_map: IndexMap<String, Box<dyn PackageInterface>>, + pub(crate) present_map: IndexMap<String, PackageInterfaceHandle>, /// Packages which cannot be mapped, platform repo, root package, other fixed repos /// Indexed by package id - pub(crate) unlockable_map: IndexMap<i64, Box<dyn PackageInterface>>, - pub(crate) result_packages: IndexMap<String, Vec<Box<dyn PackageInterface>>>, + pub(crate) unlockable_map: IndexMap<i64, PackageInterfaceHandle>, + pub(crate) result_packages: IndexMap<String, Vec<PackageInterfaceHandle>>, } impl LockTransaction { pub fn new( pool: &Pool, - present_map: IndexMap<String, Box<dyn PackageInterface>>, - unlockable_map: IndexMap<i64, Box<dyn PackageInterface>>, + present_map: IndexMap<String, PackageInterfaceHandle>, + unlockable_map: IndexMap<i64, PackageInterfaceHandle>, decisions: &Decisions, ) -> Self { let mut this = Self { @@ -38,22 +34,18 @@ impl LockTransaction { result_packages: IndexMap::new(), }; this.set_result_packages(pool, decisions); - let all: Vec<Box<dyn PackageInterface>> = this + let all: Vec<PackageInterfaceHandle> = this .result_packages .get("all") - .map(|v| v.iter().map(|p| p.clone_package_box()).collect()) + .map(|v| v.iter().cloned().collect()) .unwrap_or_default(); - let present: Vec<Box<dyn PackageInterface>> = this - .present_map - .values() - .map(|p| p.clone_package_box()) - .collect(); + let present: Vec<PackageInterfaceHandle> = this.present_map.values().cloned().collect(); this.inner = Transaction::new(present, all); this } pub fn set_result_packages(&mut self, pool: &Pool, decisions: &Decisions) { - let mut result_packages: IndexMap<String, Vec<Box<dyn PackageInterface>>> = IndexMap::new(); + let mut result_packages: IndexMap<String, Vec<PackageInterfaceHandle>> = IndexMap::new(); result_packages.insert("all".to_string(), vec![]); result_packages.insert("non-dev".to_string(), vec![]); result_packages.insert("dev".to_string(), vec![]); @@ -67,12 +59,12 @@ impl LockTransaction { result_packages .get_mut("all") .unwrap() - .push(package.clone_box()); + .push(package.clone().into()); if !self.unlockable_map.contains_key(&package.get_id()) { result_packages .get_mut("non-dev") .unwrap() - .push(package.clone_box()); + .push(package.clone().into()); } } } @@ -110,7 +102,7 @@ impl LockTransaction { &self, dev_mode: bool, update_mirrors: bool, - ) -> Vec<Box<dyn PackageInterface>> { + ) -> Vec<PackageInterfaceHandle> { let key = if dev_mode { "dev" } else { "non-dev" }; let mut packages = vec![]; @@ -120,26 +112,22 @@ impl LockTransaction { .map(|v| v.as_slice()) .unwrap_or_default(); for package in source { - if package.as_any().downcast_ref::<AliasPackage>().is_some() { + if package.as_alias().is_some() { continue; } - if update_mirrors - && !self - .present_map - .contains_key(&shirabe_php_shim::spl_object_hash(package.as_ref())) - { - let updated = self.update_mirror_and_urls(package.as_ref()); + if update_mirrors && !self.present_map.contains_key(&package.ptr_id().to_string()) { + let updated = self.update_mirror_and_urls(package); packages.push(updated); } else { - packages.push(package.clone_package_box()); + packages.push(package.clone()); } } packages } - fn update_mirror_and_urls(&self, package: &dyn PackageInterface) -> Box<dyn PackageInterface> { + fn update_mirror_and_urls(&self, package: &PackageInterfaceHandle) -> PackageInterfaceHandle { for present_package in self.present_map.values() { if package.get_name() != present_package.get_name() { continue; @@ -157,38 +145,38 @@ impl LockTransaction { continue; } - if let Some(concrete_pkg) = present_package.as_any().downcast_ref::<Package>() { - // TODO(phase-b): set_source_url/set_source_mirrors expect &mut and owned types; - // present_package is &Box<dyn PackageInterface> (immutable). Revisit ownership. + if let Some(concrete_pkg) = present_package.as_package() { + // TODO(phase-c): mirror the source url/mirrors of the present package onto it via + // its handle setters once the per-field copy semantics are reviewed. let _ = concrete_pkg; - let _ = package.get_source_url().map(|s| s.to_string()); + let _ = package.get_source_url(); let _ = package.get_source_mirrors(); } if present_package.get_dist_type() != package.get_dist_type() { - return present_package.clone_package_box(); + return present_package.clone(); } if package.get_dist_url().is_some() && present_package.get_dist_reference().is_some() - && Preg::is_match(r"{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i", package.get_dist_url().unwrap()).unwrap_or(false) + && Preg::is_match(r"{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i", &package.get_dist_url().unwrap()).unwrap_or(false) { let new_dist_url = Preg::replace( r"{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i", - present_package.get_dist_reference().unwrap(), - package.get_dist_url().unwrap(), + &present_package.get_dist_reference().unwrap(), + &package.get_dist_url().unwrap(), ) - .unwrap_or_else(|_| package.get_dist_url().unwrap().to_string()); - // TODO(phase-b): set_dist_url requires &mut PackageInterface; revisit ownership. + .unwrap_or_else(|_| package.get_dist_url().unwrap()); + // TODO(phase-c): apply new_dist_url onto present_package via its handle setter. let _ = new_dist_url; } - // TODO(phase-b): set_dist_mirrors requires &mut PackageInterface; revisit ownership. + // TODO(phase-c): apply dist mirrors onto present_package via its handle setter. let _ = package.get_dist_mirrors(); - return present_package.clone_package_box(); + return present_package.clone(); } - package.clone_package_box() + package.clone() } pub fn get_aliases( @@ -200,11 +188,11 @@ impl LockTransaction { if let Some(all_packages) = self.result_packages.get("all") { for package in all_packages { - if package.as_any().downcast_ref::<AliasPackage>().is_some() { + if package.as_alias().is_some() { let mut i = 0; while i < remaining_aliases.len() { if remaining_aliases[i].get("package").map(|s| s.as_str()) - == Some(package.get_name()) + == Some(package.get_name().as_str()) { used_aliases.push(remaining_aliases.remove(i)); } else { diff --git a/crates/shirabe/src/dependency_resolver/operation/install_operation.rs b/crates/shirabe/src/dependency_resolver/operation/install_operation.rs index ee5e84d..466c877 100644 --- a/crates/shirabe/src/dependency_resolver/operation/install_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/install_operation.rs @@ -3,19 +3,20 @@ use crate::dependency_resolver::operation::OperationInterface; use crate::dependency_resolver::operation::SolverOperation; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; #[derive(Debug)] pub struct InstallOperation { - pub(crate) package: Box<dyn PackageInterface>, + pub(crate) package: PackageInterfaceHandle, } impl InstallOperation { - pub fn new(package: Box<dyn PackageInterface>) -> Self { + pub fn new(package: PackageInterfaceHandle) -> Self { Self { package } } - pub fn get_package(&self) -> &dyn PackageInterface { - self.package.as_ref() + pub fn get_package(&self) -> &PackageInterfaceHandle { + &self.package } pub fn format(package: &dyn PackageInterface, lock: bool) -> String { @@ -43,7 +44,7 @@ impl OperationInterface for InstallOperation { } fn show(&self, lock: bool) -> String { - Self::format(self.package.as_ref(), lock) + Self::format(self.package.as_rc().borrow().as_package_interface(), lock) } fn to_string(&self) -> String { diff --git a/crates/shirabe/src/dependency_resolver/operation/mark_alias_installed_operation.rs b/crates/shirabe/src/dependency_resolver/operation/mark_alias_installed_operation.rs index e176b7f..339f86c 100644 --- a/crates/shirabe/src/dependency_resolver/operation/mark_alias_installed_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/mark_alias_installed_operation.rs @@ -42,12 +42,10 @@ impl OperationInterface for MarkAliasInstalledOperation { true, <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV, ), - PackageInterface::get_pretty_name(self.package.get_alias_of()), - PackageInterface::get_full_pretty_version( - self.package.get_alias_of(), - true, - <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV, - ), + self.package.get_alias_of().get_pretty_name(), + self.package + .get_alias_of() + .get_full_pretty_version(true, <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV,), ) } diff --git a/crates/shirabe/src/dependency_resolver/operation/mark_alias_uninstalled_operation.rs b/crates/shirabe/src/dependency_resolver/operation/mark_alias_uninstalled_operation.rs index 141bf4a..a5b7b7d 100644 --- a/crates/shirabe/src/dependency_resolver/operation/mark_alias_uninstalled_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/mark_alias_uninstalled_operation.rs @@ -42,12 +42,10 @@ impl OperationInterface for MarkAliasUninstalledOperation { true, <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV, ), - PackageInterface::get_pretty_name(self.package.get_alias_of()), - PackageInterface::get_full_pretty_version( - self.package.get_alias_of(), - true, - <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV, - ), + self.package.get_alias_of().get_pretty_name(), + self.package + .get_alias_of() + .get_full_pretty_version(true, <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV,), ) } diff --git a/crates/shirabe/src/dependency_resolver/operation/uninstall_operation.rs b/crates/shirabe/src/dependency_resolver/operation/uninstall_operation.rs index 5e1f6bc..2757146 100644 --- a/crates/shirabe/src/dependency_resolver/operation/uninstall_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/uninstall_operation.rs @@ -3,19 +3,20 @@ use crate::dependency_resolver::operation::OperationInterface; use crate::dependency_resolver::operation::SolverOperation; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; #[derive(Debug)] pub struct UninstallOperation { - pub(crate) package: Box<dyn PackageInterface>, + pub(crate) package: PackageInterfaceHandle, } impl UninstallOperation { - pub fn new(package: Box<dyn PackageInterface>) -> Self { + pub fn new(package: PackageInterfaceHandle) -> Self { Self { package } } - pub fn get_package(&self) -> &dyn PackageInterface { - self.package.as_ref() + pub fn get_package(&self) -> &PackageInterfaceHandle { + &self.package } pub fn format(package: &dyn PackageInterface, _lock: bool) -> String { @@ -42,7 +43,7 @@ impl OperationInterface for UninstallOperation { } fn show(&self, lock: bool) -> String { - Self::format(self.package.as_ref(), lock) + Self::format(self.package.as_rc().borrow().as_package_interface(), lock) } fn to_string(&self) -> String { diff --git a/crates/shirabe/src/dependency_resolver/operation/update_operation.rs b/crates/shirabe/src/dependency_resolver/operation/update_operation.rs index 2ce103b..6881782 100644 --- a/crates/shirabe/src/dependency_resolver/operation/update_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/update_operation.rs @@ -3,28 +3,29 @@ use crate::dependency_resolver::operation::OperationInterface; use crate::dependency_resolver::operation::SolverOperation; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::version::VersionParser; #[derive(Debug)] pub struct UpdateOperation { - pub(crate) initial_package: Box<dyn PackageInterface>, - pub(crate) target_package: Box<dyn PackageInterface>, + pub(crate) initial_package: PackageInterfaceHandle, + pub(crate) target_package: PackageInterfaceHandle, } impl UpdateOperation { - pub fn new(initial: Box<dyn PackageInterface>, target: Box<dyn PackageInterface>) -> Self { + pub fn new(initial: PackageInterfaceHandle, target: PackageInterfaceHandle) -> Self { Self { initial_package: initial, target_package: target, } } - pub fn get_initial_package(&self) -> &dyn PackageInterface { - self.initial_package.as_ref() + pub fn get_initial_package(&self) -> &PackageInterfaceHandle { + &self.initial_package } - pub fn get_target_package(&self) -> &dyn PackageInterface { - self.target_package.as_ref() + pub fn get_target_package(&self) -> &PackageInterfaceHandle { + &self.target_package } pub fn format( @@ -89,8 +90,8 @@ impl OperationInterface for UpdateOperation { fn show(&self, lock: bool) -> String { Self::format( - self.initial_package.as_ref(), - self.target_package.as_ref(), + self.initial_package.as_rc().borrow().as_package_interface(), + self.target_package.as_rc().borrow().as_package_interface(), lock, ) } diff --git a/crates/shirabe/src/dependency_resolver/policy_interface.rs b/crates/shirabe/src/dependency_resolver/policy_interface.rs index 148e21f..002bfe9 100644 --- a/crates/shirabe/src/dependency_resolver/policy_interface.rs +++ b/crates/shirabe/src/dependency_resolver/policy_interface.rs @@ -1,15 +1,11 @@ //! ref: composer/src/Composer/DependencyResolver/PolicyInterface.php use crate::dependency_resolver::Pool; -use crate::package::PackageInterface; +use crate::package::BasePackageHandle; pub trait PolicyInterface: std::fmt::Debug { - fn version_compare( - &self, - a: &dyn PackageInterface, - b: &dyn PackageInterface, - operator: &str, - ) -> bool; + fn version_compare(&self, a: &BasePackageHandle, b: &BasePackageHandle, operator: &str) + -> bool; fn select_preferred_packages( &self, diff --git a/crates/shirabe/src/dependency_resolver/pool.rs b/crates/shirabe/src/dependency_resolver/pool.rs index 771f363..388f23f 100644 --- a/crates/shirabe/src/dependency_resolver/pool.rs +++ b/crates/shirabe/src/dependency_resolver/pool.rs @@ -3,28 +3,29 @@ use std::fmt; use indexmap::IndexMap; -use shirabe_php_shim::{Countable, STR_PAD_LEFT, abs, spl_object_hash, str_pad}; +use shirabe_php_shim::{Countable, STR_PAD_LEFT, abs, str_pad}; use shirabe_semver::compiling_matcher::CompilingMatcher; use shirabe_semver::constraint::AnyConstraint; use shirabe_semver::constraint::SimpleConstraint; use crate::advisory::PartialSecurityAdvisory; use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::version::VersionParser; /// A package pool contains all packages for dependency resolution #[derive(Debug)] pub struct Pool { /// @var BasePackage[] - pub(crate) packages: Vec<Box<dyn BasePackage>>, + pub(crate) packages: Vec<BasePackageHandle>, /// @var array<string, BasePackage[]> - pub(crate) package_by_name: IndexMap<String, Vec<Box<dyn BasePackage>>>, + pub(crate) package_by_name: IndexMap<String, Vec<BasePackageHandle>>, /// @var VersionParser pub(crate) version_parser: VersionParser, /// @var array<string, array<string, BasePackage[]>> - pub(crate) provider_cache: IndexMap<String, IndexMap<String, Vec<Box<dyn BasePackage>>>>, + pub(crate) provider_cache: IndexMap<String, IndexMap<String, Vec<BasePackageHandle>>>, /// @var BasePackage[] - pub(crate) unacceptable_fixed_or_locked_packages: Vec<Box<dyn BasePackage>>, + pub(crate) unacceptable_fixed_or_locked_packages: Vec<BasePackageHandle>, /// @var array<string, array<string, string>> Map of package name => normalized version => pretty version pub(crate) removed_versions: IndexMap<String, IndexMap<String, String>>, /// @var array<string, array<string, string>> Map of package object hash => removed normalized versions => removed pretty version @@ -44,8 +45,8 @@ impl Pool { /// @param array<string, array<string, array<SecurityAdvisory|PartialSecurityAdvisory>>> $securityRemovedVersions /// @param array<string, array<string, string>> $abandonedRemovedVersions pub fn new( - packages: Vec<Box<dyn BasePackage>>, - unacceptable_fixed_or_locked_packages: Vec<Box<dyn BasePackage>>, + packages: Vec<BasePackageHandle>, + unacceptable_fixed_or_locked_packages: Vec<BasePackageHandle>, removed_versions: IndexMap<String, IndexMap<String, String>>, removed_versions_by_package: IndexMap<String, IndexMap<String, String>>, security_removed_versions: IndexMap<String, IndexMap<String, Vec<PartialSecurityAdvisory>>>, @@ -197,18 +198,18 @@ impl Pool { } /// @param BasePackage[] $packages - fn set_packages(&mut self, packages: Vec<Box<dyn BasePackage>>) { + fn set_packages(&mut self, packages: Vec<BasePackageHandle>) { let mut id: i64 = 1; - for mut package in packages { - *package.id_mut() = id; + for package in packages { + package.set_id(id); id += 1; for provided in package.get_names(true) { self.package_by_name .entry(provided) .or_insert_with(Vec::new) - .push(package.clone_box()); + .push(package.clone()); } self.packages.push(package); @@ -216,13 +217,13 @@ impl Pool { } /// @return BasePackage[] - pub fn get_packages(&self) -> &Vec<Box<dyn BasePackage>> { + pub fn get_packages(&self) -> &Vec<BasePackageHandle> { &self.packages } /// Retrieves the package object for a given package id. - pub fn package_by_id(&self, id: i64) -> &dyn BasePackage { - self.packages[(id - 1) as usize].as_ref() + pub fn package_by_id(&self, id: i64) -> BasePackageHandle { + self.packages[(id - 1) as usize].clone() } /// Searches all packages providing the given package name and match the constraint @@ -235,7 +236,7 @@ impl Pool { &mut self, name: &str, constraint: Option<&AnyConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { // PHP: $key = (string) $constraint; let key = match constraint { Some(c) => c.to_string(), @@ -243,7 +244,7 @@ impl Pool { }; if let Some(by_key) = self.provider_cache.get(name) { if let Some(cached) = by_key.get(&key) { - return cached.iter().map(|p| p.clone_box()).collect(); + return cached.clone(); } } @@ -251,7 +252,7 @@ impl Pool { self.provider_cache .entry(name.to_string()) .or_insert_with(IndexMap::new) - .insert(key, computed.iter().map(|p| p.clone_box()).collect()); + .insert(key, computed.clone()); computed } @@ -263,23 +264,23 @@ impl Pool { &self, name: &str, constraint: Option<&AnyConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { let Some(candidates) = self.package_by_name.get(name) else { return vec![]; }; - let mut matches: Vec<Box<dyn BasePackage>> = vec![]; + let mut matches: Vec<BasePackageHandle> = vec![]; for candidate in candidates { - if self.r#match(candidate.as_ref(), name, constraint) { - matches.push(candidate.clone_box()); + if self.r#match(candidate, name, constraint) { + matches.push(candidate.clone()); } } matches } - pub fn literal_to_package(&self, literal: i64) -> &dyn BasePackage { + pub fn literal_to_package(&self, literal: i64) -> BasePackageHandle { let package_id = abs(literal); self.package_by_id(package_id) @@ -289,7 +290,7 @@ impl Pool { pub fn literal_to_pretty_string( &self, literal: i64, - installed_map: &IndexMap<String, Box<dyn BasePackage>>, + installed_map: &IndexMap<String, BasePackageHandle>, ) -> String { let package = self.literal_to_package(literal); @@ -312,7 +313,7 @@ impl Pool { /// @param string $name Name of the package to be matched pub fn r#match( &self, - candidate: &dyn BasePackage, + candidate: &BasePackageHandle, name: &str, constraint: Option<&AnyConstraint>, ) -> bool { @@ -370,17 +371,16 @@ impl Pool { false } - pub fn is_unacceptable_fixed_or_locked_package(&self, package: &dyn BasePackage) -> bool { + pub fn is_unacceptable_fixed_or_locked_package(&self, package: &BasePackageHandle) -> bool { // PHP: \in_array($package, $this->unacceptableFixedOrLockedPackages, true) // strict comparison checks reference identity for objects - let target_hash = spl_object_hash(package); self.unacceptable_fixed_or_locked_packages .iter() - .any(|p| spl_object_hash(p.as_ref()) == target_hash) + .any(|p| p.ptr_eq(package)) } /// @return BasePackage[] - pub fn get_unacceptable_fixed_or_locked_packages(&self) -> &Vec<Box<dyn BasePackage>> { + pub fn get_unacceptable_fixed_or_locked_packages(&self) -> &Vec<BasePackageHandle> { &self.unacceptable_fixed_or_locked_packages } } diff --git a/crates/shirabe/src/dependency_resolver/pool_builder.rs b/crates/shirabe/src/dependency_resolver/pool_builder.rs index f80ef38..38a083e 100644 --- a/crates/shirabe/src/dependency_resolver/pool_builder.rs +++ b/crates/shirabe/src/dependency_resolver/pool_builder.rs @@ -8,8 +8,8 @@ use shirabe_external_packages::composer::semver::CompilingMatcher; use shirabe_external_packages::composer::semver::Intervals; use shirabe_php_shim::{ LogicException, PhpMixed, array_chunk, array_flip, array_flip_strings, array_map, array_merge, - array_search, array_search_mixed, count, in_array, microtime, number_format, round, - spl_object_hash, sprintf, strpos, + array_search, array_search_mixed, count, in_array, microtime, number_format, round, sprintf, + strpos, }; use shirabe_semver::constraint::AnyConstraint; use shirabe_semver::constraint::MatchAllConstraint; @@ -23,10 +23,12 @@ use crate::dependency_resolver::SecurityAdvisoryPoolFilter; use crate::event_dispatcher::EventDispatcher; use crate::io::IOInterface; use crate::package::AliasPackage; +use crate::package::BasePackageHandle; use crate::package::CompleteAliasPackage; use crate::package::CompletePackage; use crate::package::PackageInterface; -use crate::package::base_package::{self, BasePackage}; +use crate::package::PackageInterfaceHandle; +use crate::package::base_package; use crate::package::version::StabilityFilter; use crate::plugin::PluginEvents; use crate::plugin::PrePoolCreateEvent; @@ -48,11 +50,11 @@ pub struct PoolBuilder { alias_map: IndexMap<String, IndexMap<i64, AliasPackage>>, packages_to_load: IndexMap<String, AnyConstraint>, loaded_packages: IndexMap<String, AnyConstraint>, - loaded_per_repo: IndexMap<i64, IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>>, - packages: IndexMap<i64, Box<dyn BasePackage>>, - unacceptable_fixed_or_locked_packages: Vec<Box<dyn BasePackage>>, + loaded_per_repo: IndexMap<i64, IndexMap<String, IndexMap<String, PackageInterfaceHandle>>>, + packages: IndexMap<i64, BasePackageHandle>, + unacceptable_fixed_or_locked_packages: Vec<BasePackageHandle>, update_allow_list: Vec<String>, - skipped_load: IndexMap<String, Vec<Box<dyn PackageInterface>>>, + skipped_load: IndexMap<String, Vec<PackageInterfaceHandle>>, ignored_types: Vec<String>, allowed_types: Option<Vec<String>>, /// If provided, only these package names are loaded @@ -161,10 +163,7 @@ impl PoolBuilder { for locked_package in CanonicalPackagesTrait::get_packages(request.get_locked_repository().unwrap()) { - if !self.is_update_allowed(&*locked_package) { - // TODO(phase-b): PackageInterface lacks clone_box; PHP shares references. - // skipped_load population needs shared-ownership Rc<dyn PackageInterface>. - + if !self.is_update_allowed(locked_package.as_rc().borrow().as_package_interface()) { // Path repo packages are never loaded from lock, to force them to always remain in sync // unless symlinking is disabled in which case we probably should rather treat them like // regular packages. We mark them specially so they can be reloaded fully including update propagation @@ -182,10 +181,7 @@ impl PoolBuilder { } } - // TODO(phase-b): lock_package wants Box<dyn BasePackage>; locked_package is a - // PackageInterface trait object from CanonicalPackagesTrait::get_packages. The - // PHP code passes the same object; needs Rc<dyn BasePackage> migration. - request.lock_package(todo!("convert PackageInterface → Box<dyn BasePackage>")); + request.lock_package(locked_package.into()); } } } @@ -209,9 +205,9 @@ impl PoolBuilder { // TODO in how far can we do the above for conflicts? It's more tricky cause conflicts can be limited to // specific versions while replace is a conflict with all versions of the name - let in_root_or_platform = package - .get_repository() - .map(|r| { + // TODO(phase-c): package->repository back-reference not yet on handles + let in_root_or_platform = None + .map(|r: &dyn RepositoryInterface| { r.as_any().is::<RootPackageRepository>() || r.as_any().is::<PlatformRepository>() }) @@ -221,13 +217,12 @@ impl PoolBuilder { &self.acceptable_stabilities, &self.stability_flags, &package.get_names(true), - package.get_stability(), + &package.get_stability(), ) { - self.load_package(request, &repositories, &*package, false)?; + self.load_package(request, &repositories, &package, false)?; } else { - self.unacceptable_fixed_or_locked_packages - .push(package.clone_box()); + self.unacceptable_fixed_or_locked_packages.push(package); } } @@ -261,11 +256,11 @@ impl PoolBuilder { let indices: Vec<i64> = self.packages.keys().cloned().collect(); for i in indices { let package = match self.packages.get(&i) { - Some(p) => p.clone_box(), + Some(p) => p.clone(), None => continue, }; // we check all alias related packages at once, so no need to check individual aliases - if package.as_alias_package().is_some() { + if package.as_alias().is_some() { continue; } @@ -275,12 +270,11 @@ impl PoolBuilder { None => continue, }; - // TODO(phase-b): package_and_aliases originally held Box<dyn BasePackage>; - // AliasPackage is a PHP class so we collect (index, version) tuples instead of - // cloning the alias objects. + // TODO(phase-c): alias_map still stores AliasPackage by value, so we collect + // (index, version) tuples instead of the alias handles. let mut package_and_aliases: Vec<(i64, String)> = Vec::new(); package_and_aliases.push((i, package.get_version().to_string())); - if let Some(aliases) = self.alias_map.get(&spl_object_hash(&*package)) { + if let Some(aliases) = self.alias_map.get(&package.ptr_id().to_string()) { for (idx, alias) in aliases { package_and_aliases.push((*idx, alias.get_version().to_string())); } @@ -314,10 +308,10 @@ impl PoolBuilder { self.stability_flags.clone(), self.root_aliases.clone(), self.root_references.clone(), - self.packages.values().map(|p| p.clone_box()).collect(), + self.packages.values().cloned().collect(), self.unacceptable_fixed_or_locked_packages .iter() - .map(|p| p.clone_box()) + .cloned() .collect(), ); // TODO(phase-b): EventDispatcher::dispatch expects an owned Event, not &mut PrePoolCreateEvent @@ -327,24 +321,16 @@ impl PoolBuilder { .borrow_mut() .dispatch(Some(pre_pool_create_event.get_name()), None)?; // PHP rebinds $this->packages to a list-style array; preserve indices via reindexing. - self.packages = pre_pool_create_event - .get_packages() - .iter() - .enumerate() - .map(|(i, p)| (i as i64, p.clone_box())) - .collect(); - self.unacceptable_fixed_or_locked_packages = pre_pool_create_event - .get_unacceptable_fixed_packages() - .iter() - .map(|p| p.clone_box()) - .collect(); + // TODO(plugin)/TODO(phase-c): rebind self.packages from the (handle-based) event packages + // once EventDispatcher::dispatch returns the mutated event. + let _ = &pre_pool_create_event; } let mut pool = Pool::new( - self.packages.values().map(|p| p.clone_box()).collect(), + self.packages.values().cloned().collect(), self.unacceptable_fixed_or_locked_packages .iter() - .map(|p| p.clone_box()) + .cloned() .collect(), IndexMap::new(), IndexMap::new(), @@ -543,7 +529,7 @@ impl PoolBuilder { k.clone(), inner .iter() - .map(|(kk, vv)| (kk.clone(), vv.clone_package_box())) + .map(|(kk, vv)| (kk.clone(), vv.clone())) .collect(), ) }) @@ -561,8 +547,6 @@ impl PoolBuilder { } let packages_in_result = result.packages; for (_, package) in &packages_in_result { - // TODO(phase-b): proper upcast Box<dyn BasePackage> → Box<dyn PackageInterface>; - // clone_box on BasePackage produces a BasePackage, while loaded_per_repo stores PackageInterface. let pkg_name = package.get_name().to_string(); let pkg_version = package.get_version().to_string(); let pkg_type = package.get_type().to_string(); @@ -592,8 +576,8 @@ impl PoolBuilder { continue; } let _ = (pkg_name, pkg_version); - let propagate = !self.path_repo_unlocked.contains_key(package.get_name()); - self.load_package(request, repositories, package.as_ref(), propagate)?; + let propagate = !self.path_repo_unlocked.contains_key(&package.get_name()); + self.load_package(request, repositories, package, propagate)?; } } @@ -627,36 +611,33 @@ impl PoolBuilder { &mut self, request: &mut Request, repositories: &Vec<Box<dyn RepositoryInterface>>, - package: &dyn BasePackage, + package: &BasePackageHandle, propagate_update: bool, ) -> anyhow::Result<()> { let index = self.index_counter; self.index_counter += 1; - self.packages.insert(index, package.clone_box()); + self.packages.insert(index, package.clone()); - if let Some(alias) = package.as_alias_package() { + if let Some(alias) = package.as_alias() { // TODO(phase-b): alias_map should hold shared references (Rc<AliasPackage>); AliasPackage // is a PHP class and must not be cloned. - let _ = alias; + let _ = &alias; self.alias_map - .entry(spl_object_hash(alias.get_alias_of())) + .entry(alias.get_alias_of().ptr_id().to_string()) .or_insert_with(IndexMap::new) .insert(index, todo!("share AliasPackage via Rc")); } - let name = PackageInterface::get_name(package).to_string(); + let name = package.get_name(); // we're simply setting the root references on all versions for a name here and rely on the solver to pick the // right version. It'd be more work to figure out which versions and which aliases of those versions this may // apply to if let Some(reference) = self.root_references.get(&name) { - // do not modify the references on already locked or fixed packages - if !request.is_locked_package(package) && !request.is_fixed_package(package) { - // TODO(phase-b): set_source_dist_references mutates the package; load_package takes - // `&dyn BasePackage`. PHP passes by reference (shared) and mutates in place. Needs - // either &mut dyn BasePackage propagation or Rc<RefCell<...>>. - let _ = reference; - } + // TODO(phase-c): apply root references to the package; PHP mutates the shared package in + // place and skips already locked/fixed packages, which needs &mut access through the + // handle plus a handle-based Request. + let _ = reference; } // if propagateUpdate is false we are loading a fixed or locked package, root aliases do not apply as they are @@ -664,51 +645,39 @@ impl PoolBuilder { // // packages in pathRepoUnlocked however need to also load root aliases, they have propagateUpdate set to // false because their deps should not be unlocked, but that is irrelevant for root aliases - let path_repo_match = self - .path_repo_unlocked - .contains_key(PackageInterface::get_name(package)); + let path_repo_match = self.path_repo_unlocked.contains_key(&package.get_name()); let alias_for_version = self .root_aliases .get(&name) - .and_then(|m| m.get(package.get_version())) + .and_then(|m| m.get(&package.get_version())) .cloned(); if (propagate_update || path_repo_match) && alias_for_version.is_some() { let alias = alias_for_version.unwrap(); - let base_package: Box<dyn BasePackage> = if let Some(ap) = package.as_alias_package() { - ap.get_alias_of().clone_box() + let base_package: BasePackageHandle = if let Some(ap) = package.as_alias() { + ap.get_alias_of().into() } else { - package.clone_box() + package.clone() + }; + let _ = (&base_package, &alias); + let alias_package: BasePackageHandle = if base_package.as_complete_package().is_some() { + // TODO(phase-c): construct CompleteAliasPackage from the aliasOf handle. + todo!("new CompleteAliasPackage(base_package, alias_normalized, alias)") + } else { + // TODO(phase-c): construct AliasPackage from the aliasOf handle. + todo!("new AliasPackage(base_package, alias_normalized, alias)") }; - let alias_package: Box<dyn BasePackage> = - if base_package.as_any().is::<CompletePackage>() { - // TODO(phase-b): CompleteAliasPackage does not yet impl BasePackage; also its - // constructor wants CompletePackage by value but BasePackage is a PHP class - // (shared). Needs Rc<CompletePackage> migration + BasePackage impl. - let _ = CompleteAliasPackage::new( - todo!("downcast Box<dyn BasePackage> to CompletePackage by value"), - alias.get("alias_normalized").cloned().unwrap_or_default(), - alias.get("alias").cloned().unwrap_or_default(), - ); - todo!("CompleteAliasPackage must implement BasePackage") - } else { - Box::new(AliasPackage::new( - base_package.clone_box(), - alias.get("alias_normalized").cloned().unwrap_or_default(), - alias.get("alias").cloned().unwrap_or_default(), - )) - }; // PHP: $aliasPackage->setRootPackageAlias(true); // BasePackage doesn't expose this directly; the AliasPackage trait method handles it. let new_index = self.index_counter; self.index_counter += 1; - self.packages.insert(new_index, alias_package.clone_box()); - if let Some(ap) = alias_package.as_alias_package() { + self.packages.insert(new_index, alias_package.clone()); + if let Some(ap) = alias_package.as_alias() { // TODO(phase-b): alias_map should hold shared references (Rc<AliasPackage>); AliasPackage // is a PHP class and must not be cloned. - let _ = ap; + let _ = ≈ self.alias_map - .entry(spl_object_hash(ap.get_alias_of())) + .entry(ap.get_alias_of().ptr_id().to_string()) .or_insert_with(IndexMap::new) .insert(new_index, todo!("share AliasPackage via Rc")); } @@ -800,7 +769,7 @@ impl PoolBuilder { if root_requires.contains_key(name) { let name_owned = name.to_string(); return array_map( - |package: &Box<dyn PackageInterface>| -> String { + |package: &PackageInterfaceHandle| -> String { if name_owned != package.get_name() { format!("{} (via replace of {})", package.get_name(), name_owned) } else { @@ -812,8 +781,8 @@ impl PoolBuilder { } for package_or_replacer in &self.skipped_load[name] { - if root_requires.contains_key(package_or_replacer.get_name()) { - matches.push(package_or_replacer.get_name().to_string()); + if root_requires.contains_key(&package_or_replacer.get_name()) { + matches.push(package_or_replacer.get_name()); } for (_k, link) in &package_or_replacer.get_replaces() { if root_requires.contains_key(link.get_target()) { @@ -863,7 +832,7 @@ impl PoolBuilder { for package in CanonicalPackagesTrait::get_packages(request.get_locked_repository().unwrap()) { - if Preg::is_match3(&pattern_regexp, package.get_name(), None).unwrap_or(false) { + if Preg::is_match3(&pattern_regexp, &package.get_name(), None).unwrap_or(false) { continue 'outer; } } @@ -905,10 +874,10 @@ impl PoolBuilder { repositories: &Vec<Box<dyn RepositoryInterface>>, name: &str, ) -> anyhow::Result<()> { - let skipped: Vec<Box<dyn PackageInterface>> = self + let skipped: Vec<PackageInterfaceHandle> = self .skipped_load .get(name) - .map(|v| v.iter().map(|p| p.clone_package_box()).collect()) + .map(|v| v.iter().cloned().collect()) .unwrap_or_default(); for package_or_replacer in &skipped { // if we unfixed a replaced package name, we also need to unfix the replacer itself @@ -916,9 +885,9 @@ impl PoolBuilder { if package_or_replacer.get_name() != name && self .skipped_load - .contains_key(package_or_replacer.get_name()) + .contains_key(&package_or_replacer.get_name()) { - let replacer_name = package_or_replacer.get_name().to_string(); + let replacer_name = package_or_replacer.get_name(); if request.get_update_allow_transitive_root_dependencies() || (!self.is_root_require(request, name) && !self.is_root_require(request, &replacer_name)) @@ -932,8 +901,8 @@ impl PoolBuilder { &MatchAllConstraint::new(None).into(), ); } else { - let pkgs: Vec<Box<dyn BasePackage>> = - self.packages.values().map(|p| p.clone_box()).collect(); + let pkgs: Vec<BasePackageHandle> = + self.packages.values().cloned().collect(); for loaded_package in &pkgs { let requires = loaded_package.get_requires(); if let Some(req_link) = requires.get(&replacer_name) { @@ -950,14 +919,14 @@ impl PoolBuilder { } if self.path_repo_unlocked.contains_key(name) { - let entries: Vec<(i64, Box<dyn BasePackage>)> = self + let entries: Vec<(i64, BasePackageHandle)> = self .packages .iter() - .filter(|(_, p)| PackageInterface::get_name(p.as_ref()) == name) - .map(|(i, p)| (*i, p.clone_box())) + .filter(|(_, p)| p.get_name() == name) + .map(|(i, p)| (*i, p.clone())) .collect(); for (index, package) in &entries { - self.remove_loaded_package(request, repositories, &**package, *index); + self.remove_loaded_package(request, repositories, package, *index); } } @@ -967,58 +936,41 @@ impl PoolBuilder { self.path_repo_unlocked.shift_remove(name); // remove locked package by this name which was already initialized - let locked_packages: Vec<Box<dyn BasePackage>> = request - .get_locked_packages() - .values() - .map(|p| p.clone_box()) - .collect(); + let locked_packages: Vec<BasePackageHandle> = + request.get_locked_packages().values().cloned().collect(); for locked_package in &locked_packages { - if locked_package.as_alias_package().is_none() && locked_package.get_name() == name { - let pkgs: Vec<Box<dyn BasePackage>> = - self.packages.values().map(|p| p.clone_box()).collect(); + if locked_package.as_alias().is_none() && locked_package.get_name() == name { + let pkgs: Vec<BasePackageHandle> = self.packages.values().cloned().collect(); // PHP uses array_search with strict identity; map to pointer comparison. - let index_opt = pkgs.iter().position(|p| { - std::ptr::eq( - p.as_ref() as *const _ as *const u8, - locked_package.as_ref() as *const _ as *const u8, - ) - }); + let index_opt = pkgs.iter().position(|p| p.ptr_eq(locked_package)); if let Some(index) = index_opt { - request.unlock_package(&**locked_package); - self.remove_loaded_package( - request, - repositories, - &**locked_package, - index as i64, - ); + request.unlock_package(locked_package); + self.remove_loaded_package(request, repositories, locked_package, index as i64); // make sure that any requirements for this package by other locked or fixed packages are now // also loaded, as they were previously ignored because the locked (now unlocked) package already // satisfied their requirements // and if this package is replacing another that is required by a locked or fixed package, ensure // that we load that replaced package in case an update to this package removes the replacement - let fixed_or_locked: Vec<Box<dyn BasePackage>> = request + let fixed_or_locked: Vec<BasePackageHandle> = request .get_fixed_or_locked_packages() .values() - .map(|p| p.clone_box()) + .cloned() .collect(); for fixed_or_locked_package in &fixed_or_locked { - if std::ptr::eq( - fixed_or_locked_package.as_ref() as *const _, - locked_package.as_ref() as *const _, - ) { + if fixed_or_locked_package.ptr_eq(locked_package) { continue; } if self .skipped_load - .contains_key(fixed_or_locked_package.get_name()) + .contains_key(&fixed_or_locked_package.get_name()) { let requires = fixed_or_locked_package.get_requires(); - if let Some(req_link) = requires.get(locked_package.get_name()) { + if let Some(req_link) = requires.get(&locked_package.get_name()) { self.mark_package_name_for_loading( request, - locked_package.get_name(), + &locked_package.get_name(), req_link.get_constraint(), ); } @@ -1054,8 +1006,7 @@ impl PoolBuilder { self.mark_package_name_for_loading(request, name, &cons); } - let pkgs: Vec<Box<dyn BasePackage>> = - self.packages.values().map(|p| p.clone_box()).collect(); + let pkgs: Vec<BasePackageHandle> = self.packages.values().cloned().collect(); for package in &pkgs { for (_k, link) in &package.get_requires() { if name == link.get_target() { @@ -1073,35 +1024,24 @@ impl PoolBuilder { &mut self, _request: &Request, repositories: &Vec<Box<dyn RepositoryInterface>>, - package: &dyn BasePackage, + package: &BasePackageHandle, index: i64, ) { let repos_box: Vec<Box<dyn RepositoryInterface>> = repositories.iter().map(|r| r.clone_box()).collect(); - let repo_index: i64 = match package.get_repository() { - // PHP uses array_search with strict identity; map to pointer comparison. - Some(repo) => repos_box - .iter() - .position(|r| { - std::ptr::eq( - r.as_ref() as *const _ as *const u8, - repo as *const _ as *const u8, - ) - }) - .map(|i| i as i64) - .unwrap_or(-1), - None => -1, - }; + let _ = &repos_box; + // TODO(phase-c): package->repository back-reference not yet on handles + let repo_index: i64 = -1; if repo_index >= 0 { if let Some(repo_map) = self.loaded_per_repo.get_mut(&repo_index) { - if let Some(name_map) = repo_map.get_mut(PackageInterface::get_name(package)) { - name_map.shift_remove(package.get_version()); + if let Some(name_map) = repo_map.get_mut(&package.get_name()) { + name_map.shift_remove(&package.get_version()); } } } self.packages.shift_remove(&index); - let object_hash = spl_object_hash(package); + let object_hash = package.ptr_id().to_string(); if let Some(aliases) = self.alias_map.shift_remove(&object_hash) { for (alias_index, alias_package) in &aliases { if repo_index >= 0 { diff --git a/crates/shirabe/src/dependency_resolver/pool_optimizer.rs b/crates/shirabe/src/dependency_resolver/pool_optimizer.rs index 27b3ffb..8aa8324 100644 --- a/crates/shirabe/src/dependency_resolver/pool_optimizer.rs +++ b/crates/shirabe/src/dependency_resolver/pool_optimizer.rs @@ -1,10 +1,8 @@ //! ref: composer/src/Composer/DependencyResolver/PoolOptimizer.php -use std::any::Any; - use anyhow::Result; use indexmap::IndexMap; -use shirabe_php_shim::{LogicException, PhpMixed, implode, ksort, spl_object_hash}; +use shirabe_php_shim::{LogicException, PhpMixed, implode, ksort}; use shirabe_semver::compiling_matcher::CompilingMatcher; use shirabe_semver::constraint::AnyConstraint; use shirabe_semver::constraint::MultiConstraint; @@ -14,8 +12,7 @@ use shirabe_semver::intervals::Intervals; use crate::dependency_resolver::PolicyInterface; use crate::dependency_resolver::Pool; use crate::dependency_resolver::Request; -use crate::package::AliasPackage; -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::PackageInterface; use crate::package::version::VersionParser; @@ -38,7 +35,7 @@ pub struct PoolOptimizer { packages_to_remove: IndexMap<i64, bool>, /// @var array<int, BasePackage[]> - aliases_per_package: IndexMap<i64, Vec<Box<dyn BasePackage>>>, + aliases_per_package: IndexMap<i64, Vec<BasePackageHandle>>, /// @var array<string, array<string, string>> removed_versions_by_package: IndexMap<String, IndexMap<String, String>>, @@ -94,7 +91,7 @@ impl PoolOptimizer { // Mark fixed or locked packages as irremovable for (_, package) in request.get_fixed_or_locked_packages() { irremovable_package_constraint_groups - .entry(PackageInterface::get_name(package.as_ref()).to_string()) + .entry(package.get_name()) .or_insert_with(Vec::new) .push( SimpleConstraint::new( @@ -130,11 +127,11 @@ impl PoolOptimizer { // Keep track of alias packages for every package so if either the alias or aliased is kept // we keep the others as they are a unit of packages really - if let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() { + if let Some(alias_pkg) = package.as_alias() { self.aliases_per_package .entry(alias_pkg.get_alias_of().id()) .or_insert_with(Vec::new) - .push(package.clone_box()); + .push(package.clone()); } } @@ -153,31 +150,30 @@ impl PoolOptimizer { // Mark the packages as irremovable based on the constraints for package in pool.get_packages() { - if !irremovable_package_constraints - .contains_key(PackageInterface::get_name(package.as_ref())) - { + if !irremovable_package_constraints.contains_key(&package.get_name()) { continue; } let constraint = irremovable_package_constraints - .get(PackageInterface::get_name(package.as_ref())) + .get(&package.get_name()) .unwrap(); if CompilingMatcher::r#match( constraint, SimpleConstraint::OP_EQ, package.get_version().to_string(), ) { - self.mark_package_irremovable(package.as_ref()); + self.mark_package_irremovable(package); } } } - fn mark_package_irremovable(&mut self, package: &dyn BasePackage) { + fn mark_package_irremovable(&mut self, package: &BasePackageHandle) { self.irremovable_packages.insert(package.id(), true); - if let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() { + if let Some(alias_pkg) = package.as_alias() { // recursing here so aliasesPerPackage for the aliasOf can be checked // and all its aliases marked as irremovable as well - self.mark_package_irremovable(alias_pkg.get_alias_of()); + let aliased: BasePackageHandle = alias_pkg.get_alias_of().into(); + self.mark_package_irremovable(&aliased); } // PHP: foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) let alias_ids: Vec<i64> = self @@ -192,14 +188,14 @@ impl PoolOptimizer { /// @return Pool Optimized pool fn apply_removals_to_pool(&self, pool: &Pool) -> Pool { - let mut packages: Vec<Box<dyn BasePackage>> = vec![]; + let mut packages: Vec<BasePackageHandle> = vec![]; let mut removed_versions: IndexMap<String, IndexMap<String, String>> = IndexMap::new(); for package in pool.get_packages() { if !self.packages_to_remove.contains_key(&package.id()) { - packages.push(package.clone_box()); + packages.push(package.clone()); } else { removed_versions - .entry(PackageInterface::get_name(package.as_ref()).to_string()) + .entry(package.get_name()) .or_insert_with(IndexMap::new) .insert( package.get_version().to_string(), @@ -210,8 +206,7 @@ impl PoolOptimizer { Pool::new( packages, - // TODO(phase-b): clone Vec<Box<BasePackage>> from getter - todo!("pool.get_unacceptable_fixed_or_locked_packages().clone()"), + pool.get_unacceptable_fixed_or_locked_packages().clone(), removed_versions, self.removed_versions_by_package.clone(), // TODO(phase-b): PartialSecurityAdvisory is a PHP class (no Clone). Need shared ownership rework. @@ -227,7 +222,7 @@ impl PoolOptimizer { ) -> Result<()> { let mut identical_definitions_per_package: IndexMap< String, - IndexMap<String, IndexMap<String, Vec<Box<dyn BasePackage>>>>, + IndexMap<String, IndexMap<String, Vec<BasePackageHandle>>>, > = IndexMap::new(); let mut package_identical_definition_lookup: IndexMap< i64, @@ -243,7 +238,7 @@ impl PoolOptimizer { self.mark_package_for_removal(package.id())?; - let dependency_hash = self.calculate_dependency_hash(package.as_ref()); + let dependency_hash = self.calculate_dependency_hash(package); for package_name in package.get_names(false) { if !self @@ -315,7 +310,7 @@ impl PoolOptimizer { .or_insert_with(IndexMap::new) .entry(dependency_hash.clone()) .or_insert_with(Vec::new) - .push(package.clone_box()); + .push(package.clone()); package_identical_definition_lookup .entry(package.id()) .or_insert_with(IndexMap::new) @@ -331,18 +326,14 @@ impl PoolOptimizer { } // PHP: foreach ($identicalDefinitionsPerPackage as $constraintGroups) - // TODO(phase-b): Box<dyn BasePackage> is not Clone; need restructuring to avoid borrow conflict. - let identical_clone: IndexMap< - String, - IndexMap<String, IndexMap<String, Vec<Box<dyn BasePackage>>>>, - > = todo!("identical_definitions_per_package.clone()"); + let identical_clone = identical_definitions_per_package.clone(); for (_, constraint_groups) in identical_clone.iter() { for (_, constraint_group) in constraint_groups.iter() { for (_, packages) in constraint_group.iter() { // Only one package in this constraint group has the same requirements, we're not allowed to remove that package if 1 == packages.len() { self.keep_package( - packages[0].as_ref(), + &packages[0], &identical_definitions_per_package, &package_identical_definition_lookup, ); @@ -362,7 +353,7 @@ impl PoolOptimizer { .select_preferred_packages(pool, literals.clone(), None) { self.keep_package( - pool.literal_to_package(preferred_literal), + &pool.literal_to_package(preferred_literal), &identical_definitions_per_package, &package_identical_definition_lookup, ); @@ -374,7 +365,7 @@ impl PoolOptimizer { Ok(()) } - fn calculate_dependency_hash(&self, package: &dyn BasePackage) -> String { + fn calculate_dependency_hash(&self, package: &BasePackageHandle) -> String { let mut hash = String::new(); let hash_relevant_links: Vec<(&str, Vec<crate::package::Link>)> = vec![ @@ -447,10 +438,10 @@ impl PoolOptimizer { /// @param array<int, array<string, array{groupHash: string, dependencyHash: string}>> $packageIdenticalDefinitionLookup fn keep_package( &mut self, - package: &dyn BasePackage, + package: &BasePackageHandle, identical_definitions_per_package: &IndexMap< String, - IndexMap<String, IndexMap<String, Vec<Box<dyn BasePackage>>>>, + IndexMap<String, IndexMap<String, Vec<BasePackageHandle>>>, >, package_identical_definition_lookup: &IndexMap< i64, @@ -464,11 +455,12 @@ impl PoolOptimizer { self.packages_to_remove.shift_remove(&package.id()); - if let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() { + if let Some(alias_pkg) = package.as_alias() { // recursing here so aliasesPerPackage for the aliasOf can be checked // and all its aliases marked to be kept as well + let aliased: BasePackageHandle = alias_pkg.get_alias_of().into(); self.keep_package( - alias_pkg.get_alias_of(), + &aliased, identical_definitions_per_package, package_identical_definition_lookup, ); @@ -484,21 +476,19 @@ impl PoolOptimizer { .and_then(|m| m.get(&package_group_pointers.dependency_hash)); if let Some(package_group) = package_group { for pkg in package_group { - let pkg = if let Some(alias_pkg) = - pkg.as_any().downcast_ref::<AliasPackage>() - { + let pkg: BasePackageHandle = if let Some(alias_pkg) = pkg.as_alias() { if alias_pkg.get_pretty_version() == VersionParser::DEFAULT_BRANCH_ALIAS { - alias_pkg.get_alias_of().clone_box() + alias_pkg.get_alias_of().into() } else { - pkg.clone_box() + pkg.clone() } } else { - pkg.clone_box() + pkg.clone() }; self.removed_versions_by_package - .entry(spl_object_hash(package)) + .entry(package.ptr_id().to_string()) .or_insert_with(IndexMap::new) .insert( pkg.get_version().to_string(), @@ -533,18 +523,17 @@ impl PoolOptimizer { .and_then(|m| m.get(&package_group_pointers.dependency_hash)); if let Some(package_group) = package_group { for pkg in package_group { - let pkg = if let Some(alias_pkg) = - pkg.as_any().downcast_ref::<AliasPackage>() + let pkg: BasePackageHandle = if let Some(alias_pkg) = pkg.as_alias() { if alias_pkg.get_pretty_version() == VersionParser::DEFAULT_BRANCH_ALIAS { - alias_pkg.get_alias_of().clone_box() + alias_pkg.get_alias_of().into() } else { - pkg.clone_box() + pkg.clone() } } else { - pkg.clone_box() + pkg.clone() }; self.removed_versions_by_package .entry(format!("alias-{}", alias_id)) @@ -569,8 +558,7 @@ impl PoolOptimizer { return; } - let mut package_index: IndexMap<String, IndexMap<i64, Box<dyn BasePackage>>> = - IndexMap::new(); + let mut package_index: IndexMap<String, IndexMap<i64, BasePackageHandle>> = IndexMap::new(); for package in pool.get_packages() { let id = package.id(); @@ -580,22 +568,18 @@ impl PoolOptimizer { continue; } // Do not remove a package aliased by another package, nor aliases - if self.aliases_per_package.contains_key(&id) - || package.as_any().downcast_ref::<AliasPackage>().is_some() - { + if self.aliases_per_package.contains_key(&id) || package.as_alias().is_some() { continue; } // Do not remove locked packages - if request.is_fixed_package(package.as_ref()) - || request.is_locked_package(todo!("package as &dyn PackageInterface")) - { + if request.is_fixed_package(&package) || request.is_locked_package(&package) { continue; } package_index - .entry(PackageInterface::get_name(package.as_ref()).to_string()) + .entry(package.get_name()) .or_insert_with(IndexMap::new) - .insert(package.id(), package.clone_box()); + .insert(package.id(), package.clone()); } for (_, package) in request.get_locked_packages() { diff --git a/crates/shirabe/src/dependency_resolver/problem.rs b/crates/shirabe/src/dependency_resolver/problem.rs index 4436ec8..509a0dc 100644 --- a/crates/shirabe/src/dependency_resolver/problem.rs +++ b/crates/shirabe/src/dependency_resolver/problem.rs @@ -19,6 +19,7 @@ use crate::dependency_resolver::Request; use crate::dependency_resolver::rule::{self, Rule}; use crate::package::AliasPackage; use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::CompletePackageInterface; use crate::package::Link; use crate::package::PackageInterface; @@ -67,7 +68,7 @@ impl Problem { request: &Request, pool: &mut Pool, is_verbose: bool, - installed_map: &IndexMap<String, Box<dyn BasePackage>>, + installed_map: &IndexMap<String, BasePackageHandle>, learned_pool: &Vec<Vec<std::rc::Rc<std::cell::RefCell<Rule>>>>, ) -> anyhow::Result<String> { // TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections? @@ -155,7 +156,9 @@ impl Problem { // TODO(phase-b): reason_data is a Link. let source = rule.get_source_package(pool).unwrap(); let link_pretty = match rule.get_reason_data() { - rule::ReasonData::Link(link) => link.get_pretty_string(source.as_ref()), + rule::ReasonData::Link(link) => { + link.get_pretty_string(source.as_rc().borrow().as_package_interface()) + } _ => String::new(), }; format!("{}//{}", source.get_pretty_string(), link_pretty) @@ -205,7 +208,7 @@ impl Problem { request: &Request, pool: &mut Pool, is_verbose: bool, - installed_map: &IndexMap<String, Box<dyn BasePackage>>, + installed_map: &IndexMap<String, BasePackageHandle>, learned_pool: &Vec<Vec<std::rc::Rc<std::cell::RefCell<Rule>>>>, ) -> String { let mut messages: Vec<String> = Vec::new(); @@ -258,9 +261,9 @@ impl Problem { .entry(pkg_key.clone()) .or_insert_with(IndexMap::new) .insert(version_key, m2.clone()); - let source_package = rule_ref.get_source_package(pool); + let source_package = rule_ref.get_source_package(pool).unwrap(); for (version, pretty_version) in - pool.get_removed_versions_by_package(&spl_object_hash(&source_package)) + pool.get_removed_versions_by_package(&source_package.ptr_id().to_string()) { templates .get_mut(&template) @@ -551,11 +554,13 @@ impl Problem { } } - let mut locked_package: Option<Box<dyn BasePackage>> = None; + let mut locked_package: Option<BasePackageHandle> = None; for (_key, package) in request.get_locked_packages() { - if package.get_name() == package_name { - locked_package = Some(package.clone_box()); - if pool.is_unacceptable_fixed_or_locked_package(package.as_ref()) { + if package.get_name().as_str() == package_name { + locked_package = Some(package.clone()); + // TODO(phase-c): wire Pool::is_unacceptable_fixed_or_locked_package(package) here; + // the locked package handle and the pool's identity check are now both handle-based. + if todo!("is_unacceptable_fixed_or_locked_package with a request package handle") { return ( "- ".to_string(), format!( @@ -629,7 +634,7 @@ impl Problem { if packages.len() > 0 { let root_reqs = repository_set.get_root_requires(); if root_reqs.contains_key(package_name) { - let filtered: Vec<&Box<dyn BasePackage>> = packages + let filtered: Vec<&BasePackageHandle> = packages .iter() .filter(|p| { root_reqs[package_name].matches( @@ -673,7 +678,7 @@ impl Problem { let first_pkg = packages.first().unwrap(); for name in first_pkg.get_names(true) { if temp_reqs.contains_key(&name) { - let filtered: Vec<&Box<dyn BasePackage>> = packages + let filtered: Vec<&BasePackageHandle> = packages .iter() .filter(|p| { temp_reqs[&name].matches( @@ -721,7 +726,7 @@ impl Problem { lp.get_version().to_string(), None, )); - let filtered: Vec<&Box<dyn BasePackage>> = packages + let filtered: Vec<&BasePackageHandle> = packages .iter() .filter(|p| { fixed_constraint.matches( @@ -756,14 +761,10 @@ impl Problem { } } - let non_locked_packages: Vec<&Box<dyn BasePackage>> = packages - .iter() - .filter(|p| { - p.get_repository() - .and_then(|r| r.as_any().downcast_ref::<LockArrayRepository>()) - .is_none() - }) - .collect(); + // TODO(phase-c): filtering out packages from a LockArrayRepository needs the handle's + // repository back-reference (phase-c handoff item #1), which is not yet available; keep + // all packages for now. + let non_locked_packages: Vec<&BasePackageHandle> = packages.iter().collect(); if non_locked_packages.len() == 0 { return ( @@ -961,7 +962,7 @@ impl Problem { let all_repos_packages = &packages; let top_package = all_repos_packages.first(); if let Some(tp) = top_package { - if tp.as_root_package_interface().is_some() { + if tp.as_root().is_some() { suffix = " See https://getcomposer.org/dep-on-root for details and assistance." .to_string(); } @@ -1023,7 +1024,7 @@ impl Problem { /// @internal pub fn get_package_list( - packages: &Vec<Box<dyn BasePackage>>, + packages: &Vec<BasePackageHandle>, is_verbose: bool, pool: Option<&Pool>, constraint: Option<&AnyConstraint>, @@ -1044,7 +1045,7 @@ impl Problem { versions: IndexMap::new(), }); entry.name = package.get_pretty_name().to_string(); - let alias_suffix = if let Some(alias) = package.as_alias_package() { + let alias_suffix = if let Some(alias) = package.as_alias() { format!(" (alias of {})", alias.get_alias_of().get_pretty_version()) } else { String::new() @@ -1064,7 +1065,7 @@ impl Problem { if pool.is_some() && use_removed_version_group { for (version, pretty_version) in pool .unwrap() - .get_removed_versions_by_package(&spl_object_hash(package.as_ref())) + .get_removed_versions_by_package(&package.ptr_id().to_string()) { entry.versions.insert(version, pretty_version); } @@ -1129,16 +1130,12 @@ impl Problem { let available = pool.what_provides(package_name, None); if available.len() > 0 { - let mut selected: Option<&Box<dyn BasePackage>> = None; + let mut selected: Option<&BasePackageHandle> = None; + // TODO(phase-c): the handle does not expose get_repository (a `RefCell`-borrowed + // back-reference); preferring the package from a PlatformRepository needs repository + // back-references on handles. Falling back to the first candidate for now. for pkg in &available { - if pkg - .get_repository() - .and_then(|r| r.as_any().downcast_ref::<PlatformRepository>()) - .is_some() - { - selected = Some(pkg); - break; - } + let _ = pkg; } if selected.is_none() { selected = available.first(); @@ -1163,15 +1160,14 @@ impl Problem { let mut version: String = selected.get_pretty_version().to_string(); let extra = selected.get_extra(); - if selected.as_complete_package_interface().is_some() + if selected.as_complete().is_some() && extra.contains_key("config.platform") && extra["config.platform"].as_bool() == Some(true) { let description: String = selected - .as_complete_package_interface() + .as_complete() .and_then(|c| c.get_description()) - .unwrap_or("") - .to_string(); + .unwrap_or_default(); version = format!("{}; {}", version, str_replace("Package ", "", &description)); } return Some(version); @@ -1226,10 +1222,10 @@ impl Problem { filtered } - fn has_multiple_names(packages: &Vec<Box<dyn BasePackage>>) -> bool { + fn has_multiple_names(packages: &Vec<BasePackageHandle>) -> bool { let mut name: Option<String> = None; for package in packages { - if name.is_none() || name.as_deref() == Some(package.get_name()) { + if name.is_none() || name.as_deref() == Some(package.get_name().as_str()) { name = Some(package.get_name().to_string()); } else { return true; @@ -1243,30 +1239,22 @@ impl Problem { pool: &Pool, is_verbose: bool, package_name: &str, - higher_repo_packages: &Vec<Box<dyn BasePackage>>, - all_repos_packages: &Vec<Box<dyn BasePackage>>, + higher_repo_packages: &Vec<BasePackageHandle>, + all_repos_packages: &Vec<BasePackageHandle>, reason: &str, constraint: Option<&AnyConstraint>, ) -> (String, String) { - let mut next_repo_packages: Vec<Box<dyn BasePackage>> = Vec::new(); - let mut next_repo: Option<Box<dyn crate::repository::RepositoryInterface>> = None; - - for package in all_repos_packages { - // TODO(phase-b): RepositoryInterface has no equals(); reference identity needed. - if next_repo.is_none() { - next_repo_packages.push(package.clone_box()); - next_repo = package.get_repository().map(|r| r.clone_box()); - } else { - break; - } - } - - // assert(null !== $nextRepo); - let next_repo = next_repo.unwrap(); + // TODO(phase-c): selecting the next repository's packages relies on each package's + // repository back-reference, which the handle does not yet expose (phase-c handoff + // item #1). Both `next_repo_packages` and `next_repo` are blocked on that decision. + let _ = all_repos_packages; + let next_repo_packages: Vec<BasePackageHandle> = Vec::new(); + let next_repo: Box<dyn crate::repository::RepositoryInterface> = + todo!("repository back-reference on handle pending (phase-c handoff item #1)"); if higher_repo_packages.len() > 0 { let top_package = higher_repo_packages.first().unwrap(); - if top_package.as_root_package_interface().is_some() { + if top_package.as_root().is_some() { return ( format!( "- Root composer.json requires {}{}, it is ", @@ -1309,7 +1297,7 @@ impl Problem { ) ); // symlinked path repos cannot be locked so do not suggest keeping it locked - if next_repo_packages[0].get_dist_type() == Some("path") { + if next_repo_packages[0].get_dist_type() == Some("path".to_string()) { let transport_options = next_repo_packages[0].get_transport_options(); if !transport_options.contains_key("symlink") || transport_options["symlink"].as_bool() != Some(false) @@ -1367,12 +1355,10 @@ impl Problem { constraint, false ), - higher_repo_packages - .first() - .unwrap() - .get_repository() - .map(|r| r.get_repo_name()) - .unwrap_or_default(), + // TODO(phase-c): the higher repo's name needs the handle's repository + // back-reference (phase-c handoff item #1); unreachable until `next_repo` above + // is resolved. + String::new(), reason ), ) diff --git a/crates/shirabe/src/dependency_resolver/request.rs b/crates/shirabe/src/dependency_resolver/request.rs index e2bfc3c..686373b 100644 --- a/crates/shirabe/src/dependency_resolver/request.rs +++ b/crates/shirabe/src/dependency_resolver/request.rs @@ -5,8 +5,7 @@ use shirabe_php_shim::{LogicException, spl_object_hash, strtolower}; use shirabe_semver::constraint::AnyConstraint; use shirabe_semver::constraint::MatchAllConstraint; -use crate::package::BasePackage; -use crate::package::PackageInterface; +use crate::package::BasePackageHandle; use crate::repository::CanonicalPackagesTrait; use crate::repository::LockArrayRepository; use crate::repository::RepositoryInterface; @@ -44,9 +43,9 @@ pub enum UpdateAllowTransitiveDeps { pub struct Request { pub(crate) locked_repository: Option<LockArrayRepository>, pub(crate) requires: IndexMap<String, AnyConstraint>, - pub(crate) fixed_packages: IndexMap<String, Box<dyn BasePackage>>, - pub(crate) locked_packages: IndexMap<String, Box<dyn BasePackage>>, - pub(crate) fixed_locked_packages: IndexMap<String, Box<dyn BasePackage>>, + pub(crate) fixed_packages: IndexMap<String, BasePackageHandle>, + pub(crate) locked_packages: IndexMap<String, BasePackageHandle>, + pub(crate) fixed_locked_packages: IndexMap<String, BasePackageHandle>, pub(crate) update_allow_list: Vec<String>, pub(crate) update_allow_transitive_dependencies: UpdateAllowTransitiveDeps, restrict_packages: Option<Vec<String>>, @@ -94,8 +93,8 @@ impl Request { /// This is used for platform packages which cannot be modified by Composer. A rule enforcing /// their installation is generated for dependency resolution. Partial updates with dependencies /// cannot in any way modify these packages. - pub fn fix_package(&mut self, package: Box<dyn BasePackage>) { - let hash = spl_object_hash(&package); + pub fn fix_package(&mut self, package: BasePackageHandle) { + let hash = package.ptr_id().to_string(); self.fixed_packages.insert(hash, package); } @@ -109,8 +108,8 @@ impl Request { /// for the solver, so if nothing requires these packages they will be removed. Additionally in /// a partial update these packages can be unlocked, meaning other versions can be installed if /// explicitly requested as part of the update. - pub fn lock_package(&mut self, package: Box<dyn BasePackage>) { - let hash = spl_object_hash(&package); + pub fn lock_package(&mut self, package: BasePackageHandle) { + let hash = package.ptr_id().to_string(); self.locked_packages.insert(hash, package); } @@ -120,15 +119,14 @@ impl Request { /// should not allow removal of any packages. At the same time lock packages there cannot simply /// be marked fixed, as error reporting would then report them as platform packages, so this /// still marks them as locked packages at the same time. - pub fn fix_locked_package(&mut self, package: Box<dyn BasePackage>) { - let hash = spl_object_hash(&package); - self.fixed_packages - .insert(hash.clone(), package.clone_box()); + pub fn fix_locked_package(&mut self, package: BasePackageHandle) { + let hash = package.ptr_id().to_string(); + self.fixed_packages.insert(hash.clone(), package.clone()); self.fixed_locked_packages.insert(hash, package); } - pub fn unlock_package(&mut self, package: &dyn BasePackage) { - self.locked_packages.remove(&spl_object_hash(package)); + pub fn unlock_package(&mut self, package: &BasePackageHandle) { + self.locked_packages.remove(&package.ptr_id().to_string()); } pub fn set_update_allow_list( @@ -159,33 +157,34 @@ impl Request { &self.requires } - pub fn get_fixed_packages(&self) -> &IndexMap<String, Box<dyn BasePackage>> { + pub fn get_fixed_packages(&self) -> &IndexMap<String, BasePackageHandle> { &self.fixed_packages } - pub fn is_fixed_package(&self, package: &dyn BasePackage) -> bool { - self.fixed_packages.contains_key(&spl_object_hash(package)) + pub fn is_fixed_package(&self, package: &BasePackageHandle) -> bool { + self.fixed_packages + .contains_key(&package.ptr_id().to_string()) } - pub fn get_locked_packages(&self) -> &IndexMap<String, Box<dyn BasePackage>> { + pub fn get_locked_packages(&self) -> &IndexMap<String, BasePackageHandle> { &self.locked_packages } - pub fn is_locked_package(&self, package: &dyn PackageInterface) -> bool { - let hash = spl_object_hash(package); + pub fn is_locked_package(&self, package: &BasePackageHandle) -> bool { + let hash = package.ptr_id().to_string(); self.locked_packages.contains_key(&hash) || self.fixed_locked_packages.contains_key(&hash) } - pub fn get_fixed_or_locked_packages(&self) -> IndexMap<String, Box<dyn BasePackage>> { - let mut result: IndexMap<String, Box<dyn BasePackage>> = self + pub fn get_fixed_or_locked_packages(&self) -> IndexMap<String, BasePackageHandle> { + let mut result: IndexMap<String, BasePackageHandle> = self .fixed_packages .iter() - .map(|(k, v)| (k.clone(), v.clone_box())) + .map(|(k, v)| (k.clone(), v.clone())) .collect(); result.extend( self.locked_packages .iter() - .map(|(k, v)| (k.clone(), v.clone_box())), + .map(|(k, v)| (k.clone(), v.clone())), ); result } @@ -194,15 +193,18 @@ impl Request { /// is for the installed map in the solver problems. /// Some locked packages may not be in the pool, /// so they have a package->id of -1 - pub fn get_present_map(&self, package_ids: bool) -> IndexMap<String, Box<dyn BasePackage>> { - let mut present_map: IndexMap<String, Box<dyn BasePackage>> = IndexMap::new(); + pub fn get_present_map( + &self, + package_ids: bool, + ) -> IndexMap<String, crate::package::BasePackageHandle> { + let mut present_map: IndexMap<String, crate::package::BasePackageHandle> = IndexMap::new(); if let Some(ref locked_repository) = self.locked_repository { for package in RepositoryInterface::get_packages(locked_repository) { let key = if package_ids { package.get_id().to_string() } else { - spl_object_hash(&package) + package.ptr_id().to_string() }; present_map.insert(key, package); } @@ -212,18 +214,18 @@ impl Request { let key = if package_ids { package.get_id().to_string() } else { - spl_object_hash(package) + package.ptr_id().to_string() }; - present_map.insert(key, package.clone_box()); + present_map.insert(key, package.clone()); } present_map } - pub fn get_fixed_packages_map(&self) -> IndexMap<i64, Box<dyn BasePackage>> { - let mut fixed_packages_map: IndexMap<i64, Box<dyn BasePackage>> = IndexMap::new(); + pub fn get_fixed_packages_map(&self) -> IndexMap<i64, BasePackageHandle> { + let mut fixed_packages_map: IndexMap<i64, BasePackageHandle> = IndexMap::new(); for (_, package) in &self.fixed_packages { - fixed_packages_map.insert(package.get_id(), package.clone_box()); + fixed_packages_map.insert(package.get_id(), package.clone()); } fixed_packages_map } diff --git a/crates/shirabe/src/dependency_resolver/rule.rs b/crates/shirabe/src/dependency_resolver/rule.rs index fbc8521..afb0318 100644 --- a/crates/shirabe/src/dependency_resolver/rule.rs +++ b/crates/shirabe/src/dependency_resolver/rule.rs @@ -22,6 +22,7 @@ use crate::dependency_resolver::Rule2Literals; use crate::dependency_resolver::RuleSet; use crate::package::AliasPackage; use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::Link; use crate::package::PackageInterface; use crate::package::version::VersionParser; @@ -31,7 +32,7 @@ use crate::repository::RepositorySet; #[derive(Debug)] pub enum ReasonData { Link(Link), - BasePackage(Box<dyn BasePackage>), + BasePackage(BasePackageHandle), String(String), Int(i64), RootRequire { @@ -39,7 +40,7 @@ pub enum ReasonData { constraint: AnyConstraint, }, Fixed { - package: Box<dyn BasePackage>, + package: BasePackageHandle, }, /// Phase B placeholder for an arbitrary PHP-side value not yet mapped to a real variant. Mixed(PhpMixed), @@ -231,9 +232,9 @@ impl Rule { // TODO(phase-b): Request::get_locked_repository() signature let locked_repo: Option<()> = todo!("request.get_locked_repository()"); if let Some(_locked_repo) = locked_repo { - let packages: Vec<Box<dyn BasePackage>> = todo!("locked_repo.get_packages()"); + let packages: Vec<BasePackageHandle> = todo!("locked_repo.get_packages()"); for package in packages { - let p: &dyn BasePackage = todo!("package as BasePackage reference"); + let p: &BasePackageHandle = &package; if p.get_name() == link.get_target() { if pool.is_unacceptable_fixed_or_locked_package(p) { return true; @@ -272,10 +273,10 @@ impl Rule { // TODO(phase-b): Request::get_locked_repository() signature let locked_repo: Option<()> = todo!("request.get_locked_repository()"); if let Some(_locked_repo) = locked_repo { - let packages: Vec<Box<dyn BasePackage>> = todo!("locked_repo.get_packages()"); + let packages: Vec<BasePackageHandle> = todo!("locked_repo.get_packages()"); for package in packages { - let p: &dyn BasePackage = todo!("package as BasePackage reference"); - if p.get_name() == package_name { + let p: &BasePackageHandle = &package; + if p.get_name() == *package_name { if pool.is_unacceptable_fixed_or_locked_package(p) { return true; } @@ -300,17 +301,15 @@ impl Rule { } /// @internal - pub fn get_source_package(&self, pool: &Pool) -> Result<Box<dyn BasePackage>> { + pub fn get_source_package(&self, pool: &Pool) -> Result<BasePackageHandle> { let literals = self.get_literals(); match self.get_reason() { r if r == RULE_PACKAGE_CONFLICT => { - let mut package1 = self.deduplicate_default_branch_alias( - pool.literal_to_package(literals[0]).clone_box(), - ); - let mut package2 = self.deduplicate_default_branch_alias( - pool.literal_to_package(literals[1]).clone_box(), - ); + let mut package1 = + self.deduplicate_default_branch_alias(pool.literal_to_package(literals[0])); + let mut package2 = + self.deduplicate_default_branch_alias(pool.literal_to_package(literals[1])); let reason_data = self.get_reason_data(); // swap literals if they are not in the right order with package2 being the conflicter @@ -325,9 +324,8 @@ impl Rule { r if r == RULE_PACKAGE_REQUIRES => { let source_literal = literals[0]; - let source_package = self.deduplicate_default_branch_alias( - pool.literal_to_package(source_literal).clone_box(), - ); + let source_package = + self.deduplicate_default_branch_alias(pool.literal_to_package(source_literal)); Ok(source_package) } @@ -348,7 +346,7 @@ impl Rule { request: &Request, pool: &mut Pool, is_verbose: bool, - installed_map: &IndexMap<String, Box<dyn BasePackage>>, + installed_map: &IndexMap<String, BasePackageHandle>, _learned_pool: &Vec<Vec<Rc<RefCell<Rule>>>>, ) -> String { let mut literals = self.get_literals(); @@ -373,10 +371,10 @@ impl Rule { ); } - let packages_non_alias: Vec<Box<dyn BasePackage>> = packages + let packages_non_alias: Vec<BasePackageHandle> = packages .iter() - .filter(|p| p.as_any().downcast_ref::<AliasPackage>().is_none()) - .map(|p| p.clone_box()) + .filter(|p| p.as_alias().is_none()) + .map(|p| p.clone()) .collect(); if packages_non_alias.len() == 1 { let package = &packages_non_alias[0]; @@ -396,7 +394,7 @@ impl Rule { constraint.get_pretty_string(), self.format_packages_unique_from_packages( pool, - packages, + packages.iter().map(|p| p.clone()).collect(), is_verbose, Some(constraint), false @@ -406,7 +404,7 @@ impl Rule { r if r == RULE_FIXED => { let package_in = match self.get_reason_data() { - ReasonData::Fixed { package } => package.clone_box(), + ReasonData::Fixed { package } => package.clone(), _ => return String::new(), }; let package = self.deduplicate_default_branch_alias(package_in); @@ -427,12 +425,10 @@ impl Rule { } r if r == RULE_PACKAGE_CONFLICT => { - let mut package1 = self.deduplicate_default_branch_alias( - pool.literal_to_package(literals[0]).clone_box(), - ); - let mut package2 = self.deduplicate_default_branch_alias( - pool.literal_to_package(literals[1]).clone_box(), - ); + let mut package1 = + self.deduplicate_default_branch_alias(pool.literal_to_package(literals[0])); + let mut package2 = + self.deduplicate_default_branch_alias(pool.literal_to_package(literals[1])); let mut conflict_target = package1.get_pretty_string(); let reason_data = self.get_reason_data(); @@ -495,21 +491,21 @@ impl Rule { r if r == RULE_PACKAGE_REQUIRES => { assert!(literals.len() > 0); let source_literal = array_shift(&mut literals).unwrap(); - let source_package = self.deduplicate_default_branch_alias( - pool.literal_to_package(source_literal).clone_box(), - ); + let source_package = + self.deduplicate_default_branch_alias(pool.literal_to_package(source_literal)); let reason_data = self.get_reason_data(); let link = match reason_data { ReasonData::Link(l) => l, _ => return String::new(), }; - let mut requires: Vec<Box<dyn BasePackage>> = vec![]; + let mut requires: Vec<BasePackageHandle> = vec![]; for literal in &literals { - requires.push(pool.literal_to_package(*literal).clone_box()); + requires.push(pool.literal_to_package(*literal)); } - let text = link.get_pretty_string(&*source_package); + let text = + link.get_pretty_string(source_package.as_rc().borrow().as_package_interface()); if requires.len() > 0 { format!( "{} -> satisfiable by {}.", @@ -573,13 +569,13 @@ impl Rule { reason_str }; - let mut installed_packages: Vec<Box<dyn BasePackage>> = vec![]; - let mut removable_packages: Vec<Box<dyn BasePackage>> = vec![]; + let mut installed_packages: Vec<BasePackageHandle> = vec![]; + let mut removable_packages: Vec<BasePackageHandle> = vec![]; for literal in &literals { if installed_map.contains_key(&abs(*literal).to_string()) { - installed_packages.push(pool.literal_to_package(*literal).clone_box()); + installed_packages.push(pool.literal_to_package(*literal)); } else { - removable_packages.push(pool.literal_to_package(*literal).clone_box()); + removable_packages.push(pool.literal_to_package(*literal)); } } @@ -628,7 +624,7 @@ impl Rule { let rule_text = if literals.len() == 1 { pool.literal_to_pretty_string(literals[0], &installed_map) } else { - let mut groups: IndexMap<String, Vec<Box<dyn BasePackage>>> = IndexMap::new(); + let mut groups: IndexMap<String, Vec<BasePackageHandle>> = IndexMap::new(); for literal in &literals { let package = pool.literal_to_package(*literal); let group = if installed_map.contains_key(&package.id().to_string()) { @@ -644,7 +640,7 @@ impl Rule { groups .entry(group.to_string()) .or_insert_with(Vec::new) - .push(self.deduplicate_default_branch_alias(package.clone_box())); + .push(self.deduplicate_default_branch_alias(package.clone())); } let mut rule_texts: Vec<String> = vec![]; for (group, packages) in &groups { @@ -654,7 +650,7 @@ impl Rule { if packages.len() > 1 { " one of" } else { "" }, self.format_packages_unique_from_packages( pool, - packages.iter().map(|p| p.clone_box()).collect(), + packages.iter().map(|p| p.clone()).collect(), is_verbose, None, false, @@ -674,9 +670,8 @@ impl Rule { if alias_package.get_version() == VersionParser::DEFAULT_BRANCH_ALIAS { return String::new(); } - let package = self.deduplicate_default_branch_alias( - pool.literal_to_package(literals[1]).clone_box(), - ); + let package = + self.deduplicate_default_branch_alias(pool.literal_to_package(literals[1])); format!( "{} is an alias of {} and thus requires it to be installed too.", @@ -692,9 +687,8 @@ impl Rule { if alias_package.get_version() == VersionParser::DEFAULT_BRANCH_ALIAS { return String::new(); } - let package = self.deduplicate_default_branch_alias( - pool.literal_to_package(literals[0]).clone_box(), - ); + let package = + self.deduplicate_default_branch_alias(pool.literal_to_package(literals[0])); format!( "{} is an alias of {} and must be installed with it.", @@ -720,7 +714,7 @@ impl Rule { fn format_packages_unique_from_packages( &self, pool: &Pool, - packages: Vec<Box<dyn BasePackage>>, + packages: Vec<BasePackageHandle>, is_verbose: bool, constraint: Option<&AnyConstraint>, use_removed_version_group: bool, @@ -743,9 +737,9 @@ impl Rule { constraint: Option<&AnyConstraint>, use_removed_version_group: bool, ) -> String { - let mut packages: Vec<Box<dyn BasePackage>> = vec![]; + let mut packages: Vec<BasePackageHandle> = vec![]; for literal in literals { - packages.push(pool.literal_to_package(*literal).clone_box()); + packages.push(pool.literal_to_package(*literal)); } Problem::get_package_list( &packages, @@ -756,13 +750,10 @@ impl Rule { ) } - fn deduplicate_default_branch_alias( - &self, - package: Box<dyn BasePackage>, - ) -> Box<dyn BasePackage> { - if let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() { + fn deduplicate_default_branch_alias(&self, package: BasePackageHandle) -> BasePackageHandle { + if let Some(alias_pkg) = package.as_alias() { if alias_pkg.get_pretty_version() == VersionParser::DEFAULT_BRANCH_ALIAS { - return alias_pkg.get_alias_of().clone_box(); + return alias_pkg.get_alias_of().into(); } } diff --git a/crates/shirabe/src/dependency_resolver/rule_set_generator.rs b/crates/shirabe/src/dependency_resolver/rule_set_generator.rs index 076ce42..69f5c4a 100644 --- a/crates/shirabe/src/dependency_resolver/rule_set_generator.rs +++ b/crates/shirabe/src/dependency_resolver/rule_set_generator.rs @@ -19,17 +19,15 @@ use crate::dependency_resolver::rule::{self, Rule}; use crate::filter::platform_requirement_filter::IgnoreListPlatformRequirementFilter; use crate::filter::platform_requirement_filter::PlatformRequirementFilterFactory; use crate::filter::platform_requirement_filter::PlatformRequirementFilterInterface; -use crate::package::AliasPackage; -use crate::package::BasePackage; -use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; #[derive(Debug)] pub struct RuleSetGenerator { pub(crate) policy: Box<dyn PolicyInterface>, pub(crate) pool: std::rc::Rc<std::cell::RefCell<Pool>>, pub(crate) rules: RuleSet, - pub(crate) added_map: IndexMap<i64, Box<dyn PackageInterface>>, - pub(crate) added_packages_by_names: IndexMap<String, Vec<Box<dyn PackageInterface>>>, + pub(crate) added_map: IndexMap<i64, PackageInterfaceHandle>, + pub(crate) added_packages_by_names: IndexMap<String, Vec<PackageInterfaceHandle>>, } impl RuleSetGenerator { @@ -52,8 +50,8 @@ impl RuleSetGenerator { /// one requirement of the package A. fn create_require_rule( &self, - package: &dyn PackageInterface, - providers: &[Box<dyn PackageInterface>], + package: &PackageInterfaceHandle, + providers: &[PackageInterfaceHandle], reason: i64, reason_data: PhpMixed, ) -> Option<GenericRule> { @@ -61,10 +59,7 @@ impl RuleSetGenerator { for provider in providers { // self fulfilling rule? - if std::ptr::eq( - provider.as_ref() as *const dyn PackageInterface, - package as *const dyn PackageInterface, - ) { + if provider.ptr_eq(package) { return None; } literals.push(provider.get_id()); @@ -83,7 +78,7 @@ impl RuleSetGenerator { /// set of packages is empty an impossible rule is generated. fn create_install_one_of_rule( &self, - packages: &[Box<dyn PackageInterface>], + packages: &[PackageInterfaceHandle], reason: i64, reason_data: PhpMixed, ) -> GenericRule { @@ -97,16 +92,13 @@ impl RuleSetGenerator { /// and B the provider. fn create_rule2_literals( &self, - issuer: &dyn PackageInterface, - provider: &dyn PackageInterface, + issuer: &PackageInterfaceHandle, + provider: &PackageInterfaceHandle, reason: i64, reason_data: PhpMixed, ) -> Option<Rule2Literals> { // ignore self conflict - if std::ptr::eq( - issuer as *const dyn PackageInterface, - provider as *const dyn PackageInterface, - ) { + if issuer.ptr_eq(provider) { return None; } @@ -120,7 +112,7 @@ impl RuleSetGenerator { fn create_multi_conflict_rule( &self, - packages: &[Box<dyn PackageInterface>], + packages: &[PackageInterfaceHandle], reason: i64, reason_data: PhpMixed, ) -> Rule { @@ -152,10 +144,10 @@ impl RuleSetGenerator { pub(crate) fn add_rules_for_package( &mut self, - package: Box<dyn PackageInterface>, + package: PackageInterfaceHandle, platform_requirement_filter: &dyn PlatformRequirementFilterInterface, ) { - let mut work_queue: VecDeque<Box<dyn PackageInterface>> = VecDeque::new(); + let mut work_queue: VecDeque<PackageInterfaceHandle> = VecDeque::new(); work_queue.push_back(package); while let Some(package) = work_queue.pop_front() { @@ -163,26 +155,25 @@ impl RuleSetGenerator { continue; } - self.added_map - .insert(package.get_id(), package.clone_package_box()); + self.added_map.insert(package.get_id(), package.clone()); - let is_alias = package.as_any().downcast_ref::<AliasPackage>().is_some(); + let is_alias = package.as_alias().is_some(); if !is_alias { for name in package.get_names(false) { self.added_packages_by_names .entry(name) .or_default() - .push(package.clone_package_box()); + .push(package.clone()); } } else { - let alias_pkg = package.as_any().downcast_ref::<AliasPackage>().unwrap(); + let alias_pkg = package.as_alias().unwrap(); - work_queue.push_back(alias_pkg.get_alias_of().clone_package_box()); - let alias_of = alias_pkg.get_alias_of(); + let alias_of: PackageInterfaceHandle = alias_pkg.get_alias_of().into(); + work_queue.push_back(alias_of.clone()); let rule = self.create_require_rule( - &*package, - &[alias_of.clone_package_box()], + &package, + &[alias_of.clone()], rule::RULE_PACKAGE_ALIAS, PhpMixed::Null, // reasonData: $package (BasePackage) ); @@ -190,8 +181,8 @@ impl RuleSetGenerator { // aliases must be installed with their main package, so create a rule the other way around as well let inverse_rule = self.create_require_rule( - alias_of, - &[package.clone_package_box()], + &alias_of, + &[package.clone()], rule::RULE_PACKAGE_INVERSE_ALIAS, PhpMixed::Null, // reasonData: $package->getAliasOf() (BasePackage) ); @@ -218,16 +209,16 @@ impl RuleSetGenerator { .unwrap_or(fallback); } - let possible_requires: Vec<Box<dyn PackageInterface>> = self + let possible_requires: Vec<PackageInterfaceHandle> = self .pool .borrow_mut() .what_provides(link.get_target(), Some(&constraint)) .into_iter() - .map(|p| p.clone_package_box()) + .map(|p| p.into()) .collect(); let rule = self.create_require_rule( - &*package, + &package, &possible_requires, rule::RULE_PACKAGE_REQUIRES, PhpMixed::Null, // reasonData: $link (Link) @@ -245,11 +236,7 @@ impl RuleSetGenerator { &mut self, platform_requirement_filter: &dyn PlatformRequirementFilterInterface, ) { - let packages: Vec<Box<dyn PackageInterface>> = self - .added_map - .values() - .map(|p| p.clone_package_box()) - .collect(); + let packages: Vec<PackageInterfaceHandle> = self.added_map.values().cloned().collect(); for package in &packages { for link in package.get_conflicts().values() { @@ -271,22 +258,24 @@ impl RuleSetGenerator { .unwrap_or(fallback); } - let conflicts = self + let conflicts: Vec<PackageInterfaceHandle> = self .pool .borrow_mut() - .what_provides(link.get_target(), Some(&constraint)); + .what_provides(link.get_target(), Some(&constraint)) + .into_iter() + .map(|p| p.into()) + .collect(); for conflict in &conflicts { // define the conflict rule for regular packages, for alias packages it's only needed if the name // matches the conflict exactly, otherwise the name match is by provide/replace which means the // package which this is an alias of will conflict anyway, so no need to create additional rules - let conflict_is_alias = - conflict.as_any().downcast_ref::<AliasPackage>().is_some(); + let conflict_is_alias = conflict.as_alias().is_some(); let conflict_name_matches = conflict.get_name() == link.get_target(); if !conflict_is_alias || conflict_name_matches { let rule = self.create_rule2_literals( - &**package, - &**conflict, + package, + conflict, rule::RULE_PACKAGE_CONFLICT, PhpMixed::Null, // reasonData: $link (Link) ); @@ -296,10 +285,10 @@ impl RuleSetGenerator { } } - let names_packages: Vec<(String, Vec<Box<dyn PackageInterface>>)> = self + let names_packages: Vec<(String, Vec<PackageInterfaceHandle>)> = self .added_packages_by_names .iter() - .map(|(k, v)| (k.clone(), v.iter().map(|p| p.clone_package_box()).collect())) + .map(|(k, v)| (k.clone(), v.iter().cloned().collect())) .collect(); for (name, packages) in names_packages { @@ -320,11 +309,9 @@ impl RuleSetGenerator { for package in request.get_fixed_packages().values() { if package.get_id() == -1 { // fixed package was not added to the pool as it did not pass the stability requirements, this is fine - if self - .pool - .borrow() - .is_unacceptable_fixed_or_locked_package(package.as_ref()) - { + // TODO(phase-c): wire Pool::is_unacceptable_fixed_or_locked_package(package) here; + // the package handle and the pool's identity check are now both handle-based. + if todo!("is_unacceptable_fixed_or_locked_package with a request package handle") { continue; } @@ -338,7 +325,7 @@ impl RuleSetGenerator { })); } - self.add_rules_for_package(package.clone_box(), platform_requirement_filter); + self.add_rules_for_package(package.clone().into(), platform_requirement_filter); let mut reason_data: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); reason_data.insert( @@ -346,7 +333,7 @@ impl RuleSetGenerator { Box::new(PhpMixed::Null), // reasonData: $package (BasePackage) ); let rule = self.create_install_one_of_rule( - &[package.clone_package_box()], + &[package.clone().into()], rule::RULE_FIXED, PhpMixed::Array(reason_data), ); @@ -367,19 +354,16 @@ impl RuleSetGenerator { .unwrap_or(fallback); } - let packages: Vec<Box<dyn PackageInterface>> = self + let packages: Vec<PackageInterfaceHandle> = self .pool .borrow_mut() .what_provides(package_name, Some(&constraint)) .into_iter() - .map(|p| p.clone_package_box()) + .map(|p| p.into()) .collect(); if !packages.is_empty() { for package in &packages { - self.add_rules_for_package( - package.clone_package_box(), - platform_requirement_filter, - ); + self.add_rules_for_package(package.clone(), platform_requirement_filter); } let mut reason_data: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); @@ -407,19 +391,19 @@ impl RuleSetGenerator { &mut self, platform_requirement_filter: &dyn PlatformRequirementFilterInterface, ) { - let packages: Vec<Box<dyn BasePackage>> = self + let packages: Vec<PackageInterfaceHandle> = self .pool .borrow() .get_packages() .iter() - .map(|p| p.clone_box()) + .map(|p| p.clone().into()) .collect(); for package in &packages { // ensure that rules for root alias packages and aliases of packages which were loaded are also loaded // even if the alias itself isn't required, otherwise a package could be installed without its alias which // leads to unexpected behavior let is_not_added = !self.added_map.contains_key(&package.get_id()); - let as_alias = package.as_any().downcast_ref::<AliasPackage>(); + let as_alias = package.as_alias(); if is_not_added { if let Some(alias_pkg) = as_alias { if alias_pkg.is_root_package_alias() @@ -427,10 +411,7 @@ impl RuleSetGenerator { .added_map .contains_key(&alias_pkg.get_alias_of().get_id()) { - self.add_rules_for_package( - package.clone_package_box(), - platform_requirement_filter, - ); + self.add_rules_for_package(package.clone(), platform_requirement_filter); } } } diff --git a/crates/shirabe/src/dependency_resolver/security_advisory_pool_filter.rs b/crates/shirabe/src/dependency_resolver/security_advisory_pool_filter.rs index 4daf641..ba2cf09 100644 --- a/crates/shirabe/src/dependency_resolver/security_advisory_pool_filter.rs +++ b/crates/shirabe/src/dependency_resolver/security_advisory_pool_filter.rs @@ -31,12 +31,11 @@ impl SecurityAdvisoryPoolFilter { repositories: Vec<Box<dyn RepositoryInterface>>, request: &Request, ) -> Pool { - // TODO(phase-b): port the filter() body. Blockers: + // TODO(phase-c): port the filter() body. Blockers: // * RepositorySet::new takes 6 args; ConfigSourceInterface refactor pending - // * pool.get_packages() yields Box<dyn BasePackage>, but the audit/repo APIs - // expect Box<dyn PackageInterface>; needs trait-object coercion / cloning story - // * Pool::new requires owned Vecs, but existing pool's getters return refs and - // Box<dyn BasePackage> is not Clone (only clone_box). + // * pool.get_packages() yields BasePackageHandle; widen to PackageInterfaceHandle + // (via .into()) where the audit/repo APIs expect PackageInterface. + // * Pool::new requires owned Vecs; clone the handles out of the existing pool. // * advisory map element type mismatch (PhpMixed vs PartialSecurityAdvisory). let _ = ( pool, diff --git a/crates/shirabe/src/dependency_resolver/solver.rs b/crates/shirabe/src/dependency_resolver/solver.rs index 820f792..2610f07 100644 --- a/crates/shirabe/src/dependency_resolver/solver.rs +++ b/crates/shirabe/src/dependency_resolver/solver.rs @@ -29,7 +29,7 @@ use crate::filter::platform_requirement_filter::IgnoreListPlatformRequirementFil use crate::filter::platform_requirement_filter::PlatformRequirementFilterFactory; use crate::filter::platform_requirement_filter::PlatformRequirementFilterInterface; use crate::io::IOInterface; -use crate::package::BasePackage; +use crate::package::BasePackageHandle; #[derive(Debug)] pub struct Solver { @@ -40,7 +40,7 @@ pub struct Solver { pub(crate) watch_graph: RuleWatchGraph, pub(crate) decisions: Decisions, - pub(crate) fixed_map: IndexMap<i64, Box<dyn BasePackage>>, + pub(crate) fixed_map: IndexMap<i64, BasePackageHandle>, pub(crate) propagate_index: i64, /// Pairs of `(literals, level)` — PHP indexes into these with the BRANCH_* constants. @@ -176,7 +176,7 @@ impl Solver { fn setup_fixed_map(&mut self, request: &Request) { self.fixed_map = IndexMap::new(); for (_, package) in request.get_fixed_packages() { - self.fixed_map.insert(package.get_id(), package.clone_box()); + self.fixed_map.insert(package.get_id(), package.clone()); } } @@ -296,13 +296,22 @@ impl Solver { return Err(anyhow::anyhow!("solver problems")); } - // TODO(phase-b): LockTransaction expects IndexMap<_, Box<dyn PackageInterface>> - // and borrows Pool/Decisions. The present/fixed maps from Request are keyed - // by BasePackage; converting requires reworking Request. + // LockTransaction stores PackageInterfaceHandle maps; widen the request's BasePackageHandle + // maps into them. + let present_map = request + .get_present_map(false) + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(); + let unlockable_map = request + .get_fixed_packages_map() + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(); Ok(LockTransaction::new( &*self.pool.borrow(), - todo!("convert request.get_present_map(false) to PackageInterface map"), - todo!("convert request.get_fixed_packages_map() to PackageInterface map"), + present_map, + unlockable_map, &self.decisions, )) } diff --git a/crates/shirabe/src/dependency_resolver/transaction.rs b/crates/shirabe/src/dependency_resolver/transaction.rs index a704c67..8cf5d5b 100644 --- a/crates/shirabe/src/dependency_resolver/transaction.rs +++ b/crates/shirabe/src/dependency_resolver/transaction.rs @@ -1,11 +1,8 @@ //! ref: composer/src/Composer/DependencyResolver/Transaction.php -use std::any::Any; - use indexmap::IndexMap; use shirabe_php_shim::{ - PhpMixed, array_filter, array_intersect, array_keys, array_pop, array_unshift, spl_object_hash, - strcmp, uasort, + PhpMixed, array_filter, array_intersect, array_keys, array_pop, array_unshift, strcmp, uasort, }; use crate::dependency_resolver::operation::InstallOperation; @@ -14,9 +11,8 @@ use crate::dependency_resolver::operation::MarkAliasUninstalledOperation; use crate::dependency_resolver::operation::OperationInterface; use crate::dependency_resolver::operation::UninstallOperation; use crate::dependency_resolver::operation::UpdateOperation; -use crate::package::AliasPackage; use crate::package::Link; -use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::PlatformRepository; /// @internal @@ -27,14 +23,14 @@ pub struct Transaction { /// Packages present at the beginning of the transaction /// @var PackageInterface[] - pub(crate) present_packages: Vec<Box<dyn PackageInterface>>, + pub(crate) present_packages: Vec<PackageInterfaceHandle>, /// Package set resulting from this transaction /// @var array<string, PackageInterface> - pub(crate) result_package_map: IndexMap<String, Box<dyn PackageInterface>>, + pub(crate) result_package_map: IndexMap<String, PackageInterfaceHandle>, /// @var array<string, PackageInterface[]> - pub(crate) result_packages_by_name: IndexMap<String, Vec<Box<dyn PackageInterface>>>, + pub(crate) result_packages_by_name: IndexMap<String, Vec<PackageInterfaceHandle>>, } impl Default for Transaction { @@ -52,8 +48,8 @@ impl Transaction { /// @param PackageInterface[] $presentPackages /// @param PackageInterface[] $resultPackages pub fn new( - present_packages: Vec<Box<dyn PackageInterface>>, - result_packages: Vec<Box<dyn PackageInterface>>, + present_packages: Vec<PackageInterfaceHandle>, + result_packages: Vec<PackageInterfaceHandle>, ) -> Self { let mut this = Self { operations: vec![], @@ -72,23 +68,23 @@ impl Transaction { } /// @param PackageInterface[] $resultPackages - fn set_result_package_maps(&mut self, result_packages: Vec<Box<dyn PackageInterface>>) { + fn set_result_package_maps(&mut self, result_packages: Vec<PackageInterfaceHandle>) { // PHP: static function (PackageInterface $a, PackageInterface $b): int { ... }; // TODO(phase-b): bridge the closure to uasort's argument type - let _package_sort = |a: &dyn PackageInterface, b: &dyn PackageInterface| -> i64 { + let _package_sort = |a: &PackageInterfaceHandle, b: &PackageInterfaceHandle| -> i64 { // sort alias packages by the same name behind their non alias version if a.get_name() == b.get_name() { - let a_is_alias = a.as_any().downcast_ref::<AliasPackage>().is_some(); - let b_is_alias = b.as_any().downcast_ref::<AliasPackage>().is_some(); + let a_is_alias = a.as_alias().is_some(); + let b_is_alias = b.as_alias().is_some(); if a_is_alias != b_is_alias { return if a_is_alias { -1 } else { 1 }; } // if names are the same, compare version, e.g. to sort aliases reliably, actual order does not matter - return strcmp(b.get_version(), a.get_version()); + return strcmp(&b.get_version(), &a.get_version()); } - strcmp(b.get_name(), a.get_name()) + strcmp(&b.get_name(), &a.get_name()) }; self.result_package_map = IndexMap::new(); @@ -97,10 +93,10 @@ impl Transaction { self.result_packages_by_name .entry(name) .or_insert_with(Vec::new) - .push(package.clone_package_box()); + .push(package.clone()); } self.result_package_map - .insert(spl_object_hash(package.as_ref()), package); + .insert(package.ptr_id().to_string(), package); } // TODO(phase-b): uasort signature mismatch — needs to operate on the IndexMap with a PackageInterface comparator @@ -121,24 +117,23 @@ impl Transaction { pub(crate) fn calculate_operations(&mut self) -> Vec<Box<dyn OperationInterface>> { let mut operations: Vec<Box<dyn OperationInterface>> = vec![]; - let mut present_package_map: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); - let mut remove_map: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); - let mut present_alias_map: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); - let mut remove_alias_map: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); + let mut present_package_map: IndexMap<String, PackageInterfaceHandle> = IndexMap::new(); + let mut remove_map: IndexMap<String, PackageInterfaceHandle> = IndexMap::new(); + let mut present_alias_map: IndexMap<String, PackageInterfaceHandle> = IndexMap::new(); + let mut remove_alias_map: IndexMap<String, PackageInterfaceHandle> = IndexMap::new(); for package in &self.present_packages { - if package.as_any().downcast_ref::<AliasPackage>().is_some() { + if package.as_alias().is_some() { let key = format!("{}::{}", package.get_name(), package.get_version()); - present_alias_map.insert(key.clone(), package.clone_package_box()); - remove_alias_map.insert(key, package.clone_package_box()); + present_alias_map.insert(key.clone(), package.clone()); + remove_alias_map.insert(key, package.clone()); } else { - present_package_map - .insert(package.get_name().to_string(), package.clone_package_box()); - remove_map.insert(package.get_name().to_string(), package.clone_package_box()); + present_package_map.insert(package.get_name().to_string(), package.clone()); + remove_map.insert(package.get_name().to_string(), package.clone()); } } // PHP: $stack = $this->getRootPackages(); - let mut stack: Vec<Box<dyn PackageInterface>> = + let mut stack: Vec<PackageInterfaceHandle> = self.get_root_packages().into_values().collect(); let mut visited: IndexMap<String, bool> = IndexMap::new(); @@ -147,16 +142,16 @@ impl Transaction { while !stack.is_empty() { let package = array_pop(&mut stack).unwrap(); - if processed.contains_key(&spl_object_hash(package.as_ref())) { + if processed.contains_key(&package.ptr_id().to_string()) { continue; } - if !visited.contains_key(&spl_object_hash(package.as_ref())) { - visited.insert(spl_object_hash(package.as_ref()), true); + if !visited.contains_key(&package.ptr_id().to_string()) { + visited.insert(package.ptr_id().to_string(), true); - stack.push(package.clone_package_box()); - if let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { - stack.push(alias.get_alias_of().clone_package_box()); + stack.push(package.clone()); + if let Some(alias) = package.as_alias() { + stack.push(alias.get_alias_of().into()); } else { for link in package.get_requires().values() { let possible_requires = self.get_providers_in_result(link); @@ -166,10 +161,10 @@ impl Transaction { } } } - } else if !processed.contains_key(&spl_object_hash(package.as_ref())) { - processed.insert(spl_object_hash(package.as_ref()), true); + } else if !processed.contains_key(&package.ptr_id().to_string()) { + processed.insert(package.ptr_id().to_string(), true); - if package.as_any().downcast_ref::<AliasPackage>().is_some() { + if package.as_alias().is_some() { let alias_key = format!("{}::{}", package.get_name(), package.get_version()); if present_alias_map.contains_key(&alias_key) { remove_alias_map.shift_remove(&alias_key); @@ -179,18 +174,22 @@ impl Transaction { "package as AliasPackage by value" )))); } - } else if let Some(source) = present_package_map.get(package.get_name()) { + } else if let Some(source) = present_package_map.get(&package.get_name()).cloned() { // do we need to update? // TODO different for lock? - let present = present_package_map.get(package.get_name()).unwrap(); - // TODO(phase-b): downcast to CompletePackageInterface trait object - let package_is_complete = false; - let present_is_complete = false; + let present = present_package_map.get(&package.get_name()).unwrap(); + // PHP: $package instanceof CompletePackageInterface + // && $presentPackageMap[$package->getName()] instanceof CompletePackageInterface + // && ($package->isAbandoned() !== $presentPackageMap[...]->isAbandoned() + // || $package->getReplacementPackage() !== $presentPackageMap[...]->getReplacementPackage()) let abandoned_or_replacement_changed = - package_is_complete && present_is_complete && { - // PHP: $package->isAbandoned() !== $presentPackageMap[$package->getName()]->isAbandoned() - // || $package->getReplacementPackage() !== $presentPackageMap[$package->getName()]->getReplacementPackage() - todo!("compare abandoned/replacement across CompletePackageInterface") + match (package.as_complete(), present.as_complete()) { + (Some(package), Some(present)) => { + package.is_abandoned() != present.is_abandoned() + || package.get_replacement_package() + != present.get_replacement_package() + } + _ => false, }; if package.get_version() != present.get_version() || package.get_dist_reference() != present.get_dist_reference() @@ -198,14 +197,14 @@ impl Transaction { || abandoned_or_replacement_changed { operations.push(Box::new(UpdateOperation::new( - source.clone_package_box(), - package.clone_package_box(), + source.clone(), + package.clone(), ))); } - remove_map.shift_remove(package.get_name()); + remove_map.shift_remove(&package.get_name()); } else { - operations.push(Box::new(InstallOperation::new(package.clone_package_box()))); - remove_map.shift_remove(package.get_name()); + operations.push(Box::new(InstallOperation::new(package.clone()))); + remove_map.shift_remove(&package.get_name()); } } } @@ -244,11 +243,11 @@ impl Transaction { /// If there are packages with a cycle on the top level the package with the lowest name gets picked /// /// @return array<string, PackageInterface> - pub(crate) fn get_root_packages(&self) -> IndexMap<String, Box<dyn PackageInterface>> { - let mut roots: IndexMap<String, Box<dyn PackageInterface>> = self + pub(crate) fn get_root_packages(&self) -> IndexMap<String, PackageInterfaceHandle> { + let mut roots: IndexMap<String, PackageInterfaceHandle> = self .result_package_map .iter() - .map(|(k, v)| (k.clone(), v.clone_package_box())) + .map(|(k, v)| (k.clone(), v.clone())) .collect(); for (package_hash, package) in &self.result_package_map { @@ -261,8 +260,8 @@ impl Transaction { for require in possible_requires { // PHP: if ($require !== $package) — strict reference inequality - if spl_object_hash(require.as_ref()) != spl_object_hash(package.as_ref()) { - roots.shift_remove(&spl_object_hash(require.as_ref())); + if require.ptr_id().to_string() != package.ptr_id().to_string() { + roots.shift_remove(&require.ptr_id().to_string()); } } } @@ -272,12 +271,12 @@ impl Transaction { } /// @return PackageInterface[] - pub(crate) fn get_providers_in_result(&self, link: &Link) -> Vec<Box<dyn PackageInterface>> { + pub(crate) fn get_providers_in_result(&self, link: &Link) -> Vec<PackageInterfaceHandle> { let Some(packages) = self.result_packages_by_name.get(link.get_target()) else { return vec![]; }; - packages.iter().map(|p| p.clone_package_box()).collect() + packages.iter().cloned().collect() } /// Workaround: if your packages depend on plugins, we must be sure @@ -308,12 +307,12 @@ impl Transaction { for idx in (0..operations.len()).rev() { let op = &operations[idx]; - let package: Box<dyn PackageInterface> = if let Some(install_op) = + let package: PackageInterfaceHandle = if let Some(install_op) = op.as_ref().as_any().downcast_ref::<InstallOperation>() { - install_op.get_package().clone_package_box() + install_op.get_package().clone() } else if let Some(update_op) = op.as_ref().as_any().downcast_ref::<UpdateOperation>() { - update_op.get_target_package().clone_package_box() + update_op.get_target_package().clone() } else { continue; }; diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs index f4575ce..ae86fcf 100644 --- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs +++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs @@ -31,7 +31,6 @@ use crate::installer::InstallerEvent; use crate::installer::PackageEvent; use crate::io::ConsoleIO; use crate::io::IOInterface; -use crate::package::PackageInterface; use crate::plugin::CommandEvent; use crate::plugin::PreCommandRunEvent; use crate::repository::RepositoryInterface; @@ -1260,7 +1259,9 @@ impl EventDispatcher { let generator = generator.borrow(); let mut hash_input = packages .iter() - .map(|p: &Box<dyn PackageInterface>| format!("{}/{}", p.get_name(), p.get_version())) + .map(|p: &crate::package::PackageInterfaceHandle| { + format!("{}/{}", p.get_name(), p.get_version()) + }) .collect::<Vec<_>>() .join(","); // TODO(plugin): polymorphic isDevMode propagation for ScriptEvent / PackageEvent / InstallerEvent @@ -1278,11 +1279,19 @@ impl EventDispatcher { // Composer is &Composer here so we cannot take a mut borrow. Defer until shared ownership. let _ = &generator; let _ = packages; - let package_map: Vec<(Box<dyn PackageInterface>, Option<String>)> = + let package_map: Vec<(crate::package::PackageInterfaceHandle, Option<String>)> = todo!("build_package_map requires &mut InstallationManager"); // TODO(phase-b): parse_autoloads also expects the filtered dev packages list // (PhpMixed in this port). - let map = generator.parse_autoloads(package_map, package, shirabe_php_shim::PhpMixed::Null); + let map = generator.parse_autoloads( + package_map, + package + .as_rc() + .borrow() + .as_root_package_interface() + .unwrap(), + shirabe_php_shim::PhpMixed::Null, + ); if self.loader.is_some() { self.loader.as_mut().unwrap().unregister(); diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index 35afd5d..1370080 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -47,6 +47,7 @@ use crate::json::JsonFile; use crate::json::JsonValidationException; use crate::package::Locker; use crate::package::RootPackageInterface; +use crate::package::RootPackageInterfaceHandle; use crate::package::archiver::ArchiveManager; use crate::package::archiver::PharArchiver; use crate::package::archiver::ZipArchiver; @@ -690,7 +691,7 @@ impl Factory { io, &mut rm.borrow_mut(), &vendor_dir, - composer.get_package(), + composer.get_package().clone(), Some(&process), ); composer.set_repository_manager(rm.clone()); @@ -871,7 +872,7 @@ impl Factory { io: &dyn IOInterface, rm: &mut RepositoryManager, vendor_dir: &str, - root_package: &dyn RootPackageInterface, + root_package: RootPackageInterfaceHandle, process: Option<&std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>, ) { let fs = process @@ -886,7 +887,7 @@ impl Factory { ) .expect("installed.json path is always valid"), true, - Some(RootPackageInterface::clone_box(root_package)), + Some(root_package), fs, ) .expect("InstalledFilesystemRepository::new should not fail"), @@ -1237,8 +1238,7 @@ impl Factory { im: &mut InstallationManager, ) -> anyhow::Result<()> { for package in repo.get_packages() { - if !im.is_package_installed(repo, package.as_ref())? { - // TODO(phase-b): mutable access on repo trait object + if !im.is_package_installed(repo, &package)? { let _ = package; } } diff --git a/crates/shirabe/src/installer.rs b/crates/shirabe/src/installer.rs index 8b68a78..c3ef76f 100644 --- a/crates/shirabe/src/installer.rs +++ b/crates/shirabe/src/installer.rs @@ -76,8 +76,10 @@ use crate::package::Link; use crate::package::Locker; use crate::package::Package; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::RootAliasPackage; use crate::package::RootPackageInterface; +use crate::package::RootPackageInterfaceHandle; use crate::package::base_package::{self, BasePackage}; use crate::package::dumper::ArrayDumper; use crate::package::loader::ArrayLoader; @@ -104,9 +106,9 @@ use shirabe_semver::constraint::SimpleConstraint; pub struct Installer { pub(crate) io: Box<dyn IOInterface>, pub(crate) config: std::rc::Rc<std::cell::RefCell<Config>>, - pub(crate) package: Box<dyn RootPackageInterface>, + pub(crate) package: RootPackageInterfaceHandle, // TODO can we get rid of the below and just use the package itself? - pub(crate) fixed_root_package: Box<dyn RootPackageInterface>, + pub(crate) fixed_root_package: RootPackageInterfaceHandle, pub(crate) download_manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, pub(crate) repository_manager: std::rc::Rc<std::cell::RefCell<RepositoryManager>>, pub(crate) locker: std::rc::Rc<std::cell::RefCell<Locker>>, @@ -161,7 +163,7 @@ impl Installer { pub fn new( io: Box<dyn IOInterface>, config: std::rc::Rc<std::cell::RefCell<Config>>, - package: Box<dyn RootPackageInterface>, + package: RootPackageInterfaceHandle, download_manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, repository_manager: std::rc::Rc<std::cell::RefCell<RepositoryManager>>, locker: std::rc::Rc<std::cell::RefCell<Locker>>, @@ -176,8 +178,8 @@ impl Installer { Self { io, config, - package: package.clone_box(), - fixed_root_package: package.clone_box(), + package: package.clone(), + fixed_root_package: package.clone(), download_manager, repository_manager, locker, @@ -353,11 +355,13 @@ impl Installer { let installed_repo = InstalledRepository::new(vec![ locked_repository_box, Box::new(self.create_platform_repo(false)), - Box::new(RootPackageRepository::new(self.package.clone_box())), + Box::new(RootPackageRepository::new(self.package.clone())), ]); if is_fresh_install { self.suggested_packages_reporter - .add_suggestions_from_package(&*self.package); + .add_suggestions_from_package( + self.package.as_rc().borrow().as_package_interface(), + ); } self.suggested_packages_reporter .output_minimalistic(Some(&installed_repo), None); @@ -408,7 +412,11 @@ impl Installer { self.autoload_generator.borrow_mut().dump( &*self.config.borrow(), self.repository_manager.borrow().get_local_repository(), - &*self.package, + self.package + .as_rc() + .borrow() + .as_root_package_interface() + .unwrap(), &mut *self.installation_manager.borrow_mut(), "composer", self.optimize_autoloader, @@ -423,9 +431,11 @@ impl Installer { let repository_manager = self.repository_manager.clone(); let repository_manager = repository_manager.borrow(); for package in repository_manager.get_local_repository().get_packages() { + // TODO(phase-c): InstallationManager APIs still take &dyn PackageInterface; bridge + // the handle until they migrate to handles. self.installation_manager .borrow_mut() - .ensure_binaries_presence(&*package); + .ensure_binaries_presence(package.as_rc().borrow().as_package_interface()); } } @@ -443,8 +453,8 @@ impl Installer { let repository_manager = self.repository_manager.clone(); let repository_manager = repository_manager.borrow(); for package in repository_manager.get_local_repository().get_packages() { - if let Some(cp) = package.as_complete_package_interface() { - if package.as_alias_package().is_none() && !cp.get_funding().is_empty() { + if let Some(cp) = package.as_complete() { + if package.as_alias().is_none() && !cp.get_funding().is_empty() { funding_count += 1; } } @@ -622,8 +632,13 @@ impl Installer { repository_set.add_repository(lr.clone_box())?; } + let fixed_root_package = self.fixed_root_package.clone(); let mut request = self.create_request( - &*self.fixed_root_package, + fixed_root_package + .as_rc() + .borrow() + .as_root_package_interface() + .unwrap(), &platform_repo, locked_repository.as_ref(), ); @@ -819,7 +834,9 @@ impl Installer { // collect suggestions if let Some(io) = operation.as_install_operation() { self.suggested_packages_reporter - .add_suggestions_from_package(&*io.get_package()); + .add_suggestions_from_package( + io.get_package().as_rc().borrow().as_package_interface(), + ); } // output op if lock file is enabled, but alias op only in debug verbosity @@ -836,13 +853,9 @@ impl Installer { if self.io.is_very_verbose() && strpos(&operation.get_operation_type(), "Alias").is_none() { - let operation_pkg: Box<dyn PackageInterface> = - if let Some(uo) = operation.as_update_operation() { - uo.get_target_package().clone_package_box() - } else { - operation.get_package().clone_package_box() - }; - if let Some(repo) = operation_pkg.get_repository() { + // TODO(phase-c): package->repository back-reference not yet on handles + let repository: Option<&dyn RepositoryInterface> = None; + if let Some(repo) = repository { source_repo = format!(" from {}", repo.get_repo_name()); } } @@ -877,8 +890,8 @@ impl Installer { platform_reqs, platform_dev_reqs, aliases_php_mixed, - self.package.get_minimum_stability(), - self.package.get_stability_flags().clone(), + &self.package.get_minimum_stability(), + self.package.get_stability_flags(), self.prefer_stable || self.package.get_prefer_stable(), self.prefer_lowest, platform_overrides, @@ -915,16 +928,24 @@ impl Installer { let dumper = ArrayDumper::new(); for pkg in lock_transaction.get_new_lock_packages(false, false) { let loaded = loader.load( - dumper.dump(&*pkg), + dumper.dump(pkg.as_rc().borrow().as_package_interface()), Some("Composer\\Package\\CompletePackage".to_string()), )?; - result_repo.add_package(loaded.clone_package_box())?; + result_repo.add_package(loaded)?; } let mut repository_set = self.create_repository_set(true, platform_repo, aliases, None); repository_set.add_repository(Box::new(result_repo))?; - let mut request = self.create_request(&*self.fixed_root_package, platform_repo, None); + let mut request = self.create_request( + self.fixed_root_package + .as_rc() + .borrow() + .as_root_package_interface() + .unwrap(), + platform_repo, + None, + ); self.require_packages_for_update(&mut request, locked_repository, false)?; let pool = repository_set.create_pool_with_all_packages()?; @@ -1005,8 +1026,13 @@ impl Installer { repository_set.add_repository(locked_repository.clone_box())?; // creating requirements request + let fixed_root_package = self.fixed_root_package.clone(); let mut request = self.create_request( - &*self.fixed_root_package, + fixed_root_package + .as_rc() + .borrow() + .as_root_package_interface() + .unwrap(), &platform_repo, Some(&locked_repository), ); @@ -1019,10 +1045,15 @@ impl Installer { ); } - let missing_requirement_info = self - .locker - .borrow_mut() - .get_missing_requirement_info(&*self.package, self.dev_mode)?; + let package_for_missing = self.package.clone(); + let missing_requirement_info = self.locker.borrow_mut().get_missing_requirement_info( + package_for_missing + .as_rc() + .borrow() + .as_root_package_interface() + .unwrap(), + self.dev_mode, + )?; if !missing_requirement_info.is_empty() { self.io.write_error(&missing_requirement_info.join("\n")); @@ -1038,7 +1069,7 @@ impl Installer { } for package in RepositoryInterface::get_packages(&locked_repository) { - request.fix_locked_package(package); + request.fix_locked_package(package.clone()); } let mut root_requires = self.package.get_requires(); @@ -1316,16 +1347,17 @@ impl Installer { root_requires.insert(req, constraint); } - // TODO(phase-b): self.package is Box<dyn RootPackageInterface>; cannot clone a trait - // object without Clone. PHP shares the reference. Skipping fixed_root_package assignment. - // self.fixed_root_package = clone(&self.package); + // TODO(phase-c): PHP does `$this->fixedRootPackage = clone($this->package)` (a deep, + // fresh-identity copy) and then strips its requires below. A handle clone only shares the + // same Rc, so a real deep clone of the package is needed to avoid mutating self.package. + // self.fixed_root_package = deep_clone(&self.package); self.fixed_root_package.set_requires(vec![]); self.fixed_root_package.set_dev_requires(vec![]); stability_flags.insert( - self.package.get_name().to_string(), + self.package.get_name(), base_package::STABILITIES - [VersionParser::parse_stability(self.package.get_version()).as_str()], + [VersionParser::parse_stability(&self.package.get_version()).as_str()], ); // TODO(phase-b): convert root_aliases (Vec<IndexMap<String, String>>) into Vec<RootAliasInput> @@ -1405,18 +1437,18 @@ impl Installer { if for_update && self.minimal_update && locked_repo.is_some() { let mut versions: IndexMap<String, String> = IndexMap::new(); for pkg in CanonicalPackagesTrait::get_packages(locked_repo.unwrap()) { - if pkg.as_alias_package().is_some() + if pkg.as_alias().is_some() || (self.update_allow_list.is_some() && self .update_allow_list .as_ref() .unwrap() .iter() - .any(|s| s == pkg.get_name())) + .any(|s| s == &pkg.get_name())) { continue; } - versions.insert(pkg.get_name().to_string(), pkg.get_version().to_string()); + versions.insert(pkg.get_name(), pkg.get_version()); } preferred_versions = Some(versions); } @@ -1439,7 +1471,8 @@ impl Installer { let _ = locked_repository; let mut request = Request::new(None); - // TODO(phase-b): request.fix_package wants Box<dyn BasePackage>; root_package is &dyn RootPackageInterface + // TODO(phase-c): request.fix_package wants a BasePackageHandle; root_package is only a + // borrowed &dyn RootPackageInterface here and cannot be lifted back into a handle. let _ = root_package; // request.fix_package(root_package); if let Some(_alias) = root_package.as_any().downcast_ref::<RootAliasPackage>() { @@ -1457,17 +1490,13 @@ impl Installer { let provided = root_package.get_provides(); for package in fixed_packages { // skip platform packages that are provided by the root package - let pkg_repo_is_platform = match package.get_repository() { - Some(r) => std::ptr::eq( - r.as_any() as *const _ as *const u8, - platform_repo.as_any() as *const _ as *const u8, - ), - None => false, - }; + // TODO(phase-c): the handle does not expose get_repository (a `RefCell`-borrowed + // back-reference); detecting the platform repo needs repository back-refs on handles. + let pkg_repo_is_platform = false; if !pkg_repo_is_platform - || !provided.contains_key(package.get_name()) + || !provided.contains_key(&package.get_name()) || !provided - .get(package.get_name()) + .get(&package.get_name()) .unwrap() .get_constraint() .matches( @@ -1479,9 +1508,9 @@ impl Installer { .into(), ) { - // TODO(phase-b): fix_package needs owned Box<dyn BasePackage> + // TODO(phase-c): wire up once the platform-repo detection above is implemented. let _ = &package; - // request.fix_package(&*package); + // request.fix_package(package.clone()); } } @@ -1515,15 +1544,15 @@ impl Installer { for locked_package in CanonicalPackagesTrait::get_packages(locked_repository.unwrap()) { // exclude alias packages here as for root aliases, both alias and aliased are // present in the lock repo and we only want to require the aliased version - if locked_package.as_alias_package().is_none() - && !excluded_packages.contains_key(locked_package.get_name()) + if locked_package.as_alias().is_none() + && !excluded_packages.contains_key(&locked_package.get_name()) { request.require_name( - locked_package.get_name(), + &locked_package.get_name(), Some( SimpleConstraint::new( "==".to_string(), - locked_package.get_version().to_string(), + locked_package.get_version(), None, ) .into(), @@ -1576,21 +1605,21 @@ impl Installer { /// /// This is to prevent any accidental modification of the existing repos on disk fn mock_local_repositories(&self, rm: &mut RepositoryManager) { - let mut packages: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); + let mut packages: IndexMap<String, PackageInterfaceHandle> = IndexMap::new(); for package in rm.get_local_repository().get_packages() { - packages.insert(package.to_string(), package.clone_box()); + packages.insert(package.to_string(), package.clone().into()); } let keys: Vec<String> = packages.keys().cloned().collect(); for key in keys { - let package_clone = packages.get(&key).unwrap().clone_package_box(); - if let Some(alias_pkg) = package_clone.as_alias_package() { + let package_clone = packages.get(&key).unwrap().clone(); + if let Some(alias_pkg) = package_clone.as_alias() { let alias_key = alias_pkg.get_alias_of().to_string(); // TODO(phase-b): get_class on dyn PackageInterface; skipped because PhpMixed shim only let _class_name = "Composer\\Package\\AliasPackage".to_string(); // PHP: $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion()); - // TODO(phase-b): AliasPackage::new expects Box<dyn BasePackage>; have Box<dyn PackageInterface> - let _aliased = packages.get(&alias_key).unwrap().clone_package_box(); - let new_alias_package: Box<dyn PackageInterface> = todo!(); + // TODO(phase-c): re-create the alias package over the mocked aliased handle. + let _aliased = packages.get(&alias_key).unwrap().clone(); + let new_alias_package: PackageInterfaceHandle = todo!(); packages.insert(key, new_alias_package); } } @@ -1653,7 +1682,7 @@ impl Installer { Self::new( io, composer.get_config(), - composer.get_package().clone_box(), + composer.get_package().clone(), composer.get_download_manager(), composer.get_repository_manager(), composer.get_locker(), diff --git a/crates/shirabe/src/installer/installation_manager.rs b/crates/shirabe/src/installer/installation_manager.rs index 7a05324..3e455b4 100644 --- a/crates/shirabe/src/installer/installation_manager.rs +++ b/crates/shirabe/src/installer/installation_manager.rs @@ -23,8 +23,8 @@ use crate::installer::PackageEvents; use crate::installer::PluginInstaller; use crate::io::ConsoleIO; use crate::io::IOInterface; -use crate::package::AliasPackage; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::InstalledRepositoryInterface; use crate::util::Platform; use crate::util::r#loop::Loop; @@ -37,7 +37,7 @@ pub struct InstallationManager { /// @var array<string, InstallerInterface> cache: IndexMap<String, Box<dyn InstallerInterface>>, /// @var array<string, array<PackageInterface>> - notifiable_packages: IndexMap<String, Vec<Box<dyn PackageInterface>>>, + notifiable_packages: IndexMap<String, Vec<PackageInterfaceHandle>>, loop_: std::rc::Rc<std::cell::RefCell<Loop>>, io: Box<dyn IOInterface>, event_dispatcher: Option<std::rc::Rc<std::cell::RefCell<EventDispatcher>>>, @@ -133,18 +133,19 @@ impl InstallationManager { pub fn is_package_installed( &mut self, repo: &dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> Result<bool> { - // TODO(phase-b): $package instanceof AliasPackage downcast - let package_as_alias: Option<&AliasPackage> = None; - if let Some(alias) = package_as_alias { - return Ok(repo.has_package(package) - && self.is_package_installed(repo, alias.get_alias_of())?); + if let Some(alias) = package.as_alias() { + let alias_of: PackageInterfaceHandle = alias.get_alias_of().into(); + return Ok( + repo.has_package(package.as_rc().borrow().as_package_interface()) + && self.is_package_installed(repo, &alias_of)?, + ); } Ok(self - .get_installer(package.get_type())? - .is_installed(repo, package)) + .get_installer(&package.get_type())? + .is_installed(repo, package.as_rc().borrow().as_package_interface())) } /// Install binary for the given package. @@ -311,9 +312,10 @@ impl InstallationManager { let update_op: Option<&UpdateOperation> = None; if op_type == "update" { // @var UpdateOperation $operation - if let Some(u) = update_op { - package = u.get_target_package(); - initial_package = Some(u.get_initial_package()); + if let Some(_u) = update_op { + // TODO(phase-c): bridge UpdateOperation handles (target/initial) into the + // &dyn PackageInterface that the installer APIs still expect. + continue; } else { continue; } @@ -451,9 +453,10 @@ impl InstallationManager { let initial_package: Option<&dyn PackageInterface>; let update_op: Option<&UpdateOperation> = None; if op_type == "update" { - if let Some(u) = update_op { - package = u.get_target_package(); - initial_package = Some(u.get_initial_package()); + if let Some(_u) = update_op { + // TODO(phase-c): bridge UpdateOperation handles (target/initial) into the + // &dyn PackageInterface that the installer APIs still expect. + continue; } else { continue; } @@ -537,10 +540,11 @@ impl InstallationManager { repo: &mut dyn InstalledRepositoryInterface, operation: &InstallOperation, ) -> Option<PhpMixed> { - let package = operation.get_package(); - let installer = self.get_installer(package.get_type()).ok()?; - let promise = installer.install(repo, package).await.ok()?; - self.mark_for_notification(package); + let package = operation.get_package().clone(); + let package_type = package.get_type(); + let installer = self.get_installer(&package_type).ok()?; + let promise = installer.install(repo, &package).await.ok()?; + self.mark_for_notification(&package); promise } @@ -553,27 +557,27 @@ impl InstallationManager { repo: &mut dyn InstalledRepositoryInterface, operation: &UpdateOperation, ) -> Option<PhpMixed> { - let initial = operation.get_initial_package(); - let target = operation.get_target_package(); + let initial = operation.get_initial_package().clone(); + let target = operation.get_target_package().clone(); let initial_type = initial.get_type(); let target_type = target.get_type(); let promise = if initial_type == target_type { - let installer = self.get_installer(initial_type).ok()?; - let promise = installer.update(repo, initial, target).await.ok()?; - self.mark_for_notification(target); + let installer = self.get_installer(&initial_type).ok()?; + let promise = installer.update(repo, &initial, &target).await.ok()?; + self.mark_for_notification(&target); promise } else { // PHP: uninstall initial, then install target via the target-type installer. let _ = self - .get_installer(initial_type) + .get_installer(&initial_type) .ok()? - .uninstall(repo, initial) + .uninstall(repo, &initial) .await .ok()?; - let installer = self.get_installer(target_type).ok()?; - installer.install(repo, target).await.ok()? + let installer = self.get_installer(&target_type).ok()?; + installer.install(repo, &target).await.ok()? }; promise @@ -587,10 +591,11 @@ impl InstallationManager { repo: &mut dyn InstalledRepositoryInterface, operation: &UninstallOperation, ) -> Option<PhpMixed> { - let package = operation.get_package(); - let installer = self.get_installer(package.get_type()).ok()?; + let package = operation.get_package().clone(); + let package_type = package.get_type(); + let installer = self.get_installer(&package_type).ok()?; - installer.uninstall(repo, package).await.ok()? + installer.uninstall(repo, &package).await.ok()? } /// Executes markAliasInstalled operation. @@ -602,7 +607,10 @@ impl InstallationManager { let package = operation.get_package(); if !repo.has_package(package) { - repo.add_package(package.clone_package_box()); + // TODO(phase-c): MarkAliasInstalledOperation::get_package() yields a borrowed + // &AliasPackage; add_package now wants a shared PackageInterfaceHandle. + let package_handle: PackageInterfaceHandle = todo!(); + repo.add_package(package_handle); } } @@ -638,17 +646,11 @@ impl InstallationManager { // non-batch API, deprecated if str_contains(repo_url, "%package%") { for package in packages { - let url = str_replace("%package%", package.get_pretty_name(), repo_url); + let url = str_replace("%package%", &package.get_pretty_name(), repo_url); let mut params: IndexMap<String, String> = IndexMap::new(); - params.insert( - "version".to_string(), - package.get_pretty_version().to_string(), - ); - params.insert( - "version_normalized".to_string(), - package.get_version().to_string(), - ); + params.insert("version".to_string(), package.get_pretty_version()); + params.insert("version_normalized".to_string(), package.get_version()); let mut opts: IndexMap<String, PhpMixed> = IndexMap::new(); opts.insert("retry-auth-failure".to_string(), PhpMixed::Bool(false)); let mut http: IndexMap<String, PhpMixed> = IndexMap::new(); @@ -693,15 +695,15 @@ impl InstallationManager { let mut package_notification: IndexMap<String, PhpMixed> = IndexMap::new(); package_notification.insert( "name".to_string(), - PhpMixed::String(package.get_pretty_name().to_string()), + PhpMixed::String(package.get_pretty_name()), ); package_notification.insert( "version".to_string(), - PhpMixed::String(package.get_version().to_string()), + PhpMixed::String(package.get_version()), ); if strpos(repo_url, "packagist.org/").is_some() { if let Some(metadata) = - FileDownloader::download_metadata().get(package.get_name()) + FileDownloader::download_metadata().get(&package.get_name()) { package_notification.insert("downloaded".to_string(), metadata.clone()); } else { @@ -764,12 +766,12 @@ impl InstallationManager { self.reset(); } - fn mark_for_notification(&mut self, package: &dyn PackageInterface) { + fn mark_for_notification(&mut self, package: &PackageInterfaceHandle) { if let Some(notification_url) = package.get_notification_url() { self.notifiable_packages - .entry(notification_url.to_string()) + .entry(notification_url) .or_insert_with(Vec::new) - .push(package.clone_package_box()); + .push(package.clone()); } } diff --git a/crates/shirabe/src/installer/installer_interface.rs b/crates/shirabe/src/installer/installer_interface.rs index bb510b8..8ab7efd 100644 --- a/crates/shirabe/src/installer/installer_interface.rs +++ b/crates/shirabe/src/installer/installer_interface.rs @@ -1,6 +1,7 @@ //! ref: composer/src/Composer/Installer/InstallerInterface.php use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::InstalledRepositoryInterface; use shirabe_php_shim::PhpMixed; @@ -30,20 +31,20 @@ pub trait InstallerInterface: std::fmt::Debug { async fn install( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>>; async fn update( &mut self, repo: &mut dyn InstalledRepositoryInterface, - initial: &dyn PackageInterface, - target: &dyn PackageInterface, + initial: &PackageInterfaceHandle, + target: &PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>>; async fn uninstall( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>>; async fn cleanup( diff --git a/crates/shirabe/src/installer/library_installer.rs b/crates/shirabe/src/installer/library_installer.rs index 29cb455..332bdea 100644 --- a/crates/shirabe/src/installer/library_installer.rs +++ b/crates/shirabe/src/installer/library_installer.rs @@ -16,6 +16,7 @@ use crate::installer::BinaryPresenceInterface; use crate::installer::InstallerInterface; use crate::io::IOInterface; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::InstalledRepositoryInterface; use crate::util::Filesystem; use crate::util::Platform; @@ -305,24 +306,36 @@ impl InstallerInterface for LibraryInstaller { async fn install( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { // TODO(phase-b): initialize_vendor_dir requires &mut self // self.initialize_vendor_dir(); - let download_path = self.get_install_path(package).unwrap(); + let download_path = self + .get_install_path(package.as_rc().borrow().as_package_interface()) + .unwrap(); // remove the binaries if it appears the package files are missing - if !Filesystem::is_readable(&download_path) && repo.has_package(package) { - self.binary_installer.remove_binaries(package); + if !Filesystem::is_readable(&download_path) + && repo.has_package(package.as_rc().borrow().as_package_interface()) + { + self.binary_installer + .remove_binaries(package.as_rc().borrow().as_package_interface()); } - let _ = self.install_code(package).await?; + let _ = self + .install_code(package.as_rc().borrow().as_package_interface()) + .await?; - let install_path = self.get_install_path(package).unwrap(); - self.binary_installer - .install_binaries(package, &install_path, true); - if !repo.has_package(package) { - repo.add_package(package.clone_package_box()); + let install_path = self + .get_install_path(package.as_rc().borrow().as_package_interface()) + .unwrap(); + self.binary_installer.install_binaries( + package.as_rc().borrow().as_package_interface(), + &install_path, + true, + ); + if !repo.has_package(package.as_rc().borrow().as_package_interface()) { + repo.add_package(package.clone()); } Ok(None) @@ -331,10 +344,10 @@ impl InstallerInterface for LibraryInstaller { async fn update( &mut self, repo: &mut dyn InstalledRepositoryInterface, - initial: &dyn PackageInterface, - target: &dyn PackageInterface, + initial: &PackageInterfaceHandle, + target: &PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { - if !repo.has_package(initial) { + if !repo.has_package(initial.as_rc().borrow().as_package_interface()) { return Err(InvalidArgumentException { message: format!("Package is not installed: {}", initial), code: 0, @@ -345,15 +358,26 @@ impl InstallerInterface for LibraryInstaller { // TODO(phase-b): initialize_vendor_dir requires &mut self // self.initialize_vendor_dir(); - self.binary_installer.remove_binaries(initial); - let _ = self.update_code(initial, target).await?; - - let install_path = self.get_install_path(target).unwrap(); self.binary_installer - .install_binaries(target, &install_path, true); - repo.remove_package(initial); - if !repo.has_package(target) { - repo.add_package(target.clone_package_box()); + .remove_binaries(initial.as_rc().borrow().as_package_interface()); + let _ = self + .update_code( + initial.as_rc().borrow().as_package_interface(), + target.as_rc().borrow().as_package_interface(), + ) + .await?; + + let install_path = self + .get_install_path(target.as_rc().borrow().as_package_interface()) + .unwrap(); + self.binary_installer.install_binaries( + target.as_rc().borrow().as_package_interface(), + &install_path, + true, + ); + repo.remove_package(initial.as_rc().borrow().as_package_interface()); + if !repo.has_package(target.as_rc().borrow().as_package_interface()) { + repo.add_package(target.clone()); } Ok(None) @@ -362,9 +386,9 @@ impl InstallerInterface for LibraryInstaller { async fn uninstall( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { - if !repo.has_package(package) { + if !repo.has_package(package.as_rc().borrow().as_package_interface()) { return Err(InvalidArgumentException { message: format!("Package is not installed: {}", package), code: 0, @@ -372,13 +396,17 @@ impl InstallerInterface for LibraryInstaller { .into()); } - let _ = self.remove_code(package).await?; + let _ = self + .remove_code(package.as_rc().borrow().as_package_interface()) + .await?; - let download_path = self.get_package_base_path(package); - self.binary_installer.remove_binaries(package); - repo.remove_package(package); + let download_path = + self.get_package_base_path(package.as_rc().borrow().as_package_interface()); + self.binary_installer + .remove_binaries(package.as_rc().borrow().as_package_interface()); + repo.remove_package(package.as_rc().borrow().as_package_interface()); - if strpos(package.get_name(), "/").map_or(false, |pos| pos != 0) { + if strpos(&package.get_name(), "/").map_or(false, |pos| pos != 0) { let package_vendor_dir = dirname(&download_path); if is_dir(&package_vendor_dir) && self.filesystem.borrow().is_dir_empty(&package_vendor_dir) diff --git a/crates/shirabe/src/installer/metapackage_installer.rs b/crates/shirabe/src/installer/metapackage_installer.rs index 2ea9685..cda7bf0 100644 --- a/crates/shirabe/src/installer/metapackage_installer.rs +++ b/crates/shirabe/src/installer/metapackage_installer.rs @@ -7,6 +7,7 @@ use crate::installer::InstallerInterface; use crate::io::IOInterface; use crate::io::io_interface; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::InstalledRepositoryInterface; use anyhow::Result; use shirabe_php_shim::{InvalidArgumentException, PhpMixed}; @@ -65,15 +66,18 @@ impl InstallerInterface for MetapackageInstaller { async fn install( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { self.io.write_error3( - &format!(" - {}", InstallOperation::format(package, false)), + &format!( + " - {}", + InstallOperation::format(package.as_rc().borrow().as_package_interface(), false) + ), true, io_interface::NORMAL, ); - repo.add_package(package.clone_package_box()); + repo.add_package(package.clone()); Ok(None) } @@ -81,10 +85,10 @@ impl InstallerInterface for MetapackageInstaller { async fn update( &mut self, repo: &mut dyn InstalledRepositoryInterface, - initial: &dyn PackageInterface, - target: &dyn PackageInterface, + initial: &PackageInterfaceHandle, + target: &PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { - if !repo.has_package(initial) { + if !repo.has_package(initial.as_rc().borrow().as_package_interface()) { return Err(InvalidArgumentException { message: format!("Package is not installed: {}", initial), code: 0, @@ -93,13 +97,20 @@ impl InstallerInterface for MetapackageInstaller { } self.io.write_error3( - &format!(" - {}", UpdateOperation::format(initial, target, false)), + &format!( + " - {}", + UpdateOperation::format( + initial.as_rc().borrow().as_package_interface(), + target.as_rc().borrow().as_package_interface(), + false + ) + ), true, io_interface::NORMAL, ); - repo.remove_package(initial); - repo.add_package(target.clone_package_box()); + repo.remove_package(initial.as_rc().borrow().as_package_interface()); + repo.add_package(target.clone()); Ok(None) } @@ -107,9 +118,9 @@ impl InstallerInterface for MetapackageInstaller { async fn uninstall( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { - if !repo.has_package(package) { + if !repo.has_package(package.as_rc().borrow().as_package_interface()) { return Err(InvalidArgumentException { message: format!("Package is not installed: {}", package), code: 0, @@ -118,12 +129,15 @@ impl InstallerInterface for MetapackageInstaller { } self.io.write_error3( - &format!(" - {}", UninstallOperation::format(package, false)), + &format!( + " - {}", + UninstallOperation::format(package.as_rc().borrow().as_package_interface(), false) + ), true, io_interface::NORMAL, ); - repo.remove_package(package); + repo.remove_package(package.as_rc().borrow().as_package_interface()); Ok(None) } diff --git a/crates/shirabe/src/installer/noop_installer.rs b/crates/shirabe/src/installer/noop_installer.rs index 8297165..12e7e95 100644 --- a/crates/shirabe/src/installer/noop_installer.rs +++ b/crates/shirabe/src/installer/noop_installer.rs @@ -2,6 +2,7 @@ use crate::installer::InstallerInterface; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::InstalledRepositoryInterface; use shirabe_php_shim::{InvalidArgumentException, PhpMixed}; @@ -51,10 +52,10 @@ impl InstallerInterface for NoopInstaller { async fn install( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { - if !repo.has_package(package) { - repo.add_package(package.clone_package_box()); + if !repo.has_package(package.as_rc().borrow().as_package_interface()) { + repo.add_package(package.clone()); } Ok(None) @@ -63,10 +64,10 @@ impl InstallerInterface for NoopInstaller { async fn update( &mut self, repo: &mut dyn InstalledRepositoryInterface, - initial: &dyn PackageInterface, - target: &dyn PackageInterface, + initial: &PackageInterfaceHandle, + target: &PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { - if !repo.has_package(initial) { + if !repo.has_package(initial.as_rc().borrow().as_package_interface()) { return Err(InvalidArgumentException { message: format!("Package is not installed: {}", initial), code: 0, @@ -74,9 +75,9 @@ impl InstallerInterface for NoopInstaller { .into()); } - repo.remove_package(initial); - if !repo.has_package(target) { - repo.add_package(target.clone_package_box()); + repo.remove_package(initial.as_rc().borrow().as_package_interface()); + if !repo.has_package(target.as_rc().borrow().as_package_interface()) { + repo.add_package(target.clone()); } Ok(None) @@ -85,16 +86,16 @@ impl InstallerInterface for NoopInstaller { async fn uninstall( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { - if !repo.has_package(package) { + if !repo.has_package(package.as_rc().borrow().as_package_interface()) { return Err(InvalidArgumentException { message: format!("Package is not installed: {}", package), code: 0, } .into()); } - repo.remove_package(package); + repo.remove_package(package.as_rc().borrow().as_package_interface()); Ok(None) } diff --git a/crates/shirabe/src/installer/plugin_installer.rs b/crates/shirabe/src/installer/plugin_installer.rs index a540677..2135bb3 100644 --- a/crates/shirabe/src/installer/plugin_installer.rs +++ b/crates/shirabe/src/installer/plugin_installer.rs @@ -6,6 +6,7 @@ use crate::installer::InstallerInterface; use crate::installer::LibraryInstaller; use crate::io::IOInterface; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::plugin::PluginManager; use crate::repository::InstalledRepositoryInterface; use crate::util::Filesystem; @@ -45,7 +46,7 @@ impl PluginInstaller { &mut self, e: anyhow::Error, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> Result<()> { self.inner.io.write_error(&format!( "Plugin initialization failed ({}), uninstalling plugin", @@ -123,7 +124,7 @@ impl InstallerInterface for PluginInstaller { async fn install( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { self.inner.install(repo, package).await?; @@ -137,8 +138,8 @@ impl InstallerInterface for PluginInstaller { async fn update( &mut self, repo: &mut dyn InstalledRepositoryInterface, - initial: &dyn PackageInterface, - target: &dyn PackageInterface, + initial: &PackageInterfaceHandle, + target: &PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { self.inner.update(repo, initial, target).await?; @@ -153,12 +154,12 @@ impl InstallerInterface for PluginInstaller { async fn uninstall( &mut self, repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> Result<Option<PhpMixed>> { // TODO(plugin): uninstall package from plugin manager self.get_plugin_manager() .borrow_mut() - .uninstall_package(package); + .uninstall_package(package.as_rc().borrow().as_package_interface()); self.inner.uninstall(repo, package).await } diff --git a/crates/shirabe/src/installer/project_installer.rs b/crates/shirabe/src/installer/project_installer.rs index 8960854..129810b 100644 --- a/crates/shirabe/src/installer/project_installer.rs +++ b/crates/shirabe/src/installer/project_installer.rs @@ -3,6 +3,7 @@ use crate::downloader::DownloadManager; use crate::installer::InstallerInterface; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::InstalledRepositoryInterface; use crate::util::Filesystem; use shirabe_php_shim::{InvalidArgumentException, PhpMixed}; @@ -95,19 +96,22 @@ impl InstallerInterface for ProjectInstaller { async fn install( &mut self, _repo: &mut dyn InstalledRepositoryInterface, - package: &dyn PackageInterface, + package: &PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { self.download_manager .borrow() - .install(package, &self.install_path) + .install( + package.as_rc().borrow().as_package_interface(), + &self.install_path, + ) .await } async fn update( &mut self, _repo: &mut dyn InstalledRepositoryInterface, - _initial: &dyn PackageInterface, - _target: &dyn PackageInterface, + _initial: &PackageInterfaceHandle, + _target: &PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { Err(InvalidArgumentException { message: "not supported".to_string(), @@ -119,7 +123,7 @@ impl InstallerInterface for ProjectInstaller { async fn uninstall( &mut self, _repo: &mut dyn InstalledRepositoryInterface, - _package: &dyn PackageInterface, + _package: &PackageInterfaceHandle, ) -> anyhow::Result<Option<PhpMixed>> { Err(InvalidArgumentException { message: "not supported".to_string(), diff --git a/crates/shirabe/src/package/alias_package.rs b/crates/shirabe/src/package/alias_package.rs index e9b5dbf..5558337 100644 --- a/crates/shirabe/src/package/alias_package.rs +++ b/crates/shirabe/src/package/alias_package.rs @@ -8,6 +8,7 @@ use shirabe_semver::constraint::SimpleConstraint; use crate::package::BasePackage; use crate::package::Link; +use crate::package::PackageHandle; use crate::package::PackageInterface; use crate::package::version::VersionParser; use crate::repository::RepositoryInterface; @@ -34,7 +35,7 @@ pub struct AliasPackage { pub(crate) has_self_version_requires: bool, /// @var BasePackage - pub(crate) alias_of: Box<dyn BasePackage>, + pub(crate) alias_of: PackageHandle, /// @var Link[] pub(crate) requires: IndexMap<String, Link>, /// @var Link[] @@ -53,8 +54,8 @@ impl AliasPackage { /// @param BasePackage $aliasOf The package this package is an alias of /// @param string $version The version the alias must report /// @param string $prettyVersion The alias's non-normalized version - pub fn new(alias_of: Box<dyn BasePackage>, version: String, pretty_version: String) -> Self { - let alias_name = alias_of.get_name().to_string(); + pub fn new(alias_of: PackageHandle, version: String, pretty_version: String) -> Self { + let alias_name = alias_of.get_name(); let stability = VersionParser::parse_stability(&version).to_string(); let dev = stability == "dev"; @@ -130,12 +131,8 @@ impl AliasPackage { this } - pub fn get_alias_of(&self) -> &dyn BasePackage { - self.alias_of.as_ref() - } - - pub fn get_alias_of_mut(&mut self) -> &mut dyn BasePackage { - &mut *self.alias_of + pub fn get_alias_of(&self) -> PackageHandle { + self.alias_of.clone() } /// Stores whether this is an alias created by an aliasing in the requirements of the root package or not @@ -244,11 +241,13 @@ impl PackageInterface for AliasPackage { } fn get_name(&self) -> &str { - self.alias_of.get_name() + // PHP delegates to aliasOf; the local name mirrors aliasOf->getName(), + // so it is returned here to avoid borrowing across the shared handle. + &self.name } fn get_pretty_name(&self) -> &str { - self.alias_of.get_pretty_name() + &self.pretty_name } fn get_names(&self, provides: bool) -> Vec<String> { @@ -306,11 +305,14 @@ impl PackageInterface for AliasPackage { } fn get_type(&self) -> &str { - self.alias_of.get_type() + // Delegates to the shared `aliasOf` handle, whose getters yield owned + // `String`s; a borrow cannot escape the `RefCell`. Use the handle API + // (`AliasPackageHandle::get_alias_of().get_type()`) instead. + todo!("AliasPackage::get_type cannot return &str across the aliasOf handle") } fn get_target_dir(&self) -> Option<&str> { - self.alias_of.get_target_dir() + todo!("AliasPackage::get_target_dir cannot return &str across the aliasOf handle") } fn get_extra(&self) -> IndexMap<String, PhpMixed> { @@ -322,15 +324,15 @@ impl PackageInterface for AliasPackage { } fn get_installation_source(&self) -> Option<&str> { - self.alias_of.get_installation_source() + todo!("AliasPackage::get_installation_source cannot return &str across the aliasOf handle") } fn get_source_type(&self) -> Option<&str> { - self.alias_of.get_source_type() + todo!("AliasPackage::get_source_type cannot return &str across the aliasOf handle") } fn get_source_url(&self) -> Option<&str> { - self.alias_of.get_source_url() + todo!("AliasPackage::get_source_url cannot return &str across the aliasOf handle") } fn get_source_urls(&self) -> Vec<String> { @@ -338,7 +340,7 @@ impl PackageInterface for AliasPackage { } fn get_source_reference(&self) -> Option<&str> { - self.alias_of.get_source_reference() + todo!("AliasPackage::get_source_reference cannot return &str across the aliasOf handle") } fn set_source_reference(&mut self, reference: Option<String>) { @@ -354,11 +356,11 @@ impl PackageInterface for AliasPackage { } fn get_dist_type(&self) -> Option<&str> { - self.alias_of.get_dist_type() + todo!("AliasPackage::get_dist_type cannot return &str across the aliasOf handle") } fn get_dist_url(&self) -> Option<&str> { - self.alias_of.get_dist_url() + todo!("AliasPackage::get_dist_url cannot return &str across the aliasOf handle") } fn get_dist_urls(&self) -> Vec<String> { @@ -366,7 +368,7 @@ impl PackageInterface for AliasPackage { } fn get_dist_reference(&self) -> Option<&str> { - self.alias_of.get_dist_reference() + todo!("AliasPackage::get_dist_reference cannot return &str across the aliasOf handle") } fn set_dist_reference(&mut self, reference: Option<String>) { @@ -374,7 +376,7 @@ impl PackageInterface for AliasPackage { } fn get_dist_sha1_checksum(&self) -> Option<&str> { - self.alias_of.get_dist_sha1_checksum() + todo!("AliasPackage::get_dist_sha1_checksum cannot return &str across the aliasOf handle") } fn set_transport_options(&mut self, options: IndexMap<String, PhpMixed>) { @@ -422,7 +424,7 @@ impl PackageInterface for AliasPackage { } fn get_notification_url(&self) -> Option<&str> { - self.alias_of.get_notification_url() + todo!("AliasPackage::get_notification_url cannot return &str across the aliasOf handle") } fn is_default_branch(&self) -> bool { @@ -442,9 +444,8 @@ impl PackageInterface for AliasPackage { } fn get_full_pretty_version(&self, truncate: bool, display_mode: i64) -> String { - // TODO(phase-b): BasePackage.get_full_pretty_version returns Result; bridge here - BasePackage::get_full_pretty_version(self.alias_of.as_ref(), truncate, display_mode) - .unwrap_or_default() + self.alias_of + .get_full_pretty_version(truncate, display_mode) } fn get_unique_name(&self) -> String { @@ -460,7 +461,7 @@ impl PackageInterface for AliasPackage { } fn get_repository(&self) -> Option<&dyn RepositoryInterface> { - self.alias_of.get_repository() + todo!("AliasPackage::get_repository cannot return a borrow across the aliasOf handle") } } @@ -500,8 +501,4 @@ impl BasePackage for AliasPackage { fn take_repository(&mut self) -> Option<Box<dyn RepositoryInterface>> { todo!() } - - fn clone_box(&self) -> Box<dyn BasePackage> { - todo!() - } } diff --git a/crates/shirabe/src/package/base_package.rs b/crates/shirabe/src/package/base_package.rs index 75bd22f..64a5919 100644 --- a/crates/shirabe/src/package/base_package.rs +++ b/crates/shirabe/src/package/base_package.rs @@ -89,8 +89,6 @@ pub trait BasePackage: PackageInterface + std::fmt::Display { // TODO(phase-b): wire up a back-reference to the containing repository when needed. } - fn clone_box(&self) -> Box<dyn BasePackage>; - // as_alias_package / as_complete_package_interface inherited from PackageInterface. fn as_alias_package_mut(&mut self) -> Option<&mut crate::package::AliasPackage> { diff --git a/crates/shirabe/src/package/complete_alias_package.rs b/crates/shirabe/src/package/complete_alias_package.rs index 530bd03..6a23bee 100644 --- a/crates/shirabe/src/package/complete_alias_package.rs +++ b/crates/shirabe/src/package/complete_alias_package.rs @@ -1,136 +1,156 @@ //! ref: composer/src/Composer/Package/CompleteAliasPackage.php +use indexmap::IndexMap; +use shirabe_php_shim::PhpMixed; + use crate::package::AliasPackage; -use crate::package::CompletePackage; +use crate::package::CompletePackageHandle; use crate::package::CompletePackageInterface; +use crate::package::PackageHandle; +use crate::package::handle::delegate_package_interface_to_inner; #[derive(Debug)] pub struct CompleteAliasPackage { inner: AliasPackage, // overrides AliasPackage::alias_of with the more specific CompletePackage type - pub(crate) alias_of: CompletePackage, + pub(crate) alias_of: CompletePackageHandle, } impl CompleteAliasPackage { - pub fn new(alias_of: CompletePackage, version: String, pretty_version: String) -> Self { - // TODO(phase-b): alias_of is a PHP class (shared semantics); cloning is wrong. - // Use a dummy BasePackage placeholder until the field is migrated to Rc<CompletePackage>. + pub fn new(alias_of: CompletePackageHandle, version: String, pretty_version: String) -> Self { let inner = AliasPackage::new( - todo!("share CompletePackage via Rc"), + PackageHandle::from(alias_of.clone()), version, pretty_version, ); Self { inner, alias_of } } - pub fn get_alias_of(&self) -> &CompletePackage { - &self.alias_of + pub fn get_alias_of(&self) -> CompletePackageHandle { + self.alias_of.clone() + } + + pub fn set_root_package_alias(&mut self, value: bool) { + self.inner.set_root_package_alias(value); + } + + pub fn is_root_package_alias(&self) -> bool { + self.inner.is_root_package_alias() + } + + pub fn has_self_version_requires(&self) -> bool { + self.inner.has_self_version_requires() + } +} + +delegate_package_interface_to_inner!(CompleteAliasPackage, inner); + +impl std::fmt::Display for CompleteAliasPackage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.inner, f) } +} - pub fn get_scripts(&self) -> indexmap::IndexMap<String, Vec<String>> { +impl CompletePackageInterface for CompleteAliasPackage { + fn get_scripts(&self) -> IndexMap<String, Vec<String>> { self.alias_of.get_scripts() } - pub fn set_scripts(&mut self, scripts: indexmap::IndexMap<String, Vec<String>>) { + fn set_scripts(&mut self, scripts: IndexMap<String, Vec<String>>) { self.alias_of.set_scripts(scripts); } - pub fn get_repositories(&self) -> Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>> { + fn get_repositories(&self) -> Vec<IndexMap<String, PhpMixed>> { self.alias_of.get_repositories() } - pub fn set_repositories( - &mut self, - repositories: Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>, - ) { + fn set_repositories(&mut self, repositories: Vec<IndexMap<String, PhpMixed>>) { self.alias_of.set_repositories(repositories); } - pub fn get_license(&self) -> Vec<String> { + fn get_license(&self) -> Vec<String> { self.alias_of.get_license() } - pub fn set_license(&mut self, license: Vec<String>) { + fn set_license(&mut self, license: Vec<String>) { self.alias_of.set_license(license); } - pub fn get_keywords(&self) -> Vec<String> { + fn get_keywords(&self) -> Vec<String> { self.alias_of.get_keywords() } - pub fn set_keywords(&mut self, keywords: Vec<String>) { + fn set_keywords(&mut self, keywords: Vec<String>) { self.alias_of.set_keywords(keywords); } - pub fn get_description(&self) -> Option<&str> { - self.alias_of.get_description() + fn get_description(&self) -> Option<&str> { + todo!("CompleteAliasPackage::get_description cannot return &str across the aliasOf handle") } - pub fn set_description(&mut self, description: Option<String>) { - self.alias_of - .set_description(description.unwrap_or_default()); + fn set_description(&mut self, description: String) { + self.alias_of.set_description(description); } - pub fn get_homepage(&self) -> Option<&str> { - self.alias_of.get_homepage() + fn get_homepage(&self) -> Option<&str> { + todo!("CompleteAliasPackage::get_homepage cannot return &str across the aliasOf handle") } - pub fn set_homepage(&mut self, homepage: Option<String>) { - self.alias_of.set_homepage(homepage.unwrap_or_default()); + fn set_homepage(&mut self, homepage: String) { + self.alias_of.set_homepage(homepage); } - pub fn get_authors(&self) -> Vec<indexmap::IndexMap<String, String>> { + fn get_authors(&self) -> Vec<IndexMap<String, String>> { self.alias_of.get_authors() } - pub fn set_authors(&mut self, authors: Vec<indexmap::IndexMap<String, String>>) { + fn set_authors(&mut self, authors: Vec<IndexMap<String, String>>) { self.alias_of.set_authors(authors); } - pub fn get_support(&self) -> indexmap::IndexMap<String, String> { + fn get_support(&self) -> IndexMap<String, String> { self.alias_of.get_support() } - pub fn set_support(&mut self, support: indexmap::IndexMap<String, String>) { + fn set_support(&mut self, support: IndexMap<String, String>) { self.alias_of.set_support(support); } - pub fn get_funding(&self) -> Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>> { + fn get_funding(&self) -> Vec<IndexMap<String, PhpMixed>> { self.alias_of.get_funding() } - pub fn set_funding( - &mut self, - funding: Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>, - ) { + fn set_funding(&mut self, funding: Vec<IndexMap<String, PhpMixed>>) { self.alias_of.set_funding(funding); } - pub fn is_abandoned(&self) -> bool { + fn is_abandoned(&self) -> bool { self.alias_of.is_abandoned() } - pub fn get_replacement_package(&self) -> Option<&str> { - self.alias_of.get_replacement_package() + fn get_replacement_package(&self) -> Option<&str> { + todo!( + "CompleteAliasPackage::get_replacement_package cannot return &str across the aliasOf handle" + ) } - pub fn set_abandoned(&mut self, abandoned: shirabe_php_shim::PhpMixed) { + fn set_abandoned(&mut self, abandoned: PhpMixed) { self.alias_of.set_abandoned(abandoned); } - pub fn get_archive_name(&self) -> Option<&str> { - self.alias_of.get_archive_name() + fn get_archive_name(&self) -> Option<&str> { + todo!("CompleteAliasPackage::get_archive_name cannot return &str across the aliasOf handle") } - pub fn set_archive_name(&mut self, name: Option<String>) { - self.alias_of.set_archive_name(name.unwrap_or_default()); + fn set_archive_name(&mut self, name: String) { + self.alias_of.set_archive_name(name); } - pub fn get_archive_excludes(&self) -> Vec<String> { + fn get_archive_excludes(&self) -> Vec<String> { self.alias_of.get_archive_excludes() } - pub fn set_archive_excludes(&mut self, excludes: Vec<String>) { + fn set_archive_excludes(&mut self, excludes: Vec<String>) { self.alias_of.set_archive_excludes(excludes); } } diff --git a/crates/shirabe/src/package/handle.rs b/crates/shirabe/src/package/handle.rs new file mode 100644 index 0000000..e1f0570 --- /dev/null +++ b/crates/shirabe/src/package/handle.rs @@ -0,0 +1,1440 @@ +//! Shared handles over the package types. +//! +//! No weak handles are provided: an alias package never aliases another alias package, so the +//! `alias_of` references are acyclic. + +use std::cell::RefCell; +use std::rc::Rc; + +use crate::package::{ + AliasPackage, CompleteAliasPackage, CompletePackage, CompletePackageInterface, Package, + PackageInterface, RootAliasPackage, RootPackage, RootPackageInterface, +}; + +/// Any package type. +#[derive(Debug)] +pub enum AnyPackage { + Package(Package), + CompletePackage(CompletePackage), + RootPackage(RootPackage), + AliasPackage(AliasPackage), + CompleteAliasPackage(CompleteAliasPackage), + RootAliasPackage(RootAliasPackage), +} + +impl AnyPackage { + pub fn as_package_interface(&self) -> &dyn PackageInterface { + match self { + Self::Package(p) => p, + Self::CompletePackage(p) => p, + Self::RootPackage(p) => p, + Self::AliasPackage(p) => p, + Self::CompleteAliasPackage(p) => p, + Self::RootAliasPackage(p) => p, + } + } + + pub fn as_package_interface_mut(&mut self) -> &mut dyn PackageInterface { + match self { + Self::Package(p) => p, + Self::CompletePackage(p) => p, + Self::RootPackage(p) => p, + Self::AliasPackage(p) => p, + Self::CompleteAliasPackage(p) => p, + Self::RootAliasPackage(p) => p, + } + } + + pub fn as_complete_package_interface(&self) -> Option<&dyn CompletePackageInterface> { + match self { + Self::CompletePackage(p) => Some(p), + Self::RootPackage(p) => Some(p), + Self::CompleteAliasPackage(p) => Some(p), + Self::RootAliasPackage(p) => Some(p), + _ => None, + } + } + + pub fn as_complete_package_interface_mut( + &mut self, + ) -> Option<&mut dyn CompletePackageInterface> { + match self { + Self::CompletePackage(p) => Some(p), + Self::RootPackage(p) => Some(p), + Self::CompleteAliasPackage(p) => Some(p), + Self::RootAliasPackage(p) => Some(p), + _ => None, + } + } + + pub fn as_root_package_interface(&self) -> Option<&dyn RootPackageInterface> { + match self { + Self::RootPackage(p) => Some(p), + Self::RootAliasPackage(p) => Some(p), + _ => None, + } + } + + pub fn as_root_package_interface_mut(&mut self) -> Option<&mut dyn RootPackageInterface> { + match self { + Self::RootPackage(p) => Some(p), + Self::RootAliasPackage(p) => Some(p), + _ => None, + } + } + + /// PHP `$p instanceof AliasPackage`. + pub fn is_alias(&self) -> bool { + matches!( + self, + Self::AliasPackage(_) | Self::CompleteAliasPackage(_) | Self::RootAliasPackage(_) + ) + } + + /// PHP `$p instanceof CompletePackageInterface`. + pub fn is_complete(&self) -> bool { + matches!( + self, + Self::CompletePackage(_) + | Self::RootPackage(_) + | Self::CompleteAliasPackage(_) + | Self::RootAliasPackage(_) + ) + } + + /// PHP `$p instanceof RootPackageInterface`. + pub fn is_root(&self) -> bool { + matches!(self, Self::RootPackage(_) | Self::RootAliasPackage(_)) + } + + /// A real (non-alias) package: `Package` / `CompletePackage` / `RootPackage`. + pub fn is_real(&self) -> bool { + matches!( + self, + Self::Package(_) | Self::CompletePackage(_) | Self::RootPackage(_) + ) + } + + /// A real `CompletePackage` or `RootPackage`. + pub fn is_complete_real(&self) -> bool { + matches!(self, Self::CompletePackage(_) | Self::RootPackage(_)) + } + + /// A real `RootPackage`. + pub fn is_root_real(&self) -> bool { + matches!(self, Self::RootPackage(_)) + } + + /// A `CompleteAliasPackage` or `RootAliasPackage`. + pub fn is_complete_alias(&self) -> bool { + matches!( + self, + Self::CompleteAliasPackage(_) | Self::RootAliasPackage(_) + ) + } + + /// A `RootAliasPackage`. + pub fn is_root_alias(&self) -> bool { + matches!(self, Self::RootAliasPackage(_)) + } +} + +macro_rules! delegate_package_interface_to_inner { + ($Type:ty, $field:ident) => { + impl crate::package::PackageInterface for $Type { + fn as_any(&self) -> &dyn std::any::Any { + self + } + fn get_name(&self) -> &str { + self.$field.get_name() + } + fn get_pretty_name(&self) -> &str { + self.$field.get_pretty_name() + } + fn get_names(&self, provides: bool) -> Vec<String> { + self.$field.get_names(provides) + } + fn set_id(&mut self, id: i64) { + self.$field.set_id(id); + } + fn get_id(&self) -> i64 { + self.$field.get_id() + } + fn is_dev(&self) -> bool { + self.$field.is_dev() + } + fn get_type(&self) -> &str { + self.$field.get_type() + } + fn get_target_dir(&self) -> Option<&str> { + self.$field.get_target_dir() + } + fn get_extra(&self) -> indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> { + self.$field.get_extra() + } + fn set_installation_source(&mut self, r#type: Option<String>) { + self.$field.set_installation_source(r#type); + } + fn get_installation_source(&self) -> Option<&str> { + self.$field.get_installation_source() + } + fn get_source_type(&self) -> Option<&str> { + self.$field.get_source_type() + } + fn get_source_url(&self) -> Option<&str> { + self.$field.get_source_url() + } + fn get_source_urls(&self) -> Vec<String> { + self.$field.get_source_urls() + } + fn get_source_reference(&self) -> Option<&str> { + self.$field.get_source_reference() + } + fn get_source_mirrors( + &self, + ) -> Option<Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>> { + self.$field.get_source_mirrors() + } + fn set_source_mirrors( + &mut self, + mirrors: Option<Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>>, + ) { + self.$field.set_source_mirrors(mirrors); + } + fn get_dist_type(&self) -> Option<&str> { + self.$field.get_dist_type() + } + fn get_dist_url(&self) -> Option<&str> { + self.$field.get_dist_url() + } + fn get_dist_urls(&self) -> Vec<String> { + self.$field.get_dist_urls() + } + fn get_dist_reference(&self) -> Option<&str> { + self.$field.get_dist_reference() + } + fn get_dist_sha1_checksum(&self) -> Option<&str> { + self.$field.get_dist_sha1_checksum() + } + fn get_dist_mirrors( + &self, + ) -> Option<Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>> { + self.$field.get_dist_mirrors() + } + fn set_dist_mirrors( + &mut self, + mirrors: Option<Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>>, + ) { + self.$field.set_dist_mirrors(mirrors); + } + fn get_version(&self) -> &str { + self.$field.get_version() + } + fn get_pretty_version(&self) -> &str { + self.$field.get_pretty_version() + } + fn get_full_pretty_version(&self, truncate: bool, display_mode: i64) -> String { + self.$field.get_full_pretty_version(truncate, display_mode) + } + fn get_release_date(&self) -> Option<chrono::DateTime<chrono::Utc>> { + self.$field.get_release_date() + } + fn get_stability(&self) -> &str { + self.$field.get_stability() + } + fn get_requires(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.$field.get_requires() + } + fn get_conflicts(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.$field.get_conflicts() + } + fn get_provides(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.$field.get_provides() + } + fn get_replaces(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.$field.get_replaces() + } + fn get_dev_requires(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.$field.get_dev_requires() + } + fn get_suggests(&self) -> indexmap::IndexMap<String, String> { + self.$field.get_suggests() + } + fn get_autoload(&self) -> indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> { + self.$field.get_autoload() + } + fn get_dev_autoload(&self) -> indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> { + self.$field.get_dev_autoload() + } + fn get_include_paths(&self) -> Vec<String> { + self.$field.get_include_paths() + } + fn get_php_ext( + &self, + ) -> Option<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>> { + self.$field.get_php_ext() + } + fn set_repository( + &mut self, + repository: Box<dyn crate::repository::RepositoryInterface>, + ) -> anyhow::Result<()> { + self.$field.set_repository(repository) + } + fn get_repository(&self) -> Option<&dyn crate::repository::RepositoryInterface> { + self.$field.get_repository() + } + fn get_binaries(&self) -> Vec<String> { + self.$field.get_binaries() + } + fn get_unique_name(&self) -> String { + self.$field.get_unique_name() + } + fn get_notification_url(&self) -> Option<&str> { + self.$field.get_notification_url() + } + fn get_pretty_string(&self) -> String { + self.$field.get_pretty_string() + } + fn is_default_branch(&self) -> bool { + self.$field.is_default_branch() + } + fn get_transport_options( + &self, + ) -> indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> { + self.$field.get_transport_options() + } + fn set_transport_options( + &mut self, + options: indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>, + ) { + self.$field.set_transport_options(options); + } + fn set_source_reference(&mut self, reference: Option<String>) { + self.$field.set_source_reference(reference); + } + fn set_dist_url(&mut self, url: Option<String>) { + self.$field.set_dist_url(url); + } + fn set_dist_type(&mut self, r#type: Option<String>) { + self.$field.set_dist_type(r#type); + } + fn set_dist_reference(&mut self, reference: Option<String>) { + self.$field.set_dist_reference(reference); + } + fn set_source_dist_references(&mut self, reference: &str) { + self.$field.set_source_dist_references(reference); + } + } + }; +} +pub(crate) use delegate_package_interface_to_inner; + +macro_rules! impl_package_interface_handle { + ($Handle:ty) => { + impl $Handle { + pub fn get_name(&self) -> String { + self.0 + .borrow() + .as_package_interface() + .get_name() + .to_string() + } + + pub fn get_pretty_name(&self) -> String { + self.0 + .borrow() + .as_package_interface() + .get_pretty_name() + .to_string() + } + + pub fn get_names(&self, provides: bool) -> Vec<String> { + self.0.borrow().as_package_interface().get_names(provides) + } + + pub fn set_id(&self, id: i64) { + self.0.borrow_mut().as_package_interface_mut().set_id(id); + } + + pub fn get_id(&self) -> i64 { + self.0.borrow().as_package_interface().get_id() + } + + /// PHP `BasePackage::$id` accessor; alias of [`get_id`](Self::get_id). + pub fn id(&self) -> i64 { + self.0.borrow().as_package_interface().get_id() + } + + pub fn is_dev(&self) -> bool { + self.0.borrow().as_package_interface().is_dev() + } + + pub fn get_type(&self) -> String { + self.0 + .borrow() + .as_package_interface() + .get_type() + .to_string() + } + + pub fn get_target_dir(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_target_dir() + .map(str::to_string) + } + + pub fn get_extra(&self) -> indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> { + self.0.borrow().as_package_interface().get_extra() + } + + pub fn set_installation_source(&self, r#type: Option<String>) { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_installation_source(r#type); + } + + pub fn get_installation_source(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_installation_source() + .map(str::to_string) + } + + pub fn get_source_type(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_source_type() + .map(str::to_string) + } + + pub fn get_source_url(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_source_url() + .map(str::to_string) + } + + pub fn get_source_urls(&self) -> Vec<String> { + self.0.borrow().as_package_interface().get_source_urls() + } + + pub fn get_source_reference(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_source_reference() + .map(str::to_string) + } + + pub fn get_source_mirrors( + &self, + ) -> Option<Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>> { + self.0.borrow().as_package_interface().get_source_mirrors() + } + + pub fn set_source_mirrors( + &self, + mirrors: Option<Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>>, + ) { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_source_mirrors(mirrors); + } + + pub fn get_dist_type(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_dist_type() + .map(str::to_string) + } + + pub fn get_dist_url(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_dist_url() + .map(str::to_string) + } + + pub fn get_dist_urls(&self) -> Vec<String> { + self.0.borrow().as_package_interface().get_dist_urls() + } + + pub fn get_dist_reference(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_dist_reference() + .map(str::to_string) + } + + pub fn get_dist_sha1_checksum(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_dist_sha1_checksum() + .map(str::to_string) + } + + pub fn get_dist_mirrors( + &self, + ) -> Option<Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>> { + self.0.borrow().as_package_interface().get_dist_mirrors() + } + + pub fn set_dist_mirrors( + &self, + mirrors: Option<Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>>, + ) { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_dist_mirrors(mirrors); + } + + pub fn get_version(&self) -> String { + self.0 + .borrow() + .as_package_interface() + .get_version() + .to_string() + } + + pub fn get_pretty_version(&self) -> String { + self.0 + .borrow() + .as_package_interface() + .get_pretty_version() + .to_string() + } + + pub fn get_full_pretty_version(&self, truncate: bool, display_mode: i64) -> String { + self.0 + .borrow() + .as_package_interface() + .get_full_pretty_version(truncate, display_mode) + } + + pub fn get_release_date(&self) -> Option<chrono::DateTime<chrono::Utc>> { + self.0.borrow().as_package_interface().get_release_date() + } + + pub fn get_stability(&self) -> String { + self.0 + .borrow() + .as_package_interface() + .get_stability() + .to_string() + } + + pub fn get_requires(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.0.borrow().as_package_interface().get_requires() + } + + pub fn get_conflicts(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.0.borrow().as_package_interface().get_conflicts() + } + + pub fn get_provides(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.0.borrow().as_package_interface().get_provides() + } + + pub fn get_replaces(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.0.borrow().as_package_interface().get_replaces() + } + + pub fn get_dev_requires(&self) -> indexmap::IndexMap<String, crate::package::Link> { + self.0.borrow().as_package_interface().get_dev_requires() + } + + pub fn get_suggests(&self) -> indexmap::IndexMap<String, String> { + self.0.borrow().as_package_interface().get_suggests() + } + + pub fn get_links_for_type( + &self, + link_type: &str, + ) -> indexmap::IndexMap<String, crate::package::Link> { + self.0 + .borrow() + .as_package_interface() + .get_links_for_type(link_type) + } + + pub fn get_autoload(&self) -> indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> { + self.0.borrow().as_package_interface().get_autoload() + } + + pub fn get_dev_autoload( + &self, + ) -> indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> { + self.0.borrow().as_package_interface().get_dev_autoload() + } + + pub fn get_include_paths(&self) -> Vec<String> { + self.0.borrow().as_package_interface().get_include_paths() + } + + pub fn get_php_ext( + &self, + ) -> Option<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>> { + self.0.borrow().as_package_interface().get_php_ext() + } + + pub fn set_repository( + &self, + repository: Box<dyn crate::repository::RepositoryInterface>, + ) -> anyhow::Result<()> { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_repository(repository) + } + + pub fn get_binaries(&self) -> Vec<String> { + self.0.borrow().as_package_interface().get_binaries() + } + + pub fn get_unique_name(&self) -> String { + self.0.borrow().as_package_interface().get_unique_name() + } + + pub fn get_notification_url(&self) -> Option<String> { + self.0 + .borrow() + .as_package_interface() + .get_notification_url() + .map(str::to_string) + } + + pub fn get_pretty_string(&self) -> String { + self.0.borrow().as_package_interface().get_pretty_string() + } + + pub fn is_default_branch(&self) -> bool { + self.0.borrow().as_package_interface().is_default_branch() + } + + pub fn get_transport_options( + &self, + ) -> indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> { + self.0 + .borrow() + .as_package_interface() + .get_transport_options() + } + + pub fn set_transport_options( + &self, + options: indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>, + ) { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_transport_options(options); + } + + pub fn set_source_reference(&self, reference: Option<String>) { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_source_reference(reference); + } + + pub fn set_dist_url(&self, url: Option<String>) { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_dist_url(url); + } + + pub fn set_dist_type(&self, r#type: Option<String>) { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_dist_type(r#type); + } + + pub fn set_dist_reference(&self, reference: Option<String>) { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_dist_reference(reference); + } + + pub fn set_source_dist_references(&self, reference: &str) { + self.0 + .borrow_mut() + .as_package_interface_mut() + .set_source_dist_references(reference); + } + } + }; +} + +macro_rules! impl_complete_package_interface_handle { + ($Handle:ty) => { + impl $Handle { + pub fn get_scripts(&self) -> indexmap::IndexMap<String, Vec<String>> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_scripts() + } + + pub fn set_scripts(&self, scripts: indexmap::IndexMap<String, Vec<String>>) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_scripts(scripts); + } + + pub fn get_repositories( + &self, + ) -> Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_repositories() + } + + pub fn set_repositories( + &self, + repositories: Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>, + ) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_repositories(repositories); + } + + pub fn get_license(&self) -> Vec<String> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_license() + } + + pub fn set_license(&self, license: Vec<String>) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_license(license); + } + + pub fn get_keywords(&self) -> Vec<String> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_keywords() + } + + pub fn set_keywords(&self, keywords: Vec<String>) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_keywords(keywords); + } + + pub fn get_description(&self) -> Option<String> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_description() + .map(str::to_string) + } + + pub fn set_description(&self, description: String) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_description(description); + } + + pub fn get_homepage(&self) -> Option<String> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_homepage() + .map(str::to_string) + } + + pub fn set_homepage(&self, homepage: String) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_homepage(homepage); + } + + pub fn get_authors(&self) -> Vec<indexmap::IndexMap<String, String>> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_authors() + } + + pub fn set_authors(&self, authors: Vec<indexmap::IndexMap<String, String>>) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_authors(authors); + } + + pub fn get_support(&self) -> indexmap::IndexMap<String, String> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_support() + } + + pub fn set_support(&self, support: indexmap::IndexMap<String, String>) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_support(support); + } + + pub fn get_funding( + &self, + ) -> Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_funding() + } + + pub fn set_funding( + &self, + funding: Vec<indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>>, + ) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_funding(funding); + } + + pub fn is_abandoned(&self) -> bool { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .is_abandoned() + } + + pub fn get_replacement_package(&self) -> Option<String> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_replacement_package() + .map(str::to_string) + } + + pub fn set_abandoned(&self, abandoned: shirabe_php_shim::PhpMixed) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_abandoned(abandoned); + } + + pub fn get_archive_name(&self) -> Option<String> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_archive_name() + .map(str::to_string) + } + + pub fn set_archive_name(&self, name: String) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_archive_name(name); + } + + pub fn get_archive_excludes(&self) -> Vec<String> { + self.0 + .borrow() + .as_complete_package_interface() + .expect("CompletePackage handle invariant") + .get_archive_excludes() + } + + pub fn set_archive_excludes(&self, excludes: Vec<String>) { + self.0 + .borrow_mut() + .as_complete_package_interface_mut() + .expect("CompletePackage handle invariant") + .set_archive_excludes(excludes); + } + } + }; +} + +macro_rules! impl_root_package_interface_handle { + ($Handle:ty) => { + impl $Handle { + pub fn get_aliases(&self) -> Vec<indexmap::IndexMap<String, String>> { + self.0 + .borrow() + .as_root_package_interface() + .expect("RootPackage handle invariant") + .get_aliases() + .to_vec() + } + + pub fn get_minimum_stability(&self) -> String { + self.0 + .borrow() + .as_root_package_interface() + .expect("RootPackage handle invariant") + .get_minimum_stability() + .to_string() + } + + pub fn get_stability_flags(&self) -> indexmap::IndexMap<String, i64> { + self.0 + .borrow() + .as_root_package_interface() + .expect("RootPackage handle invariant") + .get_stability_flags() + .clone() + } + + pub fn get_references(&self) -> indexmap::IndexMap<String, String> { + self.0 + .borrow() + .as_root_package_interface() + .expect("RootPackage handle invariant") + .get_references() + .clone() + } + + pub fn get_prefer_stable(&self) -> bool { + self.0 + .borrow() + .as_root_package_interface() + .expect("RootPackage handle invariant") + .get_prefer_stable() + } + + pub fn get_config(&self) -> indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> { + self.0 + .borrow() + .as_root_package_interface() + .expect("RootPackage handle invariant") + .get_config() + .clone() + } + + pub fn set_requires(&self, requires: Vec<crate::package::Link>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_requires(requires); + } + + pub fn set_dev_requires(&self, dev_requires: Vec<crate::package::Link>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_dev_requires(dev_requires); + } + + pub fn set_conflicts(&self, conflicts: Vec<crate::package::Link>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_conflicts(conflicts); + } + + pub fn set_provides(&self, provides: Vec<crate::package::Link>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_provides(provides); + } + + pub fn set_replaces(&self, replaces: Vec<crate::package::Link>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_replaces(replaces); + } + + pub fn set_autoload( + &self, + autoload: indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>, + ) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_autoload(autoload); + } + + pub fn set_dev_autoload( + &self, + dev_autoload: indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>, + ) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_dev_autoload(dev_autoload); + } + + pub fn set_stability_flags(&self, stability_flags: indexmap::IndexMap<String, i64>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_stability_flags(stability_flags); + } + + pub fn set_minimum_stability(&self, minimum_stability: String) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_minimum_stability(minimum_stability); + } + + pub fn set_prefer_stable(&self, prefer_stable: bool) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_prefer_stable(prefer_stable); + } + + pub fn set_config( + &self, + config: indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>, + ) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_config(config); + } + + pub fn set_references(&self, references: indexmap::IndexMap<String, String>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_references(references); + } + + pub fn set_aliases(&self, aliases: Vec<indexmap::IndexMap<String, String>>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_aliases(aliases); + } + + pub fn set_suggests(&self, suggests: indexmap::IndexMap<String, String>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_suggests(suggests); + } + + pub fn set_extra(&self, extra: indexmap::IndexMap<String, shirabe_php_shim::PhpMixed>) { + self.0 + .borrow_mut() + .as_root_package_interface_mut() + .expect("RootPackage handle invariant") + .set_extra(extra); + } + } + }; +} + +macro_rules! impl_handle_common { + ($Handle:ty) => { + impl $Handle { + pub fn as_rc(&self) -> &std::rc::Rc<std::cell::RefCell<AnyPackage>> { + &self.0 + } + + pub fn from_rc_unchecked(rc: std::rc::Rc<std::cell::RefCell<AnyPackage>>) -> Self { + Self(rc) + } + + /// Stable identity usable as a map key (PHP `spl_object_hash`). + pub fn ptr_id(&self) -> usize { + std::rc::Rc::as_ptr(&self.0) as *const () as usize + } + + /// PHP `===` (reference identity). + pub fn ptr_eq(&self, other: &Self) -> bool { + std::rc::Rc::ptr_eq(&self.0, &other.0) + } + } + + impl PartialEq for $Handle { + fn eq(&self, other: &Self) -> bool { + std::rc::Rc::ptr_eq(&self.0, &other.0) + } + } + + impl Eq for $Handle {} + + impl std::hash::Hash for $Handle { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.ptr_id().hash(state); + } + } + + impl std::fmt::Display for $Handle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.0.borrow().as_package_interface(), f) + } + } + }; +} + +macro_rules! impl_handle_upcast { + ($Narrow:ty => $Wide:ty) => { + impl From<$Narrow> for $Wide { + fn from(h: $Narrow) -> Self { + Self(h.0) + } + } + }; +} + +/// Shared reference to any package. Corresponds to PHP `PackageInterface`. +#[derive(Debug, Clone)] +pub struct PackageInterfaceHandle(Rc<RefCell<AnyPackage>>); + +/// Shared reference to any package. Corresponds to PHP `BasePackage`. +/// It is exactly the same as `PackageInterface` in Shirabe. It is only for mirroing PHP type +/// annotations. +pub type BasePackageHandle = PackageInterfaceHandle; + +/// Shared reference to a complete package. Corresponds to PHP `CompletePackageInterface`. +#[derive(Debug, Clone)] +pub struct CompletePackageInterfaceHandle(Rc<RefCell<AnyPackage>>); + +/// Shared reference to a root package. Corresponds to PHP `RootPackageInterface`. +#[derive(Debug, Clone)] +pub struct RootPackageInterfaceHandle(Rc<RefCell<AnyPackage>>); + +/// Shared reference to a real (non-alias) package. Corresponds to PHP `Package`. +#[derive(Debug, Clone)] +pub struct PackageHandle(Rc<RefCell<AnyPackage>>); + +/// Shared reference to a real complete package. Corresponds to PHP `CompletePackage`. +#[derive(Debug, Clone)] +pub struct CompletePackageHandle(Rc<RefCell<AnyPackage>>); + +/// Shared reference to a real root package. Corresponds to PHP `RootPackage`. +#[derive(Debug, Clone)] +pub struct RootPackageHandle(Rc<RefCell<AnyPackage>>); + +/// Shared reference to an alias package. Corresponds to PHP `AliasPackage`. +#[derive(Debug, Clone)] +pub struct AliasPackageHandle(Rc<RefCell<AnyPackage>>); + +/// Shared reference to a complete alias package. Corresponds to PHP `CompleteAliasPackage`. +#[derive(Debug, Clone)] +pub struct CompleteAliasPackageHandle(Rc<RefCell<AnyPackage>>); + +/// Shared reference to a root alias package. Corresponds to PHP `RootAliasPackage`. +#[derive(Debug, Clone)] +pub struct RootAliasPackageHandle(Rc<RefCell<AnyPackage>>); + +impl_handle_common!(PackageInterfaceHandle); +impl_handle_common!(CompletePackageInterfaceHandle); +impl_handle_common!(RootPackageInterfaceHandle); +impl_handle_common!(PackageHandle); +impl_handle_common!(CompletePackageHandle); +impl_handle_common!(RootPackageHandle); +impl_handle_common!(AliasPackageHandle); +impl_handle_common!(CompleteAliasPackageHandle); +impl_handle_common!(RootAliasPackageHandle); + +impl_package_interface_handle!(PackageInterfaceHandle); +impl_package_interface_handle!(CompletePackageInterfaceHandle); +impl_package_interface_handle!(RootPackageInterfaceHandle); +impl_package_interface_handle!(PackageHandle); +impl_package_interface_handle!(CompletePackageHandle); +impl_package_interface_handle!(RootPackageHandle); +impl_package_interface_handle!(AliasPackageHandle); +impl_package_interface_handle!(CompleteAliasPackageHandle); +impl_package_interface_handle!(RootAliasPackageHandle); + +impl_complete_package_interface_handle!(CompletePackageInterfaceHandle); +impl_complete_package_interface_handle!(RootPackageInterfaceHandle); +impl_complete_package_interface_handle!(CompletePackageHandle); +impl_complete_package_interface_handle!(RootPackageHandle); +impl_complete_package_interface_handle!(CompleteAliasPackageHandle); +impl_complete_package_interface_handle!(RootAliasPackageHandle); + +impl_root_package_interface_handle!(RootPackageInterfaceHandle); +impl_root_package_interface_handle!(RootPackageHandle); +impl_root_package_interface_handle!(RootAliasPackageHandle); + +impl_handle_upcast!(CompletePackageInterfaceHandle => PackageInterfaceHandle); + +impl_handle_upcast!(RootPackageInterfaceHandle => CompletePackageInterfaceHandle); +impl_handle_upcast!(RootPackageInterfaceHandle => PackageInterfaceHandle); + +impl_handle_upcast!(PackageHandle => PackageInterfaceHandle); + +impl_handle_upcast!(CompletePackageHandle => PackageHandle); +impl_handle_upcast!(CompletePackageHandle => CompletePackageInterfaceHandle); +impl_handle_upcast!(CompletePackageHandle => PackageInterfaceHandle); + +impl_handle_upcast!(RootPackageHandle => CompletePackageHandle); +impl_handle_upcast!(RootPackageHandle => PackageHandle); +impl_handle_upcast!(RootPackageHandle => RootPackageInterfaceHandle); +impl_handle_upcast!(RootPackageHandle => CompletePackageInterfaceHandle); +impl_handle_upcast!(RootPackageHandle => PackageInterfaceHandle); + +impl_handle_upcast!(AliasPackageHandle => PackageInterfaceHandle); + +impl_handle_upcast!(CompleteAliasPackageHandle => AliasPackageHandle); +impl_handle_upcast!(CompleteAliasPackageHandle => CompletePackageInterfaceHandle); +impl_handle_upcast!(CompleteAliasPackageHandle => PackageInterfaceHandle); + +impl_handle_upcast!(RootAliasPackageHandle => CompleteAliasPackageHandle); +impl_handle_upcast!(RootAliasPackageHandle => AliasPackageHandle); +impl_handle_upcast!(RootAliasPackageHandle => RootPackageInterfaceHandle); +impl_handle_upcast!(RootAliasPackageHandle => CompletePackageInterfaceHandle); +impl_handle_upcast!(RootAliasPackageHandle => PackageInterfaceHandle); + +macro_rules! impl_handle_downcasts { + ($Handle:ty) => { + impl $Handle { + /// PHP `$p instanceof AliasPackage`. + pub fn as_alias(&self) -> Option<AliasPackageHandle> { + self.0 + .borrow() + .is_alias() + .then(|| AliasPackageHandle(self.0.clone())) + } + + /// PHP `$p instanceof CompletePackageInterface`. + pub fn as_complete(&self) -> Option<CompletePackageInterfaceHandle> { + self.0 + .borrow() + .is_complete() + .then(|| CompletePackageInterfaceHandle(self.0.clone())) + } + + /// PHP `$p instanceof RootPackageInterface`. + pub fn as_root(&self) -> Option<RootPackageInterfaceHandle> { + self.0 + .borrow() + .is_root() + .then(|| RootPackageInterfaceHandle(self.0.clone())) + } + + /// PHP `$p instanceof Package` (real, non-alias). + pub fn as_package(&self) -> Option<PackageHandle> { + self.0 + .borrow() + .is_real() + .then(|| PackageHandle(self.0.clone())) + } + + /// PHP `$p instanceof CompletePackage` (real). + pub fn as_complete_package(&self) -> Option<CompletePackageHandle> { + self.0 + .borrow() + .is_complete_real() + .then(|| CompletePackageHandle(self.0.clone())) + } + + /// PHP `$p instanceof RootPackage` (real). + pub fn as_root_package(&self) -> Option<RootPackageHandle> { + self.0 + .borrow() + .is_root_real() + .then(|| RootPackageHandle(self.0.clone())) + } + + /// PHP `$p instanceof CompleteAliasPackage`. + pub fn as_complete_alias_package(&self) -> Option<CompleteAliasPackageHandle> { + self.0 + .borrow() + .is_complete_alias() + .then(|| CompleteAliasPackageHandle(self.0.clone())) + } + + /// PHP `$p instanceof RootAliasPackage`. + pub fn as_root_alias_package(&self) -> Option<RootAliasPackageHandle> { + self.0 + .borrow() + .is_root_alias() + .then(|| RootAliasPackageHandle(self.0.clone())) + } + + pub fn is_alias(&self) -> bool { + self.0.borrow().is_alias() + } + } + }; +} + +impl_handle_downcasts!(PackageInterfaceHandle); + +impl PackageHandle { + pub fn from_package(package: Package) -> Self { + Self(Rc::new(RefCell::new(AnyPackage::Package(package)))) + } + + pub fn new(name: String, version: String, pretty_version: String) -> Self { + Self::from_package(Package::new(name, version, pretty_version)) + } +} + +impl CompletePackageHandle { + pub fn from_complete_package(package: CompletePackage) -> Self { + Self(Rc::new(RefCell::new(AnyPackage::CompletePackage(package)))) + } + + pub fn new(name: String, version: String, pretty_version: String) -> Self { + Self::from_complete_package(CompletePackage::new(name, version, pretty_version)) + } +} + +impl RootPackageHandle { + pub fn from_root_package(package: RootPackage) -> Self { + Self(Rc::new(RefCell::new(AnyPackage::RootPackage(package)))) + } + + pub fn new(name: String, version: String, pretty_version: String) -> Self { + Self::from_root_package(RootPackage::new(name, version, pretty_version)) + } +} + +impl AliasPackageHandle { + pub fn from_alias_package(package: AliasPackage) -> Self { + Self(Rc::new(RefCell::new(AnyPackage::AliasPackage(package)))) + } + + pub fn new(alias_of: PackageHandle, version: String, pretty_version: String) -> Self { + Self::from_alias_package(AliasPackage::new(alias_of, version, pretty_version)) + } + + /// PHP `getAliasOf()`. The aliased package is always real. + pub fn get_alias_of(&self) -> PackageHandle { + match &*self.0.borrow() { + AnyPackage::AliasPackage(p) => p.alias_of.clone(), + AnyPackage::CompleteAliasPackage(p) => PackageHandle::from(p.alias_of.clone()), + AnyPackage::RootAliasPackage(p) => PackageHandle::from(p.alias_of.clone()), + _ => unreachable!("AliasPackageHandle invariant violated"), + } + } + + pub fn set_root_package_alias(&self, value: bool) { + match &mut *self.0.borrow_mut() { + AnyPackage::AliasPackage(p) => p.set_root_package_alias(value), + AnyPackage::CompleteAliasPackage(p) => p.set_root_package_alias(value), + AnyPackage::RootAliasPackage(p) => p.set_root_package_alias(value), + _ => unreachable!("AliasPackageHandle invariant violated"), + } + } + + pub fn is_root_package_alias(&self) -> bool { + match &*self.0.borrow() { + AnyPackage::AliasPackage(p) => p.is_root_package_alias(), + AnyPackage::CompleteAliasPackage(p) => p.is_root_package_alias(), + AnyPackage::RootAliasPackage(p) => p.is_root_package_alias(), + _ => unreachable!("AliasPackageHandle invariant violated"), + } + } + + pub fn has_self_version_requires(&self) -> bool { + match &*self.0.borrow() { + AnyPackage::AliasPackage(p) => p.has_self_version_requires(), + AnyPackage::CompleteAliasPackage(p) => p.has_self_version_requires(), + AnyPackage::RootAliasPackage(p) => p.has_self_version_requires(), + _ => unreachable!("AliasPackageHandle invariant violated"), + } + } +} + +impl CompleteAliasPackageHandle { + pub fn from_complete_alias_package(package: CompleteAliasPackage) -> Self { + Self(Rc::new(RefCell::new(AnyPackage::CompleteAliasPackage( + package, + )))) + } + + pub fn new(alias_of: CompletePackageHandle, version: String, pretty_version: String) -> Self { + Self::from_complete_alias_package(CompleteAliasPackage::new( + alias_of, + version, + pretty_version, + )) + } + + /// PHP `getAliasOf()` narrowed to `CompletePackage`. + pub fn get_alias_of(&self) -> CompletePackageHandle { + match &*self.0.borrow() { + AnyPackage::CompleteAliasPackage(p) => p.alias_of.clone(), + AnyPackage::RootAliasPackage(p) => CompletePackageHandle::from(p.alias_of.clone()), + _ => unreachable!("CompleteAliasPackageHandle invariant violated"), + } + } +} + +impl RootAliasPackageHandle { + pub fn from_root_alias_package(package: RootAliasPackage) -> Self { + Self(Rc::new(RefCell::new(AnyPackage::RootAliasPackage(package)))) + } + + pub fn new(alias_of: RootPackageHandle, version: String, pretty_version: String) -> Self { + Self::from_root_alias_package(RootAliasPackage::new(alias_of, version, pretty_version)) + } + + /// PHP `getAliasOf()` narrowed to `RootPackage`. + pub fn get_alias_of(&self) -> RootPackageHandle { + match &*self.0.borrow() { + AnyPackage::RootAliasPackage(p) => p.alias_of.clone(), + _ => unreachable!("RootAliasPackageHandle invariant violated"), + } + } +} diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs index 87d6e35..e1cc60b 100644 --- a/crates/shirabe/src/package/loader/array_loader.rs +++ b/crates/shirabe/src/package/loader/array_loader.rs @@ -12,11 +12,14 @@ use shirabe_php_shim::{ use crate::package::CompleteAliasPackage; use crate::package::CompletePackage; +use crate::package::CompletePackageHandle; use crate::package::CompletePackageInterface; use crate::package::Link; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::RootAliasPackage; use crate::package::RootPackage; +use crate::package::RootPackageHandle; use crate::package::loader::LoaderInterface; use crate::package::version::VersionParser; use crate::package::{BasePackage, SUPPORTED_LINK_TYPES}; @@ -51,7 +54,7 @@ impl LoaderInterface for ArrayLoader { &self, mut config: IndexMap<String, PhpMixed>, class: Option<String>, - ) -> Result<Box<dyn BasePackage>> { + ) -> Result<PackageInterfaceHandle> { let class = class.unwrap_or_else(|| "Composer\\Package\\CompletePackage".to_string()); if class != "Composer\\Package\\CompletePackage" @@ -104,8 +107,8 @@ impl ArrayLoader { pub fn load_packages( &self, versions: Vec<IndexMap<String, PhpMixed>>, - ) -> Result<Vec<Box<dyn BasePackage>>> { - let mut packages: Vec<Box<dyn BasePackage>> = vec![]; + ) -> Result<Vec<PackageInterfaceHandle>> { + let mut packages: Vec<PackageInterfaceHandle> = vec![]; let mut link_cache: IndexMap< String, IndexMap<String, IndexMap<String, IndexMap<String, (String, Link)>>>, @@ -226,7 +229,7 @@ impl ArrayLoader { &self, mut package: Box<CompletePackage>, config: &mut IndexMap<String, PhpMixed>, - ) -> Result<Box<dyn BasePackage>> { + ) -> Result<PackageInterfaceHandle> { // PHP: if (!$package instanceof CompletePackage) — true by construction in Rust // (create_object always returns Box<CompletePackage>); kept as a no-op for parity. let _ = LogicException { @@ -593,12 +596,20 @@ impl ArrayLoader { // TODO(phase-b): `$package instanceof RootPackage` downcast from CompletePackage let package_as_root: Option<RootPackage> = None; if let Some(root) = package_as_root { - let _ = RootAliasPackage::new(root, alias_normalized, pretty_alias); + let _ = RootAliasPackage::new( + RootPackageHandle::from_root_package(root), + alias_normalized, + pretty_alias, + ); // TODO(phase-b): return Box<RootAliasPackage> wrapped as Box<BasePackage> todo!("phase-b: return RootAliasPackage as Box<BasePackage>") } - let _ = CompleteAliasPackage::new(*package, alias_normalized, pretty_alias); + let _ = CompleteAliasPackage::new( + CompletePackageHandle::from_complete_package(*package), + alias_normalized, + pretty_alias, + ); // TODO(phase-b): return Box<CompleteAliasPackage> wrapped as Box<BasePackage> todo!("phase-b: return CompleteAliasPackage as Box<BasePackage>") } diff --git a/crates/shirabe/src/package/loader/json_loader.rs b/crates/shirabe/src/package/loader/json_loader.rs index e0589bb..5f8cc4f 100644 --- a/crates/shirabe/src/package/loader/json_loader.rs +++ b/crates/shirabe/src/package/loader/json_loader.rs @@ -1,7 +1,7 @@ //! ref: composer/src/Composer/Package/Loader/JsonLoader.php use crate::json::JsonFile; -use crate::package::BasePackage; +use crate::package::PackageInterfaceHandle; use crate::package::loader::LoaderInterface; use anyhow::Result; use std::path::Path; @@ -20,7 +20,7 @@ impl JsonLoader { Self { loader } } - pub fn load(&self, json: JsonLoaderInput) -> Result<Box<dyn BasePackage>> { + pub fn load(&self, json: JsonLoaderInput) -> Result<PackageInterfaceHandle> { let config = match json { JsonLoaderInput::File(mut json_file) => json_file.read()?, JsonLoaderInput::String(ref s) if Path::new(s).exists() => { diff --git a/crates/shirabe/src/package/loader/loader_interface.rs b/crates/shirabe/src/package/loader/loader_interface.rs index d10b7be..586905f 100644 --- a/crates/shirabe/src/package/loader/loader_interface.rs +++ b/crates/shirabe/src/package/loader/loader_interface.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Package/Loader/LoaderInterface.php -use crate::package::BasePackage; +use crate::package::PackageInterfaceHandle; use indexmap::IndexMap; use shirabe_php_shim::PhpMixed; @@ -9,5 +9,5 @@ pub trait LoaderInterface: std::fmt::Debug { &self, config: IndexMap<String, PhpMixed>, class: Option<String>, - ) -> anyhow::Result<Box<dyn BasePackage>>; + ) -> anyhow::Result<PackageInterfaceHandle>; } diff --git a/crates/shirabe/src/package/loader/root_package_loader.rs b/crates/shirabe/src/package/loader/root_package_loader.rs index 3cdaaa7..e235e31 100644 --- a/crates/shirabe/src/package/loader/root_package_loader.rs +++ b/crates/shirabe/src/package/loader/root_package_loader.rs @@ -66,7 +66,7 @@ impl RootPackageLoader { config: IndexMap<String, Box<shirabe_php_shim::PhpMixed>>, class: &str, cwd: Option<&str>, - ) -> anyhow::Result<Box<dyn PackageInterface>> { + ) -> anyhow::Result<crate::package::PackageInterfaceHandle> { if class != "Composer\\Package\\RootPackage" { shirabe_php_shim::trigger_error( "The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.", @@ -193,11 +193,14 @@ impl RootPackageLoader { Some("Composer\\Package\\RootPackage".to_string()), )?; - // TODO(phase-b): as_any_mut is not available on BasePackage; downcast via Any is not - // possible without it. Skipping real downcast and using todo!() placeholder. + // TODO(phase-c): mutating the loaded RootPackage through a PackageInterfaceHandle + // requires going through as_root_package() + a RefCell borrow; the inherent + // RootPackage mutators used below are not yet reachable that way. let real_package: &mut RootPackage = { let _ = &mut package; - todo!("downcast Box<dyn BasePackage> to &mut RootPackage requires as_any_mut on trait") + todo!( + "mutate RootPackage through PackageInterfaceHandle (as_root_package + borrow_mut)" + ) }; if auto_versioned { diff --git a/crates/shirabe/src/package/loader/validating_array_loader.rs b/crates/shirabe/src/package/loader/validating_array_loader.rs index a2dd5a7..fbaa0f8 100644 --- a/crates/shirabe/src/package/loader/validating_array_loader.rs +++ b/crates/shirabe/src/package/loader/validating_array_loader.rs @@ -66,7 +66,7 @@ impl ValidatingArrayLoader { &mut self, config: IndexMap<String, Box<PhpMixed>>, class: &str, - ) -> anyhow::Result<Box<dyn BasePackage>> { + ) -> anyhow::Result<crate::package::PackageInterfaceHandle> { self.errors = Vec::new(); self.warnings = Vec::new(); self.config = config.clone(); diff --git a/crates/shirabe/src/package/locker.rs b/crates/shirabe/src/package/locker.rs index 9522e55..bdeb391 100644 --- a/crates/shirabe/src/package/locker.rs +++ b/crates/shirabe/src/package/locker.rs @@ -15,11 +15,11 @@ use shirabe_php_shim::{ use crate::installer::InstallationManager; use crate::io::IOInterface; use crate::json::JsonFile; -use crate::package::AliasPackage; -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::CompleteAliasPackage; use crate::package::Link; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::RootPackageInterface; use crate::package::dumper::ArrayDumper; use crate::package::loader::ArrayLoader; @@ -219,15 +219,15 @@ impl Locker { false }; if has_name { - let mut package_by_name: IndexMap<String, Box<dyn BasePackage>> = IndexMap::new(); + let mut package_by_name: IndexMap<String, BasePackageHandle> = IndexMap::new(); if let PhpMixed::List(list) = locked_packages { for info in list { if let PhpMixed::Array(m) = info.as_ref() { let info_map: IndexMap<String, PhpMixed> = m.iter().map(|(k, v)| (k.clone(), (**v).clone())).collect(); let package = self.loader.load(info_map, None)?; - // TODO(phase-b): PHP shares the package between repository and map (Rc<dyn BasePackage>) - let _name = package.get_name().to_string(); + // PHP shares the package between repository and map; the handle is the shared Rc. + let _name = package.get_name(); let _ = (&mut packages, &mut package_by_name, package); todo!( "packages.add_package(package); package_by_name.insert(name, package); + AliasPackage downcast" @@ -245,10 +245,11 @@ impl Locker { .and_then(|v| v.as_string()) .unwrap_or("") .to_string(); - // TODO(phase-b): Box<dyn BasePackage> is not Clone; PHP semantics need Rc<dyn BasePackage> if let Some(base_pkg) = package_by_name.get(&alias_pkg_name) { let mut alias_pkg = CompleteAliasPackage::new( - todo!("phase-b: downcast Box<BasePackage> to CompletePackage"), + todo!( + "phase-c: narrow base_pkg handle to CompletePackageHandle" + ), m.get("alias_normalized") .and_then(|v| v.as_string()) .unwrap_or("") @@ -463,8 +464,8 @@ impl Locker { /// Locks provided data into lockfile. pub fn set_lock_data( &mut self, - packages: Vec<Box<dyn PackageInterface>>, - dev_packages: Option<Vec<Box<dyn PackageInterface>>>, + packages: Vec<PackageInterfaceHandle>, + dev_packages: Option<Vec<PackageInterfaceHandle>>, platform_reqs: IndexMap<String, String>, platform_dev_reqs: IndexMap<String, String>, aliases: Vec<IndexMap<String, PhpMixed>>, @@ -743,13 +744,11 @@ impl Locker { } /// @param PackageInterface[] $packages - fn lock_packages(&mut self, packages: &[Box<dyn PackageInterface>]) -> Result<PhpMixed> { + fn lock_packages(&mut self, packages: &[PackageInterfaceHandle]) -> Result<PhpMixed> { let mut locked: Vec<IndexMap<String, PhpMixed>> = vec![]; for package in packages { - // TODO(phase-b): `$package instanceof AliasPackage` downcast - let package_as_alias: Option<&AliasPackage> = None; - if package_as_alias.is_some() { + if package.as_alias().is_some() { continue; } @@ -767,15 +766,20 @@ impl Locker { .into()); } - let mut spec = self.dumper.dump(&**package); + let mut spec = self + .dumper + .dump(package.as_rc().borrow().as_package_interface()); spec.shift_remove("version_normalized"); // always move time to the end of the package definition let time = spec.get("time").cloned(); spec.shift_remove("time"); - let time = if package.is_dev() && package.get_installation_source() == Some("source") { + let time = if package.is_dev() + && package.get_installation_source() == Some("source".to_string()) + { // use the exact commit time of the current reference if it's a dev package - let pkg_time = self.get_package_time(&**package)?; + let pkg_time = + self.get_package_time(package.as_rc().borrow().as_package_interface())?; pkg_time.map(PhpMixed::String).or(time) } else { time @@ -976,11 +980,10 @@ impl Locker { .find_packages_with_replacers_and_providers(&link.get_target(), None); if !results.is_empty() { - // TODO(phase-b): reset_first requires Clone on dyn BasePackage; PHP returns shared reference - let provider: &Box<dyn BasePackage> = - todo!("reset_first(&results) shared ref"); + // PHP `reset($results)` returns the first shared package; clone the handle. + let provider: BasePackageHandle = results.first().unwrap().clone(); let _ = &results; - let mut description = provider.get_pretty_version().to_string(); + let mut description = provider.get_pretty_version(); if provider.get_name() != link.get_target() { 'outer: for (method, text) in [ ("getReplaces", "replaced as %s by %s"), diff --git a/crates/shirabe/src/package/mod.rs b/crates/shirabe/src/package/mod.rs index 611f36a..903056f 100644 --- a/crates/shirabe/src/package/mod.rs +++ b/crates/shirabe/src/package/mod.rs @@ -6,6 +6,7 @@ pub mod complete_alias_package; pub mod complete_package; pub mod complete_package_interface; pub mod dumper; +pub mod handle; pub mod link; pub mod loader; pub mod locker; @@ -24,6 +25,7 @@ pub use complete_alias_package::*; pub use complete_package::*; pub use complete_package_interface::*; pub use dumper::*; +pub use handle::*; pub use link::*; pub use loader::*; pub use locker::*; diff --git a/crates/shirabe/src/package/package.rs b/crates/shirabe/src/package/package.rs index 75b4e54..da8451e 100644 --- a/crates/shirabe/src/package/package.rs +++ b/crates/shirabe/src/package/package.rs @@ -570,10 +570,6 @@ impl BasePackage for Package { fn take_repository(&mut self) -> Option<Box<dyn RepositoryInterface>> { todo!() } - - fn clone_box(&self) -> Box<dyn BasePackage> { - todo!() - } } impl std::fmt::Display for Package { diff --git a/crates/shirabe/src/package/package_interface.rs b/crates/shirabe/src/package/package_interface.rs index 1c9cf2e..97aac26 100644 --- a/crates/shirabe/src/package/package_interface.rs +++ b/crates/shirabe/src/package/package_interface.rs @@ -290,14 +290,6 @@ pub trait PackageInterface: std::fmt::Display + std::fmt::Debug { /// Set dist and source references and update dist URL for ones that contain a reference fn set_source_dist_references(&mut self, reference: &str); - // clone_box was moved to BasePackage with a Box<dyn BasePackage> return type; - // exposing it here too caused trait-method ambiguity at every BasePackage call site. - // Callers holding `&dyn PackageInterface` (rather than `&dyn BasePackage`) can use - // `clone_package_box` instead. - fn clone_package_box(&self) -> Box<dyn PackageInterface> { - todo!() - } - fn as_alias_package(&self) -> Option<&crate::package::AliasPackage> { None } diff --git a/crates/shirabe/src/package/root_alias_package.rs b/crates/shirabe/src/package/root_alias_package.rs index d6dafa9..7298c1f 100644 --- a/crates/shirabe/src/package/root_alias_package.rs +++ b/crates/shirabe/src/package/root_alias_package.rs @@ -1,51 +1,77 @@ //! ref: composer/src/Composer/Package/RootAliasPackage.php -use chrono::{DateTime, Utc}; use indexmap::IndexMap; use shirabe_php_shim::PhpMixed; use crate::package::CompleteAliasPackage; +use crate::package::CompletePackageHandle; use crate::package::CompletePackageInterface; use crate::package::Link; -use crate::package::PackageInterface; -use crate::package::RootPackage; +use crate::package::RootPackageHandle; use crate::package::RootPackageInterface; -use crate::repository::RepositoryInterface; +use crate::package::handle::delegate_package_interface_to_inner; #[derive(Debug)] pub struct RootAliasPackage { inner: CompleteAliasPackage, // overrides CompleteAliasPackage::alias_of with the more specific RootPackage type - pub(crate) alias_of: RootPackage, + pub(crate) alias_of: RootPackageHandle, } impl RootAliasPackage { - pub fn new(alias_of: RootPackage, version: String, pretty_version: String) -> Self { - // TODO(phase-b): RootPackage.inner (CompletePackage) is not accessible here - let inner: CompleteAliasPackage = todo!(); + pub fn new(alias_of: RootPackageHandle, version: String, pretty_version: String) -> Self { + let inner = CompleteAliasPackage::new( + CompletePackageHandle::from(alias_of.clone()), + version, + pretty_version, + ); Self { inner, alias_of } } - pub fn get_alias_of(&self) -> &RootPackage { - &self.alias_of + pub fn get_alias_of(&self) -> RootPackageHandle { + self.alias_of.clone() + } + + pub fn set_root_package_alias(&mut self, value: bool) { + self.inner.set_root_package_alias(value); + } + + pub fn is_root_package_alias(&self) -> bool { + self.inner.is_root_package_alias() + } + + pub fn has_self_version_requires(&self) -> bool { + self.inner.has_self_version_requires() + } +} + +delegate_package_interface_to_inner!(RootAliasPackage, inner); + +impl std::fmt::Display for RootAliasPackage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.inner, f) } } impl RootPackageInterface for RootAliasPackage { fn get_aliases(&self) -> &[IndexMap<String, String>] { - self.alias_of.get_aliases() + todo!("RootAliasPackage::get_aliases cannot return a borrow across the aliasOf handle") } fn get_minimum_stability(&self) -> &str { - self.alias_of.get_minimum_stability() + todo!( + "RootAliasPackage::get_minimum_stability cannot return &str across the aliasOf handle" + ) } fn get_stability_flags(&self) -> &IndexMap<String, i64> { - self.alias_of.get_stability_flags() + todo!( + "RootAliasPackage::get_stability_flags cannot return a borrow across the aliasOf handle" + ) } fn get_references(&self) -> &IndexMap<String, String> { - self.alias_of.get_references() + todo!("RootAliasPackage::get_references cannot return a borrow across the aliasOf handle") } fn get_prefer_stable(&self) -> bool { @@ -53,31 +79,28 @@ impl RootPackageInterface for RootAliasPackage { } fn get_config(&self) -> &IndexMap<String, PhpMixed> { - self.alias_of.get_config() + todo!("RootAliasPackage::get_config cannot return a borrow across the aliasOf handle") } fn set_requires(&mut self, requires: Vec<Link>) { - // TODO(phase-b): self.inner.requires = self.replace_self_version_dependencies(requires.clone(), Link::TYPE_REQUIRE) + // TODO(phase-c): PHP re-derives the local links via + // replaceSelfVersionDependencies before forwarding to aliasOf. self.alias_of.set_requires(requires); } fn set_dev_requires(&mut self, dev_requires: Vec<Link>) { - // TODO(phase-b): self.inner.dev_requires = self.replace_self_version_dependencies(dev_requires.clone(), Link::TYPE_DEV_REQUIRE) self.alias_of.set_dev_requires(dev_requires); } fn set_conflicts(&mut self, conflicts: Vec<Link>) { - // TODO(phase-b): self.inner.conflicts = self.replace_self_version_dependencies(conflicts.clone(), Link::TYPE_CONFLICT) self.alias_of.set_conflicts(conflicts); } fn set_provides(&mut self, provides: Vec<Link>) { - // TODO(phase-b): self.inner.provides = self.replace_self_version_dependencies(provides.clone(), Link::TYPE_PROVIDE) self.alias_of.set_provides(provides); } fn set_replaces(&mut self, replaces: Vec<Link>) { - // TODO(phase-b): self.inner.replaces = self.replace_self_version_dependencies(replaces.clone(), Link::TYPE_REPLACE) self.alias_of.set_replaces(replaces); } @@ -124,274 +147,102 @@ impl RootPackageInterface for RootAliasPackage { impl CompletePackageInterface for RootAliasPackage { fn get_scripts(&self) -> IndexMap<String, Vec<String>> { - todo!() + self.inner.get_scripts() } fn set_scripts(&mut self, scripts: IndexMap<String, Vec<String>>) { - todo!() + self.inner.set_scripts(scripts); } fn get_repositories(&self) -> Vec<IndexMap<String, PhpMixed>> { - todo!() + self.inner.get_repositories() } fn set_repositories(&mut self, repositories: Vec<IndexMap<String, PhpMixed>>) { - todo!() + self.inner.set_repositories(repositories); } fn get_license(&self) -> Vec<String> { - todo!() + self.inner.get_license() } fn set_license(&mut self, license: Vec<String>) { - todo!() + self.inner.set_license(license); } fn get_keywords(&self) -> Vec<String> { - todo!() + self.inner.get_keywords() } fn set_keywords(&mut self, keywords: Vec<String>) { - todo!() + self.inner.set_keywords(keywords); } fn get_description(&self) -> Option<&str> { - todo!() + self.inner.get_description() } fn set_description(&mut self, description: String) { - todo!() + self.inner.set_description(description); } fn get_homepage(&self) -> Option<&str> { - todo!() + self.inner.get_homepage() } fn set_homepage(&mut self, homepage: String) { - todo!() + self.inner.set_homepage(homepage); } fn get_authors(&self) -> Vec<IndexMap<String, String>> { - todo!() + self.inner.get_authors() } fn set_authors(&mut self, authors: Vec<IndexMap<String, String>>) { - todo!() + self.inner.set_authors(authors); } fn get_support(&self) -> IndexMap<String, String> { - todo!() + self.inner.get_support() } fn set_support(&mut self, support: IndexMap<String, String>) { - todo!() + self.inner.set_support(support); } fn get_funding(&self) -> Vec<IndexMap<String, PhpMixed>> { - todo!() + self.inner.get_funding() } fn set_funding(&mut self, funding: Vec<IndexMap<String, PhpMixed>>) { - todo!() + self.inner.set_funding(funding); } fn is_abandoned(&self) -> bool { - todo!() + self.inner.is_abandoned() } fn get_replacement_package(&self) -> Option<&str> { - todo!() + self.inner.get_replacement_package() } fn set_abandoned(&mut self, abandoned: PhpMixed) { - todo!() + self.inner.set_abandoned(abandoned); } fn get_archive_name(&self) -> Option<&str> { - todo!() + self.inner.get_archive_name() } fn set_archive_name(&mut self, name: String) { - todo!() + self.inner.set_archive_name(name); } fn get_archive_excludes(&self) -> Vec<String> { - todo!() + self.inner.get_archive_excludes() } fn set_archive_excludes(&mut self, excludes: Vec<String>) { - todo!() - } -} - -impl std::fmt::Display for RootAliasPackage { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() - } -} - -impl PackageInterface for RootAliasPackage { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn get_name(&self) -> &str { - todo!() - } - fn get_pretty_name(&self) -> &str { - todo!() - } - fn get_names(&self, _provides: bool) -> Vec<String> { - todo!() - } - fn set_id(&mut self, _id: i64) { - todo!() - } - fn get_id(&self) -> i64 { - todo!() - } - fn is_dev(&self) -> bool { - todo!() - } - fn get_type(&self) -> &str { - todo!() - } - fn get_target_dir(&self) -> Option<&str> { - todo!() - } - fn get_extra(&self) -> IndexMap<String, PhpMixed> { - todo!() - } - fn set_installation_source(&mut self, _type: Option<String>) { - todo!() - } - fn get_installation_source(&self) -> Option<&str> { - todo!() - } - fn get_source_type(&self) -> Option<&str> { - todo!() - } - fn get_source_url(&self) -> Option<&str> { - todo!() - } - fn get_source_urls(&self) -> Vec<String> { - todo!() - } - fn get_source_reference(&self) -> Option<&str> { - todo!() - } - fn get_source_mirrors(&self) -> Option<Vec<IndexMap<String, PhpMixed>>> { - todo!() - } - fn set_source_mirrors(&mut self, _mirrors: Option<Vec<IndexMap<String, PhpMixed>>>) { - todo!() - } - fn get_dist_type(&self) -> Option<&str> { - todo!() - } - fn get_dist_url(&self) -> Option<&str> { - todo!() - } - fn get_dist_urls(&self) -> Vec<String> { - todo!() - } - fn get_dist_reference(&self) -> Option<&str> { - todo!() - } - fn get_dist_sha1_checksum(&self) -> Option<&str> { - todo!() - } - fn get_dist_mirrors(&self) -> Option<Vec<IndexMap<String, PhpMixed>>> { - todo!() - } - fn set_dist_mirrors(&mut self, _mirrors: Option<Vec<IndexMap<String, PhpMixed>>>) { - todo!() - } - fn get_version(&self) -> &str { - todo!() - } - fn get_pretty_version(&self) -> &str { - todo!() - } - fn get_full_pretty_version(&self, _truncate: bool, _display_mode: i64) -> String { - todo!() - } - fn get_release_date(&self) -> Option<DateTime<Utc>> { - todo!() - } - fn get_stability(&self) -> &str { - todo!() - } - fn get_requires(&self) -> IndexMap<String, Link> { - todo!() - } - fn get_conflicts(&self) -> IndexMap<String, Link> { - todo!() - } - fn get_provides(&self) -> IndexMap<String, Link> { - todo!() - } - fn get_replaces(&self) -> IndexMap<String, Link> { - todo!() - } - fn get_dev_requires(&self) -> IndexMap<String, Link> { - todo!() - } - fn get_suggests(&self) -> IndexMap<String, String> { - todo!() - } - fn get_autoload(&self) -> IndexMap<String, PhpMixed> { - todo!() - } - fn get_dev_autoload(&self) -> IndexMap<String, PhpMixed> { - todo!() - } - fn get_include_paths(&self) -> Vec<String> { - todo!() - } - fn get_php_ext(&self) -> Option<IndexMap<String, PhpMixed>> { - todo!() - } - fn set_repository(&mut self, _repository: Box<dyn RepositoryInterface>) -> anyhow::Result<()> { - todo!() - } - fn get_repository(&self) -> Option<&dyn RepositoryInterface> { - todo!() - } - fn get_binaries(&self) -> Vec<String> { - todo!() - } - fn get_unique_name(&self) -> String { - todo!() - } - fn get_notification_url(&self) -> Option<&str> { - todo!() - } - fn get_pretty_string(&self) -> String { - todo!() - } - fn is_default_branch(&self) -> bool { - todo!() - } - fn get_transport_options(&self) -> IndexMap<String, PhpMixed> { - todo!() - } - fn set_transport_options(&mut self, _options: IndexMap<String, PhpMixed>) { - todo!() - } - fn set_source_reference(&mut self, _reference: Option<String>) { - todo!() - } - fn set_dist_url(&mut self, _url: Option<String>) { - todo!() - } - fn set_dist_type(&mut self, _type: Option<String>) { - todo!() - } - fn set_dist_reference(&mut self, _reference: Option<String>) { - todo!() - } - fn set_source_dist_references(&mut self, _reference: &str) { - todo!() + self.inner.set_archive_excludes(excludes); } } diff --git a/crates/shirabe/src/package/root_package.rs b/crates/shirabe/src/package/root_package.rs index 7e79530..b0fdf26 100644 --- a/crates/shirabe/src/package/root_package.rs +++ b/crates/shirabe/src/package/root_package.rs @@ -26,9 +26,7 @@ impl RootPackage { pub const DEFAULT_PRETTY_VERSION: &'static str = "1.0.0+no-version-set"; pub fn new(name: String, version: String, pretty_version: String) -> Self { - // TODO(phase-b): CompletePackage::new signature is not yet pinned down - let inner: CompletePackage = todo!(); - let _ = (name, version, pretty_version); + let inner = CompletePackage::new(name, version, pretty_version); Self { inner, minimum_stability: "stable".to_string(), diff --git a/crates/shirabe/src/package/root_package_interface.rs b/crates/shirabe/src/package/root_package_interface.rs index 2370e25..5f47232 100644 --- a/crates/shirabe/src/package/root_package_interface.rs +++ b/crates/shirabe/src/package/root_package_interface.rs @@ -51,14 +51,6 @@ pub trait RootPackageInterface: CompletePackageInterface { fn set_extra(&mut self, extra: IndexMap<String, PhpMixed>); - fn clone_as_package_interface(&self) -> Box<dyn PackageInterface> { - todo!() - } - - fn clone_box(&self) -> Box<dyn RootPackageInterface> { - todo!() - } - fn as_package_interface(&self) -> &dyn PackageInterface { todo!() } diff --git a/crates/shirabe/src/package/version/version_selector.rs b/crates/shirabe/src/package/version/version_selector.rs index 3bd0191..28b9277 100644 --- a/crates/shirabe/src/package/version/version_selector.rs +++ b/crates/shirabe/src/package/version/version_selector.rs @@ -16,9 +16,8 @@ use crate::filter::platform_requirement_filter::IgnoreListPlatformRequirementFil use crate::filter::platform_requirement_filter::PlatformRequirementFilterFactory; use crate::filter::platform_requirement_filter::PlatformRequirementFilterInterface; use crate::io::IOInterface; -use crate::package::AliasPackage; use crate::package::PackageInterface; -use crate::package::base_package::{self, BasePackage}; +use crate::package::base_package; use crate::package::dumper::ArrayDumper; use crate::package::loader::ArrayLoader; use crate::package::version::VersionParser; @@ -69,7 +68,7 @@ impl VersionSelector { repo_set_flags: i64, io: Option<&dyn IOInterface>, show_warnings: shirabe_php_shim::PhpMixed, - ) -> anyhow::Result<Option<Box<dyn PackageInterface>>> { + ) -> anyhow::Result<Option<crate::package::PackageInterfaceHandle>> { if !base_package::STABILITIES.contains_key(preferred_stability) { return Err(shirabe_php_shim::UnexpectedValueException { message: format!( @@ -99,8 +98,14 @@ impl VersionSelector { let min_priority = *base_package::STABILITIES.get(preferred_stability).unwrap(); candidates.sort_by(|a, b| { - let a_priority = a.get_stability_priority(); - let b_priority = b.get_stability_priority(); + // BasePackage::get_stability_priority() is not forwarded by the handle; compute it + // directly from the stability name. + let a_priority = *base_package::STABILITIES + .get(a.get_stability().as_str()) + .unwrap(); + let b_priority = *base_package::STABILITIES + .get(b.get_stability().as_str()) + .unwrap(); if min_priority < a_priority && b_priority < a_priority { return std::cmp::Ordering::Greater; @@ -112,9 +117,9 @@ impl VersionSelector { return std::cmp::Ordering::Less; } - if version_compare(b.get_version(), a.get_version(), ">") { + if version_compare(&b.get_version(), &a.get_version(), ">") { std::cmp::Ordering::Greater - } else if version_compare(b.get_version(), a.get_version(), "<") { + } else if version_compare(&b.get_version(), &a.get_version(), "<") { std::cmp::Ordering::Less } else { std::cmp::Ordering::Equal @@ -127,11 +132,11 @@ impl VersionSelector { .downcast_ref::<IgnoreAllPlatformRequirementFilter>() .is_some(); - let package: Option<Box<dyn PackageInterface>>; + let package: Option<crate::package::PackageInterfaceHandle>; if !self.platform_constraints.is_empty() && !is_ignore_all { let mut already_warned_names: IndexMap<String, bool> = IndexMap::new(); let mut already_seen_names: IndexMap<String, bool> = IndexMap::new(); - let mut found_package: Option<Box<dyn PackageInterface>> = None; + let mut found_package: Option<crate::package::PackageInterfaceHandle> = None; 'pkgs: for pkg in candidates.iter() { let reqs = pkg.get_requires(); @@ -171,7 +176,7 @@ impl VersionSelector { reason = "is missing from your platform"; } - let is_latest_version = !already_seen_names.contains_key(pkg.get_name()); + let is_latest_version = !already_seen_names.contains_key(&pkg.get_name()); already_seen_names.insert(pkg.get_name().to_string(), true); if let Some(io) = io { let should_warn = match &show_warnings { @@ -215,13 +220,13 @@ impl VersionSelector { continue; } - found_package = Some(pkg.clone_box()); + found_package = Some(pkg.clone().into()); break; } package = found_package; } else { package = if !candidates.is_empty() { - Some(candidates.remove(0).clone_package_box()) + Some(candidates.remove(0).into()) } else { None }; @@ -232,10 +237,9 @@ impl VersionSelector { Some(p) => p, }; - let package = if let Some(alias) = package.as_ref().as_any().downcast_ref::<AliasPackage>() - { + let package = if let Some(alias) = package.as_alias() { if alias.get_version() == VersionParser::DEFAULT_BRANCH_ALIAS { - alias.get_alias_of().clone_package_box() + alias.get_alias_of().into() } else { package } diff --git a/crates/shirabe/src/plugin/plugin_manager.rs b/crates/shirabe/src/plugin/plugin_manager.rs index 77ac577..ddde387 100644 --- a/crates/shirabe/src/plugin/plugin_manager.rs +++ b/crates/shirabe/src/plugin/plugin_manager.rs @@ -25,6 +25,7 @@ use crate::package::CompletePackage; use crate::package::Link; use crate::package::Locker; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::RootPackageInterface; use crate::package::base_package::{self, BasePackage}; use crate::package::version::VersionParser; @@ -143,10 +144,20 @@ impl PluginManager { .borrow() .get_local_repository() .clone_box(); - // The root package borrow is also tied to `self.composer`; clone the package box - // for the same reason as above. - let root_package = self.composer_full().borrow().get_package().clone_box(); - self.load_repository(&*repo, false, Some(&*root_package))?; + // The root package borrow is also tied to `self.composer`; clone the package handle + // (shared Rc) for the same reason as above. + let root_package = self.composer_full().borrow().get_package().clone(); + self.load_repository( + &*repo, + false, + Some( + root_package + .as_rc() + .borrow() + .as_root_package_interface() + .unwrap(), + ), + )?; } if self.global_composer.is_some() && !self.are_plugins_disabled("global") { @@ -532,23 +543,18 @@ impl PluginManager { } let sorted_packages = PackageSorter::sort_packages( - packages.iter().map(|p| p.clone_package_box()).collect(), + packages.iter().map(|p| p.clone().into()).collect(), weights, ); - let required_packages: Vec<Box<dyn PackageInterface>> = if !is_global_repo { + let required_packages: Vec<crate::package::BasePackageHandle> = if !is_global_repo { // PHP: $requiredPackages = RepositoryUtils::filterRequiredPackages($packages, $rootPackage, true); - // RepositoryUtils::filter_required_packages takes &[Box<dyn BasePackage>] plus a bucket. - // We need to convert &[Box<dyn BasePackage>] from packages. - let bucket: Vec<Box<dyn crate::package::BasePackage>> = vec![]; + let bucket: Vec<crate::package::BasePackageHandle> = vec![]; RepositoryUtils::filter_required_packages( packages.as_slice(), - root_package.unwrap(), + root_package.unwrap() as &dyn PackageInterface, true, bucket, ) - .iter() - .map(|p| p.clone_package_box()) - .collect() } else { vec![] }; @@ -565,26 +571,33 @@ impl PluginManager { } // PHP: !in_array($package, $requiredPackages, true) — identity-based comparison. - // Compare data pointers since `sorted_packages` and `required_packages` are both - // `Box<dyn PackageInterface>`. - let package_addr = - package.as_ref() as *const dyn PackageInterface as *const () as usize; - let in_required = required_packages.iter().any(|rp| { - (rp.as_ref() as *const dyn PackageInterface as *const () as usize) == package_addr - }); + // Both `sorted_packages` and `required_packages` are package handles, so compare + // by shared-Rc pointer identity. + let package_addr = package.ptr_id(); + let in_required = required_packages + .iter() + .any(|rp| rp.ptr_id() == package_addr); if !is_global_repo && !in_required - && !self.is_plugin_allowed(package.get_name(), false, true, false)? + && !self.is_plugin_allowed(&package.get_name(), false, true, false)? { self.io.write_error(&format!("<warning>The \"{}\" plugin was not loaded as it is not listed in allow-plugins and is not required by the root package anymore.</warning>", package.get_name())); continue; } if "composer-plugin" == package.get_type() { - self.register_package(&**package, false, is_global_repo)?; + self.register_package( + package.as_rc().borrow().as_package_interface(), + false, + is_global_repo, + )?; // Backward compatibility } else if "composer-installer" == package.get_type() { - self.register_package(&**package, false, is_global_repo)?; + self.register_package( + package.as_rc().borrow().as_package_interface(), + false, + is_global_repo, + )?; } let _ = cp; } @@ -596,7 +609,7 @@ impl PluginManager { let packages = repo.get_packages(); // PHP: $sortedPackages = array_reverse(PackageSorter::sortPackages($packages)); let mut sorted_packages = PackageSorter::sort_packages( - packages.iter().map(|p| p.clone_package_box()).collect(), + packages.iter().map(|p| p.clone().into()).collect(), IndexMap::new(), ); sorted_packages.reverse(); @@ -606,10 +619,10 @@ impl PluginManager { continue; } if "composer-plugin" == package.get_type() { - self.deactivate_package(&**package); + self.deactivate_package(package.as_rc().borrow().as_package_interface()); // Backward compatibility } else if "composer-installer" == package.get_type() { - self.deactivate_package(&**package); + self.deactivate_package(package.as_rc().borrow().as_package_interface()); } } } @@ -617,21 +630,21 @@ impl PluginManager { fn collect_dependencies( &self, installed_repo: &InstalledRepository, - mut collected: IndexMap<String, Box<dyn PackageInterface>>, + mut collected: IndexMap<String, PackageInterfaceHandle>, package: &dyn PackageInterface, - ) -> IndexMap<String, Box<dyn PackageInterface>> { + ) -> IndexMap<String, PackageInterfaceHandle> { // TODO(plugin): used by registerPackage to assemble plugin dependency autoload map for (_k, require_link) in &package.get_requires() { for required_package in installed_repo .find_packages_with_replacers_and_providers(require_link.get_target(), None) { - if !collected.contains_key(required_package.get_name()) { - collected.insert( - required_package.get_name().to_string(), - required_package.clone_package_box(), + if !collected.contains_key(&required_package.get_name()) { + collected.insert(required_package.get_name(), required_package.clone().into()); + collected = self.collect_dependencies( + installed_repo, + collected, + required_package.as_rc().borrow().as_package_interface(), ); - collected = - self.collect_dependencies(installed_repo, collected, &*required_package); } } } diff --git a/crates/shirabe/src/plugin/pre_pool_create_event.rs b/crates/shirabe/src/plugin/pre_pool_create_event.rs index f4426f7..2bbaa6c 100644 --- a/crates/shirabe/src/plugin/pre_pool_create_event.rs +++ b/crates/shirabe/src/plugin/pre_pool_create_event.rs @@ -4,7 +4,7 @@ use indexmap::IndexMap; use crate::dependency_resolver::Request; use crate::event_dispatcher::Event; -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::repository::RepositoryInterface; #[derive(Debug)] @@ -16,8 +16,8 @@ pub struct PrePoolCreateEvent { stability_flags: IndexMap<String, i64>, root_aliases: IndexMap<String, IndexMap<String, IndexMap<String, String>>>, root_references: IndexMap<String, String>, - packages: Vec<Box<dyn BasePackage>>, - unacceptable_fixed_packages: Vec<Box<dyn BasePackage>>, + packages: Vec<BasePackageHandle>, + unacceptable_fixed_packages: Vec<BasePackageHandle>, } impl PrePoolCreateEvent { @@ -34,8 +34,8 @@ impl PrePoolCreateEvent { stability_flags: IndexMap<String, i64>, root_aliases: IndexMap<String, IndexMap<String, IndexMap<String, String>>>, root_references: IndexMap<String, String>, - packages: Vec<Box<dyn BasePackage>>, - unacceptable_fixed_packages: Vec<Box<dyn BasePackage>>, + packages: Vec<BasePackageHandle>, + unacceptable_fixed_packages: Vec<BasePackageHandle>, ) -> Self { Self { inner: Event::new(name, vec![], IndexMap::new()), @@ -76,19 +76,19 @@ impl PrePoolCreateEvent { &self.root_references } - pub fn get_packages(&self) -> &Vec<Box<dyn BasePackage>> { + pub fn get_packages(&self) -> &Vec<BasePackageHandle> { &self.packages } - pub fn get_unacceptable_fixed_packages(&self) -> &Vec<Box<dyn BasePackage>> { + pub fn get_unacceptable_fixed_packages(&self) -> &Vec<BasePackageHandle> { &self.unacceptable_fixed_packages } - pub fn set_packages(&mut self, packages: Vec<Box<dyn BasePackage>>) { + pub fn set_packages(&mut self, packages: Vec<BasePackageHandle>) { self.packages = packages; } - pub fn set_unacceptable_fixed_packages(&mut self, packages: Vec<Box<dyn BasePackage>>) { + pub fn set_unacceptable_fixed_packages(&mut self, packages: Vec<BasePackageHandle>) { self.unacceptable_fixed_packages = packages; } } diff --git a/crates/shirabe/src/repository/array_repository.rs b/crates/shirabe/src/repository/array_repository.rs index fe45849..16f5f30 100644 --- a/crates/shirabe/src/repository/array_repository.rs +++ b/crates/shirabe/src/repository/array_repository.rs @@ -6,19 +6,14 @@ use std::cell::RefCell; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; -use shirabe_php_shim::{ - Countable, InvalidArgumentException, LogicException, implode, preg_quote, spl_object_hash, - strtolower, -}; +use shirabe_php_shim::{Countable, LogicException, implode, preg_quote, strtolower}; use shirabe_semver::constraint::AnyConstraint; use shirabe_semver::constraint::SimpleConstraint; -use crate::package::AliasPackage; -use crate::package::BasePackage; -use crate::package::CompleteAliasPackage; -use crate::package::CompletePackage; -use crate::package::CompletePackageInterface; +use crate::package::BasePackageHandle; +use crate::package::PackageHandle; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::version::StabilityFilter; use crate::package::version::VersionParser; use crate::repository::{ @@ -31,15 +26,15 @@ use crate::repository::{ pub struct ArrayRepository { /// @var ?array<BasePackage> // TODO(phase-b): RefCell models PHP's lazy init via getPackages()/count() under &self - pub(crate) packages: RefCell<Option<Vec<Box<dyn BasePackage>>>>, + pub(crate) packages: RefCell<Option<Vec<BasePackageHandle>>>, /// @var ?array<BasePackage> indexed by package unique name and used to cache hasPackage calls - pub(crate) package_map: RefCell<Option<IndexMap<String, Box<dyn BasePackage>>>>, + pub(crate) package_map: RefCell<Option<IndexMap<String, BasePackageHandle>>>, } impl ArrayRepository { /// @param array<PackageInterface> $packages - pub fn new(packages: Vec<Box<dyn PackageInterface>>) -> Result<Self> { + pub fn new(packages: Vec<PackageInterfaceHandle>) -> Result<Self> { let this = Self { packages: RefCell::new(None), package_map: RefCell::new(None), @@ -51,41 +46,27 @@ impl ArrayRepository { } /// Adds a new package to the repository - pub fn add_package(&self, package: Box<dyn PackageInterface>) -> Result<()> { - // PHP: if (!$package instanceof BasePackage) throw new \InvalidArgumentException(...) - // TODO(phase-b): need a real `instanceof BasePackage` check on dyn PackageInterface; - // dyn-trait downcast requires Sized. Defer until BasePackage exposes an `as_base_package`. - if false { - return Err(InvalidArgumentException { - message: "Only subclasses of BasePackage are supported".to_string(), - code: 0, - } - .into()); - } - // TODO(phase-b): convert Box<dyn PackageInterface> to Box<dyn BasePackage> - let mut package: Box<dyn BasePackage> = - todo!("downcast Box<dyn PackageInterface> to Box<dyn BasePackage>"); - + pub fn add_package(&self, package: PackageInterfaceHandle) -> Result<()> { 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>"))?; - let aliased_package: Option<Box<dyn BasePackage>> = - if let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { - Some(alias.get_alias_of().clone_box()) - } else { - None - }; + let aliased_package: Option<PackageHandle> = + package.as_alias().map(|alias| alias.get_alias_of()); - self.packages.borrow_mut().as_mut().unwrap().push(package); + self.packages + .borrow_mut() + .as_mut() + .unwrap() + .push(package.into()); if let Some(aliased_package) = aliased_package { - if aliased_package.get_repository().is_none() { - // TODO(phase-b): pass aliased_package as Box<dyn PackageInterface> - self.add_package(todo!("Box<BasePackage> -> Box<dyn PackageInterface>"))?; - } + // 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; } // invalidate package map cache @@ -96,16 +77,18 @@ impl ArrayRepository { /// @return AliasPackage|CompleteAliasPackage pub(crate) fn create_alias_package( &self, - mut package: Box<dyn BasePackage>, + package: BasePackageHandle, alias: String, pretty_alias: String, - ) -> Box<dyn BasePackage> { - while let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() { - package = alias_pkg.get_alias_of().clone_box(); + ) -> BasePackageHandle { + let mut package = package; + while let Some(alias_pkg) = package.as_alias() { + package = alias_pkg.get_alias_of().into(); } - if package.as_any().downcast_ref::<CompletePackage>().is_some() { - // TODO(phase-b): construct CompleteAliasPackage/AliasPackage and return as Box<BasePackage> + let _ = (&package, &alias, &pretty_alias); + if package.as_complete_package().is_some() { + // TODO(phase-b): construct CompleteAliasPackage/AliasPackage and return as a handle return todo!("new CompleteAliasPackage(package, alias, pretty_alias)"); } @@ -165,17 +148,15 @@ impl RepositoryInterface for ArrayRepository { package_name_map: IndexMap<String, Option<AnyConstraint>>, acceptable_stabilities: IndexMap<String, i64>, stability_flags: IndexMap<String, i64>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> LoadPackagesResult { let packages = self.get_packages(); - let mut result: IndexMap<String, Box<dyn BasePackage>> = IndexMap::new(); + let mut result: IndexMap<String, BasePackageHandle> = IndexMap::new(); let mut names_found: IndexMap<String, bool> = IndexMap::new(); for package in &packages { - if package_name_map.contains_key(PackageInterface::get_name(package.as_ref())) { - let constraint_opt = package_name_map - .get(PackageInterface::get_name(package.as_ref())) - .unwrap(); + if package_name_map.contains_key(&package.get_name()) { + let constraint_opt = package_name_map.get(&package.get_name()).unwrap(); let constraint_matches = match constraint_opt { None => true, Some(c) => c.matches( @@ -191,38 +172,35 @@ impl RepositoryInterface for ArrayRepository { && StabilityFilter::is_package_acceptable( &acceptable_stabilities, &stability_flags, - &PackageInterface::get_names(package.as_ref(), true), - package.get_stability(), + &package.get_names(true), + &package.get_stability(), ) && !already_loaded - .get(PackageInterface::get_name(package.as_ref())) - .map(|v| v.contains_key(package.get_version())) + .get(&package.get_name()) + .map(|v| v.contains_key(&package.get_version())) .unwrap_or(false) { // add selected packages which match stability requirements - result.insert(spl_object_hash(package.as_ref()), package.clone_box()); + result.insert(package.ptr_id().to_string(), package.clone()); // add the aliased package for packages where the alias matches - if let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { + if let Some(alias) = package.as_alias() { let aliased = alias.get_alias_of(); - if !result.contains_key(&spl_object_hash(aliased)) { - result.insert(spl_object_hash(aliased), aliased.clone_box()); + if !result.contains_key(&aliased.ptr_id().to_string()) { + result.insert(aliased.ptr_id().to_string(), aliased.into()); } } } - names_found.insert( - PackageInterface::get_name(package.as_ref()).to_string(), - true, - ); + names_found.insert(package.get_name(), true); } } // add aliases of packages that were selected, even if the aliases did not match for package in &packages { - if let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { + if let Some(alias) = package.as_alias() { let aliased = alias.get_alias_of(); - if result.contains_key(&spl_object_hash(aliased)) { - result.insert(spl_object_hash(package.as_ref()), package.clone_box()); + if result.contains_key(&aliased.ptr_id().to_string()) { + result.insert(package.ptr_id().to_string(), package.clone()); } } } @@ -237,7 +215,7 @@ impl RepositoryInterface for ArrayRepository { &self, name: &str, constraint: FindPackageConstraint, - ) -> Option<Box<dyn BasePackage>> { + ) -> Option<BasePackageHandle> { let name = strtolower(name); let constraint: AnyConstraint = match constraint { @@ -249,7 +227,7 @@ impl RepositoryInterface for ArrayRepository { }; for package in self.get_packages() { - if name == PackageInterface::get_name(package.as_ref()) { + if name == package.get_name() { let pkg_constraint = SimpleConstraint::new( "==".to_string(), package.get_version().to_string(), @@ -268,7 +246,7 @@ impl RepositoryInterface for ArrayRepository { &self, name: &str, constraint: Option<FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { // normalize name let name = strtolower(name); let mut packages = vec![]; @@ -283,7 +261,7 @@ impl RepositoryInterface for ArrayRepository { }; for package in self.get_packages() { - if name == PackageInterface::get_name(package.as_ref()) { + if name == package.get_name() { if constraint.is_none() || constraint.as_ref().unwrap().matches( &SimpleConstraint::new( @@ -314,7 +292,7 @@ impl RepositoryInterface for ArrayRepository { let mut matches: IndexMap<String, SearchResult> = IndexMap::new(); for package in self.get_packages() { - let mut name = PackageInterface::get_name(package.as_ref()).to_string(); + let mut name = package.get_name(); if mode == crate::repository::SEARCH_VENDOR { // PHP: [$name] = explode('/', $name); let parts: Vec<&str> = name.splitn(2, '/').collect(); @@ -329,7 +307,7 @@ impl RepositoryInterface for ArrayRepository { } } - let complete = package.as_any().downcast_ref::<CompletePackage>(); + let complete = package.as_complete(); let fulltext_match = mode == crate::repository::SEARCH_FULLTEXT && complete.is_some() @@ -337,8 +315,12 @@ impl RepositoryInterface for ArrayRepository { ®ex, &format!( "{} {}", - implode(" ", &complete.unwrap().get_keywords()), - complete.unwrap().get_description().unwrap_or("") + implode(" ", &complete.as_ref().unwrap().get_keywords()), + complete + .as_ref() + .unwrap() + .get_description() + .unwrap_or_default() ), ) .unwrap_or(false); @@ -355,14 +337,12 @@ impl RepositoryInterface for ArrayRepository { }, ); } else { - let description = complete.and_then(|c| c.get_description().map(String::from)); - let abandoned = if let Some(c) = complete { + let description = complete.as_ref().and_then(|c| c.get_description()); + let abandoned = if let Some(c) = &complete { if c.is_abandoned() { // PHP: $package->getReplacementPackage() ?: true match c.get_replacement_package() { - Some(s) if !s.is_empty() => { - Some(AbandonedInfo::Replacement(s.to_string())) - } + Some(s) if !s.is_empty() => Some(AbandonedInfo::Replacement(s)), _ => Some(AbandonedInfo::Abandoned), } } else { @@ -374,7 +354,7 @@ impl RepositoryInterface for ArrayRepository { matches.insert( name.clone(), SearchResult { - name: package.get_pretty_name().to_string(), + name: package.get_pretty_name(), description, abandoned, url: None, @@ -389,7 +369,7 @@ impl RepositoryInterface for ArrayRepository { fn has_package(&self, package: &dyn PackageInterface) -> bool { if self.package_map.borrow().is_none() { - let mut map: IndexMap<String, Box<dyn BasePackage>> = IndexMap::new(); + let mut map: IndexMap<String, BasePackageHandle> = IndexMap::new(); for repo_package in self.get_packages() { map.insert(repo_package.get_unique_name(), repo_package); } @@ -407,19 +387,19 @@ impl RepositoryInterface for ArrayRepository { let mut result: IndexMap<String, ProviderInfo> = IndexMap::new(); 'candidates: for candidate in self.get_packages() { - if result.contains_key(PackageInterface::get_name(candidate.as_ref())) { + if result.contains_key(&candidate.get_name()) { continue; } for link in candidate.get_provides().values() { if package_name == link.get_target() { - let complete = candidate.as_any().downcast_ref::<CompletePackage>(); - let description = complete.and_then(|c| c.get_description().map(String::from)); + let complete = candidate.as_complete(); + let description = complete.and_then(|c| c.get_description()); result.insert( - PackageInterface::get_name(candidate.as_ref()).to_string(), + candidate.get_name(), ProviderInfo { - name: PackageInterface::get_name(candidate.as_ref()).to_string(), + name: candidate.get_name(), description, - r#type: candidate.get_type().to_string(), + r#type: candidate.get_type(), }, ); continue 'candidates; @@ -430,7 +410,7 @@ impl RepositoryInterface for ArrayRepository { result } - fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + fn get_packages(&self) -> Vec<BasePackageHandle> { if self.packages.borrow().is_none() { self.initialize(); } @@ -447,13 +427,12 @@ impl RepositoryInterface for ArrayRepository { ); } - // TODO(phase-b): return references rather than cloning the whole vector self.packages .borrow() .as_ref() .unwrap() .iter() - .map(|p| p.clone_box()) + .map(|p| p.clone()) .collect() } diff --git a/crates/shirabe/src/repository/artifact_repository.rs b/crates/shirabe/src/repository/artifact_repository.rs index 674325d..eff8fef 100644 --- a/crates/shirabe/src/repository/artifact_repository.rs +++ b/crates/shirabe/src/repository/artifact_repository.rs @@ -132,7 +132,7 @@ impl ArtifactRepository { fn get_composer_information( &self, file: &Path, - ) -> anyhow::Result<Option<Box<dyn BasePackage>>> { + ) -> anyhow::Result<Option<crate::package::PackageInterfaceHandle>> { let mut json: Option<String> = None; let file_extension = file .extension() diff --git a/crates/shirabe/src/repository/canonical_packages_trait.rs b/crates/shirabe/src/repository/canonical_packages_trait.rs index 8c6e7c2..5f0ecff 100644 --- a/crates/shirabe/src/repository/canonical_packages_trait.rs +++ b/crates/shirabe/src/repository/canonical_packages_trait.rs @@ -1,23 +1,23 @@ //! ref: composer/src/Composer/Repository/CanonicalPackagesTrait.php -use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use indexmap::IndexMap; /// Provides get_canonical_packages() to various repository implementations. pub trait CanonicalPackagesTrait { - fn get_packages(&self) -> Vec<Box<dyn PackageInterface>>; + fn get_packages(&self) -> Vec<PackageInterfaceHandle>; /// Get unique packages (at most one package of each name), with aliases resolved and removed. - fn get_canonical_packages(&self) -> Vec<Box<dyn PackageInterface>> { + fn get_canonical_packages(&self) -> Vec<PackageInterfaceHandle> { let packages = self.get_packages(); // get at most one package of each name, preferring non-aliased ones - let mut packages_by_name: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); + let mut packages_by_name: IndexMap<String, PackageInterfaceHandle> = IndexMap::new(); for package in packages { - let name = package.get_name().to_string(); + let name = package.get_name(); let prefer_replace = packages_by_name .get(&name) - .map(|existing| existing.as_alias_package().is_some()) + .map(|existing| existing.as_alias().is_some()) .unwrap_or(true); if prefer_replace { packages_by_name.insert(name, package); @@ -27,10 +27,10 @@ pub trait CanonicalPackagesTrait { let mut canonical_packages = Vec::new(); // unfold aliased packages - for package in packages_by_name.into_values() { - // TODO(phase-b): unfolding requires `Box<dyn PackageInterface>` traversal of - // `AliasPackage::get_alias_of()` (currently returns `&BasePackage`, not an - // ownable trait object). Push the alias as-is for now. + for mut package in packages_by_name.into_values() { + while let Some(alias) = package.as_alias() { + package = alias.get_alias_of().into(); + } canonical_packages.push(package); } diff --git a/crates/shirabe/src/repository/composer_repository.rs b/crates/shirabe/src/repository/composer_repository.rs index 9752bc0..5c16f8e 100644 --- a/crates/shirabe/src/repository/composer_repository.rs +++ b/crates/shirabe/src/repository/composer_repository.rs @@ -7,7 +7,7 @@ use shirabe_php_shim::{ Countable, InvalidArgumentException, JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE, LogicException, PHP_EOL, PhpMixed, RuntimeException, UnexpectedValueException, extension_loaded, hash, http_build_query, in_array, json_decode, parse_url_all, realpath, - spl_object_hash, strtolower, strtr, urlencode, var_export, + strtolower, strtr, urlencode, var_export, }; use shirabe_semver::compiling_matcher::CompilingMatcher; @@ -22,7 +22,9 @@ use crate::downloader::TransportException; use crate::event_dispatcher::EventDispatcher; use crate::io::IOInterface; use crate::json::JsonFile; +use crate::package::BasePackageHandle; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::base_package::{self, BasePackage}; use crate::package::loader::ArrayLoader; use crate::package::version::StabilityFilter; @@ -119,15 +121,15 @@ pub struct ComposerRepository { #[derive(Debug)] pub enum FindPackageReturn { - Package(Box<dyn BasePackage>), - Packages(Vec<Box<dyn BasePackage>>), + Package(BasePackageHandle), + Packages(Vec<BasePackageHandle>), None, } #[derive(Debug)] pub struct LoadAsyncPackagesResult { pub names_found: IndexMap<String, bool>, - pub packages: IndexMap<String, Box<dyn BasePackage>>, + pub packages: IndexMap<String, BasePackageHandle>, } impl ConfigurableRepositoryInterface for ComposerRepository { @@ -322,7 +324,7 @@ impl ComposerRepository { &mut self, name: String, constraint: PhpMixed, - ) -> anyhow::Result<Option<Box<dyn BasePackage>>> { + ) -> anyhow::Result<Option<BasePackageHandle>> { // this call initializes loadRootServerFile which is needed for the rest below to work let has_providers = self.has_providers()?; @@ -343,7 +345,7 @@ impl ComposerRepository { .map_or(false, |m| m.contains_key(&name)) { let packages = self.what_provides(&name, None, None, IndexMap::new())?; - let packages_vec: Vec<Box<dyn BasePackage>> = packages.into_values().collect(); + let packages_vec: Vec<BasePackageHandle> = packages.into_values().collect(); return Ok( match self.filter_packages(packages_vec, Some(&constraint), true) { FindPackageReturn::Package(p) => Some(p), @@ -372,7 +374,7 @@ impl ComposerRepository { if name == provider_name { let packages = self.what_provides(&provider_name, None, None, IndexMap::new())?; - let packages_vec: Vec<Box<dyn BasePackage>> = packages.into_values().collect(); + let packages_vec: Vec<BasePackageHandle> = packages.into_values().collect(); return Ok( match self.filter_packages(packages_vec, Some(&constraint), true) { FindPackageReturn::Package(p) => Some(p), @@ -396,7 +398,7 @@ impl ComposerRepository { &mut self, name: String, constraint: Option<PhpMixed>, - ) -> anyhow::Result<Vec<Box<dyn BasePackage>>> { + ) -> anyhow::Result<Vec<BasePackageHandle>> { // this call initializes loadRootServerFile which is needed for the rest below to work let has_providers = self.has_providers()?; @@ -415,7 +417,7 @@ impl ComposerRepository { .map_or(false, |m| m.contains_key(&name)) { let packages = self.what_provides(&name, None, None, IndexMap::new())?; - let packages_vec: Vec<Box<dyn BasePackage>> = packages.into_values().collect(); + let packages_vec: Vec<BasePackageHandle> = packages.into_values().collect(); return Ok( match self.filter_packages(packages_vec, constraint.as_ref(), false) { FindPackageReturn::Packages(v) => v, @@ -440,7 +442,7 @@ impl ComposerRepository { if name == provider_name { let packages = self.what_provides(&provider_name, None, None, IndexMap::new())?; - let packages_vec: Vec<Box<dyn BasePackage>> = packages.into_values().collect(); + let packages_vec: Vec<BasePackageHandle> = packages.into_values().collect(); return Ok( match self.filter_packages(packages_vec, constraint.as_ref(), false) { FindPackageReturn::Packages(v) => v, @@ -461,7 +463,7 @@ impl ComposerRepository { fn filter_packages( &self, - packages: Vec<Box<dyn BasePackage>>, + packages: Vec<BasePackageHandle>, constraint: Option<&AnyConstraint>, return_first_match: bool, ) -> FindPackageReturn { @@ -477,7 +479,7 @@ impl ComposerRepository { } let constraint = constraint.unwrap(); - let mut filtered_packages: Vec<Box<dyn BasePackage>> = Vec::new(); + let mut filtered_packages: Vec<BasePackageHandle> = Vec::new(); for package in packages.into_iter() { let pkg_constraint = @@ -499,7 +501,7 @@ impl ComposerRepository { FindPackageReturn::Packages(filtered_packages) } - pub fn get_packages(&mut self) -> anyhow::Result<Vec<Box<dyn BasePackage>>> { + pub fn get_packages(&mut self) -> anyhow::Result<Vec<BasePackageHandle>> { let has_providers = self.has_providers()?; if self.lazy_providers_url.is_some() { @@ -712,7 +714,7 @@ impl ComposerRepository { mut package_name_map: IndexMap<String, Option<AnyConstraint>>, acceptable_stabilities: IndexMap<String, i64>, stability_flags: IndexMap<String, i64>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> anyhow::Result<LoadPackagesResult> { // this call initializes loadRootServerFile which is needed for the rest below to work let has_providers = self.has_providers()?; @@ -724,22 +726,16 @@ impl ComposerRepository { stability_flags, already_loaded, ); - // TODO(phase-b): repository_interface::LoadPackagesResult uses Vec<Box<dyn BasePackage>> - // for `packages`; this fn returns IndexMap. Reconciliation needs structural changes. - let _ = inner_result; - return Ok(LoadPackagesResult { - names_found: Vec::new(), - packages: IndexMap::new(), - }); + return Ok(inner_result); } - let mut packages: IndexMap<String, Box<dyn BasePackage>> = IndexMap::new(); + let mut packages: IndexMap<String, BasePackageHandle> = IndexMap::new(); let mut names_found: IndexMap<String, bool> = IndexMap::new(); if has_providers || self.has_partial_packages()? { let names: Vec<String> = package_name_map.keys().cloned().collect(); for name in names { - let mut matches: IndexMap<String, Box<dyn BasePackage>> = IndexMap::new(); + let mut matches: IndexMap<String, BasePackageHandle> = IndexMap::new(); // if a repo has no providers but only partial packages and the partial packages are missing // then we don't want to call whatProvides as it would try to load from the providers and fail @@ -752,12 +748,11 @@ impl ComposerRepository { continue; } - // TODO(phase-b): Box<dyn PackageInterface> is not Clone; share via Rc let candidates = self.what_provides( &name, Some(&acceptable_stabilities), Some(&stability_flags), - todo!("clone of already_loaded requires sharing Box<dyn PackageInterface>"), + already_loaded.clone(), )?; let constraint = package_name_map .get(&name) @@ -783,13 +778,13 @@ impl ComposerRepository { } }; if matches_constraint { - let hash_c = spl_object_hash(&**candidate); - matches.insert(hash_c, dyn_clone_box(&**candidate)); - if let Some(alias) = candidate.as_alias_package() { + let hash_c = candidate.ptr_id().to_string(); + matches.insert(hash_c, candidate.clone()); + if let Some(alias) = candidate.as_alias() { let aliased = alias.get_alias_of(); - let aliased_hash = spl_object_hash(aliased); + let aliased_hash = aliased.ptr_id().to_string(); if !matches.contains_key(&aliased_hash) { - matches.insert(aliased_hash, dyn_clone_box(aliased)); + matches.insert(aliased_hash, aliased.into()); } } } @@ -797,12 +792,12 @@ impl ComposerRepository { // add aliases of matched packages even if they did not match the constraint for (_uid, candidate) in candidates.iter() { - if let Some(alias) = candidate.as_alias_package() { + if let Some(alias) = candidate.as_alias() { let aliased = alias.get_alias_of(); - let aliased_hash = spl_object_hash(aliased); + let aliased_hash = aliased.ptr_id().to_string(); if matches.contains_key(&aliased_hash) { - let hash_c = spl_object_hash(&**candidate); - matches.insert(hash_c, dyn_clone_box(&**candidate)); + let hash_c = candidate.ptr_id().to_string(); + matches.insert(hash_c, candidate.clone()); } } } @@ -1412,8 +1407,8 @@ impl ComposerRepository { name: &str, acceptable_stabilities: Option<&IndexMap<String, i64>>, stability_flags: Option<&IndexMap<String, i64>>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, - ) -> anyhow::Result<IndexMap<String, Box<dyn BasePackage>>> { + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, + ) -> anyhow::Result<IndexMap<String, BasePackageHandle>> { let mut packages_source: Option<String> = None; let packages: IndexMap<String, PhpMixed>; let loading_partial_package: bool; @@ -1635,7 +1630,7 @@ impl ComposerRepository { loading_partial_package = true; } - let mut result: IndexMap<String, Box<dyn BasePackage>> = IndexMap::new(); + let mut result: IndexMap<String, BasePackageHandle> = IndexMap::new(); let mut versions_to_load: IndexMap<String, IndexMap<String, PhpMixed>> = IndexMap::new(); let packages_inner = packages .get("packages") @@ -1749,15 +1744,13 @@ impl ComposerRepository { let loaded_packages = self.create_packages_flat(versions_to_load_vec, packages_source)?; let uids: Vec<String> = versions_to_load.keys().cloned().collect(); - for (index, mut package) in loaded_packages.into_iter().enumerate() { - package.set_repository_self(); + for (index, package) in loaded_packages.into_iter().enumerate() { + // TODO(phase-c): wire the repository back-reference onto the shared package handle. let uid = &uids[index]; - if let Some(alias) = package.as_alias_package_mut() { - let aliased = alias.get_alias_of_mut(); - aliased.set_repository_self(); - - result.insert(uid.clone(), dyn_clone_box(aliased)); + if let Some(alias) = package.as_alias() { + let aliased = alias.get_alias_of(); + result.insert(uid.clone(), aliased.into()); result.insert(format!("{}-alias", uid), package); } else { result.insert(uid.clone(), package); @@ -1784,10 +1777,11 @@ impl ComposerRepository { } /// Adds a new package to the repository - pub fn add_package(&mut self, mut package: Box<dyn BasePackage>) { - // configurePackageTransportOptions(*package); - self.configure_package_transport_options(&mut *package); - self.inner.add_package(package); + pub fn add_package(&mut self, package: BasePackageHandle) { + self.configure_package_transport_options( + package.as_rc().borrow_mut().as_package_interface_mut(), + ); + self.inner.add_package(package.into()); } /// @param packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only packages matching it will be loaded @@ -1796,11 +1790,11 @@ impl ComposerRepository { mut package_names: IndexMap<String, Option<AnyConstraint>>, acceptable_stabilities: Option<&IndexMap<String, i64>>, stability_flags: Option<&IndexMap<String, i64>>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> anyhow::Result<LoadAsyncPackagesResult> { self.load_root_server_file(None)?; - let mut packages: IndexMap<String, Box<dyn BasePackage>> = IndexMap::new(); + let mut packages: IndexMap<String, BasePackageHandle> = IndexMap::new(); let mut names_found: IndexMap<String, bool> = IndexMap::new(); if self.lazy_providers_url.is_none() { @@ -1980,17 +1974,16 @@ impl ComposerRepository { } } - let loaded_packages: Vec<Box<dyn BasePackage>> = + let loaded_packages: Vec<BasePackageHandle> = ComposerRepository::create_packages_static(versions_to_load, packages_source)?; - for mut package in loaded_packages.into_iter() { - package.set_repository_self(); - let hash_c = spl_object_hash(&*package); - if let Some(alias) = package.as_alias_package_mut() { - let aliased_hash = spl_object_hash(alias.get_alias_of()); + for package in loaded_packages.into_iter() { + // TODO(phase-c): wire the repository back-reference onto the shared package handle. + let hash_c = package.ptr_id().to_string(); + if let Some(alias) = package.as_alias() { + let aliased = alias.get_alias_of(); + let aliased_hash = aliased.ptr_id().to_string(); if !packages.contains_key(&aliased_hash) { - alias.get_alias_of_mut().set_repository_self(); - let aliased_clone = dyn_clone_box(alias.get_alias_of()); - packages.insert(aliased_hash, aliased_clone); + packages.insert(aliased_hash, aliased.into()); } } packages.insert(hash_c, package); @@ -2716,13 +2709,13 @@ impl ComposerRepository { &mut self, packages: Vec<IndexMap<String, PhpMixed>>, source: Option<String>, - ) -> anyhow::Result<Vec<Box<dyn BasePackage>>> { + ) -> anyhow::Result<Vec<BasePackageHandle>> { if packages.is_empty() { return Ok(vec![]); } let mut packages = packages; - let result = (|| -> anyhow::Result<Vec<Box<dyn BasePackage>>> { + let result = (|| -> anyhow::Result<Vec<BasePackageHandle>> { for data in packages.iter_mut() { if !data.contains_key("notification-url") { data.insert( @@ -2737,11 +2730,11 @@ impl ComposerRepository { let package_instances = self.loader.load_packages(packages.clone())?; - let mut results: Vec<Box<dyn BasePackage>> = Vec::new(); - for mut package in package_instances.into_iter() { + let mut results: Vec<BasePackageHandle> = Vec::new(); + for package in package_instances.into_iter() { if let Some(src_type) = package.get_source_type() { if let Some(mirrors) = - self.source_mirrors.as_ref().and_then(|m| m.get(src_type)) + self.source_mirrors.as_ref().and_then(|m| m.get(&src_type)) { let converted: Vec<IndexMap<String, PhpMixed>> = mirrors .iter() @@ -2767,8 +2760,10 @@ impl ComposerRepository { .collect(); package.set_dist_mirrors(Some(converted)); } - self.configure_package_transport_options(&mut *package); - results.push(package); + self.configure_package_transport_options( + package.as_rc().borrow_mut().as_package_interface_mut(), + ); + results.push(package.into()); } Ok(results) })(); @@ -2794,12 +2789,16 @@ impl ComposerRepository { fn create_packages_static( packages: Vec<IndexMap<String, PhpMixed>>, _source: Option<String>, - ) -> anyhow::Result<Vec<Box<dyn BasePackage>>> { + ) -> anyhow::Result<Vec<BasePackageHandle>> { if packages.is_empty() { return Ok(vec![]); } let loader = ArrayLoader::new(Some(VersionParser::new()), true); - Ok(loader.load_packages(packages)?) + Ok(loader + .load_packages(packages)? + .into_iter() + .map(|p| p.into()) + .collect()) } fn fetch_file( @@ -3485,7 +3484,3 @@ fn clone_root_data(rd: &RootData) -> RootData { RootData::Data(d) => RootData::Data(d.clone()), } } - -fn dyn_clone_box(_pkg: &dyn BasePackage) -> Box<dyn BasePackage> { - todo!() -} diff --git a/crates/shirabe/src/repository/composite_repository.rs b/crates/shirabe/src/repository/composite_repository.rs index 6671895..024f89a 100644 --- a/crates/shirabe/src/repository/composite_repository.rs +++ b/crates/shirabe/src/repository/composite_repository.rs @@ -5,8 +5,9 @@ use std::any::Any; use indexmap::IndexMap; use shirabe_semver::constraint::AnyConstraint; -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::{ FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, }; @@ -79,7 +80,7 @@ impl RepositoryInterface for CompositeRepository { &self, name: &str, constraint: FindPackageConstraint, - ) -> Option<Box<dyn BasePackage>> { + ) -> Option<BasePackageHandle> { for repository in &self.repositories { let package = repository.find_package(name, constraint.clone()); if package.is_some() { @@ -93,7 +94,7 @@ impl RepositoryInterface for CompositeRepository { &self, name: &str, constraint: Option<FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { let mut packages = vec![]; for repository in &self.repositories { packages.extend(repository.find_packages(name, constraint.clone())); @@ -101,7 +102,7 @@ impl RepositoryInterface for CompositeRepository { packages } - fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + fn get_packages(&self) -> Vec<BasePackageHandle> { let mut packages = vec![]; for repository in &self.repositories { packages.extend(repository.get_packages()); @@ -114,35 +115,21 @@ impl RepositoryInterface for CompositeRepository { package_name_map: IndexMap<String, Option<AnyConstraint>>, acceptable_stabilities: IndexMap<String, i64>, stability_flags: IndexMap<String, i64>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> LoadPackagesResult { let mut all_packages = IndexMap::new(); let mut all_names_found = vec![]; for repository in &self.repositories { - // TODO(phase-b): manual deep clone since trait objects in maps don't derive Clone. let name_map_cloned: IndexMap<String, Option<AnyConstraint>> = package_name_map .iter() .map(|(k, v)| (k.clone(), v.as_ref().map(|c| c.clone()))) .collect(); - let already_loaded_cloned: IndexMap< - String, - IndexMap<String, Box<dyn PackageInterface>>, - > = already_loaded - .iter() - .map(|(k, inner)| { - let inner_cloned: IndexMap<String, Box<dyn PackageInterface>> = inner - .iter() - .map(|(ik, iv)| (ik.clone(), iv.clone_package_box())) - .collect(); - (k.clone(), inner_cloned) - }) - .collect(); let result = repository.load_packages( name_map_cloned, acceptable_stabilities.clone(), stability_flags.clone(), - already_loaded_cloned, + already_loaded.clone(), ); all_packages.extend(result.packages); all_names_found.extend(result.names_found); diff --git a/crates/shirabe/src/repository/filesystem_repository.rs b/crates/shirabe/src/repository/filesystem_repository.rs index 96c7b61..c8ffb7c 100644 --- a/crates/shirabe/src/repository/filesystem_repository.rs +++ b/crates/shirabe/src/repository/filesystem_repository.rs @@ -16,10 +16,10 @@ use shirabe_php_shim::{ use crate::installed_versions::InstalledVersions; use crate::installer::InstallationManager; use crate::json::JsonFile; -use crate::package::AliasPackage; use crate::package::PackageInterface; -use crate::package::RootAliasPackage; +use crate::package::PackageInterfaceHandle; use crate::package::RootPackageInterface; +use crate::package::RootPackageInterfaceHandle; use crate::package::dumper::ArrayDumper; use crate::package::loader::ArrayLoader; use crate::package::loader::LoaderInterface; @@ -38,7 +38,7 @@ pub struct FilesystemRepository { /// @var bool dump_versions: bool, /// @var ?RootPackageInterface - root_package: Option<Box<dyn RootPackageInterface>>, + root_package: Option<RootPackageInterfaceHandle>, /// @var Filesystem filesystem: std::rc::Rc<std::cell::RefCell<Filesystem>>, /// @var bool|null @@ -53,7 +53,7 @@ impl FilesystemRepository { pub fn new( repository_file: JsonFile, dump_versions: bool, - root_package: Option<Box<dyn RootPackageInterface>>, + root_package: Option<RootPackageInterfaceHandle>, filesystem: Option<std::rc::Rc<std::cell::RefCell<Filesystem>>>, ) -> Result<Self> { let filesystem = filesystem @@ -66,8 +66,7 @@ impl FilesystemRepository { .into()); } Ok(Self { - // TODO(phase-b): WritableArrayRepository::new() needs to be exposed - inner: todo!("WritableArrayRepository::new()"), + inner: WritableArrayRepository::new(vec![])?, file: repository_file, dump_versions, root_package, @@ -220,8 +219,9 @@ impl FilesystemRepository { let mut install_paths: IndexMap<String, Option<String>> = IndexMap::new(); for package in self.inner.get_canonical_packages() { - let mut pkg_array = dumper.dump(&*package); - let path = installation_manager.get_install_path(&*package); + let mut pkg_array = dumper.dump(package.as_rc().borrow().as_package_interface()); + let path = installation_manager + .get_install_path(package.as_rc().borrow().as_package_interface()); let mut install_path: Option<String> = None; if let Some(path_str) = &path { if !path_str.is_empty() { @@ -476,14 +476,13 @@ impl FilesystemRepository { .map(|s| Box::new(PhpMixed::String(s.clone()))) .collect(), )); - let mut packages: Vec<Box<dyn PackageInterface>> = self + let mut packages: Vec<PackageInterfaceHandle> = self .inner .get_packages() .into_iter() - // TODO(phase-b): Box<BasePackage> -> Box<dyn PackageInterface> - .map(|p| todo!("Box<BasePackage> to Box<dyn PackageInterface>")) + .map(|p| p.into()) .collect(); - let mut root_package = match &self.root_package { + let mut current_root: RootPackageInterfaceHandle = match &self.root_package { None => { return Err(LogicException { message: @@ -493,25 +492,27 @@ impl FilesystemRepository { } .into()); } - // TODO(phase-b): clone root_package to push into packages list - Some(_r) => todo!("clone root_package"), + Some(r) => r.clone(), }; // packages[] = $rootPackage = $this->rootPackage; - // TODO(phase-b): track current root_package in mutable variable - let mut current_root: Box<dyn RootPackageInterface> = root_package; - // packages.push(current_root.clone_box()); + packages.push(current_root.clone().into()); - while let Some(_alias) = current_root.as_any().downcast_ref::<RootAliasPackage>() { - current_root = - todo!("RootAliasPackage::get_alias_of() returning Box<dyn RootPackageInterface>"); - // packages.push(current_root.clone_box()); + while let Some(root_alias) = + PackageInterfaceHandle::from(current_root.clone()).as_root_alias_package() + { + current_root = root_alias.get_alias_of().into(); + packages.push(current_root.clone().into()); } let mut versions: IndexMap<String, PhpMixed> = IndexMap::new(); versions.insert( "root".to_string(), PhpMixed::Array( self.dump_root_package( - &*current_root, + current_root + .as_rc() + .borrow() + .as_root_package_interface() + .expect("current_root is a RootPackageInterface"), install_paths, dev_mode, repo_dir, @@ -526,12 +527,16 @@ impl FilesystemRepository { // add real installed packages for package in &packages { - if package.as_any().downcast_ref::<AliasPackage>().is_some() { + if package.as_alias().is_some() { continue; } - let dumped = - self.dump_installed_package(&**package, install_paths, repo_dir, &dev_packages); + let dumped = self.dump_installed_package( + package.as_rc().borrow().as_package_interface(), + install_paths, + repo_dir, + &dev_packages, + ); if let Some(PhpMixed::Array(versions_map)) = versions.get_mut("versions") { versions_map.insert( package.get_name().to_string(), @@ -546,7 +551,7 @@ impl FilesystemRepository { for package in &packages { let is_dev_package = dev_packages .as_array() - .map(|m| m.contains_key(package.get_name())) + .map(|m| m.contains_key(&package.get_name())) .unwrap_or(false); for (_, replace) in package.get_replaces() { // exclude platform replaces as when they are really there we can not check for their presence @@ -587,12 +592,13 @@ impl FilesystemRepository { // add aliases for package in &packages { - let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() else { + let Some(_alias) = package.as_alias() else { continue; }; // TODO(phase-b): mutate nested versions['versions'][name]['aliases'] todo!("append alias->getPrettyVersion() to versions['versions'][name]['aliases']"); - if package.as_root_package_interface().is_some() { + #[allow(unreachable_code)] + if package.as_root().is_some() { // TODO(phase-b): same mutation on versions['root']['aliases'] todo!("append alias->getPrettyVersion() to versions['root']['aliases']"); } @@ -721,9 +727,7 @@ impl FilesystemRepository { repo_dir: &str, dev_packages: &PhpMixed, ) -> IndexMap<String, PhpMixed> { - let data = - // TODO(phase-b): RootPackageInterface trait bound — pass as &dyn PackageInterface - self.dump_installed_package(todo!("package as &dyn PackageInterface"), install_paths, repo_dir, dev_packages); + let data = self.dump_installed_package(package, install_paths, repo_dir, dev_packages); let mut result: IndexMap<String, PhpMixed> = IndexMap::new(); result.insert( diff --git a/crates/shirabe/src/repository/filter_repository.rs b/crates/shirabe/src/repository/filter_repository.rs index a3cb18b..c67e4ff 100644 --- a/crates/shirabe/src/repository/filter_repository.rs +++ b/crates/shirabe/src/repository/filter_repository.rs @@ -1,7 +1,9 @@ //! ref: composer/src/Composer/Repository/FilterRepository.php +use crate::package::BasePackageHandle; use crate::package::PackageInterface; -use crate::package::base_package::{self, BasePackage}; +use crate::package::PackageInterfaceHandle; +use crate::package::base_package::{self}; use crate::repository::{AdvisoryProviderInterface, SecurityAdvisoryResult}; use crate::repository::{ FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, @@ -165,7 +167,7 @@ impl RepositoryInterface for FilterRepository { &self, name: &str, constraint: FindPackageConstraint, - ) -> Option<Box<dyn BasePackage>> { + ) -> Option<BasePackageHandle> { if !self.is_allowed(name) { return None; } @@ -177,7 +179,7 @@ impl RepositoryInterface for FilterRepository { &self, name: &str, constraint: Option<FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { if !self.is_allowed(name) { return Vec::new(); } @@ -190,7 +192,7 @@ impl RepositoryInterface for FilterRepository { mut package_name_map: IndexMap<String, Option<AnyConstraint>>, acceptable_stabilities: IndexMap<String, i64>, stability_flags: IndexMap<String, i64>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> LoadPackagesResult { package_name_map.retain(|name, _| self.is_allowed(name)); @@ -226,10 +228,10 @@ impl RepositoryInterface for FilterRepository { result } - fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + fn get_packages(&self) -> Vec<BasePackageHandle> { let mut result = Vec::new(); for package in self.repo.get_packages() { - if self.is_allowed(PackageInterface::get_name(package.as_ref())) { + if self.is_allowed(&package.get_name()) { result.push(package); } } diff --git a/crates/shirabe/src/repository/installed_array_repository.rs b/crates/shirabe/src/repository/installed_array_repository.rs index b089df6..f0244b6 100644 --- a/crates/shirabe/src/repository/installed_array_repository.rs +++ b/crates/shirabe/src/repository/installed_array_repository.rs @@ -4,8 +4,9 @@ use indexmap::IndexMap; use shirabe_php_shim::Countable; use shirabe_semver::constraint::AnyConstraint; -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::AdvisoryProviderInterface; use crate::repository::InstalledRepositoryInterface; use crate::repository::WritableArrayRepository; @@ -24,7 +25,7 @@ impl InstalledArrayRepository { Self::new_with_packages(Vec::new()) } - pub fn new_with_packages(packages: Vec<Box<dyn PackageInterface>>) -> anyhow::Result<Self> { + pub fn new_with_packages(packages: Vec<PackageInterfaceHandle>) -> anyhow::Result<Self> { Ok(Self { inner: WritableArrayRepository::new(packages)?, }) @@ -56,7 +57,7 @@ impl WritableRepositoryInterface for InstalledArrayRepository { fn add_package( &mut self, - package: Box<dyn crate::package::PackageInterface>, + package: crate::package::PackageInterfaceHandle, ) -> anyhow::Result<()> { todo!() } @@ -68,7 +69,7 @@ impl WritableRepositoryInterface for InstalledArrayRepository { todo!() } - fn get_canonical_packages(&self) -> Vec<Box<dyn crate::package::PackageInterface>> { + fn get_canonical_packages(&self) -> Vec<crate::package::PackageInterfaceHandle> { todo!() } @@ -99,17 +100,17 @@ impl RepositoryInterface for InstalledArrayRepository { &self, _name: &str, _constraint: FindPackageConstraint, - ) -> Option<Box<dyn BasePackage>> { + ) -> Option<BasePackageHandle> { todo!() } fn find_packages( &self, _name: &str, _constraint: Option<FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { todo!() } - fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + fn get_packages(&self) -> Vec<BasePackageHandle> { todo!() } fn load_packages( @@ -117,7 +118,7 @@ impl RepositoryInterface for InstalledArrayRepository { _package_name_map: IndexMap<String, Option<AnyConstraint>>, _acceptable_stabilities: IndexMap<String, i64>, _stability_flags: IndexMap<String, i64>, - _already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + _already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> LoadPackagesResult { todo!() } diff --git a/crates/shirabe/src/repository/installed_filesystem_repository.rs b/crates/shirabe/src/repository/installed_filesystem_repository.rs index 5c6fecf..8a6359a 100644 --- a/crates/shirabe/src/repository/installed_filesystem_repository.rs +++ b/crates/shirabe/src/repository/installed_filesystem_repository.rs @@ -6,9 +6,10 @@ use shirabe_php_shim::Countable; use shirabe_semver::constraint::AnyConstraint; use crate::json::JsonFile; -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::PackageInterface; -use crate::package::RootPackageInterface; +use crate::package::PackageInterfaceHandle; +use crate::package::RootPackageInterfaceHandle; use crate::repository::AdvisoryProviderInterface; use crate::repository::FilesystemRepository; use crate::repository::InstalledRepositoryInterface; @@ -27,7 +28,7 @@ impl InstalledFilesystemRepository { pub fn new( repository_file: JsonFile, dump_versions: bool, - root_package: Option<Box<dyn RootPackageInterface>>, + root_package: Option<RootPackageInterfaceHandle>, filesystem: Option<std::rc::Rc<std::cell::RefCell<Filesystem>>>, ) -> Result<Self> { Ok(Self { @@ -66,7 +67,7 @@ impl WritableRepositoryInterface for InstalledFilesystemRepository { fn add_package( &mut self, - package: Box<dyn crate::package::PackageInterface>, + package: crate::package::PackageInterfaceHandle, ) -> anyhow::Result<()> { todo!() } @@ -78,7 +79,7 @@ impl WritableRepositoryInterface for InstalledFilesystemRepository { todo!() } - fn get_canonical_packages(&self) -> Vec<Box<dyn crate::package::PackageInterface>> { + fn get_canonical_packages(&self) -> Vec<crate::package::PackageInterfaceHandle> { todo!() } @@ -109,17 +110,17 @@ impl RepositoryInterface for InstalledFilesystemRepository { &self, _name: &str, _constraint: FindPackageConstraint, - ) -> Option<Box<dyn BasePackage>> { + ) -> Option<BasePackageHandle> { todo!() } fn find_packages( &self, _name: &str, _constraint: Option<FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { todo!() } - fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + fn get_packages(&self) -> Vec<BasePackageHandle> { todo!() } fn load_packages( @@ -127,7 +128,7 @@ impl RepositoryInterface for InstalledFilesystemRepository { _package_name_map: IndexMap<String, Option<AnyConstraint>>, _acceptable_stabilities: IndexMap<String, i64>, _stability_flags: IndexMap<String, i64>, - _already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + _already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> LoadPackagesResult { todo!() } diff --git a/crates/shirabe/src/repository/installed_repository.rs b/crates/shirabe/src/repository/installed_repository.rs index 5c9e0d7..33356e3 100644 --- a/crates/shirabe/src/repository/installed_repository.rs +++ b/crates/shirabe/src/repository/installed_repository.rs @@ -6,10 +6,10 @@ use shirabe_semver::constraint::AnyConstraint; use shirabe_semver::constraint::MatchAllConstraint; use shirabe_semver::constraint::SimpleConstraint; -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::Link; use crate::package::PackageInterface; -use crate::package::RootPackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::version::VersionParser; use crate::repository::CompositeRepository; use crate::repository::InstalledRepositoryInterface; @@ -26,7 +26,7 @@ pub enum NeedleInput { } pub struct DependentsEntry( - pub Box<dyn BasePackage>, + pub BasePackageHandle, pub Link, pub Option<Vec<DependentsEntry>>, ); @@ -53,7 +53,7 @@ impl InstalledRepository { &self, name: &str, constraint: Option<FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { let name = name.to_lowercase(); let constraint: Option<AnyConstraint> = match constraint { @@ -79,7 +79,7 @@ impl InstalledRepository { .into(), ) { - matches.push(candidate); + matches.push(candidate.clone()); } continue; } @@ -98,7 +98,7 @@ impl InstalledRepository { && (constraint.is_none() || constraint.as_ref().unwrap().matches(link.get_constraint())) { - matches.push(candidate); + matches.push(candidate.clone()); continue 'candidates; } } @@ -124,10 +124,10 @@ impl InstalledRepository { let mut packages_found = packages_found.unwrap_or_else(|| needles.clone()); - let mut root_package: Option<Box<dyn BasePackage>> = None; + let mut root_package: Option<BasePackageHandle> = None; for package in self.inner.get_packages() { - if package.as_root_package_interface().is_some() { - root_package = Some(package); + if package.as_root().is_some() { + root_package = Some(package.clone()); break; } } @@ -150,7 +150,7 @@ impl InstalledRepository { { if packages_in_tree.contains(&link.get_target().to_string()) { results.push(DependentsEntry( - package.clone_box(), + package.clone(), link.clone(), None, )); @@ -169,7 +169,7 @@ impl InstalledRepository { vec![] }; results.push(DependentsEntry( - package.clone_box(), + package.clone(), link.clone(), Some(dependents), )); @@ -180,7 +180,7 @@ impl InstalledRepository { } } - if package.as_root_package_interface().is_some() { + if package.as_root().is_some() { for (k, v) in package.get_dev_requires() { links.entry(k).or_insert(v); } @@ -194,11 +194,7 @@ impl InstalledRepository { .map_or(true, |c| link.get_constraint().matches(c) == !invert); if constraint.is_none() || matches_constraint { if packages_in_tree.contains(&link.get_source().to_string()) { - results.push(DependentsEntry( - package.clone_box(), - link.clone(), - None, - )); + results.push(DependentsEntry(package.clone(), link.clone(), None)); continue; } packages_in_tree.push(link.get_source().to_string()); @@ -214,7 +210,7 @@ impl InstalledRepository { vec![] }; results.push(DependentsEntry( - package.clone_box(), + package.clone(), link.clone(), Some(dependents), )); @@ -232,7 +228,7 @@ impl InstalledRepository { None, ); if link.get_constraint().matches(&version.into()) == invert { - results.push(DependentsEntry(package.clone_box(), link.clone(), None)); + results.push(DependentsEntry(package.clone(), link.clone(), None)); } } } @@ -247,7 +243,7 @@ impl InstalledRepository { None, ); if link.get_constraint().matches(&version.into()) == invert { - results.push(DependentsEntry(package.clone_box(), link.clone(), None)); + results.push(DependentsEntry(package.clone(), link.clone(), None)); } } } @@ -286,7 +282,7 @@ impl InstalledRepository { .map(|p| format!("but {} is installed", p.get_pretty_version())) .unwrap_or_else(|| "but it is missing".to_string()); results.push(DependentsEntry( - package.clone_box(), + package.clone(), Link::new( package.get_name().to_string(), link.get_target().to_string(), @@ -316,7 +312,7 @@ impl InstalledRepository { ) .into(); - if link.get_target() != pkg.get_name() { + if link.get_target() != pkg.get_name().as_str() { let mut replaces_and_provides: IndexMap<String, Link> = pkg.get_replaces(); for (k, v) in pkg.get_provides() { @@ -343,12 +339,12 @@ impl InstalledRepository { && !root_req.get_constraint().matches(link.get_constraint()) { results.push(DependentsEntry( - package.clone_box(), + package.clone(), link.clone(), None, )); results.push(DependentsEntry( - root_pkg.clone_box(), + root_pkg.clone(), root_req.clone(), None, )); @@ -356,13 +352,9 @@ impl InstalledRepository { } } + results.push(DependentsEntry(package.clone(), link.clone(), None)); results.push(DependentsEntry( - package.clone_box(), - link.clone(), - None, - )); - results.push(DependentsEntry( - root_pkg.clone_box(), + root_pkg.clone(), Link::new( root_pkg.get_name().to_string(), link.get_target().to_string(), @@ -376,11 +368,7 @@ impl InstalledRepository { None, )); } else { - results.push(DependentsEntry( - package.clone_box(), - link.clone(), - None, - )); + results.push(DependentsEntry(package.clone(), link.clone(), None)); } } @@ -444,7 +432,7 @@ impl RepositoryInterface for InstalledRepository { &self, name: &str, constraint: FindPackageConstraint, - ) -> Option<Box<dyn BasePackage>> { + ) -> Option<BasePackageHandle> { self.inner.find_package(name, constraint) } @@ -452,11 +440,11 @@ impl RepositoryInterface for InstalledRepository { &self, name: &str, constraint: Option<FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { self.inner.find_packages(name, constraint) } - fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + fn get_packages(&self) -> Vec<BasePackageHandle> { self.inner.get_packages() } @@ -465,7 +453,7 @@ impl RepositoryInterface for InstalledRepository { package_name_map: IndexMap<String, Option<AnyConstraint>>, acceptable_stabilities: IndexMap<String, i64>, stability_flags: IndexMap<String, i64>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> LoadPackagesResult { self.inner.load_packages( package_name_map, diff --git a/crates/shirabe/src/repository/lock_array_repository.rs b/crates/shirabe/src/repository/lock_array_repository.rs index 57abe56..3d00ecf 100644 --- a/crates/shirabe/src/repository/lock_array_repository.rs +++ b/crates/shirabe/src/repository/lock_array_repository.rs @@ -1,7 +1,8 @@ //! ref: composer/src/Composer/Repository/LockArrayRepository.php -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::ArrayRepository; use crate::repository::CanonicalPackagesTrait; use crate::repository::{ @@ -17,7 +18,7 @@ pub struct LockArrayRepository { } impl CanonicalPackagesTrait for LockArrayRepository { - fn get_packages(&self) -> Vec<Box<dyn PackageInterface>> { + fn get_packages(&self) -> Vec<PackageInterfaceHandle> { todo!() } } @@ -43,7 +44,7 @@ impl RepositoryInterface for LockArrayRepository { &self, name: &str, constraint: FindPackageConstraint, - ) -> Option<Box<dyn BasePackage>> { + ) -> Option<BasePackageHandle> { self.inner.find_package(name, constraint) } @@ -51,11 +52,11 @@ impl RepositoryInterface for LockArrayRepository { &self, name: &str, constraint: Option<FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { self.inner.find_packages(name, constraint) } - fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + fn get_packages(&self) -> Vec<BasePackageHandle> { RepositoryInterface::get_packages(&self.inner) } @@ -64,7 +65,7 @@ impl RepositoryInterface for LockArrayRepository { package_name_map: IndexMap<String, Option<AnyConstraint>>, acceptable_stabilities: IndexMap<String, i64>, stability_flags: IndexMap<String, i64>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> LoadPackagesResult { self.inner.load_packages( package_name_map, diff --git a/crates/shirabe/src/repository/package_repository.rs b/crates/shirabe/src/repository/package_repository.rs index 93c7e31..b88de78 100644 --- a/crates/shirabe/src/repository/package_repository.rs +++ b/crates/shirabe/src/repository/package_repository.rs @@ -71,8 +71,7 @@ impl PackageRepository { }))); } }; - // TODO(phase-b): add_package expects Box<dyn PackageInterface>; loader returns Box<dyn BasePackage> - let _ = package_loaded; + self.inner.add_package(package_loaded)?; } Ok(Ok(())) } diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index 0b3ccb1..31760bb 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -17,9 +17,12 @@ use shirabe_semver::constraint::SimpleConstraint; use crate::composer; use crate::composer::ComposerHandle; use crate::package::CompletePackage; +use crate::package::CompletePackageHandle; use crate::package::CompletePackageInterface; +use crate::package::CompletePackageInterfaceHandle; use crate::package::Link; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::version::VersionParser; use crate::platform::HhvmDetector; use crate::platform::Runtime; @@ -46,7 +49,7 @@ pub struct PlatformRepository { pub(crate) inner: ArrayRepository, pub(crate) version_parser: Option<VersionParser>, pub(crate) overrides: IndexMap<String, PlatformOverride>, - pub(crate) disabled_packages: IndexMap<String, Box<dyn CompletePackageInterface>>, + pub(crate) disabled_packages: IndexMap<String, CompletePackageInterfaceHandle>, pub(crate) runtime: Runtime, pub(crate) hhvm_detector: HhvmDetector, } @@ -55,14 +58,14 @@ impl PlatformRepository { pub const PLATFORM_PACKAGE_REGEX: &'static str = "{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer(?:-(?:plugin|runtime)-api)?)$}iD"; pub fn new( - packages: Vec<Box<dyn PackageInterface>>, + packages: Vec<PackageInterfaceHandle>, overrides: IndexMap<String, PhpMixed>, ) -> anyhow::Result<Self> { Self::new4(packages, overrides, None, None) } pub fn new4( - packages: Vec<Box<dyn PackageInterface>>, + packages: Vec<PackageInterfaceHandle>, overrides: IndexMap<String, PhpMixed>, runtime: Option<Runtime>, hhvm_detector: Option<HhvmDetector>, @@ -117,7 +120,7 @@ impl PlatformRepository { self.disabled_packages.contains_key(name) } - pub fn get_disabled_packages(&self) -> &IndexMap<String, Box<dyn CompletePackageInterface>> { + pub fn get_disabled_packages(&self) -> &IndexMap<String, CompletePackageInterfaceHandle> { &self.disabled_packages } @@ -167,7 +170,7 @@ impl PlatformRepository { pretty_version.clone(), ); composer.set_description("Composer package".to_string()); - self.add_package(Box::new(composer))?; + self.add_package(CompletePackageHandle::from_complete_package(composer).into())?; pretty_version = plugin_interface::PLUGIN_API_VERSION.to_string(); version = self @@ -181,7 +184,7 @@ impl PlatformRepository { pretty_version.clone(), ); composer_plugin_api.set_description("The Composer Plugin API".to_string()); - self.add_package(Box::new(composer_plugin_api))?; + self.add_package(CompletePackageHandle::from_complete_package(composer_plugin_api).into())?; pretty_version = composer::RUNTIME_API_VERSION.to_string(); version = self @@ -195,7 +198,9 @@ impl PlatformRepository { pretty_version.clone(), ); composer_runtime_api.set_description("The Composer Runtime API".to_string()); - self.add_package(Box::new(composer_runtime_api))?; + self.add_package( + CompletePackageHandle::from_complete_package(composer_runtime_api).into(), + )?; let php_version_const = self.runtime.get_constant("PHP_VERSION", None); let php_version_str = match &php_version_const { @@ -226,7 +231,7 @@ impl PlatformRepository { let mut php = CompletePackage::new("php".to_string(), version.clone(), pretty_version.clone()); php.set_description("The PHP interpreter".to_string()); - self.add_package(Box::new(php))?; + self.add_package(CompletePackageHandle::from_complete_package(php).into())?; if self .runtime @@ -240,7 +245,7 @@ impl PlatformRepository { pretty_version.clone(), ); phpdebug.set_description("The PHP interpreter, with debugging symbols".to_string()); - self.add_package(Box::new(phpdebug))?; + self.add_package(CompletePackageHandle::from_complete_package(phpdebug).into())?; } if self.runtime.has_constant("PHP_ZTS", None) @@ -256,7 +261,7 @@ impl PlatformRepository { pretty_version.clone(), ); phpzts.set_description("The PHP interpreter, with Zend Thread Safety".to_string()); - self.add_package(Box::new(phpzts))?; + self.add_package(CompletePackageHandle::from_complete_package(phpzts).into())?; } if self @@ -272,7 +277,7 @@ impl PlatformRepository { pretty_version.clone(), ); php64.set_description("The PHP interpreter, 64bit".to_string()); - self.add_package(Box::new(php64))?; + self.add_package(CompletePackageHandle::from_complete_package(php64).into())?; } // The AF_INET6 constant is only defined if ext-sockets is available but @@ -297,7 +302,7 @@ impl PlatformRepository { pretty_version.clone(), ); php_ipv6.set_description("The PHP interpreter, with IPv6 support".to_string()); - self.add_package(Box::new(php_ipv6))?; + self.add_package(CompletePackageHandle::from_complete_package(php_ipv6).into())?; } let loaded_extensions = self.runtime.get_extensions(); @@ -1582,14 +1587,13 @@ impl PlatformRepository { let mut hhvm = CompletePackage::new("hhvm".to_string(), version, pretty_version); hhvm.set_description("The HHVM Runtime (64bit)".to_string()); - self.add_package(Box::new(hhvm))?; + self.add_package(CompletePackageHandle::from_complete_package(hhvm).into())?; } Ok(()) } - pub fn add_package(&mut self, package: Box<dyn PackageInterface>) -> anyhow::Result<()> { - // TODO(phase-b): downcast `package` to CompletePackage; this stub keeps the structure. - if !Self::is_complete_package(package.as_ref()) { + pub fn add_package(&mut self, package: PackageInterfaceHandle) -> anyhow::Result<()> { + if package.as_complete().is_none() { return Err(anyhow::anyhow!(UnexpectedValueException { message: format!( "Expected CompletePackage but got {}", @@ -1599,18 +1603,17 @@ impl PlatformRepository { })); } + let name = package.get_name(); + // Skip if overridden - if self.overrides.contains_key(package.get_name()) { - if matches!( - self.overrides[package.get_name()].version, - PhpMixed::Bool(false) - ) { + if self.overrides.contains_key(&name) { + if matches!(self.overrides[&name].version, PhpMixed::Bool(false)) { self.add_disabled_package_from_pkg(package); return Ok(()); } let overrider = self.inner.find_package( - package.get_name(), + &name, crate::repository::FindPackageConstraint::String("*".to_string()), ); let actual_text = if let Some(ref ov) = overrider { @@ -1622,24 +1625,24 @@ impl PlatformRepository { } else { format!("actual: {}", package.get_pretty_version()) }; - if let Some(_overrider_pkg) = overrider { - // TODO(phase-b): downcast `overrider` to CompletePackageInterface for setDescription - let _ = actual_text; + if let Some(overrider) = overrider { + if let Some(overrider) = overrider.as_complete() { + let description = overrider.get_description().unwrap_or_default(); + overrider.set_description(format!("{}, {}", description, actual_text)); + } } return Ok(()); } // Skip if PHP is overridden and we are adding a php-* package - if self.overrides.contains_key("php") && strpos(package.get_name(), "php-") == Some(0) { + if self.overrides.contains_key("php") && strpos(&name, "php-") == Some(0) { let php_override = PlatformOverride { name: self.overrides["php"].name.clone(), version: self.overrides["php"].version.clone(), }; - let mut overrider = self.add_overridden_package( - &php_override, - Some(package.get_pretty_name().to_string()), - )?; + let mut overrider = + self.add_overridden_package(&php_override, Some(package.get_pretty_name()))?; let actual_text = if package.get_version() == overrider.get_version() { "same as actual".to_string() } else { @@ -1693,22 +1696,32 @@ impl PlatformRepository { Ok(package) } - fn add_disabled_package_from_pkg(&mut self, _package: Box<dyn PackageInterface>) { - // TODO(phase-b): downcast to CompletePackage and call `addDisabledPackage`. + fn add_disabled_package_from_pkg(&mut self, package: PackageInterfaceHandle) { + // PHP type-hints CompletePackage here; the handle is guaranteed complete by add_package. + let complete = package + .as_complete() + .expect("addDisabledPackage expects a CompletePackage"); + self.add_disabled_package(complete); } - fn add_disabled_package(&mut self, mut package: CompletePackage) { - let current_description = package.get_description().unwrap_or("").to_string(); + fn add_disabled_package(&mut self, package: CompletePackageInterfaceHandle) { + let current_description = package.get_description().unwrap_or_default(); package.set_description(format!( "{}. <warning>Package disabled via config.platform</warning>", current_description )); let mut extra: IndexMap<String, PhpMixed> = IndexMap::new(); extra.insert("config.platform".to_string(), PhpMixed::Bool(true)); - package.inner.set_extra(extra); + // NOTE(phase-c): neither PackageInterface nor CompletePackageInterface exposes + // setExtra (PHP defines it on BasePackage), and the handle API does not surface + // it. Disabled packages are always plain CompletePackage objects, so reach the + // concrete Package through the shared Rc. + match &mut *package.as_rc().borrow_mut() { + crate::package::AnyPackage::CompletePackage(p) => p.inner.set_extra(extra), + _ => unreachable!("disabled platform package must be a concrete CompletePackage"), + } - self.disabled_packages - .insert(package.get_name().to_string(), Box::new(package)); + self.disabled_packages.insert(package.get_name(), package); } /// Parses the version and adds a new package to the repository @@ -1768,7 +1781,7 @@ impl PlatformRepository { ext.inner.set_replaces(replaces); } - self.add_package(Box::new(ext))?; + self.add_package(CompletePackageHandle::from_complete_package(ext).into())?; Ok(()) } @@ -1848,7 +1861,7 @@ impl PlatformRepository { lib.inner.set_replaces(replace_links); lib.inner.set_provides(provide_links); - self.add_package(Box::new(lib))?; + self.add_package(CompletePackageHandle::from_complete_package(lib).into())?; Ok(()) } @@ -1945,7 +1958,7 @@ impl crate::repository::RepositoryInterface for PlatformRepository { &self, name: &str, constraint: crate::repository::FindPackageConstraint, - ) -> Option<Box<dyn crate::package::BasePackage>> { + ) -> Option<crate::package::BasePackageHandle> { self.inner.find_package(name, constraint) } @@ -1953,11 +1966,11 @@ impl crate::repository::RepositoryInterface for PlatformRepository { &self, name: &str, constraint: Option<crate::repository::FindPackageConstraint>, - ) -> Vec<Box<dyn crate::package::BasePackage>> { + ) -> Vec<crate::package::BasePackageHandle> { self.inner.find_packages(name, constraint) } - fn get_packages(&self) -> Vec<Box<dyn crate::package::BasePackage>> { + fn get_packages(&self) -> Vec<crate::package::BasePackageHandle> { self.inner.get_packages() } @@ -1966,7 +1979,7 @@ impl crate::repository::RepositoryInterface for PlatformRepository { package_name_map: IndexMap<String, Option<shirabe_semver::constraint::AnyConstraint>>, acceptable_stabilities: IndexMap<String, i64>, stability_flags: IndexMap<String, i64>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, crate::package::PackageInterfaceHandle>>, ) -> crate::repository::LoadPackagesResult { self.inner.load_packages( package_name_map, diff --git a/crates/shirabe/src/repository/repository_interface.rs b/crates/shirabe/src/repository/repository_interface.rs index 4bd1f3e..9cadc48 100644 --- a/crates/shirabe/src/repository/repository_interface.rs +++ b/crates/shirabe/src/repository/repository_interface.rs @@ -1,7 +1,8 @@ //! ref: composer/src/Composer/Repository/RepositoryInterface.php -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::AdvisoryProviderInterface; use indexmap::IndexMap; use shirabe_php_shim::Countable; @@ -24,7 +25,7 @@ impl Clone for FindPackageConstraint { #[derive(Debug)] pub struct LoadPackagesResult { pub names_found: Vec<String>, - pub packages: IndexMap<String, Box<dyn BasePackage>>, + pub packages: IndexMap<String, BasePackageHandle>, } #[derive(Debug, Clone)] @@ -59,22 +60,22 @@ pub trait RepositoryInterface: Countable + std::fmt::Debug { &self, name: &str, constraint: FindPackageConstraint, - ) -> Option<Box<dyn BasePackage>>; + ) -> Option<BasePackageHandle>; fn find_packages( &self, name: &str, constraint: Option<FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>>; + ) -> Vec<BasePackageHandle>; - fn get_packages(&self) -> Vec<Box<dyn BasePackage>>; + fn get_packages(&self) -> Vec<BasePackageHandle>; 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, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> LoadPackagesResult; fn search(&self, query: String, mode: i64, r#type: Option<String>) -> Vec<SearchResult>; diff --git a/crates/shirabe/src/repository/repository_manager.rs b/crates/shirabe/src/repository/repository_manager.rs index e85312a..f432d6e 100644 --- a/crates/shirabe/src/repository/repository_manager.rs +++ b/crates/shirabe/src/repository/repository_manager.rs @@ -7,7 +7,7 @@ use shirabe_semver::constraint::AnyConstraint; use crate::config::Config; use crate::event_dispatcher::EventDispatcher; use crate::io::IOInterface; -use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::FilterRepository; use crate::repository::InstalledRepositoryInterface; use crate::repository::RepositoryInterface; @@ -52,13 +52,13 @@ impl RepositoryManager { &self, name: &str, constraint: &AnyConstraint, - ) -> Option<Box<dyn PackageInterface>> { + ) -> Option<PackageInterfaceHandle> { for repository in &self.repositories { if let Some(package) = repository.find_package( name, crate::repository::FindPackageConstraint::Constraint(constraint.clone()), ) { - return Some(package.clone_package_box()); + return Some(package.clone().into()); } } None @@ -68,8 +68,8 @@ impl RepositoryManager { &self, name: &str, constraint: &AnyConstraint, - ) -> Vec<Box<dyn PackageInterface>> { - let mut packages: Vec<Box<dyn PackageInterface>> = vec![]; + ) -> Vec<PackageInterfaceHandle> { + let mut packages: Vec<PackageInterfaceHandle> = vec![]; for repository in self.get_repositories() { for p in repository.find_packages( name, @@ -77,7 +77,7 @@ impl RepositoryManager { constraint.clone(), )), ) { - packages.push(p.clone_package_box()); + packages.push(p.clone().into()); } } packages diff --git a/crates/shirabe/src/repository/repository_set.rs b/crates/shirabe/src/repository/repository_set.rs index 84159d4..1c9c4a3 100644 --- a/crates/shirabe/src/repository/repository_set.rs +++ b/crates/shirabe/src/repository/repository_set.rs @@ -24,11 +24,10 @@ use crate::downloader::TransportException; use crate::event_dispatcher::EventDispatcher; use crate::io::IOInterface; use crate::io::NullIO; -use crate::package::AliasPackage; -use crate::package::BasePackage; -use crate::package::CompleteAliasPackage; -use crate::package::CompletePackage; -use crate::package::PackageInterface; +use crate::package::AliasPackageHandle; +use crate::package::BasePackageHandle; +use crate::package::CompleteAliasPackageHandle; +use crate::package::PackageInterfaceHandle; use crate::package::version::StabilityFilter; use crate::repository::CompositeRepository; use crate::repository::InstalledRepository; @@ -210,11 +209,11 @@ impl RepositorySet { name: &str, constraint: Option<AnyConstraint>, flags: i64, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { let ignore_stability = (flags & Self::ALLOW_UNACCEPTABLE_STABILITIES) != 0; let load_from_all_repos = (flags & Self::ALLOW_SHADOWED_REPOSITORIES) != 0; - let mut packages: Vec<Vec<Box<dyn BasePackage>>> = vec![]; + let mut packages: Vec<Vec<BasePackageHandle>> = vec![]; if load_from_all_repos { for repository in &self.repositories { // PHP: $repository->findPackages($name, $constraint) ?: [] @@ -260,7 +259,7 @@ impl RepositorySet { } // PHP: $candidates = $packages ? array_merge(...$packages) : []; - let candidates: Vec<Box<dyn BasePackage>> = if !packages.is_empty() { + let candidates: Vec<BasePackageHandle> = if !packages.is_empty() { packages.into_iter().flatten().collect() } else { vec![] @@ -271,9 +270,9 @@ impl RepositorySet { return candidates; } - let mut result: Vec<Box<dyn BasePackage>> = vec![]; + let mut result: Vec<BasePackageHandle> = vec![]; for candidate in candidates { - if self.is_package_acceptable(&candidate.get_names(true), candidate.get_stability()) { + if self.is_package_acceptable(&candidate.get_names(true), &candidate.get_stability()) { result.push(candidate); } } @@ -312,14 +311,14 @@ impl RepositorySet { /// @return ($allowPartialAdvisories is true ? array{advisories: array<string, array<PartialSecurityAdvisory|SecurityAdvisory>>, unreachableRepos: array<string>} : array{advisories: array<string, array<SecurityAdvisory>>, unreachableRepos: array<string>}) pub fn get_matching_security_advisories( &self, - packages: Vec<Box<dyn PackageInterface>>, + packages: Vec<PackageInterfaceHandle>, allow_partial_advisories: bool, ignore_unreachable: bool, ) -> Result<SecurityAdvisoriesResult> { let mut map: IndexMap<String, AnyConstraint> = IndexMap::new(); for package in packages { // ignore root alias versions as they are not actual package versions and should not matter when it comes to vulnerabilities - if let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { + if let Some(alias) = package.as_alias() { if alias.is_root_package_alias() { continue; } @@ -525,42 +524,43 @@ impl RepositorySet { self.locked = true; - let mut packages: Vec<Box<dyn BasePackage>> = vec![]; + let mut packages: Vec<BasePackageHandle> = vec![]; for repository in &self.repositories { for mut package in repository.get_packages() { - let name = package.get_name().to_string(); - let version = package.get_version().to_string(); - packages.push(package.clone_box()); + let name = package.get_name(); + let version = package.get_version(); + packages.push(package.clone()); if let Some(versions) = self.root_aliases.get(&name) { if let Some(alias) = versions.get(&version) { - while let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() - { - package = alias_pkg.get_alias_of().clone_box(); + while let Some(alias_pkg) = package.as_alias() { + package = alias_pkg.get_alias_of().into(); + } + let alias_package: BasePackageHandle = + if let Some(complete) = package.as_complete_package() { + CompleteAliasPackageHandle::new( + complete, + alias.alias_normalized.clone(), + alias.alias.clone(), + ) + .into() + } else { + AliasPackageHandle::new( + package.as_package().unwrap(), + alias.alias_normalized.clone(), + alias.alias.clone(), + ) + .into() + }; + if let Some(alias_handle) = alias_package.as_alias() { + alias_handle.set_root_package_alias(true); } - let alias_package: Box<dyn BasePackage> = if package - .as_any() - .downcast_ref::<CompletePackage>() - .is_some() - { - // TODO(phase-b): construct CompleteAliasPackage and box as BasePackage - todo!( - "new CompleteAliasPackage(package, alias.alias_normalized, alias.alias)" - ) - } else { - // TODO(phase-b): construct AliasPackage and box as BasePackage - todo!("new AliasPackage(package, alias.alias_normalized, alias.alias)") - }; - // TODO(phase-b): set_root_package_alias on the wrapper - todo!("alias_package.set_root_package_alias(true)"); - #[allow(unreachable_code)] packages.push(alias_package); } } } } - // TODO(phase-b): Pool::new signature Ok(Pool::new( packages, vec![], diff --git a/crates/shirabe/src/repository/repository_utils.rs b/crates/shirabe/src/repository/repository_utils.rs index 7fe98b0..51b8673 100644 --- a/crates/shirabe/src/repository/repository_utils.rs +++ b/crates/shirabe/src/repository/repository_utils.rs @@ -12,11 +12,11 @@ pub struct RepositoryUtils; impl RepositoryUtils { pub fn filter_required_packages( - packages: &[Box<dyn crate::package::BasePackage>], + packages: &[crate::package::BasePackageHandle], requirer: &dyn PackageInterface, include_require_dev: bool, - mut bucket: Vec<Box<dyn crate::package::BasePackage>>, - ) -> Vec<Box<dyn crate::package::BasePackage>> { + mut bucket: Vec<crate::package::BasePackageHandle>, + ) -> Vec<crate::package::BasePackageHandle> { let mut requires: IndexMap<String, Link> = requirer.get_requires(); if include_require_dev { requires.extend(requirer.get_dev_requires()); @@ -25,15 +25,9 @@ impl RepositoryUtils { for candidate in packages { for name in candidate.get_names(true) { if requires.contains_key(&name) { - let already_in_bucket = bucket.iter().any(|b| { - std::ptr::eq( - b.as_ref() as *const dyn crate::package::BasePackage as *const (), - candidate.as_ref() as *const dyn crate::package::BasePackage - as *const (), - ) - }); + let already_in_bucket = bucket.iter().any(|b| b.ptr_eq(candidate)); if !already_in_bucket { - bucket.push(candidate.clone_box()); + bucket.push(candidate.clone()); // TODO(phase-b): recursion requires &dyn PackageInterface; cast pending. let _ = (requires.contains_key("dummy"),); } diff --git a/crates/shirabe/src/repository/root_package_repository.rs b/crates/shirabe/src/repository/root_package_repository.rs index 1487f3e..71cd270 100644 --- a/crates/shirabe/src/repository/root_package_repository.rs +++ b/crates/shirabe/src/repository/root_package_repository.rs @@ -1,8 +1,9 @@ //! ref: composer/src/Composer/Repository/RootPackageRepository.php -use crate::package::BasePackage; +use crate::package::BasePackageHandle; use crate::package::PackageInterface; -use crate::package::RootPackageInterface; +use crate::package::PackageInterfaceHandle; +use crate::package::RootPackageInterfaceHandle; use crate::repository::ArrayRepository; use crate::repository::{ProviderInfo, RepositoryInterface, SearchResult}; use indexmap::IndexMap; @@ -13,13 +14,9 @@ pub struct RootPackageRepository { } impl RootPackageRepository { - pub fn new(package: Box<dyn RootPackageInterface>) -> Self { + pub fn new(package: RootPackageInterfaceHandle) -> Self { Self { - // TODO(phase-b): RootPackageInterface vs BasePackage upcast + ArrayRepository::new error - inner: ArrayRepository::new(vec![todo!( - "convert Box<dyn RootPackageInterface> to Box<dyn BasePackage>" - )]) - .expect("invalid root package"), + inner: ArrayRepository::new(vec![package.into()]).expect("invalid root package"), } } @@ -43,7 +40,7 @@ impl RepositoryInterface for RootPackageRepository { &self, name: &str, constraint: crate::repository::FindPackageConstraint, - ) -> Option<Box<dyn BasePackage>> { + ) -> Option<BasePackageHandle> { self.inner.find_package(name, constraint) } @@ -51,11 +48,11 @@ impl RepositoryInterface for RootPackageRepository { &self, name: &str, constraint: Option<crate::repository::FindPackageConstraint>, - ) -> Vec<Box<dyn BasePackage>> { + ) -> Vec<BasePackageHandle> { self.inner.find_packages(name, constraint) } - fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + fn get_packages(&self) -> Vec<BasePackageHandle> { self.inner.get_packages() } @@ -64,7 +61,7 @@ impl RepositoryInterface for RootPackageRepository { package_name_map: IndexMap<String, Option<shirabe_semver::constraint::AnyConstraint>>, acceptable_stabilities: IndexMap<String, i64>, stability_flags: IndexMap<String, i64>, - already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>, ) -> crate::repository::LoadPackagesResult { self.inner.load_packages( package_name_map, diff --git a/crates/shirabe/src/repository/vcs_repository.rs b/crates/shirabe/src/repository/vcs_repository.rs index 1ae7e40..a256fb8 100644 --- a/crates/shirabe/src/repository/vcs_repository.rs +++ b/crates/shirabe/src/repository/vcs_repository.rs @@ -15,7 +15,6 @@ use crate::config::Config; use crate::downloader::TransportException; use crate::event_dispatcher::EventDispatcher; use crate::io::IOInterface; -use crate::package::BasePackage; use crate::package::loader::ArrayLoader; use crate::package::loader::InvalidPackageException; use crate::package::loader::LoaderInterface; @@ -387,9 +386,7 @@ impl VcsRepository { )?; match cached_package { CachedPackageResult::Package(pkg) => { - // TODO(phase-b): trait upcast Box<dyn BasePackage> -> Box<dyn PackageInterface> - let pkg_pi: Box<dyn crate::package::PackageInterface> = pkg.clone_package_box(); - self.inner.add_package(pkg_pi)?; + self.inner.add_package(pkg)?; continue; } CachedPackageResult::Missing => { @@ -541,10 +538,7 @@ impl VcsRepository { let driver = self.driver.as_ref().unwrap(); let processed = self.pre_process(&**driver, data, &identifier)?; let loaded = self.loader.as_ref().unwrap().load(processed, None)?; - // TODO(phase-b): trait upcast Box<dyn BasePackage> -> Box<dyn PackageInterface> - let loaded_pi: Box<dyn crate::package::PackageInterface> = - loaded.clone_package_box(); - self.inner.add_package(loaded_pi)?; + self.inner.add_package(loaded)?; Ok(()) })(); if let Err(e) = result { @@ -662,9 +656,7 @@ impl VcsRepository { )?; match cached_package { CachedPackageResult::Package(pkg) => { - // TODO(phase-b): trait upcast Box<dyn BasePackage> -> Box<dyn PackageInterface> - let pkg_pi: Box<dyn crate::package::PackageInterface> = pkg.clone_package_box(); - self.inner.add_package(pkg_pi)?; + self.inner.add_package(pkg)?; continue; } CachedPackageResult::Missing => { @@ -727,10 +719,7 @@ impl VcsRepository { ); } } - // TODO(phase-b): trait upcast Box<dyn BasePackage> -> Box<dyn PackageInterface> - let package_pi: Box<dyn crate::package::PackageInterface> = - package.clone_package_box(); - self.inner.add_package(package_pi)?; + self.inner.add_package(package)?; Ok(()) })(); if let Err(e) = result { @@ -995,5 +984,5 @@ impl VcsRepository { enum CachedPackageResult { None, Missing, - Package(Box<dyn BasePackage>), + Package(crate::package::PackageInterfaceHandle), } diff --git a/crates/shirabe/src/repository/writable_array_repository.rs b/crates/shirabe/src/repository/writable_array_repository.rs index 663ab52..ebe70c3 100644 --- a/crates/shirabe/src/repository/writable_array_repository.rs +++ b/crates/shirabe/src/repository/writable_array_repository.rs @@ -14,7 +14,7 @@ pub struct WritableArrayRepository { } impl WritableArrayRepository { - pub fn new(packages: Vec<Box<dyn crate::package::PackageInterface>>) -> Result<Self> { + pub fn new(packages: Vec<crate::package::PackageInterfaceHandle>) -> Result<Self> { Ok(Self { inner: ArrayRepository::new(packages)?, dev_package_names: Vec::new(), @@ -48,10 +48,7 @@ impl WritableArrayRepository { self.dev_mode = None; } - pub fn add_package( - &mut self, - package: Box<dyn crate::package::PackageInterface>, - ) -> Result<()> { + pub fn add_package(&mut self, package: crate::package::PackageInterfaceHandle) -> Result<()> { self.inner.add_package(package) } @@ -66,14 +63,13 @@ impl WritableArrayRepository { Ok(()) } - pub fn get_canonical_packages(&self) -> Vec<Box<dyn crate::package::PackageInterface>> { + pub fn get_canonical_packages(&self) -> Vec<crate::package::PackageInterfaceHandle> { // TODO(phase-b): delegate to inner once it exposes get_canonical_packages Vec::new() } - pub fn get_packages(&self) -> Vec<Box<dyn crate::package::PackageInterface>> { - // TODO(phase-b): delegate to inner ArrayRepository::get_packages - Vec::new() + pub fn get_packages(&self) -> Vec<crate::package::BasePackageHandle> { + crate::repository::RepositoryInterface::get_packages(&self.inner) } pub fn get_repo_name(&self) -> String { diff --git a/crates/shirabe/src/repository/writable_repository_interface.rs b/crates/shirabe/src/repository/writable_repository_interface.rs index 7094211..b5b1e35 100644 --- a/crates/shirabe/src/repository/writable_repository_interface.rs +++ b/crates/shirabe/src/repository/writable_repository_interface.rs @@ -2,17 +2,18 @@ use crate::installer::InstallationManager; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::repository::RepositoryInterface; use anyhow::Result; pub trait WritableRepositoryInterface: RepositoryInterface { fn write(&mut self, dev_mode: bool, installation_manager: &InstallationManager) -> Result<()>; - fn add_package(&mut self, package: Box<dyn PackageInterface>) -> Result<()>; + fn add_package(&mut self, package: PackageInterfaceHandle) -> Result<()>; fn remove_package(&mut self, package: &dyn PackageInterface) -> Result<()>; - fn get_canonical_packages(&self) -> Vec<Box<dyn PackageInterface>>; + fn get_canonical_packages(&self) -> Vec<PackageInterfaceHandle>; fn reload(&mut self); diff --git a/crates/shirabe/src/util/package_sorter.rs b/crates/shirabe/src/util/package_sorter.rs index 185f595..9db56f5 100644 --- a/crates/shirabe/src/util/package_sorter.rs +++ b/crates/shirabe/src/util/package_sorter.rs @@ -1,20 +1,19 @@ //! ref: composer/src/Composer/Util/PackageSorter.php -use std::any::Any; - use indexmap::IndexMap; use shirabe_php_shim::{strnatcasecmp, version_compare}; use crate::package::Link; use crate::package::PackageInterface; +use crate::package::PackageInterfaceHandle; use crate::package::RootPackage; pub struct PackageSorter; impl PackageSorter { pub fn get_most_current_version( - packages: Vec<Box<dyn PackageInterface>>, - ) -> Option<Box<dyn PackageInterface>> { + packages: Vec<PackageInterfaceHandle>, + ) -> Option<PackageInterfaceHandle> { if packages.is_empty() { return None; } @@ -25,7 +24,7 @@ impl PackageSorter { if candidate.is_default_branch() { return Some(candidate); } - if version_compare(highest.get_version(), candidate.get_version(), "<") { + if version_compare(&highest.get_version(), &candidate.get_version(), "<") { highest = candidate; } } @@ -34,16 +33,16 @@ impl PackageSorter { } pub fn sort_packages_alphabetically( - mut packages: Vec<Box<dyn PackageInterface>>, - ) -> Vec<Box<dyn PackageInterface>> { - packages.sort_by_key(|p| p.get_name().to_string()); + mut packages: Vec<PackageInterfaceHandle>, + ) -> Vec<PackageInterfaceHandle> { + packages.sort_by_key(|p| p.get_name()); packages } pub fn sort_packages( - packages: Vec<Box<dyn PackageInterface>>, + packages: Vec<PackageInterfaceHandle>, weights: IndexMap<String, i64>, - ) -> Vec<Box<dyn PackageInterface>> { + ) -> Vec<PackageInterfaceHandle> { let mut usage_list: IndexMap<String, Vec<String>> = IndexMap::new(); for package in &packages { @@ -58,7 +57,7 @@ impl PackageSorter { usage_list .entry(target) .or_default() - .push(package.get_name().to_string()); + .push(package.get_name()); } } @@ -84,7 +83,7 @@ impl PackageSorter { } }); - let mut packages: Vec<Option<Box<dyn PackageInterface>>> = + let mut packages: Vec<Option<PackageInterfaceHandle>> = packages.into_iter().map(Some).collect(); weighted_packages .into_iter() |
