From 8f50c7063c6a1cf46942db6de652e3acf496bca5 Mon Sep 17 00:00:00 2001 From: Kyle Anthony Williams Date: Fri, 26 Jul 2024 16:25:07 -0400 Subject: [PATCH] finally got implicit discriminants working --- src/adapter/edges.rs | 52 +++- src/adapter/enum_variant.rs | 304 ++++++++++++++++++++++ src/adapter/mod.rs | 1 + src/adapter/origin.rs | 22 +- src/adapter/properties.rs | 11 +- src/adapter/tests.rs | 62 ++++- src/adapter/vertex.rs | 50 ++-- src/rustdoc_schema.graphql | 7 - test_crates/enum_discriminants/src/lib.rs | 9 + 9 files changed, 459 insertions(+), 59 deletions(-) create mode 100644 src/adapter/enum_variant.rs diff --git a/src/adapter/edges.rs b/src/adapter/edges.rs index a33f15a..b87403e 100644 --- a/src/adapter/edges.rs +++ b/src/adapter/edges.rs @@ -1,4 +1,5 @@ -use rustdoc_types::{GenericBound::TraitBound, Id, ItemEnum, VariantKind}; +use rustdoc_types::{GenericBound::TraitBound, Id, ItemEnum, Variant, VariantKind}; +use std::sync::Arc; use trustfall::provider::{ resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, VertexIterator, @@ -6,7 +7,9 @@ use trustfall::provider::{ use crate::{adapter::supported_item_kind, attributes::Attribute, IndexedCrate}; -use super::{optimizations, origin::Origin, vertex::Vertex, RustdocAdapter}; +use super::{ + enum_variant::LazyDiscriminants, optimizations, origin::Origin, vertex::Vertex, RustdocAdapter, +}; pub(super) fn resolve_crate_diff_edge<'a, V: AsVertex> + 'a>( contexts: ContextIterator<'a, V>, @@ -295,15 +298,10 @@ pub(super) fn resolve_variant_edge<'a, V: AsVertex> + 'a>( }), "discriminant" => resolve_neighbors_with(contexts, move |vertex| { let origin = vertex.origin; - let item = vertex.as_variant().expect("vertex was not a Variant"); - - if let Some(discriminant) = &item.discriminant { - Box::new(std::iter::once( - origin.make_discriminant_vertex(discriminant), - )) - } else { - Box::new(std::iter::empty()) - } + let enum_var = vertex.as_enum_variant().expect("vertex was not a Variant"); + Box::new(std::iter::once( + origin.make_discriminant_vertex(enum_var.discriminant().clone()), + )) }), _ => unreachable!("resolve_variant_edge {edge_name}"), } @@ -329,9 +327,35 @@ pub(super) fn resolve_enum_edge<'a, V: AsVertex> + 'a>( .index } }; - Box::new(enum_item.variants.iter().map(move |field_id| { - origin.make_item_vertex(item_index.get(field_id).expect("missing item")) - })) + + let discriminants = { + let variants: Vec<&Variant> = enum_item + .variants + .iter() + .map(move |field_id| { + let inner = &item_index.get(field_id).expect("missing item").inner; + match inner { + ItemEnum::Variant(v) => v, + _ => unreachable!("Item {inner:?} not a Variant"), + } + }) + .collect(); + Arc::new(LazyDiscriminants::new(variants)) + }; + + Box::new( + enum_item + .variants + .iter() + .enumerate() + .map(move |(index, field_id)| { + origin.make_variant_vertex( + item_index.get(field_id).expect("missing item"), + discriminants.clone(), + index, + ) + }), + ) }), _ => unreachable!("resolve_enum_edge {edge_name}"), } diff --git a/src/adapter/enum_variant.rs b/src/adapter/enum_variant.rs new file mode 100644 index 0000000..7d33b76 --- /dev/null +++ b/src/adapter/enum_variant.rs @@ -0,0 +1,304 @@ +use rustdoc_types::{Item, ItemEnum, Variant}; +use std::fmt; +use std::num::ParseIntError; +use std::sync::Arc; +use std::{str::FromStr, sync::OnceLock}; + +#[non_exhaustive] +#[derive(Debug, Clone)] +pub(super) struct EnumVariant<'a> { + item: &'a Item, + discriminants: Arc>, + index: usize, +} + +#[non_exhaustive] +#[derive(Debug, Clone)] +pub(super) struct LazyDiscriminants<'a> { + variants: Vec<&'a Variant>, + discriminants: OnceLock>, +} + +impl<'a> LazyDiscriminants<'a> { + pub(super) fn new(variants: Vec<&'a Variant>) -> Self { + Self { + variants, + discriminants: OnceLock::new(), + } + } + + pub(super) fn get_discriminants(&self) -> &Vec { + self.discriminants + .get_or_init(|| assign_discriminants(&self.variants)) + } +} + +impl<'a> EnumVariant<'a> { + pub(super) fn new( + item: &'a Item, + discriminants: Arc>, + index: usize, + ) -> Self { + Self { + item, + discriminants, + index, + } + } + + pub(super) fn variant(&self) -> Option<&'a Variant> { + match &self.item.inner { + ItemEnum::Variant(v) => Some(v), + _ => None, + } + } + + pub(super) fn discriminant(&'a self) -> &'a String { + self.discriminants + .get_discriminants() + .get(self.index) + .unwrap() + } +} + +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 for DiscriminantValue { + fn from(value: i64) -> Self { + DiscriminantValue::I64(value) + } +} + +impl From for DiscriminantValue { + fn from(value: i128) -> Self { + DiscriminantValue::I128(value) + } +} + +impl From for DiscriminantValue { + fn from(value: u64) -> Self { + DiscriminantValue::U64(value) + } +} + +impl From for DiscriminantValue { + fn from(value: u128) -> Self { + DiscriminantValue::U128(value) + } +} + +impl FromStr for DiscriminantValue { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + 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), + } + } +} + +/// +pub(super) fn assign_discriminants(variants: &Vec<&Variant>) -> Vec { + let mut last: DiscriminantValue = DiscriminantValue::I64(0); + let mut discriminants: Vec = Vec::with_capacity(variants.len()); + for v in variants { + discriminants.push(match &v.discriminant { + Some(d) => { + last = DiscriminantValue::from_str(&d.value).unwrap(); + d.value.clone() + } + None => 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 = 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 = vec![ + "9223372036854775807".into(), + "9223372036854775808".into(), + "18446744073709551615".into(), + "18446744073709551616".into(), + "170141183460469231731687303715884105727".into(), + "170141183460469231731687303715884105728".into(), + "340282366920938463463374607431768211455".into(), + ]; + assert_eq!(actual, expected); + } +} diff --git a/src/adapter/mod.rs b/src/adapter/mod.rs index e4d1667..0a4e154 100644 --- a/src/adapter/mod.rs +++ b/src/adapter/mod.rs @@ -17,6 +17,7 @@ use self::{ }; mod edges; +mod enum_variant; mod optimizations; mod origin; mod properties; diff --git a/src/adapter/origin.rs b/src/adapter/origin.rs index 7a57e24..3dea145 100644 --- a/src/adapter/origin.rs +++ b/src/adapter/origin.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::{rc::Rc, sync::Arc}; use rustdoc_types::{Abi, Item, Span}; @@ -7,7 +7,10 @@ use crate::{ indexed_crate::ImportablePath, }; -use super::vertex::{Vertex, VertexKind}; +use super::{ + enum_variant::{EnumVariant, LazyDiscriminants}, + vertex::{Vertex, VertexKind}, +}; #[non_exhaustive] #[derive(Debug, Clone, Copy)] @@ -97,13 +100,22 @@ impl Origin { } } - pub(super) fn make_discriminant_vertex<'a>( + pub(super) fn make_discriminant_vertex<'a>(&self, value: String) -> Vertex<'a> { + Vertex { + origin: *self, + kind: VertexKind::Discriminant(value), + } + } + + pub(super) fn make_variant_vertex<'a>( &self, - discriminant: &'a rustdoc_types::Discriminant, + item: &'a Item, + discriminants: Arc>, + index: usize, ) -> Vertex<'a> { Vertex { origin: *self, - kind: discriminant.into(), + kind: VertexKind::Variant(EnumVariant::new(item, discriminants, index)), } } } diff --git a/src/adapter/properties.rs b/src/adapter/properties.rs index 088fd7f..0a65a6d 100644 --- a/src/adapter/properties.rs +++ b/src/adapter/properties.rs @@ -566,8 +566,13 @@ pub(crate) fn resolve_discriminant_property<'a, V: AsVertex> + 'a>( property_name: &str, ) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { - "expr" => resolve_property_with(contexts, field_property!(as_discriminant, expr)), - "value" => resolve_property_with(contexts, field_property!(as_discriminant, value)), - _ => unreachable!("AssociatedConstant property {property_name}"), + "value" => resolve_property_with(contexts, |vertex| { + vertex + .as_discriminant() + .expect("vertex was not a Discriminant") + .clone() + .into() + }), + _ => unreachable!("Discriminant property {property_name}"), } } diff --git a/src/adapter/tests.rs b/src/adapter/tests.rs index 770bc80..ea8b5bf 100644 --- a/src/adapter/tests.rs +++ b/src/adapter/tests.rs @@ -1677,7 +1677,6 @@ fn enum_discriminants() { name @output variant { discriminant { - expr @output value @output } } @@ -1694,7 +1693,6 @@ fn enum_discriminants() { #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, serde::Deserialize)] struct Output { name: String, - expr: String, value: String, } @@ -1709,34 +1707,80 @@ fn enum_discriminants() { vec![ Output { name: "A".into(), - expr: "1".into(), + value: "0".into(), + }, + Output { + name: "A".into(), value: "1".into(), }, Output { name: "A".into(), - expr: "99".into(), - value: "99".into(), + value: "100".into(), + }, + Output { + name: "A".into(), + value: "2".into(), + }, + Output { + name: "A".into(), + value: "3".into(), }, Output { name: "A".into(), - expr: "{ _ }".into(), + value: "99".into(), + }, + Output { + name: "Fieldful".into(), + value: "0".into(), + }, + Output { + name: "Fieldful".into(), + value: "1".into(), + }, + Output { + name: "Fieldful".into(), value: "2".into(), }, Output { name: "Fieldful".into(), - expr: "9".into(), value: "9".into(), }, Output { name: "FieldlessWithDiscrimants".into(), - expr: "10".into(), value: "10".into(), }, Output { name: "FieldlessWithDiscrimants".into(), - expr: "20".into(), + value: "11".into(), + }, + Output { + name: "FieldlessWithDiscrimants".into(), value: "20".into(), }, + Output { + name: "FieldlessWithDiscrimants".into(), + value: "21".into(), + }, + Output { + name: "FieldlessWithDiscrimants".into(), + value: "22".into(), + }, + Output { + name: "Pathological".into(), + value: "-170141183460469231731687303715884105726".into(), + }, + Output { + name: "Pathological".into(), + value: "-170141183460469231731687303715884105727".into(), + }, + Output { + name: "Pathological".into(), + value: "-170141183460469231731687303715884105728".into(), + }, + Output { + name: "Pathological".into(), + value: "170141183460469231731687303715884105727".into(), + } ], results ); diff --git a/src/adapter/vertex.rs b/src/adapter/vertex.rs index 6a37bff..0ccd508 100644 --- a/src/adapter/vertex.rs +++ b/src/adapter/vertex.rs @@ -1,8 +1,8 @@ use std::rc::Rc; use rustdoc_types::{ - Abi, Constant, Crate, Discriminant, Enum, Function, Impl, Item, Module, Path, Span, Static, - Struct, Trait, Type, Union, Variant, VariantKind, + Abi, Constant, Crate, Enum, Function, Impl, Item, Module, Path, Span, Static, Struct, Trait, + Type, Union, Variant, VariantKind, }; use trustfall::provider::Typename; @@ -12,7 +12,7 @@ use crate::{ IndexedCrate, }; -use super::origin::Origin; +use super::{enum_variant::EnumVariant, origin::Origin}; #[non_exhaustive] #[derive(Debug, Clone)] @@ -36,7 +36,8 @@ pub enum VertexKind<'a> { ImplementedTrait(&'a Path, &'a Item), FunctionParameter(&'a str), FunctionAbi(&'a Abi), - Discriminant(&'a Discriminant), + Discriminant(String), + Variant(EnumVariant<'a>), } impl<'a> Typename for Vertex<'a> { @@ -44,24 +45,25 @@ impl<'a> Typename for Vertex<'a> { /// intended to fulfill resolution requests for the __typename property. #[inline] fn typename(&self) -> &'static str { - match self.kind { + match &self.kind { VertexKind::Item(item) => match &item.inner { rustdoc_types::ItemEnum::Module { .. } => "Module", rustdoc_types::ItemEnum::Struct(..) => "Struct", rustdoc_types::ItemEnum::Enum(..) => "Enum", rustdoc_types::ItemEnum::Union(..) => "Union", rustdoc_types::ItemEnum::Function(..) => "Function", - rustdoc_types::ItemEnum::Variant(variant) => match variant.kind { - VariantKind::Plain => "PlainVariant", - VariantKind::Tuple(..) => "TupleVariant", - VariantKind::Struct { .. } => "StructVariant", - }, rustdoc_types::ItemEnum::StructField(..) => "StructField", rustdoc_types::ItemEnum::Impl(..) => "Impl", rustdoc_types::ItemEnum::Trait(..) => "Trait", rustdoc_types::ItemEnum::Constant(..) => "Constant", rustdoc_types::ItemEnum::Static(..) => "Static", rustdoc_types::ItemEnum::AssocType { .. } => "AssociatedType", + // TODO: How are we even here??? + rustdoc_types::ItemEnum::Variant(variant) => match variant.kind { + VariantKind::Plain => "PlainVariant", + VariantKind::Tuple(..) => "TupleVariant", + VariantKind::Struct { .. } => "StructVariant", + }, _ => unreachable!("unexpected item.inner for item: {item:?}"), }, VertexKind::Span(..) => "Span", @@ -79,6 +81,11 @@ impl<'a> Typename for Vertex<'a> { VertexKind::FunctionParameter(..) => "FunctionParameter", VertexKind::FunctionAbi(..) => "FunctionAbi", VertexKind::Discriminant(..) => "Discriminant", + VertexKind::Variant(ev) => match ev.variant().unwrap().kind { + VariantKind::Plain => "PlainVariant", + VariantKind::Tuple(..) => "TupleVariant", + VariantKind::Struct { .. } => "StructVariant", + }, } } } @@ -167,10 +174,17 @@ impl<'a> Vertex<'a> { } pub(super) fn as_variant(&self) -> Option<&'a Variant> { - self.as_item().and_then(|item| match &item.inner { - rustdoc_types::ItemEnum::Variant(v) => Some(v), + match &self.kind { + VertexKind::Variant(variant) => variant.variant(), _ => None, - }) + } + } + + pub(super) fn as_enum_variant(&self) -> Option<&'a EnumVariant> { + match &self.kind { + VertexKind::Variant(variant) => Some(variant), + _ => None, + } } pub(super) fn as_path(&self) -> Option<&'a [String]> { @@ -257,9 +271,9 @@ impl<'a> Vertex<'a> { } } - pub(super) fn as_discriminant(&self) -> Option<&'a rustdoc_types::Discriminant> { + pub(super) fn as_discriminant(&self) -> Option<&String> { match &self.kind { - VertexKind::Discriminant(discriminant) => Some(discriminant), + VertexKind::Discriminant(variant) => Some(variant), _ => None, } } @@ -288,9 +302,3 @@ impl<'a> From<&'a Abi> for VertexKind<'a> { Self::FunctionAbi(a) } } - -impl<'a> From<&'a Discriminant> for VertexKind<'a> { - fn from(d: &'a Discriminant) -> Self { - Self::Discriminant(d) - } -} diff --git a/src/rustdoc_schema.graphql b/src/rustdoc_schema.graphql index 7fea71c..15ce851 100644 --- a/src/rustdoc_schema.graphql +++ b/src/rustdoc_schema.graphql @@ -441,13 +441,6 @@ https://docs.rs/rustdoc-types/0.28.0/rustdoc_types/struct.Discriminant.html https://doc.rust-lang.org/reference/items/enumerations.html """ type Discriminant { - """ - The expression that produced the discriminant. Preserves the original formatting seen in source code (e.g. suffixes, hexadecimal, and underscores). - - In some cases, when the value is too complex, this may be a placeholder value such as `"{ _ }"`. - """ - expr: String! - """ The numerical value of the discriminant. Stored as a string. Ranges from `i128::MIN` to `u128::MAX`. """ diff --git a/test_crates/enum_discriminants/src/lib.rs b/test_crates/enum_discriminants/src/lib.rs index eb09d0c..154c0a2 100644 --- a/test_crates/enum_discriminants/src/lib.rs +++ b/test_crates/enum_discriminants/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(incomplete_features)] +#![feature(repr128)] /// Some examples from #[repr(C)] @@ -27,3 +29,10 @@ pub enum Fieldful { Unit2 = 9 } +#[repr(i128)] +pub enum Pathological { + Min = i128::MIN, + MinPlusOne, + MinPlusTwo, + Max = i128::MAX, +}