Problem
consider the following enum:
enum Items{
A(FileItemTreeId<i32>),
B(FileItemTreeId<String>),
C(FileItemTreeId<FunctionNodeType>),
D ...
}
one might want to execute the same code for multiple variants of the enum. e.g:
fn print(it : Items){
match(it){
Items::A(it) => println!("{it}"},
Items::B(it) => println!("{it}"},
...
}
}
An example can be found in the Rust Analyzer code in "crates/hir-def/src/item_tree.rs", where a ModItem implements an ast_id with this recurring pattern. While you can move the function to a macro in this case, a more general way might be helpful.
Looking through the source code of Rust-Analyzer, I also found other repetitive code related to structs that group multiple elements together. e.g:
#[derive(Default, Debug, Eq, PartialEq)]
struct ItemTreeData {
imports: Arena<Import>,
extern_crates: Arena<ExternCrate>,
extern_blocks: Arena<ExternBlock>,
functions: Arena<Function>,
...
}
fn shrink_to_fit(&mut self) {
if let Some(data) = &mut self.data {
let ItemTreeData {
imports,
extern_crates,
extern_blocks,
functions,
...
} = &mut **data;
imports.shrink_to_fit();
extern_crates.shrink_to_fit();
extern_blocks.shrink_to_fit();
functions.shrink_to_fit();
rules.shrink_to_fit();
Although you could simplify this code with a custom derivation macro, a more general, simpler method might be helpful.
Possible Syntax - Idea:
To solve these repetitions, one could add an additional syntax.
For the first enum matching case:
match(it){
//overide custom behavior for Items::A
Items::A(it) => { println("{}",it.index},
//Match all variants with exact one tuple field of generic type T:
impl<T : ToString> for Items::$name(T) => println!("{}:{}", stringify!($name), name.0},
//name would be like a local variable with the type of the matched variant, in this case an enum
//so name.0 might return an FileItemTreeId<*> object.
//$named would return the name of the variant, so this could return the ident A of Items::A
//Match all struct-variants with exact two fields named x and y of generic Type T:
impl<T : ToString> for Items::$name{x : T, y : T} => println!("value:{}",name.x},
//Match all variants with the first tuple field of generic Type T:
impl<T : ToString> for Items::$name(T, _) => println!("value:{}", name.0},
//Match all struct-variants with at least two fields named x and y of generic Type T:
impl<T : ToString> for Items::$name{x : T, y : T, _} => println!("value:{}",name.y},
}
The impl keyword is a bit strange, but in the end we implement a generic inline function for the patern-matched type.
Another idea would be to use similar syntax or other macro expressions to iterate over all enum variants or struct fields and generate code for each entry:
fn test(v1 : ItemTreeData, v2 : Items){
//This might iterate over all field members of ItemTreeData and call the inline function for every matching case
impl<T : ToString> for ItemTreeData::$name : T in v1 => println!("{}:{}", stringify!($name), name.toString()};
//This might go statically through all the enum variants of items and call the inline function for each matching case
impl<T : ToString> for Items::$name(T) => println!("{}",stringify!($name)};
}
Or you could even add fields or variants based on other fields or variants of the type.
struct s{
s1 : i32,
s2 : i32,
s3 : i32,
//Create a field for every variant in Items matching the pattern, with the name of the variant.
// "s1", "s2" and "s3" would not match as this is already used above.
impl<T> for Items::$name(T) => $name : T,
}
What do you think about such impl syntax? Would it be useful? Would it be possible to implement it. In the end, too many complicated syntaxes for special cases are not good either.