├── Cargo.toml ├── label ├── README.md ├── examples │ ├── consts.rs │ └── general.rs ├── Cargo.toml ├── tests │ ├── consts.rs │ └── main.rs └── src │ └── lib.rs ├── label-macros ├── Cargo.toml └── src │ └── lib.rs ├── LICENSE ├── README.md ├── .github └── workflows │ ├── ci.yml │ └── publish.yml └── .gitignore /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | default-members = ["label"] 3 | members = ["label", "label-macros"] -------------------------------------------------------------------------------- /label/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Label 3 | 4 | This crate is only used to re-export [label-macros](../label-macros). No actual implementation is included in it. 5 | 6 | -------------------------------------------------------------------------------- /label/examples/consts.rs: -------------------------------------------------------------------------------- 1 | // use label::create_label; 2 | // 3 | // create_label!( 4 | // static varname: usize; 5 | // ); 6 | // 7 | // #[varname::label] 8 | // static A: usize = 3; 9 | // 10 | // #[varname::label] 11 | // static B: usize = 3; 12 | // 13 | fn main() { 14 | // for i in varname::iter() { 15 | // println!("{}", i); 16 | // } 17 | } 18 | -------------------------------------------------------------------------------- /label/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "label" 3 | version = "0.5.1" 4 | authors = ["jonay2000 "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Label functions and iterate over them." 8 | documentation = "https://docs.rs/label" 9 | repository = "https://github.com/jonay2000/label" 10 | homepage = "https://github.com/jonay2000/label" 11 | readme = "../README.md" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | label-macros = {path="../label-macros", version="0.5.1"} 17 | ctor = "0.1.15" 18 | -------------------------------------------------------------------------------- /label-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "label-macros" 3 | version = "0.5.1" 4 | authors = ["jonay2000 "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Label functions and iterate over them." 8 | documentation = "https://docs.rs/label" 9 | repository = "https://github.com/jonay2000/label" 10 | homepage = "https://github.com/jonay2000/label" 11 | readme = "../README.md" 12 | 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [dependencies] 20 | syn = {version="1.0.33", features=["full"]} 21 | proc-macro2 = "1.0.24" 22 | quote = "1.0.7" 23 | -------------------------------------------------------------------------------- /label/tests/consts.rs: -------------------------------------------------------------------------------- 1 | use label::create_label; 2 | 3 | create_label!( 4 | static staticname: usize; 5 | const constname: usize; 6 | static mut staticmutname: usize; 7 | ); 8 | 9 | #[staticname::label] 10 | static A: usize = 3; 11 | 12 | #[constname::label] 13 | const B: usize = 4; 14 | 15 | #[test] 16 | fn test_simple() { 17 | for i in staticname::iter() { 18 | assert_eq!(*i, 3); 19 | } 20 | 21 | for i in constname::iter() { 22 | assert_eq!(*i, 4); 23 | } 24 | } 25 | 26 | #[test] 27 | fn test_named() { 28 | for (name, i) in staticname::iter_named() { 29 | assert_eq!(*i, 3); 30 | assert_eq!(name, "A") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /label/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_doctest_main)] 2 | //! # Label 3 | //! 4 | //! `label` is a library that can be used to create custom attributes for functions, through which you can list them and perform actions on them. 5 | //! Label uses no global state during the compilation process, to avoid incremental compilation breaking it. 6 | //! 7 | //! # Example 8 | //! 9 | //! ``` 10 | //! use label::create_label; 11 | //! 12 | //! create_label!(fn test() -> ()); 13 | //! 14 | //! #[test::label] 15 | //! fn my_fn() { 16 | //! println!("Test!"); 17 | //! } 18 | //! 19 | //! fn main() { 20 | //! println!("calling all 'test' label"); 21 | //! // using iter you can go through all functions with this annotation. 22 | //! for i in test::iter() { 23 | //! i(); 24 | //! } 25 | //! } 26 | //! 27 | //! 28 | //! ``` 29 | //! 30 | //! Label also supports labels on `static` and `const` variables, and iterating over the names of labeled items. 31 | //! For more information about this, visit the docs on [create_label](label_macros::create_label) 32 | //! 33 | 34 | pub use ctor::ctor; 35 | pub use label_macros::__label; 36 | pub use label_macros::create_label; 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Label 3 | 4 | ![](https://img.shields.io/crates/v/label) 5 | ![](https://docs.rs/label/badge.svg) 6 | ![](https://github.com/jonay2000/label/workflows/label/badge.svg) 7 | 8 | `label` is a library that can be used to create custom attributes for functions, through which you can list them and perform actions on them. 9 | Label uses no global state during the compilation process, to avoid incremental compilation breaking it. 10 | 11 | # Example 12 | 13 | ```rust 14 | 15 | create_label!(fn test() -> ()); 16 | 17 | #[test::label] 18 | fn my_fn() { 19 | println!("Test!"); 20 | } 21 | 22 | fn main() { 23 | println!("calling all 'test' label"); 24 | // using iter you can go through all functions with this annotation. 25 | for i in test::iter() { 26 | i(); 27 | } 28 | } 29 | 30 | ``` 31 | 32 | Label also supports labels on `static` and `const` variables, and iterating over the names of labeled items. 33 | For more information about this, visit the [docs](https://docs.rs/label) 34 | 35 | ## Contributing 36 | 37 | Any contributions are welcome. Just make a pull request or issue and I will try to respond as soon as possible. 38 | 39 | ### License 40 | 41 | [MIT](./LICENSE) 42 | -------------------------------------------------------------------------------- /label/examples/general.rs: -------------------------------------------------------------------------------- 1 | use label::create_label; 2 | 3 | // Create two labels. 4 | create_label!( 5 | fn test() -> (); 6 | fn test2(usize) -> usize; 7 | ); 8 | 9 | pub mod child { 10 | // annotate a function by giving the path to the annotation and postfixing ::annotate. 11 | #[super::test::label] 12 | fn my_fn() { 13 | println!("Test2!"); 14 | } 15 | } 16 | 17 | pub mod folder { 18 | // multiple label living in any submodule or supermodule are possible. 19 | #[crate::test::label] 20 | #[child::test1::label] 21 | fn my_fn() { 22 | println!("Test4!"); 23 | } 24 | 25 | pub mod child { 26 | use label::create_label; 27 | 28 | #[super::super::test::label] 29 | fn my_fn() { 30 | println!("Test3!"); 31 | } 32 | 33 | create_label!(fn test1() -> ()); 34 | } 35 | } 36 | 37 | #[test::label] 38 | #[folder::child::test1::label] 39 | fn my_fn() { 40 | println!("Test1!"); 41 | } 42 | 43 | #[test2::label] 44 | // label are typed, so functions annotated with test2 must take a usize and return one. 45 | fn my_usize_fn(x: usize) -> usize { 46 | println!("my usize: {}", x); 47 | x + 1 48 | } 49 | 50 | fn main() { 51 | println!("calling all 'test' label"); 52 | // using iter you can go through all functions with this annotation. 53 | for i in test::iter() { 54 | i(); 55 | } 56 | 57 | println!("calling all 'test1' label"); 58 | for i in folder::child::test1::iter() { 59 | i(); 60 | } 61 | 62 | println!("calling all 'usize' label"); 63 | for i in test2::iter() { 64 | println!("{}", i(3)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: label 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | 13 | - name: Install nightly toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: nightly 18 | override: true 19 | 20 | - name: Run cargo check 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: check 24 | 25 | test: 26 | name: Test Suite 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout sources 30 | uses: actions/checkout@v2 31 | 32 | - name: Install nightly toolchain 33 | uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: nightly 37 | override: true 38 | 39 | - name: Run cargo test 40 | uses: actions-rs/cargo@v1 41 | with: 42 | command: test 43 | 44 | lints: 45 | name: Lints 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Checkout sources 49 | uses: actions/checkout@v2 50 | 51 | - name: Install nightly toolchain 52 | uses: actions-rs/toolchain@v1 53 | with: 54 | profile: minimal 55 | toolchain: nightly 56 | override: true 57 | components: rustfmt, clippy 58 | 59 | - name: Run cargo fmt 60 | uses: actions-rs/cargo@v1 61 | with: 62 | command: fmt 63 | args: --all -- --check 64 | 65 | - name: Run cargo clippy 66 | uses: actions-rs/cargo@v1 67 | with: 68 | command: clippy 69 | args: -- -D warnings 70 | 71 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Create Release 8 | 9 | jobs: 10 | build: 11 | name: Create Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@master 16 | 17 | - name: Install latest nightly 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | toolchain: nightly 21 | override: true 22 | 23 | - name: Set env 24 | run: | 25 | echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/} 26 | echo ::set-env name=BRANCH::${GITHUB_REF#refs/heads/} 27 | 28 | - name: Publish 29 | env: 30 | CARGO_DEPLOY_TOKEN: ${{ secrets.CARGO_DEPLOY_TOKEN }} 31 | run: | 32 | git config --global user.name "github-actions[bot]" 33 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 34 | 35 | git checkout -b "$BRANCH-update" 36 | 37 | echo $RELEASE_VERSION 38 | export RELEASE_VERSION=$(echo $RELEASE_VERSION | cut -b 1 --complement) 39 | 40 | awk 'NR==1,/claudio/{sub(/version += +".*"/, "version = RELEASE_VERSION")} 1' label/Cargo.toml | sed "s/RELEASE_VERSION/\"$RELEASE_VERSION\"/g" > label/Cargo1.toml 41 | mv label/Cargo1.toml label/Cargo.toml 42 | 43 | awk 'NR==1,/claudio/{sub(/version += +".*"/, "version = RELEASE_VERSION")} 1' label-macros/Cargo.toml | sed "s/RELEASE_VERSION/\"$RELEASE_VERSION\"/g" > label-macros/Cargo1.toml 44 | mv label-macros/Cargo1.toml label-macros/Cargo.toml 45 | 46 | sed -i "s/path *= *\"..\/label-macros\", *version *= *\".*\"/path=\"..\/label-macros\", version=\"$RELEASE_VERSION\"/" label/Cargo.toml 47 | cat label/Cargo.toml 48 | cat label-macros/Cargo.toml 49 | 50 | git add label/Cargo.toml label-macros/Cargo.toml 51 | git commit -m "Updated to version $RELEASE_VERSION" 52 | 53 | cargo login "$CARGO_DEPLOY_TOKEN" 54 | cd label-macros && cargo +nightly publish 55 | sleep 30 56 | cd ../label && cargo +nightly publish 57 | 58 | git push origin "$BRANCH-update" 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/vim,intellij+all,rust 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=vim,intellij+all,rust 4 | 5 | ### Intellij+all ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/artifacts 37 | # .idea/compiler.xml 38 | # .idea/jarRepositories.xml 39 | # .idea/modules.xml 40 | # .idea/*.iml 41 | # .idea/modules 42 | # *.iml 43 | # *.ipr 44 | 45 | # CMake 46 | cmake-build-*/ 47 | 48 | # Mongo Explorer plugin 49 | .idea/**/mongoSettings.xml 50 | 51 | # File-based project format 52 | *.iws 53 | 54 | # IntelliJ 55 | out/ 56 | 57 | # mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | # JIRA plugin 61 | atlassian-ide-plugin.xml 62 | 63 | # Cursive Clojure plugin 64 | .idea/replstate.xml 65 | 66 | # Crashlytics plugin (for Android Studio and IntelliJ) 67 | com_crashlytics_export_strings.xml 68 | crashlytics.properties 69 | crashlytics-build.properties 70 | fabric.properties 71 | 72 | # Editor-based Rest Client 73 | .idea/httpRequests 74 | 75 | # Android studio 3.1+ serialized cache file 76 | .idea/caches/build_file_checksums.ser 77 | 78 | ### Intellij+all Patch ### 79 | # Ignores the whole .idea folder and all .iml files 80 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 81 | 82 | .idea/ 83 | 84 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 85 | 86 | *.iml 87 | modules.xml 88 | .idea/misc.xml 89 | *.ipr 90 | 91 | # Sonarlint plugin 92 | .idea/sonarlint 93 | 94 | ### Rust ### 95 | # Generated by Cargo 96 | # will have compiled files and executables 97 | /target/ 98 | 99 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 100 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 101 | Cargo.lock 102 | 103 | # These are backup files generated by rustfmt 104 | **/*.rs.bk 105 | 106 | ### Vim ### 107 | # Swap 108 | [._]*.s[a-v][a-z] 109 | !*.svg # comment out if you don't need vector files 110 | [._]*.sw[a-p] 111 | [._]s[a-rt-v][a-z] 112 | [._]ss[a-gi-z] 113 | [._]sw[a-p] 114 | 115 | # Session 116 | Session.vim 117 | Sessionx.vim 118 | 119 | # Temporary 120 | .netrwhist 121 | *~ 122 | # Auto-generated tag files 123 | tags 124 | # Persistent undo 125 | [._]*.un~ 126 | 127 | # End of https://www.toptal.com/developers/gitignore/api/vim,intellij+all,rust 128 | -------------------------------------------------------------------------------- /label/tests/main.rs: -------------------------------------------------------------------------------- 1 | use label::create_label; 2 | use std::any::Any; 3 | use std::collections::HashSet; 4 | 5 | // TODO: allow for creating multiple label in one create_annotation! macro. 6 | // Create two label. 7 | create_label!( 8 | // test that comments work here 9 | // V test that pub works (does nothing) 10 | pub(self) fn test() -> &'static str; 11 | fn test2(usize) -> usize; 12 | ); 13 | 14 | pub mod child { 15 | // annotate a function by giving the path to the annotation and postfixing ::annotate. 16 | #[super::test::label] 17 | fn my_fn() -> &'static str { 18 | "Test2!" 19 | } 20 | } 21 | 22 | pub mod folder { 23 | // multiple label living in any submodule or supermodule are possible. 24 | #[crate::test::label] 25 | #[child::test1::label] 26 | fn fn_four() -> &'static str { 27 | "Test4!" 28 | } 29 | 30 | pub mod child { 31 | use label::create_label; 32 | 33 | #[super::super::test::label] 34 | fn my_fn() -> &'static str { 35 | "Test3!" 36 | } 37 | 38 | create_label!(fn test1() -> &'static str); 39 | } 40 | } 41 | 42 | #[test::label] 43 | #[folder::child::test1::label] 44 | fn another_fn() -> &'static str { 45 | "Test1!" 46 | } 47 | 48 | #[test2::label] 49 | // label are typed, so functions annotated with test2 must take a usize and return one. 50 | fn my_usize_fn(x: usize) -> usize { 51 | x + 1 52 | } 53 | 54 | #[test] 55 | fn test_simple() { 56 | // using iter you can go through all functions with this annotation. 57 | let mut ret = HashSet::new(); 58 | for i in test::iter() { 59 | ret.insert(i()); 60 | } 61 | 62 | assert!(ret.contains("Test1!")); 63 | assert!(ret.contains("Test2!")); 64 | assert!(ret.contains("Test3!")); 65 | assert!(ret.contains("Test4!")); 66 | } 67 | 68 | #[test] 69 | fn test_call_normal() { 70 | // Test to see if calling an annotated function normally still works 71 | assert_eq!(my_usize_fn(2), 3); 72 | } 73 | 74 | #[test] 75 | fn test_label_in_module() { 76 | let mut ret = HashSet::new(); 77 | 78 | for i in folder::child::test1::iter() { 79 | ret.insert(i()); 80 | } 81 | 82 | assert!(ret.contains("Test1!")); 83 | assert!(ret.contains("Test4!")); 84 | } 85 | 86 | #[test] 87 | fn test_add_one() { 88 | for i in test2::iter() { 89 | assert_eq!(i(3), 4); 90 | } 91 | } 92 | 93 | #[test] 94 | fn test_simple_named() { 95 | // using iter you can go through all functions with this annotation. 96 | let mut ret = HashSet::new(); 97 | for (name, i) in test::iter_named() { 98 | ret.insert((name, i())); 99 | } 100 | 101 | assert!(ret.contains(&("another_fn", "Test1!"))); 102 | assert!(ret.contains(&("my_fn", "Test2!"))); 103 | assert!(ret.contains(&("my_fn", "Test3!"))); 104 | assert!(ret.contains(&("fn_four", "Test4!"))); 105 | } 106 | 107 | pub struct Test<'a, 'b> { 108 | a: &'a usize, 109 | _b: &'b usize, 110 | } 111 | 112 | create_label!( 113 | fn with_lifetime<'a, 'b>(Test<'a, 'b>)-> &'a usize 114 | ); 115 | 116 | #[with_lifetime::label] 117 | fn fn_test_with_lifetimes<'a, 'b>(val: Test<'a, 'b>) -> &'a usize { 118 | val.a 119 | } 120 | 121 | #[test] 122 | fn test_with_generics() { 123 | for i in with_lifetime::iter() { 124 | assert_eq!(i(Test { a: &10, _b: &15 }), &10); 125 | } 126 | } 127 | 128 | #[test] 129 | fn test_traits() { 130 | fn implements(_value: T) {} 131 | 132 | for i in with_lifetime::iter() { 133 | implements(i); 134 | } 135 | for i in test::iter() { 136 | implements(i); 137 | } 138 | for i in test2::iter() { 139 | implements(i); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /label-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Let's hope this is stabilized soon. There is some activity on it. https://github.com/rust-lang/rust/issues/54725 2 | #![feature(proc_macro_span)] 3 | //! # Label 4 | //! 5 | //! `label` is a library that can be used to create custom attributes for functions, through which you can list them and perform actions on them. 6 | //! 7 | //! For more documentation, refer to [https://docs.rs/label](https://docs.rs/label). 8 | //! 9 | 10 | extern crate proc_macro; 11 | 12 | use proc_macro::{Span, TokenStream}; 13 | use quote::quote; 14 | use quote::ToTokens; 15 | use syn::parse::discouraged::Speculative; 16 | use syn::parse::{Parse, ParseStream, Result}; 17 | use syn::punctuated::Punctuated; 18 | use syn::spanned::Spanned; 19 | 20 | struct ParsableAttribute { 21 | pub attributes: Vec, 22 | } 23 | 24 | impl Parse for ParsableAttribute { 25 | fn parse(input: ParseStream) -> Result { 26 | Ok(ParsableAttribute { 27 | attributes: input.call(syn::Attribute::parse_outer)?, 28 | }) 29 | } 30 | } 31 | 32 | fn simplify_path(mut path: syn::Path) -> syn::Path { 33 | // replace ::annotate with ::add in the path. 34 | // It would be cleaner to remove ::annotate entirely, but couldn't find 35 | // a way to do that. .pop() retains the ::. 36 | if let Some(i) = path.segments.last_mut() { 37 | assert_eq!(i.ident.to_string(), "label"); 38 | let new_ident = syn::Ident::new("add", i.span()); 39 | i.ident = new_ident; 40 | } 41 | 42 | path 43 | } 44 | 45 | enum Item { 46 | Func(syn::ItemFn), 47 | Static(syn::ItemStatic), 48 | Const(syn::ItemConst), 49 | } 50 | 51 | impl Item { 52 | pub fn name(&self) -> &syn::Ident { 53 | match self { 54 | Item::Func(i) => &i.sig.ident, 55 | Item::Static(i) => &i.ident, 56 | Item::Const(i) => &i.ident, 57 | } 58 | } 59 | 60 | pub fn attrs(&self) -> Vec { 61 | match self { 62 | Item::Func(i) => i.attrs.clone(), 63 | Item::Static(i) => i.attrs.clone(), 64 | Item::Const(i) => i.attrs.clone(), 65 | } 66 | } 67 | 68 | pub fn set_attrs(&mut self, attrs: Vec) { 69 | match self { 70 | Item::Func(i) => i.attrs = attrs, 71 | Item::Static(i) => i.attrs = attrs, 72 | Item::Const(i) => i.attrs = attrs, 73 | } 74 | } 75 | } 76 | 77 | impl ToTokens for Item { 78 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 79 | match self { 80 | Item::Func(i) => i.to_tokens(tokens), 81 | Item::Static(i) => i.to_tokens(tokens), 82 | Item::Const(i) => i.to_tokens(tokens), 83 | } 84 | } 85 | } 86 | 87 | impl Parse for Item { 88 | fn parse(input: ParseStream) -> Result { 89 | let tokens = input.fork(); 90 | if let Ok(i) = tokens.parse() { 91 | input.advance_to(&tokens); 92 | return Ok(Item::Func(i)); 93 | } 94 | 95 | let tokens = input.fork(); 96 | if let Ok(i) = tokens.parse() { 97 | input.advance_to(&tokens); 98 | return Ok(Item::Static(i)); 99 | } 100 | 101 | let tokens = input.fork(); 102 | if let Ok(i) = tokens.parse() { 103 | input.advance_to(&tokens); 104 | return Ok(Item::Const(i)); 105 | } 106 | 107 | Err(input.error("Expected either function definition, static variable or const variable.")) 108 | } 109 | } 110 | 111 | #[proc_macro_attribute] 112 | #[doc(hidden)] 113 | /// DO NOT USE DIRECTLY! USE THROUGH CREATE_ANNOTATION 114 | pub fn __label(_attr: TokenStream, item: TokenStream) -> TokenStream { 115 | let mut item = syn::parse_macro_input!(item as Item); 116 | 117 | // other annotation attributes 118 | let mut other_annotations = Vec::new(); 119 | // any other attribute present 120 | let mut other_attrs = Vec::new(); 121 | for i in item.attrs() { 122 | if let Some(ref lst) = i.path.segments.last() { 123 | if &*lst.ident.to_string() == "label" { 124 | other_annotations.push(simplify_path(i.path)); 125 | continue; 126 | } 127 | } 128 | other_attrs.push(i); 129 | } 130 | 131 | // remove all label from the function's attributes 132 | // but keep other attributes 133 | item.set_attrs(other_attrs); 134 | 135 | let item_name = item.name(); 136 | 137 | // for the following, the feature 138 | // #![feature(proc_macro_quote)] 139 | // could be used together with syn and proc_macro::quote_span. 140 | // However, this feature does not look like it will stabilize any time soon. 141 | // Therefore a regex is in my opinion currently better suited. 142 | let spans = { 143 | let mut current = Span::call_site(); 144 | let mut possible = vec![current]; 145 | 146 | while let Some(new) = current.parent() { 147 | current = new; 148 | possible.push(new); 149 | } 150 | 151 | possible 152 | }; 153 | 154 | let mut res = None; 155 | for span in spans 156 | .iter() 157 | .rev() 158 | .map(|i| i.source_text()) 159 | .filter_map(|i| i) 160 | { 161 | if let Ok(i) = syn::parse_str::(&span) { 162 | res = Some(i); 163 | break; 164 | } 165 | } 166 | 167 | let path: syn::Path = if let Some(res) = res { 168 | res.attributes[0].path.clone() 169 | } else { 170 | unreachable!() 171 | }; 172 | 173 | let callpath = simplify_path(path); 174 | let item_name_str = format!("{}", item_name); 175 | 176 | let item_quote = match &item { 177 | Item::Func(_) => quote! { 178 | #item_name 179 | }, 180 | Item::Static(_) => { 181 | quote! { 182 | &#item_name 183 | } 184 | } 185 | Item::Const(_) => quote! { 186 | &#item_name 187 | }, 188 | }; 189 | 190 | let result = quote! { 191 | #item 192 | 193 | #[allow(non_snake_case)] 194 | // This uses: https://github.com/rust-lang/rust/issues/54912 to make anonymous modules. 195 | // Anonymous modules use the parent scope meaning no more imports of `super::*` are needed 196 | const _: () = { 197 | use label::ctor; 198 | 199 | #[ctor] 200 | fn create () { 201 | // Safety: This is unsafe because sometimes I use mut statics here. However, I'm only giving out pointers 202 | // to them for which I make sure you can't use them without an unsafe block where they are used. 203 | unsafe { 204 | // register for all label it should be registered for 205 | #callpath::__add_label(#item_name_str, #item_quote); 206 | 207 | #(#other_annotations ::__add_label(#item_name_str, #item_quote);)* 208 | } 209 | } 210 | }; 211 | }; 212 | 213 | result.into() 214 | } 215 | 216 | struct Definitions { 217 | signatures: Punctuated, 218 | } 219 | 220 | impl Parse for Definitions { 221 | fn parse(input: ParseStream) -> Result { 222 | Ok(Self { 223 | signatures: input.parse_terminated::<_, syn::Token![;]>(Definition::parse)?, 224 | }) 225 | } 226 | } 227 | 228 | enum Definition { 229 | Function { 230 | name: syn::Ident, 231 | params: syn::punctuated::Punctuated, 232 | generics: syn::Generics, 233 | returntype: syn::ReturnType, 234 | }, 235 | Static { 236 | name: syn::Ident, 237 | var_type: syn::Type, 238 | }, 239 | } 240 | 241 | impl Parse for Definition { 242 | fn parse(input: ParseStream) -> Result { 243 | let _ = input.parse::(); 244 | 245 | if input.peek(syn::Token![fn]) { 246 | input.parse::()?; 247 | 248 | let name = input.parse::()?; 249 | let before = input.fork(); 250 | 251 | let generics: syn::Generics = input.parse()?; 252 | 253 | if generics.type_params().next().is_some() { 254 | return Err( 255 | before.error("Labels can not have generic type parameters (only lifetimes).") 256 | ); 257 | } 258 | if generics.const_params().next().is_some() { 259 | return Err(before.error("Labels can not have const parameters (only lifetimes).")); 260 | } 261 | 262 | let content; 263 | syn::parenthesized!( 264 | content in input 265 | ); 266 | 267 | let params = content.parse_terminated::<_, syn::Token![,]>(syn::BareFnArg::parse)?; 268 | 269 | let returntype = input.parse::()?; 270 | 271 | Ok(Definition::Function { 272 | name, 273 | params, 274 | generics, 275 | returntype, 276 | }) 277 | } else if input.peek(syn::Token![static]) { 278 | input.parse::()?; 279 | 280 | let _ = input.parse::(); 281 | let name = input.parse::()?; 282 | 283 | input.parse::()?; 284 | 285 | let var_type: syn::Type = input.parse()?; 286 | 287 | Ok(Definition::Static { name, var_type }) 288 | } else if input.peek(syn::Token![const]) { 289 | input.parse::()?; 290 | 291 | let name = input.parse::()?; 292 | 293 | input.parse::()?; 294 | 295 | let var_type: syn::Type = input.parse()?; 296 | 297 | Ok(Definition::Static { name, var_type }) 298 | } else { 299 | Err(input 300 | .error("Expected either function definition, static variable or const variable.")) 301 | } 302 | } 303 | } 304 | 305 | #[proc_macro] 306 | /// Creates a new label. 307 | /// ``` 308 | /// create_label!(fn test() -> ()); 309 | /// ``` 310 | /// 311 | /// To use a label, add an attribute to a function in the following style: 312 | /// 313 | /// ``` 314 | /// #[test::label] 315 | /// fn my_function() { 316 | /// // contents 317 | /// } 318 | /// 319 | /// ``` 320 | /// 321 | /// `test` is the name of your label (this has to be a full path to it. Labels can be imported). 322 | /// The annotation has to end with `::label`, or otherwise it will not compile. 323 | /// 324 | /// 325 | /// It is possible to create multipe labels in one invocation of the `create_label!()` macro. The syntax for this is as follows: 326 | /// ``` 327 | /// create_label!( 328 | /// fn test() -> (); 329 | /// fn test1(usize) -> (usize); 330 | /// fn test2(usize) -> (isize); 331 | /// ); 332 | /// 333 | /// ``` 334 | /// 335 | /// It is not supported to have two labels in scope with the same name, just like two structs in the same scope with the same name won't work either. 336 | /// 337 | ///1 338 | /// After a label is created, it is possible to iterate over all functions annotated with this label, using the iter function: 339 | /// 340 | /// ``` 341 | /// for func in test::iter() { 342 | /// // do something with the function 343 | /// func(); 344 | /// } 345 | /// 346 | /// ``` 347 | /// 348 | /// The order in which iteration occurs is *not* defined. 349 | /// 350 | /// Alternatively, you can iterate over functions and their names using the `iter_named()` function: 351 | /// 352 | /// ``` 353 | /// for (name, func) in test::iter_named() { 354 | /// println!("name: {}", name); 355 | /// 356 | /// // do something with the function 357 | /// func(); 358 | /// } 359 | /// 360 | /// ``` 361 | /// 362 | /// Labels can also be given to `static` or `const` variables. Iterating over such labeled variables 363 | /// returns an `&'static` reference to the variable. You can define variable labels with 364 | /// `create_label!()`. It does not matter if you use `const` or `static`, they are handled the same. 365 | /// `static mut` is supported, though iterating over labels will *never* allow you to mutate these 366 | /// variables. `static mut` in `create_label!()` does nothing. If a `static mut` is locally updated, 367 | /// and the label is iterated over, the changed value is reflected. 368 | /// 369 | /// ``` 370 | /// create_label!( 371 | /// const name: usize; 372 | /// static other_name: usize; 373 | /// ); 374 | /// ``` 375 | /// 376 | /// ``` 377 | /// for i in name::iter() { 378 | /// println!("value: {}", *i); 379 | /// } 380 | /// ``` 381 | /// 382 | /// 383 | pub fn create_label(signatures: TokenStream) -> TokenStream { 384 | let labels = syn::parse_macro_input!(signatures as Definitions) 385 | .signatures 386 | .iter() 387 | .map(|definition| { 388 | let (signature, name) = match definition { 389 | Definition::Function { 390 | name, 391 | generics, 392 | params, 393 | returntype, 394 | } => { 395 | let lifetimes = generics.lifetimes(); 396 | 397 | ( 398 | quote! { 399 | for <#(#lifetimes),*> fn(#params) #returntype 400 | }, 401 | name, 402 | ) 403 | } 404 | Definition::Static { name, var_type } => ( 405 | quote! { 406 | &'static #var_type 407 | }, 408 | name, 409 | ), 410 | }; 411 | 412 | quote! { 413 | #[allow(non_snake_case)] 414 | pub mod #name { 415 | use super::*; 416 | 417 | pub use std::collections::HashMap; 418 | pub use label::__label as label; 419 | 420 | pub static mut FUNCTIONS: Option> = None; 421 | 422 | pub fn iter() -> impl Iterator { 423 | // Safety: after FUNCTIONS is populated (before main is called), 424 | // FUNCTIONS remains unchanged for the entire rest of the program. 425 | 426 | unsafe{ 427 | FUNCTIONS.iter().flat_map(|i| i.iter().map(|i| &i.1)).cloned() 428 | } 429 | } 430 | 431 | pub fn iter_named() -> impl Iterator { 432 | // Safety: after FUNCTIONS is populated (before main is called), 433 | // FUNCTIONS remains unchanged for the entire rest of the program. 434 | unsafe{ 435 | FUNCTIONS.iter().flat_map(|i| i).cloned() 436 | } 437 | } 438 | 439 | pub mod add { 440 | use super::*; 441 | // WARNING: DO NOT CALL. THIS HAS TO BE PUBLIC FOR OTHER 442 | // PARTS OF THE LIBRARY TO WORK BUT SHOULD NEVER BE USED. 443 | pub fn __add_label(name: &'static str, func: #signature) { 444 | unsafe { 445 | if let Some(f) = &mut FUNCTIONS { 446 | f.push((name, func)); 447 | } else { 448 | FUNCTIONS = Some(vec![(name, func)]) 449 | } 450 | } 451 | } 452 | } 453 | } 454 | } 455 | }) 456 | .collect::>(); 457 | 458 | let result = quote! { 459 | #(#labels)* 460 | }; 461 | 462 | result.into() 463 | } 464 | --------------------------------------------------------------------------------