-
Notifications
You must be signed in to change notification settings - Fork 94
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
Disallow duplicated enum variant indices #628
base: master
Are you sure you want to change the base?
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), .. }), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this doesn't capture any discriminant, user can write this: #[derive(Encode, Decode)]
enum A {
A = 3 + 4,
} I think it can be ok to constraint our implementation, but then we should compile-error if the expression is not a int literal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated with to disallow this pattern |
||
)) = 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_iter
is not required.You only need
variant_index
, which should be a verbatim copy of the oldvariant_index
function plus that you add the resulting index toused_set
and 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.current
by incrementing it until theu8
is not contained inside the used set.