//! Version-availability data for PHP's native symbols.
//!
//! Answers one question per symbol: in which PHP versions was it available,
//! deprecated and removed? See the crate README for the model.
//!
//! Coverage walks the ladder PHP 7.4 -> 8.0 -> 8.1 -> 8.2 -> 8.3 -> 8.4 -> 8.5.
//! A symbol present at or before the 7.4 floor carries `added: None`; one
//! introduced later carries its real version; a deprecated-but-present symbol
//! stays in the table and is flagged via `deprecated`; a symbol removed at or
//! before 7.4 is excluded entirely.
//!
//! Native function, constant, class (interface, enum) and method availability,
//! deprecation and removal, plus an editorial deprecation `replacement`. The
//! tables under `generated/` are machine-written from pinned phpstorm-stubs data,
//! cross-checked against PHPCompatibility for functions, constants and classes
//! (see `tools/regenerate` and `NOTICE`). Methods have no PHPCompatibility sniff,
//! so their availability rests on the single authoritative stub `@since`/`@removed`.
//! Constant names are case-sensitive; function, class and method names are not.
//!
//! Each `*_availability` lookup returns an [`Availability`]; the `is_*` helpers
//! are thin wrappers. The bulk iterators [`functions`], [`constants`], [`classes`]
//! and [`methods`] yield every row, and [`is_core_extension`] flags whether an
//! [`Availability::extension`] is one a default PHP build ships (an editorial
//! default-build assumption, not a runtime guarantee).
//!
//! On top of the per-symbol lookups, a query layer serves version-aware tools.
//! [`compatibility_issue_at`] and [`compatibility_report_at`] classify a set of
//! used symbols against a target version (via [`SymbolRef`]) and report a viable
//! [`CompatibilityWindow`]. [`callable_method_availability`] resolves a method
//! through the class hierarchy (parents and interfaces), beside the
//! declared-only [`method_availability`]. [`function_changes_between`] and the
//! `*_added_in` / `*_deprecated_as_of` / `*_removed_by` iterators give
//! per-version diffs and sets; [`resolve_function`] and its kin return the
//! canonical table key; [`extensions`] and [`source_manifest`] expose the
//! extension inventory and the pinned data provenance; and
//! [`availability_provenance`] reports how each kind of fact is sourced. An
//! optional `serde` feature (off by default) serialises the public types.

#![forbid(unsafe_code)]
// cargo-llvm-cov sets cfg(coverage_nightly) on its nightly run; there the
// `coverage(off)` attribute on the test modules excludes test scaffolding from
// the coverage denominator. The attribute is inert on stable, so the MSRV holds.
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]

mod availability;
mod changes;
mod classes;
mod compatibility;
mod constants;
mod extension;
mod generated;
mod lookup;
mod provenance;
mod query;
mod sources;
mod symbols;
mod version;

pub use availability::{Availability, SymbolKind};
pub use changes::{
    class_changes_between, constant_changes_between, function_changes_between,
    method_changes_between, symbol_changes_between, ClassChange, ConstantChange, FunctionChange,
    MethodChange, SymbolChange, SymbolChangeKind, VersionRangeError,
};
pub use classes::{
    callable_method_availability, class_availability, classes, classes_added_in,
    classes_available_at, classes_deprecated_as_of, classes_removed_by, is_callable_method,
    is_callable_method_available, is_callable_method_deprecated_at, is_class, is_class_available,
    is_class_deprecated_at, is_method, is_method_available, is_method_deprecated_at,
    method_availability, methods, methods_added_in, methods_available_at, methods_deprecated_as_of,
    methods_removed_by, resolve_class, resolve_method, CallableMethod,
};
pub use compatibility::{
    compatibility_issue_at, compatibility_report_at, compatibility_window, CompatibilityIssue,
    CompatibilityReport, CompatibilityWindow,
};
pub use constants::{
    constant_availability, constants, constants_added_in, constants_available_at,
    constants_deprecated_as_of, constants_removed_by, is_constant, is_constant_available,
    is_constant_deprecated_at, resolve_constant,
};
pub use extension::{
    classes_in_extension, constants_in_extension, extension_requirement, extension_requirements,
    extensions, functions_in_extension, is_core_extension, methods_in_extension, symbol_extension,
    ExtensionRequirement,
};
pub use provenance::{availability_provenance, AvailabilityField, FactConfidence, FieldProvenance};
pub use query::{
    function_availability, functions, functions_added_in, functions_available_at,
    functions_deprecated_as_of, functions_removed_by, is_function, is_function_available,
    is_function_deprecated_at, resolve_function,
};
pub use sources::{
    coverage_range, source_manifest, supported_versions, CoverageRange, SourceInfo, SourceManifest,
    SourceRole,
};
pub use symbols::{ResolvedSymbol, SymbolRef};
pub use version::{ParsePhpVersionError, PhpVersion};

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
    use super::*;

    #[test]
    fn from_str_parses_full_and_partial_forms() {
        assert_eq!("8".parse::<PhpVersion>(), Ok(PhpVersion::new(8, 0, 0)));
        assert_eq!("8.1".parse::<PhpVersion>(), Ok(PhpVersion::new(8, 1, 0)));
        assert_eq!("8.1.3".parse::<PhpVersion>(), Ok(PhpVersion::new(8, 1, 3)));
    }

    #[test]
    fn from_str_rejects_a_fourth_component() {
        assert_eq!(
            "8.1.3.4".parse::<PhpVersion>(),
            Err(ParsePhpVersionError::Shape)
        );
    }

    #[test]
    fn from_str_rejects_non_numeric_and_overflowing_components() {
        // 256 is outside u8 range: it must error, never wrap to 0.
        assert!("256".parse::<PhpVersion>().is_err());
        assert!("8.x".parse::<PhpVersion>().is_err());
    }

    #[test]
    fn versions_order_by_major_then_minor_then_patch() {
        assert!(PhpVersion::minor(7, 4) < PhpVersion::minor(8, 0));
        assert!(PhpVersion::minor(8, 0) < PhpVersion::minor(8, 1));
        assert!(PhpVersion::new(8, 1, 0) < PhpVersion::new(8, 1, 3));
    }
}
