use std::cmp::{Ordering, PartialOrd};
use std::fmt;

use crate::borrow_tracker::AccessKind;
use crate::borrow_tracker::tree_borrows::diagnostics::TransitionError;
use crate::borrow_tracker::tree_borrows::tree::AccessRelatedness;

/// The activation states of a pointer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum PermissionPriv {
    /// represents: a shared reference to interior mutable data.
    /// allows: all foreign and child accesses;
    /// rejects: nothing
    Cell,
    /// represents: a local mutable reference that has not yet been written to;
    /// allows: child reads, foreign reads;
    /// affected by: child writes (becomes Unique),
    /// rejects: foreign writes (Disabled).
    ///
    /// `ReservedFrz` is mostly for types that are `Freeze` (no interior mutability).
    /// If the type has interior mutability, see `ReservedIM` instead.
    /// (Note: since the discovery of `tests/fail/tree_borrows/reservedim_spurious_write.rs`,
    /// we also use `ReservedFreeze` for mutable references that were retagged with a protector
    /// independently of interior mutability)
    ///
    /// special case: behaves differently when protected, which is where `conflicted`
    /// is relevant
    /// - `conflicted` is set on foreign reads,
    /// - `conflicted` must not be set on child writes (there is UB otherwise).
    ///
    /// This is so that the behavior of `Reserved` adheres to the rules of `noalias`:
    /// - foreign-read then child-write is UB due to `conflicted`,
    /// - child-write then foreign-read is UB since child-write will activate and then
    ///   foreign-read disables a protected `Unique`, which is UB.
    ReservedFrz { conflicted: bool },
    /// Alternative version of `ReservedFrz` made for types with interior mutability.
    /// allows: child reads, foreign reads, foreign writes (extra);
    /// affected by: child writes (becomes Unique);
    /// rejects: nothing.
    ReservedIM,
    /// represents: a unique pointer;
    /// allows: child reads, child writes;
    /// rejects: foreign reads (Frozen), foreign writes (Disabled).
    Unique,
    /// represents: a shared pointer;
    /// allows: all read accesses;
    /// rejects child writes (UB), foreign writes (Disabled).
    Frozen,
    /// represents: a dead pointer;
    /// allows: all foreign accesses;
    /// rejects: all child accesses (UB).
    Disabled,
}
use self::PermissionPriv::*;
use super::foreign_access_skipping::IdempotentForeignAccess;
use super::wildcard::WildcardAccessLevel;

impl PartialOrd for PermissionPriv {
    /// PermissionPriv is ordered by the reflexive transitive closure of
    /// `Reserved(conflicted=false) < Reserved(conflicted=true) < Unique < Frozen < Disabled`.
    /// `Reserved` that have incompatible `ty_is_freeze` are incomparable to each other.
    /// This ordering matches the reachability by transitions, as asserted by the exhaustive test
    /// `permissionpriv_partialord_is_reachability`.
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        use Ordering::*;
        Some(match (self, other) {
            (a, b) if a == b => Equal,
            // Versions of `Reserved` with different interior mutability are incomparable with each
            // other.
            (ReservedIM, ReservedFrz { .. })
            | (ReservedFrz { .. }, ReservedIM)
            // `Cell` is not comparable with any other permission
            // since it never transitions to any other state and we
            // can never get to `Cell` from another state.
            | (Cell, _) | (_, Cell) => return None,
            (Disabled, _) => Greater,
            (_, Disabled) => Less,
            (Frozen, _) => Greater,
            (_, Frozen) => Less,
            (Unique, _) => Greater,
            (_, Unique) => Less,
            (ReservedIM, ReservedIM) => Equal,
            (ReservedFrz { conflicted: c1 }, ReservedFrz { conflicted: c2 }) => {
                // `bool` is ordered such that `false <= true`, so this works as intended.
                c1.cmp(c2)
            }
        })
    }
}

impl PermissionPriv {
    /// Check if `self` can be the initial state of a pointer.
    fn is_initial(&self) -> bool {
        matches!(self, ReservedFrz { conflicted: false } | Frozen | ReservedIM | Cell)
    }

    /// Reject `ReservedIM` that cannot exist in the presence of a protector.
    #[cfg(test)]
    fn compatible_with_protector(&self) -> bool {
        // FIXME(TB-Cell): It is unclear what to do here.
        // `Cell` will occur with a protector but won't provide the guarantees
        // of noalias (it will fail the `protected_enforces_noalias` test).
        !matches!(self, ReservedIM | Cell)
    }

    /// See `foreign_access_skipping.rs`. Computes the SIFA of a permission.
    fn strongest_idempotent_foreign_access(&self, prot: bool) -> IdempotentForeignAccess {
        match self {
            // Cell survives any foreign access
            Cell => IdempotentForeignAccess::Write,
            // A protected non-conflicted Reserved will become conflicted under a foreign read,
            // and is hence not idempotent under it.
            ReservedFrz { conflicted } if prot && !conflicted => IdempotentForeignAccess::None,
            // Otherwise, foreign reads do not affect Reserved
            ReservedFrz { .. } => IdempotentForeignAccess::Read,
            // Famously, ReservedIM survives foreign writes. It is never protected.
            ReservedIM if prot => unreachable!("Protected ReservedIM should not exist!"),
            ReservedIM => IdempotentForeignAccess::Write,
            // Unique changes on any foreign access (becomes Frozen/Disabled).
            Unique => IdempotentForeignAccess::None,
            // Frozen survives foreign reads, but not writes.
            Frozen => IdempotentForeignAccess::Read,
            // Disabled survives foreign reads and writes. It survives them
            // even if protected, because a protected `Disabled` is not initialized
            // and does therefore not trigger UB.
            Disabled => IdempotentForeignAccess::Write,
        }
    }
}

/// This module controls how each permission individually reacts to an access.
/// Although these functions take `protected` as an argument, this is NOT because
/// we check protector violations here, but because some permissions behave differently
/// when protected.
mod transition {
    use super::*;
    /// A child node was read-accessed: UB on Disabled, noop on the rest.
    fn child_read(state: PermissionPriv, _protected: bool) -> Option<PermissionPriv> {
        Some(match state {
            Disabled => return None,
            // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
            // accesses, since the data is not being mutated. Hence the `{ .. }`.
            readable @ (Cell | ReservedFrz { .. } | ReservedIM | Unique | Frozen) => readable,
        })
    }

    /// A non-child node was read-accessed: keep `Reserved` but mark it as `conflicted` if it
    /// is protected; invalidate `Unique`.
    fn foreign_read(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
        Some(match state {
            // Cell ignores foreign reads.
            Cell => Cell,
            // Non-writeable states just ignore foreign reads.
            non_writeable @ (Frozen | Disabled) => non_writeable,
            // Writeable states are more tricky, and depend on whether things are protected.
            // The inner data `ty_is_freeze` of `Reserved` is always irrelevant for Read
            // accesses, since the data is not being mutated. Hence the `{ .. }`

            // Someone else read. To make sure we won't write before function exit,
            // we set the "conflicted" flag, which will disallow writes while we are protected.
            ReservedFrz { .. } if protected => ReservedFrz { conflicted: true },
            // Before activation and without protectors, foreign reads are fine.
            // That's the entire point of 2-phase borrows.
            res @ (ReservedFrz { .. } | ReservedIM) => {
                // Even though we haven't checked `ReservedIM if protected` separately,
                // it is a state that cannot occur because under a protector we only
                // create `ReservedFrz` never `ReservedIM`.
                assert!(!protected);
                res
            }
            Unique =>
                if protected {
                    // We wrote, someone else reads -- that's bad.
                    // (Since Unique is always initialized, this move-to-protected will mean insta-UB.)
                    Disabled
                } else {
                    // We don't want to disable here to allow read-read reordering: it is crucial
                    // that the foreign read does not invalidate future reads through this tag.
                    Frozen
                },
        })
    }

    /// A child node was write-accessed: `Reserved` must become `Unique` to obtain
    /// write permissions, `Frozen` and `Disabled` cannot obtain such permissions and produce UB.
    fn child_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
        Some(match state {
            // Cell ignores child writes.
            Cell => Cell,
            // If the `conflicted` flag is set, then there was a foreign read during
            // the function call that is still ongoing (still `protected`),
            // this is UB (`noalias` violation).
            ReservedFrz { conflicted: true } if protected => return None,
            // A write always activates the 2-phase borrow, even with interior
            // mutability
            ReservedFrz { .. } | ReservedIM | Unique => Unique,
            Frozen | Disabled => return None,
        })
    }

    /// A non-child node was write-accessed: this makes everything `Disabled` except for
    /// non-protected interior mutable `Reserved` which stay the same.
    fn foreign_write(state: PermissionPriv, protected: bool) -> Option<PermissionPriv> {
        // There is no explicit dependency on `protected`, but recall that interior mutable
        // types receive a `ReservedFrz` instead of `ReservedIM` when retagged under a protector,
        // so the result of this function does indirectly depend on (past) protector status.
        Some(match state {
            // Cell ignores foreign writes.
            Cell => Cell,
            res @ ReservedIM => {
                // We can never create a `ReservedIM` under a protector, only `ReservedFrz`.
                assert!(!protected);
                res
            }
            _ => Disabled,
        })
    }

    /// Dispatch handler depending on the kind of access and its position.
    pub(super) fn perform_access(
        kind: AccessKind,
        rel_pos: AccessRelatedness,
        child: PermissionPriv,
        protected: bool,
    ) -> Option<PermissionPriv> {
        match (kind, rel_pos.is_foreign()) {
            (AccessKind::Write, true) => foreign_write(child, protected),
            (AccessKind::Read, true) => foreign_read(child, protected),
            (AccessKind::Write, false) => child_write(child, protected),
            (AccessKind::Read, false) => child_read(child, protected),
        }
    }
}

/// Public interface to the state machine that controls read-write permissions.
/// This is the "private `enum`" pattern.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd)]
pub struct Permission {
    inner: PermissionPriv,
}

/// Transition from one permission to the next.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PermTransition {
    from: PermissionPriv,
    to: PermissionPriv,
}

impl Permission {
    /// Check if `self` can be the initial state of a pointer.
    pub fn is_initial(&self) -> bool {
        self.inner.is_initial()
    }
    /// Check if `self` is the terminal state of a pointer (is `Disabled`).
    pub fn is_disabled(&self) -> bool {
        self.inner == Disabled
    }
    /// Check if `self` is the never-allow-writes-again state of a pointer (is `Frozen`).
    pub fn is_frozen(&self) -> bool {
        self.inner == Frozen
    }

    /// Check if `self` is the shared-reference-to-interior-mutable-data state of a pointer.
    pub fn is_cell(&self) -> bool {
        self.inner == Cell
    }

    /// Default initial permission of the root of a new tree at inbounds positions.
    /// Must *only* be used for the root, this is not in general an "initial" permission!
    pub fn new_unique() -> Self {
        Self { inner: Unique }
    }

    /// Default initial permission of a reborrowed mutable reference that is either
    /// protected or not interior mutable.
    pub fn new_reserved_frz() -> Self {
        Self { inner: ReservedFrz { conflicted: false } }
    }

    /// Default initial permission of an unprotected interior mutable reference.
    pub fn new_reserved_im() -> Self {
        Self { inner: ReservedIM }
    }

    /// Default initial permission of a reborrowed shared reference.
    pub fn new_frozen() -> Self {
        Self { inner: Frozen }
    }

    /// Default initial permission of  the root of a new tree at out-of-bounds positions.
    /// Must *only* be used for the root, this is not in general an "initial" permission!
    pub fn new_disabled() -> Self {
        Self { inner: Disabled }
    }

    /// Default initial permission of a shared reference to interior mutable data.
    pub fn new_cell() -> Self {
        Self { inner: Cell }
    }

    /// Reject `ReservedIM` that cannot exist in the presence of a protector.
    #[cfg(test)]
    pub fn compatible_with_protector(&self) -> bool {
        self.inner.compatible_with_protector()
    }

    /// What kind of access to perform before releasing the protector.
    pub fn protector_end_access(&self) -> Option<AccessKind> {
        match self.inner {
            // Do not do perform access if it is a `Cell`, as this
            // can cause data races when using thread-safe data types.
            Cell => None,
            Unique => Some(AccessKind::Write),
            _ => Some(AccessKind::Read),
        }
    }

    /// Apply the transition to the inner PermissionPriv.
    pub fn perform_access(
        kind: AccessKind,
        rel_pos: AccessRelatedness,
        old_perm: Self,
        protected: bool,
    ) -> Option<PermTransition> {
        let old_state = old_perm.inner;
        transition::perform_access(kind, rel_pos, old_state, protected)
            .map(|new_state| PermTransition { from: old_state, to: new_state })
    }

    /// During a provenance GC, we want to compact the tree.
    /// For this, we want to merge nodes upwards if they have a singleton parent.
    /// But we need to be careful: If the parent is Frozen, and the child is Reserved,
    /// we can not do such a merge. In general, such a merge is possible if the parent
    /// allows similar accesses, and in particular if the parent never causes UB on its
    /// own. This is enforced by a test, namely `tree_compacting_is_sound`. See that
    /// test for more information.
    /// This method is only sound if the parent is not protected. We never attempt to
    /// remove protected parents.
    pub fn can_be_replaced_by_child(self, child: Self) -> bool {
        match (self.inner, child.inner) {
            // Cell allows all transitions.
            (Cell, _) => true,
            // Cell is the most permissive, nothing can be replaced by Cell.
            // (ReservedIM, Cell) => true,
            (_, Cell) => false,
            // ReservedIM can be replaced by anything besides Cell.
            // ReservedIM allows all transitions, but unlike Cell, a local write
            // to ReservedIM transitions to Unique, while it is a no-op for Cell.
            (ReservedIM, _) => true,
            (_, ReservedIM) => false,
            // Reserved (as parent, where conflictedness does not matter)
            // can be replaced by all but ReservedIM and Cell,
            // since ReservedIM and Cell alone would survive foreign writes
            (ReservedFrz { .. }, _) => true,
            (_, ReservedFrz { .. }) => false,
            // Unique can not be replaced by something surviving
            // foreign reads and then remaining writable (i.e., Reserved*).
            // Replacing a state by itself is always okay, even if the child state is protected.
            // Unique can be replaced by Frozen, since it is not protected.
            (Unique, Unique | Frozen | Disabled) => true,
            (_, Unique) => false,
            // Frozen can only be replaced by Disabled (and itself).
            (Frozen, Frozen | Disabled) => true,
            (_, Frozen) => false,
            // Disabled can not be replaced by anything else.
            (Disabled, Disabled) => true,
        }
    }

    /// Returns the strongest foreign action this node survives (without change),
    /// where `prot` indicates if it is protected.
    /// See `foreign_access_skipping`
    pub fn strongest_idempotent_foreign_access(&self, prot: bool) -> IdempotentForeignAccess {
        self.inner.strongest_idempotent_foreign_access(prot)
    }

    /// Returns the strongest access allowed that is local to this node without
    /// causing UB (only considers possible transitions to this permission).
    pub fn strongest_allowed_local_access(&self, protected: bool) -> WildcardAccessLevel {
        match self.inner {
            // Everything except disabled can be accessed by read access.
            Disabled => WildcardAccessLevel::None,
            // Frozen references cannot be written to by a child.
            Frozen => WildcardAccessLevel::Read,
            // If the `conflicted` flag is set, then there was a foreign read
            // during the function call that is still ongoing (still `protected`),
            // this is UB (`noalias` violation).
            ReservedFrz { conflicted: true } if protected => WildcardAccessLevel::Read,
            // Everything else allows writes.
            _ => WildcardAccessLevel::Write,
        }
    }
}

impl PermTransition {
    /// All transitions created through normal means (using `perform_access`)
    /// should be possible, but the same is not guaranteed by construction of
    /// transitions inferred by diagnostics. This checks that a transition
    /// reconstructed by diagnostics is indeed one that could happen.
    fn is_possible(self) -> bool {
        self.from <= self.to
    }

    pub fn is_noop(self) -> bool {
        self.from == self.to
    }

    /// Extract result of a transition (checks that the starting point matches).
    pub fn applied(self, starting_point: Permission) -> Option<Permission> {
        (starting_point.inner == self.from).then_some(Permission { inner: self.to })
    }

    /// Determines if this transition would disable the permission.
    pub fn produces_disabled(self) -> bool {
        self.to == Disabled
    }
}

pub mod diagnostics {
    use super::*;
    impl fmt::Display for PermissionPriv {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(
                f,
                "{}",
                match self {
                    Cell => "Cell",
                    ReservedFrz { conflicted: false } => "Reserved",
                    ReservedFrz { conflicted: true } => "Reserved (conflicted)",
                    ReservedIM => "Reserved (interior mutable)",
                    Unique => "Unique",
                    Frozen => "Frozen",
                    Disabled => "Disabled",
                }
            )
        }
    }

    impl fmt::Display for PermTransition {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "from {} to {}", self.from, self.to)
        }
    }

    impl fmt::Display for Permission {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "{}", self.inner)
        }
    }

    impl Permission {
        /// Abbreviated name of the permission (uniformly 3 letters for nice alignment).
        pub fn short_name(self) -> &'static str {
            // Make sure there are all of the same length as each other
            // and also as `diagnostics::DisplayFmtPermission.uninit` otherwise
            // alignment will be incorrect.
            match self.inner {
                Cell => "Cel ",
                ReservedFrz { conflicted: false } => "Res ",
                ReservedFrz { conflicted: true } => "ResC",
                ReservedIM => "ReIM",
                Unique => "Act ",
                Frozen => "Frz ",
                Disabled => "Dis ",
            }
        }
    }

    impl PermTransition {
        /// Readable explanation of the consequences of an event.
        /// Fits in the sentence "This transition corresponds to {trans.summary()}".
        pub fn summary(&self) -> &'static str {
            assert!(self.is_possible());
            assert!(!self.is_noop());
            match (self.from, self.to) {
                (_, Unique) => "the first write to a 2-phase borrowed mutable reference",
                (_, Frozen) => "a loss of write permissions",
                (ReservedFrz { conflicted: false }, ReservedFrz { conflicted: true }) =>
                    "a temporary loss of write permissions until function exit",
                (Frozen, Disabled) => "a loss of read permissions",
                (_, Disabled) => "a loss of read and write permissions",
                (old, new) =>
                    unreachable!("Transition from {old:?} to {new:?} should never be possible"),
            }
        }

        /// Determines whether `self` is a relevant transition for the error `err`.
        /// `self` will be a transition that happened to a tag some time before
        /// that tag caused the error.
        ///
        /// Irrelevant events:
        /// - modifications of write permissions when the error is related to read permissions
        ///   (on failed reads and protected `Frozen -> Disabled`, ignore `Reserved -> Unique`,
        ///   `Reserved(conflicted=false) -> Reserved(conflicted=true)`, and `Unique -> Frozen`)
        /// - all transitions for attempts to deallocate strongly protected tags
        ///
        /// # Panics
        ///
        /// This function assumes that its arguments apply to the same location
        /// and that they were obtained during a normal execution. It will panic otherwise.
        /// - all transitions involved in `self` and `err` should be increasing
        ///   (Reserved < Unique < Frozen < Disabled);
        /// - between `self` and `err` the permission should also be increasing,
        ///   so all permissions inside `err` should be greater than `self.1`;
        /// - `Unique`, `Reserved(conflicted=false)`, and `Cell` cannot cause an error
        ///   due to insufficient permissions, so `err` cannot be a `ChildAccessForbidden(_)`
        ///   of either of them;
        /// - `err` should not be `ProtectedDisabled(Disabled)`, because the protected
        ///   tag should not have been `Disabled` in the first place (if this occurs it means
        ///   we have unprotected tags that become protected)
        pub(in super::super) fn is_relevant(&self, err: TransitionError) -> bool {
            // NOTE: `super::super` is the visibility of `TransitionError`
            assert!(self.is_possible());
            if self.is_noop() {
                return false;
            }
            match err {
                TransitionError::ChildAccessForbidden(insufficient) => {
                    // Show where the permission was gained then lost,
                    // but ignore unrelated permissions.
                    // This eliminates transitions like `Unique -> Frozen`
                    // when the error is a failed `Read`.
                    match (self.to, insufficient.inner) {
                        (Frozen, Frozen) => true,
                        (Unique, Frozen) => true,
                        (Disabled, Disabled) => true,
                        (
                            ReservedFrz { conflicted: true, .. },
                            ReservedFrz { conflicted: true, .. },
                        ) => true,
                        // A pointer being `Disabled` is a strictly stronger source of
                        // errors than it being `Frozen`. If we try to access a `Disabled`,
                        // then where it became `Frozen` (or `Unique` or `Reserved`) is the least
                        // of our concerns for now.
                        (ReservedFrz { conflicted: true } | Unique | Frozen, Disabled) => false,
                        (ReservedFrz { conflicted: true }, Frozen) => false,

                        // `Unique`, `Reserved`, and `Cell` have all permissions, so a
                        // `ChildAccessForbidden(Reserved | Unique)` can never exist.
                        (_, Unique) | (_, ReservedFrz { conflicted: false }) | (_, Cell) =>
                            unreachable!("this permission cannot cause an error"),
                        // No transition has `Reserved { conflicted: false }` or `ReservedIM`
                        // as its `.to` unless it's a noop. `Cell` cannot be in its `.to`
                        // because all child accesses are a noop.
                        (ReservedFrz { conflicted: false } | ReservedIM | Cell, _) =>
                            unreachable!("self is a noop transition"),
                        // All transitions produced in normal executions (using `apply_access`)
                        // change permissions in the order `Reserved -> Unique -> Frozen -> Disabled`.
                        // We assume that the error was triggered on the same location that
                        // the transition `self` applies to, so permissions found must be increasing
                        // in the order `self.from < self.to <= insufficient.inner`
                        (Unique | Frozen | Disabled, ReservedFrz { .. } | ReservedIM)
                        | (Disabled, Frozen)
                        | (ReservedFrz { .. }, ReservedIM) =>
                            unreachable!("permissions between self and err must be increasing"),
                    }
                }
                TransitionError::ProtectedDisabled(before_disabled) => {
                    // Show how we got to the starting point of the forbidden transition,
                    // but ignore what came before.
                    // This eliminates transitions like `Reserved -> Unique`
                    // when the error is a `Frozen -> Disabled`.
                    match (self.to, before_disabled.inner) {
                        // We absolutely want to know where it was activated/frozen/marked
                        // conflicted.
                        (Unique, Unique) => true,
                        (Frozen, Frozen) => true,
                        (
                            ReservedFrz { conflicted: true, .. },
                            ReservedFrz { conflicted: true, .. },
                        ) => true,
                        // If the error is a transition `Frozen -> Disabled`, then we don't really
                        // care whether before that was `Reserved -> Unique -> Frozen` or
                        // `Frozen` directly.
                        // The error will only show either
                        // - created as Reserved { conflicted: false },
                        //   then Reserved { .. } -> Disabled is forbidden
                        // - created as Reserved { conflicted: false },
                        //   then Unique -> Disabled is forbidden
                        // A potential `Reserved { conflicted: false }
                        //   -> Reserved { conflicted: true }` is inexistent or irrelevant,
                        // and so is the `Reserved { conflicted: false } -> Unique`
                        (Unique, Frozen) => false,
                        (ReservedFrz { conflicted: true }, _) => false,

                        (_, Disabled) =>
                            unreachable!(
                                "permission that results in Disabled should not itself be Disabled in the first place"
                            ),
                        // No transition has `Reserved { conflicted: false }` or `ReservedIM` as its `.to`
                        // unless it's a noop. `Cell` cannot be in its `.to` because all child
                        // accesses are a noop.
                        (ReservedFrz { conflicted: false } | ReservedIM | Cell, _) =>
                            unreachable!("self is a noop transition"),

                        // Permissions only evolve in the order `Reserved -> Unique -> Frozen -> Disabled`,
                        // so permissions found must be increasing in the order
                        // `self.from < self.to <= forbidden.from < forbidden.to`.
                        (Disabled, Cell | ReservedFrz { .. } | ReservedIM | Unique | Frozen)
                        | (Frozen, Cell | ReservedFrz { .. } | ReservedIM | Unique)
                        | (Unique, Cell | ReservedFrz { .. } | ReservedIM) =>
                            unreachable!("permissions between self and err must be increasing"),
                    }
                }
                // We don't care because protectors evolve independently from
                // permissions.
                TransitionError::ProtectedDealloc => false,
            }
        }

        /// Endpoint of a transition.
        /// Meant only for diagnostics, use `applied` in non-diagnostics
        /// code, which also checks that the starting point matches the current state.
        pub fn endpoint(&self) -> Permission {
            Permission { inner: self.to }
        }
    }
}

#[cfg(test)]
impl Permission {
    pub fn is_reserved_frz_with_conflicted(&self, expected_conflicted: bool) -> bool {
        match self.inner {
            ReservedFrz { conflicted } => conflicted == expected_conflicted,
            _ => false,
        }
    }
}

#[cfg(test)]
mod propagation_optimization_checks {
    pub use super::*;
    use crate::borrow_tracker::tree_borrows::exhaustive::{Exhaustive, precondition};

    impl Exhaustive for PermissionPriv {
        fn exhaustive() -> Box<dyn Iterator<Item = Self>> {
            Box::new(
                vec![Unique, Frozen, Disabled, ReservedIM, Cell]
                    .into_iter()
                    .chain(<bool>::exhaustive().map(|conflicted| ReservedFrz { conflicted })),
            )
        }
    }

    impl Exhaustive for Permission {
        fn exhaustive() -> Box<dyn Iterator<Item = Self>> {
            Box::new(PermissionPriv::exhaustive().map(|inner| Self { inner }))
        }
    }

    impl Exhaustive for AccessKind {
        fn exhaustive() -> Box<dyn Iterator<Item = Self>> {
            use AccessKind::*;
            Box::new(vec![Read, Write].into_iter())
        }
    }

    impl Exhaustive for AccessRelatedness {
        fn exhaustive() -> Box<dyn Iterator<Item = Self>> {
            use AccessRelatedness::*;
            Box::new(vec![ForeignAccess, LocalAccess].into_iter())
        }
    }

    #[test]
    // For any kind of access, if we do it twice the second should be a no-op.
    // Even if the protector has disappeared.
    fn all_transitions_idempotent() {
        use transition::*;
        for old in PermissionPriv::exhaustive() {
            for (old_protected, new_protected) in <(bool, bool)>::exhaustive() {
                // Protector can't appear out of nowhere: either the permission was
                // created with a protector (`old_protected = true`) and it then may
                // or may not lose it (`new_protected = false`, resp. `new_protected = true`),
                // or it didn't have one upon creation and never will
                // (`old_protected = new_protected = false`).
                // We thus eliminate from this test and all other tests
                // the case where the tag is initially unprotected and later becomes protected.
                precondition!(old_protected || !new_protected);
                if old_protected {
                    precondition!(old.compatible_with_protector());
                }
                for (access, rel_pos) in <(AccessKind, AccessRelatedness)>::exhaustive() {
                    if let Some(new) = perform_access(access, rel_pos, old, old_protected) {
                        assert_eq!(
                            new,
                            perform_access(access, rel_pos, new, new_protected).unwrap()
                        );
                    }
                }
            }
        }
    }

    #[test]
    #[rustfmt::skip]
    fn foreign_read_is_noop_after_foreign_write() {
        use transition::*;
        let old_access = AccessKind::Write;
        let new_access = AccessKind::Read;
        for old in PermissionPriv::exhaustive() {
            for [old_protected, new_protected] in <[bool; 2]>::exhaustive() {
                precondition!(old_protected || !new_protected);
                if old_protected {
                    precondition!(old.compatible_with_protector());
                }
                for rel_pos in AccessRelatedness::exhaustive() {
                    precondition!(rel_pos.is_foreign());
                    if let Some(new) = perform_access(old_access, rel_pos, old, old_protected) {
                        assert_eq!(
                            new,
                            perform_access(new_access, rel_pos, new, new_protected).unwrap()
                        );
                    }
                }
            }
        }
    }

    #[test]
    #[rustfmt::skip]
    fn permission_sifa_is_correct() {
        // Tests that `strongest_idempotent_foreign_access` is correct. See `foreign_access_skipping.rs`.
        for perm in PermissionPriv::exhaustive() {
            // Assert that adding a protector makes it less idempotent.
            if perm.compatible_with_protector() {
                assert!(perm.strongest_idempotent_foreign_access(true) <= perm.strongest_idempotent_foreign_access(false));
            }
            for prot in bool::exhaustive() {
                if prot {
                    precondition!(perm.compatible_with_protector());
                }
                let access = perm.strongest_idempotent_foreign_access(prot);
                // We now assert it is idempotent, and never causes UB.
                // First, if the SIFA includes foreign reads, assert it is idempotent under foreign reads.
                if access >= IdempotentForeignAccess::Read {
                    assert_eq!(perm, transition::perform_access(AccessKind::Read, AccessRelatedness::ForeignAccess, perm, prot).unwrap());
                }
                // Then, if the SIFA includes foreign writes, assert it is idempotent under foreign writes.
                if access >= IdempotentForeignAccess::Write {
                    assert_eq!(perm, transition::perform_access(AccessKind::Write, AccessRelatedness::ForeignAccess, perm, prot).unwrap());
                }
            }
        }
    }

    #[test]
    // Check that all transitions are consistent with the order on PermissionPriv,
    // i.e. Reserved -> Unique -> Frozen -> Disabled
    fn permissionpriv_partialord_is_reachability() {
        let reach = {
            let mut reach = rustc_data_structures::fx::FxHashSet::default();
            // One-step transitions + reflexivity
            for start in PermissionPriv::exhaustive() {
                reach.insert((start, start));
                for (access, rel) in <(AccessKind, AccessRelatedness)>::exhaustive() {
                    for prot in bool::exhaustive() {
                        if prot {
                            precondition!(start.compatible_with_protector());
                        }
                        if let Some(end) = transition::perform_access(access, rel, start, prot) {
                            reach.insert((start, end));
                        }
                    }
                }
            }
            // Transitive closure
            let mut finished = false;
            while !finished {
                finished = true;
                for [start, mid, end] in <[PermissionPriv; 3]>::exhaustive() {
                    if reach.contains(&(start, mid))
                        && reach.contains(&(mid, end))
                        && !reach.contains(&(start, end))
                    {
                        finished = false;
                        reach.insert((start, end));
                    }
                }
            }
            reach
        };
        // Check that it matches `<`
        for [p1, p2] in <[PermissionPriv; 2]>::exhaustive() {
            let le12 = p1 <= p2;
            let reach12 = reach.contains(&(p1, p2));
            assert!(
                le12 == reach12,
                "`{p1} reach {p2}` ({reach12}) does not match `{p1} <= {p2}` ({le12})"
            );
        }
    }

    /// Checks that  `strongest_allowed_child_access` correctly
    /// represents which transitions are possible.
    #[test]
    fn strongest_allowed_local_access() {
        for (permission, protected) in <(Permission, bool)>::exhaustive() {
            let strongest_local_access = permission.strongest_allowed_local_access(protected);

            let is_read_valid = Permission::perform_access(
                AccessKind::Read,
                AccessRelatedness::LocalAccess,
                permission,
                protected,
            )
            .is_some();

            let is_write_valid = Permission::perform_access(
                AccessKind::Write,
                AccessRelatedness::LocalAccess,
                permission,
                protected,
            )
            .is_some();

            assert_eq!(is_read_valid, strongest_local_access >= WildcardAccessLevel::Read);
            assert_eq!(is_write_valid, strongest_local_access >= WildcardAccessLevel::Write);
        }
    }
}
