Allow in expression position #43

Open
opened 2021-03-26 23:11:14 -05:00 by mcclure · 5 comments
mcclure commented 2021-03-26 23:11:14 -05:00 (Migrated from github.com)

Summary:

This macro is currently allowed for statements, but is not allowed as an expression. But, I think it could work as an expression.

Repro:

Try this short program:

use cfg_if::cfg_if;

fn main() {
	let value = cfg_if! {
        if #[cfg(feature = "testfeature")] {
            3 // No `debug` library
        } else {
            4
        }
    };
    println!("Value {}", value);
}

It will fail with this long and vaguely alarming set of messages:

error: macro expansion ignores token `$crate` and any following
  --> /Users/mcc/.cargo/registry/src/github.com-1ecc6299db9ec823/cfg-if-1.0.0/src/lib.rs:79:9
   |
79 |           $crate::cfg_if! { @__items ($($not,)* $($m,)*) ; $($rest)* }
   |           ^^^^^^
   | 
  ::: src/main.rs:4:17
   |
4  |       let value = cfg_if! {
   |  _________________-
5  | |         if #[cfg(feature = "testfeature")] {
6  | |             3 // No `debug` library
7  | |         } else {
8  | |             4
9  | |         }
10 | |     };
   | |     -
   | |     |
   | |_____caused by the macro expansion here
   |       help: you might be missing a semicolon here: `;`
   |
   = note: the usage of `cfg_if!` is likely invalid in expression context

error[E0658]: attributes on expressions are experimental
  --> src/main.rs:4:14
   |
4  |       let value = cfg_if! {
   |  _________________^
5  | |         if #[cfg(feature = "testfeature")] {
6  | |             3 // No `debug` library
7  | |         } else {
8  | |             4
9  | |         }
10 | |     };
   | |_____^
   |
   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: removing an expression is not supported in this position
  --> src/main.rs:4:14
   |
4  |       let value = cfg_if! {
   |  _________________^
5  | |         if #[cfg(feature = "testfeature")] {
6  | |             3 // No `debug` library
7  | |         } else {
8  | |             4
9  | |         }
10 | |     };
   | |_____^
   |
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0658`.
error: could not compile `ifcfgtest`

Analysis:

You can actually make this work now by just wrapping an extra {} around the cfg_if expression:

fn main() {
	let value = {cfg_if! {
        if #[cfg(feature = "testfeature")] {
            3 // No `debug` library
        } else {
            4
        }
    }};
    println!("Value {}", value);
}

If you do this, rather than the cfg_if! being an expression, it is a statement inside of a block expression.

Expected behavior:

The cfg_if! macro should just wrap that extra {} around its result itself, thus making it allowed in an expression context.

**Summary:** This macro is currently allowed for statements, but is not allowed as an expression. But, I think it *could* work as an expression. **Repro:** Try this short program: ``` use cfg_if::cfg_if; fn main() { let value = cfg_if! { if #[cfg(feature = "testfeature")] { 3 // No `debug` library } else { 4 } }; println!("Value {}", value); } ``` It will fail with this long and vaguely alarming set of messages: ``` error: macro expansion ignores token `$crate` and any following --> /Users/mcc/.cargo/registry/src/github.com-1ecc6299db9ec823/cfg-if-1.0.0/src/lib.rs:79:9 | 79 | $crate::cfg_if! { @__items ($($not,)* $($m,)*) ; $($rest)* } | ^^^^^^ | ::: src/main.rs:4:17 | 4 | let value = cfg_if! { | _________________- 5 | | if #[cfg(feature = "testfeature")] { 6 | | 3 // No `debug` library 7 | | } else { 8 | | 4 9 | | } 10 | | }; | | - | | | | |_____caused by the macro expansion here | help: you might be missing a semicolon here: `;` | = note: the usage of `cfg_if!` is likely invalid in expression context error[E0658]: attributes on expressions are experimental --> src/main.rs:4:14 | 4 | let value = cfg_if! { | _________________^ 5 | | if #[cfg(feature = "testfeature")] { 6 | | 3 // No `debug` library 7 | | } else { 8 | | 4 9 | | } 10 | | }; | |_____^ | = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error: removing an expression is not supported in this position --> src/main.rs:4:14 | 4 | let value = cfg_if! { | _________________^ 5 | | if #[cfg(feature = "testfeature")] { 6 | | 3 // No `debug` library 7 | | } else { 8 | | 4 9 | | } 10 | | }; | |_____^ | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0658`. error: could not compile `ifcfgtest` ``` **Analysis:** You can actually make this work now by just wrapping an extra `{}` around the `cfg_if` expression: ``` fn main() { let value = {cfg_if! { if #[cfg(feature = "testfeature")] { 3 // No `debug` library } else { 4 } }}; println!("Value {}", value); } ``` If you do this, rather than the `cfg_if!` being an expression, it is a statement inside of a block expression. **Expected behavior**: The `cfg_if!` macro should just wrap that extra `{}` around its result itself, thus making it allowed in an expression context.
mainrs commented 2021-03-28 16:10:19 -05:00 (Migrated from github.com)

Just came to open an issue as well. I misread the documentation and thought it works for statements as well. Wouldn't wrapping at the macro level make it stop working in a statement like the one in the readme?

cfg_if::cfg_if! {
    if #[cfg(unix)] {
        fn foo() { /* unix specific functionality */ }
    } else if #[cfg(target_pointer_width = "32")] {
        fn foo() { /* non-unix, 32-bit functionality */ }
    } else {
        fn foo() { /* fallback implementation */ }
    }
}

fn main() {
    foo();
}
Just came to open an issue as well. I misread the documentation and thought it works for statements as well. Wouldn't wrapping at the macro level make it stop working in a statement like the one in the readme? ```rust cfg_if::cfg_if! { if #[cfg(unix)] { fn foo() { /* unix specific functionality */ } } else if #[cfg(target_pointer_width = "32")] { fn foo() { /* non-unix, 32-bit functionality */ } } else { fn foo() { /* fallback implementation */ } } } fn main() { foo(); } ```
mcclure commented 2021-03-28 17:40:32 -05:00 (Migrated from github.com)

There might be a way to make both work. If not, having separate cfg_if and cfg_if_exp/cfg_if_value would be somewhat reasonable.

There might be a way to make both work. If not, having separate `cfg_if` and `cfg_if_exp`/`cfg_if_value` would be somewhat reasonable.
alexcrichton commented 2021-03-29 09:01:38 -05:00 (Migrated from github.com)

I agree that it's be awesome to support this, but to do so in the main macro would require the macro knowing what context it's being expanded in, which is information not availble to Rust macros. Otherwise this would, I believe, require a separate macro which does the {-surrounding. I'd personally prefer, though, to avoid adding a second macro to this library.

I agree that it's be awesome to support this, but to do so in the main macro would require the macro knowing what context it's being expanded in, which is information not availble to Rust macros. Otherwise this would, I believe, require a separate macro which does the `{`-surrounding. I'd personally prefer, though, to avoid adding a second macro to this library.
rbtcollins commented 2021-09-19 06:12:50 -05:00 (Migrated from github.com)

Maybe just documenting this approach in the main docs would be enough?

Maybe just documenting this approach in the main docs would be enough?
JohnScience commented 2023-10-04 06:34:06 -05:00 (Migrated from github.com)

My approach to solving this problem is currently this:

macro_rules! wrap {
    ($($tt:tt)+) => {
        {
            cfg_if!(
                if #[cfg(feature = "python")] {
                    let ret = async move {
                        let value = {
                            $($tt)+
                        };
                        PyResult::Ok(value)
                    };
                } else {
                    let ret = $($tt)+;
                }
            );
            ret
        }
    };
}

I.e. define a variable ret in each branch and return it within a block. This block is an expression.

I was writing another library but I believe it's important enough to provide cfg_if_expr. I might do it within a couple of days.

My approach to solving this problem is currently this: ```rust macro_rules! wrap { ($($tt:tt)+) => { { cfg_if!( if #[cfg(feature = "python")] { let ret = async move { let value = { $($tt)+ }; PyResult::Ok(value) }; } else { let ret = $($tt)+; } ); ret } }; } ``` I.e. define a variable `ret` in each branch and return it within a block. This block is an expression. I was writing another library but I believe it's important enough to provide `cfg_if_expr`. I might do it within a couple of days.
This discussion has been locked. Commenting is limited to contributors.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-rust-lang/cfg-if#43
No description provided.