├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples └── example │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── lib.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | **/target -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "compile-fail" 5 | version = "0.1.0" 6 | dependencies = [ 7 | "proc-macro2", 8 | "quote", 9 | "syn", 10 | ] 11 | 12 | [[package]] 13 | name = "proc-macro2" 14 | version = "1.0.18" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 17 | dependencies = [ 18 | "unicode-xid", 19 | ] 20 | 21 | [[package]] 22 | name = "quote" 23 | version = "1.0.7" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 26 | dependencies = [ 27 | "proc-macro2", 28 | ] 29 | 30 | [[package]] 31 | name = "syn" 32 | version = "1.0.33" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" 35 | dependencies = [ 36 | "proc-macro2", 37 | "quote", 38 | "unicode-xid", 39 | ] 40 | 41 | [[package]] 42 | name = "unicode-xid" 43 | version = "0.2.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 46 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compile-fail" 3 | version = "0.1.0" 4 | authors = ["Ryan Levick "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | proc-macro2 = "1.0" 12 | syn = { version = "1.0", features = ["full"] } 13 | quote = "1.0" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # compile-fail 2 | 3 | A simple utility for ensuring that certain code does not compile. 4 | 5 | It's often necessary to ensure that certain code does not compile. For instance, 6 | let's say you want to be sure that a method does not implement send: 7 | 8 | ```rust 9 | pub struct MyType { 10 | _ptr: *mut (), 11 | } 12 | 13 | impl MyType { 14 | pub fn new() -> Self { 15 | Self { 16 | _ptr: std::ptr::null_mut(), 17 | } 18 | } 19 | } 20 | ``` 21 | 22 | To test that trying to send this over a thread boundary fails to compile, you can use the following: 23 | 24 | ```rust 25 | #[cfg(test)] 26 | mod tests { 27 | use compile_fail::compile_fail; 28 | 29 | #[compile_fail] 30 | fn cannot_send_non_send_value() { 31 | let t = super::MyType::new(); 32 | // `t` is not send so this should not compile 33 | std::thread::spawn(|| t) 34 | } 35 | } 36 | ``` 37 | 38 | ### Downsides 39 | 40 | This crate is probably not appropriate for large scale testing of items that should fail as any compilation failure 41 | will trigger a passing test. For more robust testing use something like [`trybuild`](https://crates.io/crates/trybuild). 42 | 43 | To quickly see that your test does indeed fail to compile, you can easily turn off the compile failure testing like so: 44 | 45 | ```rust 46 | #[compile_fail(off = true)] 47 | ``` -------------------------------------------------------------------------------- /examples/example/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "compile-fail" 5 | version = "0.1.0" 6 | dependencies = [ 7 | "proc-macro2", 8 | "quote", 9 | "syn", 10 | ] 11 | 12 | [[package]] 13 | name = "example" 14 | version = "0.1.0" 15 | dependencies = [ 16 | "compile-fail", 17 | ] 18 | 19 | [[package]] 20 | name = "proc-macro2" 21 | version = "1.0.18" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 24 | dependencies = [ 25 | "unicode-xid", 26 | ] 27 | 28 | [[package]] 29 | name = "quote" 30 | version = "1.0.7" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 33 | dependencies = [ 34 | "proc-macro2", 35 | ] 36 | 37 | [[package]] 38 | name = "syn" 39 | version = "1.0.33" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" 42 | dependencies = [ 43 | "proc-macro2", 44 | "quote", 45 | "unicode-xid", 46 | ] 47 | 48 | [[package]] 49 | name = "unicode-xid" 50 | version = "0.2.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 53 | -------------------------------------------------------------------------------- /examples/example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | authors = ["Ryan Levick "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | compile-fail = { path = "../.." } 11 | -------------------------------------------------------------------------------- /examples/example/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub struct MyType { 2 | _ptr: *mut (), 3 | } 4 | 5 | impl MyType { 6 | pub fn new() -> Self { 7 | Self { 8 | _ptr: std::ptr::null_mut(), 9 | } 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use compile_fail::compile_fail; 16 | 17 | #[compile_fail] 18 | fn cannot_add_a_number_and_a_string() { 19 | 12 + ""; 20 | } 21 | 22 | #[compile_fail] 23 | fn cannot_send_non_send_value() { 24 | let t = super::MyType::new(); 25 | // `t` is not send so this should not compile 26 | std::thread::spawn(|| t) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{parse_macro_input, AttributeArgs, ItemFn, Lit, Meta, NestedMeta}; 4 | 5 | #[proc_macro_attribute] 6 | pub fn compile_fail(attr: TokenStream, stream: TokenStream) -> TokenStream { 7 | let mut args = parse_macro_input!(attr as AttributeArgs); 8 | let off = match args.pop() { 9 | Some(NestedMeta::Meta(Meta::NameValue(m))) if m.path.is_ident("off") => { 10 | if let Lit::Bool(bool) = m.lit { 11 | // TODO: ensure args is now empty 12 | bool.value 13 | } else { 14 | // TODO: return an error indicating that attributes are malformed 15 | false 16 | } 17 | } 18 | Some(_) => { 19 | // TODO: return an error indicating that attributes are malformed 20 | false 21 | } 22 | None => false, 23 | }; 24 | 25 | let fun = parse_macro_input!(stream as ItemFn); 26 | let mut tokens = proc_macro2::TokenStream::new(); 27 | fun.block.to_tokens(&mut tokens); 28 | let output = if off { 29 | quote! { 30 | #fun 31 | } 32 | } else { 33 | let name = fun.sig.ident; 34 | let code = tokens.to_string(); 35 | let doc = format!( 36 | " 37 | ```compile_fail 38 | {} 39 | ``` 40 | ", 41 | code 42 | ); 43 | quote! { 44 | #[test] 45 | #[doc = #doc] 46 | fn #name() {} 47 | } 48 | }; 49 | 50 | output.into() 51 | } 52 | --------------------------------------------------------------------------------