diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-25 00:58:20 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-25 00:58:36 +0900 |
| commit | 1921f173ea219cb4b25847294d2d3fa465550fbb (patch) | |
| tree | 0d30486a2cb9a0c106e5d5827be3f655c60cd871 /crates/shirabe/src/command | |
| parent | dbdecaf5a1c54a876b7ee0153d58dd39b1080f97 (diff) | |
| download | php-shirabe-1921f173ea219cb4b25847294d2d3fa465550fbb.tar.gz php-shirabe-1921f173ea219cb4b25847294d2d3fa465550fbb.tar.zst php-shirabe-1921f173ea219cb4b25847294d2d3fa465550fbb.zip | |
refactor(package): introduce Rc<RefCell<_>> handles for packages
PHP packages have reference semantics, so introduce shared-ownership
handles over an AnyPackage enum (PackageInterfaceHandle and friends)
and replace Box<dyn PackageInterface> throughout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/command')
18 files changed, 304 insertions, 315 deletions
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, |
