From ff3379486fd5bda7d8279448911ba569083ecfdb Mon Sep 17 00:00:00 2001 From: Kyle Anthony Williams Date: Sun, 1 Sep 2024 12:42:56 -0400 Subject: [PATCH] Add enum discriminants (#337) * Remove nightly from rustdoc v28 test matrix, since that's now v29. (#332) * Rust 1.78 is no longer beta. (#334) * Add enum discriminants * discriminant clone to lifetime annotation * complicate test examples * improved discriminant docs * hedge bets against placeholder representation Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> * finally got implicit discriminants working * remove sync structs * move to Cow<'a, str> for discriminants * Various clean-ups, still fighting closure bounds *sigh* error[E0521]: borrowed data escapes outside of closure --> src/adapter/edges.rs:304:28 | 260 | pub(super) fn resolve_variant_edge<'a, V: AsVertex> + 'a>( | -- lifetime `'a` defined here ... 302 | "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { | ------ - let's call the lifetime of this reference `'1` | | | `vertex` is a reference that is only valid in the closure body 303 | let origin = vertex.origin; 304 | let enum_var = vertex | ____________________________^ 305 | | .as_variant() | | ^ | | | | |_____________________________`vertex` escapes the closure body here | argument requires that `'1` must outlive `'a` * Actually clone Cow; same error as before, though... * Got the sucker. * Wrap up * Better dcocs for discriminant Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> * Finish up docs, improve tests * Add name back. --------- Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> --- src/adapter/edges.rs | 50 +++- src/adapter/enum_variant.rs | 320 ++++++++++++++++++++++ src/adapter/mod.rs | 4 + src/adapter/origin.rs | 26 +- src/adapter/properties.rs | 16 ++ src/adapter/tests.rs | 164 +++++++++++ src/adapter/vertex.rs | 34 ++- src/rustdoc_schema.graphql | 91 ++++++ test_crates/enum_discriminants/Cargo.toml | 9 + test_crates/enum_discriminants/src/lib.rs | 44 +++ 10 files changed, 742 insertions(+), 16 deletions(-) create mode 100644 src/adapter/enum_variant.rs create mode 100644 test_crates/enum_discriminants/Cargo.toml create mode 100644 test_crates/enum_discriminants/src/lib.rs diff --git a/src/adapter/edges.rs b/src/adapter/edges.rs index 2bd328c..84dae80 100644 --- a/src/adapter/edges.rs +++ b/src/adapter/edges.rs @@ -1,4 +1,5 @@ use rustdoc_types::{GenericBound::TraitBound, Id, ItemEnum, VariantKind}; +use std::rc::Rc; 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>, @@ -263,7 +266,10 @@ pub(super) fn resolve_variant_edge<'a, V: AsVertex> + 'a>( match edge_name { "field" => resolve_neighbors_with(contexts, move |vertex| { let origin = vertex.origin; - let item = vertex.as_variant().expect("vertex was not a Variant"); + let item = vertex + .as_variant() + .expect("vertex was not a Variant") + .variant(); let item_index = match origin { Origin::CurrentCrate => ¤t_crate.inner.index, Origin::PreviousCrate => { @@ -293,6 +299,14 @@ pub(super) fn resolve_variant_edge<'a, V: AsVertex> + 'a>( })), } }), + "discriminant" => resolve_neighbors_with(contexts, move |vertex: &'_ Vertex<'a>| { + let origin = vertex.origin; + let enum_var = vertex.as_variant().expect("vertex was not a Variant"); + let discriminant = enum_var.discriminant(); + Box::new(std::iter::once( + origin.make_discriminant_vertex(discriminant), + )) + }), _ => unreachable!("resolve_variant_edge {edge_name}"), } } @@ -317,9 +331,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 = 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(); + Rc::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..b65e7ce --- /dev/null +++ b/src/adapter/enum_variant.rs @@ -0,0 +1,320 @@ +use rustdoc_types::{Item, ItemEnum, Variant}; +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; + +#[non_exhaustive] +#[derive(Debug, Clone)] +pub struct EnumVariant<'a> { + item: &'a Item, + discriminants: Rc>, + 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>>, +} + +impl<'a> Debug for LazyDiscriminants<'a> { + 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> { + self.discriminants + .get_or_init(|| assign_discriminants(&self.variants)) + } +} + +impl<'a> EnumVariant<'a> { + pub(super) fn new( + item: &'a Item, + discriminants: Rc>, + 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) -> Cow<'a, str> { + self.discriminants + .get_discriminants() + .get(self.index) + .expect("self.index should exist in self.discriminants") + .clone() + } + + #[inline] + pub(super) fn item(&self) -> &'a Item { + self.item + } +} + +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<'a>(variants: &[&'a 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(); + 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 = 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 997e5f6..9dbb66f 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; @@ -158,6 +159,9 @@ impl<'a> Adapter<'a> for RustdocAdapter<'a> { properties::resolve_associated_constant_property(contexts, property_name) } "Constant" => properties::resolve_constant_property(contexts, property_name), + "Discriminant" => { + properties::resolve_discriminant_property(contexts, property_name) + } _ => unreachable!("resolve_property {type_name} {property_name}"), } } diff --git a/src/adapter/origin.rs b/src/adapter/origin.rs index 5d92bdc..71219e0 100644 --- a/src/adapter/origin.rs +++ b/src/adapter/origin.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::{borrow::Cow, rc::Rc}; 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)] @@ -96,4 +99,23 @@ impl Origin { kind: abi.into(), } } + + pub(super) fn make_discriminant_vertex<'a>(&self, value: Cow<'a, str>) -> Vertex<'a> { + Vertex { + origin: *self, + kind: VertexKind::Discriminant(value), + } + } + + pub(super) fn make_variant_vertex<'a>( + &self, + item: &'a Item, + discriminants: Rc>, + index: usize, + ) -> Vertex<'a> { + Vertex { + origin: *self, + kind: VertexKind::Variant(EnumVariant::new(item, discriminants, index)), + } + } } diff --git a/src/adapter/properties.rs b/src/adapter/properties.rs index ad6de2a..03e71ca 100644 --- a/src/adapter/properties.rs +++ b/src/adapter/properties.rs @@ -577,3 +577,19 @@ pub(crate) fn resolve_constant_property<'a, V: AsVertex> + 'a>( _ => unreachable!("Constant property {property_name}"), } } + +pub(crate) fn resolve_discriminant_property<'a, V: AsVertex> + 'a>( + contexts: ContextIterator<'a, V>, + property_name: &str, +) -> ContextOutcomeIterator<'a, V, FieldValue> { + match property_name { + "value" => resolve_property_with(contexts, |vertex| { + vertex + .as_discriminant() + .expect("vertex was not a Discriminant") + .to_string() + .into() + }), + _ => unreachable!("Discriminant property {property_name}"), + } +} diff --git a/src/adapter/tests.rs b/src/adapter/tests.rs index 28ce035..c65e9ed 100644 --- a/src/adapter/tests.rs +++ b/src/adapter/tests.rs @@ -1995,3 +1995,167 @@ fn function_has_body() { similar_asserts::assert_eq!(expected_results, results); } + +#[test] +fn enum_discriminants() { + let path = "./localdata/test_data/enum_discriminants/rustdoc.json"; + let content = std::fs::read_to_string(path) + .with_context(|| format!("Could not load {path} file, did you forget to run ./scripts/regenerate_test_rustdocs.sh ?")) + .expect("failed to load rustdoc"); + + let crate_ = serde_json::from_str(&content).expect("failed to parse rustdoc"); + let indexed_crate = IndexedCrate::new(&crate_); + let adapter = RustdocAdapter::new(&indexed_crate, None); + + let query = r#" +{ + Crate { + item { + ... on Enum { + enum_name: name @output + variant { + variant_name: name @output + discriminant @optional { + value @output + } + } + } + } + } +} +"#; + let variables: BTreeMap<&str, &str> = btreemap! {}; + + let schema = + Schema::parse(include_str!("../rustdoc_schema.graphql")).expect("schema failed to parse"); + + #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, serde::Deserialize)] + struct Output { + enum_name: String, + variant_name: String, + value: Option, + } + + let mut results: Vec = + trustfall::execute_query(&schema, adapter.into(), query, variables.clone()) + .expect("failed to run query") + .map(|row| row.try_into_struct().expect("shape mismatch")) + .collect(); + results.sort_unstable(); + + similar_asserts::assert_eq!( + vec![ + Output { + enum_name: "A".into(), + variant_name: "Five".into(), + value: Some("100".into(),), + }, + Output { + enum_name: "A".into(), + variant_name: "Four".into(), + value: Some("99".into(),), + }, + Output { + enum_name: "A".into(), + variant_name: "One".into(), + value: Some("1".into(),), + }, + Output { + enum_name: "A".into(), + variant_name: "Three".into(), + value: Some("3".into(),), + }, + Output { + enum_name: "A".into(), + variant_name: "Two".into(), + value: Some("2".into(),), + }, + Output { + enum_name: "A".into(), + variant_name: "Zero".into(), + value: Some("0".into(),), + }, + Output { + enum_name: "Fieldful".into(), + variant_name: "Struct".into(), + value: Some("2".into(),), + }, + Output { + enum_name: "Fieldful".into(), + variant_name: "Tuple".into(), + value: Some("1".into(),), + }, + Output { + enum_name: "Fieldful".into(), + variant_name: "Unit".into(), + value: Some("0".into(),), + }, + Output { + enum_name: "Fieldful".into(), + variant_name: "Unit2".into(), + value: Some("9".into(),), + }, + Output { + enum_name: "FieldfulNoRepr".into(), + variant_name: "Struct".into(), + value: Some("2".into(),), + }, + Output { + enum_name: "FieldfulNoRepr".into(), + variant_name: "Tuple".into(), + value: Some("1".into(),), + }, + Output { + enum_name: "FieldfulNoRepr".into(), + variant_name: "Unit".into(), + value: Some("0".into(),), + }, + Output { + enum_name: "FieldlessWithDiscrimants".into(), + variant_name: "First".into(), + value: Some("10".into(),), + }, + Output { + enum_name: "FieldlessWithDiscrimants".into(), + variant_name: "Second".into(), + value: Some("20".into(),), + }, + Output { + enum_name: "FieldlessWithDiscrimants".into(), + variant_name: "Struct".into(), + value: Some("21".into(),), + }, + Output { + enum_name: "FieldlessWithDiscrimants".into(), + variant_name: "Tuple".into(), + value: Some("11".into(),), + }, + Output { + enum_name: "FieldlessWithDiscrimants".into(), + variant_name: "Unit".into(), + value: Some("22".into(),), + }, + Output { + enum_name: "Pathological".into(), + variant_name: "Max".into(), + value: Some("170141183460469231731687303715884105727".into(),), + }, + Output { + enum_name: "Pathological".into(), + variant_name: "Min".into(), + value: Some("-170141183460469231731687303715884105728".into(),), + }, + Output { + enum_name: "Pathological".into(), + variant_name: "MinPlusOne".into(), + value: Some("-170141183460469231731687303715884105727".into(),), + }, + Output { + enum_name: "Pathological".into(), + variant_name: "MinPlusTwo".into(), + value: Some("-170141183460469231731687303715884105726".into(),), + }, + ], + results + ); +} diff --git a/src/adapter/vertex.rs b/src/adapter/vertex.rs index abccb28..7f16436 100644 --- a/src/adapter/vertex.rs +++ b/src/adapter/vertex.rs @@ -1,8 +1,8 @@ -use std::rc::Rc; +use std::{borrow::Cow, rc::Rc}; use rustdoc_types::{ Abi, Constant, Crate, Enum, Function, Impl, Item, Module, Path, Span, Static, Struct, Trait, - Type, Union, Variant, VariantKind, + Type, Union, 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,6 +36,8 @@ pub enum VertexKind<'a> { ImplementedTrait(&'a Path, &'a Item), FunctionParameter(&'a str), FunctionAbi(&'a Abi), + Discriminant(Cow<'a, str>), + Variant(EnumVariant<'a>), } impl<'a> Typename for Vertex<'a> { @@ -43,7 +45,7 @@ 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", @@ -77,6 +79,12 @@ impl<'a> Typename for Vertex<'a> { }, VertexKind::FunctionParameter(..) => "FunctionParameter", VertexKind::FunctionAbi(..) => "FunctionAbi", + VertexKind::Discriminant(..) => "Discriminant", + VertexKind::Variant(ev) => match ev.variant().kind { + VariantKind::Plain => "PlainVariant", + VariantKind::Tuple(..) => "TupleVariant", + VariantKind::Struct { .. } => "StructVariant", + }, } } } @@ -109,8 +117,9 @@ impl<'a> Vertex<'a> { } pub(super) fn as_item(&self) -> Option<&'a Item> { - match self.kind { + match &self.kind { VertexKind::Item(item) => Some(item), + VertexKind::Variant(variant) => Some(variant.item()), _ => None, } } @@ -164,11 +173,11 @@ 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), + pub(super) fn as_variant(&self) -> Option<&'_ EnumVariant<'a>> { + match &self.kind { + VertexKind::Variant(variant) => Some(variant), _ => None, - }) + } } pub(super) fn as_path(&self) -> Option<&'a [String]> { @@ -254,6 +263,13 @@ impl<'a> Vertex<'a> { _ => None, } } + + pub(super) fn as_discriminant(&self) -> Option<&Cow<'a, str>> { + match &self.kind { + VertexKind::Discriminant(variant) => Some(variant), + _ => None, + } + } } impl<'a> From<&'a Item> for VertexKind<'a> { diff --git a/src/rustdoc_schema.graphql b/src/rustdoc_schema.graphql index e8a8a99..4030877 100644 --- a/src/rustdoc_schema.graphql +++ b/src/rustdoc_schema.graphql @@ -429,6 +429,37 @@ interface Variant implements Item { # own edges field: [StructField!] + + """ + The discriminant value of this variant, if its value is well-defined. + + A discriminant is an integer value that is logically associated with an enum variant, + and is used to determine which variant an enum value holds. + + Discriminant values are not always well-defined. + + In order for variants to have well-defined discriminants, their enum must either + - define a primitive representation (e.g., `#[repr(u8)]`), or + - be "unit-only" i.e. consisting solely of unit variants. + + The variants of such enums may be explicitly assigned a discriminant value, or + the discriminant may be defined implicitly: + - The first variant has a discriminant of zero, unless explicitly assigned a value. + - Subsequent variants have a discriminant one higher than the previous variant, + unless explicitly assigned a value. + """ + discriminant: Discriminant +} + +""" +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 numerical value of the discriminant. Stored as a string. Ranges from `i128::MIN` to `u128::MAX`. + """ + value: String! } """ @@ -483,6 +514,26 @@ type PlainVariant implements Item & Variant { # edges from Variant field: [StructField!] + + """ + The discriminant value of this variant, if its value is well-defined. + + A discriminant is an integer value that is logically associated with an enum variant, + and is used to determine which variant an enum value holds. + + Discriminant values are not always well-defined. + + In order for variants to have well-defined discriminants, their enum must either + - define a primitive representation (e.g., `#[repr(u8)]`), or + - be "unit-only" i.e. consisting solely of unit variants. + + The variants of such enums may be explicitly assigned a discriminant value, or + the discriminant may be defined implicitly: + - The first variant has a discriminant of zero, unless explicitly assigned a value. + - Subsequent variants have a discriminant one higher than the previous variant, + unless explicitly assigned a value. + """ + discriminant: Discriminant } """ @@ -537,6 +588,26 @@ type TupleVariant implements Item & Variant { # edges from Variant field: [StructField!] + + """ + The discriminant value of this variant, if its value is well-defined. + + A discriminant is an integer value that is logically associated with an enum variant, + and is used to determine which variant an enum value holds. + + Discriminant values are not always well-defined. + + In order for variants to have well-defined discriminants, their enum must either + - define a primitive representation (e.g., `#[repr(u8)]`), or + - be "unit-only" i.e. consisting solely of unit variants. + + The variants of such enums may be explicitly assigned a discriminant value, or + the discriminant may be defined implicitly: + - The first variant has a discriminant of zero, unless explicitly assigned a value. + - Subsequent variants have a discriminant one higher than the previous variant, + unless explicitly assigned a value. + """ + discriminant: Discriminant } """ @@ -591,6 +662,26 @@ type StructVariant implements Item & Variant { # edges from Variant field: [StructField!] + + """ + The discriminant value of this variant, if its value is well-defined. + + A discriminant is an integer value that is logically associated with an enum variant, + and is used to determine which variant an enum value holds. + + Discriminant values are not always well-defined. + + In order for variants to have well-defined discriminants, their enum must either + - define a primitive representation (e.g., `#[repr(u8)]`), or + - be "unit-only" i.e. consisting solely of unit variants. + + The variants of such enums may be explicitly assigned a discriminant value, or + the discriminant may be defined implicitly: + - The first variant has a discriminant of zero, unless explicitly assigned a value. + - Subsequent variants have a discriminant one higher than the previous variant, + unless explicitly assigned a value. + """ + discriminant: Discriminant } """ diff --git a/test_crates/enum_discriminants/Cargo.toml b/test_crates/enum_discriminants/Cargo.toml new file mode 100644 index 0000000..e9b8c5f --- /dev/null +++ b/test_crates/enum_discriminants/Cargo.toml @@ -0,0 +1,9 @@ +[package] +publish = false +name = "enum_discriminants" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test_crates/enum_discriminants/src/lib.rs b/test_crates/enum_discriminants/src/lib.rs new file mode 100644 index 0000000..faafcbe --- /dev/null +++ b/test_crates/enum_discriminants/src/lib.rs @@ -0,0 +1,44 @@ +#![allow(incomplete_features)] +#![feature(repr128)] +/// Some examples from + +#[repr(C)] +pub enum A { + Zero, + One = 1, + Two = 1 + 1, + Three, + Four = 99, + Five +} + +#[repr(u8)] +pub enum FieldlessWithDiscrimants { + First = 10, + Tuple(), + Second = 20, + Struct{}, + Unit, +} + +#[repr(i64)] +pub enum Fieldful { + Unit, + Tuple(bool), + Struct{a: bool}, + Unit2 = 9 +} + +pub enum FieldfulNoRepr { + Unit, + Tuple(bool), + Struct{a: bool}, +} + +#[repr(i128)] +pub enum Pathological { + Min = i128::MIN, + MinPlusOne, + MinPlusTwo, + Max = i128::MAX, +}