make types within generate_solution_type macro explicit (#8447)

* make types within `generate_solution_type` macro explicit

Closes #8444.

Just changes the parsing logic for that macro; does not change any
emitted code. The associated types associated with the macro now
require explicit, keyword-style declaration.

**Old**:

```rust
sp_npos_elections::generate_solution_type!(
	#[compact]
	pub struct TestCompact::<VoterIndex, TargetIndex, PerU16>(16)
);
```

**New**:

```rust
sp_npos_elections::generate_solution_type!(
	#[compact]
	pub struct TestCompact::<VoterIndex = VoterIndex, CandidateIndex = TargetIndex, Accuracy = PerU16>(16)
);
```

* un-ignore doc-tests

* use new form in bin/node/runtime/

* rename CandidateIndex -> TargetIndex

* add tests demonstrating some potential compile failures
This commit is contained in:
Peter Goodspeed-Niklaus
2021-03-28 10:21:06 +02:00
committed by GitHub
parent 2af0de50c9
commit c2dd5e21a4
17 changed files with 145 additions and 16 deletions
@@ -19,3 +19,9 @@ syn = { version = "1.0.58", features = ["full", "visit"] }
quote = "1.0"
proc-macro2 = "1.0.6"
proc-macro-crate = "1.0.0"
[dev-dependencies]
parity-scale-codec = "2.0.1"
sp-arithmetic = { path = "../../arithmetic" }
sp-npos-elections = { path = ".." }
trybuild = "1.0.41"
@@ -52,8 +52,14 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
/// For example, the following generates a public struct with name `TestSolution` with `u16` voter
/// type, `u8` target type and `Perbill` accuracy with maximum of 8 edges per voter.
///
/// ```ignore
/// generate_solution_type!(pub struct TestSolution<u16, u8, Perbill>::(8))
/// ```
/// # use sp_npos_elections_compact::generate_solution_type;
/// # use sp_arithmetic::per_things::Perbill;
/// generate_solution_type!(pub struct TestSolution::<
/// VoterIndex = u16,
/// TargetIndex = u8,
/// Accuracy = Perbill,
/// >(8));
/// ```
///
/// The given struct provides function to convert from/to Assignment:
@@ -65,11 +71,13 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
/// lead to many 0s in the solution. If prefixed with `#[compact]`, then a custom compact encoding
/// for numbers will be used, similar to how `parity-scale-codec`'s `Compact` works.
///
/// ```ignore
/// ```
/// # use sp_npos_elections_compact::generate_solution_type;
/// # use sp_arithmetic::per_things::Perbill;
/// generate_solution_type!(
/// #[compact]
/// pub struct TestSolutionCompact<u16, u8, Perbill>::(8)
/// )
/// pub struct TestSolutionCompact::<VoterIndex = u16, TargetIndex = u8, Accuracy = Perbill>(8)
/// );
/// ```
#[proc_macro]
pub fn generate_solution_type(item: TokenStream) -> TokenStream {
@@ -386,7 +394,7 @@ fn check_compact_attr(input: ParseStream) -> Result<bool> {
}
}
/// #[compact] pub struct CompactName::<u32, u32, u32>()
/// #[compact] pub struct CompactName::<VoterIndex = u32, TargetIndex = u32, Accuracy = u32>()
impl Parse for SolutionDef {
fn parse(input: ParseStream) -> syn::Result<Self> {
// optional #[compact]
@@ -405,9 +413,22 @@ impl Parse for SolutionDef {
return Err(syn_err("Must provide 3 generic args."))
}
let mut types: Vec<syn::Type> = generics.args.iter().map(|t|
let expected_types = ["VoterIndex", "TargetIndex", "Accuracy"];
let mut types: Vec<syn::Type> = generics.args.iter().zip(expected_types.iter()).map(|(t, expected)|
match t {
syn::GenericArgument::Type(ty) => Ok(ty.clone()),
syn::GenericArgument::Type(ty) => {
// this is now an error
Err(syn::Error::new_spanned(ty, format!("Expected binding: `{} = ...`", expected)))
},
syn::GenericArgument::Binding(syn::Binding{ident, ty, ..}) => {
// check that we have the right keyword for this position in the argument list
if ident == expected {
Ok(ty.clone())
} else {
Err(syn::Error::new_spanned(ident, format!("Expected `{}`", expected)))
}
}
_ => Err(syn_err("Wrong type of generic provided. Must be a `type`.")),
}
).collect::<Result<_>>()?;
@@ -436,3 +457,12 @@ impl Parse for SolutionDef {
fn field_name_for(n: usize) -> Ident {
Ident::new(&format!("{}{}", PREFIX, n), Span::call_site())
}
#[cfg(test)]
mod tests {
#[test]
fn ui_fail() {
let cases = trybuild::TestCases::new();
cases.compile_fail("tests/ui/fail/*.rs");
}
}
@@ -0,0 +1,9 @@
use sp_npos_elections_compact::generate_solution_type;
generate_solution_type!(pub struct TestSolution::<
VoterIndex = u16,
TargetIndex = u8,
Perbill,
>(8));
fn main() {}
@@ -0,0 +1,5 @@
error: Expected binding: `Accuracy = ...`
--> $DIR/missing_accuracy.rs:6:2
|
6 | Perbill,
| ^^^^^^^
@@ -0,0 +1,9 @@
use sp_npos_elections_compact::generate_solution_type;
generate_solution_type!(pub struct TestSolution::<
VoterIndex = u16,
u8,
Accuracy = Perbill,
>(8));
fn main() {}
@@ -0,0 +1,5 @@
error: Expected binding: `TargetIndex = ...`
--> $DIR/missing_target.rs:5:2
|
5 | u8,
| ^^
@@ -0,0 +1,9 @@
use sp_npos_elections_compact::generate_solution_type;
generate_solution_type!(pub struct TestSolution::<
u16,
TargetIndex = u8,
Accuracy = Perbill,
>(8));
fn main() {}
@@ -0,0 +1,5 @@
error: Expected binding: `VoterIndex = ...`
--> $DIR/missing_voter.rs:4:2
|
4 | u16,
| ^^^
@@ -0,0 +1,9 @@
use sp_npos_elections_compact::generate_solution_type;
generate_solution_type!(pub struct TestSolution::<
u16,
u8,
Perbill,
>(8));
fn main() {}
@@ -0,0 +1,5 @@
error: Expected binding: `VoterIndex = ...`
--> $DIR/no_annotations.rs:4:2
|
4 | u16,
| ^^^
@@ -0,0 +1,9 @@
use sp_npos_elections_compact::generate_solution_type;
generate_solution_type!(pub struct TestSolution::<
TargetIndex = u16,
VoterIndex = u8,
Accuracy = Perbill,
>(8));
fn main() {}
@@ -0,0 +1,5 @@
error: Expected `VoterIndex`
--> $DIR/swap_voter_target.rs:4:2
|
4 | TargetIndex = u16,
| ^^^^^^^^^^^
@@ -4,7 +4,11 @@ use sp_npos_elections::sp_arithmetic::Percent;
use sp_runtime::codec::{Encode, Error};
fn main() {
generate_solution_type!(#[compact] pub struct InnerTestSolutionCompact::<u32, u32, Percent>(16));
generate_solution_type!(#[compact] pub struct InnerTestSolutionCompact::<
VoterIndex = u32,
TargetIndex = u32,
Accuracy = Percent,
>(16));
loop {
fuzz!(|fuzzer_data: &[u8]| {
let result_decoded: Result<InnerTestSolutionCompact, Error> =
@@ -1148,7 +1148,11 @@ mod solution_type {
type TestAccuracy = Percent;
generate_solution_type!(pub struct TestSolutionCompact::<u32, u8, TestAccuracy>(16));
generate_solution_type!(pub struct TestSolutionCompact::<
VoterIndex = u32,
TargetIndex = u8,
Accuracy = TestAccuracy,
>(16));
#[allow(dead_code)]
mod __private {
@@ -1158,7 +1162,7 @@ mod solution_type {
use sp_arithmetic::Percent;
generate_solution_type!(
#[compact]
struct InnerTestSolutionCompact::<u32, u8, Percent>(12)
struct InnerTestSolutionCompact::<VoterIndex = u32, TargetIndex = u8, Accuracy = Percent>(12)
);
}
@@ -1166,7 +1170,11 @@ mod solution_type {
fn solution_struct_works_with_and_without_compact() {
// we use u32 size to make sure compact is smaller.
let without_compact = {
generate_solution_type!(pub struct InnerTestSolution::<u32, u32, Percent>(16));
generate_solution_type!(pub struct InnerTestSolution::<
VoterIndex = u32,
TargetIndex = u32,
Accuracy = Percent,
>(16));
let compact = InnerTestSolution {
votes1: vec![(2, 20), (4, 40)],
votes2: vec![
@@ -1180,7 +1188,11 @@ mod solution_type {
};
let with_compact = {
generate_solution_type!(#[compact] pub struct InnerTestSolutionCompact::<u32, u32, Percent>(16));
generate_solution_type!(#[compact] pub struct InnerTestSolutionCompact::<
VoterIndex = u32,
TargetIndex = u32,
Accuracy = Percent,
>(16));
let compact = InnerTestSolutionCompact {
votes1: vec![(2, 20), (4, 40)],
votes2: vec![