Reject cfg on expressions that cannot be safely removed - #159580
Reject cfg on expressions that cannot be safely removed#159580Unique-Usman wants to merge 1 commit into
Conversation
|
rustbot has assigned @petrochenkov. Use Why was this reviewer chosen?The reviewer was selected based on:
|
| if self.expand_cfg_true(node, attr, pos).as_bool() { | ||
| if matches!( | ||
| Node::KIND, | ||
| AstFragmentKind::Expr | AstFragmentKind::MethodReceiverExpr | ||
| ) { | ||
| self.cx.dcx().emit_err(RemoveExprNotSupported { span }); | ||
| } | ||
| continue; | ||
| } |
There was a problem hiding this comment.
What this code does it emits this error if the cfg predicate is true, then if it's false we call expand_cfg_false and (if it's not the crate root) that emits the same error. Not only isn't that immediately obvious, it also suggests that there are still places where #[cfg(true)] is allowed but #[cfg(false)] is not.
Can you change it so there's only one place we emit this error (whether the predicate is true or not)?
Can you also change the error name and message to say that cfg is not supported in these positions?
There was a problem hiding this comment.
(this check could also be moved elsewhere, I'm not sure what the best place for it is)
There was a problem hiding this comment.
Can you change it so there's only one place we emit this error (whether the predicate is true or not)?
The false version is reported in expand_cfg_false.
This seems like an ok place to do this check for the true case, if it's moved to expand_cfg_true, the match #159580 (comment) will be much larger at least.
There was a problem hiding this comment.
Not only isn't that immediately obvious, it also suggests that there are still places where
#[cfg(true)]is allowed but#[cfg(false)]is not.
If expand_cfg_false is inlined and replaced with a similar match then it will be more clear.
There was a problem hiding this comment.
Can this file have some #[cfg(true)] as well?
|
I don't think we should do this.
|
|
@petrochenkov should the toolchain provide guidance for things that are likely to cause trouble because they are always a footgun regardless? Code like Edit: even if we don't emit an error, shouldn't this case in particular be at least deny-by-default lint, given that it is syntactically correct, but unlikely to be what the user intended? |
#159581 won't stabilize attribute macros on expressions, so should we move Also if we ever get a way for macros to overload based on what position (type, expr, etc) they're in (perhaps similar to |
Yes, this just looks like a lint material to me. Are you sure |
For an example, see https://github.com/PyO3/pyo3/blob/09b8d484b3f79b8519cac63be9d71a107cb14952/pyo3-ffi/examples/string-sum/src/lib.rs#L36 where the value of an array length depends on conditional compilation. It would be quite tempting to try to write that as... const SLOTS_LEN: usize = 1 + #[cfg(Py_3_12)] 1 + #[cfg(Py_GIL_DISABLED)] 1 + #[cfg(Py_3_15)] 4);...and end up in one of those situations where it looks like something that does what you expect, only for it to turn out you're wrong. It would just be another gotcha that most people are going to run into at some point. |
This is technically a breaking change, because it closes a stability hole. macro_rules! mac {
($expr:expr) => { $expr.clone() }
}
fn main() {
let _ = mac!(#[cfg(true)] 10);
} |
|
Let's land this, it will simplify the stabilization of |
|
Reminder, once the PR becomes ready for a review, use |
|
Could you also add this as a test case? #[derive(Clone)]
enum E {
V1 = #[cfg(true)] 1,
V2 = #[cfg_attr(true, cfg(true))] 2,
}Both variants should produce errors. |
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
Could you also do the changes
|
|
This is also a language change that needs to go through the language team. |
|
@petrochenkov - what changes do you want me to make regarding this ? |
At the time I wrote this, the test was just fn main() {
let _ = #[cfg(false)] ();
//~^ ERROR removing an expression is not supported in this position
let _ = 1 + 2 + #[cfg(false)] 3;
//~^ ERROR removing an expression is not supported in this position
let _ = [1, 2, 3][#[cfg(false)] 1];
//~^ ERROR removing an expression is not supported in this position
}i.e. just testing false cfgs, not true ones as well, but it looks like it's partially been addressed already. Can you update to also include smth like fn main() {
let _ = #[cfg(true)] ();
//~^ ERROR removing an expression is not supported in this position
let _ = 1 + 2 + #[cfg(true)] 3;
//~^ ERROR removing an expression is not supported in this position
let _ = [1, 2, 3][#[cfg(true)] 1];
//~^ ERROR removing an expression is not supported in this position
} |
Previously, expressions behind stmt_expr_attributes were only rejected when the cfg condition evaluated to false. If the condition was true, the code compiled successfully. This meant code like an attributed binary operand could compile on one platform but fail on another, even though removing the operand would leave an invalid expression. This change always rejects cfg in expression positions where removing the expression would produce invalid code. Expression positions where removal is safe continue to work as before. Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
View all comments
Change description
This PR changes how
#[cfg]behaves in expression positions where removing the expression would result in invalid syntax.For example:
This currently works on Unix because the condition is
true, but becomes invalid on non-Unix targets when the expression is removed.The proposed change rejects
cfgin these positions regardless of whether the condition istrueorfalse. The goal is to avoid code whose validity depends on the target and to make the behavior more predictable asstmt_expr_attributesis stabilized.Summary of the discussion
@petrochenkov pointed out that this differs from the usual token-based model for macros:
#[cfg(true)] exprproducesexpr, while#[cfg(false)] exprproduces no tokens. From that perspective, only the latter naturally results in an error.On the other hand, @estebank pointed out that these cases are easy to introduce accidentally and may only be discovered when the code is compiled for another target. This becomes more important with the stabilization of expression attributes.
We also discussed that this is technically a breaking change, since some currently accepted code such as
#[cfg(true)]inside a macro invocation would stop compiling. The conclusion was that this is effectively closing a stability hole, and the amount of affected code is expected to be very small.The current implementation also uses
RemoveNodeNotSupportedfor these cases and adds coverage for both#[cfg(true)]and#[cfg(false)].