Skip to content

Commit

Permalink
replace uses of expect by unwrap_unchecked
Browse files Browse the repository at this point in the history
  • Loading branch information
brunocodutra committed Oct 2, 2023
1 parent 8d348df commit ff86216
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 22 deletions.
2 changes: 1 addition & 1 deletion bin/uci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl Uci {

fn go(&mut self, limits: Limits) -> Result<(), Anyhow> {
let pv = self.engine.search(&self.position, limits);
let best = *pv.first().expect("expected some legal move");
let best = *pv.first().context("the engine failed to find a move")?;

Check warning on line 71 in bin/uci.rs

View check run for this annotation

Codecov / codecov/patch

bin/uci.rs#L70-L71

Added lines #L70 - L71 were not covered by tests

let score = match pv.score().mate() {
Some(p) if p > 0 => UciInfoAttribute::from_mate((p + 1).get() / 2),
Expand Down
11 changes: 4 additions & 7 deletions lib/chess/move.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::chess::{Role, Square};
use crate::util::{Binary, Bits};
use crate::util::{Assume, Binary, Bits};
use derive_more::{DebugCustom, Display, Error};
use shakmaty as sm;
use vampirc_uci::UciMove;
Expand Down Expand Up @@ -80,7 +80,7 @@ impl Move {
pub fn role(&self) -> Role {
let mut bits = self.role;
match bits.pop::<u8, 1>().get() {
0 => Role::decode(bits.pop()).expect("expected valid encoding"),
0 => Role::decode(bits.pop()).assume(),
_ => Role::Pawn,
}
}
Expand All @@ -90,7 +90,7 @@ impl Move {
let mut bits = self.role;
match bits.pop::<u8, 1>().get() {
0 => None,
_ => Some(Role::decode(bits.pop()).expect("expected valid encoding")),
_ => Some(Role::decode(bits.pop()).assume()),
}
}

Expand All @@ -104,10 +104,7 @@ impl Move {
Square::new(self.whither.file(), self.whence.rank()),
))
} else {
Some((
Role::decode(self.capture).expect("expected valid encoding"),
self.whither,
))
Some((Role::decode(self.capture).assume(), self.whither))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions lib/chess/position.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::chess::{Bitboard, Color, Move, Outcome, Piece, Role, Square};
use crate::util::{Bits, Buffer};
use crate::util::{Assume, Bits, Buffer};
use derive_more::{DebugCustom, Display, Error, From};
use shakmaty as sm;
use std::hash::{Hash, Hasher};
Expand Down Expand Up @@ -163,7 +163,7 @@ impl Position {
self.by_piece(Piece(side, Role::King))
.into_iter()
.next()
.expect("expected king on the board")
.assume()
}

/// The [`Role`] of the piece on the given [`Square`], if any.
Expand Down
138 changes: 138 additions & 0 deletions lib/chess/promotion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use crate::chess::Role;
use crate::util::{Binary, Bits};
use derive_more::{Display, Error};
use shakmaty as sm;
use vampirc_uci::UciPiece;

/// A promotion specifier.
#[derive(Debug, Display, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
#[repr(u8)]
pub enum Promotion {
#[display(fmt = "")]
None,
#[display(fmt = "n")]
Knight,
#[display(fmt = "b")]
Bishop,
#[display(fmt = "r")]
Rook,
#[display(fmt = "q")]
Queen,
}

/// The reason why decoding [`Promotion`] from binary failed.
#[derive(Debug, Display, Clone, Eq, PartialEq, Error)]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
#[display(fmt = "not a valid promotion")]
pub struct DecodePromotionError;

impl Binary for Promotion {
type Bits = Bits<u8, 3>;
type Error = DecodePromotionError;

fn encode(&self) -> Self::Bits {
Bits::new(*self as _)
}

fn decode(bits: Self::Bits) -> Result<Self, Self::Error> {
use Promotion::*;
[None, Knight, Bishop, Rook, Queen]
.into_iter()
.nth(bits.get() as _)
.ok_or(DecodePromotionError)
}
}

impl From<Promotion> for Option<Role> {
fn from(p: Promotion) -> Self {
match p {
Promotion::None => None,
Promotion::Knight => Some(Role::Knight),
Promotion::Bishop => Some(Role::Bishop),
Promotion::Rook => Some(Role::Rook),
Promotion::Queen => Some(Role::Queen),
}
}
}

#[doc(hidden)]
impl From<Promotion> for Option<UciPiece> {
fn from(p: Promotion) -> Self {
match p {
Promotion::None => None,
Promotion::Knight => Some(UciPiece::Knight),
Promotion::Bishop => Some(UciPiece::Bishop),
Promotion::Rook => Some(UciPiece::Rook),
Promotion::Queen => Some(UciPiece::Queen),
}
}
}

#[doc(hidden)]
impl From<Option<UciPiece>> for Promotion {
fn from(p: Option<UciPiece>) -> Self {
match p {
None => Promotion::None,
Some(UciPiece::Knight) => Promotion::Knight,
Some(UciPiece::Bishop) => Promotion::Bishop,
Some(UciPiece::Rook) => Promotion::Rook,
Some(UciPiece::Queen) => Promotion::Queen,
Some(v) => panic!("unexpected {v:?}"),
}
}
}

#[doc(hidden)]
impl From<Option<sm::Role>> for Promotion {
fn from(p: Option<sm::Role>) -> Self {
match p {
None => Promotion::None,
Some(sm::Role::Knight) => Promotion::Knight,
Some(sm::Role::Bishop) => Promotion::Bishop,
Some(sm::Role::Rook) => Promotion::Rook,
Some(sm::Role::Queen) => Promotion::Queen,
Some(v) => panic!("unexpected {v:?}"),
}
}
}

#[doc(hidden)]
impl From<Promotion> for Option<sm::Role> {
fn from(p: Promotion) -> Self {
match p {
Promotion::None => None,
Promotion::Knight => Some(sm::Role::Knight),
Promotion::Bishop => Some(sm::Role::Bishop),
Promotion::Rook => Some(sm::Role::Rook),
Promotion::Queen => Some(sm::Role::Queen),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use test_strategy::proptest;

#[proptest]
fn decoding_encoded_promotion_is_an_identity(p: Promotion) {
assert_eq!(Promotion::decode(p.encode()), Ok(p));
}

#[proptest]
fn decoding_promotion_fails_for_invalid_bits(#[strategy(5u8..8)] n: u8) {
let b = <Promotion as Binary>::Bits::new(n as _);
assert_eq!(Promotion::decode(b), Err(DecodePromotionError));
}

#[proptest]
fn promotion_has_an_equivalent_vampirc_uci_representation(p: Promotion) {
assert_eq!(Promotion::from(Option::<UciPiece>::from(p)), p);
}

#[proptest]
fn promotion_has_an_equivalent_shakmaty_representation(p: Promotion) {
assert_eq!(Promotion::from(Option::<sm::Role>::from(p)), p);
}
}
12 changes: 6 additions & 6 deletions lib/search/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::chess::{Move, Piece, Position, Role, Zobrist};
use crate::nnue::Evaluator;
use crate::search::{Depth, Limits, Options, Ply, Pv, Score, Value};
use crate::search::{Transposition, TranspositionTable};
use crate::util::{Buffer, Timeout, Timer};
use crate::util::{Assume, Buffer, Timeout, Timer};
use rayon::{prelude::*, ThreadPool, ThreadPoolBuilder};
use std::sync::atomic::{AtomicI16, Ordering};
use std::{cmp::max, ops::Range, time::Duration};
Expand Down Expand Up @@ -186,7 +186,7 @@ impl Engine {
} else if !in_check {
if let Some(d) = self.nmp(pos, score, beta, depth) {
let mut next = pos.clone();
next.pass().expect("expected possible pass");
next.pass().assume();
if d <= ply || -self.nw(&next, -beta + 1, d, ply + 1, timer)? >= beta {
#[cfg(not(test))]
// The null move pruning heuristic is not exact.
Expand All @@ -201,7 +201,7 @@ impl Engine {
}

let mut next = pos.clone();
next.play(m).expect("expected legal move");
next.play(m).assume();
let guess = -next.see(m.whither(), Value::LOWER..Value::UPPER).cast();

let rank = if Some(m) == tpos.map(|t| t.best()) {
Expand All @@ -218,7 +218,7 @@ impl Engine {
None => return Ok(Pv::new(score, [])),
Some((m, _, _)) => {
let mut next = pos.clone();
next.play(m).expect("expected legal move");
next.play(m).assume();
let mut pv = -self.ns(&next, -beta..-alpha, depth, ply + 1, timer)?;
pv.shift(m);
pv
Expand All @@ -244,7 +244,7 @@ impl Engine {
}

let mut next = pos.clone();
next.play(m).expect("expected legal move");
next.play(m).assume();

if !in_check {
if let Some(d) = self.lmp(&next, guess, alpha, depth) {
Expand Down Expand Up @@ -276,7 +276,7 @@ impl Engine {
})
.chain([Ok(Some((best, i16::MAX)))])
.try_reduce(|| None, |a, b| Ok(max(a, b)))?
.expect("expected at least one principal variation");
.assume();

self.record(zobrist, bounds, depth, ply, best.score(), best[0]);

Expand Down
11 changes: 5 additions & 6 deletions lib/search/transposition.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::chess::{Move, Zobrist};
use crate::search::{Depth, Score};
use crate::util::{Binary, Bits, Cache};
use crate::util::{Assume, Binary, Bits, Cache};
use derive_more::{Display, Error};
use std::{cmp::Ordering, mem::size_of, ops::RangeInclusive};

Expand Down Expand Up @@ -196,7 +196,7 @@ impl TranspositionTable {

let sig = self.signature_of(key);
let bits = self.cache.load(self.index_of(key));
match Binary::decode(bits).expect("expected valid encoding") {
match Binary::decode(bits).assume() {
Some(SignedTransposition(t, s)) if s == sig => Some(t),
_ => None,
}
Expand All @@ -209,12 +209,11 @@ impl TranspositionTable {
if self.capacity() > 0 {
let sig = self.signature_of(key);
let bits = Some(SignedTransposition(transposition, sig)).encode();
self.cache.update(self.index_of(key), |r| {
match Binary::decode(r).expect("expected valid encoding") {
self.cache
.update(self.index_of(key), |r| match Binary::decode(r).assume() {
Some(SignedTransposition(t, _)) if t > transposition => None,
_ => Some(bits),
}
})
})
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions lib/util.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod assume;
mod binary;
mod bits;
mod bounds;
Expand All @@ -6,6 +7,7 @@ mod cache;
mod saturating;
mod timer;

pub use assume::*;
pub use binary::*;
pub use bits::*;
pub use bounds::*;
Expand Down
26 changes: 26 additions & 0 deletions lib/util/assume.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/// A trait for types that can be assumed to be another type.
pub trait Assume {
/// The type of the assumed value.
type Assumed;

/// Assume `Self` represents a value of `Self::Assumed`.
fn assume(self) -> Self::Assumed;
}

impl<T> Assume for Option<T> {
type Assumed = T;

fn assume(self) -> Self::Assumed {
// Definitely not safe, but we'll do it anyway.
unsafe { self.unwrap_unchecked() }
}
}

impl<T, E> Assume for Result<T, E> {
type Assumed = T;

fn assume(self) -> Self::Assumed {
// Definitely not safe, but we'll do it anyway.
unsafe { self.unwrap_unchecked() }
}
}

0 comments on commit ff86216

Please sign in to comment.