├── tests ├── errors │ ├── .gitignore │ ├── basic │ │ ├── no_inputs │ │ ├── only_semicolon │ │ ├── verbose_not_ident │ │ ├── verbose_semicolon │ │ ├── global_sub_semicolon │ │ ├── parameters_not_encapsulated │ │ ├── short_missing_substitution │ │ ├── verbose_after_ident │ │ ├── short_missing_substitution_bracket │ │ ├── substitute_item_with_duplicate │ │ ├── short_superfluous_substitution │ │ ├── short_unfinished_identifier_list │ │ ├── short_without_duplicates │ │ ├── substitute_item_with_duplicate_2 │ │ ├── substitute_item_with_duplicate_3 │ │ ├── duplicate_without_duplicates │ │ ├── substitute_item_with_duplicate_4 │ │ ├── verbose_incomplete_substitution_group │ │ ├── verbose_unexpected_substitution_identifier │ │ └── verbose_wrong_argument_count │ ├── highlight │ │ ├── only_semicolon │ │ ├── substitute_item_with_duplicate │ │ ├── global_sub_semicolon │ │ ├── short_missing_substitution │ │ ├── verbose_after_ident │ │ ├── verbose_wrong_argument_count │ │ ├── short_missing_substitution_bracket │ │ ├── substitute_item_with_duplicate_2 │ │ ├── verbose_not_ident │ │ ├── verbose_semicolon │ │ ├── short_superfluous_substitution │ │ ├── verbose_unexpected_substitution_identifier │ │ ├── substitute_item_with_duplicate_3 │ │ ├── verbose_incomplete_substitution_group │ │ └── parameters_not_encapsulated │ ├── hint │ │ ├── verbose_not_ident │ │ ├── short_unfinished_identifier_list │ │ ├── substitute_item_with_duplicate │ │ ├── substitute_item_with_duplicate_4 │ │ ├── no_inputs │ │ ├── only_semicolon │ │ ├── substitute_item_with_duplicate_2 │ │ ├── substitute_item_with_duplicate_3 │ │ ├── duplicate_without_duplicates │ │ ├── global_sub_semicolon │ │ ├── short_without_duplicates │ │ ├── short_missing_substitution_bracket │ │ ├── parameters_not_encapsulated │ │ ├── verbose_semicolon │ │ ├── short_missing_substitution │ │ ├── short_superfluous_substitution │ │ ├── verbose_unexpected_substitution_identifier │ │ ├── verbose_incomplete_substitution_group │ │ └── verbose_wrong_argument_count │ ├── source │ │ ├── no_inputs.rs │ │ ├── only_semicolon.rs │ │ ├── short_without_duplicates.rs │ │ ├── duplicate_without_duplicates.rs │ │ ├── global_sub_semicolon.rs │ │ ├── short_unfinished_identifier_list.rs │ │ ├── verbose_after_ident.rs │ │ ├── short_missing_substitution.rs │ │ ├── short_superfluous_substitution.rs │ │ ├── short_missing_substitution_bracket.rs │ │ ├── verbose_not_ident.rs │ │ ├── verbose_semicolon.rs │ │ ├── substitute_item_with_duplicate_4.rs │ │ ├── parameters_not_encapsulated.rs │ │ ├── verbose_wrong_argument_count.rs │ │ ├── verbose_incomplete_substitution_group.rs │ │ ├── verbose_unexpected_substitution_identifier.rs │ │ ├── substitute_item_with_duplicate.rs │ │ ├── substitute_item_with_duplicate_2.rs │ │ └── substitute_item_with_duplicate_3.rs │ └── mod.rs ├── no_features │ ├── .gitignore │ ├── edition_2021 │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── expected │ │ ├── only_global_substitutions.expanded.rs │ │ ├── insert.expanded.rs │ │ ├── short_ident_argument.expanded.rs │ │ ├── verbose_ident_argument.expanded.rs │ │ └── nested_duplicate.expanded.rs │ ├── expected_both │ │ ├── from_macro.expanded.rs │ │ ├── nested_duplicate.expanded.rs │ │ └── syntax.expanded.rs │ ├── from │ │ ├── only_global_substitutions.rs │ │ ├── insert.rs │ │ ├── short_syntax.rs │ │ ├── short_nested_duplicate.rs │ │ ├── verbose_syntax.rs │ │ ├── verbose_nested_duplicate.rs │ │ ├── short_ident_argument.rs │ │ ├── nested_duplicate.rs │ │ ├── short_from_macro.rs │ │ ├── verbose_from_macro.rs │ │ └── verbose_ident_argument.rs │ └── mod.rs ├── module_disambiguation │ ├── .gitignore │ ├── errors │ │ ├── .gitignore │ │ ├── basic │ │ │ └── all_substitutions_equal │ │ ├── highlight │ │ │ └── all_substitutions_equal │ │ ├── source │ │ │ └── all_substitutions_equal.rs │ │ ├── hint │ │ │ └── all_substitutions_equal │ │ └── mod.rs │ ├── mod.rs │ ├── expected_both │ │ └── syntax.expanded.rs │ ├── from │ │ ├── short_syntax.rs │ │ ├── verbose_syntax.rs │ │ └── short_first_valid_is_chosen.rs │ └── expected │ │ └── short_first_valid_is_chosen.expanded.rs ├── tests.rs ├── default_features │ └── mod.rs └── utils.rs ├── .gitignore ├── src ├── crate_readme_test.rs ├── error.rs ├── module_disambiguation.rs ├── pretty_errors.rs ├── substitute.rs ├── token_iter.rs ├── parse.rs └── lib.rs ├── rustfmt.toml ├── .github ├── workflows │ ├── issue-triage.yml │ └── rust.yml ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── support-request.md │ ├── feature-proposal.md │ └── bug-report.md └── actions │ └── run-test-groups │ └── action.yml ├── LICENSE-MIT ├── Cargo.toml ├── cargo-readme.md ├── README.md ├── CHANGELOG.md └── LICENSE-APACHE /tests/errors/.gitignore: -------------------------------------------------------------------------------- 1 | /testing* -------------------------------------------------------------------------------- /tests/no_features/.gitignore: -------------------------------------------------------------------------------- 1 | /testing -------------------------------------------------------------------------------- /tests/module_disambiguation/.gitignore: -------------------------------------------------------------------------------- 1 | /testing -------------------------------------------------------------------------------- /tests/errors/basic/no_inputs: -------------------------------------------------------------------------------- 1 | Unexpected end of code -------------------------------------------------------------------------------- /tests/errors/basic/only_semicolon: -------------------------------------------------------------------------------- 1 | Unexpected token. -------------------------------------------------------------------------------- /tests/errors/basic/verbose_not_ident: -------------------------------------------------------------------------------- 1 | Unexpected token -------------------------------------------------------------------------------- /tests/errors/basic/verbose_semicolon: -------------------------------------------------------------------------------- 1 | Unexpected token -------------------------------------------------------------------------------- /tests/errors/highlight/only_semicolon: -------------------------------------------------------------------------------- 1 | 3 | ; 2 | | ^ -------------------------------------------------------------------------------- /tests/errors/basic/global_sub_semicolon: -------------------------------------------------------------------------------- 1 | Unexpected token -------------------------------------------------------------------------------- /tests/errors/basic/parameters_not_encapsulated: -------------------------------------------------------------------------------- 1 | Expected '[' -------------------------------------------------------------------------------- /tests/errors/basic/short_missing_substitution: -------------------------------------------------------------------------------- 1 | Expected '[' -------------------------------------------------------------------------------- /tests/errors/basic/verbose_after_ident: -------------------------------------------------------------------------------- 1 | Expected '(' or '[' -------------------------------------------------------------------------------- /tests/module_disambiguation/errors/.gitignore: -------------------------------------------------------------------------------- 1 | /testing* -------------------------------------------------------------------------------- /tests/errors/basic/short_missing_substitution_bracket: -------------------------------------------------------------------------------- 1 | Expected '[' -------------------------------------------------------------------------------- /tests/errors/basic/substitute_item_with_duplicate: -------------------------------------------------------------------------------- 1 | Unexpected token. -------------------------------------------------------------------------------- /tests/errors/basic/short_superfluous_substitution: -------------------------------------------------------------------------------- 1 | Unexpected delimiter -------------------------------------------------------------------------------- /tests/errors/basic/short_unfinished_identifier_list: -------------------------------------------------------------------------------- 1 | Unexpected end of code -------------------------------------------------------------------------------- /tests/errors/basic/short_without_duplicates: -------------------------------------------------------------------------------- 1 | Expected substitution group -------------------------------------------------------------------------------- /tests/errors/basic/substitute_item_with_duplicate_2: -------------------------------------------------------------------------------- 1 | Expected '(' or '['. -------------------------------------------------------------------------------- /tests/errors/basic/substitute_item_with_duplicate_3: -------------------------------------------------------------------------------- 1 | Unexpected delimiter. -------------------------------------------------------------------------------- /tests/errors/highlight/substitute_item_with_duplicate: -------------------------------------------------------------------------------- 1 | 6 | 123 2 | | ^^^ -------------------------------------------------------------------------------- /tests/errors/hint/verbose_not_ident: -------------------------------------------------------------------------------- 1 | Expected a substitution identifier. -------------------------------------------------------------------------------- /tests/errors/basic/duplicate_without_duplicates: -------------------------------------------------------------------------------- 1 | Expected substitution group -------------------------------------------------------------------------------- /tests/errors/basic/substitute_item_with_duplicate_4: -------------------------------------------------------------------------------- 1 | Unexpected end of code. -------------------------------------------------------------------------------- /tests/errors/highlight/global_sub_semicolon: -------------------------------------------------------------------------------- 1 | 4 | ty [u32] 2 | | ^^ -------------------------------------------------------------------------------- /tests/errors/highlight/short_missing_substitution: -------------------------------------------------------------------------------- 1 | 4 | [sub1]; 2 | | ^ -------------------------------------------------------------------------------- /tests/errors/highlight/verbose_after_ident: -------------------------------------------------------------------------------- 1 | 4 | name * [sub1] 2 | | ^ -------------------------------------------------------------------------------- /tests/errors/highlight/verbose_wrong_argument_count: -------------------------------------------------------------------------------- 1 | 7 | name [sub1] 2 | | ^^^^ -------------------------------------------------------------------------------- /tests/errors/hint/short_unfinished_identifier_list: -------------------------------------------------------------------------------- 1 | substitution_identifier or ';' -------------------------------------------------------------------------------- /tests/errors/hint/substitute_item_with_duplicate: -------------------------------------------------------------------------------- 1 | Expected a substitution identifier. -------------------------------------------------------------------------------- /tests/errors/basic/verbose_incomplete_substitution_group: -------------------------------------------------------------------------------- 1 | Incomplete substitution group -------------------------------------------------------------------------------- /tests/errors/highlight/short_missing_substitution_bracket: -------------------------------------------------------------------------------- 1 | 4 | sub1 sub2; 2 | | ^^^^ -------------------------------------------------------------------------------- /tests/errors/highlight/substitute_item_with_duplicate_2: -------------------------------------------------------------------------------- 1 | 6 | dup_sub; 2 | | ^ -------------------------------------------------------------------------------- /tests/errors/highlight/verbose_not_ident: -------------------------------------------------------------------------------- 1 | 4 | name [sub1] * 2 | | ^ -------------------------------------------------------------------------------- /tests/errors/highlight/verbose_semicolon: -------------------------------------------------------------------------------- 1 | 4 | name [sub1]; 2 | | ^ -------------------------------------------------------------------------------- /tests/errors/hint/substitute_item_with_duplicate_4: -------------------------------------------------------------------------------- 1 | Expected a substitution identifier. -------------------------------------------------------------------------------- /tests/errors/highlight/short_superfluous_substitution: -------------------------------------------------------------------------------- 1 | 4 | [sub1] [sub2]; 2 | | ^^^^^^ -------------------------------------------------------------------------------- /tests/module_disambiguation/errors/basic/all_substitutions_equal: -------------------------------------------------------------------------------- 1 | Item cannot be disambiguated -------------------------------------------------------------------------------- /tests/module_disambiguation/errors/highlight/all_substitutions_equal: -------------------------------------------------------------------------------- 1 | 8 | mod __ 2 | | ^^ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /tests/no_features/edition_2021/target/ 3 | Cargo.lock 4 | .idea 5 | *.iml -------------------------------------------------------------------------------- /tests/errors/basic/verbose_unexpected_substitution_identifier: -------------------------------------------------------------------------------- 1 | Unexpected substitution identifier -------------------------------------------------------------------------------- /tests/errors/basic/verbose_wrong_argument_count: -------------------------------------------------------------------------------- 1 | Wrong argument count for substitution identifier -------------------------------------------------------------------------------- /tests/errors/highlight/verbose_unexpected_substitution_identifier: -------------------------------------------------------------------------------- 1 | 8 | ty [sub2] 2 | | ^^ -------------------------------------------------------------------------------- /tests/errors/hint/no_inputs: -------------------------------------------------------------------------------- 1 | substitution_identifier (short syntax) or substitution group (verbose syntax). -------------------------------------------------------------------------------- /tests/errors/highlight/substitute_item_with_duplicate_3: -------------------------------------------------------------------------------- 1 | 6 | / [ 2 | 7 | | name2 [sub2] 3 | 8 | | ] 4 | | |_^ -------------------------------------------------------------------------------- /tests/errors/highlight/verbose_incomplete_substitution_group: -------------------------------------------------------------------------------- 1 | 7 | / [ 2 | 8 | | name [sub1] 3 | 9 | | ] 4 | | |_^ -------------------------------------------------------------------------------- /tests/errors/hint/only_semicolon: -------------------------------------------------------------------------------- 1 | substitution_identifier (short syntax) or substitution group (verbose syntax) -------------------------------------------------------------------------------- /tests/errors/highlight/parameters_not_encapsulated: -------------------------------------------------------------------------------- 1 | 6 | fn from(x: refs(Bits<1, false>)) -> bool { 2 | | ^^^^ -------------------------------------------------------------------------------- /tests/errors/hint/substitute_item_with_duplicate_2: -------------------------------------------------------------------------------- 1 | Only global substitutions are allowed. Try 'duplicate' or 'duplicate_item'. -------------------------------------------------------------------------------- /tests/errors/hint/substitute_item_with_duplicate_3: -------------------------------------------------------------------------------- 1 | Only global substitutions are allowed. Try 'duplicate' or 'duplicate_item'. -------------------------------------------------------------------------------- /tests/no_features/edition_2021/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Emoun/duplicate/HEAD/tests/no_features/edition_2021/.gitignore -------------------------------------------------------------------------------- /tests/errors/hint/duplicate_without_duplicates: -------------------------------------------------------------------------------- 1 | Must specify at least one substitution group, otherwise use 'substitute!' or 'substitute_item' -------------------------------------------------------------------------------- /tests/errors/source/no_inputs.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | 4 | )]//duplicate_end 5 | pub struct name(ty); 6 | //item_end 7 | -------------------------------------------------------------------------------- /src/crate_readme_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(doctest)] 2 | // Tests the crate readme-file's Rust examples. 3 | doc_comment::doctest!("../cargo-readme.md"); 4 | -------------------------------------------------------------------------------- /tests/errors/source/only_semicolon.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | ; 4 | )]//duplicate_end 5 | pub struct name(ty); 6 | //item_end 7 | -------------------------------------------------------------------------------- /tests/errors/hint/global_sub_semicolon: -------------------------------------------------------------------------------- 1 | Each global substitution should end with ';' 2 | Example: 3 | name [sub1]; 4 | typ [sub2]; -------------------------------------------------------------------------------- /tests/errors/source/short_without_duplicates.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | name; 4 | )]//duplicate_end 5 | pub struct name(ty); 6 | //item_end 7 | -------------------------------------------------------------------------------- /tests/errors/source/duplicate_without_duplicates.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | name [SomeStruct]; 4 | )]//duplicate_end 5 | pub struct name(ty); 6 | //item_end 7 | -------------------------------------------------------------------------------- /tests/errors/source/global_sub_semicolon.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | name [sub1] 4 | ty [u32] 5 | )]//duplicate_end 6 | pub struct name(ty); 7 | //item_end 8 | -------------------------------------------------------------------------------- /tests/errors/source/short_unfinished_identifier_list.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | ident1 ident2 4 | )]//duplicate_end 5 | pub struct name(ty); 6 | //item_end 7 | -------------------------------------------------------------------------------- /tests/errors/source/verbose_after_ident.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | [ 4 | name * [sub1] 5 | ] 6 | )]//duplicate_end 7 | pub struct name(ty); 8 | //item_end 9 | -------------------------------------------------------------------------------- /tests/errors/source/short_missing_substitution.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | ident1 ident2; 4 | [sub1]; 5 | )]//duplicate_end 6 | pub struct name(ty); 7 | //item_end 8 | -------------------------------------------------------------------------------- /tests/errors/source/short_superfluous_substitution.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | ident1; 4 | [sub1] [sub2]; 5 | )]//duplicate_end 6 | pub struct name(ty); 7 | //item_end 8 | -------------------------------------------------------------------------------- /tests/no_features/expected/only_global_substitutions.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | pub struct SomeStruct4(u16); 3 | pub struct SomeStruct5(u32); 4 | pub struct SomeStruct6(&'static u64); -------------------------------------------------------------------------------- /tests/errors/source/short_missing_substitution_bracket.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | ident1 ident2; 4 | sub1 sub2; 5 | )]//duplicate_end 6 | pub struct name(ty); 7 | //item_end 8 | -------------------------------------------------------------------------------- /tests/errors/source/verbose_not_ident.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | [ 4 | name [sub1] * 5 | ty [u32] 6 | ] 7 | )]//duplicate_end 8 | pub struct name(ty); 9 | //item_end 10 | -------------------------------------------------------------------------------- /tests/errors/source/verbose_semicolon.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | [ 4 | name [sub1]; 5 | ty [u32]; 6 | ] 7 | )]//duplicate_end 8 | pub struct name(ty); 9 | //item_end 10 | -------------------------------------------------------------------------------- /tests/no_features/edition_2021/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "edition_2021" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | duplicate = { path = "../../..", default-features = false } -------------------------------------------------------------------------------- /tests/errors/hint/short_without_duplicates: -------------------------------------------------------------------------------- 1 | Add a substitution group after the substitution identifiers. 2 | Example: 3 | name; 4 | [SomeSubstitution]; 5 | ^^^^^^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- /tests/module_disambiguation/mod.rs: -------------------------------------------------------------------------------- 1 | mod errors; 2 | 3 | #[test] 4 | pub fn test_expansions() 5 | { 6 | crate::utils::ExpansionTester::run_default_test_setup("tests/module_disambiguation", "testing"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/errors/hint/short_missing_substitution_bracket: -------------------------------------------------------------------------------- 1 | Each substitution should be enclosed in '[]'. 2 | Example: 3 | ident1 ident2; 4 | [ sub1 ] [ sub2 ] ; 5 | ^^^ ^^^^^ ^^^ -------------------------------------------------------------------------------- /tests/no_features/expected/insert.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | pub struct SomeName(); 3 | pub struct SomeName2(i32); 4 | pub struct SomeName3(Vec); 5 | pub struct SomeName41(i16); 6 | pub struct SomeName42(i16); -------------------------------------------------------------------------------- /tests/errors/hint/parameters_not_encapsulated: -------------------------------------------------------------------------------- 1 | Substitution parameters should be enclosed in '[]' each. 2 | Example: 3 | sub_ident( [ parameter1 ] , [ paramter2 ] ) 4 | ^^^ ^^^ ^^^ ^^^ -------------------------------------------------------------------------------- /tests/errors/source/substitute_item_with_duplicate_4.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | // Test that substitute macros require at least one global substitution 3 | #[substitute_item( 4 | )]//duplicate_end 5 | pub struct name(ty); 6 | //item_end 7 | -------------------------------------------------------------------------------- /tests/errors/source/parameters_not_encapsulated.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | 3 | #[duplicate_item( 4 | refs(T); [& T]; [T] 5 | )]//duplicate_end 6 | fn from(x: refs(Bits<1, false>)) -> bool { 7 | x.value == 1 8 | } 9 | //item_end 10 | -------------------------------------------------------------------------------- /tests/errors/hint/verbose_semicolon: -------------------------------------------------------------------------------- 1 | Verbose syntax does not accept semicolons between substitutions. 2 | Example: 3 | [ 4 | name [sub1] // No semicolon 5 | ty [u32] // No semicolon 6 | ] -------------------------------------------------------------------------------- /tests/errors/source/verbose_wrong_argument_count.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | [ 4 | name(arg1) [sub1(arg1)] 5 | ] 6 | [ 7 | name [sub1] 8 | ] 9 | )]//duplicate_end 10 | pub struct name([i32]); 11 | //item_end 12 | -------------------------------------------------------------------------------- /tests/errors/hint/short_missing_substitution: -------------------------------------------------------------------------------- 1 | Number of substitutions must match the number of substitutions identifiers. 2 | Example: 3 | ident1 ident2; 4 | 1^^^^^^ ^^^^^^2 5 | [sub1] [sub2]; 6 | 1^^^^^^ ^^^^^^2 -------------------------------------------------------------------------------- /tests/errors/hint/short_superfluous_substitution: -------------------------------------------------------------------------------- 1 | Number of substitutions must match the number of substitutions identifiers. 2 | Example: 3 | ident1 ident2; 4 | 1^^^^^^ ^^^^^^2 5 | [sub1] [sub2]; 6 | 1^^^^^^ ^^^^^^2 -------------------------------------------------------------------------------- /tests/errors/source/verbose_incomplete_substitution_group.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | [ 4 | name [sub1] 5 | ty [sub2] 6 | ] 7 | [ 8 | name [sub1] 9 | ] 10 | )]//duplicate_end 11 | pub struct name(ty); 12 | //item_end 13 | -------------------------------------------------------------------------------- /tests/errors/source/verbose_unexpected_substitution_identifier.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | [ 4 | name [sub1] 5 | ] 6 | [ 7 | name [sub1] 8 | ty [sub2] 9 | ] 10 | )]//duplicate_end 11 | pub struct name(i32); 12 | //item_end 13 | -------------------------------------------------------------------------------- /tests/module_disambiguation/errors/source/all_substitutions_equal.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | 3 | #[duplicate_item( 4 | sub_typ sub_fn; 5 | [ SomeType ] [ some_fn ]; 6 | [ SomeType ] [ some_fn ]; 7 | )]//duplicate_end 8 | mod __ 9 | {} 10 | //item_end 11 | -------------------------------------------------------------------------------- /tests/errors/source/substitute_item_with_duplicate.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | // Test that substitute macros look for inline substitutions 3 | #[substitute_item( 4 | name [sub1]; 5 | ty [u32]; 6 | 123 7 | )]//duplicate_end 8 | pub struct name(ty); 9 | //item_end 10 | -------------------------------------------------------------------------------- /tests/errors/source/substitute_item_with_duplicate_2.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | // Tests that if the globals are followed by short syntax, the hint refers to 'duplicate' 3 | #[substitute_item( 4 | name [sub1]; 5 | ty [u32]; 6 | dup_sub; 7 | [i8]; 8 | [i16]; 9 | )]//duplicate_end 10 | pub struct name(ty); 11 | //item_end 12 | -------------------------------------------------------------------------------- /tests/module_disambiguation/errors/hint/all_substitutions_equal: -------------------------------------------------------------------------------- 1 | There must be a substitution identifier that is substituted by a single and unique identifier for each duplicate. 2 | Example: 3 | invalid valid; 4 | [i32] [som_ident]; 5 | [Some>] [som_other_ident]; -------------------------------------------------------------------------------- /tests/no_features/expected_both/from_macro.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | pub struct SomeName1(); 3 | pub struct SomeName2(u8); 4 | pub struct SomeName3(u8); 5 | const SomeName4: u8 = 4; 6 | pub struct SomeName5(u8); 7 | fn some_fn6() { 8 | let SomeName6; 9 | } 10 | fn some_fn7() { 11 | 7; 12 | } 13 | pub struct SomeName8(); -------------------------------------------------------------------------------- /tests/errors/hint/verbose_unexpected_substitution_identifier: -------------------------------------------------------------------------------- 1 | All substitution groups must define the same substitution identifiers. 2 | Example: 3 | [ 4 | ident1 [sub1] 5 | ident2 [sub2] 6 | ] 7 | [ 8 | ident1 [sub3] 9 | ident2 [sub4] 10 | ] -------------------------------------------------------------------------------- /tests/errors/source/substitute_item_with_duplicate_3.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | // Tests that if the globals are followed by verbose syntax, the hint refers to 'duplicate' 3 | #[substitute_item( 4 | name [sub1]; 5 | ty [u32]; 6 | [ 7 | name2 [sub2] 8 | ] 9 | [ 10 | name2 [sub3] 11 | ] 12 | )]//duplicate_end 13 | pub struct name(ty); 14 | //item_end 15 | -------------------------------------------------------------------------------- /tests/errors/hint/verbose_incomplete_substitution_group: -------------------------------------------------------------------------------- 1 | Missing substitution for: 'ty' 2 | All substitution groups must define the same substitution identifiers. 3 | Example: 4 | [ 5 | ident1 [sub1] 6 | ident2 [sub2] 7 | ] 8 | [ 9 | ident1 [sub3] 10 | ident2 [sub4] 11 | ] -------------------------------------------------------------------------------- /tests/errors/hint/verbose_wrong_argument_count: -------------------------------------------------------------------------------- 1 | The same substitution identifier must take the same number of argument across all substitution groups. 2 | Example: 3 | [ 4 | ident1(arg1, arg2) [sub1 arg1 arg2] 5 | ^^^^^^^^^^ 6 | ] 7 | [ 8 | ident1(arg1, arg2) [arg1 arg2 sub2] 9 | ^^^^^^^^^^ 10 | ] -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | /// Returns the directory of the calling file as a &str 2 | macro_rules! file_dir { 3 | () => {{ 4 | let mut buf = std::path::PathBuf::from(file!()); 5 | buf.pop(); 6 | buf.to_str().unwrap().to_string().as_str() 7 | }}; 8 | } 9 | 10 | #[cfg(feature = "default")] 11 | mod default_features; 12 | mod errors; 13 | #[cfg(feature = "module_disambiguation")] 14 | mod module_disambiguation; 15 | mod no_features; 16 | mod utils; 17 | -------------------------------------------------------------------------------- /tests/no_features/from/only_global_substitutions.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[substitute_item( 3 | ty [u16]; 4 | )]//duplicate_end 5 | pub struct SomeStruct4(ty); 6 | //item_end 7 | 8 | #[substitute_item( 9 | Name [SomeStruct5]; 10 | ty [u32]; 11 | )]//duplicate_end 12 | pub struct Name(ty); 13 | //item_end 14 | 15 | #[substitute_item( 16 | Name [SomeStruct6]; 17 | ty(extra) [extra]; 18 | )]//duplicate_end 19 | pub struct Name(ty([&'static u64])); 20 | //item_end -------------------------------------------------------------------------------- /tests/module_disambiguation/errors/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{ 2 | run_basic_expansion_error_tests, run_error_highlight_tests, run_error_hint_tests, 3 | }; 4 | 5 | #[test] 6 | fn basic_expansion_errors() 7 | { 8 | run_basic_expansion_error_tests(file_dir!()); 9 | } 10 | 11 | #[test] 12 | fn highlight_expansion_errors() 13 | { 14 | run_error_highlight_tests(file_dir!()); 15 | } 16 | 17 | #[test] 18 | fn hint_expansion_errors() 19 | { 20 | run_error_hint_tests(file_dir!()); 21 | } 22 | -------------------------------------------------------------------------------- /tests/no_features/edition_2021/src/main.rs: -------------------------------------------------------------------------------- 1 | /// Used to test that duplicate doesn't force its edition on the expanded code. 2 | /// The following code acceptable in edition 2021 but not the earlier. 3 | /// So if duplicate uses an earlier edition, it shouldn't result in this code being 4 | /// rejected. 5 | #[deny(non_fmt_panics)] 6 | fn main(){ 7 | let a = 42; 8 | // This allowed in edition 2021 but not <=2018 9 | // Above deny ensured build fails if this code is treated as edition <=2018 10 | duplicate::duplicate! { [foo; [];] 11 | panic!("{a}"); 12 | } 13 | } -------------------------------------------------------------------------------- /tests/no_features/from/insert.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[substitute_item( 3 | name [SomeName] 4 | )]//duplicate_end 5 | pub struct name(); 6 | //item_end 7 | #[substitute_item( 8 | name [SomeName2]; 9 | ty [i32] 10 | )]//duplicate_end 11 | pub struct name(ty); 12 | //item_end 13 | #[substitute_item( 14 | name [SomeName3]; 15 | rf(ty) [Vec]; 16 | )]//duplicate_end 17 | pub struct name(rf([i32])); 18 | //item_end 19 | 20 | #[duplicate_item( 21 | ty [i16]; 22 | name; [SomeName41]; [SomeName42] 23 | )]//duplicate_end 24 | pub struct name(ty); 25 | //item_end 26 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | brace_style="AlwaysNextLine" 3 | combine_control_expr=false 4 | control_brace_style="AlwaysNextLine" 5 | empty_item_single_line=true 6 | force_multiline_blocks=true 7 | format_strings=true 8 | hard_tabs=true 9 | imports_granularity="Crate" 10 | newline_style="Unix" 11 | normalize_comments=true 12 | normalize_doc_attributes=true 13 | reorder_impl_items=true 14 | space_after_colon=true 15 | struct_lit_single_line=true 16 | trailing_semicolon=true 17 | use_field_init_shorthand=true 18 | use_try_shorthand=true 19 | wrap_comments=true 20 | match_block_trailing_comma=true -------------------------------------------------------------------------------- /.github/workflows/issue-triage.yml: -------------------------------------------------------------------------------- 1 | name: Triage Issue 2 | 3 | # Ensures that all opened issues are initially given the 'T-new' label 4 | # unless one of the other triage labels is present. 5 | 6 | on: 7 | issues: 8 | types: opened 9 | 10 | jobs: 11 | add_label: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-ecosystem/action-add-labels@v1 16 | if: ${{ 17 | !contains(github.event.issue.labels.*.name, 'T-accepted') && 18 | !contains(github.event.issue.labels.*.name, 'T-rejected') }} 19 | with: 20 | labels: T-new -------------------------------------------------------------------------------- /tests/no_features/expected/short_ident_argument.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | fn fn_const_1(arg: &i32) {} 3 | fn fn_mut_1(arg: &mut i32) {} 4 | fn fn_const_2<'a>(arg: &Vec) {} 5 | fn fn_mut_2<'a>(arg: &mut Vec) {} 6 | fn fn_const_3<'a>(arg: &'a i32) {} 7 | fn fn_mut_3<'a>(arg: &'a mut i32) {} 8 | fn fn_const_4<'a>(arg: &'a Result) {} 9 | fn fn_mut_4<'a>(arg: &'a mut Result) {} 10 | fn fn_const_5<'a>(arg: &'a Result) -> &'a i32 {} 11 | fn fn_mut_5<'a>(arg: &'a mut Result) -> &'a mut i32 {} 12 | fn fn_const_6(arg: &i32) {} 13 | fn fn_mut_6(arg: &mut i32) {} 14 | fn fn_const_7(arg: &&i32) {} 15 | fn fn_mut_7(arg: &mut &mut i32) {} -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 5 | #### Fixes # 6 | 11 | -------------------------------------------------------------------------------- /tests/no_features/expected_both/nested_duplicate.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | pub struct SomeName1(); 3 | pub struct SomeName2(); 4 | pub struct SomeName3(); 5 | pub struct SomeName4(); 6 | pub struct SomeName5(); 7 | pub struct SomeName6(); 8 | pub struct SomeName7(); 9 | trait SomeTrait {} 10 | impl SomeTrait for () {} 11 | impl SomeTrait for () {} 12 | impl SomeTrait for () {} 13 | impl SomeTrait for () {} 14 | impl SomeTrait for () {} 15 | impl SomeTrait for () {} 16 | impl SomeTrait for () {} 17 | impl SomeTrait for () {} 18 | fn outer_1() { 19 | 1; 20 | } 21 | fn outer_2() { 22 | 2; 23 | } 24 | -------------------------------------------------------------------------------- /tests/no_features/expected_both/syntax.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | pub struct SomeName1(); 3 | pub struct SomeName2(u8); 4 | pub struct SomeName3(u8); 5 | pub struct SomeName4(u16); 6 | mod mod1 { 7 | use super::*; 8 | pub struct SomeName5(u8); 9 | pub struct SomeName6(u16); 10 | pub struct SomeName7(u32); 11 | pub struct SomeName8(u64); 12 | } 13 | mod mod2 { 14 | use super::*; 15 | pub struct SomeName5(u8); 16 | pub struct SomeName6(u16); 17 | pub struct SomeName7(u32); 18 | pub struct SomeName8(u64); 19 | } 20 | fn fn_name_1() { 21 | let _ = std::io::empty(); 22 | } 23 | fn fn_name_2() { 24 | let _ = [4; 0]; 25 | } 26 | fn fn_name_3() { 27 | let _ = {}; 28 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support Request 3 | about: Get in contact with the maintainers of the crate to ask questions, seek help, 4 | or regarding anything else that does not fit the other issue types. 5 | title: '' 6 | labels: I-support 7 | assignees: '' 8 | 9 | --- 10 | 11 | 18 | -------------------------------------------------------------------------------- /tests/no_features/expected/verbose_ident_argument.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | fn fn_const_1(arg: &i32) {} 3 | fn fn_mut_1(arg: &mut i32) {} 4 | fn fn_const_2<'a>(arg: &Vec) {} 5 | fn fn_mut_2<'a>(arg: &mut Vec) {} 6 | fn fn_const_3<'a>(arg: &'a i32) {} 7 | fn fn_mut_3<'a>(arg: &'a mut i32) {} 8 | fn fn_const_4<'a>(arg: &'a Result) {} 9 | fn fn_mut_4<'a>(arg: &'a mut Result) {} 10 | fn fn_const_5<'a>(arg: &'a Result) -> &'a i32 {} 11 | fn fn_mut_5<'a>(arg: &'a mut Result) -> &'a mut i32 {} 12 | fn fn_const_6(arg: &i32) {} 13 | fn fn_mut_6(arg: &mut i32) {} 14 | fn fn_const_7(arg: &i32) {} 15 | fn fn_mut_7(arg: &mut i32) {} 16 | fn fn_const_8(arg: &i32, second_arg: &mut i64) {} 17 | fn fn_mut_8(arg: &mut i32, second_arg: &i64) {} 18 | fn fn_const_9(arg: &&i32) {} 19 | fn fn_mut_9(arg: &mut &mut i32) {} -------------------------------------------------------------------------------- /tests/module_disambiguation/expected_both/syntax.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | mod module_some_name11 { 3 | pub struct SomeName11(); 4 | } 5 | mod module_some_name12 { 6 | pub struct SomeName12(); 7 | } 8 | mod module_some_name13 { 9 | pub struct SomeName13(); 10 | } 11 | mod module_some_name21 { 12 | pub struct SomeName21(Vec<()>); 13 | } 14 | mod module_some_name22 { 15 | pub struct SomeName22(u32); 16 | } 17 | mod module_some_name23 { 18 | pub struct SomeName23(u64); 19 | } 20 | mod module_some_name31 { 21 | pub struct SomeName31(u8); 22 | } 23 | mod module_some_name32 { 24 | pub struct SomeName32(::Output); 25 | } 26 | mod module_some_name33 { 27 | pub struct SomeName33(u64); 28 | } 29 | mod module_some_name41 { 30 | pub struct module(); 31 | } 32 | mod module_some_name42 { 33 | pub struct module(); 34 | } -------------------------------------------------------------------------------- /tests/errors/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{ 2 | run_basic_expansion_error_tests, run_error_highlight_tests, run_error_hint_tests, 3 | }; 4 | 5 | /// Tests all expected basic error messages in 'basic' on their respective 6 | /// source files in 'source' 7 | /// 8 | /// Expects every source file to have an expected basic. 9 | #[test] 10 | fn basic_expansion_errors() 11 | { 12 | run_basic_expansion_error_tests(file_dir!()); 13 | } 14 | 15 | /// Tests the expected hints in 'hint' against their respective source files in 16 | /// 'source'. 17 | /// 18 | /// Only tests source files that have a hint file. 19 | #[test] 20 | fn hint_expansion_errors() 21 | { 22 | run_error_hint_tests(file_dir!()); 23 | } 24 | 25 | /// Tests the expected code highlights in 'highlight' against their respective 26 | /// source files in 'source'. 27 | /// 28 | /// Only tests source files that have a highlight file. 29 | #[test] 30 | fn highlight_expansion_errors() 31 | { 32 | run_error_highlight_tests(file_dir!()); 33 | } 34 | -------------------------------------------------------------------------------- /tests/no_features/expected/nested_duplicate.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | struct Example { 3 | one: u8, 4 | two: u8, 5 | } 6 | impl Example { 7 | fn inline_new() -> Self { 8 | Example { one: 0, two: 0 } 9 | } 10 | fn attr_new() -> Self { 11 | Example { one: 0, two: 0 } 12 | } 13 | } 14 | struct StructName1(u8, u16); 15 | struct TypeName21(u8, u16); 16 | impl std::error::Error for () {} 17 | impl std::error::Error for () {} 18 | impl std::error::Error for () {} 19 | impl std::error::Error for () {} 20 | trait SomeType {} 21 | impl SomeType for () {} 22 | impl SomeType for () {} 23 | impl SomeType for () {} 24 | impl SomeType for () {} 25 | impl SomeType for () {} 26 | impl SomeType for () {} 27 | impl SomeType for () {} 28 | impl SomeType for () {} 29 | struct Example2 { 30 | one: u8, 31 | } 32 | const SOME_STRUCT1: Example2 = Example2 { one: 0 }; 33 | const SOME_STRUCT2: Example2 = Example2 { one: 1 }; 34 | const SOME_STRUCT3: Example2 = Example2 { one: 2 }; -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Emad Jacob Maroun 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Proposal 3 | about: Suggest a new feature that should be added to the crate or a change to an existing 4 | one 5 | title: '' 6 | labels: D-discussion, I-feature 7 | assignees: '' 8 | 9 | --- 10 | 11 | ### Short Description: 12 | 15 | 16 | ### Motivation: 17 | 24 | 25 | ### Design 26 | 30 | 31 | ### Misc: 32 | 36 | -------------------------------------------------------------------------------- /tests/no_features/from/short_syntax.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | name; 4 | [SomeName1]; 5 | )]//duplicate_end 6 | pub struct name(); 7 | //item_end 8 | 9 | #[duplicate_item( 10 | name member; 11 | [SomeName2] [u8] 12 | )]//duplicate_end 13 | pub struct name(member); 14 | //item_end 15 | 16 | #[duplicate_item( 17 | name member; 18 | [SomeName3] [u8]; 19 | [SomeName4] [u16]; 20 | )]//duplicate_end 21 | pub struct name(member); 22 | //item_end 23 | 24 | #[duplicate_item( 25 | module ; 26 | [ mod1 ]; 27 | [ mod2 ] 28 | )]//duplicate_end 29 | mod module { 30 | use super::*; 31 | 32 | // We add a space so that the test setup doesn't 33 | // recognize it and try to change it to a `duplicate` call 34 | #[ duplicate_item( 35 | name member; 36 | [SomeName5] [u8]; 37 | [SomeName6] [u16]; 38 | )] 39 | pub struct name(member); 40 | 41 | duplicate!{ 42 | [ 43 | name member; 44 | [SomeName7] [u32]; 45 | [SomeName8] [u64]; 46 | ] 47 | pub struct name(member); 48 | } 49 | } 50 | //item_end 51 | 52 | // Test substitution that includes braces 53 | #[duplicate_item( 54 | fn_name var; 55 | [ fn_name_1 ] [ std::io::empty() ]; 56 | [ fn_name_2 ] [ [4;0] ]; 57 | [ fn_name_3 ] [ {} ]; 58 | )]//duplicate_end 59 | fn fn_name() { 60 | let _ = var; 61 | } 62 | //item_end -------------------------------------------------------------------------------- /tests/default_features/mod.rs: -------------------------------------------------------------------------------- 1 | //! This file tests which features are on by default 2 | //! 3 | //! To tests this correctly, tests should be run without using the `--features` 4 | //! and `--no-default-features` flags to ensure that only the default features 5 | //! are enable. 6 | 7 | /// Tests that a feature is enabled by default. 8 | /// 9 | /// Must first be given a unique identifier for the feature (which doesn't 10 | /// necessarily need to be the same as the feature name, but it might be a good 11 | /// idea), and then a string containing the feature name. 12 | /// 13 | /// ### Example 14 | /// 15 | /// The following code will test whether a feature named "feature_name" is 16 | /// enabled by default. 17 | /// 18 | /// ``` 19 | /// default_feature!{feature_id "feature_name"} 20 | /// ``` 21 | macro_rules! default_features { 22 | { 23 | $feature:ident $feature_string:literal 24 | } => { 25 | mod $feature 26 | { 27 | #[cfg(feature = $feature_string)] 28 | const IS_DEFAULT: bool = true; 29 | #[cfg(not(feature = $feature_string))] 30 | const IS_DEFAULT: bool = false; 31 | 32 | #[test] 33 | pub fn is_default() 34 | { 35 | assert!(IS_DEFAULT, "Feature '{}' is not enabled by default.", $feature_string); 36 | } 37 | } 38 | }; 39 | } 40 | 41 | default_features!(pretty_errors "pretty_errors"); 42 | default_features!(module_disambiguation "module_disambiguation"); 43 | -------------------------------------------------------------------------------- /tests/module_disambiguation/from/short_syntax.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | // Tests module names are postfixed from substitution identifier 3 | #[duplicate_item( 4 | name; 5 | [SomeName11]; 6 | [SomeName12]; 7 | [SomeName13] 8 | )]//duplicate_end 9 | mod module { 10 | pub struct name(); 11 | } 12 | //item_end 13 | 14 | // Tests if multiple identifiers are given, the first identifier who's substitutions 15 | // all are simple identifiers (and nothing else) is chosen 16 | #[duplicate_item( 17 | member_type name; 18 | [Vec<()>] [SomeName21]; 19 | [u32] [SomeName22]; 20 | [u64] [SomeName23] 21 | )]//duplicate_end 22 | mod module { 23 | pub struct name(member_type); 24 | } 25 | //item_end 26 | 27 | // Tests if multiple identifiers are given, the first identifier who's substitutions 28 | // all are simple identifiers is chosen 29 | #[duplicate_item( 30 | member_type filler_ident name; 31 | [u8] [SomeIdent] [SomeName31]; 32 | [::Output] [SomeOtherIdent] [SomeName32]; 33 | [u64] [Not] [SomeName33] 34 | )]//duplicate_end 35 | mod module { 36 | pub struct name(member_type); 37 | } 38 | //item_end 39 | 40 | // Tests only the module name is disambiguated and not any identifiers used inside it. 41 | #[duplicate_item( 42 | name; 43 | [SomeName41]; 44 | [SomeName42]; 45 | )]//duplicate_end 46 | mod module { 47 | pub struct module(); 48 | } 49 | //item_end -------------------------------------------------------------------------------- /tests/no_features/mod.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn test_expansions() 3 | { 4 | crate::utils::ExpansionTester::run_default_test_setup("tests/no_features", "testing"); 5 | } 6 | 7 | /// Test that using the crate from an edition 2021 crate works, even if 8 | /// the use expands to code that only works with edition 2021. 9 | #[rustversion::since(1.56)] 10 | #[test] 11 | fn test_edition_2021() 12 | { 13 | let output = std::process::Command::new("cargo") 14 | .arg("build") 15 | .current_dir("tests/no_features/edition_2021") 16 | .output() 17 | .unwrap(); 18 | assert!( 19 | output.status.success(), 20 | "Failed to build edition 2021: {:?}", 21 | output 22 | ); 23 | } 24 | 25 | /// Tests that nowhere in the source code do we call `Group::new` as that has 26 | /// the huge trap of setting the span to `Span::call_site`, which could be 27 | /// extremely problematic (e.g. it means the crate's edition could leak to the 28 | /// user's code) 29 | /// 30 | /// Note: use `new_group` instead of `Group::new` as the sanctioned way to 31 | /// create new groups 32 | #[test] 33 | fn ensure_no_group_new() 34 | { 35 | let re = regex::Regex::new(r"[[:^alpha:]]Group(\s)*::(\s)*new").unwrap(); 36 | for path in std::fs::read_dir("src").unwrap() 37 | { 38 | let path = path.unwrap().path(); 39 | let file_content = std::fs::read_to_string(&path).unwrap(); 40 | assert!( 41 | !re.is_match(file_content.as_str()), 42 | "Found 'Group::new' in {:?}", 43 | path 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/module_disambiguation/expected/short_first_valid_is_chosen.expanded.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | mod module_some_name11 { 3 | pub struct SomeName11(u8); 4 | } 5 | mod module_some_name12 { 6 | pub struct SomeName12(u32); 7 | } 8 | mod module_some_name13 { 9 | pub struct SomeName13(u64); 10 | } 11 | mod module_u8 { 12 | pub struct SomeName21(u8); 13 | } 14 | mod module_u32 { 15 | pub struct SomeName22(u32); 16 | } 17 | mod module_u64 { 18 | pub struct SomeName23(u64); 19 | } 20 | mod module_some_name31 { 21 | pub struct SomeName31(u8); 22 | } 23 | mod module_some_name32 { 24 | pub struct SomeName32(u32); 25 | } 26 | mod module_some_name33 { 27 | pub struct SomeName33(u64); 28 | } 29 | mod module_i8 { 30 | pub struct SomeName41(i8); 31 | } 32 | mod module_i32 { 33 | pub struct SomeName42(i32); 34 | } 35 | mod module_i64 { 36 | pub struct SomeName43(i64); 37 | } 38 | mod module_some_name51 { 39 | pub struct SomeName51(u8); 40 | } 41 | mod module_some_name52 { 42 | pub struct SomeName52(u32); 43 | } 44 | mod module_some_name53 { 45 | pub struct SomeName53(u64); 46 | } 47 | mod module_u16 { 48 | pub struct SomeName61(u16); 49 | } 50 | mod module_i16 { 51 | pub struct SomeName62(i16); 52 | } 53 | mod module_bool { 54 | pub struct SomeName63(bool); 55 | } 56 | mod module_other_ident { 57 | pub struct SomeName71(u8); 58 | } 59 | mod module_another_ident { 60 | pub struct SomeName72(u32); 61 | } 62 | mod module_zero_is_definately_not_the_length_of_this_ident { 63 | pub struct SomeName73(u64); 64 | } -------------------------------------------------------------------------------- /tests/no_features/from/short_nested_duplicate.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | name; 4 | duplicate!{[ some_name; [SomeName1]; [SomeName2] ] 5 | [some_name]; 6 | } 7 | [SomeName3] 8 | )]//duplicate_end 9 | pub struct name(); 10 | //item_end 11 | 12 | // Test more than one nesting 13 | #[duplicate_item( 14 | name; 15 | duplicate!{[ some_name; [SomeName4]; [SomeName5] ] 16 | [some_name]; 17 | } 18 | duplicate!{ 19 | [ // Test verbose syntax in nested call 20 | [ some_name [SomeName6] ] 21 | [ some_name [SomeName7] ] 22 | ] 23 | [some_name]; 24 | } 25 | )]//duplicate_end 26 | pub struct name(); 27 | //item_end 28 | 29 | trait SomeTrait {} 30 | // Test 2 substitution groups in nested invocation. 31 | // Output should be the same as the next test. 32 | #[duplicate_item( 33 | member1 member2; 34 | duplicate!{ [ typ; [u8]; [u16]] 35 | [typ] [u32]; 36 | [typ] [u64]; 37 | } 38 | )]//duplicate_end 39 | impl SomeTrait for (){} 40 | //item_end 41 | 42 | // Test nesting depth of 2. 43 | // Output should be the same as the previous test 44 | #[duplicate_item( 45 | member1 member2; 46 | duplicate!{[ typ; [i8]; [i16]] 47 | duplicate!{[ typ2; [i32]; [i64] ] 48 | [typ] [typ2]; 49 | } 50 | } 51 | )]//duplicate_end 52 | impl SomeTrait for (){} 53 | //item_end 54 | 55 | #[duplicate_item( 56 | name some_int; 57 | [outer_1] [1]; 58 | [outer_2] [2] 59 | )]//duplicate_end 60 | fn name() 61 | { 62 | substitute! ( [ 63 | sub [ some_int ] 64 | ] 65 | sub; 66 | ) 67 | } 68 | //item_end -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "duplicate" 3 | version = "0.0.0" 4 | authors = ["Emad Jacob Maroun "] 5 | edition = "2021" 6 | rust-version = "1.65" 7 | 8 | license = "MIT OR Apache-2.0" 9 | description = "Provides macros for duplication of code with variable substitution." 10 | repository = "https://github.com/Emoun/duplicate" 11 | documentation = "https://docs.rs/duplicate" 12 | readme = "cargo-readme.md" 13 | 14 | categories = ["development-tools"] 15 | 16 | exclude = ["rustfmt.toml", ".gitignore", "README.md", "CHANGELOG.md"] 17 | 18 | [lib] 19 | proc-macro = true 20 | 21 | [dependencies] 22 | # We have to turn of coloring (remove default features), because it causes the Unicode escape character to be emitted 23 | # in hints. See: https://github.com/SergioBenitez/proc-macro2-diagnostics/issues/11 24 | proc-macro2-diagnostics = { version = "0.10", default-features = false, optional = true } 25 | proc-macro2 = { version = "1.0.85", optional = true } 26 | heck = { version = "0.5", optional = true } 27 | 28 | [dev-dependencies] 29 | duplicate_macrotest = "1.0.8" 30 | doc-comment = "0.3.3" 31 | serde = "1.0.105" # Needed because macrotest's cargo.toml uses 1.0 however fails with < 1.0.105 32 | regex = "1.6.0" 33 | rustversion = "1.0.7" 34 | 35 | [features] 36 | default = ["pretty_errors", "module_disambiguation"] 37 | pretty_errors = ["dep:proc-macro2-diagnostics", "dep:proc-macro2"] 38 | module_disambiguation = ["dep:heck"] 39 | fail-on-warnings = [] # Forces compilation to fail if any warnings are given. Used in CI. 40 | 41 | [package.metadata.docs.rs] 42 | all-features = true 43 | -------------------------------------------------------------------------------- /tests/no_features/from/verbose_syntax.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | [ 4 | name [SomeName1] 5 | ] 6 | )]//duplicate_end 7 | pub struct name(); 8 | //item_end 9 | 10 | #[duplicate_item( 11 | [ 12 | name [SomeName2] 13 | member [u8] 14 | ] 15 | )]//duplicate_end 16 | pub struct name(member); 17 | //item_end 18 | 19 | #[duplicate_item( 20 | [ 21 | name [SomeName3] 22 | member [u8] 23 | ] 24 | [ 25 | name [SomeName4] 26 | member [u16] 27 | ] 28 | )]//duplicate_end 29 | pub struct name(member); 30 | //item_end 31 | 32 | #[duplicate_item( 33 | [ 34 | module [ mod1 ] 35 | ] 36 | [ 37 | module [ mod2 ] 38 | ] 39 | )]//duplicate_end 40 | mod module { 41 | use super::*; 42 | 43 | // We add a space so that the test setup doesn't 44 | // recognize it and try to change it to a `duplicate` call 45 | #[ duplicate_item( 46 | [ 47 | name [SomeName5] 48 | member [u8] 49 | ] 50 | [ 51 | name [SomeName6] 52 | member [u16] 53 | ] 54 | )] 55 | pub struct name(member); 56 | 57 | duplicate!{ 58 | [ 59 | [ 60 | name [SomeName7] 61 | member [u32] 62 | ] 63 | [ 64 | name [SomeName8] 65 | member [u64] 66 | ] 67 | ] 68 | pub struct name(member); 69 | } 70 | } 71 | //item_end 72 | 73 | // Test substitution that includes braces 74 | #[duplicate_item( 75 | [ 76 | fn_name [ fn_name_1 ] 77 | var [ std::io::empty() ] 78 | ] 79 | [ 80 | fn_name [ fn_name_2 ] 81 | var [ [4; 0] ] 82 | ] 83 | [ 84 | fn_name [ fn_name_3 ] 85 | var [ {} ] 86 | ] 87 | )]//duplicate_end 88 | fn fn_name() { 89 | let _ = var; 90 | } 91 | //item_end 92 | -------------------------------------------------------------------------------- /tests/module_disambiguation/from/verbose_syntax.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | // Tests module names are postfixed from substitution identifier 3 | #[duplicate_item( 4 | [ 5 | name [SomeName11] 6 | ] 7 | [ 8 | name [SomeName12] 9 | ] 10 | [ 11 | name [SomeName13] 12 | ] 13 | )]//duplicate_end 14 | mod module { 15 | pub struct name(); 16 | } 17 | //item_end 18 | 19 | // Tests if multiple identifiers are given, the first identifier who's substitutions 20 | // all are simple identifiers (and nothing else) is chosen 21 | #[duplicate_item( 22 | [ 23 | member_type [Vec<()>] 24 | name [SomeName21] 25 | ] 26 | [ 27 | member_type [u32] 28 | name [SomeName22] 29 | ] 30 | [ 31 | member_type [u64] 32 | name [SomeName23] 33 | ] 34 | )]//duplicate_end 35 | mod module { 36 | pub struct name(member_type); 37 | } 38 | //item_end 39 | 40 | // Tests if multiple identifiers are given, the first identifier who's substitutions 41 | // all are simple identifiers is chosen 42 | #[duplicate_item( 43 | [ 44 | member_type [u8] 45 | filler_ident [SomeIdent] 46 | name [SomeName31] 47 | ] 48 | [ 49 | member_type [::Output] 50 | filler_ident [SomeOtherIdent] 51 | name [SomeName32] 52 | ] 53 | [ 54 | member_type [u64] 55 | filler_ident [Not] 56 | name [SomeName33] 57 | ] 58 | )]//duplicate_end 59 | mod module { 60 | pub struct name(member_type); 61 | } 62 | //item_end 63 | 64 | // Tests only the module name is disambiguated and not any identifiers used inside it. 65 | #[duplicate_item( 66 | [ 67 | name [SomeName41] 68 | ] 69 | [ 70 | name [SomeName42] 71 | ] 72 | )]//duplicate_end 73 | mod module { 74 | pub struct module(); 75 | } 76 | //item_end -------------------------------------------------------------------------------- /tests/no_features/from/verbose_nested_duplicate.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | #[duplicate_item( 3 | duplicate!{[ some_name; [SomeName1]; [SomeName2] ] 4 | [ name [some_name] ] 5 | } 6 | [ 7 | name [SomeName3] 8 | ] 9 | )]//duplicate_end 10 | pub struct name(); 11 | //item_end 12 | 13 | // Test more than one nesting 14 | #[duplicate_item( 15 | duplicate!{[ some_name; [SomeName4]; [SomeName5] ] 16 | [ name [some_name] ] 17 | } 18 | duplicate!{ 19 | [ // Test verbose syntax in nested call 20 | [ some_name [SomeName6] ] 21 | [ some_name [SomeName7] ] 22 | ] 23 | [ name [some_name] ] 24 | } 25 | )]//duplicate_end 26 | pub struct name(); 27 | //item_end 28 | 29 | trait SomeTrait {} 30 | // Test 2 substitution groups in nested invocation. 31 | // Output should be the same as the next test. 32 | #[duplicate_item( 33 | duplicate!{[ typ; [u8]; [u16] ] 34 | [ 35 | member1 [typ] 36 | member2 [u32] 37 | ] 38 | [ 39 | member1 [typ] 40 | member2 [u64] 41 | ] 42 | } 43 | )]//duplicate_end 44 | impl SomeTrait for (){} 45 | //item_end 46 | 47 | // Test nesting depth of 2. 48 | // Output should be the same as the previous test 49 | #[duplicate_item( 50 | duplicate!{[ typ; [i8]; [i16] ] 51 | duplicate!{[ typ2; [i32]; [i64] ] 52 | [ 53 | member1 [typ] 54 | member2 [typ2] 55 | ] 56 | } 57 | } 58 | )]//duplicate_end 59 | impl SomeTrait for (){} 60 | //item_end 61 | 62 | #[duplicate_item( 63 | [ 64 | name [outer_1] 65 | some_int [1] 66 | ] 67 | [ 68 | name [outer_2] 69 | some_int [2] 70 | ] 71 | )]//duplicate_end 72 | fn name() 73 | { 74 | substitute! ( [ 75 | sub [ some_int ] 76 | ] 77 | sub; 78 | ) 79 | } 80 | //item_end -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug in the crate so a fix can be made 4 | title: '' 5 | labels: D-discussion, I-bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 14 | ### Situation: 15 | 18 | 19 | ### Reproduction: 20 | 24 | 25 | ### Expected Behavior: 26 | 29 | 30 | ### Actual Behavior: 31 | 35 | 36 | ### Affected Versions: 37 | 41 | 42 | ### Local Environment: 43 | 47 | 48 | ### Miscellaneous: 49 | 54 | -------------------------------------------------------------------------------- /tests/no_features/from/short_ident_argument.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | // Test single-token argument 3 | #[duplicate_item( 4 | fn_name refs(type); 5 | [fn_const_1] [&type]; 6 | [fn_mut_1] [&mut type]; 7 | )]//duplicate_end 8 | fn fn_name(arg: refs([i32])){} 9 | //item_end 10 | 11 | // Test multi-token argument 12 | #[duplicate_item( 13 | fn_name refs(type); 14 | [fn_const_2] [& type]; 15 | [fn_mut_2] [& mut type]; 16 | )]//duplicate_end 17 | fn fn_name<'a>(arg: refs([Vec])){} 18 | //item_end 19 | 20 | // Test multi-argument identifier 21 | #[duplicate_item( 22 | fn_name refs(lifetime, type); 23 | [fn_const_3] [& 'lifetime type]; 24 | [fn_mut_3] [& 'lifetime mut type]; 25 | )]//duplicate_end 26 | fn fn_name<'a>(arg: refs([a],[i32])){} 27 | //item_end 28 | 29 | // Test multi-argument identifier and multi-token arguments 30 | #[duplicate_item( 31 | fn_name refs(lifetime, type); 32 | [fn_const_4] [& 'lifetime type]; 33 | [fn_mut_4] [& 'lifetime mut type]; 34 | )]//duplicate_end 35 | fn fn_name<'a>(arg: refs([a],[Result],)){} 36 | //item_end 37 | 38 | // Test multiple invocations of identifiers with arguments 39 | #[duplicate_item( 40 | fn_name refs(lifetime, type); 41 | [fn_const_5] [& 'lifetime type]; 42 | [fn_mut_5] [& 'lifetime mut type]; 43 | )]//duplicate_end 44 | fn fn_name<'a>(arg: refs([a], [Result],)) -> refs([a], [i32]) {} 45 | //item_end 46 | 47 | // Test identifier with argument declaration can be followed by another identifier. 48 | #[duplicate_item( 49 | refs(type) fn_name; 50 | [&type] [fn_const_6]; 51 | [&mut type] [fn_mut_6]; 52 | )]//duplicate_end 53 | fn fn_name(arg: refs([i32])){} 54 | //item_end 55 | 56 | // Test identifier with argument called inside itself 57 | #[duplicate_item( 58 | refs(type) fn_name; 59 | [&type] [fn_const_7]; 60 | [&mut type] [fn_mut_7]; 61 | )]//duplicate_end 62 | fn fn_name(arg: refs([refs([i32])])){} 63 | //item_end 64 | -------------------------------------------------------------------------------- /tests/no_features/from/nested_duplicate.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | struct Example{one: u8, two: u8} 3 | // Tests nesting in substitutions 4 | #[substitute_item( 5 | members [ 6 | duplicate!{ 7 | [ 8 | mem; [one]; [two] 9 | ] 10 | mem: 0, 11 | } 12 | ]; 13 | )]//duplicate_end 14 | impl Example { 15 | fn inline_new() -> Self { 16 | Example {members} 17 | } 18 | fn attr_new() -> Self { 19 | Example {members} 20 | } 21 | } 22 | //item_end 23 | 24 | // Tests nesting between global substitutions 25 | #[substitute_item( 26 | name [StructName1]; 27 | duplicate!{ 28 | [ 29 | nam typ; 30 | [typ1 ] [u8]; 31 | ] 32 | nam [typ]; 33 | } 34 | typ2 [u16]; 35 | )]//duplicate_end 36 | struct name(typ1,typ2); 37 | //item_end 38 | 39 | // Tests nesting between individual substitutions 40 | #[duplicate_item( 41 | name typ1 typ2; 42 | [TypeName21] 43 | duplicate!{ 44 | [ 45 | typ; [u8] 46 | ] 47 | [typ] 48 | } 49 | [u16]; 50 | )]//duplicate_end 51 | struct name(typ1, typ2); 52 | //item_end 53 | 54 | // Tests sequential nesting 55 | #[duplicate_item( 56 | v1; [u8]; [u16]; 57 | )]//duplicate_end 58 | #[duplicate_item( 59 | v2; [u32, v1]; [u64, v1]; 60 | )]//duplicate_end 61 | impl std::error::Error for (){} 62 | //item_end 63 | //item_end 64 | 65 | trait SomeType{} 66 | // Tests sequential nesting (3-deep) 67 | #[duplicate_item( 68 | v1; [u8]; [u16]; 69 | )]//duplicate_end 70 | #[duplicate_item( 71 | v2; [u32, v1]; [u64, v1]; 72 | )]//duplicate_end 73 | #[duplicate_item( 74 | v3; [i8, v2]; [i16, v2]; 75 | )]//duplicate_end 76 | impl SomeType for (){} 77 | //item_end 78 | //item_end 79 | //item_end 80 | 81 | struct Example2{one: u8} 82 | // Tests nesting substitute! in substitutions 83 | #[substitute_item( 84 | member [ 85 | substitute!{ 86 | [ 87 | mem [one]; 88 | ] 89 | mem: 0, 90 | } 91 | ]; 92 | )]//duplicate_end 93 | const SOME_STRUCT1: Example2 = Example2{member}; 94 | //item_end 95 | // Tests nesting substitute! in duplicate 96 | #[duplicate_item( 97 | name member; 98 | [SOME_STRUCT2] [ 99 | substitute!{ 100 | [ 101 | mem [one]; 102 | ] 103 | mem: 1, 104 | } 105 | ]; 106 | [SOME_STRUCT3] [ 107 | substitute!{ 108 | [ 109 | val [2]; 110 | ] 111 | one: val, 112 | } 113 | ]; 114 | )]//duplicate_end 115 | const name: Example2 = Example2{member}; 116 | //item_end -------------------------------------------------------------------------------- /tests/module_disambiguation/from/short_first_valid_is_chosen.rs: -------------------------------------------------------------------------------- 1 | // The following tests all ensure that if multiple substitution identifiers 2 | // can be used to postfix the module, then the first is chosen. 3 | // We have many test here to ensure that if the choice of identifier to use 4 | // is pseudo-random, most likely at least one of them will fail. 5 | use duplicate::*; 6 | // Test 1 7 | #[duplicate_item( 8 | name member_type; 9 | [SomeName11] [u8]; 10 | [SomeName12] [u32]; 11 | [SomeName13] [u64] 12 | )]//duplicate_end 13 | mod module { 14 | pub struct name(member_type); 15 | } 16 | //item_end 17 | 18 | // Test 1, reversed 19 | #[duplicate_item( 20 | member_type name; 21 | [u8] [SomeName21]; 22 | [u32] [SomeName22]; 23 | [u64] [SomeName23] 24 | )]//duplicate_end 25 | mod module { 26 | pub struct name(member_type); 27 | } 28 | //item_end 29 | 30 | // Test 2, the names have been changed from test 1 to have reverse alphabetical order 31 | #[duplicate_item( 32 | a_name b_member_type; 33 | [SomeName31] [u8]; 34 | [SomeName32] [u32]; 35 | [SomeName33] [u64] 36 | )]//duplicate_end 37 | mod module { 38 | pub struct a_name(b_member_type); 39 | } 40 | //item_end 41 | 42 | // Test 2, reversed 43 | #[duplicate_item( 44 | b_member_type a_name; 45 | [i8] [SomeName41]; 46 | [i32] [SomeName42]; 47 | [i64] [SomeName43] 48 | )]//duplicate_end 49 | mod module { 50 | pub struct a_name(b_member_type); 51 | } 52 | //item_end 53 | 54 | // Test 3, 3 valid identifers 55 | #[duplicate_item( 56 | name member_type last_identifier; 57 | [SomeName51] [u8] [OtherIdent]; 58 | [SomeName52] [u32] [AnotherIdent]; 59 | [SomeName53] [u64] [ZeroIsDefinatelyNotTheLengthOfThisIdent] 60 | )]//duplicate_end 61 | mod module { 62 | pub struct name(member_type); 63 | } 64 | //item_end 65 | 66 | // Test 3, permutation 2 67 | #[duplicate_item( 68 | member_type last_identifier name; 69 | [u16] [OtherIdent] [SomeName61]; 70 | [i16] [AnotherIdent] [SomeName62]; 71 | [bool] [ZeroIsDefinatelyNotTheLengthOfThisIdent] [SomeName63] 72 | )]//duplicate_end 73 | mod module { 74 | pub struct name(member_type); 75 | } 76 | //item_end 77 | 78 | // Test 3, permutation 3 79 | #[duplicate_item( 80 | last_identifier name member_type; 81 | [OtherIdent] [SomeName71] [u8]; 82 | [AnotherIdent] [SomeName72] [u32]; 83 | [ZeroIsDefinatelyNotTheLengthOfThisIdent] [SomeName73] [u64] 84 | )]//duplicate_end 85 | mod module { 86 | pub struct name(member_type); 87 | } 88 | //item_end -------------------------------------------------------------------------------- /.github/actions/run-test-groups/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Test All Test Groups' 2 | description: 'Tests all test groups' 3 | inputs: 4 | rust-version: 5 | description: 'Which Rust version to use for test' 6 | required: true 7 | additional-arguments: 8 | description: "Additional arguments to pass to cargo test" 9 | required: false 10 | minimal-versions: 11 | description: "Whether cargo.lock should be set to minimal versions before running the tests" 12 | required: true 13 | 14 | runs: 15 | using: "composite" 16 | steps: 17 | - run: echo "::group::Check Arguments" 18 | shell: bash 19 | - run: ${{(inputs.rust-version != '')}} 20 | shell: bash 21 | - run: echo "::group::Setup Rust Environment" 22 | shell: bash 23 | - shell: bash 24 | run: | 25 | # Install dependencies of 'macrotest' 26 | rustup toolchain install nightly 27 | cargo install --locked cargo-expand 28 | # Set the rust version to use for this build 29 | rustup default ${{ inputs.rust-version }} 30 | - if: inputs.minimal-versions == 'true' 31 | # We use cargo check only to set the cargo.lock file 32 | # since we have to use nightly for -Z 33 | run: cargo +nightly -Z minimal-versions check 34 | shell: bash 35 | - run: echo "::group::Test no_features" 36 | shell: bash 37 | - run: "cargo test ${{ inputs.additional-arguments }} --no-default-features" 38 | shell: bash 39 | - run: echo "::group::Test module_disambiguation" 40 | shell: bash 41 | - run: "cargo test --no-default-features --features module_disambiguation ${{ inputs.additional-arguments }}" 42 | shell: bash 43 | - run: echo "::group::Test pretty_errors" 44 | shell: bash 45 | - run: "cargo test --no-default-features --features pretty_errors ${{ inputs.additional-arguments }}" 46 | shell: bash 47 | - run: echo "::group::Test Default Features" 48 | shell: bash 49 | - run: "cargo test default_features:: ${{ inputs.additional-arguments }}" 50 | shell: bash 51 | - run: echo "::group::Test All Features" 52 | shell: bash 53 | - run: "cargo test --no-default-features --features pretty_errors,module_disambiguation ${{ inputs.additional-arguments }}" 54 | shell: bash 55 | - run: echo "::group::Test Documentation Code" 56 | shell: bash 57 | - run: "cargo test --doc --all-features ${{ inputs.additional-arguments }}" 58 | shell: bash 59 | - run: echo "::group::Build Documentation" 60 | shell: bash 61 | - run: cargo doc 62 | shell: bash 63 | 64 | -------------------------------------------------------------------------------- /tests/no_features/from/short_from_macro.rs: -------------------------------------------------------------------------------- 1 | // These tests ensure that the short syntax works if it 2 | // was produced from the expansion of a macro_rules macro. 3 | // 4 | // Each test consists of a macro_rules declaration which uses 5 | // some specific macro variable type to no_features to the duplicate invocation. 6 | // Then the created macro is invoked. 7 | use duplicate::*; 8 | 9 | macro_rules! test_ident_from_macro_variable{ 10 | { $name:ident } => { 11 | #[duplicate_item( 12 | $name; [SomeName1] 13 | )]//duplicate_end 14 | pub struct $name(); 15 | //item_end 16 | } 17 | } 18 | test_ident_from_macro_variable!(name); 19 | 20 | macro_rules! test_2_idents_from_macro_variable{ 21 | { $($idents:ident)* } => { 22 | #[duplicate_item( 23 | $($idents)*; 24 | [SomeName2] [u8] 25 | )]//duplicate_end 26 | pub struct name(member); 27 | //item_end 28 | } 29 | } 30 | test_2_idents_from_macro_variable!(name member); 31 | 32 | macro_rules! test_ident_from_macro_path_variable{ 33 | { $name:path } => { 34 | #[duplicate_item( 35 | $name; [u8] 36 | )]//duplicate_end 37 | pub struct SomeName3($name); 38 | //item_end 39 | } 40 | } 41 | test_ident_from_macro_path_variable!(name); 42 | 43 | macro_rules! test_ident_from_macro_expr_variable{ 44 | { $name:expr } => { 45 | #[duplicate_item( 46 | $name; [4] 47 | )]//duplicate_end 48 | const SomeName4: u8 = $name; 49 | //item_end 50 | } 51 | } 52 | test_ident_from_macro_expr_variable!(name); 53 | 54 | macro_rules! test_ident_from_macro_type_variable{ 55 | { $name:ty } => { 56 | #[duplicate_item( 57 | $name; [u8] 58 | )]//duplicate_end 59 | pub struct SomeName5($name); 60 | //item_end 61 | } 62 | } 63 | test_ident_from_macro_type_variable!(name); 64 | 65 | macro_rules! test_ident_from_macro_pattern_variable{ 66 | { $name:pat } => { 67 | #[duplicate_item( 68 | $name; [SomeName6] 69 | )]//duplicate_end 70 | fn some_fn6(){ 71 | let $name; 72 | } 73 | //item_end 74 | } 75 | } 76 | test_ident_from_macro_pattern_variable!(name); 77 | 78 | macro_rules! test_ident_from_macro_statement_variable{ 79 | { $name:stmt } => { 80 | #[duplicate_item( 81 | $name; [7] 82 | )]//duplicate_end 83 | fn some_fn7(){ 84 | $name; 85 | } 86 | //item_end 87 | } 88 | } 89 | test_ident_from_macro_statement_variable!(name); 90 | 91 | macro_rules! test_ident_from_macro_token_tree_variable{ 92 | { $name:tt } => { 93 | #[duplicate_item( 94 | $name; [SomeName8] 95 | )]//duplicate_end 96 | pub struct $name(); 97 | //item_end 98 | } 99 | } 100 | test_ident_from_macro_token_tree_variable!(name); -------------------------------------------------------------------------------- /tests/no_features/from/verbose_from_macro.rs: -------------------------------------------------------------------------------- 1 | // These tests ensure that the verbose syntax works if it 2 | // was produced from the expansion of a macro_rules macro. 3 | // 4 | // Each test consists of a macro_rules declaration which uses 5 | // some specific macro variable type to no_features to the duplicate invocation. 6 | // Then the created macro is invoked. 7 | use duplicate::*; 8 | 9 | macro_rules! test_ident_from_macro_variable{ 10 | { $name:ident } => { 11 | #[duplicate_item( 12 | [ 13 | $name [SomeName1] 14 | ] 15 | )]//duplicate_end 16 | pub struct $name(); 17 | //item_end 18 | } 19 | } 20 | test_ident_from_macro_variable!(name); 21 | 22 | macro_rules! test_2_idents_from_macro_variable{ 23 | { $($idents:ident)*, $($tts:tt)*} => { 24 | #[duplicate_item( 25 | [ 26 | $($idents[$tts])* 27 | ] 28 | )]//duplicate_end 29 | pub struct name(member); 30 | //item_end 31 | } 32 | } 33 | test_2_idents_from_macro_variable!(name member, SomeName2 u8); 34 | 35 | macro_rules! test_ident_from_macro_path_variable{ 36 | { $name:path } => { 37 | #[duplicate_item( 38 | [ 39 | $name [u8] 40 | ] 41 | )]//duplicate_end 42 | pub struct SomeName3($name); 43 | //item_end 44 | } 45 | } 46 | test_ident_from_macro_path_variable!(name); 47 | 48 | macro_rules! test_ident_from_macro_expr_variable{ 49 | { $name:expr } => { 50 | #[duplicate_item( 51 | [ 52 | $name [4] 53 | ] 54 | )]//duplicate_end 55 | const SomeName4: u8 = $name; 56 | //item_end 57 | } 58 | } 59 | test_ident_from_macro_expr_variable!(name); 60 | 61 | macro_rules! test_ident_from_macro_type_variable{ 62 | { $name:ty } => { 63 | #[duplicate_item( 64 | [ 65 | $name [u8] 66 | ] 67 | )]//duplicate_end 68 | pub struct SomeName5($name); 69 | //item_end 70 | } 71 | } 72 | test_ident_from_macro_type_variable!(name); 73 | 74 | macro_rules! test_ident_from_macro_pattern_variable{ 75 | { $name:pat } => { 76 | #[duplicate_item( 77 | [ 78 | $name [SomeName6] 79 | ] 80 | )]//duplicate_end 81 | fn some_fn6(){ 82 | let $name; 83 | } 84 | //item_end 85 | } 86 | } 87 | test_ident_from_macro_pattern_variable!(name); 88 | 89 | macro_rules! test_ident_from_macro_statement_variable{ 90 | { $name:stmt } => { 91 | #[duplicate_item( 92 | [ 93 | $name [7] 94 | ] 95 | )]//duplicate_end 96 | fn some_fn7(){ 97 | $name; 98 | } 99 | //item_end 100 | } 101 | } 102 | test_ident_from_macro_statement_variable!(name); 103 | 104 | macro_rules! test_ident_from_macro_token_tree_variable{ 105 | { $name:tt } => { 106 | #[duplicate_item( 107 | [ 108 | $name [SomeName8] 109 | ] 110 | )]//duplicate_end 111 | pub struct $name(); 112 | //item_end 113 | } 114 | } 115 | test_ident_from_macro_token_tree_variable!(name); -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::Span; 2 | #[cfg(feature = "pretty_errors")] 3 | use proc_macro2::Span as Span2; 4 | #[cfg(feature = "pretty_errors")] 5 | use proc_macro2_diagnostics::{Diagnostic, Level}; 6 | 7 | /// Used to report errors. 8 | /// 9 | /// When 'pretty_errors' isn't enabled, simply includes a basic message. 10 | /// When enabled, adds a span for the source of the error and a more detailed 11 | /// and helpful message we call hint. 12 | #[derive(Debug)] 13 | pub struct Error 14 | { 15 | /// Basic error message. 16 | /// 17 | /// Will always be reported (first). 18 | msg: String, 19 | 20 | /// The source of the error 21 | #[cfg(feature = "pretty_errors")] 22 | span: Span, 23 | 24 | /// Additional error details and help 25 | #[cfg(feature = "pretty_errors")] 26 | hint: String, 27 | } 28 | 29 | impl Error 30 | { 31 | /// Creates a basic error. 32 | pub fn new(msg: impl Into) -> Self 33 | { 34 | #[cfg(feature = "pretty_errors")] 35 | { 36 | Self { 37 | msg: msg.into(), 38 | span: Span::call_site(), 39 | hint: "".to_string(), 40 | } 41 | } 42 | #[cfg(not(feature = "pretty_errors"))] 43 | { 44 | Self { msg: msg.into() } 45 | } 46 | } 47 | 48 | /// Adds a span to the error and returns it. 49 | /// 50 | /// If `pretty_errors` is disabled, does nothing. 51 | #[allow(unused_variables)] 52 | #[allow(unused_mut)] 53 | pub fn span(mut self, span: Span) -> Self 54 | { 55 | #[cfg(feature = "pretty_errors")] 56 | { 57 | self.span = span; 58 | } 59 | self 60 | } 61 | 62 | /// Adds a hint to the error and returns it. 63 | /// 64 | /// If `pretty_errors` is disabled, does nothing. 65 | #[allow(unused_variables)] 66 | #[allow(unused_mut)] 67 | pub fn hint(mut self, hint: impl Into) -> Self 68 | { 69 | #[cfg(feature = "pretty_errors")] 70 | { 71 | self.hint = hint.into(); 72 | } 73 | self 74 | } 75 | 76 | /// Returns the source span of the error 77 | /// (or a stub value if the `pretty_errors` feature is disabled). 78 | pub fn get_span(&self) -> Span 79 | { 80 | #[cfg(feature = "pretty_errors")] 81 | { 82 | self.span 83 | } 84 | #[cfg(not(feature = "pretty_errors"))] 85 | { 86 | Span::call_site() 87 | } 88 | } 89 | 90 | /// Returns the message of the error. 91 | #[cfg(not(feature = "pretty_errors"))] 92 | pub fn into_panic_message(self) -> String 93 | { 94 | self.msg 95 | } 96 | 97 | #[cfg(feature = "pretty_errors")] 98 | /// Converts the error into a [`Diagnostic`] ready for emitting. 99 | pub fn into_diagnostic(self) -> Diagnostic 100 | { 101 | let mut diagnostic = Diagnostic::spanned(Span2::from(self.span), Level::Error, self.msg); 102 | if !self.hint.is_empty() 103 | { 104 | diagnostic = diagnostic.help(self.hint); 105 | } 106 | diagnostic 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/module_disambiguation.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::Error, 3 | token_iter::{is_ident, SubGroupIter}, 4 | Result, SubstitutionGroup, TokenIter, 5 | }; 6 | use heck::ToSnakeCase; 7 | use proc_macro::{Ident, Span, TokenStream, TokenTree}; 8 | 9 | /// Finds a substitution identifier whose substitutions only contain one 10 | /// identifier and nothing else for all duplicates. 11 | pub(crate) fn find_simple<'a>( 12 | substitutions: impl Iterator + Clone, 13 | mod_span: Span, 14 | ) -> Result 15 | { 16 | let mut substitutions = substitutions.peekable(); 17 | if substitutions.peek().is_none() 18 | { 19 | // No duplications are made, so either the module doesn't need disambiguation 20 | // (as even with global substitutions only 1 duplicate will be made) 21 | // or the invocation will fails somewhere else (from the lack of substitution 22 | // groups) 23 | return Ok("".into()); 24 | } 25 | 'outer: for ident in substitutions.peek().unwrap().identifiers_ordered() 26 | { 27 | for group in substitutions.clone() 28 | { 29 | let substitution = group.substitution_of(ident).unwrap(); 30 | if substitution.substitutes_identifier().is_none() 31 | { 32 | continue 'outer; 33 | } 34 | } 35 | return Ok(ident.clone()); 36 | } 37 | Err(Error::new( 38 | "Was unable to find a suitable substitution identifier to postfix on the module's \ 39 | name.\nHint: If a substitution identifier's substitutions all consist of a single \ 40 | identifier and nothing, they will automatically be postfixed on the module name to make \ 41 | them unique.", 42 | ) 43 | .span(mod_span)) 44 | } 45 | 46 | /// If the next token is the 'mod' keyword, substitutes the following module 47 | /// name with its disambiguation, returning 'mod' plus the disambiguation. 48 | pub(crate) fn try_substitute_mod<'a, T: SubGroupIter<'a>>( 49 | // If Some(), then tries to disambiguate, otherwise doesn't. 50 | // 51 | // First is the module name to disambiguate, then the substitution identifier to use 52 | // for disambiguation. 53 | mod_and_postfix_sub: &Option<(Ident, String)>, 54 | substitutions: &SubstitutionGroup, 55 | // The item being substituted. Will consume 'mod' and the following name if successful 56 | item_iter: &mut TokenIter<'a, T>, 57 | ) -> TokenStream 58 | { 59 | let mut result = TokenStream::new(); 60 | if let Some((mod_name, mod_sub_ident)) = mod_and_postfix_sub 61 | { 62 | if let Ok(mod_keyword) = item_iter.extract_simple(|t| is_ident(t, Some("mod")), |t| t, None) 63 | { 64 | result.extend(Some(mod_keyword).into_iter()); 65 | 66 | // Consume mod name (since we will replace it) 67 | let mod_name_t = item_iter.next_fallible().unwrap().unwrap(); 68 | 69 | let postfix = substitutions 70 | .substitution_of(&mod_sub_ident) 71 | .unwrap() 72 | .substitutes_identifier() 73 | .unwrap() 74 | .to_string() 75 | .to_snake_case(); 76 | let replacement_name = mod_name.to_string() + "_" + &postfix; 77 | let replacement = Ident::new(&replacement_name, TokenTree::from(mod_name_t).span()); 78 | result.extend(Some(TokenTree::Ident(replacement)).into_iter()); 79 | } 80 | } 81 | result 82 | } 83 | -------------------------------------------------------------------------------- /tests/no_features/from/verbose_ident_argument.rs: -------------------------------------------------------------------------------- 1 | use duplicate::*; 2 | // Test single-token argument 3 | #[duplicate_item( 4 | [ 5 | fn_name [fn_const_1] 6 | refs(type) [&type] 7 | ] 8 | [ 9 | fn_name [fn_mut_1] 10 | refs(type) [&mut type] 11 | ] 12 | )]//duplicate_end 13 | fn fn_name(arg: refs([i32])){} 14 | //item_end 15 | 16 | // Test multi-token argument 17 | #[duplicate_item( 18 | [ 19 | fn_name [fn_const_2] 20 | refs(type) [&type] 21 | ] 22 | [ 23 | fn_name [fn_mut_2] 24 | refs(type) [&mut type] 25 | ] 26 | )]//duplicate_end 27 | fn fn_name<'a>(arg: refs([Vec])){} 28 | //item_end 29 | 30 | // Test multi-argument identifier 31 | #[duplicate_item( 32 | [ 33 | fn_name [fn_const_3] 34 | refs(lifetime, type) [& 'lifetime type] 35 | ] 36 | [ 37 | fn_name [fn_mut_3] 38 | refs(lifetime, type) [& 'lifetime mut type] 39 | ] 40 | )]//duplicate_end 41 | fn fn_name<'a>(arg: refs([a],[i32])){} 42 | //item_end 43 | 44 | // Test multi-argument identifier and multi-token arguments 45 | #[duplicate_item( 46 | [ 47 | fn_name [fn_const_4] 48 | refs(lifetime, type) [& 'lifetime type] 49 | ] 50 | [ 51 | fn_name [fn_mut_4] 52 | refs(lifetime, type) [& 'lifetime mut type] 53 | ] 54 | )]//duplicate_end 55 | fn fn_name<'a>(arg: refs([a],[Result],)){} 56 | //item_end 57 | 58 | // Test multiple invocations of identifiers with arguments 59 | #[duplicate_item( 60 | [ 61 | fn_name [fn_const_5] 62 | refs(lifetime, type) [& 'lifetime type] 63 | ] 64 | [ 65 | fn_name [fn_mut_5] 66 | refs(lifetime, type) [& 'lifetime mut type] 67 | ] 68 | )]//duplicate_end 69 | fn fn_name<'a>(arg: refs([a], [Result],)) -> refs([a], [i32]) {} 70 | //item_end 71 | 72 | // Test the same identifier argument can have different names in different substitution groups. 73 | #[duplicate_item( 74 | [ 75 | fn_name [fn_const_6] 76 | refs(type_1) [&type_1] 77 | ] 78 | [ 79 | fn_name [fn_mut_6] 80 | refs(type_2) [&mut type_2] 81 | ] 82 | )]//duplicate_end 83 | fn fn_name(arg: refs([i32])){} 84 | //item_end 85 | 86 | // Test that identifiers with arguments don't have to come in the same order in different 87 | // substitution groups. 88 | #[duplicate_item( 89 | [ 90 | fn_name [fn_const_7] 91 | refs(type) [&type] 92 | ] 93 | [ 94 | refs(type) [&mut type] 95 | fn_name [fn_mut_7] 96 | ] 97 | )]//duplicate_end 98 | fn fn_name(arg: refs([i32])){} 99 | //item_end 100 | 101 | // Test multiple identifiers with arguments 102 | #[duplicate_item( 103 | [ 104 | fn_name [fn_const_8] 105 | refs(type) [&type] 106 | arg_2(type) [&mut type] 107 | ] 108 | [ 109 | refs(type) [&mut type] 110 | fn_name [fn_mut_8] 111 | arg_2(type) [&type] 112 | ] 113 | )]//duplicate_end 114 | fn fn_name(arg: refs([i32]), second_arg: arg_2([i64])){} 115 | //item_end 116 | 117 | // Test identifier with argument called inside itself 118 | #[duplicate_item( 119 | [ 120 | fn_name [fn_const_9] 121 | refs(type) [&type] 122 | ] 123 | [ 124 | refs(type) [&mut type] 125 | fn_name [fn_mut_9] 126 | ] 127 | )]//duplicate_end 128 | fn fn_name(arg: refs([refs([i32])])){} 129 | //item_end -------------------------------------------------------------------------------- /cargo-readme.md: -------------------------------------------------------------------------------- 1 | duplicate 2 | ============================= 3 | 4 | Crate for easy code duplication with substitution. 5 | 6 | ## Motivation 7 | 8 | If you find yourself in need of copying a block of code and then making some small changes to fit the new use case, this crate is for you. 9 | 10 | The `duplicate_item` attribute macro will duplicate an item any number of times while inserting custom code in the designated places in each duplicate. 11 | The `duplicate` function-like procedural macro will do the same for any code you give it. 12 | 13 | For an in-depth explanation of the syntax and features, [see the documentation](https://docs.rs/duplicate). 14 | 15 | ## Example 16 | 17 | ```rust 18 | use duplicate::duplicate_item; 19 | 20 | /// Trait we want to implement for u8, u16, and u32 21 | trait IsMax { 22 | /// Returns true if self is its maximum possible value. 23 | fn is_max(&self) -> bool; 24 | } 25 | 26 | #[duplicate_item( 27 | int_type max_value; 28 | [ u8 ] [ 255 ]; 29 | [ u16 ] [ 65_535 ]; 30 | [ u32 ] [ 4_294_967_295 ]; 31 | )] 32 | impl IsMax for int_type { 33 | fn is_max(&self) -> bool { 34 | *self == max_value 35 | } 36 | } 37 | 38 | assert!(!42u8.is_max()); 39 | assert!(!42u16.is_max()); 40 | assert!(!42u32.is_max()); 41 | ``` 42 | Expands to: 43 | 44 | ```rust 45 | use duplicate::duplicate_item; 46 | 47 | /// Trait we want to implement for u8, u16, and u32 48 | trait IsMax { 49 | /// Returns true if self is its maximum possible value. 50 | fn is_max(&self) -> bool; 51 | } 52 | 53 | impl IsMax for u8 { 54 | fn is_max(&self) -> bool { 55 | *self == 255 56 | } 57 | } 58 | impl IsMax for u16 { 59 | fn is_max(&self) -> bool { 60 | *self == 65_535 61 | } 62 | } 63 | impl IsMax for u32 { 64 | fn is_max(&self) -> bool { 65 | *self == 4_294_967_295 66 | } 67 | } 68 | 69 | assert!(!42u8.is_max()); 70 | assert!(!42u16.is_max()); 71 | assert!(!42u32.is_max()); 72 | ``` 73 | 74 | ## MSRV Policy 75 | 76 | This crate's _Minimum Supported Rust Version_ (MSRV) depends on which features are enabled. 77 | 78 | The _Base MSRV_ is 1.65. It applies when no features are enabled and is the lowest possible MSRV. 79 | Enabling the following features increases the MSRV to the stated version: 80 | 81 | _No features currently increase the MSRV beyond the base._ 82 | 83 | Enabling features not on the above list doesn't increase the MSRV. 84 | 85 | Increasing the Base MSRV or the MSRV of any specific existing feature is a breaking change and will be accompanied by a major version bump. 86 | Adding new features doesn't count as a breaking change, even if they are enabled by default and thereby increase the commulative MSRV of the default features. 87 | 88 | Only the minimal versions of this crate's direct and transitive dependencies are guaranteed to work with the MSRV. 89 | 90 | ## Changelog 91 | 92 | This project adheres to [Semantic Versioning.](https://semver.org/spec/v2.0.0.html) 93 | 94 | [changelog_body] 95 | 96 | This changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and shows only the changes since the previous version. 97 | [See the full changelog](https://github.com/Emoun/duplicate/blob/master/CHANGELOG.md) for changes to all released versions. 98 | 99 | #### License 100 | 101 | 102 | Licensed under either of Apache License, Version 103 | 2.0 or MIT license at your option. 104 | 105 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: '**' 6 | tags: 7 | - '[0-9]+.[0-9]+.[0-9]+' 8 | - '[0-9]+.[0-9]+.[0-9]+-**' 9 | pull_request: 10 | schedule: 11 | # Run every monday at 5:45 AM 12 | - cron: '45 5 * * MON' 13 | 14 | env: 15 | CARGO_TERM_COLOR: always 16 | 17 | jobs: 18 | test: 19 | name: Test (${{matrix.rust-version}}) 20 | runs-on: ubuntu-latest 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | rust-version: ['1.65.0', stable, nightly] 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Run Test Groups 29 | uses: ./.github/actions/run-test-groups 30 | with: 31 | rust-version: ${{ matrix.rust-version }} 32 | minimal-versions: ${{ matrix.rust-version == '1.65.0' }} 33 | 34 | deny-warnings: 35 | name: Deny Warnings 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - name: Run Test Groups 40 | uses: ./.github/actions/run-test-groups 41 | with: 42 | rust-version: stable 43 | additional-arguments: --features=fail-on-warnings 44 | 45 | rustfmt: 46 | name: Rustfmt 47 | runs-on: ubuntu-latest 48 | env: 49 | RUSTV: nightly-2024-04-16 50 | steps: 51 | - uses: actions/checkout@v2 52 | - name: Install 53 | run: | 54 | rustup toolchain install $RUSTV 55 | rustup component add --toolchain $RUSTV rustfmt 56 | - name: Check 57 | run: cargo +$RUSTV fmt -- --check 58 | deploy: 59 | name: Deploy 60 | runs-on: ubuntu-latest 61 | needs: [test, rustfmt, deny-warnings] 62 | if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') 63 | steps: 64 | - uses: actions/checkout@v2 65 | - name: Get Version 66 | run: echo GIT_VERSION=$(git describe --tags) >> $GITHUB_ENV 67 | - name: Prepare Git 68 | run: | 69 | git config user.email "github@github.com" 70 | git config user.name "Github Actions" 71 | git checkout -b master 72 | # Use throw-away branch so we don't push the changes to origin 73 | git checkout -b deploy_branch 74 | - name: Prepare Crate 75 | run: | 76 | # Update cargo version, 77 | sed -i "s/version = \"0.0.0\"/version = \"$GIT_VERSION\"/" Cargo.toml 78 | git add Cargo.toml 79 | # Insert changes to cargo readme 80 | sed -n "/^## \[Unreleased]/,/^## \[[0-9]/p;/^## \[[0-9]/q" CHANGELOG.md | head -n -1 | tail -n +2 > CHANGES.txt 81 | sed -e '/\[changelog_body]/{' -e 'r CHANGES.txt' -e 'd' -e '}' -i cargo-readme.md 82 | git add cargo-readme.md 83 | rm CHANGES.txt 84 | # Commit changes so cargo doesn't complain about dirty repo 85 | git commit -m "Deploy changes." 86 | # Package crate to ensure it works without issue 87 | cargo package 88 | - name: Cargo Login 89 | env: 90 | CRATES_IO_DEPLOY_TOKEN: ${{ secrets.CRATES_IO_DEPLOY_TOKEN }} 91 | run: cargo login "$CRATES_IO_DEPLOY_TOKEN" 92 | - name: Publish 93 | run: cargo publish 94 | - name: Update Changelog 95 | run: | 96 | # Back to master to clean the changes made during packaging. 97 | git checkout master 98 | # Update changelog 99 | DATE=$(date +%Y-%m-%d) 100 | sed -i "s/## \[Unreleased]/## \[Unreleased]\n\n## \[$GIT_VERSION] - $DATE/" CHANGELOG.md 101 | git add CHANGELOG.md 102 | git commit -m "Reset CHANGELOG after v$GIT_VERSION." 103 | # Push changes to repo 104 | git push origin master 105 | -------------------------------------------------------------------------------- /src/pretty_errors.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "pretty_errors"), allow(dead_code))] 2 | 3 | /// For when substitution parameters aren't enclosed in brackets 4 | pub(crate) const BRACKET_SUB_PARAM: &'static str = r#"Substitution parameters should be enclosed in '[]' each. 5 | Example: 6 | sub_ident( [ parameter1 ] , [ paramter2 ] ) 7 | ^^^ ^^^ ^^^ ^^^ 8 | "#; 9 | 10 | /// For when neither syntaxes get any invocation input 11 | pub(crate) const NO_INVOCATION: &'static str = 12 | "substitution_identifier (short syntax) or substitution group (verbose syntax)"; 13 | 14 | /// Basic message for when no substitution group is given, but at least one must 15 | /// be given 16 | pub(crate) const NO_GROUPS: &'static str = r#"Expected substitution group."#; 17 | 18 | /// Hint for when no substitution group is given, but at least one must be given 19 | pub(crate) const NO_GROUPS_HINT: &'static str = "Must specify at least one substitution group, \ 20 | otherwise use 'substitute!' or 'substitute_item'"; 21 | 22 | /// For when short syntax has declared substitution identifiers but no 23 | /// substitution groups. 24 | pub(crate) const SHORT_SYNTAX_NO_GROUPS: &'static str = r#"Add a substitution group after the substitution identifiers. 25 | Example: 26 | name; 27 | [SomeSubstitution]; 28 | ^^^^^^^^^^^^^^^^^^^ 29 | "#; 30 | 31 | /// For when short syntax substitutions aren't enclosed in brackets 32 | pub(crate) const SHORT_SYNTAX_MISSING_SUB_BRACKET: &'static str = r#"Each substitution should be enclosed in '[]'. 33 | Example: 34 | ident1 ident2; 35 | [ sub1 ] [ sub2 ] ; 36 | ^^^ ^^^^^ ^^^ 37 | "#; 38 | 39 | /// For when short syntax substitution group has too few or too many 40 | /// substitutions 41 | pub(crate) const SHORT_SYNTAX_SUBSTITUTION_COUNT: &'static str = r#"Number of substitutions must match the number of substitutions identifiers. 42 | Example: 43 | ident1 ident2; 44 | 1^^^^^^ ^^^^^^2 45 | [sub1] [sub2]; 46 | 1^^^^^^ ^^^^^^2 47 | "#; 48 | 49 | /// For when verbose syntax substitution group has too few or too many 50 | /// substitutions 51 | pub(crate) const VERBOSE_SYNTAX_SUBSTITUTION_IDENTIFIERS: &'static str = r#"All substitution groups must define the same substitution identifiers. 52 | Example: 53 | [ 54 | ident1 [sub1] 55 | ident2 [sub2] 56 | ] 57 | [ 58 | ident1 [sub3] 59 | ident2 [sub4] 60 | ] 61 | "#; 62 | 63 | /// For when verbose syntax substitution identifier has too few or too many 64 | /// arguments 65 | pub(crate) const VERBOSE_SYNTAX_SUBSTITUTION_IDENTIFIERS_ARGS: &'static str = r#"The same substitution identifier must take the same number of argument across all substitution groups. 66 | Example: 67 | [ 68 | ident1(arg1, arg2) [sub1 arg1 arg2] 69 | ^^^^^^^^^^ 70 | ] 71 | [ 72 | ident1(arg1, arg2) [arg1 arg2 sub2] 73 | ^^^^^^^^^^ 74 | ] 75 | "#; 76 | 77 | /// For when verbose syntax substitution pair is followed by a semicolon 78 | pub(crate) const VERBOSE_SEMICOLON: &'static str = r#"Verbose syntax does not accept semicolons between substitutions. 79 | Example: 80 | [ 81 | name [sub1] // No semicolon 82 | ty [u32] // No semicolon 83 | ] 84 | "#; 85 | 86 | /// For when global substitutions aren't followed by ';' 87 | pub(crate) const GLOBAL_SUB_SEMICOLON: &'static str = r#"Each global substitution should end with ';' 88 | Example: 89 | name [sub1]; 90 | typ [sub2]; 91 | "#; 92 | 93 | /// For when module disambiguation cannot find a substitution identifier to use 94 | /// for disambiguation 95 | #[cfg_attr(not(feature = "module_disambiguation"), allow(dead_code))] 96 | pub(crate) const MOD_DISAMB_NO_UNIQUE_SUB: &'static str = r#"There must be a substitution identifier that is substituted by a single and unique identifier for each duplicate. 97 | Example: 98 | invalid valid; 99 | [i32] [som_ident]; 100 | [Some>] [som_other_ident]; 101 | "#; 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | duplicate 2 | ============================= 3 | 4 | [![Rust](https://github.com/Emoun/duplicate/actions/workflows/rust.yml/badge.svg)](https://github.com/Emoun/duplicate/actions) 5 | [![Latest Version](https://img.shields.io/crates/v/duplicate.svg)](https://crates.io/crates/duplicate) 6 | [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/duplicate) 7 | 8 | macros for code duplication with substitution. 9 | 10 | This document is meant for contributors to the crate. The documentation is hosted at [docs.rs](https://docs.rs/duplicate). 11 | 12 | # Testing 13 | 14 | #### Setup 15 | 16 | This crate uses [macrotest](https://crates.io/crates/macrotest) for testing expansions. 17 | Therefore, before running these tests the nightly compiler must be installed together with `rustfmt` and `cargo-expand`: 18 | 19 | ``` 20 | cargo install --locked cargo-expand 21 | rustup toolchain install nightly 22 | rustup component add rustfmt 23 | ``` 24 | 25 | The tests can then be run normally using `cargo test` as seen below. 26 | 27 | #### Test Groups 28 | 29 | Tests are divided into the following groups: 30 | 31 | - `No Features`: 32 | Tests the minimal API of the crate with no features enabled. 33 | 34 | ``` 35 | cargo test --no-default-features 36 | ``` 37 | 38 | - `Default Features`: 39 | Test that the features that are enabled by default. 40 | 41 | ``` 42 | cargo test 43 | ``` 44 | 45 | - `All Feature Combinations`: 46 | Tests any combination of features. After `--features` add a comma separated list of features to test: 47 | 48 | ``` 49 | cargo test --no-default-features --features module_disambiguation,pretty_errors 50 | ``` 51 | 52 | - `documentation`: 53 | Tests code in the documentation. Even though some of the other test groups might test some of the documentaion code, they are not guaranteed to run all tests. E.g. the test of the cargo readme file (`cargo-readme.md` are only run when this command is used. 54 | ``` 55 | cargo test --doc --all-features 56 | ``` 57 | 58 | #### Warnings 59 | 60 | Compilation warnings are prohibited in this crate and cause CI failure. 61 | However, this prohibition of off by default in the code to allow for warnings while work is still in progress. 62 | 63 | To make compilation fail on warnings, simply add `--features=fail-on-warnings` to your build/test command. E.g.: 64 | 65 | ``` 66 | cargo test --features=fail-on-warnings 67 | ``` 68 | 69 | # Formatting 70 | 71 | We use `rustfmt` to manage the formatting of this crate's code. 72 | To have cargo format the code for you, you must have the nightly compiler installed (but not necessarily the default) and then run the command: 73 | 74 | ``` 75 | cargo +nightly fmt 76 | ``` 77 | 78 | The CI/CD will check the formatting and fail if it isn't formatted correctly. 79 | 80 | # Release Deployment 81 | 82 | Deployment of new versions of this crate is managed by CI/CD using git tags. 83 | To trigger a new release, simply push a tag to the repository containing only the version number: 84 | 85 | ``` 86 | git tag 1.0.0 87 | git push --tags 88 | ``` 89 | 90 | We do not use the `Cargo.toml` to manage the versioning of this crate. 91 | The version given in it should not be changed! 92 | It must remain as `0.0.0` so CI/CD can correctly modify it for every release. 93 | 94 | CI/CD will also reset the change log as part of the release, so do not change the `## [Unreleased]` line nor add an entry for new releases yourself. 95 | 96 | CI/CD will also add the new release's changes to `cargo-readme.md` under the `Changelog` section. So do not touch that either. 97 | 98 | #### License 99 | 100 | 101 | Licensed under either of Apache License, Version 102 | 2.0 or MIT license at your option. 103 | 104 | 105 |
106 | 107 | 108 | Unless you explicitly state otherwise, any contribution intentionally 109 | submitted for inclusion in this crate by you, as defined in the Apache-2.0 110 | license, shall be dual licensed as above, without any additional terms or 111 | conditions. 112 | 113 | 114 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [2.0.1] - 2025-11-03 11 | 12 | ### Changed 13 | 14 | - Documentaion on nested invocations now clarifies that inner calls to `duplicate` are expanded before outer calls. 15 | 16 | ### Fixed 17 | 18 | - Hints no longer print unusual Unicode characters. This was caused by [an issue with the `proc-macro2-diagnostics`](https://github.com/SergioBenitez/proc-macro2-diagnostics/issues/11) crate. 19 | - Fixed an issue where nested calls would result in outer calls only using substitutes from the first substitution group in all duplicates. 20 | - Module disambiguation now produces an error if no substitution identifier is suitable for disambiguating the module name. See [#64](https://github.com/Emoun/duplicate/issues/64) 21 | 22 | ## [2.0.0] - 2024-09-16 23 | 24 | ### Added 25 | 26 | - `substitute!` and `substitute_item` allow the use of global substitutions without duplication. See [#49](https://github.com/Emoun/duplicate/issues/49). 27 | 28 | ### Changed 29 | 30 | - [BREAKING] Increased base MSRV to 1.65. 31 | - [BREAKING] `duplicate!` and `duplicate_item` no longer allow using exclusively global substitutions. 32 | - Edition increased to 2021. 33 | - Replaced `proc-macro-error` dependency with `proc-macro2-diagnostics` for printing nice error messages and hints. See [#61](https://github.com/Emoun/duplicate/issues/61). 34 | - Updated `heck` dependency to version 0.5. 35 | 36 | ## [1.0.0] - 2023-03-10 37 | 38 | ### Changed 39 | 40 | - Overhauled the `pretty_errors` feature to more consistently provide useful hints and code highlights. See [#19](https://github.com/Emoun/duplicate/issues/19). 41 | 42 | ### Fixed 43 | 44 | - `duplicate` now also substitutes the invocations of nested `duplicate`'s. See [#48](https://github.com/Emoun/duplicate/issues/48). 45 | - Forgetting to enclose subtitution parameters in brackets now reports sensible error. See [#30](https://github.com/Emoun/duplicate/issues/30). 46 | - Short syntax no longer accepts providing no substitution groups after the identifier list. See [#29](https://github.com/Emoun/duplicate/issues/29). 47 | - Fixed several issues where code was accepted which shouldn't have been. 48 | 49 | ## [0.4.1] - 2022-07-17 50 | 51 | ### Fixed 52 | 53 | - Fixed an issue where `duplicate`'s edition would leak into the user's code. See [#47](https://github.com/Emoun/duplicate/issues/47). 54 | 55 | ## [0.4.0] - 2022-02-20 56 | 57 | ### Added 58 | 59 | - Nested invocations can now be used everywhere. 60 | 61 | ### Changed 62 | 63 | - [BREAKING] Renamed `duplicate::duplicate` to `duplicate::duplicate_item`. See [#40](https://github.com/Emoun/duplicate/issues/40). 64 | - [BREAKING] Renamed `duplicate::duplicate_inline` to `duplicate::duplicate`. See [#40](https://github.com/Emoun/duplicate/issues/40). 65 | - [BREAKING] Nested invocation now uses `duplicate!{[] }` syntax instead of `#[][]`. See [#28](https://github.com/Emoun/duplicate/issues/28). 66 | - [BREAKING] Increased base MSRV to 1.42. See [#45](https://github.com/Emoun/duplicate/issues/45) 67 | - [BREAKING] Relaxed MSRV policy. Only the minimal versions of direct and transitive dependencies are guaranteed to work for the MSRV. See [#44](https://github.com/Emoun/duplicate/issues/44) 68 | - No longer pinning dependencies following new MSRV policy. 69 | - Updated `heck` dependency to version 0.4. 70 | 71 | ## [0.3.0] - 2021-06-08 72 | 73 | ### Added 74 | 75 | - Global Substitution: Allows substitutions that are applied to all duplicates equally. See [#23](https://github.com/Emoun/duplicate/issues/23). 76 | 77 | ### Changed 78 | 79 | - [BREAKING] Limited which group delimiters are allowed in various syntactic positions. 80 | The specific delimiters required now follow the use of delimiters in previous documentation examples. 81 | See [#25](https://github.com/Emoun/duplicate/issues/25). 82 | - Substituted the `convert_case` dependency for `heck`. See [#22](https://github.com/Emoun/duplicate/issues/22). 83 | 84 | 85 | ### Fixed 86 | 87 | - Now also substitutes code inside substitution arguments. See [#24](https://github.com/Emoun/duplicate/issues/24). 88 | - Module_disambiguation: Now only substitutes the module name and not any matching identifier in the body of the module. See [#27](https://github.com/Emoun/duplicate/issues/27). 89 | 90 | ## [0.2.9] - 2020-09-28 91 | 92 | ### Added 93 | 94 | - New _Minimum Supported Rust Version_ (MSRV) Policy. Briefly, MSRV is 1.34 but may be increased by enabling features. See [#18](https://github.com/Emoun/duplicate/issues/18#issuecomment-697554595). 95 | - The crate's readme now contains section on MSRV policy. 96 | - Parameterized substitution is now also available for the verbose syntax. See [#8](https://github.com/Emoun/duplicate/issues/8). 97 | 98 | ## [0.2.8] - 2020-09-24 99 | 100 | ## [0.2.7] - 2020-08-10 101 | 102 | ### Added 103 | 104 | - `duplicate_inline`: Function-like procedural macro version of `duplicate`. 105 | Has the same functionality, but can be used in other contexts and can duplicate any number of items. 106 | See also [#6](https://github.com/Emoun/duplicate/issues/6). 107 | 108 | ### Changed 109 | 110 | - Updated `proc_macro_error` dependency to version 1.0.4. 111 | 112 | ## [0.2.6] - 2020-07-13 113 | 114 | ### Added 115 | 116 | - New feature named `pretty_errors` (enabled by default). When enabled, errors are more detailed and helpful. 117 | - New feature named `module_disambiguation` (enabled by default). When enabled, automatically uses a suitable substitution identifier to disambiguate the name of a module being duplicated. See the documentation for more details. See also [#7](https://github.com/Emoun/duplicate/issues/7). 118 | 119 | ### Changed 120 | 121 | - Errors are now less detailed and helpful unless the `pretty_errors` feature is enabled. 122 | - The dependence on the `proc_macro_error` crate is now optional and used by the `pretty_errors` feature. 123 | 124 | ## [0.2.5] - 2020-06-29 125 | 126 | ### Fixed 127 | 128 | - Fixed a build issue when using version 1.0.3 of `proc_macro_error`. See [#11](https://github.com/Emoun/duplicate/issues/11). 129 | 130 | ### Changed 131 | 132 | - The `proc_macro_error` dependency is fixed to version 1.0.3 to avoid potential breaking changes with future updates to that crate. 133 | 134 | ## [0.2.4] - 2020-06-23 135 | 136 | ### Fixed 137 | 138 | - Fixed issue with the short syntax where substitutions that included any bracket type would be expanded wrong. See [#9](https://github.com/Emoun/duplicate/issues/9). 139 | 140 | ## [0.2.3] - 2020-06-21 [YANKED] 141 | 142 | ### Added 143 | 144 | - Short syntax now supports parameterized substitution. 145 | This allows identifiers to take arguments that can be used to customize the substitution for each use. 146 | See [#5](https://github.com/Emoun/duplicate/issues/5). 147 | 148 | ## [0.2.2] - 2020-05-17 149 | 150 | ### Added 151 | 152 | - Short syntax now supports nested macro invocations. Can only be used after the initial list of substitution identifiers. See [#2](https://github.com/Emoun/duplicate/issues/2). 153 | 154 | ### Changed 155 | 156 | - Updated documentation. The short syntax is now used for the primary examples. 157 | - Crate readme license section no longer includes paragraph on the licensing of contibutions. 158 | 159 | ### Fixed 160 | 161 | - Fixed an issue where `#[duplicate(..)]` would throw an error if it was generated by a macro expansion. 162 | It would fail if a substitution identifier originated from a macro variable. 163 | This is caused by the variable's expansion sometimes being wrapped in a 164 | [group](https://doc.rust-lang.org/proc_macro/struct.Group.html) with 165 | [no delimiters](https://doc.rust-lang.org/proc_macro/enum.Delimiter.html#variant.None), 166 | which `duplicate` couldn't handle. 167 | 168 | ## [0.2.1] - 2020-04-26 169 | 170 | ### Changed 171 | 172 | - Updated crate readme to new short syntax. 173 | 174 | ## [0.2.0] - 2020-04-26 175 | 176 | ### Added 177 | 178 | - Crate readme changelog section now asserts the use of [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 179 | 180 | ### Changed 181 | 182 | - New short syntax with row-based substitution groups instead of the old column-based syntax. 183 | See [github issue](https://github.com/Emoun/duplicate/issues/1). 184 | 185 | ## [0.1.5] - 2020-04-12 186 | 187 | ### Changed 188 | 189 | - Crate readme format titles are now bigger than changelog entries. 190 | 191 | ## [0.1.4] - 2020-04-12 192 | 193 | ### Added 194 | 195 | - Crate readme now includes list of changes when published to `crates.io`. 196 | 197 | ## [0.1.3] - 2020-04-12 198 | 199 | ## [0.1.2] - 2020-04-9 200 | 201 | ### Added 202 | 203 | - `duplicate` attribute macro for code duplication and substitution. 204 | - Short invocation syntax for `duplicate`. 205 | - Verbose invocation syntax for `duplicate`. -------------------------------------------------------------------------------- /src/substitute.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "module_disambiguation")] 2 | use crate::module_disambiguation::try_substitute_mod; 3 | use crate::{ 4 | disambiguate_module, error::Error, new_group, token_iter::SubGroupIter, Result, 5 | SubstitutionGroup, Token, TokenIter, 6 | }; 7 | use proc_macro::{Delimiter, Ident, Span, TokenStream, TokenTree}; 8 | 9 | /// The types of sub-substitutions composing a single substitution. 10 | #[derive(Debug)] 11 | pub enum SubType 12 | { 13 | /// A simple substitution with the TokenStream 14 | Token(TokenStream), 15 | /// Substitute with the TokenStream in the argument of given index. 16 | Argument(usize), 17 | /// Substitution with a group with the specified delimiter and the contents 18 | /// being what is produced by the nested substitution. 19 | Group(Delimiter, Substitution), 20 | } 21 | 22 | /// A substitution for an identifier. 23 | /// 24 | /// A substitution takes a specific number of arguments as TokenStreams and 25 | /// produces a TokenStream that is to be substituted for the identifier. 26 | /// 27 | /// To create a substitution for an identifier, `new` is given the list of 28 | /// arguments and the tokens to be used as a substitution. The `apply` method 29 | /// can then be given a set of substitution arguments as TokenStreams (the 30 | /// number of arguments must match the number given to `new`,) which will yield 31 | /// the final TokenStream that should be substituted for the identifier ( + 32 | /// arguments). 33 | #[derive(Debug)] 34 | pub struct Substitution 35 | { 36 | /// The number of arguments to the substitution 37 | arg_count: usize, 38 | /// The substitution. The list is ordered, with the result of an application 39 | /// being the concatenation of each sub-substitution. 40 | sub: Vec, 41 | } 42 | 43 | impl Substitution 44 | { 45 | /// Create a new substitution that takes no arguments. 46 | pub fn new_simple(substitution: TokenStream) -> Self 47 | { 48 | Self { 49 | arg_count: 0, 50 | sub: vec![SubType::Token(substitution)], 51 | } 52 | } 53 | 54 | /// Create a new substitution. 55 | /// 56 | /// The given argument list is assumed to be ordered and its length is the 57 | /// number of arguments to the substitution. 58 | /// The tokens produced by the iterator will be the basis for applying a 59 | /// substitution, where each instance of an argument identifier being 60 | /// replaced by the arguments passed to the a substitution identifier. 61 | pub(crate) fn new<'a, T: SubGroupIter<'a>>( 62 | arguments: &Vec, 63 | mut stream: TokenIter<'a, T>, 64 | ) -> Result 65 | { 66 | let mut substitutions = Vec::new(); 67 | // Group tokens that aren't substitution identifiers or groups 68 | let mut saved_tokens = None; 69 | 70 | let find_argument = 71 | |ident: &Ident| arguments.iter().position(|arg| *arg == ident.to_string()); 72 | 73 | while let Some(token) = stream.next_fallible()? 74 | { 75 | match token 76 | { 77 | Token::Simple(TokenTree::Ident(ident)) if find_argument(&ident).is_some() => 78 | { 79 | if let Some(sub_stream) = saved_tokens.take() 80 | { 81 | substitutions.push(SubType::Token(sub_stream)); 82 | } 83 | substitutions.push(SubType::Argument(find_argument(&ident).unwrap())); 84 | }, 85 | Token::Group(del, iter, _) => 86 | { 87 | if let Some(sub_stream) = saved_tokens.take() 88 | { 89 | substitutions.push(SubType::Token(sub_stream)); 90 | } 91 | substitutions.push(SubType::Group(del, Substitution::new(arguments, iter)?)); 92 | }, 93 | token => 94 | { 95 | saved_tokens 96 | .get_or_insert_with(|| TokenStream::new()) 97 | .extend(Some(TokenTree::from(token)).into_iter()) 98 | }, 99 | } 100 | } 101 | if let Some(sub_stream) = saved_tokens 102 | { 103 | substitutions.push(SubType::Token(sub_stream)); 104 | } 105 | let substitution = Self { 106 | arg_count: arguments.len(), 107 | sub: substitutions, 108 | }; 109 | Ok(substitution) 110 | } 111 | 112 | /// Apply the substitution, assuming it takes no arguments. 113 | pub fn apply_simple(&self, err_span: Span) -> Result 114 | { 115 | self.apply(&Vec::new(), err_span) 116 | } 117 | 118 | /// Apply the substitution to the given arguments. 119 | /// 120 | /// The number of arguments must match the exact number accepted by the 121 | /// substitution. 122 | pub fn apply(&self, arguments: &Vec, err_span: Span) -> Result 123 | { 124 | if arguments.len() == self.arg_count 125 | { 126 | let mut result = TokenStream::new(); 127 | for sub in self.sub.iter() 128 | { 129 | result.extend( 130 | match sub 131 | { 132 | SubType::Token(stream) => stream.clone(), 133 | SubType::Argument(idx) => arguments[*idx].clone(), 134 | SubType::Group(delimiter, subst) => 135 | { 136 | TokenStream::from(TokenTree::Group(new_group( 137 | delimiter.clone(), 138 | subst.apply(arguments, err_span)?, 139 | Span::call_site(), 140 | ))) 141 | }, 142 | } 143 | .into_iter(), 144 | ) 145 | } 146 | Ok(result) 147 | } 148 | else 149 | { 150 | Err(Error::new(format!( 151 | "Expected {} substitution arguments but got {}", 152 | self.arg_count, 153 | arguments.len() 154 | )) 155 | .span(err_span)) 156 | } 157 | } 158 | 159 | #[cfg(feature = "module_disambiguation")] 160 | /// If this substitution simply produces an identifier and nothing else, 161 | /// then that identifier is returned, otherwise None 162 | pub fn substitutes_identifier(&self) -> Option 163 | { 164 | if self.sub.len() == 1 165 | { 166 | if let SubType::Token(token) = &self.sub[0] 167 | { 168 | let mut iter = token.clone().into_iter(); 169 | if let TokenTree::Ident(ident) = iter.next()? 170 | { 171 | // Ensure there are no more tokens, since we only allow 1 identifier. 172 | if iter.next().is_none() 173 | { 174 | return Some(ident); 175 | } 176 | } 177 | } 178 | } 179 | None 180 | } 181 | 182 | pub fn argument_count(&self) -> usize 183 | { 184 | self.arg_count 185 | } 186 | } 187 | 188 | /// Duplicates the given token stream, substituting any identifiers found. 189 | pub(crate) fn duplicate_and_substitute<'a>( 190 | item: TokenStream, 191 | global_subs: &'a SubstitutionGroup, 192 | mut sub_groups: impl Iterator + Clone, 193 | ) -> Result 194 | { 195 | let mut result = TokenStream::new(); 196 | #[allow(unused_variables)] 197 | let mod_and_postfix_sub = disambiguate_module(&item, sub_groups.clone())?; 198 | #[cfg(feature = "module_disambiguation")] 199 | { 200 | if let Some((mod_ident, sub_ident)) = &mod_and_postfix_sub 201 | { 202 | let mut subs = std::collections::HashSet::new(); 203 | for sub in sub_groups.clone() 204 | { 205 | if !subs.insert( 206 | sub.substitution_of(sub_ident) 207 | .unwrap() 208 | .apply_simple(Span::call_site())? 209 | .to_string(), 210 | ) 211 | { 212 | return Err(Error::new("Item cannot be disambiguated") 213 | .span(mod_ident.span()) 214 | .hint(crate::pretty_errors::MOD_DISAMB_NO_UNIQUE_SUB)); 215 | } 216 | } 217 | } 218 | } 219 | 220 | let sub_groups_clone = sub_groups.clone(); 221 | let mut duplicate_and_substitute_one = |substitutions: &SubstitutionGroup| -> Result<()> { 222 | let mut item_iter = TokenIter::new(item.clone(), global_subs, sub_groups_clone.clone()); 223 | 224 | #[cfg(feature = "module_disambiguation")] 225 | let mut substituted_mod = false; 226 | loop 227 | { 228 | #[cfg(feature = "module_disambiguation")] 229 | { 230 | if !substituted_mod 231 | { 232 | let stream = 233 | try_substitute_mod(&mod_and_postfix_sub, substitutions, &mut item_iter); 234 | substituted_mod = !stream.is_empty(); 235 | result.extend(stream); 236 | } 237 | } 238 | 239 | if let Some(stream) = substitute_next_token(&mut item_iter, global_subs, substitutions)? 240 | { 241 | result.extend(stream); 242 | } 243 | else 244 | { 245 | break; 246 | } 247 | } 248 | Ok(()) 249 | }; 250 | 251 | // We always want at least 1 duplicate. 252 | // If no groups are given, we just want to run the global substitutions 253 | let empty_sub = SubstitutionGroup::new(); 254 | duplicate_and_substitute_one(sub_groups.next().unwrap_or(&empty_sub))?; 255 | 256 | for substitutions in sub_groups 257 | { 258 | duplicate_and_substitute_one(&substitutions)?; 259 | } 260 | 261 | Ok(result) 262 | } 263 | 264 | /// Recursively checks the given token for any use of the given substitution 265 | /// identifiers and substitutes them, returning the resulting token stream. 266 | fn substitute_next_token<'a, T: SubGroupIter<'a>>( 267 | tree: &mut TokenIter<'a, T>, 268 | global_subs: &SubstitutionGroup, 269 | substitutions: &SubstitutionGroup, 270 | ) -> Result> 271 | { 272 | let mut result = None; 273 | match tree.next_fallible()? 274 | { 275 | Some(Token::Simple(TokenTree::Ident(ident))) => 276 | { 277 | match ( 278 | substitutions.substitution_of(&ident.to_string()), 279 | global_subs.substitution_of(&ident.to_string()), 280 | ) 281 | { 282 | (Some(subst), None) | (None, Some(subst)) => 283 | { 284 | let stream = if subst.arg_count > 0 285 | { 286 | let (mut group_iter, span) = 287 | tree.next_group(Some(Delimiter::Parenthesis))?; 288 | let mut args = Vec::new(); 289 | loop 290 | { 291 | match group_iter.next_group(Some(Delimiter::Bracket)) 292 | { 293 | Ok((group, _)) => 294 | { 295 | args.push(duplicate_and_substitute( 296 | group.to_token_stream(), 297 | global_subs, 298 | Some(substitutions).into_iter(), 299 | )?); 300 | if group_iter.has_next()? 301 | { 302 | group_iter.expect_comma()?; 303 | } 304 | }, 305 | Err(err) => 306 | { 307 | if group_iter.has_next()? 308 | { 309 | return Err( 310 | err.hint(crate::pretty_errors::BRACKET_SUB_PARAM) 311 | ); 312 | } 313 | else 314 | { 315 | break; 316 | } 317 | }, 318 | } 319 | } 320 | subst.apply(&args, span)? 321 | } 322 | else 323 | { 324 | subst.apply_simple(ident.span())? 325 | }; 326 | result 327 | .get_or_insert_with(|| TokenStream::new()) 328 | .extend(stream.into_iter()); 329 | }, 330 | (None, None) => 331 | { 332 | result 333 | .get_or_insert_with(|| TokenStream::new()) 334 | .extend(TokenStream::from(TokenTree::Ident(ident)).into_iter()); 335 | }, 336 | _ => 337 | { 338 | return Err( 339 | Error::new("Multiple substitutions for identifier").span(ident.span()) 340 | ) 341 | }, 342 | } 343 | }, 344 | Some(Token::Group(del, mut group_iter, span)) => 345 | { 346 | let mut substituted = TokenStream::new(); 347 | while let Some(stream) = 348 | substitute_next_token(&mut group_iter, global_subs, substitutions)? 349 | { 350 | substituted.extend(stream) 351 | } 352 | result.get_or_insert_with(|| TokenStream::new()).extend( 353 | TokenStream::from(TokenTree::Group(new_group(del, substituted, span))).into_iter(), 354 | ); 355 | }, 356 | Some(token) => 357 | { 358 | result 359 | .get_or_insert_with(|| TokenStream::new()) 360 | .extend(Some(TokenTree::from(token)).into_iter()) 361 | }, 362 | _ => (), 363 | } 364 | Ok(result) 365 | } 366 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2022] [Emad Jacob Maroun] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/token_iter.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::Error, invoke_nested, new_group, Result, SubstitutionGroup}; 2 | use proc_macro::{token_stream::IntoIter, Delimiter, Ident, Spacing, Span, TokenStream, TokenTree}; 3 | use std::{ 4 | collections::VecDeque, 5 | fmt::{Debug, Formatter}, 6 | iter::{once, FromIterator}, 7 | }; 8 | 9 | /// Trait alias 10 | pub(crate) trait SubGroupIter<'a>: Iterator + Clone {} 11 | impl<'a, T: Iterator + Clone> SubGroupIter<'a> for T {} 12 | 13 | /// Designates the type of a token 14 | #[derive(Debug, Clone)] 15 | pub(crate) enum Token<'a, T: SubGroupIter<'a>> 16 | { 17 | /// A simple token (i.e. not a group) 18 | Simple(TokenTree), 19 | 20 | /// A group with the given delimiter, body, and original span 21 | Group(Delimiter, TokenIter<'a, T>, Span), 22 | } 23 | impl<'a, T: SubGroupIter<'a>> Token<'a, T> 24 | { 25 | /// Returns the span of the enclosed token(s) 26 | pub(crate) fn span(&self) -> Span 27 | { 28 | match self 29 | { 30 | Token::Simple(t) => t.span(), 31 | Token::Group(_, _, span) => span.clone(), 32 | } 33 | } 34 | } 35 | 36 | impl<'a, T: SubGroupIter<'a>> From> for TokenTree 37 | { 38 | fn from(t: Token<'a, T>) -> Self 39 | { 40 | match t 41 | { 42 | Token::Simple(t) => t, 43 | Token::Group(d, iter, span) => 44 | { 45 | TokenTree::Group(new_group(d, iter.to_token_stream(), span)) 46 | }, 47 | } 48 | } 49 | } 50 | 51 | /// Whether the token tree is a punctuation 52 | fn is_punct(t: &TokenTree, c: char) -> bool 53 | { 54 | if let TokenTree::Punct(p) = t 55 | { 56 | p.as_char() == c && p.spacing() == Spacing::Alone 57 | } 58 | else 59 | { 60 | false 61 | } 62 | } 63 | 64 | /// Whether the token tree is a semicolon punctuation 65 | pub fn is_semicolon(t: &TokenTree) -> bool 66 | { 67 | is_punct(t, ';') 68 | } 69 | 70 | /// Whether the token tree is an identifier, and if so, whether it is equal to 71 | /// the given string (if given) 72 | pub fn is_ident(t: &TokenTree, comp: Option<&str>) -> bool 73 | { 74 | if let TokenTree::Ident(id) = t 75 | { 76 | comp.map_or(true, |comp| comp == id.to_string()) 77 | } 78 | else 79 | { 80 | false 81 | } 82 | } 83 | 84 | /// If the given token tree is an identifier, gets it. 85 | pub fn get_ident(t: TokenTree) -> Option 86 | { 87 | if let TokenTree::Ident(id) = t 88 | { 89 | Some(id) 90 | } 91 | else 92 | { 93 | None 94 | } 95 | } 96 | 97 | /// Used to iterate through tokens from a TokenStream. 98 | /// 99 | /// Will automatically expand any nested `duplicate` calls, ensuring only final 100 | /// tokens are produced. Before doing the expansion, will duplicate/substitute 101 | /// the nested invocation according to the given rules. This is needed e.g. when 102 | /// the outer invocation affects the inner invocation's invocation and not 103 | /// only the body. 104 | /// 105 | /// Will also automatically extract tokens from any group without delimiters 106 | /// instead of producing the group itself. Therefore, any group produced is 107 | /// guaranteed to no use the None delimiter. 108 | /// 109 | /// Most methods return a Result because the processing happens lazily, meaning 110 | /// a processing error (e.g. if nested invocations fail) can happen at any time. 111 | /// If a method returns an error, no tokens are consumed. 112 | #[derive(Clone)] 113 | pub(crate) struct TokenIter<'a, T: SubGroupIter<'a>> 114 | { 115 | /// Tokens that have yet to be processed 116 | raw_tokens: IntoIter, 117 | 118 | /// Tokens that have yet to be produced 119 | /// 120 | /// If a token is a None-delimited group, its tokens are in the process of 121 | /// being produced. 122 | unconsumed: VecDeque>, 123 | 124 | /// While processing, nested invocations are first substituted with these 125 | /// global substitutions 126 | global_subs: &'a SubstitutionGroup, 127 | 128 | /// While processing, nested invocations are first duplicated with these 129 | /// substitution groups. 130 | sub_groups: T, 131 | 132 | /// The span of the last token to be produced. 133 | last_span: Span, 134 | } 135 | impl<'a, T: SubGroupIter<'a>> TokenIter<'a, T> 136 | { 137 | /// Gets at least 1 token from the raw stream and puts it in the unconsumed, 138 | /// expanding any nested invocation if encountered 139 | /// 140 | /// Returns whether at least 1 token was added to the unconsumed queue. 141 | fn fetch(&mut self) -> Result 142 | { 143 | if let Some(t) = self.raw_tokens.next() 144 | { 145 | /// The string identifying a nested `duplicate!` invocation 146 | const NESTED_DUPLICATE_NAME: &'static str = "duplicate"; 147 | /// The string identifying a nested `substitute!` invocation 148 | const NESTED_SUBSTITUTE_NAME: &'static str = "substitute"; 149 | match t 150 | { 151 | TokenTree::Group(g) => 152 | { 153 | self.unconsumed.push_back(Token::Group( 154 | g.delimiter(), 155 | TokenIter::new_like(g.stream(), self), 156 | g.span(), 157 | )) 158 | }, 159 | TokenTree::Ident(id) 160 | if id.to_string() == NESTED_DUPLICATE_NAME 161 | || id.to_string() == NESTED_SUBSTITUTE_NAME => 162 | { 163 | if let Some(TokenTree::Punct(p)) = self.raw_tokens.next() 164 | { 165 | if is_punct(&TokenTree::Punct(p.clone()), '!') 166 | { 167 | let stream = invoke_nested( 168 | &mut TokenIter::new_like( 169 | TokenStream::from_iter(self.raw_tokens.next().into_iter()), 170 | self, 171 | ), 172 | id.to_string() == NESTED_DUPLICATE_NAME, 173 | )?; 174 | self.unconsumed.push_back(Token::Group( 175 | Delimiter::None, 176 | TokenIter::new_like(stream, self), 177 | p.span(), 178 | )); 179 | } 180 | else 181 | { 182 | // Not nested invocation 183 | self.unconsumed 184 | .push_back(Token::Simple(TokenTree::Ident(id))); 185 | self.unconsumed 186 | .push_back(Token::Simple(TokenTree::Punct(p))); 187 | } 188 | } 189 | else 190 | { 191 | // Not nested invocation 192 | self.unconsumed 193 | .push_back(Token::Simple(TokenTree::Ident(id))); 194 | } 195 | }, 196 | _ => self.unconsumed.push_back(Token::Simple(t)), 197 | } 198 | Ok(true) 199 | } 200 | else 201 | { 202 | Ok(false) 203 | } 204 | } 205 | 206 | /// Attempts to get the next unconsumed token. 207 | /// 208 | /// If the next token is a None-delimited group, attempts to get its next 209 | /// token instead. If such a group is empty, removed it and tries again. 210 | fn next_unconsumed(&mut self) -> Result>> 211 | { 212 | self.unconsumed.pop_front().map_or(Ok(None), |t| { 213 | match t 214 | { 215 | Token::Group(del, mut iter, span) if del == Delimiter::None => 216 | { 217 | match iter.next_fallible() 218 | { 219 | Ok(Some(t)) => 220 | { 221 | self.unconsumed.push_front(Token::Group(del, iter, span)); 222 | Ok(Some(t)) 223 | }, 224 | Ok(None) => self.next_fallible(), 225 | err => err, 226 | } 227 | }, 228 | t => Ok(Some(t)), 229 | } 230 | }) 231 | } 232 | 233 | /// Gets the next fully processed token 234 | pub fn next_fallible(&mut self) -> Result>> 235 | { 236 | self.fetch()?; 237 | self.next_unconsumed() 238 | } 239 | 240 | /// Extracts a value from the next token. 241 | /// 242 | /// An error is returned if: 243 | /// * the next token is a delimited group 244 | /// * no token is left 245 | /// * `p` returns false for the next token 246 | /// 247 | /// If `p` returns true, the token is given to `f` whose result is returned. 248 | /// If an error is returned, if given, `expected` should describe what input 249 | /// was expected 250 | pub fn extract_simple bool, F: FnOnce(TokenTree) -> R>( 251 | &mut self, 252 | p: P, 253 | f: F, 254 | expected: Option<&str>, 255 | ) -> Result 256 | { 257 | let create_error = |error: &str| { 258 | let mut err = Error::new(error); 259 | if let Some(expected_string) = expected 260 | { 261 | err = err.hint("Expected ".to_string() + expected_string + "."); 262 | } 263 | err 264 | }; 265 | match self.peek()? 266 | { 267 | Some(Token::Simple(t)) if p(&t) => 268 | { 269 | self.last_span = t.span(); 270 | Ok(f(self.next_fallible().unwrap().unwrap().into())) 271 | }, 272 | Some(Token::Simple(t)) => Err(create_error("Unexpected token.").span(t.span())), 273 | Some(Token::Group(_, _, span)) => 274 | { 275 | Err(create_error("Unexpected delimiter.").span(span.clone())) 276 | }, 277 | None => Err(create_error("Unexpected end of code.")), 278 | } 279 | } 280 | 281 | /// Extracts the next identifier token. 282 | /// 283 | /// Returns an error if the next token is not an identifier. 284 | pub fn extract_identifier(&mut self, expected: Option<&str>) -> Result 285 | { 286 | self.extract_simple(|t| is_ident(t, None), |t| get_ident(t).unwrap(), expected) 287 | } 288 | 289 | /// Ensures the next token is a simple token. 290 | /// 291 | /// Returns an error if: 292 | /// * the next token is a delimited group 293 | /// * no token is left 294 | /// * `p` returns false for the next token 295 | /// 296 | /// If an error is returned, if given, the expected string is used in the 297 | /// error message 298 | pub fn expect_simple bool>( 299 | &mut self, 300 | p: P, 301 | expected: Option<&str>, 302 | ) -> Result<()> 303 | { 304 | self.extract_simple(p, |_| (), expected) 305 | } 306 | 307 | /// Ensures the next token is a comma. 308 | /// 309 | /// Otherwise returns an error. 310 | pub fn expect_comma(&mut self) -> Result<()> 311 | { 312 | self.expect_simple(|t| is_punct(t, ','), Some(",")) 313 | } 314 | 315 | /// Ensures the next token is a semicolon. 316 | /// 317 | /// Otherwise returns an error. 318 | pub fn expect_semicolon(&mut self) -> Result<()> 319 | { 320 | self.expect_simple(is_semicolon, Some("';'")) 321 | } 322 | 323 | /// Gets the body and span of the next group. 324 | /// 325 | /// Returns an error if: 326 | /// * the group is non-delimited 327 | /// * no more tokens are available 328 | /// * the next group doesn't use the expected delimiter 329 | pub fn next_group(&mut self, expected: Option) -> Result<(Self, Span)> 330 | { 331 | assert_ne!( 332 | Some(Delimiter::None), 333 | expected, 334 | "should only be used with non-None delimiters" 335 | ); 336 | 337 | let left_delimiter = |d| { 338 | match d 339 | { 340 | Some(Delimiter::Bracket) => "'['", 341 | Some(Delimiter::Brace) => "'{'", 342 | Some(Delimiter::Parenthesis) => "'('", 343 | None => "'{', '[', or '('", 344 | _ => unreachable!(), 345 | } 346 | }; 347 | let error = || format!("Expected {}.", left_delimiter(expected)); 348 | 349 | match self.peek()? 350 | { 351 | Some(Token::Group(del, _, span)) if *del != Delimiter::None => 352 | { 353 | if let Some(exp_del) = expected 354 | { 355 | if exp_del != *del 356 | { 357 | return Err(Error::new(error()).span(span.clone())); 358 | } 359 | } 360 | if let Token::Group(_, iter, span) = self.next_fallible()?.unwrap() 361 | { 362 | self.last_span = span; 363 | Ok((iter, span)) 364 | } 365 | else 366 | { 367 | unreachable!() 368 | } 369 | }, 370 | Some(token) => Err(Error::new(error()).span(token.span())), 371 | _ => Err(Error::new(error()).span(self.last_span)), 372 | } 373 | } 374 | 375 | /// Converts to a TokenStream immediately processing the whole iterator, 376 | /// panicking if an error is encountered. 377 | pub fn process_all(mut self) -> TokenStream 378 | { 379 | let mut result = TokenStream::new(); 380 | while let Some(t) = self.next_fallible().unwrap() 381 | { 382 | result.extend(once(TokenTree::from(t))); 383 | } 384 | result 385 | } 386 | 387 | /// Convert to TokenStream __without any processing__. 388 | pub fn to_token_stream(self) -> TokenStream 389 | { 390 | TokenStream::from_iter( 391 | self.unconsumed 392 | .into_iter() 393 | .map(|tok| TokenTree::from(tok)) 394 | .chain(self.raw_tokens), 395 | ) 396 | } 397 | 398 | /// Whether there are more tokens to produced 399 | pub fn has_next(&mut self) -> Result 400 | { 401 | self.peek().map_or_else(|e| Err(e), |t| Ok(t.is_some())) 402 | } 403 | 404 | /// Whether there is a next token and it is a ';' 405 | #[cfg_attr(not(feature = "pretty_errors"), allow(dead_code))] 406 | pub fn has_next_semicolon(&mut self) -> Result 407 | { 408 | self.peek().map_or_else( 409 | |e| Err(e), 410 | |t| { 411 | Ok(match t 412 | { 413 | Some(Token::Simple(t)) if is_semicolon(t) => true, 414 | _ => false, 415 | }) 416 | }, 417 | ) 418 | } 419 | 420 | /// Peek at the next token to be produced without consuming it 421 | pub fn peek(&mut self) -> Result>> 422 | { 423 | let (pop_front, should_fetch, new_front) = match self.unconsumed.front_mut() 424 | { 425 | Some(Token::Group(del, iter, _)) if *del == Delimiter::None => 426 | { 427 | if let Some(t) = iter.next_fallible()? 428 | { 429 | (false, false, Some(t)) 430 | } 431 | else 432 | { 433 | (true, true, None) 434 | } 435 | }, 436 | None => (false, true, None), 437 | _ => (false, false, None), 438 | }; 439 | if pop_front 440 | { 441 | self.unconsumed.pop_front(); 442 | } 443 | if should_fetch 444 | { 445 | if self.fetch()? 446 | { 447 | return self.peek(); 448 | } 449 | } 450 | if let Some(t) = new_front 451 | { 452 | self.unconsumed.push_front(t); 453 | } 454 | Ok(self.unconsumed.front()) 455 | } 456 | 457 | /// Returns the given token to the front, such that it is the next to be 458 | /// produced 459 | pub fn push_front(&mut self, token: Token<'a, T>) 460 | { 461 | self.unconsumed.push_front(token) 462 | } 463 | 464 | /// Construct new token iterator from the given stream. 465 | /// 466 | /// The given global substitutions and substitution groups will be used 467 | /// to substitute/duplicate nested invocations before they are expanded. 468 | pub(crate) fn new( 469 | stream: TokenStream, 470 | global_subs: &'a SubstitutionGroup, 471 | sub_groups: T, 472 | ) -> Self 473 | { 474 | Self { 475 | raw_tokens: stream.into_iter(), 476 | unconsumed: VecDeque::new(), 477 | last_span: Span::call_site(), 478 | global_subs, 479 | sub_groups, 480 | } 481 | } 482 | 483 | /// Construct new token iterator from the given stream. 484 | /// 485 | /// Substitution/duplication of nested invocations is taken from 'like' 486 | pub fn new_like(stream: TokenStream, like: &Self) -> Self 487 | { 488 | Self::new(stream, like.global_subs, like.sub_groups.clone()) 489 | } 490 | } 491 | impl<'a, T: SubGroupIter<'a> + Debug> Debug for TokenIter<'a, T> 492 | { 493 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result 494 | { 495 | f.write_str("TokenIter{")?; 496 | self.raw_tokens.clone().collect::>().fmt(f)?; 497 | f.write_str(", ")?; 498 | self.unconsumed.fmt(f)?; 499 | f.write_str(", ")?; 500 | self.global_subs.fmt(f)?; 501 | f.write_str(", ")?; 502 | self.sub_groups.fmt(f)?; 503 | f.write_str(",...}") 504 | } 505 | } 506 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | duplicate_impl, 3 | error::Error, 4 | pretty_errors::{ 5 | GLOBAL_SUB_SEMICOLON, NO_GROUPS, NO_GROUPS_HINT, NO_INVOCATION, SHORT_SYNTAX_NO_GROUPS, 6 | VERBOSE_SYNTAX_SUBSTITUTION_IDENTIFIERS, VERBOSE_SYNTAX_SUBSTITUTION_IDENTIFIERS_ARGS, 7 | }, 8 | substitute::Substitution, 9 | substitute_impl, 10 | token_iter::{get_ident, is_ident, is_semicolon, SubGroupIter, Token, TokenIter}, 11 | DuplicationDefinition, Result, SubstitutionGroup, 12 | }; 13 | use proc_macro::{Delimiter, Ident, Span, TokenStream, TokenTree}; 14 | use std::collections::HashSet; 15 | 16 | /// Parses all global substitutions, returning them. 17 | /// 18 | /// If there are other tokens than global substitutions, returns an error. 19 | pub(crate) fn parse_global_substitutions_only(attr: TokenStream) -> Result 20 | { 21 | let empty_global = SubstitutionGroup::new(); 22 | let mut iter = TokenIter::new(attr, &empty_global, std::iter::empty()); 23 | let global_substitutions = validate_global_substitutions(&mut iter)?; 24 | 25 | if let Ok(None) = iter.peek() 26 | { 27 | // Accept global substitutions on their own 28 | if global_substitutions.substitutions.is_empty() 29 | { 30 | // There are no global substitutions, return error requiring it 31 | Err(iter 32 | .extract_identifier(Some("a substitution identifier")) 33 | .unwrap_err()) 34 | } 35 | else 36 | { 37 | Ok(global_substitutions) 38 | } 39 | } 40 | else 41 | { 42 | // There are more tokens, just try to get another substitution and return its 43 | // error 44 | #[cfg_attr(not(feature = "pretty_errors"), allow(unused_mut))] 45 | let mut err = extract_inline_substitution(&mut iter).unwrap_err(); 46 | 47 | #[cfg(feature = "pretty_errors")] 48 | { 49 | if validate_short_get_identifiers(&mut iter.clone()).is_ok() 50 | || validate_verbose_invocation(&mut iter) 51 | .map(|res| res.is_some()) 52 | .unwrap_or(false) 53 | { 54 | err = err.hint( 55 | "Only global substitutions are allowed. Try 'duplicate' or 'duplicate_item'.", 56 | ); 57 | } 58 | } 59 | Err(err) 60 | } 61 | } 62 | 63 | /// Parses the invocation of duplicate, returning all the substitutions that 64 | /// should be made to code. 65 | /// 66 | /// If parsing succeeds returns first a substitution group that indicates global 67 | /// substitutions that should be applied to all duplicates but don't on their 68 | /// own indicate a duplicate. Then comes a list of substitution groups, each of 69 | /// which indicates on duplicate. 70 | pub(crate) fn parse_duplicate_invocation(attr: TokenStream) -> Result 71 | { 72 | let empty_global = SubstitutionGroup::new(); 73 | let mut iter = TokenIter::new(attr, &empty_global, std::iter::empty()); 74 | let global_substitutions = validate_global_substitutions(&mut iter)?; 75 | 76 | if let (Ok(None), false) = (iter.peek(), global_substitutions.substitutions.is_empty()) 77 | { 78 | // Do not accept no duplicates 79 | Err(Error::new(NO_GROUPS).hint(NO_GROUPS_HINT)) 80 | } 81 | else if let Some(dups) = validate_verbose_invocation(&mut iter)? 82 | { 83 | Ok(DuplicationDefinition { 84 | global_substitutions, 85 | duplications: dups, 86 | }) 87 | } 88 | else 89 | { 90 | // Otherwise, try short syntax 91 | let substitutions = validate_short_attr(iter)?; 92 | let mut reorder = Vec::new(); 93 | 94 | for _ in 0..substitutions[0].2.len() 95 | { 96 | reorder.push(SubstitutionGroup::new()); 97 | } 98 | 99 | for (ident, args, subs) in substitutions 100 | { 101 | for (idx, sub) in subs.into_iter().enumerate() 102 | { 103 | let substitution = Substitution::new( 104 | &args, 105 | TokenIter::new(sub, &SubstitutionGroup::new(), std::iter::empty()), 106 | ); 107 | if let Ok(substitution) = substitution 108 | { 109 | reorder[idx].add_substitution( 110 | Ident::new(&ident.clone(), Span::call_site()), 111 | substitution, 112 | )?; 113 | } 114 | else 115 | { 116 | return Err(Error::new( 117 | "Duplicate internal error: Failed at creating substitution", 118 | )); 119 | } 120 | } 121 | } 122 | 123 | Ok(DuplicationDefinition { 124 | global_substitutions, 125 | duplications: reorder, 126 | }) 127 | } 128 | } 129 | 130 | /// Validates global substitutions and returns a substitution group with them. 131 | /// 132 | /// When it fails to validate a global substitution, it might return the next 133 | /// identifier that the iterator produced optionally followed by the next group 134 | /// too. The two must therefore be assumed to precede any tokentrees returned by 135 | /// the iterator after calling this function. 136 | /// This may happen if the global substitutions are followed by short-syntax, 137 | /// which starts the same way as a global substitution. 138 | fn validate_global_substitutions<'a, T: SubGroupIter<'a>>( 139 | iter: &mut TokenIter<'a, T>, 140 | ) -> Result 141 | { 142 | let mut sub_group = SubstitutionGroup::new(); 143 | while let Ok((ident, sub)) = extract_inline_substitution(iter) 144 | { 145 | sub_group.add_substitution(ident, sub)?; 146 | 147 | if iter.has_next()? 148 | { 149 | iter.expect_semicolon() 150 | .map_err(|err| err.hint(GLOBAL_SUB_SEMICOLON))?; 151 | } 152 | } 153 | Ok(sub_group) 154 | } 155 | 156 | /// Validates that a duplicate invocation uses the verbose syntax, and returns 157 | /// all the substitutions that should be made. 158 | /// 159 | /// Returns 'Some' if the tokens given definitely represent the use of verbose 160 | /// syntax, even though it might still contain errors. 161 | /// Returns 'None' if an error occurred before verbose syntax was recognized 162 | fn validate_verbose_invocation<'a, T: SubGroupIter<'a>>( 163 | iter: &mut TokenIter<'a, T>, 164 | ) -> Result>> 165 | { 166 | if let Ok(Some(Token::Group(Delimiter::Bracket, _, _))) = iter.peek() 167 | { 168 | let mut sub_groups = Vec::new(); 169 | 170 | let mut substitution_ids = None; 171 | while iter.has_next()? 172 | { 173 | let (body, span) = iter.next_group(Some(Delimiter::Bracket)).map_err(|err| { 174 | err.hint( 175 | "When using verbose syntax, a substitutions must be enclosed in a \ 176 | group.\nExample:\n..\n[\n\tidentifier1 [ substitution1 ]\n\tidentifier2 [ \ 177 | substitution2 ]\n]", 178 | ) 179 | })?; 180 | sub_groups.push(extract_verbose_substitutions( 181 | body, 182 | span, 183 | &substitution_ids, 184 | )?); 185 | if None == substitution_ids 186 | { 187 | substitution_ids = Some( 188 | sub_groups[0] 189 | .identifiers_with_args() 190 | .map(|(ident, count)| (ident.clone(), count)) 191 | .collect(), 192 | ) 193 | } 194 | } 195 | Ok(Some(sub_groups)) 196 | } 197 | else 198 | { 199 | Ok(None) 200 | } 201 | } 202 | 203 | /// Extracts a substitution identifier followed by 204 | /// an optional parameter list, followed by a substitution. 205 | fn extract_inline_substitution<'a, T: SubGroupIter<'a>>( 206 | stream: &mut TokenIter<'a, T>, 207 | ) -> Result<(Ident, Substitution)> 208 | { 209 | let ident = stream.extract_identifier(Some("a substitution identifier"))?; 210 | let param_group = stream.next_group(Some(Delimiter::Parenthesis)); 211 | let substitution = stream.next_group(Some(Delimiter::Bracket)); 212 | 213 | if let Ok((params, span)) = param_group 214 | { 215 | // Found parameters, now get substitution 216 | substitution 217 | .and_then(|(sub, _)| { 218 | extract_argument_list(params.clone()) 219 | .map(|args| Substitution::new(&args, sub).unwrap()) 220 | .or_else(|err| Err(err)) 221 | }) 222 | .or_else(|err| { 223 | stream.push_front(Token::Group(Delimiter::Parenthesis, params, span)); 224 | Err(err) 225 | }) 226 | } 227 | else 228 | { 229 | // No parameters, get substitution 230 | substitution 231 | .map(|(sub, _)| Substitution::new_simple(sub.process_all())) 232 | .map_err(|old_err| Error::new("Expected '(' or '['.").span(old_err.get_span())) 233 | } 234 | .or_else(|err| { 235 | stream.push_front(Token::Simple(TokenTree::Ident(ident.clone()))); 236 | Err(err) 237 | }) 238 | .map(|result| (ident, result)) 239 | } 240 | 241 | /// Extracts a substitution group in the verbose syntax. 242 | fn extract_verbose_substitutions<'a, T: SubGroupIter<'a>>( 243 | mut iter: TokenIter<'a, T>, 244 | iter_span: Span, 245 | existing: &Option>, 246 | ) -> Result 247 | { 248 | if !iter.has_next()? 249 | { 250 | return Err(Error::new(NO_GROUPS).span(iter_span)); 251 | } 252 | 253 | // Map idents to string reference so we can use HashSet::difference 254 | let expected_idents: HashSet<_> = existing.as_ref().map_or(HashSet::new(), |idents| { 255 | idents 256 | .iter() 257 | .map(|(ident, count)| (ident, count.clone())) 258 | .collect() 259 | }); 260 | 261 | let mut substitutions = SubstitutionGroup::new(); 262 | let mut stream = iter; 263 | 264 | while stream.has_next()? 265 | { 266 | #[allow(unused_mut)] 267 | let mut hint: Option<&str> = None; 268 | 269 | #[cfg(feature = "pretty_errors")] 270 | { 271 | if stream.has_next_semicolon()? 272 | { 273 | hint = Some(crate::pretty_errors::VERBOSE_SEMICOLON); 274 | } 275 | } 276 | 277 | let (ident, substitution) = extract_inline_substitution(&mut stream) 278 | .map_err(|err| hint.into_iter().fold(err, |err, hint| err.hint(hint)))?; 279 | if !expected_idents.is_empty() 280 | && !expected_idents.contains(&(&ident.to_string(), substitution.argument_count())) 281 | { 282 | let (msg, _hint) = if expected_idents 283 | .iter() 284 | .find(|(i, _)| **i == ident.to_string()) 285 | .is_some() 286 | { 287 | ( 288 | "Wrong argument count for substitution identifier.", 289 | VERBOSE_SYNTAX_SUBSTITUTION_IDENTIFIERS_ARGS, 290 | ) 291 | } 292 | else 293 | { 294 | ( 295 | "Unexpected substitution identifier.", 296 | VERBOSE_SYNTAX_SUBSTITUTION_IDENTIFIERS, 297 | ) 298 | }; 299 | return Err(Error::new(msg).span(ident.span()).hint(_hint)); 300 | } 301 | substitutions.add_substitution(ident, substitution)?; 302 | } 303 | 304 | // Check no substitution idents are missing 305 | let found_idents: HashSet<_> = substitutions.identifiers_with_args().collect(); 306 | let missing: Vec<_> = expected_idents.difference(&found_idents).collect(); 307 | 308 | if missing.len() > 0 309 | { 310 | let mut hint = String::new(); 311 | #[cfg(feature = "pretty_errors")] 312 | { 313 | hint += "Missing"; 314 | 315 | hint += " substitution for:"; 316 | for ident in missing 317 | { 318 | hint += " '"; 319 | hint += &ident.0.to_string(); 320 | hint += "'"; 321 | } 322 | hint += "\n"; 323 | } 324 | hint += VERBOSE_SYNTAX_SUBSTITUTION_IDENTIFIERS; 325 | 326 | return Err(Error::new("Incomplete substitution group.") 327 | .span(iter_span) 328 | .hint(hint)); 329 | } 330 | 331 | Ok(substitutions) 332 | } 333 | 334 | /// Validates a duplicate invocation using the short syntax and returns the 335 | /// substitution that should be made. 336 | fn validate_short_attr<'a, T: SubGroupIter<'a>>( 337 | mut iter: TokenIter<'a, T>, 338 | ) -> Result, Vec)>> 339 | { 340 | let idents = validate_short_get_identifiers(&mut iter)?; 341 | let mut result: Vec<_> = idents 342 | .into_iter() 343 | .map(|(ident, args)| (ident, args, Vec::new())) 344 | .collect(); 345 | validate_short_get_all_substitution_goups(iter, &mut result)?; 346 | 347 | if result[0].2.is_empty() 348 | { 349 | Err(Error::new(NO_GROUPS).hint(SHORT_SYNTAX_NO_GROUPS)) 350 | } 351 | else 352 | { 353 | Ok(result) 354 | } 355 | } 356 | 357 | /// Assuming use of the short syntax, gets the initial list of substitution 358 | /// identifiers. 359 | fn validate_short_get_identifiers<'a, T: SubGroupIter<'a>>( 360 | mut iter: &mut TokenIter<'a, T>, 361 | ) -> Result)>> 362 | { 363 | let mut result = Vec::new(); 364 | while let Some(ident) = iter.extract_simple( 365 | |t| is_ident(t, None) || (is_semicolon(t) && !result.is_empty()), 366 | |t| get_ident(t), 367 | Some( 368 | if result.is_empty() 369 | { 370 | NO_INVOCATION 371 | } 372 | else 373 | { 374 | "substitution_identifier or ';'" 375 | }, 376 | ), 377 | )? 378 | { 379 | result.push(( 380 | ident.to_string(), 381 | validate_short_get_identifier_arguments(&mut iter)?, 382 | )); 383 | } 384 | Ok(result) 385 | } 386 | 387 | /// Assuming use of the short syntax, gets the list of identifier arguments. 388 | fn validate_short_get_identifier_arguments<'a, T: SubGroupIter<'a>>( 389 | iter: &mut TokenIter<'a, T>, 390 | ) -> Result> 391 | { 392 | if let Ok((group, _)) = iter.next_group(Some(Delimiter::Parenthesis)) 393 | { 394 | let result = extract_argument_list(group)?; 395 | return Ok(result); 396 | } 397 | Ok(Vec::new()) 398 | } 399 | 400 | /// Gets all substitution groups in the short syntax and inserts 401 | /// them into the given vec. 402 | fn validate_short_get_all_substitution_goups<'a, T: SubGroupIter<'a>>( 403 | mut iter: TokenIter<'a, T>, 404 | result: &mut Vec<(String, Vec, Vec)>, 405 | ) -> Result<()> 406 | { 407 | while iter.has_next()? 408 | { 409 | for (_, _, streams) in result.iter_mut() 410 | { 411 | #[allow(unused_mut)] 412 | let mut error = crate::pretty_errors::SHORT_SYNTAX_MISSING_SUB_BRACKET; 413 | #[cfg(feature = "pretty_errors")] 414 | { 415 | if iter.has_next_semicolon()? 416 | { 417 | error = crate::pretty_errors::SHORT_SYNTAX_SUBSTITUTION_COUNT; 418 | } 419 | } 420 | 421 | let (group, _) = iter 422 | .next_group(Some(Delimiter::Bracket)) 423 | .map_err(|err| err.hint(error))?; 424 | streams.push(group.to_token_stream()); 425 | } 426 | 427 | if iter.has_next()? 428 | { 429 | #[cfg(feature = "pretty_errors")] 430 | { 431 | if let Ok((_, span)) = iter.next_group(Some(Delimiter::Bracket)) 432 | { 433 | return Err(Error::new("Unexpected delimiter.") 434 | .span(span) 435 | .hint(crate::pretty_errors::SHORT_SYNTAX_SUBSTITUTION_COUNT)); 436 | } 437 | } 438 | iter.expect_semicolon()?; 439 | } 440 | } 441 | Ok(()) 442 | } 443 | 444 | /// Invokes a nested invocation of duplicate, assuming the 445 | /// next group is the body of call to `duplicate` (`is_duplicate`) or 446 | /// `substitute`(`!is_duplicate`) 447 | pub(crate) fn invoke_nested<'a, T: SubGroupIter<'a>>( 448 | iter: &mut TokenIter<'a, T>, 449 | is_duplicate: bool, 450 | ) -> Result 451 | { 452 | let (mut nested_body_iter, _) = iter.next_group(None)?; 453 | 454 | let (nested_invocation, _) = nested_body_iter.next_group(Some(Delimiter::Bracket))?; 455 | (if is_duplicate 456 | { 457 | duplicate_impl 458 | } 459 | else 460 | { 461 | substitute_impl 462 | })( 463 | nested_invocation.to_token_stream(), 464 | nested_body_iter.to_token_stream(), 465 | ) 466 | } 467 | 468 | /// Extracts a list of arguments from. 469 | /// The list is expected to be of comma-separated identifiers. 470 | pub(crate) fn extract_argument_list<'a, T: SubGroupIter<'a>>( 471 | mut args: TokenIter<'a, T>, 472 | ) -> Result> 473 | { 474 | let mut result = Vec::new(); 475 | while args.has_next()? 476 | { 477 | let ident = 478 | args.extract_identifier(Some("substitution identifier argument as identifier"))?; 479 | result.push(ident.to_string()); 480 | 481 | if args.has_next()? 482 | { 483 | args.expect_comma()?; 484 | } 485 | } 486 | Ok(result) 487 | } 488 | -------------------------------------------------------------------------------- /tests/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::OsString, 3 | fs::File, 4 | io::{BufRead, BufReader, Write}, 5 | path::{Path, PathBuf}, 6 | }; 7 | 8 | /// Whether the `pretty_errors` feature is enabled. 9 | pub const FEATURE_PRETTY_ERRORS: bool = cfg!(feature = "pretty_errors"); 10 | /// Whether the `module_disambiguation` feature is enabled. 11 | pub const FEATURE_MODULE_DISAMBIGUATION: bool = cfg!(feature = "module_disambiguation"); 12 | /// The number of enabled features. 13 | pub const NR_FEATURES: usize = 14 | 0 + FEATURE_PRETTY_ERRORS as usize + FEATURE_MODULE_DISAMBIGUATION as usize; 15 | /// A list of the enabled features. 16 | const FEATURES: [&'static str; NR_FEATURES] = get_features(); 17 | 18 | /// Returns a list of enabled features. 19 | const fn get_features() -> [&'static str; NR_FEATURES] 20 | { 21 | #[allow(unused_mut)] 22 | let mut features: [&'static str; NR_FEATURES] = [""; NR_FEATURES]; 23 | #[cfg(feature = "pretty_errors")] 24 | { 25 | features[0] = "pretty_errors"; 26 | } 27 | #[cfg(feature = "module_disambiguation")] 28 | { 29 | features[FEATURE_PRETTY_ERRORS as usize] = "module_disambiguation"; 30 | } 31 | features 32 | } 33 | 34 | /// Manages the setting up and running of expansion tests using macrotest 35 | /// 36 | /// Expansion test live in a home directory. This directory has a single 37 | /// testing sub-directory that is used during the test. Temporary testing 38 | /// files are put in the testing directory before each test but not removed 39 | /// after. (They may be deleted before each test, though) 40 | /// 41 | /// The tester is configured to generate files in the testing directory from 42 | /// files in source directories (sub-directories of the home). 43 | /// Various rules can be configured, e.g. a simple copy of files, or duplicating 44 | /// the source files a number of times in the testing directory with various 45 | /// names. 46 | pub struct ExpansionTester<'a> 47 | { 48 | /// The home directory for the tests 49 | dir: &'a str, 50 | /// The subdirectory (of the home) where test files may be put 51 | testing_dir: &'a str, 52 | /// Source sub-directory, and how it's files should be treated before 53 | /// testing 54 | source_dirs: Vec<(&'a str, Vec)>>)>, 55 | 56 | /// Whether this tester is testing errors, i.e. that expansions should fail 57 | error_tests: bool, 58 | } 59 | 60 | impl<'a> ExpansionTester<'a> 61 | { 62 | /// Construct a new tester with a home directory and a testing subdirectory. 63 | pub fn new(home_dir: &'a str, testing_dir: &'a str) -> Self 64 | { 65 | Self { 66 | dir: home_dir, 67 | testing_dir, 68 | source_dirs: Vec::new(), 69 | error_tests: false, 70 | } 71 | } 72 | 73 | /// Construct a new tester with a home directory and a testing subdirectory. 74 | /// 75 | /// This tester will be testing errors. 76 | pub fn new_errors(home_dir: &'a str, testing_dir: &'a str) -> Self 77 | { 78 | Self { 79 | dir: home_dir, 80 | testing_dir, 81 | source_dirs: Vec::new(), 82 | error_tests: true, 83 | } 84 | } 85 | 86 | /// Add a source directory under the home directory, 87 | /// with a list of actions that produce files in the testing directory 88 | /// based on each file in the source directory. 89 | pub fn add_source_dir( 90 | &mut self, 91 | dir: &'a str, 92 | actions: Vec)>>, 93 | ) 94 | { 95 | self.source_dirs.push((dir, actions)); 96 | } 97 | 98 | /// Executes the tests including first setting up the testing directory. 99 | pub fn execute_tests(&self) 100 | { 101 | // Remove old test files 102 | let testing_dir = self.dir.to_owned() + "/" + self.testing_dir; 103 | let _ = std::fs::remove_dir_all(&testing_dir); 104 | 105 | // Recreate testing dir 106 | std::fs::create_dir_all(&testing_dir).unwrap(); 107 | 108 | // For each source dir, execute action of each file 109 | for (source_dir, actions) in self.source_dirs.iter() 110 | { 111 | let source_dir_path = self.dir.to_owned() + "/" + source_dir; 112 | if let Ok(files) = std::fs::read_dir(&source_dir_path) 113 | { 114 | for file in files 115 | { 116 | if let Ok(file) = file 117 | { 118 | for action in actions.iter() 119 | { 120 | action(&file.path(), &testing_dir); 121 | } 122 | } 123 | else 124 | { 125 | panic!("Error accessing source file: {:?}", file) 126 | } 127 | } 128 | } 129 | } 130 | 131 | // Prepare feature list for expansion testing 132 | let mut args = Vec::new(); 133 | let mut features = String::new(); 134 | args.push("--no-default-features"); 135 | if NR_FEATURES > 0 136 | { 137 | args.push("--features"); 138 | for f in FEATURES.iter() 139 | { 140 | features.push_str(f); 141 | features.push(','); 142 | } 143 | args.push(features.as_str()); 144 | } 145 | 146 | if self.error_tests 147 | { 148 | duplicate_macrotest::expand_without_refresh_args_fail( 149 | testing_dir + "/*.rs", 150 | args.as_slice(), 151 | ); 152 | } 153 | else 154 | { 155 | duplicate_macrotest::expand_without_refresh_args( 156 | testing_dir + "/*.rs", 157 | args.as_slice(), 158 | ); 159 | } 160 | } 161 | 162 | /// Generates an action that copies the file given to the testing 163 | /// directory with the given prefix added to its name. 164 | pub fn copy_with_prefix(prefix: &str) -> Box)> 165 | { 166 | Self::copy_with_prefix_postfix(prefix, "") 167 | } 168 | 169 | /// Generates an action that copies the file given to the testing 170 | /// directory with the given prefix added to its name. 171 | pub fn copy_with_prefix_postfix( 172 | prefix: &str, 173 | postfix: &str, 174 | ) -> Box)> 175 | { 176 | let prefix = OsString::from(prefix); 177 | let postfix = OsString::from(postfix); 178 | Box::new(move |file, destination| { 179 | let mut destination_file = destination.as_ref().to_path_buf(); 180 | let mut file_name = prefix.clone(); 181 | file_name.push(file.file_name().unwrap()); 182 | file_name.push(postfix.clone()); 183 | destination_file.push(file_name); 184 | std::fs::copy(file, &destination_file).unwrap(); 185 | }) 186 | } 187 | 188 | /// Generates an action that simply copies the file given to the testing 189 | /// directory. 190 | pub fn copy() -> Box)> 191 | { 192 | Self::copy_with_prefix("") 193 | } 194 | 195 | /// Generates an action that creates two versions of the given file in the 196 | /// testing directory. The source file must use the attribute 197 | /// macro, where: 198 | /// - The invocation must starts with `#[duplicate_item(` or 199 | /// `#[substitute_item(` on a the first line 200 | /// (with nothing else). Notice that you must not import the attribute but 201 | /// use its full path. 202 | /// - Then the body of the invocation. Both syntaxes are allowed. 203 | /// - Then the `)]` on its own line, followed immediately by 204 | /// `//duplicate_end`. 205 | /// I.e. `)]//duplicate_end` 206 | /// - Then the item to be duplicated, followed on the next line by 207 | /// `//item_end` on 208 | /// its own. 209 | /// 210 | /// This action will then generate 2 versions of this file. The first is 211 | /// almost identical the original, but the second will change the invocation 212 | /// to instead use `duplicate` or `substitute`. It uses the exact rules 213 | /// specified above to correctly change the code, so any small deviation 214 | /// from the above rules might result in an error. The name of the first 215 | /// version is the same as the original and the second version is prefixed 216 | /// with 'inline_' 217 | /// 218 | /// ### Example 219 | /// Original file (`test.rs`): 220 | /// ``` 221 | /// #[duplicate_item( 222 | /// name; 223 | /// [SomeName]; 224 | /// )]//duplicate_end 225 | /// pub struct name(); 226 | /// //item_end 227 | /// ``` 228 | /// First version (`test.expanded.rs`): 229 | /// ``` 230 | /// #[duplicate_item( 231 | /// name; 232 | /// [SomeName]; 233 | /// )] 234 | /// pub struct name(); 235 | /// ``` 236 | /// Second version (`inline_test.expanded.rs`): 237 | /// ``` 238 | /// duplicate{ 239 | /// [ 240 | /// name; 241 | /// [SomeName]; 242 | /// ] 243 | /// pub struct name(); 244 | /// } 245 | /// ``` 246 | pub fn duplicate_for_inline() -> Box)> 247 | { 248 | Self::duplicate_for_inline_with_prefix("") 249 | } 250 | 251 | /// like 'duplicate_for_inline' except adds the given prefix to the name of 252 | /// the original files. 253 | pub fn duplicate_for_inline_with_prefix( 254 | prefix: &str, 255 | ) -> Box)> 256 | { 257 | Box::new(move |file, destination| { 258 | let mut inline_file_name = OsString::from("inline_"); 259 | inline_file_name.push(prefix); 260 | inline_file_name.push(file.file_name().unwrap()); 261 | let mut new_file_name = OsString::from(prefix); 262 | new_file_name.push(file.file_name().unwrap()); 263 | 264 | let mut dest_file_path = destination.as_ref().to_path_buf(); 265 | let mut dest_inline_file_path = destination.as_ref().to_path_buf(); 266 | 267 | dest_file_path.push(new_file_name); 268 | dest_inline_file_path.push(inline_file_name); 269 | 270 | let mut dest_file = File::create(dest_file_path).unwrap(); 271 | let mut dest_inline_file = File::create(dest_inline_file_path).unwrap(); 272 | 273 | for line in BufReader::new(File::open(file).unwrap()).lines() 274 | { 275 | let line = line.unwrap(); 276 | let line = line.trim(); 277 | 278 | match line 279 | { 280 | "#[duplicate_item(" => 281 | { 282 | dest_file.write_all("#[duplicate_item(".as_bytes()).unwrap(); 283 | dest_inline_file 284 | .write_all("duplicate!{[".as_bytes()) 285 | .unwrap(); 286 | }, 287 | "#[substitute_item(" => 288 | { 289 | dest_file 290 | .write_all("#[substitute_item(".as_bytes()) 291 | .unwrap(); 292 | dest_inline_file 293 | .write_all("substitute!{[".as_bytes()) 294 | .unwrap(); 295 | }, 296 | ")]//duplicate_end" => 297 | { 298 | dest_file.write_all(")]".as_bytes()).unwrap(); 299 | dest_inline_file.write_all("]".as_bytes()).unwrap(); 300 | }, 301 | "//item_end" => 302 | { 303 | dest_inline_file.write_all("}".as_bytes()).unwrap(); 304 | }, 305 | _ => 306 | { 307 | dest_file.write_all(line.as_bytes()).unwrap(); 308 | dest_inline_file.write_all(line.as_bytes()).unwrap(); 309 | }, 310 | } 311 | dest_file.write_all("\n".as_bytes()).unwrap(); 312 | dest_inline_file.write_all("\n".as_bytes()).unwrap(); 313 | } 314 | }) 315 | } 316 | 317 | /// Sets up and runs tests in a specific directory using our standard test 318 | /// setup. 319 | pub fn run_default_test_setup(home_dir: &str, test_subdir: &str) 320 | { 321 | Self::run_default_test_setup_errors(home_dir, test_subdir, false) 322 | } 323 | 324 | /// Sets up and runs tests in a specific directory using our standard test 325 | /// setup. 326 | pub fn run_default_test_setup_errors(home_dir: &str, test_subdir: &str, test_errors: bool) 327 | { 328 | let mut test = if test_errors 329 | { 330 | ExpansionTester::new_errors(home_dir, test_subdir) 331 | } 332 | else 333 | { 334 | ExpansionTester::new(home_dir, test_subdir) 335 | }; 336 | test.add_source_dir("from", vec![ExpansionTester::duplicate_for_inline()]); 337 | test.add_source_dir( 338 | "expected", 339 | vec![ 340 | ExpansionTester::copy(), 341 | ExpansionTester::copy_with_prefix("inline_"), 342 | ], 343 | ); 344 | test.add_source_dir( 345 | "expected_both", 346 | vec![ 347 | ExpansionTester::copy_with_prefix("inline_short_"), 348 | ExpansionTester::copy_with_prefix("inline_verbose_"), 349 | ExpansionTester::copy_with_prefix("short_"), 350 | ExpansionTester::copy_with_prefix("verbose_"), 351 | ], 352 | ); 353 | test.execute_tests(); 354 | } 355 | } 356 | 357 | /// Runs all basic error message tests in the given path. 358 | /// 359 | /// A folder named 'source' should contain each source code to test for 360 | /// expansion errors. A folder named 'basic' should contain the expected basic 361 | /// error message. The two folders must each have a file with the same name for 362 | /// each test (except the source file must have the '.rs' extension). 363 | pub fn run_basic_expansion_error_tests(path: &str) 364 | { 365 | let mut tester = ExpansionTester::new_errors(path, "testing_basic"); 366 | 367 | // First setup all basic tests 368 | tester.add_source_dir( 369 | "basic", 370 | vec![ 371 | ExpansionTester::copy_with_prefix_postfix("basic_", ".expanded.rs"), 372 | ExpansionTester::copy_with_prefix_postfix("inline_basic_", ".expanded.rs"), 373 | ], 374 | ); 375 | tester.add_source_dir( 376 | "source", 377 | vec![ExpansionTester::duplicate_for_inline_with_prefix("basic_")], 378 | ); 379 | tester.execute_tests(); 380 | } 381 | 382 | /// Copies the source file with the same name as the current file 383 | /// into the testing directory in both attribute and inline version (see 384 | /// duplicate_for_inline) 385 | #[cfg_attr(not(feature = "pretty_errors"), allow(dead_code))] 386 | pub fn get_source(prefix: &str) -> Box)> 387 | { 388 | Box::new(move |file, destination| { 389 | let mut source_file_name = std::ffi::OsString::from(file.file_name().unwrap()); 390 | source_file_name.push(".rs"); 391 | 392 | let mut source_file_path = PathBuf::from(file.parent().unwrap().parent().unwrap()); 393 | source_file_path.push("source"); 394 | source_file_path.push(source_file_name); 395 | 396 | assert!( 397 | source_file_path.exists(), 398 | "Missing file: {:?}", 399 | source_file_path.as_os_str() 400 | ); 401 | 402 | ExpansionTester::duplicate_for_inline_with_prefix(prefix)(&source_file_path, destination); 403 | }) 404 | } 405 | 406 | /// Runs all error message highlights tests in the given path. 407 | /// 408 | /// A folder named 'source' should contain each source code to test for 409 | /// expansion errors. A folder named 'highlight' should contain the expected 410 | /// basic error message. The two folders must each have a file with the same 411 | /// name for each test (except the source file must have the '.rs' extension). 412 | /// 413 | /// If the 'pretty_errors' feature is not enabled, does nothing. 414 | #[cfg_attr(not(feature = "pretty_errors"), allow(unused_variables))] 415 | pub fn run_error_highlight_tests(path: &str) 416 | { 417 | #[cfg(feature = "pretty_errors")] 418 | { 419 | let mut tester = ExpansionTester::new_errors(path, "testing_highlight"); 420 | 421 | tester.add_source_dir( 422 | "highlight", 423 | vec![ 424 | ExpansionTester::copy_with_prefix_postfix("highlight_", ".expanded.rs"), 425 | ExpansionTester::copy_with_prefix_postfix("inline_highlight_", ".expanded.rs"), 426 | get_source("highlight_"), 427 | ], 428 | ); 429 | 430 | tester.execute_tests(); 431 | } 432 | } 433 | 434 | /// Runs all error message hint tests in the given path. 435 | /// 436 | /// A folder named 'source' should contain each source code to test for 437 | /// expansion errors. A folder named 'hint' should contain the expected basic 438 | /// error message. The two folders must each have a file with the same name for 439 | /// each test (except the source file must have the '.rs' extension). 440 | /// 441 | /// If the 'pretty_errors' feature is not enabled, does nothing. 442 | #[cfg_attr(not(feature = "pretty_errors"), allow(unused_variables))] 443 | pub fn run_error_hint_tests(path: &str) 444 | { 445 | #[cfg(feature = "pretty_errors")] 446 | { 447 | let mut tester = ExpansionTester::new_errors(path, "testing_hint"); 448 | 449 | tester.add_source_dir( 450 | "hint", 451 | vec![ 452 | ExpansionTester::copy_with_prefix_postfix("hint_", ".expanded.rs"), 453 | ExpansionTester::copy_with_prefix_postfix("inline_hint_", ".expanded.rs"), 454 | get_source("hint_"), 455 | ], 456 | ); 457 | 458 | tester.execute_tests(); 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides macros for easy code duplication with substitution: 2 | //! 3 | //! - [`duplicate_item`]: Attribute macro. 4 | //! - [`duplicate`]: Function-like procedural macro. 5 | //! 6 | //! The only major difference between the two is where you can use them. 7 | //! Therefore, the following section presents how to use 8 | //! [`duplicate_item`] only. Refer to [`duplicate`]'s documentation for how it 9 | //! defers from what is specified below. 10 | //! 11 | //! [`duplicate_item`]: attr.duplicate_item.html 12 | //! [`duplicate`]: macro.duplicate.html 13 | //! # Usage 14 | //! 15 | //! Say you have a trait with a method `is_max` that should return `true` if the 16 | //! value of the object is the maximum allowed and `false` otherwise: 17 | //! ``` 18 | //! trait IsMax { 19 | //! fn is_max(&self) -> bool; 20 | //! } 21 | //! ``` 22 | //! You would like to implement this trait for the three integer types `u8`, 23 | //! `u16`, and `u32`: 24 | //! 25 | //! ``` 26 | //! # trait IsMax {fn is_max(&self) -> bool;} 27 | //! impl IsMax for u8 { 28 | //! fn is_max(&self) -> bool { 29 | //! *self == 255 30 | //! } 31 | //! } 32 | //! impl IsMax for u16 { 33 | //! fn is_max(&self) -> bool { 34 | //! *self == 65_535 35 | //! } 36 | //! } 37 | //! impl IsMax for u32 { 38 | //! fn is_max(&self) -> bool { 39 | //! *self == 4_294_967_295 40 | //! } 41 | //! } 42 | //! ``` 43 | //! This is a lot of repetition. Only the type and the maximum value are 44 | //! actually different between the three implementations. This might not be much 45 | //! in our case, but imagine doing this for all the integer types (10, as of the 46 | //! last count.) We can use the `duplicate_item` attribute to avoid repeating 47 | //! ourselves: 48 | //! 49 | //! ``` 50 | //! # trait IsMax {fn is_max(&self) -> bool;} 51 | //! use duplicate::duplicate_item; 52 | //! #[duplicate_item( 53 | //! int_type max_value; 54 | //! [ u8 ] [ 255 ]; 55 | //! [ u16 ] [ 65_535 ]; 56 | //! [ u32 ] [ 4_294_967_295 ]; 57 | //! )] 58 | //! impl IsMax for int_type { 59 | //! fn is_max(&self) -> bool { 60 | //! *self == max_value 61 | //! } 62 | //! } 63 | //! 64 | //! assert!(!42u8.is_max()); 65 | //! assert!(!42u16.is_max()); 66 | //! assert!(!42u32.is_max()); 67 | //! ``` 68 | //! The above code will expand to the three implementations before it. 69 | //! The attribute invocation specifies that the following item should be 70 | //! substituted by three duplicates of itself. Additionally, each occurrence of 71 | //! the identifier `int_type` in the first duplicate should be replaced by `u8`, 72 | //! in the second duplicate by `u16`, and in the last by `u32`. Likewise, each 73 | //! occurrence of `max_value` should be replaced by `255`, `65_535`, and 74 | //! `4_294_967_295` in the first, second, and third duplicates respectively. 75 | //! 76 | //! `int_type` and `max_value` are called _substitution identifiers_, while `[ 77 | //! u8 ]`, `[ u16 ]`, and `[ u32 ]` are each _substitutions_ for `int_type` and 78 | //! `[255]`, `[65_535]`, and `[4_294_967_295]` are substitutions for 79 | //! `max_value`. Each pair of substitutions for the identifiers is called a 80 | //! _substitution group_. Substitution groups must be seperated by `;` and the 81 | //! number of duplicates made is equal to the number of subsitution groups. 82 | //! 83 | //! Substitution identifiers must be valid Rust identifiers. 84 | //! The code inside substitutions can be arbitrary, as long as the expanded code 85 | //! is valid. 86 | //! 87 | //! ## Parameterized Substitution 88 | //! 89 | //! Say we have a struct that wraps a vector and we want to give 90 | //! access to the vector's `get` and `get_mut` methods directly: 91 | //! 92 | //! ``` 93 | //! struct VecWrap(Vec); 94 | //! 95 | //! impl VecWrap { 96 | //! pub fn get(&self, idx: usize) -> Option<&T> { 97 | //! self.0.get(idx) 98 | //! } 99 | //! pub fn get_mut(&mut self, idx: usize) -> Option<&mut T> { 100 | //! self.0.get_mut(idx) 101 | //! } 102 | //! } 103 | //! 104 | //! let mut vec = VecWrap(vec![1,2,3]); 105 | //! assert_eq!(*vec.get(0).unwrap(), 1); 106 | //! *vec.get_mut(1).unwrap() = 5; 107 | //! assert_eq!(*vec.get(1).unwrap(), 5); 108 | //! ``` 109 | //! 110 | //! Even though the implementations of the two versions of `get` are almost 111 | //! identical, we will always need to duplicate the code, because Rust cannot be 112 | //! generic over mutability. _Parameterized substitution_ allows us to pass code 113 | //! snippets to substitution identifiers to customize the substitution for that 114 | //! specific use of the identifier. We can use it to help with the 115 | //! implementation of constant and mutable versions of methods and functions. 116 | //! The following `impl` is identical to the above code: 117 | //! 118 | //! ``` 119 | //! # use duplicate::duplicate_item; 120 | //! # struct VecWrap(Vec); 121 | //! impl VecWrap { 122 | //! #[duplicate_item( 123 | //! method reference(type); 124 | //! [get] [& type]; 125 | //! [get_mut] [&mut type]; 126 | //! )] 127 | //! pub fn method(self: reference([Self]), idx: usize) -> Option { 128 | //! self.0.method(idx) 129 | //! } 130 | //! } 131 | //! # let mut vec = VecWrap(vec![1,2,3]); 132 | //! # assert_eq!(*vec.get(0).unwrap(), 1); 133 | //! # *vec.get_mut(1).unwrap() = 5; 134 | //! # assert_eq!(*vec.get(1).unwrap(), 5); 135 | //! ``` 136 | //! 137 | //! In a `duplicate_item` invocation, if a substitution identifier is followed 138 | //! by parenthises containing a list of parameters, they can be used in the 139 | //! substitution. In this example, the `reference` identifier takes 1 parameter 140 | //! named `type`, which is used in the substitutions to create either a shared 141 | //! reference to the type or a mutable one. When using the `reference` in the 142 | //! method declaration, we give it different types as arguments to construct 143 | //! either shared or mutable references. 144 | //! E.g. `reference([Self])` becomes `&Self` in the first duplicate and `&mut 145 | //! Self` in the second. An argument can be any code snippet inside `[]`. 146 | //! 147 | //! A substitution identifier can take any number of parameters. 148 | //! We can use this if we need to also provide the references with a lifetime: 149 | //! 150 | //! ``` 151 | //! # use duplicate::duplicate_item; 152 | //! # struct VecWrap(Vec); 153 | //! impl VecWrap { 154 | //! #[duplicate_item( 155 | //! method reference(lifetime, type); 156 | //! [get] [& 'lifetime type]; 157 | //! [get_mut] [& 'lifetime mut type]; 158 | //! )] 159 | //! pub fn method<'a>(self: reference([a],[Self]),idx: usize) -> Option { 160 | //! self.0.method(idx) 161 | //! } 162 | //! } 163 | //! # let mut vec = VecWrap(vec![1,2,3]); 164 | //! # assert_eq!(*vec.get(0).unwrap(), 1); 165 | //! # *vec.get_mut(1).unwrap() = 5; 166 | //! # assert_eq!(*vec.get(1).unwrap(), 5); 167 | //! ``` 168 | //! 169 | //! Here we pass the lifetime `'a` to the substitution as the first argument, 170 | //! and the type as the second. Notice how the arguments are separated by a 171 | //! comma. This results in the following code: 172 | //! 173 | //! ``` 174 | //! # struct VecWrap(Vec); 175 | //! impl VecWrap { 176 | //! pub fn get<'a>(self: &'a Self, idx: usize) -> Option<&'a T> { 177 | //! self.0.get(idx) 178 | //! } 179 | //! pub fn get_mut<'a>(self: &'a mut Self, idx: usize) -> Option<&'a mut T> { 180 | //! self.0.get_mut(idx) 181 | //! } 182 | //! } 183 | //! # let mut vec = VecWrap(vec![1,2,3]); 184 | //! # assert_eq!(*vec.get(0).unwrap(), 1); 185 | //! # *vec.get_mut(1).unwrap() = 5; 186 | //! # assert_eq!(*vec.get(1).unwrap(), 5); 187 | //! ``` 188 | //! 189 | //! Notice also the way we pass lifetimes to identifiers: `reference([a], 190 | //! [Self])`. The lifetime is passed without the `'` prefix, which is instead 191 | //! present in the substitution before the lifetime: `[& 'lifetime type]`. 192 | //! This is because the rust syntax disallows lifetimes in brackets on their 193 | //! own. Our solution is therefore a hacking of the system and not a property of 194 | //! `duplicate_item` itself. 195 | //! 196 | //! ## Nested Invocation 197 | //! 198 | //! Imagine we have the following trait with the method `is_negative` that 199 | //! should return `true` if the value of the object is negative and `false` 200 | //! otherwise: 201 | //! ``` 202 | //! trait IsNegative { 203 | //! fn is_negative(&self) -> bool; 204 | //! } 205 | //! ``` 206 | //! We want to implement this for the six integer types `u8`, `u16`, `u32`, 207 | //! `i8`, `i16`, and `i32`. For the first three types, which are all unsigned, 208 | //! the implementation of this trait should trivially return `false` as they 209 | //! can't be negative. However, for the remaining, signed types their 210 | //! implementations is identical (checking whether they are less than `0`), but, 211 | //! of course, different from the first three: 212 | //! ``` 213 | //! # trait IsNegative { fn is_negative(&self) -> bool;} 214 | //! impl IsNegative for u8 { 215 | //! fn is_negative(&self) -> bool { 216 | //! false 217 | //! } 218 | //! } 219 | //! impl IsNegative for u16 { 220 | //! fn is_negative(&self) -> bool { 221 | //! false 222 | //! } 223 | //! } 224 | //! impl IsNegative for u32 { 225 | //! fn is_negative(&self) -> bool { 226 | //! false 227 | //! } 228 | //! } 229 | //! impl IsNegative for i8 { 230 | //! fn is_negative(&self) -> bool { 231 | //! *self < 0 232 | //! } 233 | //! } 234 | //! impl IsNegative for i16 { 235 | //! fn is_negative(&self) -> bool { 236 | //! *self < 0 237 | //! } 238 | //! } 239 | //! impl IsNegative for i32 { 240 | //! fn is_negative(&self) -> bool { 241 | //! *self < 0 242 | //! } 243 | //! } 244 | //! # assert!(!42u8.is_negative()); 245 | //! # assert!(!42u16.is_negative()); 246 | //! # assert!(!42u32.is_negative()); 247 | //! # assert!(!42i8.is_negative()); 248 | //! # assert!(!42i16.is_negative()); 249 | //! # assert!(!42i32.is_negative()); 250 | //! ``` 251 | //! 252 | //! Notice how the code repetition is split over 2 axes: 1) They all implement 253 | //! the same trait 2) the method implementations of the first 3 are identical to 254 | //! each other but different to the next 3, which are also mutually identical. 255 | //! To implement this using only the syntax we have already seen, we could do 256 | //! something like this: 257 | //! 258 | //! ``` 259 | //! # trait IsNegative { fn is_negative(&self) -> bool;} 260 | //! # use duplicate::duplicate_item; 261 | //! #[duplicate_item( 262 | //! int_type implementation; 263 | //! [u8] [false]; 264 | //! [u16] [false]; 265 | //! [u32] [false]; 266 | //! [i8] [*self < 0]; 267 | //! [i16] [*self < 0]; 268 | //! [i32] [*self < 0] 269 | //! )] 270 | //! impl IsNegative for int_type { 271 | //! fn is_negative(&self) -> bool { 272 | //! implementation 273 | //! } 274 | //! } 275 | //! 276 | //! assert!(!42u8.is_negative()); 277 | //! assert!(!42u16.is_negative()); 278 | //! assert!(!42u32.is_negative()); 279 | //! assert!(!42i8.is_negative()); 280 | //! assert!(!42i16.is_negative()); 281 | //! assert!(!42i32.is_negative()); 282 | //! ``` 283 | //! 284 | //! However, ironically, we here had to repeat ourselves in the macro invocation 285 | //! instead of the code: we needed to repeat the implementations `[ false ]` and 286 | //! `[ *self < 0 ]` three times each. We can utilize _nested invocation_ to 287 | //! remove the last bit of repetition: 288 | //! 289 | //! ``` 290 | //! # trait IsNegative { fn is_negative(&self) -> bool;} 291 | //! # use duplicate::duplicate_item; 292 | //! #[duplicate_item( 293 | //! int_type implementation; 294 | //! duplicate!{ 295 | //! [ 296 | //! int_type_nested; [u8]; [u16]; [u32] 297 | //! ] 298 | //! [ int_type_nested ] [ false ]; 299 | //! } 300 | //! duplicate!{ 301 | //! [ 302 | //! int_type_nested; [i8]; [i16]; [i32] 303 | //! ] 304 | //! [ int_type_nested ] [ *self < 0 ]; 305 | //! } 306 | //! )] 307 | //! impl IsNegative for int_type { 308 | //! fn is_negative(&self) -> bool { 309 | //! implementation 310 | //! } 311 | //! } 312 | //! 313 | //! assert!(!42u8.is_negative()); 314 | //! assert!(!42u16.is_negative()); 315 | //! assert!(!42u32.is_negative()); 316 | //! assert!(!42i8.is_negative()); 317 | //! assert!(!42i16.is_negative()); 318 | //! assert!(!42i32.is_negative()); 319 | //! ``` 320 | //! 321 | //! We use `duplicate!{..}` to invoke the macro inside itself. 322 | //! In our example, we have 2 invocations that each produce 3 substitution 323 | //! groups, inserting the correct implementation for their signed or unsigned 324 | //! types. The above nested invocation is equivalent to the previous, non-nested 325 | //! invocation, and actually expands to it as an intermediate step before 326 | //! expanding the outermost invocation. 327 | //! 328 | //! Deeper levels of nested invocation are possible and work as expected. 329 | //! There is no limit on the depth of nesting, however, as might be clear from 330 | //! our example, it can get complicated to read. 331 | //! 332 | //! Lastly, we should note that we can have nested invocations interleaved with 333 | //! normal substitution groups. For example, say we want to implement 334 | //! `IsNegative` for `i8`, but don't want the same for `i16` and `i32`. We could 335 | //! do the following: 336 | //! 337 | //! ``` 338 | //! # trait IsNegative { fn is_negative(&self) -> bool;} 339 | //! # use duplicate::duplicate_item; 340 | //! #[duplicate_item( 341 | //! int_type implementation; 342 | //! duplicate!{ 343 | //! [ // -+ 344 | //! int_type_nested; [u8]; [u16]; [u32] // | Nested invocation producing 3 345 | //! ] // | substitution groups 346 | //! [int_type_nested ] [ false ]; // | 347 | //! } // -+ 348 | //! [ i8 ] [ *self < 0 ] // -- Substitution group 4 349 | //! )] 350 | //! impl IsNegative for int_type { 351 | //! fn is_negative(&self) -> bool { 352 | //! implementation 353 | //! } 354 | //! } 355 | //! 356 | //! # assert!(!42u8.is_negative()); 357 | //! # assert!(!42u16.is_negative()); 358 | //! # assert!(!42u32.is_negative()); 359 | //! # assert!(!42i8.is_negative()); 360 | //! ``` 361 | //! 362 | //! In general, nested invocations can be used anywhere. However, note that 363 | //! nested invocations are only recognized by the identifier `duplicate`, 364 | //! followed by `!`, followed by a delimiter within which the nested invocation 365 | //! is. Therefore, care must be taken to ensure the surrounding code is correct 366 | //! after the expansion. E.g. maybe `;` is needed after the invocation, or 367 | //! commas must be produced by the nested invocation itself as part of a list. 368 | //! 369 | //! ### Eager Expansion 370 | //! 371 | //! `duplicate!` calls within the body of duplicated code is eagerly expanded. 372 | //! I.e., inner `duplicate!` calls expand and resolve ahead of outer 373 | //! calls, regardless of whether the call is in the duplicated body 374 | //! or invocation. This is especially important if using the same 375 | //! substitution identifiers in both outer and inner calls. 376 | //! 377 | //! ## Verbose Syntax 378 | //! 379 | //! The syntax used in the previous examples is the _short syntax_. 380 | //! `duplicate_item` also accepts a _verbose syntax_ that is less concise, but 381 | //! more readable in some circumstances. Using the verbose syntax, the very 382 | //! first example above looks like this: 383 | //! 384 | //! ``` 385 | //! # trait IsMax {fn is_max(&self) -> bool;} 386 | //! use duplicate::duplicate_item; 387 | //! #[duplicate_item( 388 | //! [ 389 | //! int_type [ u8 ] 390 | //! max_value [ 255 ] 391 | //! ] 392 | //! [ 393 | //! int_type [ u16 ] 394 | //! max_value [ 65_535 ] 395 | //! ] 396 | //! [ 397 | //! int_type [ u32 ] 398 | //! max_value [ 4_294_967_295 ] 399 | //! ] 400 | //! )] 401 | //! impl IsMax for int_type { 402 | //! fn is_max(&self) -> bool { 403 | //! *self == max_value 404 | //! } 405 | //! } 406 | //! 407 | //! # assert!(!42u8.is_max()); 408 | //! # assert!(!42u16.is_max()); 409 | //! # assert!(!42u32.is_max()); 410 | //! ``` 411 | //! 412 | //! In the verbose syntax, a substitution group is put inside '[]' and 413 | //! includes a list of substitution identifiers followed by their substitutions. 414 | //! No `;`s are needed. Here is an annotated version of the same code: 415 | //! 416 | //! ``` 417 | //! # trait IsMax {fn is_max(&self) -> bool;} 418 | //! # use duplicate::duplicate_item; 419 | //! #[duplicate_item( 420 | //! [ //-+ 421 | //! int_type [ u8 ] // | Substitution group 1 422 | //! max_value [ 255 ] // | 423 | //! // ^^^^^^^^^ ^^^^^^^ substitution | 424 | //! // | | 425 | //! // substitution identifier | 426 | //! ] //-+ 427 | //! [ //-+ 428 | //! int_type [ u16 ] // | Substitution group 2 429 | //! max_value [ 65_535 ] // | 430 | //! ] //-+ 431 | //! [ //-+ 432 | //! max_value [ 4_294_967_295 ] // | Substitution group 3 433 | //! int_type [ u32 ] // | 434 | //! ] //-+ 435 | //! )] 436 | //! # impl IsMax for int_type { 437 | //! # fn is_max(&self) -> bool { 438 | //! # *self == max_value 439 | //! # } 440 | //! # } 441 | //! # 442 | //! # assert!(!42u8.is_max()); 443 | //! # assert!(!42u16.is_max()); 444 | //! # assert!(!42u32.is_max()); 445 | //! ``` 446 | //! Note that in each substitution group every identifier must have exactly one 447 | //! substitution. All the groups must have the exact same identifiers, though 448 | //! the order in which they arrive in each group is not important. For example, 449 | //! in the annotated example, the third group has the `max_value` identifier 450 | //! before `int_type` without having any effect on the expanded code. 451 | //! 452 | //! The verbose syntax is not very concise but it has some advantages over 453 | //! the short syntax in regards to readability. Using many identifiers and 454 | //! long substitutions can quickly become unwieldy in the short syntax. 455 | //! The verbose syntax deals better with both cases as it will grow horizontally 456 | //! instead of vertically. 457 | //! 458 | //! The verbose syntax also offers nested invocation. The syntax is exactly the 459 | //! same, but since there is no initial substitution identifier list, nested 460 | //! calls can be used anywhere (though still not inside substitution groups.) 461 | //! The previous `IsNegative` nested invocation example can be written as 462 | //! follows: 463 | //! 464 | //! ``` 465 | //! # trait IsNegative { fn is_negative(&self) -> bool;} 466 | //! # use duplicate::duplicate_item; 467 | //! #[duplicate_item( 468 | //! duplicate!{ 469 | //! [ int_type_nested; [u8]; [u16]; [u32] ] 470 | //! [ 471 | //! int_type [ int_type_nested ] 472 | //! implementation [ false ] 473 | //! ] 474 | //! } 475 | //! duplicate!{ 476 | //! [ int_type_nested; [i8]; [i16]; [i32] ] 477 | //! [ 478 | //! int_type [ int_type_nested ] 479 | //! implementation [ *self < 0 ] 480 | //! ] 481 | //! } 482 | //! )] 483 | //! impl IsNegative for int_type { 484 | //! fn is_negative(&self) -> bool { 485 | //! implementation 486 | //! } 487 | //! } 488 | //! 489 | //! assert!(!42u8.is_negative()); 490 | //! assert!(!42u16.is_negative()); 491 | //! assert!(!42u32.is_negative()); 492 | //! assert!(!42i8.is_negative()); 493 | //! assert!(!42i16.is_negative()); 494 | //! assert!(!42i32.is_negative()); 495 | //! ``` 496 | //! 497 | //! It's important to notice that the nested invocation doesn't know it 498 | //! isn't the outer-most invocation and therefore doesn't discriminate between 499 | //! identifiers. We had to use a different identifier in the nested invocations 500 | //! (`int_type_nested`) than in the code (`int_type`), because otherwise the 501 | //! nested invocation would substitute the substitution identifier too, instead 502 | //! of only substituting in the nested invocation's substitute. 503 | //! 504 | //! Nested invocations must produce the syntax of their 505 | //! parent invocation. However, each invocation's private syntax is free 506 | //! to use any syntax type. Notice in our above example, the nested 507 | //! invocations use short syntax but produce verbose syntax for the outermost 508 | //! invocation. 509 | //! 510 | //! [`macro@substitute`] is also available when using nested invocation, with 511 | //! the same behavior. 512 | //! 513 | //! ## Global Substitutions 514 | //! 515 | //! Say we have a function that takes two types as inputs and returns the same 516 | //! types as output: 517 | //! 518 | //! ``` 519 | //! # struct Some(T1,T2); 520 | //! # struct Complex(T); 521 | //! # struct Type(T); 522 | //! # struct WeDont(T1,T2,T3); 523 | //! # struct Want(); 524 | //! # struct To(); 525 | //! # struct Repeat(); 526 | //! # struct Other(); 527 | //! fn some_func( 528 | //! arg1: Some, Type>>, 529 | //! arg2: Some>>) 530 | //! -> ( 531 | //! Some, Type>>, 532 | //! Some>> 533 | //! ) 534 | //! { 535 | //! # /* 536 | //! ... 537 | //! # */ 538 | //! # unimplemented!() 539 | //! } 540 | //! ``` 541 | //! 542 | //! Using the [`macro@substitute_item`] attribute, we can avoid repeating the 543 | //! types: 544 | //! 545 | //! ``` 546 | //! # struct Some(T1,T2); 547 | //! # struct Complex(T); 548 | //! # struct Type(T); 549 | //! # struct WeDont(T1,T2,T3); 550 | //! # struct Want(); 551 | //! # struct To(); 552 | //! # struct Repeat(); 553 | //! # struct Other(); 554 | //! # use duplicate::substitute_item; 555 | //! #[substitute_item( 556 | //! typ1 [Some, Type>>]; 557 | //! typ2 [Some>>]; 558 | //! )] 559 | //! fn some_func(arg1: typ1, arg2: typ2) -> (typ1, typ2){ 560 | //! # /* 561 | //! ... 562 | //! # */ 563 | //! # unimplemented!() 564 | //! } 565 | //! ``` 566 | //! 567 | //! Here we have defined the two global substitution variables `typ1` and 568 | //! `typ2`, and used them in the function definition. [`macro@substitute_item`] 569 | //! and [`macro@substitute`] have the same syntax as verbose syntax substitution 570 | //! (identifier, optionally followed by parameters, followed by a substitution.) 571 | //! and only substitute in-place, with no duplication. 572 | //! 573 | //! If we want to both do substitution as above (called _global substitution_) 574 | //! and duplicate, e can follow global substitutions by substitution groups when 575 | //! using [`duplicate_item`]: 576 | //! 577 | //! ``` 578 | //! # struct Some(T1,T2); 579 | //! # struct Complex(T); 580 | //! # struct Type(T); 581 | //! # struct WeDont(T1,T2,T3); 582 | //! # struct Want(); 583 | //! # struct To(); 584 | //! # struct Repeat(); 585 | //! # struct Other(); 586 | //! # use duplicate::duplicate_item; 587 | //! #[duplicate_item( 588 | //! typ1 [Some, Type>>]; 589 | //! typ2 [Some>>]; 590 | //! method reference(type); 591 | //! [get] [& type]; 592 | //! [get_mut] [&mut type]; 593 | //! )] 594 | //! fn method( 595 | //! arg0: reference([Type<()>]), 596 | //! arg1: typ1, 597 | //! arg2: typ2) 598 | //! -> (reference([typ1]), reference([typ2])) 599 | //! { 600 | //! # /* 601 | //! ... 602 | //! # */ 603 | //! # unimplemented!() 604 | //! } 605 | //! ``` 606 | //! 607 | //! Here we duplicate the function to use either shared or mutable reference, 608 | //! while reusing `typ1` and `typ2` in both duplicates. 609 | //! 610 | //! The following additional rules apply when using global substitutions: 611 | //! 612 | //! * All global substitutions must come before any short or verbose syntax 613 | //! substitution groups. 614 | //! * Global substitution variable are __not__ substituted inside the bodies of 615 | //! following substitutions. If that is needed, multiple invocations can be 616 | //! used. 617 | //! * All global substitutions must be separated by `;`, also when followed by 618 | //! substitution groups. 619 | //! 620 | //! # Crate Features 621 | //! 622 | //! ### `module_disambiguation` 623 | //! __Implicit Module Name Disambiguation__ (Enabled by default) 624 | //! 625 | //! It is sometime beneficial to apply `duplicate_item` to a module, such that 626 | //! all its contents are duplicated at once. However, this will always need the 627 | //! resulting modules to have unique names to avoid the compiler issueing an 628 | //! error. Without `module_disambiguation`, module names must be substituted 629 | //! manually. With `module_disambiguation`, the following will compile 630 | //! successfully: 631 | //! 632 | //! ``` 633 | //! # #[cfg(feature="module_disambiguation")] // Ensure test is only run if feature is on 634 | //! # { 635 | //! # use duplicate::duplicate_item; 636 | //! #[duplicate_item( 637 | //! int_type max_value; 638 | //! [ u8 ] [ 255 ]; 639 | //! [ u16 ] [ 65_535 ]; 640 | //! [ u32 ] [ 4_294_967_295 ]; 641 | //! )] 642 | //! mod module { 643 | //! # // There is a bug with rustdoc, where these traits cannot 644 | //! # // be imported using 'use super::*'. 645 | //! # // This is a workaround 646 | //! # pub trait IsNegative { fn is_negative(&self) -> bool;} 647 | //! # pub trait IsMax {fn is_max(&self) -> bool;} 648 | //! impl IsMax for int_type { 649 | //! fn is_max(&self) -> bool { 650 | //! *self == max_value 651 | //! } 652 | //! } 653 | //! impl IsNegative for int_type { 654 | //! fn is_negative(&self) -> bool { 655 | //! false 656 | //! } 657 | //! } 658 | //! } 659 | //! # // This is part of the workaround for not being able to import 660 | //! # // these traits in each module. We rename them so that they 661 | //! # // don't clash with each other. 662 | //! # use module_u8::IsNegative as trait1; 663 | //! # use module_u8::IsMax as trait2; 664 | //! # use module_u16::IsNegative as trait3; 665 | //! # use module_u16::IsMax as trait4; 666 | //! # use module_u32::IsNegative as trait5; 667 | //! # use module_u32::IsMax as trait6; 668 | //! 669 | //! assert!(!42u8.is_max()); 670 | //! assert!(!42u16.is_max()); 671 | //! assert!(!42u32.is_max()); 672 | //! assert!(!42u8.is_negative()); 673 | //! assert!(!42u16.is_negative()); 674 | //! assert!(!42u32.is_negative()); 675 | //! # } 676 | //! ``` 677 | //! 678 | //! This works because the three duplicate modules get assigned unique names: 679 | //! `module_u8`, `module_u16`, and `module_u32`. However, this only works if a 680 | //! substitution identifier can be found, where all its substitutions only 681 | //! produce a single identifier and nothing else. Those identifiers are then 682 | //! converted to snake case, and postfixed to the original module's name, 683 | //! e.g., `module + u8 = module_u8`. The first suitable substitution 684 | //! identifier is chosen. 685 | //! 686 | //! Notes: 687 | //! 688 | //! * The exact way unique names are generated is not part of any stability 689 | //! guarantee and should not be depended upon. It may change in the future 690 | //! without bumping the major version. 691 | //! * Only the name of the module is substituted with the disambiguated name. 692 | //! Any matching identifier in the body of the module is ignored. 693 | //! 694 | //! ### `pretty_errors` 695 | //! __More Detailed Error Messages__ (Enabled by default) 696 | //! 697 | //! Enabling this feature will make error messages indicate exactly where the 698 | //! offending code is. Without this feature, error messages will not provide 699 | //! detailed location indicators for errors. 700 | //! 701 | //! This feature is has no effect on expansion. Therefore, libraries are advised 702 | //! to keep this feature off (note that it's enabled by default) 703 | //! to avoid forcing it on users. 704 | //! 705 | //! # Disclaimer 706 | //! 707 | //! This crate does not try to justify or condone the usage of code duplication 708 | //! instead of proper abstractions. 709 | //! This crate should only be used where it is not possible to reduce code 710 | //! duplication through other means, or where it simply is not worth it. 711 | //! 712 | //! As an example, libraries that have two or more structs/traits with similar 713 | //! APIs might use this macro to test them without having to copy-paste test 714 | //! cases and manually make the needed edits. 715 | #![cfg_attr(feature = "fail-on-warnings", deny(warnings))] 716 | 717 | extern crate proc_macro; 718 | 719 | mod crate_readme_test; 720 | mod error; 721 | #[cfg(feature = "module_disambiguation")] 722 | mod module_disambiguation; 723 | mod parse; 724 | mod pretty_errors; 725 | mod substitute; 726 | mod token_iter; 727 | 728 | use crate::{ 729 | error::Error, 730 | token_iter::{is_ident, Token, TokenIter}, 731 | }; 732 | use parse::*; 733 | use proc_macro::{Delimiter, Group, Ident, Span, TokenStream}; 734 | use std::{collections::HashMap, iter::empty}; 735 | use substitute::*; 736 | 737 | /// Duplicates the item and substitutes specific identifiers for different code 738 | /// snippets in each duplicate. 739 | /// 740 | /// # Short Syntax 741 | /// ``` 742 | /// use duplicate::duplicate_item; 743 | /// trait IsMax { 744 | /// fn is_max(&self) -> bool; 745 | /// } 746 | /// 747 | /// #[duplicate_item( 748 | /// int_type max_value; 749 | /// [ u8 ] [ 255 ]; 750 | /// [ u16 ] [ 65_535 ]; 751 | /// [ u32 ] [ 4_294_967_295 ]; 752 | /// )] 753 | /// impl IsMax for int_type { 754 | /// fn is_max(&self) -> bool { 755 | /// *self == max_value 756 | /// } 757 | /// } 758 | /// 759 | /// assert!(!42u8.is_max()); 760 | /// assert!(!42u16.is_max()); 761 | /// assert!(!42u32.is_max()); 762 | /// ``` 763 | /// The implementation of `IsMax` is duplicated 3 times: 764 | /// 765 | /// 1. For the type `u8` and its maximum value `255`. 766 | /// 2. For the type `u16` and its maximum value `65_535 `. 767 | /// 3. For the type `u32` and its maximum value `4_294_967_295 `. 768 | /// 769 | /// This syntax must start with a list of all identifiers followed by `;`. 770 | /// Then a `;` seperated list of substitution groups must be given (at least 1 771 | /// group). Every group is a list of substitutions, one for each substitution 772 | /// identifier given in the first line. 773 | /// The substitutions must be enclosed in `[]` but are otherwise 774 | /// free. 775 | /// 776 | /// # Verbose Syntax 777 | /// 778 | /// ``` 779 | /// use duplicate::duplicate_item; 780 | /// trait IsMax { 781 | /// fn is_max(&self) -> bool; 782 | /// } 783 | /// 784 | /// #[duplicate_item( 785 | /// [ 786 | /// int_type [ u8 ] 787 | /// max_value [ 255 ] 788 | /// ] 789 | /// [ 790 | /// int_type [ u16 ] 791 | /// max_value [ 65_535 ] 792 | /// ] 793 | /// [ 794 | /// max_value [ 4_294_967_295 ] 795 | /// int_type [ u32 ] 796 | /// ] 797 | /// )] 798 | /// impl IsMax for int_type { 799 | /// fn is_max(&self) -> bool { 800 | /// *self == max_value 801 | /// } 802 | /// } 803 | /// 804 | /// assert!(!42u8.is_max()); 805 | /// assert!(!42u16.is_max()); 806 | /// assert!(!42u32.is_max()); 807 | /// ``` 808 | /// Has the same functionality as the previous short-syntax example. 809 | /// 810 | /// For each duplicate needed, a _substitution group_ must be given enclosed in 811 | /// `[]`. A substitution group is a set of identifiers and 812 | /// substitution pairs, like in the short syntax, but there can only be one 813 | /// substitution per identifier. All substitution groups must have the same 814 | /// identifiers, however their order is unimportant, as can be seen from the 815 | /// last substitution group above, where `max_value` comes before `int_type`. 816 | /// 817 | /// # Parameterized Substitutoin 818 | /// 819 | /// ``` 820 | /// use duplicate::duplicate_item; 821 | /// struct VecWrap(Vec); 822 | /// 823 | /// impl VecWrap { 824 | /// #[duplicate_item( 825 | /// method reference(lifetime, type); 826 | /// [get] [& 'lifetime type]; 827 | /// [get_mut] [& 'lifetime mut type]; 828 | /// )] 829 | /// pub fn method<'a>(self: reference([a],[Self]),idx: usize) -> Option { 830 | /// self.0.method(idx) 831 | /// } 832 | /// } 833 | /// 834 | /// let mut vec = VecWrap(vec![1,2,3]); 835 | /// assert_eq!(*vec.get(0).unwrap(), 1); 836 | /// *vec.get_mut(1).unwrap() = 5; 837 | /// assert_eq!(*vec.get(1).unwrap(), 5); 838 | /// ``` 839 | /// 840 | /// This implements two versions of the method: 841 | /// 842 | /// - `get`: Borrows `self` immutably and return a shared reference. 843 | /// - `get_mut`: Borrows `self` mutably and returns a mutable reference. 844 | /// 845 | /// If an identifier is followed by parenthises (in both its declaration and its 846 | /// use), a set of parameters can be provided to customize the subtituion for 847 | /// each use. In the declaration a list of identifiers is given, which can be 848 | /// used in its substitutions. When using the identifier, argument code snippets 849 | /// must be given in a comma separated list, with each argument being inclosed 850 | /// in `[]`. 851 | /// 852 | /// Parameterized substitution is also available for the verbose syntax: 853 | /// 854 | /// ``` 855 | /// # use duplicate::duplicate_item; 856 | /// # struct VecWrap(Vec); 857 | /// impl VecWrap { 858 | /// #[duplicate_item( 859 | /// [ 860 | /// method [get] 861 | /// reference(lifetime, type) [& 'lifetime type] 862 | /// ] 863 | /// [ 864 | /// method [get_mut] 865 | /// reference(lifetime, type) [& 'lifetime mut type] 866 | /// ] 867 | /// )] 868 | /// pub fn method<'a>(self: reference([a],[Self]),idx: usize) -> Option { 869 | /// self.0.method(idx) 870 | /// } 871 | /// } 872 | /// # let mut vec = VecWrap(vec![1,2,3]); 873 | /// # assert_eq!(*vec.get(0).unwrap(), 1); 874 | /// # *vec.get_mut(1).unwrap() = 5; 875 | /// # assert_eq!(*vec.get(1).unwrap(), 5); 876 | /// ``` 877 | /// 878 | /// # Nested Invocation 879 | /// ``` 880 | /// use duplicate::duplicate_item; 881 | /// trait IsNegative { 882 | /// fn is_negative(&self) -> bool; 883 | /// } 884 | /// 885 | /// #[duplicate_item( 886 | /// int_type implementation; 887 | /// duplicate!{ 888 | /// [ // -+ 889 | /// int_type_nested;[u8];[u16];[u32] // | Nested invocation producing 3 890 | /// ] // | substitution groups 891 | /// [ int_type_nested ] [ false ]; // | 892 | /// } // -+ 893 | /// [ i8 ] [ *self < 0 ] // -- Substitution group 4 894 | /// )] 895 | /// impl IsNegative for int_type { 896 | /// fn is_negative(&self) -> bool { 897 | /// implementation 898 | /// } 899 | /// } 900 | /// 901 | /// assert!(!42u8.is_negative()); 902 | /// assert!(!42u16.is_negative()); 903 | /// assert!(!42u32.is_negative()); 904 | /// assert!(!42i8.is_negative()); 905 | /// ``` 906 | /// 907 | /// This implements `IsNegative` 4 times: 908 | /// 909 | /// 1. For the type `u8` with the implementation of the method simply returning 910 | /// `false`. 1. For the type `u16` the same way as `u8`. 911 | /// 1. For the type `u32` the same way as `u8` and `u16`. 912 | /// 1. For `i8` with the implementation of the method checking whether it's less 913 | /// than `0`. 914 | /// 915 | /// We used `#` to start a _nested invocation_ of the macro. In it, we use the 916 | /// identifier `int_type_nested` to substitute the 3 unsigned integer types into 917 | /// the body of the nested invocation, which is a substitution group for the 918 | /// outer macro invocation. This therefore produces the three substitution 919 | /// groups that makes the outer macro make the duplicates for the unsigned 920 | /// integers. 921 | /// 922 | /// This code is identical to the following, which doesn't use nested 923 | /// invocation: 924 | /// 925 | /// ``` 926 | /// # use duplicate::duplicate_item; 927 | /// # trait IsNegative { 928 | /// # fn is_negative(&self) -> bool; 929 | /// # } 930 | /// #[duplicate_item( 931 | /// int_type implementation; 932 | /// [ u8 ] [ false ]; 933 | /// [ u16 ] [ false ]; 934 | /// [ u32 ] [ false ]; 935 | /// [ i8 ] [ *self < 0 ] 936 | /// )] 937 | /// impl IsNegative for int_type { 938 | /// fn is_negative(&self) -> bool { 939 | /// implementation 940 | /// } 941 | /// } 942 | /// # assert!(!42u8.is_negative()); 943 | /// # assert!(!42u16.is_negative()); 944 | /// # assert!(!42u32.is_negative()); 945 | /// # assert!(!42i8.is_negative()); 946 | /// ``` 947 | /// 948 | /// Nested invocation is also available for the verbose syntax: 949 | /// 950 | /// ``` 951 | /// use duplicate::duplicate_item; 952 | /// trait IsNegative { 953 | /// fn is_negative(&self) -> bool; 954 | /// } 955 | /// 956 | /// #[duplicate_item( 957 | /// duplicate!{ // -+ 958 | /// [ int_type_nested;[u8];[u16];[u32] ] // | 959 | /// [ // | Nested invocation producing 3 960 | /// int_type [ int_type_nested ] // | substitution groups 961 | /// implementation [ false ] // | 962 | /// ] // | 963 | /// } // -+ 964 | /// [ // -+ 965 | /// int_type [ i8 ] // | Substitution group 4 966 | /// implementation [ *self < 0 ] // | 967 | /// ] // -+ 968 | /// )] 969 | /// impl IsNegative for int_type { 970 | /// fn is_negative(&self) -> bool { 971 | /// implementation 972 | /// } 973 | /// } 974 | /// 975 | /// assert!(!42u8.is_negative()); 976 | /// assert!(!42u16.is_negative()); 977 | /// assert!(!42u32.is_negative()); 978 | /// assert!(!42i8.is_negative()); 979 | /// ``` 980 | /// 981 | /// ## Global Substitution 982 | /// 983 | /// ``` 984 | /// # struct Some(T1,T2); 985 | /// # struct Complex(T); 986 | /// # struct Type(T); 987 | /// # struct WeDont(T1,T2,T3); 988 | /// # struct Want(); 989 | /// # struct To(); 990 | /// # struct Repeat(); 991 | /// # struct Other(); 992 | /// # use duplicate::duplicate_item; 993 | /// #[duplicate_item( 994 | /// typ1 [Some, Type>>]; 995 | /// typ2 [Some>>]; 996 | /// method reference(type); 997 | /// [get] [& type]; 998 | /// [get_mut] [&mut type]; 999 | /// )] 1000 | /// fn method( 1001 | /// arg0: reference([Type<()>]), 1002 | /// arg1: typ1, 1003 | /// arg2: typ2) 1004 | /// -> (reference([typ1]), reference([typ2])) 1005 | /// { 1006 | /// # /* 1007 | /// ... 1008 | /// # */ 1009 | /// # unimplemented!() 1010 | /// } 1011 | /// ``` 1012 | /// 1013 | /// The global substitutions (`typ1` and `typ2`) are substituted in both 1014 | /// duplicates of the function. Global substitutions have the same syntax as 1015 | /// verbose syntax substitutions, are `;` separated (even from following 1016 | /// substitutions groups), must all be defined at the beginning, and aren't 1017 | /// usable in the invocation itself but only in the code being duplicated. 1018 | #[proc_macro_attribute] 1019 | pub fn duplicate_item(attr: TokenStream, item: TokenStream) -> TokenStream 1020 | { 1021 | match duplicate_impl(attr, item) 1022 | { 1023 | Ok(result) => result, 1024 | Err(err) => emit_error(err), 1025 | } 1026 | } 1027 | 1028 | /// Substitutes specific identifiers for different code 1029 | /// snippets. 1030 | /// 1031 | /// ``` 1032 | /// # struct Some(T1,T2); 1033 | /// # struct Complex(T); 1034 | /// # struct Type(T); 1035 | /// # struct WeDont(T1,T2,T3); 1036 | /// # struct Want(); 1037 | /// # struct To(); 1038 | /// # struct Repeat(); 1039 | /// # struct Other(); 1040 | /// # use duplicate::substitute_item; 1041 | /// #[substitute_item( 1042 | /// typ1 [Some, Type>>]; 1043 | /// typ2 [Some>>]; 1044 | /// )] 1045 | /// fn method( 1046 | /// arg1: typ1, 1047 | /// arg2: typ2) 1048 | /// -> (typ1, typ2) 1049 | /// { 1050 | /// # /* 1051 | /// ... 1052 | /// # */ 1053 | /// # unimplemented!() 1054 | /// } 1055 | /// ``` 1056 | /// 1057 | /// The global substitutions (`typ1` and `typ2`) are substituted in both 1058 | /// their occurrences. Global substitutions are `;` separated. 1059 | #[proc_macro_attribute] 1060 | pub fn substitute_item(attr: TokenStream, item: TokenStream) -> TokenStream 1061 | { 1062 | match substitute_impl(attr, item) 1063 | { 1064 | Ok(result) => result, 1065 | Err(err) => emit_error(err), 1066 | } 1067 | } 1068 | 1069 | /// Duplicates the given code and substitutes specific identifiers 1070 | /// for different code snippets in each duplicate. 1071 | /// 1072 | /// This is a function-like procedural macro version of [`duplicate_item`]. 1073 | /// Its functionality is the exact same, and they share the same invocation 1074 | /// syntax(es). The only difference is that `duplicate` doesn't only 1075 | /// duplicate the following item, but duplicate all code given to it after the 1076 | /// invocation block. 1077 | /// 1078 | /// ## Usage 1079 | /// 1080 | /// A call to `duplicate` must start with a `[]` containing the 1081 | /// duplication invocation. Everything after that will then be duplicated 1082 | /// according to the invocation. 1083 | /// 1084 | /// Given the following `duplicate` call: 1085 | /// ``` 1086 | /// use duplicate::duplicate; 1087 | /// # trait IsMax { 1088 | /// # fn is_max(&self) -> bool; 1089 | /// # } 1090 | /// 1091 | /// duplicate!{ 1092 | /// [ 1093 | /// // Some duplication invocation 1094 | /// # int_type max_value; 1095 | /// # [ u8 ] [ 255 ]; 1096 | /// # [ u16 ] [ 65_535 ]; 1097 | /// # [ u32 ] [ 4_294_967_295 ]; 1098 | /// ] 1099 | /// // Some code to duplicate 1100 | /// # impl IsMax for int_type { 1101 | /// # fn is_max(&self) -> bool { 1102 | /// # *self == max_value 1103 | /// # } 1104 | /// # } 1105 | /// } 1106 | /// # // We use an explicit 'main' function to ensure the previous 1107 | /// # // 'duplicate' call doesn't get treated as a statement, 1108 | /// # // which illegal before rust 1.45. 1109 | /// # fn main() { 1110 | /// # assert!(!42u8.is_max()); 1111 | /// # assert!(!42u16.is_max()); 1112 | /// # assert!(!42u32.is_max()); 1113 | /// # } 1114 | /// ``` 1115 | /// It is equivalent to the following invocation using [`duplicate_item`]: 1116 | /// ``` 1117 | /// use duplicate::duplicate_item; 1118 | /// # trait IsMax { 1119 | /// # fn is_max(&self) -> bool; 1120 | /// # } 1121 | /// 1122 | /// #[duplicate_item( 1123 | /// // Some duplication invocation 1124 | /// # int_type max_value; 1125 | /// # [ u8 ] [ 255 ]; 1126 | /// # [ u16 ] [ 65_535 ]; 1127 | /// # [ u32 ] [ 4_294_967_295 ]; 1128 | /// )] 1129 | /// // Some code to duplicate 1130 | /// # impl IsMax for int_type { 1131 | /// # fn is_max(&self) -> bool { 1132 | /// # *self == max_value 1133 | /// # } 1134 | /// # } 1135 | /// # assert!(!42u8.is_max()); 1136 | /// # assert!(!42u16.is_max()); 1137 | /// # assert!(!42u32.is_max()); 1138 | /// ``` 1139 | /// 1140 | /// For more details on about invocations and features see [`duplicate_item`]. 1141 | /// 1142 | /// [`duplicate_item`]: attr.duplicate_item.html 1143 | #[proc_macro] 1144 | pub fn duplicate(stream: TokenStream) -> TokenStream 1145 | { 1146 | inline_macro_impl(stream, duplicate_impl) 1147 | } 1148 | 1149 | /// Substitutes specific identifiers for different code 1150 | /// snippets. 1151 | /// 1152 | /// This is a function-like procedural macro version of 1153 | /// [`macro@substitute_item`]. Its functionality is the exact same. The only 1154 | /// difference is that `substitute` doesn't only substitute the following item, 1155 | /// but all code given to it after the invocation block. 1156 | /// 1157 | /// ``` 1158 | /// # struct Some(T1,T2); 1159 | /// # struct Complex(T); 1160 | /// # struct Type(T); 1161 | /// # struct WeDont(T1,T2,T3); 1162 | /// # struct Want(); 1163 | /// # struct To(); 1164 | /// # struct Repeat(); 1165 | /// # struct Other(); 1166 | /// # use duplicate::substitute; 1167 | /// 1168 | /// substitute!{ 1169 | /// [ 1170 | /// typ1 [Some, Type>>]; 1171 | /// typ2 [Some>>]; 1172 | /// ] 1173 | /// fn method( 1174 | /// arg1: typ1, 1175 | /// arg2: typ2) 1176 | /// -> (typ1, typ2) 1177 | /// { 1178 | /// # /* 1179 | /// ... 1180 | /// # */ 1181 | /// # unimplemented!() 1182 | /// } 1183 | /// } 1184 | /// ``` 1185 | /// 1186 | /// The global substitutions (`typ1` and `typ2`) are substituted in both 1187 | /// their occurrences. Global substitutions are `;` separated. 1188 | #[proc_macro] 1189 | pub fn substitute(stream: TokenStream) -> TokenStream 1190 | { 1191 | inline_macro_impl(stream, substitute_impl) 1192 | } 1193 | 1194 | /// A result that specified where in the token stream the error occured 1195 | /// and is accompanied by a message. 1196 | type Result = std::result::Result; 1197 | 1198 | /// Parses an inline macro invocation where the invocation syntax is within 1199 | /// initial brackets. 1200 | /// 1201 | /// Extracts the invocation syntax and body to be duplicated/substituted 1202 | /// and passes them to the given function. 1203 | fn inline_macro_impl( 1204 | stream: TokenStream, 1205 | f: fn(TokenStream, TokenStream) -> Result, 1206 | ) -> TokenStream 1207 | { 1208 | let empty_globals = SubstitutionGroup::new(); 1209 | let mut iter = TokenIter::new(stream, &empty_globals, empty()); 1210 | 1211 | let result = match iter.next_group(Some(Delimiter::Bracket)) 1212 | { 1213 | Ok((invocation, _)) => 1214 | { 1215 | let invocation_body = invocation.to_token_stream(); 1216 | 1217 | f(invocation_body, iter.to_token_stream()) 1218 | }, 1219 | Err(err) => Err(err.hint("Expected invocation within brackets: [...]")), 1220 | }; 1221 | 1222 | match result 1223 | { 1224 | Ok(result) => result, 1225 | Err(err) => emit_error(err), 1226 | } 1227 | } 1228 | 1229 | /// Implements the duplicate macros. 1230 | fn duplicate_impl(attr: TokenStream, item: TokenStream) -> Result 1231 | { 1232 | let dup_def = parse_duplicate_invocation(attr)?; 1233 | 1234 | duplicate_and_substitute( 1235 | item, 1236 | &dup_def.global_substitutions, 1237 | dup_def.duplications.iter(), 1238 | ) 1239 | } 1240 | 1241 | /// Implements the substitute macros 1242 | fn substitute_impl(attr: TokenStream, item: TokenStream) -> Result 1243 | { 1244 | duplicate_and_substitute(item, &parse_global_substitutions_only(attr)?, empty()) 1245 | } 1246 | 1247 | /// Terminates with an error and produces the given message. 1248 | /// 1249 | /// The `pretty_errors` feature can be enabled, the span is shown 1250 | /// with the error message. 1251 | fn emit_error(err: Error) -> TokenStream 1252 | { 1253 | #[cfg(feature = "pretty_errors")] 1254 | { 1255 | err.into_diagnostic().emit_as_item_tokens().into() 1256 | } 1257 | #[cfg(not(feature = "pretty_errors"))] 1258 | { 1259 | panic!("{}", err.into_panic_message()); 1260 | } 1261 | } 1262 | 1263 | #[derive(Debug)] 1264 | struct SubstitutionGroup 1265 | { 1266 | substitutions: HashMap, 1267 | #[cfg(feature = "module_disambiguation")] 1268 | identifier_order: Vec, 1269 | } 1270 | 1271 | impl SubstitutionGroup 1272 | { 1273 | fn new() -> Self 1274 | { 1275 | Self { 1276 | substitutions: HashMap::new(), 1277 | #[cfg(feature = "module_disambiguation")] 1278 | identifier_order: Vec::new(), 1279 | } 1280 | } 1281 | 1282 | fn add_substitution(&mut self, ident: Ident, subst: Substitution) -> Result<()> 1283 | { 1284 | if self 1285 | .substitutions 1286 | .insert(ident.to_string(), subst) 1287 | .is_some() 1288 | { 1289 | Err( 1290 | Error::new("Substitution identifier assigned mutiple substitutions") 1291 | .span(ident.span()), 1292 | ) 1293 | } 1294 | else 1295 | { 1296 | #[cfg(feature = "module_disambiguation")] 1297 | { 1298 | self.identifier_order.push(ident.to_string()); 1299 | } 1300 | Ok(()) 1301 | } 1302 | } 1303 | 1304 | fn substitution_of(&self, ident: &String) -> Option<&Substitution> 1305 | { 1306 | self.substitutions.get(ident) 1307 | } 1308 | 1309 | fn identifiers(&self) -> impl Iterator 1310 | { 1311 | self.substitutions.keys() 1312 | } 1313 | 1314 | fn identifiers_with_args(&self) -> impl Iterator 1315 | { 1316 | self.identifiers() 1317 | .map(move |ident| (ident, self.substitution_of(ident).unwrap().argument_count())) 1318 | } 1319 | 1320 | #[cfg(feature = "module_disambiguation")] 1321 | fn identifiers_ordered(&self) -> impl Iterator 1322 | { 1323 | self.identifier_order.iter() 1324 | } 1325 | } 1326 | 1327 | /// Defines how duplication should happen. 1328 | #[derive(Debug)] 1329 | struct DuplicationDefinition 1330 | { 1331 | pub global_substitutions: SubstitutionGroup, 1332 | pub duplications: Vec, 1333 | } 1334 | 1335 | /// Checks whether item is a module and whether it then needs disambiguation. 1336 | /// 1337 | /// Returns the identifier of the found module (if found) and the substitution 1338 | /// identifier that should be used to disambiguate it in each duplicate. 1339 | /// Returns none if no disambiguation is needed. 1340 | pub(crate) fn disambiguate_module<'a>( 1341 | item: &TokenStream, 1342 | sub_groups: impl Iterator + Clone, 1343 | ) -> Result> 1344 | { 1345 | let mut sub_groups = sub_groups.peekable(); 1346 | 1347 | match (sub_groups.peek(), get_module_name(&item)) 1348 | { 1349 | (Some(sub), Some(ref module)) if sub.substitution_of(&module.to_string()).is_none() => 1350 | { 1351 | #[cfg(not(feature = "module_disambiguation"))] 1352 | { 1353 | Err(Error::new(format!( 1354 | "Duplicating the module '{}' without giving each duplicate a unique \ 1355 | name.\nHint: Enable the 'duplicate' crate's 'module_disambiguation' feature \ 1356 | to automatically generate unique module names.", 1357 | module.to_string() 1358 | )) 1359 | .span(module.span())) 1360 | } 1361 | #[cfg(feature = "module_disambiguation")] 1362 | { 1363 | let span = module.span(); 1364 | Ok(Some(( 1365 | module.clone(), 1366 | crate::module_disambiguation::find_simple(sub_groups, span)?, 1367 | ))) 1368 | } 1369 | }, 1370 | _ => Ok(None), 1371 | } 1372 | } 1373 | 1374 | /// Extract the name of the module assuming the given item is a module 1375 | /// declaration. 1376 | /// 1377 | /// If not, returns None. 1378 | fn get_module_name(item: &TokenStream) -> Option 1379 | { 1380 | let empty_globals = SubstitutionGroup::new(); 1381 | let mut iter = TokenIter::new(item.clone(), &empty_globals, std::iter::empty()); 1382 | 1383 | iter.expect_simple(|t| is_ident(t, Some("mod")), None) 1384 | .ok()?; 1385 | 1386 | let module = iter.extract_identifier(None).ok()?; 1387 | iter.next_group(Some(Delimiter::Brace)).ok()?; 1388 | Some(module) 1389 | } 1390 | 1391 | /// Creates a new group with the given span correctly set as the group's span. 1392 | /// 1393 | /// Use this function instead of creating the group manually, as forgetting 1394 | /// to set the span after creating the group could cause problems like leaking 1395 | /// this crate's edition into user code or simply result in cryptic error 1396 | /// messages. 1397 | pub(crate) fn new_group(del: Delimiter, stream: TokenStream, span: Span) -> Group 1398 | { 1399 | // We rename 'Group' to not get caught by the 'ensure_no_group_new' test 1400 | use Group as Gr; 1401 | let mut g = Gr::new(del, stream); 1402 | g.set_span(span); 1403 | g 1404 | } 1405 | --------------------------------------------------------------------------------