-
Notifications
You must be signed in to change notification settings - Fork 99
Disallow duplicated enum variant indices #628
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
e6b0b0f
c986e06
56c6b3a
722f02e
f2cc6c0
7aa0d89
7b92913
614f2b7
9e3c8a6
df86890
6f31b67
f2255ee
add9eae
77396b9
a25db72
9112b92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,13 +17,14 @@ | |
| //! NOTE: attributes finder must be checked using check_attribute first, | ||
| //! otherwise the macro can panic. | ||
|
|
||
| use std::str::FromStr; | ||
| use std::{collections::HashMap, str::FromStr}; | ||
|
|
||
| use proc_macro2::TokenStream; | ||
| use proc_macro2::{Span, TokenStream}; | ||
| use quote::{quote, ToTokens}; | ||
| use syn::{ | ||
| parse::Parse, punctuated::Punctuated, spanned::Spanned, token, Attribute, Data, DeriveInput, | ||
| Field, Fields, FieldsNamed, FieldsUnnamed, Lit, Meta, MetaNameValue, NestedMeta, Path, Variant, | ||
| ExprLit, Field, Fields, FieldsNamed, FieldsUnnamed, Lit, Meta, MetaNameValue, NestedMeta, Path, | ||
| Variant, | ||
| }; | ||
|
|
||
| fn find_meta_item<'a, F, R, I, M>(mut itr: I, mut pred: F) -> Option<R> | ||
|
|
@@ -37,32 +38,99 @@ where | |
| }) | ||
| } | ||
|
|
||
| /// Look for a `#[scale(index = $int)]` attribute on a variant. If no attribute | ||
| /// is found, fall back to the discriminant or just the variant index. | ||
| pub fn variant_index(v: &Variant, i: usize) -> TokenStream { | ||
| // first look for an attribute | ||
| let index = find_meta_item(v.attrs.iter(), |meta| { | ||
| if let NestedMeta::Meta(Meta::NameValue(ref nv)) = meta { | ||
| if nv.path.is_ident("index") { | ||
| if let Lit::Int(ref v) = nv.lit { | ||
| let byte = v | ||
| .base10_parse::<u8>() | ||
| .expect("Internal error, index attribute must have been checked"); | ||
| return Some(byte); | ||
| /// Indexes used while defining variants. | ||
| pub struct UsedIndexes { | ||
| /// Map from index of the variant to it's attribute or definition span | ||
| used_set: HashMap<u8, Span>, | ||
| /// We need this u8 to correctly assign indexes to variants | ||
| /// that are not annotated by coded(index = ?) or explicit discriminant | ||
| current: u8, | ||
| } | ||
|
|
||
| impl UsedIndexes { | ||
| /// Build a Set of used indexes for use with #[scale(index = $int)] attribute or | ||
| /// explicit discriminant on the variant | ||
| pub fn from<'a, I: Iterator<Item = &'a Variant>>(values: I) -> syn::Result<Self> { | ||
| let mut map: HashMap<u8, Span> = HashMap::new(); | ||
| for v in values { | ||
| if let Some((index, nv)) = find_meta_item(v.attrs.iter(), |meta| { | ||
| if let NestedMeta::Meta(Meta::NameValue(ref nv)) = meta { | ||
| if nv.path.is_ident("index") { | ||
| if let Lit::Int(ref v) = nv.lit { | ||
| let byte = v | ||
| .base10_parse::<u8>() | ||
| .expect("Internal error, index attribute must have been checked"); | ||
| return Some((byte, nv.span())); | ||
| } | ||
| } | ||
| } | ||
| None | ||
| }) { | ||
| if let Some(span) = map.insert(index, nv.span()) { | ||
| let mut error = syn::Error::new(nv.span(), "Duplicate variant index. qed"); | ||
| error.combine(syn::Error::new(span, "Variant index already defined here.")); | ||
| return Err(error) | ||
| } | ||
| } else if let Some(( | ||
| _, | ||
| expr @ syn::Expr::Lit(ExprLit { lit: syn::Lit::Int(lit_int), .. }), | ||
|
||
| )) = v.discriminant.as_ref() | ||
| { | ||
| let index = lit_int | ||
| .base10_parse::<u8>() | ||
| .expect("Internal error, index attribute must have been checked"); | ||
| if let Some(span) = map.insert(index, expr.span()) { | ||
| let mut error = syn::Error::new(expr.span(), "Duplicate variant index. qed"); | ||
| error.combine(syn::Error::new(span, "Variant index already defined here.")); | ||
| return Err(error) | ||
| } | ||
| } | ||
| } | ||
| Ok(Self { current: 0, used_set: map }) | ||
| } | ||
|
|
||
| None | ||
| }); | ||
|
|
||
| // then fallback to discriminant or just index | ||
| index.map(|i| quote! { #i }).unwrap_or_else(|| { | ||
| v.discriminant | ||
| .as_ref() | ||
| .map(|(_, expr)| quote! { #expr }) | ||
| .unwrap_or_else(|| quote! { #i }) | ||
| }) | ||
| /// Look for a `#[scale(index = $int)]` attribute on a variant. If no attribute | ||
| /// is found, fall back to the discriminant or just the variant index. | ||
| pub fn variant_index(&mut self, v: &Variant) -> syn::Result<TokenStream> { | ||
| // first look for an attribute | ||
| let index = find_meta_item(v.attrs.iter(), |meta| { | ||
| if let NestedMeta::Meta(Meta::NameValue(ref nv)) = meta { | ||
| if nv.path.is_ident("index") { | ||
| if let Lit::Int(ref v) = nv.lit { | ||
| let byte = v | ||
| .base10_parse::<u8>() | ||
| .expect("Internal error, index attribute must have been checked"); | ||
| return Some(byte); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| None | ||
| }); | ||
|
|
||
| index.map_or_else( | ||
| || match v.discriminant.as_ref() { | ||
| Some((_, expr)) => Ok(quote! { #expr }), | ||
| None => { | ||
| let idx = self.next_index(); | ||
| Ok(quote! { #idx }) | ||
| }, | ||
| }, | ||
| |i| Ok(quote! { #i }), | ||
| ) | ||
| } | ||
pkhry marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| fn next_index(&mut self) -> u8 { | ||
| loop { | ||
| if self.used_set.contains_key(&self.current) { | ||
| self.current += 1; | ||
| } else { | ||
| let index = self.current; | ||
| self.current += 1; | ||
| return index; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Look for a `#[codec(encoded_as = "SomeType")]` outer attribute on the given | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing docs.
Also this struct should only store the
used_set. The functionfrom_iteris not required.You only need
variant_index, which should be a verbatim copy of the oldvariant_indexfunction plus that you add the resulting index toused_setand check if it already exists.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added docs.
we need current to assign incremental indexes to variants in ascending order also we need to traverse the variants first and collect all the assigned indexes before we can process them to check for duplicates and have a set of indexes already used, so that when we encounter a variant without explicit discriminant/
codec(index = ?)we could assign an index usingUsedIndexes.currentby incrementing it until theu8is not contained inside the used set.