#![allow(clippy::doc_markdown)]
use std::collections::HashMap;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeprecationStatus {
Stable,
PendingDeprecation,
Deprecated,
Removed,
}
impl std::fmt::Display for DeprecationStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Stable => write!(f, "Stable"),
Self::PendingDeprecation => write!(f, "Pending Deprecation"),
Self::Deprecated => write!(f, "Deprecated"),
Self::Removed => write!(f, "Removed"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum StabilityLevel {
Experimental,
Unstable,
Stable,
}
impl std::fmt::Display for StabilityLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Experimental => write!(f, "Experimental"),
Self::Unstable => write!(f, "Unstable"),
Self::Stable => write!(f, "Stable"),
}
}
}
#[derive(Debug, Clone)]
pub struct DeprecationInfo {
pub name: String,
pub status: DeprecationStatus,
pub deprecated_since: Option<String>,
pub removal_version: Option<String>,
pub reason: String,
pub migration_guide: String,
pub alternative: Option<String>,
pub doc_link: Option<String>,
}
impl DeprecationInfo {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
status: DeprecationStatus::Deprecated,
deprecated_since: None,
removal_version: None,
reason: String::new(),
migration_guide: String::new(),
alternative: None,
doc_link: None,
}
}
#[must_use]
pub const fn status(mut self, status: DeprecationStatus) -> Self {
self.status = status;
self
}
#[must_use]
pub fn since(mut self, version: impl Into<String>) -> Self {
self.deprecated_since = Some(version.into());
self
}
#[must_use]
pub fn removal(mut self, version: impl Into<String>) -> Self {
self.removal_version = Some(version.into());
self
}
#[must_use]
pub fn reason(mut self, reason: impl Into<String>) -> Self {
self.reason = reason.into();
self
}
#[must_use]
pub fn guide(mut self, guide: impl Into<String>) -> Self {
self.migration_guide = guide.into();
self
}
#[must_use]
pub fn alternative(mut self, alt: impl Into<String>) -> Self {
self.alternative = Some(alt.into());
self
}
#[must_use]
pub fn doc(mut self, link: impl Into<String>) -> Self {
self.doc_link = Some(link.into());
self
}
#[allow(clippy::format_push_string)] pub fn warning_message(&self) -> String {
let mut msg = format!("'{}' is {}", self.name, self.status);
if let Some(ref since) = self.deprecated_since {
msg.push_str(&format!(" since v{since}"));
}
if let Some(ref removal) = self.removal_version {
msg.push_str(&format!(", scheduled for removal in v{removal}"));
}
if !self.reason.is_empty() {
msg.push_str(&format!(". Reason: {}", self.reason));
}
if let Some(ref alt) = self.alternative {
msg.push_str(&format!(". Use '{alt}' instead"));
}
msg
}
}
impl std::fmt::Display for DeprecationInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.warning_message())
}
}
#[derive(Debug, Clone)]
pub struct ModuleStability {
pub module: String,
pub stability: StabilityLevel,
pub notes: Option<String>,
}
impl ModuleStability {
pub fn new(module: impl Into<String>, stability: StabilityLevel) -> Self {
Self {
module: module.into(),
stability,
notes: None,
}
}
#[must_use]
pub fn notes(mut self, notes: impl Into<String>) -> Self {
self.notes = Some(notes.into());
self
}
}
static DEPRECATION_REGISTRY: OnceLock<DeprecationRegistry> = OnceLock::new();
struct DeprecationRegistry {
items: HashMap<String, DeprecationInfo>,
modules: HashMap<String, ModuleStability>,
}
impl DeprecationRegistry {
fn new() -> Self {
let mut registry = Self {
items: HashMap::new(),
modules: HashMap::new(),
};
registry.register_known_deprecations();
registry.register_module_stability();
registry
}
fn register_known_deprecations(&mut self) {
self.items.insert(
"symengine_experimental".to_string(),
DeprecationInfo::new("symengine_experimental")
.status(DeprecationStatus::PendingDeprecation)
.since("0.1.0")
.removal("0.2.0")
.reason("SymEngine integration API is still experimental and may change")
.guide(
"Review updated API before upgrading to 0.2.0. \
Check CHANGELOG.md for migration instructions.",
),
);
}
fn register_module_stability(&mut self) {
self.modules.insert(
"quantrs2::core".to_string(),
ModuleStability::new("quantrs2::core", StabilityLevel::Stable),
);
self.modules.insert(
"quantrs2::circuit".to_string(),
ModuleStability::new("quantrs2::circuit", StabilityLevel::Stable),
);
self.modules.insert(
"quantrs2::sim".to_string(),
ModuleStability::new("quantrs2::sim", StabilityLevel::Stable),
);
self.modules.insert(
"quantrs2::device".to_string(),
ModuleStability::new("quantrs2::device", StabilityLevel::Unstable)
.notes("Hardware provider APIs may change based on provider updates"),
);
self.modules.insert(
"quantrs2::ml".to_string(),
ModuleStability::new("quantrs2::ml", StabilityLevel::Unstable)
.notes("Machine learning APIs are stabilizing but may change in minor versions"),
);
self.modules.insert(
"quantrs2::anneal".to_string(),
ModuleStability::new("quantrs2::anneal", StabilityLevel::Stable),
);
self.modules.insert(
"quantrs2::tytan".to_string(),
ModuleStability::new("quantrs2::tytan", StabilityLevel::Stable),
);
self.modules.insert(
"quantrs2::symengine".to_string(),
ModuleStability::new("quantrs2::symengine", StabilityLevel::Experimental)
.notes("SymEngine integration is experimental and API may change significantly"),
);
self.modules.insert(
"quantrs2::config".to_string(),
ModuleStability::new("quantrs2::config", StabilityLevel::Stable),
);
self.modules.insert(
"quantrs2::diagnostics".to_string(),
ModuleStability::new("quantrs2::diagnostics", StabilityLevel::Stable),
);
self.modules.insert(
"quantrs2::utils".to_string(),
ModuleStability::new("quantrs2::utils", StabilityLevel::Stable),
);
self.modules.insert(
"quantrs2::testing".to_string(),
ModuleStability::new("quantrs2::testing", StabilityLevel::Stable),
);
self.modules.insert(
"quantrs2::bench".to_string(),
ModuleStability::new("quantrs2::bench", StabilityLevel::Stable),
);
}
}
fn get_registry() -> &'static DeprecationRegistry {
DEPRECATION_REGISTRY.get_or_init(DeprecationRegistry::new)
}
pub fn is_deprecated(name: &str) -> bool {
get_registry().items.get(name).is_some_and(|info| {
matches!(
info.status,
DeprecationStatus::Deprecated | DeprecationStatus::PendingDeprecation
)
})
}
pub fn get_migration_info(name: &str) -> Option<&'static DeprecationInfo> {
get_registry().items.get(name)
}
pub fn get_module_stability(module: &str) -> Option<&'static ModuleStability> {
get_registry().modules.get(module)
}
pub fn list_deprecated() -> impl Iterator<Item = &'static DeprecationInfo> {
get_registry().items.values().filter(|info| {
matches!(
info.status,
DeprecationStatus::Deprecated | DeprecationStatus::PendingDeprecation
)
})
}
pub fn list_modules() -> impl Iterator<Item = &'static ModuleStability> {
get_registry().modules.values()
}
pub fn has_pending_deprecations() -> bool {
get_registry()
.items
.values()
.any(|info| info.status == DeprecationStatus::PendingDeprecation)
}
pub fn print_deprecation_warnings() {
for info in list_deprecated() {
eprintln!("[DEPRECATION WARNING] {}", info.warning_message());
}
}
pub fn migration_report() -> String {
use std::fmt::Write;
let mut report = String::from("# QuantRS2 Migration Report\n\n");
report.push_str("## Module Stability\n\n");
report.push_str("| Module | Stability | Notes |\n");
report.push_str("|--------|-----------|-------|\n");
for stability in list_modules() {
let notes = stability.notes.as_deref().unwrap_or("-");
let _ = writeln!(
report,
"| {} | {} | {} |",
stability.module, stability.stability, notes
);
}
report.push_str("\n## Deprecations\n\n");
let deprecated: Vec<_> = list_deprecated().collect();
if deprecated.is_empty() {
report.push_str("No deprecated items in current version.\n");
} else {
for info in deprecated {
let _ = writeln!(report, "### {}\n", info.name);
let _ = writeln!(report, "**Status**: {}", info.status);
if let Some(ref since) = info.deprecated_since {
let _ = writeln!(report, "**Deprecated Since**: v{since}");
}
if let Some(ref removal) = info.removal_version {
let _ = writeln!(report, "**Removal Version**: v{removal}");
}
if !info.reason.is_empty() {
let _ = writeln!(report, "\n**Reason**: {}", info.reason);
}
if let Some(ref alt) = info.alternative {
let _ = writeln!(report, "\n**Alternative**: `{alt}`");
}
if !info.migration_guide.is_empty() {
let _ = writeln!(report, "\n**Migration Guide**:\n{}", info.migration_guide);
}
report.push('\n');
}
}
report
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deprecation_status_display() {
assert_eq!(format!("{}", DeprecationStatus::Stable), "Stable");
assert_eq!(
format!("{}", DeprecationStatus::PendingDeprecation),
"Pending Deprecation"
);
assert_eq!(format!("{}", DeprecationStatus::Deprecated), "Deprecated");
assert_eq!(format!("{}", DeprecationStatus::Removed), "Removed");
}
#[test]
fn test_stability_level_display() {
assert_eq!(format!("{}", StabilityLevel::Experimental), "Experimental");
assert_eq!(format!("{}", StabilityLevel::Unstable), "Unstable");
assert_eq!(format!("{}", StabilityLevel::Stable), "Stable");
}
#[test]
fn test_deprecation_info_builder() {
let info = DeprecationInfo::new("test_api")
.since("0.1.0")
.removal("0.2.0")
.reason("Testing")
.alternative("new_api")
.guide("Use new_api instead");
assert_eq!(info.name, "test_api");
assert_eq!(info.deprecated_since, Some("0.1.0".to_string()));
assert_eq!(info.removal_version, Some("0.2.0".to_string()));
assert_eq!(info.alternative, Some("new_api".to_string()));
}
#[test]
fn test_warning_message() {
let info = DeprecationInfo::new("old_function")
.since("0.1.0")
.removal("0.2.0")
.reason("Replaced")
.alternative("new_function");
let msg = info.warning_message();
assert!(msg.contains("old_function"));
assert!(msg.contains("0.1.0"));
assert!(msg.contains("0.2.0"));
assert!(msg.contains("new_function"));
}
#[test]
fn test_registry_initialization() {
let _ = get_registry();
}
#[test]
fn test_module_stability() {
let stability = get_module_stability("quantrs2::core");
assert!(stability.is_some());
let stability = stability.expect("quantrs2::core module stability should exist");
assert_eq!(stability.stability, StabilityLevel::Stable);
}
#[test]
fn test_list_modules() {
let modules: Vec<_> = list_modules().collect();
assert!(!modules.is_empty());
let module_names: Vec<_> = modules.iter().map(|m| m.module.as_str()).collect();
assert!(module_names.contains(&"quantrs2::core"));
assert!(module_names.contains(&"quantrs2::config"));
}
#[test]
fn test_migration_report() {
let report = migration_report();
assert!(report.contains("Migration Report"));
assert!(report.contains("Module Stability"));
}
#[test]
fn test_deprecation_status_ordering() {
assert!(DeprecationStatus::Stable < DeprecationStatus::PendingDeprecation);
assert!(DeprecationStatus::PendingDeprecation < DeprecationStatus::Deprecated);
assert!(DeprecationStatus::Deprecated < DeprecationStatus::Removed);
}
#[test]
fn test_stability_level_ordering() {
assert!(StabilityLevel::Experimental < StabilityLevel::Unstable);
assert!(StabilityLevel::Unstable < StabilityLevel::Stable);
}
}