about summary refs log tree commit diff
path: root/compiler/rustc_index_macros
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2023-10-24 00:16:14 +0000
committerMichael Goulet <michael@errs.io>2023-11-18 00:20:00 +0000
commit4506681e2f53e17df0d74cd230948dc40f7d0e8d (patch)
tree30f5887e9c39e750fd89e72b7007075a11c81be3 /compiler/rustc_index_macros
parent2831701757eb7b3105eda26a306c2f3a97e2664b (diff)
downloadrust-4506681e2f53e17df0d74cd230948dc40f7d0e8d.tar.gz
rust-4506681e2f53e17df0d74cd230948dc40f7d0e8d.zip
Begin nightly-ifying rustc_type_ir
Diffstat (limited to 'compiler/rustc_index_macros')
-rw-r--r--compiler/rustc_index_macros/Cargo.toml17
-rw-r--r--compiler/rustc_index_macros/src/lib.rs30
-rw-r--r--compiler/rustc_index_macros/src/newtype.rs324
3 files changed, 371 insertions, 0 deletions
diff --git a/compiler/rustc_index_macros/Cargo.toml b/compiler/rustc_index_macros/Cargo.toml
new file mode 100644
index 00000000000..c4ca29db3c2
--- /dev/null
+++ b/compiler/rustc_index_macros/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "rustc_index_macros"
+version = "0.0.0"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+synstructure = "0.13.0"
+syn = { version = "2.0.9", features = ["full"] }
+proc-macro2 = "1"
+quote = "1"
+
+[features]
+default = ["nightly"]
+nightly = []
\ No newline at end of file
diff --git a/compiler/rustc_index_macros/src/lib.rs b/compiler/rustc_index_macros/src/lib.rs
new file mode 100644
index 00000000000..f6a6175374a
--- /dev/null
+++ b/compiler/rustc_index_macros/src/lib.rs
@@ -0,0 +1,30 @@
+#![cfg_attr(feature = "nightly", feature(allow_internal_unstable))]
+#![cfg_attr(feature = "nightly", allow(internal_features))]
+
+use proc_macro::TokenStream;
+
+mod newtype;
+
+/// Creates a struct type `S` that can be used as an index with
+/// `IndexVec` and so on.
+///
+/// There are two ways of interacting with these indices:
+///
+/// - The `From` impls are the preferred way. So you can do
+///   `S::from(v)` with a `usize` or `u32`. And you can convert back
+///   to an integer with `u32::from(s)`.
+///
+/// - Alternatively, you can use the methods `S::new(v)` and `s.index()`
+///   to create/return a value.
+///
+/// Internally, the index uses a u32, so the index must not exceed
+/// `u32::MAX`. You can also customize things like the `Debug` impl,
+/// what traits are derived, and so forth via the macro.
+#[proc_macro]
+#[cfg_attr(
+    feature = "nightly",
+    allow_internal_unstable(step_trait, rustc_attrs, trusted_step, spec_option_partial_eq)
+)]
+pub fn newtype_index(input: TokenStream) -> TokenStream {
+    newtype::newtype(input)
+}
diff --git a/compiler/rustc_index_macros/src/newtype.rs b/compiler/rustc_index_macros/src/newtype.rs
new file mode 100644
index 00000000000..2a974fd2628
--- /dev/null
+++ b/compiler/rustc_index_macros/src/newtype.rs
@@ -0,0 +1,324 @@
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use syn::parse::*;
+use syn::*;
+
+// We parse the input and emit the output in a single step.
+// This field stores the final macro output
+struct Newtype(TokenStream);
+
+impl Parse for Newtype {
+    fn parse(input: ParseStream<'_>) -> Result<Self> {
+        let mut attrs = input.call(Attribute::parse_outer)?;
+        let vis: Visibility = input.parse()?;
+        input.parse::<Token![struct]>()?;
+        let name: Ident = input.parse()?;
+
+        let body;
+        braced!(body in input);
+
+        // Any additional `#[derive]` macro paths to apply
+        let mut derive_paths: Vec<Path> = Vec::new();
+        let mut debug_format: Option<Lit> = None;
+        let mut max = None;
+        let mut consts = Vec::new();
+        let mut encodable = true;
+        let mut ord = true;
+        let mut gate_rustc_only = quote! {};
+        let mut gate_rustc_only_cfg = quote! { all() };
+
+        attrs.retain(|attr| match attr.path().get_ident() {
+            Some(ident) => match &*ident.to_string() {
+                "gate_rustc_only" => {
+                    gate_rustc_only = quote! { #[cfg(feature = "nightly")] };
+                    gate_rustc_only_cfg = quote! { feature = "nightly" };
+                    false
+                }
+                "custom_encodable" => {
+                    encodable = false;
+                    false
+                }
+                "no_ord_impl" => {
+                    ord = false;
+                    false
+                }
+                "max" => {
+                    let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta
+                    else {
+                        panic!("#[max = NUMBER] attribute requires max value");
+                    };
+
+                    if let Some(old) = max.replace(lit.lit.clone()) {
+                        panic!("Specified multiple max: {old:?}");
+                    }
+
+                    false
+                }
+                "debug_format" => {
+                    let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta
+                    else {
+                        panic!("#[debug_format = FMT] attribute requires a format");
+                    };
+
+                    if let Some(old) = debug_format.replace(lit.lit.clone()) {
+                        panic!("Specified multiple debug format options: {old:?}");
+                    }
+
+                    false
+                }
+                _ => true,
+            },
+            _ => true,
+        });
+
+        loop {
+            // We've parsed everything that the user provided, so we're done
+            if body.is_empty() {
+                break;
+            }
+
+            // Otherwise, we are parsing a user-defined constant
+            let const_attrs = body.call(Attribute::parse_outer)?;
+            body.parse::<Token![const]>()?;
+            let const_name: Ident = body.parse()?;
+            body.parse::<Token![=]>()?;
+            let const_val: Expr = body.parse()?;
+            body.parse::<Token![;]>()?;
+            consts.push(quote! { #(#const_attrs)* #vis const #const_name: #name = #name::from_u32(#const_val); });
+        }
+
+        let debug_format =
+            debug_format.unwrap_or_else(|| Lit::Str(LitStr::new("{}", Span::call_site())));
+
+        // shave off 256 indices at the end to allow space for packing these indices into enums
+        let max = max.unwrap_or_else(|| Lit::Int(LitInt::new("0xFFFF_FF00", Span::call_site())));
+
+        let encodable_impls = if encodable {
+            quote! {
+                #gate_rustc_only
+                impl<D: ::rustc_serialize::Decoder> ::rustc_serialize::Decodable<D> for #name {
+                    fn decode(d: &mut D) -> Self {
+                        Self::from_u32(d.read_u32())
+                    }
+                }
+                #gate_rustc_only
+                impl<E: ::rustc_serialize::Encoder> ::rustc_serialize::Encodable<E> for #name {
+                    fn encode(&self, e: &mut E) {
+                        e.emit_u32(self.private);
+                    }
+                }
+            }
+        } else {
+            quote! {}
+        };
+
+        if ord {
+            derive_paths.push(parse_quote!(Ord));
+            derive_paths.push(parse_quote!(PartialOrd));
+        }
+
+        let step = if ord {
+            quote! {
+                #gate_rustc_only
+                impl ::std::iter::Step for #name {
+                    #[inline]
+                    fn steps_between(start: &Self, end: &Self) -> Option<usize> {
+                        <usize as ::std::iter::Step>::steps_between(
+                            &Self::index(*start),
+                            &Self::index(*end),
+                        )
+                    }
+
+                    #[inline]
+                    fn forward_checked(start: Self, u: usize) -> Option<Self> {
+                        Self::index(start).checked_add(u).map(Self::from_usize)
+                    }
+
+                    #[inline]
+                    fn backward_checked(start: Self, u: usize) -> Option<Self> {
+                        Self::index(start).checked_sub(u).map(Self::from_usize)
+                    }
+                }
+
+                // Safety: The implementation of `Step` upholds all invariants.
+                #gate_rustc_only
+                unsafe impl ::std::iter::TrustedStep for #name {}
+            }
+        } else {
+            quote! {}
+        };
+
+        let debug_impl = quote! {
+            impl ::std::fmt::Debug for #name {
+                fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+                    write!(fmt, #debug_format, self.as_u32())
+                }
+            }
+        };
+
+        let spec_partial_eq_impl = if let Lit::Int(max) = &max {
+            if let Ok(max_val) = max.base10_parse::<u32>() {
+                quote! {
+                    #gate_rustc_only
+                    impl core::option::SpecOptionPartialEq for #name {
+                        #[inline]
+                        fn eq(l: &Option<Self>, r: &Option<Self>) -> bool {
+                            if #max_val < u32::MAX {
+                                l.map(|i| i.private).unwrap_or(#max_val+1) == r.map(|i| i.private).unwrap_or(#max_val+1)
+                            } else {
+                                match (l, r) {
+                                    (Some(l), Some(r)) => r == l,
+                                    (None, None) => true,
+                                    _ => false
+                                }
+                            }
+                        }
+                    }
+                }
+            } else {
+                quote! {}
+            }
+        } else {
+            quote! {}
+        };
+
+        Ok(Self(quote! {
+            #(#attrs)*
+            #[derive(Clone, Copy, PartialEq, Eq, Hash, #(#derive_paths),*)]
+            #[cfg_attr(#gate_rustc_only_cfg, rustc_layout_scalar_valid_range_end(#max))]
+            #[cfg_attr(#gate_rustc_only_cfg, rustc_pass_by_value)]
+            #vis struct #name {
+                private: u32,
+            }
+
+            #(#consts)*
+
+            impl #name {
+                /// Maximum value the index can take, as a `u32`.
+                #vis const MAX_AS_U32: u32  = #max;
+
+                /// Maximum value the index can take.
+                #vis const MAX: Self = Self::from_u32(#max);
+
+                /// Creates a new index from a given `usize`.
+                ///
+                /// # Panics
+                ///
+                /// Will panic if `value` exceeds `MAX`.
+                #[inline]
+                #vis const fn from_usize(value: usize) -> Self {
+                    assert!(value <= (#max as usize));
+                    // SAFETY: We just checked that `value <= max`.
+                    unsafe {
+                        Self::from_u32_unchecked(value as u32)
+                    }
+                }
+
+                /// Creates a new index from a given `u32`.
+                ///
+                /// # Panics
+                ///
+                /// Will panic if `value` exceeds `MAX`.
+                #[inline]
+                #vis const fn from_u32(value: u32) -> Self {
+                    assert!(value <= #max);
+                    // SAFETY: We just checked that `value <= max`.
+                    unsafe {
+                        Self::from_u32_unchecked(value)
+                    }
+                }
+
+                /// Creates a new index from a given `u32`.
+                ///
+                /// # Safety
+                ///
+                /// The provided value must be less than or equal to the maximum value for the newtype.
+                /// Providing a value outside this range is undefined due to layout restrictions.
+                ///
+                /// Prefer using `from_u32`.
+                #[inline]
+                #vis const unsafe fn from_u32_unchecked(value: u32) -> Self {
+                    Self { private: value }
+                }
+
+                /// Extracts the value of this index as a `usize`.
+                #[inline]
+                #vis const fn index(self) -> usize {
+                    self.as_usize()
+                }
+
+                /// Extracts the value of this index as a `u32`.
+                #[inline]
+                #vis const fn as_u32(self) -> u32 {
+                    self.private
+                }
+
+                /// Extracts the value of this index as a `usize`.
+                #[inline]
+                #vis const fn as_usize(self) -> usize {
+                    self.as_u32() as usize
+                }
+            }
+
+            impl std::ops::Add<usize> for #name {
+                type Output = Self;
+
+                fn add(self, other: usize) -> Self {
+                    Self::from_usize(self.index() + other)
+                }
+            }
+
+            impl rustc_index::Idx for #name {
+                #[inline]
+                fn new(value: usize) -> Self {
+                    Self::from_usize(value)
+                }
+
+                #[inline]
+                fn index(self) -> usize {
+                    self.as_usize()
+                }
+            }
+
+            #step
+
+            #spec_partial_eq_impl
+
+            impl From<#name> for u32 {
+                #[inline]
+                fn from(v: #name) -> u32 {
+                    v.as_u32()
+                }
+            }
+
+            impl From<#name> for usize {
+                #[inline]
+                fn from(v: #name) -> usize {
+                    v.as_usize()
+                }
+            }
+
+            impl From<usize> for #name {
+                #[inline]
+                fn from(value: usize) -> Self {
+                    Self::from_usize(value)
+                }
+            }
+
+            impl From<u32> for #name {
+                #[inline]
+                fn from(value: u32) -> Self {
+                    Self::from_u32(value)
+                }
+            }
+
+            #encodable_impls
+            #debug_impl
+        }))
+    }
+}
+
+pub fn newtype(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let input = parse_macro_input!(input as Newtype);
+    input.0.into()
+}