diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 069223a..b2c3f78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,9 +23,7 @@ jobs: strategy: matrix: rust: - - 1.31 - - 1.33 - - 1.39 + - 1.45 - 1.46 - stable - beta diff --git a/CHANGELOG.md b/CHANGELOG.md index c00401d..970a06d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ This project adheres to [Semantic Versioning](https://semver.org). ## [Unreleased] +* Accept `const_fn` attribute with no arguments and functions without `const` keyword. + This allows `const_fn` to be used as an optional dependency. + + ```rust + #[cfg_attr(feature = "...", const_fn::const_fn)] + pub fn func() { + /* ... */ + } + ``` + ## [0.4.3] - 2020-11-02 * [`const_fn` no longer fails to compile if unable to determine rustc version. Instead, it now displays a warning.](https://github.com/taiki-e/const_fn/pull/31) diff --git a/src/ast.rs b/src/ast.rs index b156493..3622a2f 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,4 +1,4 @@ -use proc_macro::{Delimiter, Literal, Span, TokenStream, TokenTree}; +use proc_macro::{Delimiter, Ident, Literal, Span, TokenStream, TokenTree}; use crate::{ iter::TokenIter, @@ -32,17 +32,6 @@ pub(crate) fn parse_input(input: TokenStream) -> Result { "#[const_fn] attribute may only be used on functions" )); } - if !sig - .iter() - .any(|tt| if let TokenTree::Ident(i) = tt { i.to_string() == "const" } else { false }) - { - let span = sig - .iter() - .position(|tt| if let TokenTree::Ident(i) = tt { i.to_string() == "fn" } else { false }) - .map(|i| sig[i].span()) - .unwrap(); - return Err(error!(span, "#[const_fn] attribute may only be used on const functions")); - } Ok(Func { attrs, sig, body: body.unwrap(), print_const: true }) } @@ -66,10 +55,22 @@ impl ToTokens for Func { fn parse_signature(input: &mut TokenIter) -> Vec { let mut sig = Vec::new(); + let mut has_const = false; loop { match input.peek() { Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => break, None => break, + Some(TokenTree::Ident(i)) if !has_const => { + match &*i.to_string() { + "const" => has_const = true, + "async" | "unsafe" | "extern" | "fn" => { + sig.push(TokenTree::Ident(Ident::new("const", i.span()))); + has_const = true; + } + _ => {} + } + sig.push(input.next().unwrap()); + } Some(_) => sig.push(input.next().unwrap()), } } diff --git a/src/lib.rs b/src/lib.rs index f216da6..0eabb4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,6 +119,7 @@ fn expand(arg: Arg, mut func: Func) -> TokenStream { func.print_const = VERSION.nightly; func.to_token_stream() } + Arg::Always => func.to_token_stream(), } } @@ -131,6 +132,8 @@ enum Arg { Cfg(TokenStream), // `const_fn(feature = "...")` Feature(TokenStream), + // `const_fn` + Always, } fn parse_arg(tokens: TokenStream) -> Result { @@ -139,6 +142,7 @@ fn parse_arg(tokens: TokenStream) -> Result { let next = iter.next(); let next_span = tt_span(next.as_ref()); match next { + None => return Ok(Arg::Always), Some(TokenTree::Ident(i)) => match &*i.to_string() { "nightly" => { parse_as_empty(iter)?; @@ -182,7 +186,7 @@ fn parse_arg(tokens: TokenStream) -> Result { }; } } - _ => {} + Some(_) => {} } Err(error!(next_span, "expected one of: `nightly`, `cfg`, `feature`, string literal")) diff --git a/test_suite/tests/test.rs b/test_suite/tests/test.rs index dc17fcb..bf36031 100644 --- a/test_suite/tests/test.rs +++ b/test_suite/tests/test.rs @@ -1,5 +1,76 @@ -#![cfg_attr(const_unstable, feature(const_fn))] +#![cfg_attr(const_unstable, feature(const_fn, const_extern_fn))] #![warn(rust_2018_idioms, single_use_lifetimes)] +#![allow(clippy::missing_safety_doc)] // this is test + +pub mod syntax { + #![allow(dead_code)] + + use const_fn::const_fn; + + // const + #[const_fn] + fn const_non_const() {} + #[const_fn] + pub fn const_non_const_pub() {} + #[const_fn] + const fn const_const() {} + #[const_fn] + pub const fn const_const_pub() {} + const _: () = const_non_const(); + const _: () = const_non_const_pub(); + const _: () = const_const(); + const _: () = const_const_pub(); + + // const unsafe + #[const_fn] + unsafe fn const_unsafe_non_const() {} + #[const_fn] + pub unsafe fn const_unsafe_non_const_pub() {} + #[const_fn] + const unsafe fn const_unsafe_const() {} + #[const_fn] + pub const unsafe fn const_unsafe_const_pub() {} + const _: () = unsafe { const_unsafe_non_const() }; + const _: () = unsafe { const_unsafe_non_const_pub() }; + const _: () = unsafe { const_unsafe_const() }; + const _: () = unsafe { const_unsafe_const_pub() }; + + // const extern + #[const_fn(cfg(const_unstable))] + extern "C" fn const_extern_non_const() {} + #[const_fn(cfg(const_unstable))] + pub extern "C" fn const_extern_non_const_pub() {} + #[const_fn(cfg(const_unstable))] + const extern "C" fn const_extern_const() {} + #[const_fn(cfg(const_unstable))] + pub const extern "C" fn const_extern_const_pub() {} + #[cfg(const_unstable)] + const _: () = const_extern_non_const(); + #[cfg(const_unstable)] + const _: () = const_extern_non_const_pub(); + #[cfg(const_unstable)] + const _: () = const_extern_const(); + #[cfg(const_unstable)] + const _: () = const_extern_const_pub(); + + // const unsafe extern + #[const_fn(cfg(const_unstable))] + unsafe extern "C" fn const_unsafe_extern_non_const() {} + #[const_fn(cfg(const_unstable))] + pub unsafe extern "C" fn const_unsafe_extern_non_const_pub() {} + #[const_fn(cfg(const_unstable))] + const unsafe extern "C" fn const_unsafe_extern_const() {} + #[const_fn(cfg(const_unstable))] + pub const unsafe extern "C" fn const_unsafe_extern_const_pub() {} + #[cfg(const_unstable)] + const _: () = unsafe { const_unsafe_extern_non_const() }; + #[cfg(const_unstable)] + const _: () = unsafe { const_unsafe_extern_non_const_pub() }; + #[cfg(const_unstable)] + const _: () = unsafe { const_unsafe_extern_const() }; + #[cfg(const_unstable)] + const _: () = unsafe { const_unsafe_extern_const_pub() }; +} pub mod version { use const_fn::const_fn;