trustfall-rustdoc-adapter 60.0.0

Trustfall query adapter for rustdoc JSON data.
Documentation
use std::borrow::Cow;
use std::cell::OnceCell;
use std::fmt::{self, Debug};
use std::num::ParseIntError;
use std::rc::Rc;
use std::str::FromStr;

use rustdoc_types::{Item, ItemEnum, Variant};

#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct EnumVariant<'a> {
    item: &'a Item,
    discriminants: Option<Rc<LazyDiscriminants<'a>>>,
    index: usize,
}

#[non_exhaustive]
pub(super) struct LazyDiscriminants<'a> {
    // TODO: Consider replacing Vec with an impl Iterator for performance
    variants: Vec<&'a Variant>,
    discriminants: OnceCell<Vec<Cow<'a, str>>>,
}

impl Debug for LazyDiscriminants<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("LazyDiscriminants")
            .field("discriminants", &self.discriminants)
            .finish_non_exhaustive()
    }
}

impl<'a> LazyDiscriminants<'a> {
    pub(super) fn new(variants: Vec<&'a Variant>) -> Self {
        Self {
            variants,
            discriminants: OnceCell::new(),
        }
    }

    pub(super) fn get_discriminants(&self) -> &Vec<Cow<'a, str>> {
        self.discriminants
            .get_or_init(|| assign_discriminants(&self.variants))
    }
}

impl<'a> EnumVariant<'a> {
    pub(super) fn new(
        item: &'a Item,
        discriminants: Option<Rc<LazyDiscriminants<'a>>>,
        index: usize,
    ) -> Self {
        Self {
            item,
            discriminants,
            index,
        }
    }

    pub(super) fn variant(&self) -> &'a Variant {
        match &self.item.inner {
            ItemEnum::Variant(v) => v,
            _ => unreachable!("Item was not a Variant"),
        }
    }

    pub(super) fn discriminant(&self) -> Option<Cow<'a, str>> {
        self.discriminants.as_ref().map(|inner| {
            inner
                .get_discriminants()
                .get(self.index)
                .expect("self.index should exist in self.discriminants")
                .clone()
        })
    }

    #[inline]
    pub(super) fn item(&self) -> &'a Item {
        self.item
    }

    pub(super) fn position(&self) -> i64 {
        self.index as i64 + 1
    }
}

enum DiscriminantValue {
    I64(i64),
    U64(u64),
    I128(i128),
    U128(u128),
}

impl DiscriminantValue {
    pub fn max(&self) -> bool {
        matches!(self, DiscriminantValue::U128(u128::MAX))
    }

    pub fn increment(&self) -> DiscriminantValue {
        match self {
            DiscriminantValue::I64(i) => {
                match i.checked_add_unsigned(1) {
                    // No overflow
                    Some(i) => i.into(),
                    // Overflow, number will fit in a u64
                    None => DiscriminantValue::from(u64::try_from(*i).unwrap()).increment(),
                }
            }
            DiscriminantValue::U64(i) => {
                match i.checked_add(1) {
                    // No overflow
                    Some(i) => i.into(),
                    // Overflow, number will fit in a i128
                    None => DiscriminantValue::from(i128::from(*i)).increment(),
                }
            }
            DiscriminantValue::I128(i) => {
                match i.checked_add_unsigned(1) {
                    // No overflow
                    Some(i) => i.into(),
                    // Overflow, number will fit in a u128
                    None => DiscriminantValue::from(u128::try_from(*i).unwrap()).increment(),
                }
            }
            DiscriminantValue::U128(i) => (i + 1).into(),
        }
    }
}

impl fmt::Display for DiscriminantValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            DiscriminantValue::I64(i) => write!(f, "{i}"),
            DiscriminantValue::U64(i) => write!(f, "{i}"),
            DiscriminantValue::I128(i) => write!(f, "{i}"),
            DiscriminantValue::U128(i) => write!(f, "{i}"),
        }
    }
}

impl From<i64> for DiscriminantValue {
    fn from(value: i64) -> Self {
        DiscriminantValue::I64(value)
    }
}

impl From<i128> for DiscriminantValue {
    fn from(value: i128) -> Self {
        DiscriminantValue::I128(value)
    }
}

impl From<u64> for DiscriminantValue {
    fn from(value: u64) -> Self {
        DiscriminantValue::U64(value)
    }
}

impl From<u128> for DiscriminantValue {
    fn from(value: u128) -> Self {
        DiscriminantValue::U128(value)
    }
}

impl FromStr for DiscriminantValue {
    type Err = ParseIntError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Ok(i) = i64::from_str(s) {
            return Ok(i.into());
        }
        if let Ok(i) = u64::from_str(s) {
            return Ok(i.into());
        }
        if let Ok(i) = i128::from_str(s) {
            return Ok(i.into());
        }
        match u128::from_str(s) {
            Ok(i) => Ok(i.into()),
            Err(e) => Err(e),
        }
    }
}

/// <https://doc.rust-lang.org/reference/items/enumerations.html#assigning-discriminant-values>
pub(super) fn assign_discriminants<'a>(variants: &[&'a Variant]) -> Vec<Cow<'a, str>> {
    let mut last: DiscriminantValue = DiscriminantValue::I64(0);
    let mut discriminants: Vec<Cow<'a, str>> = Vec::with_capacity(variants.len());
    for v in variants {
        discriminants.push(match &v.discriminant {
            Some(d) => {
                last = DiscriminantValue::from_str(&d.value).unwrap();
                Cow::Borrowed(&d.value)
            }
            None => Cow::Owned(last.to_string()),
        });
        if !last.max() {
            last = last.increment();
        }
    }
    discriminants
}

#[cfg(test)]
mod tests {
    use rustdoc_types::{Discriminant, VariantKind};

    use super::*;

    #[test]
    fn i64() {
        let explicit_1 = Variant {
            discriminant: Some(Discriminant {
                value: "5".into(),
                expr: "".into(),
            }),
            kind: VariantKind::Plain,
        };
        let explicit_2 = Variant {
            discriminant: Some(Discriminant {
                value: "7".into(),
                expr: "".into(),
            }),
            kind: VariantKind::Plain,
        };
        let explicit_3 = Variant {
            discriminant: Some(Discriminant {
                value: "-59999".into(),
                expr: "".into(),
            }),
            kind: VariantKind::Plain,
        };
        let variants = vec![
            &Variant {
                discriminant: None,
                kind: VariantKind::Plain,
            },
            &Variant {
                discriminant: None,
                kind: VariantKind::Plain,
            },
            &explicit_1,
            &explicit_2,
            &Variant {
                discriminant: None,
                kind: VariantKind::Plain,
            },
            &explicit_3,
            &Variant {
                discriminant: None,
                kind: VariantKind::Plain,
            },
        ];
        let actual = assign_discriminants(&variants);
        let expected: Vec<String> = vec![
            "0".into(),
            "1".into(),
            "5".into(),
            "7".into(),
            "8".into(),
            "-59999".into(),
            "-59998".into(),
        ];
        assert_eq!(actual, expected);
    }

    #[test]
    fn max() {
        let explicit_1 = Variant {
            discriminant: Some(Discriminant {
                value: i64::MAX.to_string(),
                expr: "".into(),
            }),
            kind: VariantKind::Plain,
        };
        let explicit_2 = Variant {
            discriminant: Some(Discriminant {
                value: u64::MAX.to_string(),
                expr: "".into(),
            }),
            kind: VariantKind::Plain,
        };
        let explicit_3 = Variant {
            discriminant: Some(Discriminant {
                value: i128::MAX.to_string(),
                expr: "".into(),
            }),
            kind: VariantKind::Plain,
        };
        let explicit_4 = Variant {
            discriminant: Some(Discriminant {
                value: u128::MAX.to_string(),
                expr: "".into(),
            }),
            kind: VariantKind::Plain,
        };
        let variants = vec![
            &explicit_1,
            &Variant {
                discriminant: None,
                kind: VariantKind::Plain,
            },
            &explicit_2,
            &Variant {
                discriminant: None,
                kind: VariantKind::Plain,
            },
            &explicit_3,
            &Variant {
                discriminant: None,
                kind: VariantKind::Plain,
            },
            &explicit_4,
        ];
        let actual = assign_discriminants(&variants);
        let expected: Vec<String> = vec![
            "9223372036854775807".into(),
            "9223372036854775808".into(),
            "18446744073709551615".into(),
            "18446744073709551616".into(),
            "170141183460469231731687303715884105727".into(),
            "170141183460469231731687303715884105728".into(),
            "340282366920938463463374607431768211455".into(),
        ];
        assert_eq!(actual, expected);
    }
}