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,
| ^^^^^^^^^^^