├── .gitignore ├── ide_support.png ├── .github ├── ISSUE_TEMPLATE │ ├── enhancement.md │ └── bug_report.yml └── workflows │ └── ci.yml ├── rust-toolchain.toml ├── tests ├── ui │ ├── no_macro.stderr │ ├── reserved.rs │ ├── no_macro.rs │ ├── no_mod_int.rs │ ├── no_custom_literals_module.rs │ ├── no_mod_int.stderr │ ├── no_custom_literals_module.stderr │ ├── error_from_inside.rs │ ├── reserved.stderr │ └── error_from_inside.stderr └── id.rs ├── Cargo.toml ├── LICENSE-MIT ├── flake.lock ├── flake.nix ├── CHANGELOG.md ├── Cargo.lock ├── README.md ├── LICENSE-APACHE └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /ide_support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nik-rev/culit/HEAD/ide_support.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement 3 | about: Suggest an improvement 4 | labels: C-enhancement 5 | --- 6 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | toolchain.channel = "nightly-2025-11-05" 2 | toolchain.components = [ 3 | "rust-analyzer", 4 | "cargo", 5 | "rustfmt", 6 | "clippy", 7 | "rustc", 8 | "rust-src" 9 | ] 10 | -------------------------------------------------------------------------------- /tests/ui/no_macro.stderr: -------------------------------------------------------------------------------- 1 | error[E0433]: failed to resolve: could not find `km` in `integer` 2 | --> tests/ui/no_macro.rs:11:13 3 | | 4 | 11 | let a = 10km; 5 | | ^^^^ could not find `km` in `integer` 6 | -------------------------------------------------------------------------------- /tests/ui/reserved.rs: -------------------------------------------------------------------------------- 1 | //! Usage of reserved suffixes 2 | 3 | use culit::culit; 4 | 5 | #[culit] 6 | fn float() { 7 | 70.0f16; 8 | 70.0f128; 9 | } 10 | 11 | #[culit] 12 | fn int() { 13 | 100i256; 14 | 70.0u256; 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /tests/ui/no_macro.rs: -------------------------------------------------------------------------------- 1 | //! `custom_literal::int::km` does not exist, I expect this to be an error directly on `10km` 2 | 3 | use culit::culit; 4 | 5 | mod custom_literal { 6 | pub mod integer {} 7 | } 8 | 9 | #[culit] 10 | fn main() { 11 | let a = 10km; 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/no_mod_int.rs: -------------------------------------------------------------------------------- 1 | //! `custom_literal::int` does not exist. This is a fundamental error not related to any integer, 2 | //! so I expect the span to be at `#[culit]` 3 | 4 | use culit::culit; 5 | 6 | mod custom_literal {} 7 | 8 | #[culit] 9 | fn main() { 10 | let a = 10km; 11 | } 12 | -------------------------------------------------------------------------------- /tests/ui/no_custom_literals_module.rs: -------------------------------------------------------------------------------- 1 | //! No `custom_literal` module in the crate root. 2 | //! I expect span to be at `#[culit]` because it's a fundamental error, 3 | //! unrelated to any integer in particular 4 | 5 | use culit::culit; 6 | 7 | #[culit] 8 | fn main() { 9 | let a = 10km; 10 | } 11 | -------------------------------------------------------------------------------- /tests/ui/no_mod_int.stderr: -------------------------------------------------------------------------------- 1 | error[E0433]: failed to resolve: could not find `integer` in `custom_literal` 2 | --> tests/ui/no_mod_int.rs:8:1 3 | | 4 | 8 | #[culit] 5 | | ^^^^^^^^ could not find `integer` in `custom_literal` 6 | | 7 | = note: this error originates in the attribute macro `culit` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/ui/no_custom_literals_module.stderr: -------------------------------------------------------------------------------- 1 | error[E0433]: failed to resolve: could not find `custom_literal` in the crate root 2 | --> tests/ui/no_custom_literals_module.rs:7:1 3 | | 4 | 7 | #[culit] 5 | | ^^^^^^^^ could not find `custom_literal` in the crate root 6 | | 7 | = note: this error originates in the attribute macro `culit` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "culit" 3 | version = "0.6.1" 4 | edition = "2021" 5 | description = "Custom literals" 6 | rust-version = "1.79" 7 | authors = ["Nik Revenco "] 8 | repository = "https://github.com/nik-rev/culit" 9 | keywords = ["custom", "literal", "proc-macro", "math", "science"] 10 | categories = ["science", "rust-patterns", "development-tools", "mathematics", "embedded"] 11 | license = "MIT OR Apache-2.0" 12 | 13 | [dependencies] 14 | # std doesn't provide us a way to get contents of a `proc_macro::Literal` 15 | litrs = "0.5" 16 | 17 | [dev-dependencies] 18 | trybuild = "1.0" 19 | 20 | [lib] 21 | proc-macro = true 22 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 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. 24 | -------------------------------------------------------------------------------- /tests/ui/error_from_inside.rs: -------------------------------------------------------------------------------- 1 | //! Each literal emits a type mismatch error. 2 | //! I expect the span of this error to be exactly at the literal 3 | 4 | use culit::culit; 5 | 6 | struct Kilometer(u32); 7 | 8 | #[culit] 9 | fn main() { 10 | 0w; 11 | 0.0w; 12 | 'x'w; 13 | b'x'w; 14 | "x"w; 15 | b"x"w; 16 | c"x"w; 17 | } 18 | 19 | mod custom_literal { 20 | macro_rules! w { 21 | ($value:literal $($tt:tt)*) => {{ 22 | let it: () = $value; 23 | it 24 | }}; 25 | } 26 | pub(crate) use w; 27 | 28 | pub mod integer { 29 | pub(crate) use super::w; 30 | } 31 | 32 | pub mod float { 33 | pub(crate) use super::w; 34 | } 35 | 36 | pub mod string { 37 | pub(crate) use super::w; 38 | } 39 | 40 | pub mod character { 41 | pub(crate) use super::w; 42 | } 43 | 44 | pub mod byte_character { 45 | pub(crate) use super::w; 46 | } 47 | 48 | pub mod byte_string { 49 | pub(crate) use super::w; 50 | } 51 | 52 | pub mod c_string { 53 | pub(crate) use super::w; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/ui/reserved.stderr: -------------------------------------------------------------------------------- 1 | error: suffix f16 is not currently used by rust, but it likely will be in the future. To avoid breakage and not compromise rust's compatibility guarantees, we forbid this suffix 2 | --> tests/ui/reserved.rs:7:5 3 | | 4 | 7 | 70.0f16; 5 | | ^^^^^^^ 6 | 7 | error: suffix f128 is not currently used by rust, but it likely will be in the future. To avoid breakage and not compromise rust's compatibility guarantees, we forbid this suffix 8 | --> tests/ui/reserved.rs:8:5 9 | | 10 | 8 | 70.0f128; 11 | | ^^^^^^^^ 12 | 13 | error: suffix i256 is not currently used by rust, but it likely will be in the future. To avoid breakage and not compromise rust's compatibility guarantees, we forbid this suffix 14 | --> tests/ui/reserved.rs:13:5 15 | | 16 | 13 | 100i256; 17 | | ^^^^^^^ 18 | 19 | error[E0433]: failed to resolve: could not find `custom_literal` in the crate root 20 | --> tests/ui/reserved.rs:11:1 21 | | 22 | 11 | #[culit] 23 | | ^^^^^^^^ could not find `custom_literal` in the crate root 24 | | 25 | = note: this error originates in the attribute macro `culit` (in Nightly builds, run with -Z macro-backtrace for more info) 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | labels: bug 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: Thank you for filing a bug report! 🐛 8 | 9 | - type: textarea 10 | id: summary 11 | attributes: 12 | label: Summary 13 | description: Please provide a short summary of the bug 14 | validations: 15 | required: true 16 | 17 | - type: textarea 18 | id: reproduction-steps 19 | attributes: 20 | label: Reproduction Steps 21 | description: Steps to replicate the bug 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | id: expected-outcome 27 | attributes: 28 | label: Expected Outcome 29 | description: What you expected to happen 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | id: actual-outcome 35 | attributes: 36 | label: Actual Outcome 37 | description: What actually happened 38 | validations: 39 | required: true 40 | 41 | - type: textarea 42 | id: extra-info 43 | attributes: 44 | label: Additional information 45 | description: Please provide any additional information that could be useful 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | RUST_BACKTRACE: 1 10 | CARGO_TERM_COLOR: always 11 | CLICOLOR: 1 12 | 13 | jobs: 14 | cargo-check-msrv: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v5 18 | - uses: actions-rust-lang/setup-rust-toolchain@v1 19 | with: 20 | toolchain: "1.79" 21 | 22 | - run: cargo check 23 | 24 | cargo-clippy: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v5 28 | - uses: actions-rust-lang/setup-rust-toolchain@v1 29 | 30 | - run: cargo clippy --workspace --all-targets -- -D warnings 31 | 32 | cargo-fmt: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v5 36 | - uses: actions-rust-lang/setup-rust-toolchain@v1 37 | 38 | - run: cargo fmt --check 39 | 40 | cargo-test: 41 | runs-on: ${{ matrix.os }} 42 | strategy: 43 | matrix: 44 | os: [ubuntu-latest, windows-latest, macos-latest] 45 | # if one OS fails, still test the other ones 46 | continue-on-error: true 47 | steps: 48 | - uses: actions/checkout@v5 49 | - uses: actions-rust-lang/setup-rust-toolchain@v1 50 | 51 | - run: cargo test --workspace 52 | -------------------------------------------------------------------------------- /tests/ui/error_from_inside.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/ui/error_from_inside.rs:8:1 3 | | 4 | 8 | #[culit] 5 | | ^^^^^^^^ expected `()`, found integer 6 | ... 7 | 22 | let it: () = $value; 8 | | -- expected due to this 9 | | 10 | = note: this error originates in the attribute macro `culit` (in Nightly builds, run with -Z macro-backtrace for more info) 11 | 12 | error[E0308]: mismatched types 13 | --> tests/ui/error_from_inside.rs:8:1 14 | | 15 | 8 | #[culit] 16 | | ^^^^^^^^ expected `()`, found floating-point number 17 | ... 18 | 22 | let it: () = $value; 19 | | -- expected due to this 20 | | 21 | = note: this error originates in the attribute macro `culit` (in Nightly builds, run with -Z macro-backtrace for more info) 22 | 23 | error[E0308]: mismatched types 24 | --> tests/ui/error_from_inside.rs:12:5 25 | | 26 | 12 | 'x'w; 27 | | ^^^^ expected `()`, found `char` 28 | ... 29 | 22 | let it: () = $value; 30 | | -- expected due to this 31 | 32 | error[E0308]: mismatched types 33 | --> tests/ui/error_from_inside.rs:13:5 34 | | 35 | 13 | b'x'w; 36 | | ^^^^^ expected `()`, found `u8` 37 | ... 38 | 22 | let it: () = $value; 39 | | -- expected due to this 40 | 41 | error[E0308]: mismatched types 42 | --> tests/ui/error_from_inside.rs:14:5 43 | | 44 | 14 | "x"w; 45 | | ^^^^ expected `()`, found `&str` 46 | ... 47 | 22 | let it: () = $value; 48 | | -- expected due to this 49 | 50 | error[E0308]: mismatched types 51 | --> tests/ui/error_from_inside.rs:15:5 52 | | 53 | 15 | b"x"w; 54 | | ^^^^^ expected `()`, found `&[u8; 1]` 55 | ... 56 | 22 | let it: () = $value; 57 | | -- expected due to this 58 | 59 | error[E0308]: mismatched types 60 | --> tests/ui/error_from_inside.rs:16:5 61 | | 62 | 16 | c"x"w; 63 | | ^^^^^ expected `()`, found `&CStr` 64 | ... 65 | 22 | let it: () = $value; 66 | | -- expected due to this 67 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "crane": { 4 | "locked": { 5 | "lastModified": 1755993354, 6 | "narHash": "sha256-FCRRAzSaL/+umLIm3RU3O/+fJ2ssaPHseI2SSFL8yZU=", 7 | "owner": "ipetkov", 8 | "repo": "crane", 9 | "rev": "25bd41b24426c7734278c2ff02e53258851db914", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "ipetkov", 14 | "repo": "crane", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1761114652, 21 | "narHash": "sha256-f/QCJM/YhrV/lavyCVz8iU3rlZun6d+dAiC3H+CDle4=", 22 | "owner": "NixOS", 23 | "repo": "nixpkgs", 24 | "rev": "01f116e4df6a15f4ccdffb1bcd41096869fb385c", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "NixOS", 29 | "ref": "nixos-unstable", 30 | "repo": "nixpkgs", 31 | "type": "github" 32 | } 33 | }, 34 | "root": { 35 | "inputs": { 36 | "nixpkgs": "nixpkgs", 37 | "rust-overlay": "rust-overlay", 38 | "wild-linker-overlay": "wild-linker-overlay" 39 | } 40 | }, 41 | "rust-overlay": { 42 | "inputs": { 43 | "nixpkgs": [ 44 | "nixpkgs" 45 | ] 46 | }, 47 | "locked": { 48 | "lastModified": 1761186948, 49 | "narHash": "sha256-mylLEdzqx4M9TrwFIPc+o0dqabHvFGbNJZGRgE43Ya0=", 50 | "owner": "oxalica", 51 | "repo": "rust-overlay", 52 | "rev": "1d1c8a009adeb4c0561bb1feea8ec7b97749946b", 53 | "type": "github" 54 | }, 55 | "original": { 56 | "owner": "oxalica", 57 | "repo": "rust-overlay", 58 | "type": "github" 59 | } 60 | }, 61 | "wild-linker-overlay": { 62 | "inputs": { 63 | "crane": "crane", 64 | "nixpkgs": [ 65 | "nixpkgs" 66 | ] 67 | }, 68 | "locked": { 69 | "lastModified": 1761077828, 70 | "narHash": "sha256-lHuLWNuq4rLVSQ0WfdyUnxzsG7wVx7Z5jDkWfpuIr9w=", 71 | "owner": "davidlattimore", 72 | "repo": "wild", 73 | "rev": "802f7984ddcac7df8124b8c8e2b9149f0887f565", 74 | "type": "github" 75 | }, 76 | "original": { 77 | "owner": "davidlattimore", 78 | "repo": "wild", 79 | "type": "github" 80 | } 81 | } 82 | }, 83 | "root": "root", 84 | "version": 7 85 | } 86 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 | rust-overlay = { 5 | url = "github:oxalica/rust-overlay"; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | wild-linker-overlay = { 9 | url = "github:davidlattimore/wild"; 10 | inputs.nixpkgs.follows = "nixpkgs"; 11 | }; 12 | }; 13 | 14 | outputs = 15 | { 16 | nixpkgs, 17 | rust-overlay, 18 | wild-linker-overlay, 19 | ... 20 | }: 21 | let 22 | supportedSystems = [ 23 | "x86_64-linux" 24 | "aarch64-linux" 25 | "x86_64-darwin" 26 | "aarch64-darwin" 27 | ]; 28 | 29 | forEachSystem = 30 | f: 31 | nixpkgs.lib.genAttrs supportedSystems ( 32 | system: 33 | let 34 | overlays = [ 35 | (import rust-overlay) 36 | (import wild-linker-overlay) 37 | ]; 38 | 39 | pkgs = import nixpkgs { 40 | inherit system overlays; 41 | }; 42 | 43 | rust = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; 44 | 45 | wild-linker = pkgs.useWildLinker pkgs.stdenv; 46 | 47 | inputs = [ 48 | rust 49 | ]; 50 | in 51 | f { 52 | inherit 53 | pkgs 54 | rust 55 | wild-linker 56 | inputs 57 | ; 58 | } 59 | ); 60 | in 61 | { 62 | devShells = forEachSystem ( 63 | { 64 | pkgs, 65 | rust, 66 | wild-linker, 67 | inputs, 68 | ... 69 | }: 70 | { 71 | default = pkgs.mkShell.override { stdenv = wild-linker; } { 72 | nativeBuildInputs = inputs; 73 | LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath inputs; 74 | }; 75 | } 76 | ); 77 | 78 | packages = forEachSystem ( 79 | { pkgs, inputs, ... }: 80 | { 81 | default = 82 | let 83 | manifest = pkgs.lib.importTOML ./Cargo.toml; 84 | in 85 | pkgs.rustPlatform.buildRustPackage { 86 | pname = manifest.package.name; 87 | version = manifest.package.version; 88 | src = pkgs.lib.cleanSource ./.; 89 | cargoLock.lockFile = ./Cargo.lock; 90 | 91 | nativeBuildInputs = inputs; 92 | }; 93 | } 94 | ); 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.6.0 2 | 3 | The default invocation of `#[culit]` is identical to `#[culit(crate::custom_literal)]`, 4 | so it is now possible to override where we look for custom literals. For example, 5 | `#[culit(foo::bar)]` looks for literals in the `foo::bar` module. 6 | 7 | **Migration guide:** Replace usages of `#[culit(local)]` with `#[culit(custom_literal)]` 8 | 9 | # v0.5.0 10 | 11 | You can now use `#[culit(local)]` which will expand to literals at `custom_literal` (must be in scope), 12 | rather than `crate::custom_literal` 13 | 14 | ```rust 15 | #[culit(local)] 16 | fn kilometer() { 17 | assert_eq!(10.4km, Kilometer(10.4)) 18 | // expands to: custom_literal::km!(10.4) 19 | } 20 | 21 | #[culit] 22 | fn kilomile() { 23 | assert_eq!(10.4km, Kilomile(10.4)) 24 | // expands to: crate::custom_literal::km!(10.4) 25 | } 26 | ``` 27 | 28 | # v0.4.0 29 | 30 | Renamed `decimal` to `float` to avoid confusion. 31 | 32 | Even though it is not 100% correct (since floats refer to the IEEE formats, but our floats can have an arbitrary exponent, 33 | precision and size), it'd technically be more correct to call them "real"s. 34 | But Rust does not make this distinction, so in order to raise the minimum possible confusion let's just call them `float`s. 35 | 36 | # v0.3.0 37 | 38 | - Integers and decimals are passed as-is, without splitting them up. e.g: 39 | - `100.003e7km` expands to `crate::custom_literal::decimal::km!(100.003e7)` 40 | - `100km` expands to `crate::custom_literal::integer::km!(100)` 41 | - Byte character is also passed as-is instead of the number, e.g.: `b'a'ascii` expands to `crate::custom_literal::byte_character::ascii!(b'a')` 42 | - MSRV is now 1.79 43 | 44 | # v0.2.0 45 | 46 | Includes significant improvements to usability, specifically in defining custom integer and decimal literals. 47 | 48 | ## Renamed the modules that we expect at `crate::custom_literal` to be more descriptive 49 | 50 | |old|new| 51 | |---|---| 52 | |`int`|`integer`| 53 | |`float`|`decimal`| 54 | |`char`|`character`| 55 | |`byte_char`|`byte_character`| 56 | |`byte_str`|`byte_string`| 57 | |`str`|`string`| 58 | |`c_str`|`c_string`| 59 | 60 | We renamed from Float because float is too-specific to the format but decimal is a more general name for what we actually give you 61 | 62 | ## The signature of custom integer literal has changed. 63 | 64 | - No more base, we handle that for you. 65 | - No more strings, you get the actual number. 66 | 67 | ```rs 68 | 10km // crate::custom_literal::integer::km!(10) 69 | 0b10km // crate::custom_literal::integer::km!(3) 70 | -10km // -crate::custom_literal::integer::km!(10) 71 | ``` 72 | 73 | Limitation: The absolute value of the custom literal may not exceed `340_282_366_920_938_463_463_374_607_431_768_211_455` 74 | 75 | ## The signature of custom decimal literal has changed 76 | 77 | - No more strings. Fractional, integral and the exponent parts are now numbers. 78 | Exponent also contains the `-` sign 79 | 80 | ```rs 81 | 10.0km // crate::custom_literal::decimal::km!(10 0 1) 82 | 10e7km // crate::custom_literal::decimal::km!(10 0 7) 83 | 10e-7km // crate::custom_literal::decimal::km!(10 0 -7) 84 | -10e-7km // -crate::custom_literal::decimal::km!(10 0 -7) 85 | -10.4e7km // -crate::custom_literal::decimal::km!(10 4 7) 86 | ``` 87 | 88 | Limitation: Each of these may not exceed `340_282_366_920_938_463_463_374_607_431_768_211_455`: 89 | 90 | - Integral part (part before the decimal point) 91 | - Fractional part (part after the decimal point, before the exponent) 92 | - Exponent 93 | 94 | # v0.1.0 95 | 96 | Initial release 97 | -------------------------------------------------------------------------------- /tests/id.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unusual_byte_groupings, clippy::inconsistent_digit_grouping)] 2 | #![allow(clippy::zero_prefixed_literal)] 3 | #![allow(clippy::mixed_case_hex_literals)] 4 | 5 | use culit::culit; 6 | 7 | #[test] 8 | fn ui() { 9 | let trybuild = trybuild::TestCases::new(); 10 | trybuild.compile_fail("tests/ui/*.rs"); 11 | } 12 | 13 | #[test] 14 | #[culit] 15 | fn integer_literals() { 16 | // Binary no undescrore 17 | assert_eq!(0b1id, 1); 18 | assert_eq!(0b10id, 2); 19 | assert_eq!(0b100id, 4); 20 | assert_eq!(0b1111id, 15); 21 | 22 | // Binary with underscores 23 | assert_eq!(0b1_0_0id, 4); 24 | assert_eq!(0b_1_0_0id, 4); 25 | assert_eq!(0b1_111_000id, 120); 26 | assert_eq!(0b_1_111_000_id, 120); 27 | 28 | // Octal no underscore 29 | assert_eq!(0o7id, 7); 30 | assert_eq!(0o10id, 8); 31 | assert_eq!(0o100id, 64); 32 | assert_eq!(0o777id, 511); 33 | 34 | // Octal with underscores 35 | assert_eq!(0o1_0_0id, 64); 36 | assert_eq!(0o_1_0_0id, 64); 37 | assert_eq!(0o7_7_7id, 511); 38 | assert_eq!(0o_7_7_7_id, 511); 39 | 40 | // Float no underscore 41 | assert_eq!(0id, 0); 42 | assert_eq!(1id, 1); 43 | assert_eq!(10id, 10); 44 | assert_eq!(100id, 100); 45 | assert_eq!(999id, 999); 46 | 47 | // Float with underscores 48 | assert_eq!(1_0_0id, 100); 49 | assert_eq!(1_000id, 1000); 50 | assert_eq!(9_9_9id, 999); 51 | assert_eq!(1_000_000id, 1_000_000); 52 | assert_eq!(1_0_0_id, 100); 53 | 54 | // Float with leading zeros (still valid integer) 55 | assert_eq!(000id, 0); 56 | assert_eq!(0007id, 7); 57 | assert_eq!(0010id, 10); 58 | 59 | // Hexadecimal no undescore 60 | assert_eq!(0x1id, 1); 61 | assert_eq!(0x10id, 16); 62 | assert_eq!(0x100id, 256); 63 | assert_eq!(0xABCid, 0xABC); 64 | assert_eq!(0xabcid, 0xabc); 65 | assert_eq!(0xDEAD_BEEFid, 0xDEADBEEF); 66 | 67 | // Hexadecimal with underscores 68 | assert_eq!(0x1_0_0id, 256); 69 | assert_eq!(0x_1_0_0id, 256); 70 | assert_eq!(0xAB_CDid, 0xABCD); 71 | assert_eq!(0xA_B_CDid, 0xABCD); 72 | assert_eq!(0x_ABC_D_id, 0xABCD); 73 | 74 | // edge cases for 0 75 | assert_eq!(0b0id, 0); 76 | assert_eq!(0o0id, 0); 77 | assert_eq!(0x0id, 0); 78 | 79 | // Large numbers with separators 80 | assert_eq!(1_000_000_000id, 1_000_000_000); 81 | assert_eq!(0b1111_1111_1111_1111id, 0xFFFF); 82 | assert_eq!(0o7_7_7_7id, 0o7777); 83 | assert_eq!(0xFFFF_FFFFid, 0xFFFFFFFF); 84 | } 85 | 86 | #[test] 87 | #[culit] 88 | fn float() { 89 | // With fractional, no exponent 90 | assert_eq!(70.0id, 70.0); 91 | assert_eq!(70.8id, 70.8); 92 | assert_eq!(7_0.8id, 7_0.8); 93 | assert_eq!(7_0_.8id, 7_0_.8); 94 | 95 | // With exponent only 96 | assert_eq!(70e7id, 70e7); 97 | assert_eq!(70e-7id, 70e-7); 98 | assert_eq!(70e+7id, 70e+7); 99 | assert_eq!(7_0e7id, 7_0e7); 100 | assert_eq!(7_0_e7id, 7_0_e7); 101 | assert_eq!(7_0_e-7id, 7_0_e-7); 102 | 103 | // With fractional and exponent 104 | assert_eq!(70.8e7id, 70.8e7); 105 | assert_eq!(70.8e-7id, 70.8e-7); 106 | assert_eq!(70.8e+7id, 70.8e+7); 107 | assert_eq!(7_0.8e7id, 7_0.8e7); 108 | assert_eq!(7_0.8e-7id, 7_0.8e-7); 109 | assert_eq!(7_0.8e+7id, 7_0.8e+7); 110 | 111 | // Fractional only, with underscores 112 | assert_eq!(70.0id, 70.0); 113 | assert_eq!(70.000id, 70.000); 114 | assert_eq!(70.123id, 70.123); 115 | assert_eq!(7_0.1_2_3id, 7_0.1_2_3); 116 | assert_eq!(7_0_.0id, 7_0_.0); 117 | 118 | // Fractional + exponent, underscores everywhere 119 | assert_eq!(70.0e7id, 70.0e7); 120 | assert_eq!(70.0e-7id, 70.0e-7); 121 | assert_eq!(70.0e+7id, 70.0e+7); 122 | assert_eq!(70.123e7id, 70.123e7); 123 | assert_eq!(7_0.1_2_3e7id, 7_0.1_2_3e7); 124 | assert_eq!(7_0.1_2_3e-7id, 7_0.1_2_3e-7); 125 | assert_eq!(7_0_.1_2_3_e_7id, 7_0_.1_2_3_e_7); 126 | assert_eq!(7_0_.0_e_7id, 7_0_.0_e_7); 127 | assert_eq!(7_0_.0_e-7id, 7_0_.0_e-7); 128 | assert_eq!(7_0_.0_e+7id, 7_0_.0_e+7); 129 | 130 | // Small edge cases 131 | assert_eq!(0.0id, 0.0); 132 | assert_eq!(0e7id, 0e7); 133 | assert_eq!(0.007id, 0.007); 134 | assert_eq!(0.0e7id, 0.0e7); 135 | assert_eq!(0.123id, 0.123); 136 | assert_eq!(0.123e7id, 0.123e7); 137 | assert_eq!(0.123e-7id, 0.123e-7); 138 | } 139 | 140 | #[test] 141 | #[culit] 142 | fn str() { 143 | assert_eq!("foo"id, stringify!("foo")); 144 | assert_eq!("\nfoo"id, stringify!("\nfoo")); 145 | assert_eq!(r"foo"id, stringify!("foo")); 146 | assert_eq!(r#"foo"#id, stringify!("foo")); 147 | assert_eq!(r#"foo\"#id, stringify!("foo\\")); 148 | } 149 | 150 | #[test] 151 | #[culit] 152 | fn byte_char() { 153 | assert_eq!(b'a'id, b'a'); 154 | } 155 | 156 | #[test] 157 | #[culit] 158 | fn byte_str() { 159 | assert_eq!(b"hello"id, stringify!(b"hello")); 160 | assert_eq!(b"hell\\o"id, stringify!(b"hell\\o")); 161 | assert_eq!(br"hell\o"id, stringify!(b"hell\\o")); 162 | assert_eq!(br#"hello"#id, stringify!(b"hello")); 163 | } 164 | 165 | mod local { 166 | use culit::culit; 167 | 168 | mod local_custom_literal { 169 | pub mod character { 170 | macro_rules! id { 171 | ($value:literal) => { 172 | "a" 173 | }; 174 | } 175 | pub(crate) use id; 176 | } 177 | } 178 | 179 | #[test] 180 | #[culit] 181 | fn char() { 182 | assert_eq!('a'id, stringify!('a')); 183 | } 184 | 185 | #[culit(local_custom_literal)] 186 | #[test] 187 | fn char_local() { 188 | assert_eq!('a'id, "a"); 189 | } 190 | } 191 | 192 | #[test] 193 | #[culit] 194 | fn c_str() { 195 | assert_eq!(c"hello"id, stringify!(c"hello")); 196 | assert_eq!(c"hell\\o"id, stringify!(c"hell\\o")); 197 | assert_eq!(cr"hell\o"id, stringify!(c"hell\\o")); 198 | assert_eq!(cr#"hello"#id, stringify!(c"hello")); 199 | } 200 | 201 | mod custom_literal { 202 | // `id` for "Identity" 203 | macro_rules! id { 204 | ($($tt:tt)*) => { 205 | stringify!($($tt)*) 206 | }; 207 | } 208 | 209 | pub(crate) use id; 210 | 211 | pub mod integer { 212 | macro_rules! id { 213 | ($value:literal) => {{ 214 | $value as i64 215 | }}; 216 | } 217 | pub(crate) use id; 218 | } 219 | 220 | pub mod float { 221 | macro_rules! id { 222 | ($value:literal) => { 223 | $value as f32 224 | }; 225 | } 226 | pub(crate) use id; 227 | } 228 | 229 | pub mod string { 230 | pub(crate) use super::id; 231 | } 232 | 233 | pub mod character { 234 | pub(crate) use super::id; 235 | } 236 | 237 | pub mod byte_character { 238 | macro_rules! id { 239 | ($value:literal) => {{ 240 | $value 241 | }}; 242 | } 243 | pub(crate) use id; 244 | } 245 | 246 | pub mod byte_string { 247 | pub(crate) use super::id; 248 | } 249 | 250 | pub mod c_string { 251 | pub(crate) use super::id; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "culit" 7 | version = "0.6.1" 8 | dependencies = [ 9 | "litrs", 10 | "trybuild", 11 | ] 12 | 13 | [[package]] 14 | name = "equivalent" 15 | version = "1.0.2" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 18 | 19 | [[package]] 20 | name = "glob" 21 | version = "0.3.3" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 24 | 25 | [[package]] 26 | name = "hashbrown" 27 | version = "0.16.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 30 | 31 | [[package]] 32 | name = "indexmap" 33 | version = "2.11.4" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 36 | dependencies = [ 37 | "equivalent", 38 | "hashbrown", 39 | ] 40 | 41 | [[package]] 42 | name = "itoa" 43 | version = "1.0.15" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 46 | 47 | [[package]] 48 | name = "litrs" 49 | version = "0.5.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "1e4cf394bcd88ba72eb3b2862e21ef6458b0e1586654e213b0176e667bb9d19e" 52 | 53 | [[package]] 54 | name = "memchr" 55 | version = "2.7.5" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 58 | 59 | [[package]] 60 | name = "proc-macro2" 61 | version = "1.0.101" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 64 | dependencies = [ 65 | "unicode-ident", 66 | ] 67 | 68 | [[package]] 69 | name = "quote" 70 | version = "1.0.40" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 73 | dependencies = [ 74 | "proc-macro2", 75 | ] 76 | 77 | [[package]] 78 | name = "ryu" 79 | version = "1.0.20" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 82 | 83 | [[package]] 84 | name = "serde" 85 | version = "1.0.226" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" 88 | dependencies = [ 89 | "serde_core", 90 | ] 91 | 92 | [[package]] 93 | name = "serde_core" 94 | version = "1.0.226" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" 97 | dependencies = [ 98 | "serde_derive", 99 | ] 100 | 101 | [[package]] 102 | name = "serde_derive" 103 | version = "1.0.226" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" 106 | dependencies = [ 107 | "proc-macro2", 108 | "quote", 109 | "syn", 110 | ] 111 | 112 | [[package]] 113 | name = "serde_json" 114 | version = "1.0.145" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 117 | dependencies = [ 118 | "itoa", 119 | "memchr", 120 | "ryu", 121 | "serde", 122 | "serde_core", 123 | ] 124 | 125 | [[package]] 126 | name = "serde_spanned" 127 | version = "1.0.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" 130 | dependencies = [ 131 | "serde_core", 132 | ] 133 | 134 | [[package]] 135 | name = "syn" 136 | version = "2.0.106" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 139 | dependencies = [ 140 | "proc-macro2", 141 | "quote", 142 | "unicode-ident", 143 | ] 144 | 145 | [[package]] 146 | name = "target-triple" 147 | version = "0.1.4" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" 150 | 151 | [[package]] 152 | name = "termcolor" 153 | version = "1.4.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 156 | dependencies = [ 157 | "winapi-util", 158 | ] 159 | 160 | [[package]] 161 | name = "toml" 162 | version = "0.9.7" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" 165 | dependencies = [ 166 | "indexmap", 167 | "serde_core", 168 | "serde_spanned", 169 | "toml_datetime", 170 | "toml_parser", 171 | "toml_writer", 172 | "winnow", 173 | ] 174 | 175 | [[package]] 176 | name = "toml_datetime" 177 | version = "0.7.2" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" 180 | dependencies = [ 181 | "serde_core", 182 | ] 183 | 184 | [[package]] 185 | name = "toml_parser" 186 | version = "1.0.3" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" 189 | dependencies = [ 190 | "winnow", 191 | ] 192 | 193 | [[package]] 194 | name = "toml_writer" 195 | version = "1.0.3" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" 198 | 199 | [[package]] 200 | name = "trybuild" 201 | version = "1.0.111" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "0ded9fdb81f30a5708920310bfcd9ea7482ff9cba5f54601f7a19a877d5c2392" 204 | dependencies = [ 205 | "glob", 206 | "serde", 207 | "serde_derive", 208 | "serde_json", 209 | "target-triple", 210 | "termcolor", 211 | "toml", 212 | ] 213 | 214 | [[package]] 215 | name = "unicode-ident" 216 | version = "1.0.19" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" 219 | 220 | [[package]] 221 | name = "winapi-util" 222 | version = "0.1.11" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 225 | dependencies = [ 226 | "windows-sys", 227 | ] 228 | 229 | [[package]] 230 | name = "windows-link" 231 | version = "0.2.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" 234 | 235 | [[package]] 236 | name = "windows-sys" 237 | version = "0.61.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" 240 | dependencies = [ 241 | "windows-link", 242 | ] 243 | 244 | [[package]] 245 | name = "winnow" 246 | version = "0.7.13" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" 249 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `culit` - Custom Literals in Rust 2 | 3 | 7 | 8 | 9 | 10 | [![crates.io](https://img.shields.io/crates/v/culit?style=flat-square&logo=rust)](https://crates.io/crates/culit) 11 | [![docs.rs](https://img.shields.io/badge/docs.rs-culit-blue?style=flat-square&logo=docs.rs)](https://docs.rs/culit) 12 | ![license](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue?style=flat-square) 13 | ![msrv](https://img.shields.io/badge/msrv-1.79-blue?style=flat-square&logo=rust) 14 | [![github](https://img.shields.io/github/stars/nik-rev/culit)](https://github.com/nik-rev/culit) 15 | 16 | You probably know that numbers in Rust can be suffixed to specify their type, e.g. `100i32`. 17 | But did you know that syntactically *any* literal can have a suffix? **And did you know that the suffix can be whatever you want**? 18 | 19 | This crate provides an attribute macro [`#[culit]`](https://docs.rs/culit/latest/culit/attr.culit.html) for "Custom Literals". When applied to any statement, it enables using custom literals in that statement. 20 | 21 | ```toml 22 | [dependencies] 23 | culit = "0.6" 24 | ``` 25 | 26 | Note: `culit` does not have any dependencies such as `syn` or `quote`, and it is a simple mapping `SourceCode -> SourceCode`, so compile-speeds will be very fast. 27 | 28 | ## Example 29 | 30 | A [`NonZeroUsize`](https://doc.rust-lang.org/stable/core/num/nonzero/type.NonZeroUsize.html) literal that fails to compile if it is `0`: `100nzusize` 31 | 32 | ```rust 33 | use culit::culit; 34 | use std::num::NonZeroUsize; 35 | 36 | #[culit] 37 | fn main() { 38 | assert_eq!(100nzusize, NonZeroUsize::new(100).unwrap()); 39 | // COMPILE ERROR! 40 | // let illegal = 0nzusize; 41 | } 42 | 43 | mod custom_literal { 44 | pub mod integer { 45 | macro_rules! nzusize { 46 | // handle `0` specially 47 | (0) => { 48 | compile_error!("`0` is not a valid `NonZeroUsize`") 49 | }; 50 | ($value:literal) => { 51 | const { NonZeroUsize::new($value).unwrap() } 52 | }; 53 | } 54 | pub(crate) use nzusize; 55 | } 56 | } 57 | ``` 58 | 59 | ## IDE Support 60 | 61 | Hovering over the custom literals shows documentation for the macro that generates them. You can also do "goto definition". It's quite nice! 62 | 63 | ![IDE Support](https://raw.githubusercontent.com/nik-rev/culit/71f4a2b32eb87b955d0c953bd3e90e80bd6a938d/ide_support.png) 64 | 65 | ## More Examples 66 | 67 | 68 | Python-like f-strings: `"hello {name}"f` 69 | 70 | ```rust 71 | use culit::culit; 72 | 73 | #[culit] 74 | fn main() { 75 | let name = "bob"; 76 | let age = 23; 77 | 78 | assert_eq!( 79 | "hi, my name is {name} and I am {age} years old"f, 80 | format!("hi, my name is {name} and I am {age} years old") 81 | ); 82 | } 83 | 84 | mod custom_literal { 85 | pub mod string { 86 | macro_rules! f { 87 | ($value:literal) => { 88 | format!($value) 89 | }; 90 | } 91 | pub(crate) use f; 92 | } 93 | } 94 | ``` 95 | 96 | [`Duration`](https://doc.rust-lang.org/stable/core/time/struct.Duration.html) literals: `100m`, `2h`... 97 | 98 | ```rust 99 | use culit::culit; 100 | use std::time::Duration; 101 | 102 | #[culit] 103 | fn main() { 104 | assert_eq!( 105 | 100d + 11h + 8m + 7s, 106 | Duration::from_secs(100 * 60 * 60 * 24) 107 | + Duration::from_secs(11 * 60 * 60) 108 | + Duration::from_secs(8 * 60) 109 | + Duration::from_secs(7) 110 | ); 111 | } 112 | 113 | // works on functions, constants, modules, everything! 114 | #[culit] 115 | const TIME: Duration = 100d; 116 | 117 | mod custom_literal { 118 | pub mod integer { 119 | // day 120 | macro_rules! d { 121 | ($value:literal) => { 122 | Duration::from_secs(60 * 60 * 24 * $value) 123 | }; 124 | } 125 | pub(crate) use d; 126 | 127 | // hour 128 | macro_rules! h { 129 | ($value:literal) => { 130 | Duration::from_secs(60 * 60 * $value) 131 | }; 132 | } 133 | pub(crate) use h; 134 | 135 | // minute 136 | macro_rules! m { 137 | ($value:literal) => { 138 | Duration::from_secs(60 * $value) 139 | }; 140 | } 141 | pub(crate) use m; 142 | 143 | // second 144 | macro_rules! s { 145 | ($value:literal) => { 146 | Duration::from_secs($value) 147 | }; 148 | } 149 | pub(crate) use s; 150 | } 151 | } 152 | ``` 153 | 154 | The possibilities are *endless!* 155 | 156 | ## Details 157 | 158 | `#[culit]` replaces every literal that has a custom suffix with a call to the macro 159 | at `crate::custom_literal::::!($value)`, where `$value` is the literal with the suffix stripped: 160 | 161 | |literal|expansion| 162 | |---|---| 163 | | `100km` | `crate::custom_literal::integer::km!(100)` | 164 | | `70.008e7feet` | `crate::custom_literal::float::feet!(70.008e7)` | 165 | | `"foo"bar` | `crate::custom_literal::string::bar!("foo")` | 166 | | `'a'ascii` | `crate::custom_literal::character::ascii!('a')` | 167 | | `b"foo"bar` | `crate::custom_literal::byte_string::bar!(b"foo")` | 168 | | `b'a'ascii` | `crate::custom_literal::byte_character::ascii!(b'a')` | 169 | | `c"foo"bar` | `crate::custom_literal::c_string::bar!(c"foo")` | 170 | 171 | Notes: 172 | 173 | - Built-in suffixes like `usize` and `f32` do **not** expand, so you cannot overwrite them. 174 | - Escapes are fully processed, so there's no `raw_byte_str`. `rb#"f\oo"#` just becomes `b"f\\oo"` 175 | 176 | ### Skeleton 177 | 178 | Here's a skeleton for the `custom_literal` module which must exist at `crate::custom_literal`. 179 | This module adds a new literal for every type of literal: 180 | 181 | ```rust 182 | mod custom_literal { 183 | pub mod integer { 184 | macro_rules! custom { 185 | ($value:literal) => { 186 | // ... 187 | } 188 | } 189 | pub(crate) use custom; 190 | } 191 | 192 | pub mod float { 193 | macro_rules! custom { 194 | ($value:literal) => { 195 | // ... 196 | } 197 | } 198 | pub(crate) use custom; 199 | } 200 | 201 | pub mod string { 202 | macro_rules! custom { 203 | ($value:literal) => { 204 | // ... 205 | } 206 | } 207 | pub(crate) use custom; 208 | } 209 | 210 | pub mod character { 211 | macro_rules! custom { 212 | ($value:literal) => { 213 | // ... 214 | } 215 | } 216 | pub(crate) use custom; 217 | } 218 | 219 | pub mod byte_character { 220 | macro_rules! custom { 221 | ($value:literal) => { 222 | // ... 223 | } 224 | } 225 | pub(crate) use custom; 226 | } 227 | 228 | pub mod byte_string { 229 | macro_rules! custom { 230 | ($value:literal) => { 231 | // ... 232 | } 233 | } 234 | pub(crate) use custom; 235 | } 236 | 237 | pub mod c_string { 238 | macro_rules! custom { 239 | ($value:literal) => { 240 | // ... 241 | } 242 | } 243 | pub(crate) use custom; 244 | } 245 | } 246 | ``` 247 | 248 | ## Custom module 249 | 250 | We look for custom literals in the `crate::custom_literal` module. 251 | You can choose a custom module by passing arguments to the `culit` macro. 252 | The default usage of `#[culit]` is identical to `#[culit(crate::custom_literal)]` 253 | 254 | ## Nightly 255 | 256 | You need to use `#[culit]` attribute everywhere you want to use these literals. On nightly, you can apply it on the module: 257 | 258 | ```rust 259 | #![feature(custom_inner_attributes)] 260 | #![feature(proc_macro_hygiene)] 261 | #![culit::culit] 262 | ``` 263 | 264 | While this *works*, I wouldn't recommend it - currently rust-analyzer is unable to properly work with custom inner attributes 265 | that modify the whole crate. For example, if you write `0nzusize` which produces a compiler error, the span of the error will point to 266 | the macro `crate::custom_literal::int::nzusize` but *not* the actual `0nzusize`, which makes it very hard to debug these 267 | 268 | 269 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![crates.io](https://img.shields.io/crates/v/culit?style=flat-square&logo=rust)](https://crates.io/crates/culit) 2 | //! [![docs.rs](https://img.shields.io/badge/docs.rs-culit-blue?style=flat-square&logo=docs.rs)](https://docs.rs/culit) 3 | //! ![license](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue?style=flat-square) 4 | //! ![msrv](https://img.shields.io/badge/msrv-1.79-blue?style=flat-square&logo=rust) 5 | //! [![github](https://img.shields.io/github/stars/nik-rev/culit)](https://github.com/nik-rev/culit) 6 | //! 7 | //! You probably know that numbers in Rust can be suffixed to specify their type, e.g. `100i32`. 8 | //! But did you know that syntactically *any* literal can have a suffix? **And did you know that the suffix can be whatever you want**? 9 | //! 10 | //! This crate provides an attribute macro [`#[culit]`](macro@culit) for "Custom Literals". When applied to any statement, it enables using custom literals in that statement. 11 | //! 12 | //! ```toml 13 | //! [dependencies] 14 | //! culit = "0.6" 15 | //! ``` 16 | //! 17 | //! Note: `culit` does not have any dependencies such as `syn` or `quote`, and it is a simple mapping `SourceCode -> SourceCode`, so compile-speeds will be very fast. 18 | //! 19 | //! # Example 20 | //! 21 | //! A [`NonZeroUsize`](std::num::NonZeroUsize) literal that fails to compile if it is `0`: `100nzusize` 22 | //! 23 | //! ``` 24 | //! use culit::culit; 25 | //! use std::num::NonZeroUsize; 26 | //! 27 | //! #[culit] 28 | //! fn main() { 29 | //! assert_eq!(100nzusize, NonZeroUsize::new(100).unwrap()); 30 | //! // COMPILE ERROR! 31 | //! // let illegal = 0nzusize; 32 | //! } 33 | //! 34 | //! mod custom_literal { 35 | //! pub mod integer { 36 | //! macro_rules! nzusize { 37 | //! // handle `0` specially 38 | //! (0) => { 39 | //! compile_error!("`0` is not a valid `NonZeroUsize`") 40 | //! }; 41 | //! ($value:literal) => { 42 | //! const { NonZeroUsize::new($value).unwrap() } 43 | //! }; 44 | //! } 45 | //! pub(crate) use nzusize; 46 | //! } 47 | //! } 48 | //! ``` 49 | //! 50 | //! # IDE Support 51 | //! 52 | //! Hovering over the custom literals shows documentation for the macro that generates them. You can also do "goto definition". It's quite nice! 53 | //! 54 | //! ![IDE Support](https://raw.githubusercontent.com/nik-rev/culit/71f4a2b32eb87b955d0c953bd3e90e80bd6a938d/ide_support.png) 55 | //! 56 | //! # More Examples 57 | //! 58 | //! 59 | //! Python-like f-strings: `"hello {name}"f` 60 | //! 61 | //! ``` 62 | //! use culit::culit; 63 | //! 64 | //! #[culit] 65 | //! fn main() { 66 | //! let name = "bob"; 67 | //! let age = 23; 68 | //! 69 | //! assert_eq!( 70 | //! "hi, my name is {name} and I am {age} years old"f, 71 | //! format!("hi, my name is {name} and I am {age} years old") 72 | //! ); 73 | //! } 74 | //! 75 | //! mod custom_literal { 76 | //! pub mod string { 77 | //! macro_rules! f { 78 | //! ($value:literal) => { 79 | //! format!($value) 80 | //! }; 81 | //! } 82 | //! pub(crate) use f; 83 | //! } 84 | //! } 85 | //! ``` 86 | //! 87 | //! [`Duration`](std::time::Duration) literals: `100m`, `2h`... 88 | //! 89 | //! ``` 90 | //! use culit::culit; 91 | //! use std::time::Duration; 92 | //! 93 | //! #[culit] 94 | //! fn main() { 95 | //! assert_eq!( 96 | //! 100d + 11h + 8m + 7s, 97 | //! Duration::from_secs(100 * 60 * 60 * 24) 98 | //! + Duration::from_secs(11 * 60 * 60) 99 | //! + Duration::from_secs(8 * 60) 100 | //! + Duration::from_secs(7) 101 | //! ); 102 | //! } 103 | //! 104 | //! // works on functions, constants, modules, everything! 105 | //! #[culit] 106 | //! const TIME: Duration = 100d; 107 | //! 108 | //! mod custom_literal { 109 | //! pub mod integer { 110 | //! // day 111 | //! macro_rules! d { 112 | //! ($value:literal) => { 113 | //! Duration::from_secs(60 * 60 * 24 * $value) 114 | //! }; 115 | //! } 116 | //! pub(crate) use d; 117 | //! 118 | //! // hour 119 | //! macro_rules! h { 120 | //! ($value:literal) => { 121 | //! Duration::from_secs(60 * 60 * $value) 122 | //! }; 123 | //! } 124 | //! pub(crate) use h; 125 | //! 126 | //! // minute 127 | //! macro_rules! m { 128 | //! ($value:literal) => { 129 | //! Duration::from_secs(60 * $value) 130 | //! }; 131 | //! } 132 | //! pub(crate) use m; 133 | //! 134 | //! // second 135 | //! macro_rules! s { 136 | //! ($value:literal) => { 137 | //! Duration::from_secs($value) 138 | //! }; 139 | //! } 140 | //! pub(crate) use s; 141 | //! } 142 | //! } 143 | //! ``` 144 | //! 145 | //! The possibilities are *endless!* 146 | //! 147 | //! # Details 148 | //! 149 | //! `#[culit]` replaces every literal that has a custom suffix with a call to the macro 150 | //! at `crate::custom_literal::::!($value)`, where `$value` is the literal with the suffix stripped: 151 | //! 152 | //! |literal|expansion| 153 | //! |---|---| 154 | //! | `100km` | `crate::custom_literal::integer::km!(100)` | 155 | //! | `70.008e7feet` | `crate::custom_literal::float::feet!(70.008e7)` | 156 | //! | `"foo"bar` | `crate::custom_literal::string::bar!("foo")` | 157 | //! | `'a'ascii` | `crate::custom_literal::character::ascii!('a')` | 158 | //! | `b"foo"bar` | `crate::custom_literal::byte_string::bar!(b"foo")` | 159 | //! | `b'a'ascii` | `crate::custom_literal::byte_character::ascii!(b'a')` | 160 | //! | `c"foo"bar` | `crate::custom_literal::c_string::bar!(c"foo")` | 161 | //! 162 | //! Notes: 163 | //! 164 | //! - Built-in suffixes like `usize` and `f32` do **not** expand, so you cannot overwrite them. 165 | //! - Escapes are fully processed, so there's no `raw_byte_str`. `rb#"f\oo"#` just becomes `b"f\\oo"` 166 | //! 167 | //! ## Skeleton 168 | //! 169 | //! Here's a skeleton for the `custom_literal` module which must exist at `crate::custom_literal`. 170 | //! This module adds a new literal for every type of literal: 171 | //! 172 | //! ``` 173 | //! mod custom_literal { 174 | //! pub mod integer { 175 | //! macro_rules! custom { 176 | //! ($value:literal) => { 177 | //! // ... 178 | //! } 179 | //! } 180 | //! pub(crate) use custom; 181 | //! } 182 | //! 183 | //! pub mod float { 184 | //! macro_rules! custom { 185 | //! ($value:literal) => { 186 | //! // ... 187 | //! } 188 | //! } 189 | //! pub(crate) use custom; 190 | //! } 191 | //! 192 | //! pub mod string { 193 | //! macro_rules! custom { 194 | //! ($value:literal) => { 195 | //! // ... 196 | //! } 197 | //! } 198 | //! pub(crate) use custom; 199 | //! } 200 | //! 201 | //! pub mod character { 202 | //! macro_rules! custom { 203 | //! ($value:literal) => { 204 | //! // ... 205 | //! } 206 | //! } 207 | //! pub(crate) use custom; 208 | //! } 209 | //! 210 | //! pub mod byte_character { 211 | //! macro_rules! custom { 212 | //! ($value:literal) => { 213 | //! // ... 214 | //! } 215 | //! } 216 | //! pub(crate) use custom; 217 | //! } 218 | //! 219 | //! pub mod byte_string { 220 | //! macro_rules! custom { 221 | //! ($value:literal) => { 222 | //! // ... 223 | //! } 224 | //! } 225 | //! pub(crate) use custom; 226 | //! } 227 | //! 228 | //! pub mod c_string { 229 | //! macro_rules! custom { 230 | //! ($value:literal) => { 231 | //! // ... 232 | //! } 233 | //! } 234 | //! pub(crate) use custom; 235 | //! } 236 | //! } 237 | //! ``` 238 | //! 239 | //! # Custom module 240 | //! 241 | //! We look for custom literals in the `crate::custom_literal` module. 242 | //! You can choose a custom module by passing arguments to the `culit` macro. 243 | //! The default usage of `#[culit]` is identical to `#[culit(crate::custom_literal)]` 244 | //! 245 | //! # Nightly 246 | //! 247 | //! You need to use `#[culit]` attribute everywhere you want to use these literals. On nightly, you can apply it on the module: 248 | //! 249 | //! ```ignore 250 | //! #![feature(custom_inner_attributes)] 251 | //! #![feature(proc_macro_hygiene)] 252 | //! #![culit::culit] 253 | //! ``` 254 | //! 255 | //! While this *works*, I wouldn't recommend it - currently rust-analyzer is unable to properly work with custom inner attributes 256 | //! that modify the whole crate. For example, if you write `0nzusize` which produces a compiler error, the span of the error will point to 257 | //! the macro `crate::custom_literal::int::nzusize` but *not* the actual `0nzusize`, which makes it very hard to debug these 258 | #![allow(clippy::needless_doctest_main)] 259 | 260 | use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; 261 | 262 | /// Supports using custom literals such as `10km` defined at `crate::custom_literal::int::km` 263 | /// 264 | /// # Example 265 | /// 266 | /// ``` 267 | /// # use culit::culit; 268 | /// # #[derive(PartialEq, Debug)] 269 | /// struct Kilometers(u32); 270 | /// 271 | /// #[culit] 272 | /// fn main() { 273 | /// assert_eq!(10km, Kilometers(10)); 274 | /// } 275 | /// 276 | /// mod custom_literal { 277 | /// pub mod integer { 278 | /// macro_rules! km { 279 | /// ($value:literal) => { 280 | /// $crate::Kilometers($value) 281 | /// } 282 | /// } 283 | /// pub(crate) use km; 284 | /// } 285 | /// } 286 | /// ``` 287 | /// 288 | /// For more information, see the [crate-level](crate) documentation 289 | #[proc_macro_attribute] 290 | pub fn culit(args: TokenStream, input: TokenStream) -> TokenStream { 291 | transform( 292 | input, 293 | if args.is_empty() { 294 | TokenStream::from_iter([ 295 | TokenTree::Ident(Ident::new("crate", Span::call_site())), 296 | TokenTree::Punct(Punct::new(':', Spacing::Joint)), 297 | TokenTree::Punct(Punct::new(':', Spacing::Joint)), 298 | TokenTree::Ident(Ident::new("custom_literal", Span::call_site())), 299 | ]) 300 | } else { 301 | args 302 | }, 303 | ) 304 | } 305 | 306 | /// Recursively replaces all literals in the `TokenStream` with a call to `crate::custom_literal::$literal_type::$suffix!($ts)` 307 | fn transform(ts: TokenStream, path: TokenStream) -> TokenStream { 308 | let mut output = TokenStream::new(); 309 | 310 | for tt in ts { 311 | match tt { 312 | TokenTree::Literal(tt_lit) => { 313 | let span = tt_lit.span(); 314 | 315 | // NOTE: `litrs::Literal::from(token_tree::Literal) exists but it unnecessarily takes by-value, 316 | // so we avoid an unnecessary clone here 317 | let lit = litrs::Literal::parse(tt_lit.to_string()).expect(concat!( 318 | "bug in the implementation of `litrs`, ", 319 | "`token_tree::Literal` -> `litrs::Literal` is infallible" 320 | )); 321 | 322 | let suffix = lit.suffix(); 323 | 324 | if suffix.is_empty() { 325 | // Totally skip this literal as there's no suffix 326 | output.extend([TokenTree::Literal(tt_lit)]); 327 | continue; 328 | } 329 | 330 | const RESERVED_MESSAGE: &str = concat!( 331 | " is not currently used ", 332 | "by rust, but it likely will be in the future", 333 | ". To avoid breakage and not compromise rust's compatibility guarantees, ", 334 | "we forbid this suffix" 335 | ); 336 | 337 | match &lit { 338 | litrs::Literal::Integer(integer_lit) => { 339 | if INT_SUFFIXES.contains(&suffix) { 340 | output.extend([TokenTree::Literal(tt_lit)]); 341 | continue; 342 | } else if INT_SUFFIXES_RESERVED.contains(&suffix) { 343 | output.extend(CompileError::new( 344 | span, 345 | format!("suffix {suffix} {RESERVED_MESSAGE}"), 346 | )); 347 | continue; 348 | } 349 | 350 | let mut int = String::with_capacity(integer_lit.raw_input().len()); 351 | int.push_str(integer_lit.base().prefix()); 352 | int.push_str(integer_lit.raw_main_part()); 353 | int.parse::().expect(concat!( 354 | "if it wasn't a valid literal, `litrs::Literal`", 355 | " would not be able to parse it" 356 | )); 357 | 358 | expand_custom_literal( 359 | lit_name::INTEGER, 360 | suffix, 361 | span, 362 | TokenStream::from(TokenTree::Literal(int.parse::().expect( 363 | concat!( 364 | "if it wasn't a valid literal, `litrs::Literal`", 365 | " would not be able to parse it" 366 | ), 367 | ))), 368 | path.clone(), 369 | &mut output, 370 | ); 371 | } 372 | // crate::custom_literal::str::$suffix!($value) 373 | litrs::Literal::String(string_lit) => expand_custom_literal( 374 | lit_name::STRING, 375 | suffix, 376 | span, 377 | TokenStream::from( 378 | // $value 379 | TokenTree::Literal(Literal::string(string_lit.value())).with_span(span), 380 | ), 381 | path.clone(), 382 | &mut output, 383 | ), 384 | litrs::Literal::Float(float_lit) => { 385 | if FLOAT_SUFFIXES.contains(&suffix) { 386 | output.extend([TokenTree::Literal(tt_lit)].into_iter()); 387 | continue; 388 | } else if FLOAT_SUFFIXES_RESERVED.contains(&suffix) { 389 | output.extend(CompileError::new( 390 | span, 391 | format!("suffix {suffix} {RESERVED_MESSAGE}"), 392 | )); 393 | continue; 394 | } 395 | 396 | expand_custom_literal( 397 | lit_name::FLOAT, 398 | suffix, 399 | span, 400 | TokenStream::from(TokenTree::Literal( 401 | float_lit.number_part().parse::().expect(concat!( 402 | "if it wasn't a valid literal, `litrs::Literal`", 403 | " would not be able to parse it" 404 | )), 405 | )), 406 | path.clone(), 407 | &mut output, 408 | ); 409 | } 410 | // crate::custom_literal::char::$suffix!($value) 411 | litrs::Literal::Char(char_lit) => expand_custom_literal( 412 | lit_name::CHARACTER, 413 | suffix, 414 | span, 415 | TokenStream::from( 416 | // $value 417 | TokenTree::Literal(Literal::character(char_lit.value())) 418 | .with_span(span), 419 | ), 420 | path.clone(), 421 | &mut output, 422 | ), 423 | // crate::custom_literal::byte_char::$suffix!($value) 424 | litrs::Literal::Byte(byte_lit) => expand_custom_literal( 425 | lit_name::BYTE_CHARACTER, 426 | suffix, 427 | span, 428 | TokenStream::from( 429 | // $value 430 | TokenTree::Literal(Literal::byte_character(byte_lit.value())) 431 | .with_span(span), 432 | ), 433 | path.clone(), 434 | &mut output, 435 | ), 436 | // crate::custom_literal::byte_str::$suffix!($value) 437 | litrs::Literal::ByteString(byte_string_lit) => { 438 | expand_custom_literal( 439 | lit_name::BYTE_STRING, 440 | suffix, 441 | span, 442 | TokenStream::from( 443 | // $value 444 | TokenTree::Literal(Literal::byte_string(byte_string_lit.value())) 445 | .with_span(span), 446 | ), 447 | path.clone(), 448 | &mut output, 449 | ) 450 | } 451 | litrs::Literal::CString(cstring_lit) => { 452 | expand_custom_literal( 453 | lit_name::C_STRING, 454 | suffix, 455 | span, 456 | TokenStream::from( 457 | // $value 458 | TokenTree::Literal(Literal::c_string(cstring_lit.value())) 459 | .with_span(span), 460 | ), 461 | path.clone(), 462 | &mut output, 463 | ) 464 | } 465 | litrs::Literal::Bool(_bool_lit) => { 466 | unreachable!( 467 | "booleans aren't `TokenTree::Literal`, they're `TokenTree::Ident`" 468 | ) 469 | } 470 | } 471 | } 472 | TokenTree::Group(group) => { 473 | output.extend( 474 | [TokenTree::Group(Group::new( 475 | group.delimiter(), 476 | // Recurse 477 | transform(group.stream(), path.clone()), 478 | ))] 479 | .into_iter(), 480 | ) 481 | } 482 | next_tt => output.extend([next_tt]), 483 | } 484 | } 485 | 486 | output 487 | } 488 | 489 | /// Expands a custom literal into `crate::custom_literal::$literal_type::$suffix!($ts)` 490 | fn expand_custom_literal( 491 | literal_type: &str, 492 | suffix: &str, 493 | span: Span, 494 | ts: TokenStream, 495 | path: TokenStream, 496 | output: &mut TokenStream, 497 | ) { 498 | output.extend(path); 499 | output.extend([ 500 | TokenTree::Punct(Punct::new(':', Spacing::Joint)), 501 | TokenTree::Punct(Punct::new(':', Spacing::Joint)), 502 | TokenTree::Ident(Ident::new(literal_type, Span::call_site())), 503 | TokenTree::Punct(Punct::new(':', Spacing::Joint)), 504 | TokenTree::Punct(Punct::new(':', Spacing::Joint)), 505 | TokenTree::Ident(Ident::new(suffix, span)), 506 | TokenTree::Punct(Punct::new('!', Spacing::Joint)).with_span(span), 507 | TokenTree::Group(Group::new(proc_macro::Delimiter::Parenthesis, ts)).with_span(span), 508 | ]); 509 | } 510 | 511 | /// `.into_iter()` generates `compile_error!($message)` at `$span` 512 | struct CompileError { 513 | /// Where the compile error is generates 514 | pub span: Span, 515 | /// Message of the compile error 516 | pub message: String, 517 | } 518 | 519 | impl CompileError { 520 | /// Create a new compile error 521 | pub fn new(span: Span, message: impl AsRef) -> Self { 522 | Self { 523 | span, 524 | message: message.as_ref().to_string(), 525 | } 526 | } 527 | } 528 | 529 | impl IntoIterator for CompileError { 530 | type Item = TokenTree; 531 | type IntoIter = std::array::IntoIter; 532 | 533 | fn into_iter(self) -> Self::IntoIter { 534 | [ 535 | TokenTree::Punct(Punct::new(':', Spacing::Joint)).with_span(self.span), 536 | TokenTree::Punct(Punct::new(':', Spacing::Joint)).with_span(self.span), 537 | TokenTree::Ident(Ident::new("core", self.span)), 538 | TokenTree::Punct(Punct::new(':', Spacing::Joint)).with_span(self.span), 539 | TokenTree::Punct(Punct::new(':', Spacing::Joint)).with_span(self.span), 540 | TokenTree::Ident(Ident::new("compile_error", self.span)), 541 | TokenTree::Punct(Punct::new('!', Spacing::Alone)).with_span(self.span), 542 | TokenTree::Group(Group::new(Delimiter::Brace, { 543 | TokenStream::from( 544 | TokenTree::Literal(Literal::string(&self.message)).with_span(self.span), 545 | ) 546 | })) 547 | .with_span(self.span), 548 | ] 549 | .into_iter() 550 | } 551 | } 552 | 553 | trait TokenTreeExt { 554 | /// Set span of `TokenTree` without needing to create a new binding 555 | fn with_span(self, span: Span) -> TokenTree; 556 | } 557 | 558 | impl TokenTreeExt for TokenTree { 559 | fn with_span(mut self, span: Span) -> TokenTree { 560 | self.set_span(span); 561 | self 562 | } 563 | } 564 | 565 | // NOTE: Renaming them is a BREAKING CHANGE 566 | 567 | /// Name of modules for all literal types 568 | mod lit_name { 569 | pub const INTEGER: &str = "integer"; 570 | pub const FLOAT: &str = "float"; 571 | pub const STRING: &str = "string"; 572 | pub const CHARACTER: &str = "character"; 573 | pub const BYTE_CHARACTER: &str = "byte_character"; 574 | pub const BYTE_STRING: &str = "byte_string"; 575 | pub const C_STRING: &str = "c_string"; 576 | } 577 | 578 | // NOTE: Adding or modifying the constants is a BREAKING CHANGE 579 | 580 | /// List of all integer suffixes currently accepted by Rust 581 | #[rustfmt::skip] 582 | const INT_SUFFIXES: &[&str] = &[ 583 | "i8", "i16", "i32", "i64", "i128", "isize", 584 | "u8", "u16", "u32", "u64", "u128", "usize", 585 | ]; 586 | 587 | /// Integer suffixes currently not accepted, but could be in the future 588 | const INT_SUFFIXES_RESERVED: &[&str] = &["i256", "u256"]; 589 | 590 | /// Float suffixes currently accepted by Rust 591 | const FLOAT_SUFFIXES: &[&str] = &["f32", "f64"]; 592 | 593 | /// Float suffixes currently not accepted, but could be in the future 594 | const FLOAT_SUFFIXES_RESERVED: &[&str] = &["f16", "f128"]; 595 | --------------------------------------------------------------------------------