Skip to content

Commit 10c255a

Browse files
authored
Add future-incompat warning against keywords in cfgs and add raw-idents (#14671)
### What does this PR try to resolve? This PR tries to address this thread #14649 (comment) in #14649 regarding `cfg(true)`/`cfg(false)` (and keywords more generally) which are wrongly accepted[^1] as ident. To address this, this PR does two things: 1. it introduce a future-incompatibility warning against those (wrongly) accepted keywords as ident 2. it add parsing for raw-idents (`r#true`) add suggest-it in the warning ### How should we test and review this PR? This PR should be reviewed commit-by-commit. Tests are included in preliminary commits. ### Additional information I added a new struct for representing `Ident`s which is rawness aware. Which implied updating `cargo-platform` to `0.2.0` due to the API changes. r? @epage [^1]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ccfb9c894dbf14e791c8ae7e4798efd0
2 parents c4a9e45 + e2028d4 commit 10c255a

File tree

10 files changed

+349
-22
lines changed

10 files changed

+349
-22
lines changed

Diff for: Cargo.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ cargo-credential = { version = "0.4.2", path = "credential/cargo-credential" }
3131
cargo-credential-libsecret = { version = "0.4.7", path = "credential/cargo-credential-libsecret" }
3232
cargo-credential-macos-keychain = { version = "0.4.7", path = "credential/cargo-credential-macos-keychain" }
3333
cargo-credential-wincred = { version = "0.4.7", path = "credential/cargo-credential-wincred" }
34-
cargo-platform = { path = "crates/cargo-platform", version = "0.1.5" }
34+
cargo-platform = { path = "crates/cargo-platform", version = "0.2.0" }
3535
cargo-test-macro = { version = "0.3.0", path = "crates/cargo-test-macro" }
3636
cargo-test-support = { version = "0.6.0", path = "crates/cargo-test-support" }
3737
cargo-util = { version = "0.2.14", path = "crates/cargo-util" }

Diff for: crates/cargo-platform/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cargo-platform"
3-
version = "0.1.9"
3+
version = "0.2.0"
44
edition.workspace = true
55
license.workspace = true
66
rust-version.workspace = true

Diff for: crates/cargo-platform/src/cfg.rs

+110-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::error::{ParseError, ParseErrorKind::*};
22
use std::fmt;
3+
use std::hash::{Hash, Hasher};
34
use std::iter;
45
use std::str::{self, FromStr};
56

@@ -16,21 +17,41 @@ pub enum CfgExpr {
1617
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
1718
pub enum Cfg {
1819
/// A named cfg value, like `unix`.
19-
Name(String),
20+
Name(Ident),
2021
/// A key/value cfg pair, like `target_os = "linux"`.
21-
KeyPair(String, String),
22+
KeyPair(Ident, String),
23+
}
24+
25+
/// A identifier
26+
#[derive(Eq, Ord, PartialOrd, Clone, Debug)]
27+
pub struct Ident {
28+
/// The identifier
29+
pub name: String,
30+
/// Is this a raw ident: `r#async`
31+
///
32+
/// It's mainly used for display and doesn't take
33+
/// part in the hash or equality (`foo` == `r#foo`).
34+
pub raw: bool,
2235
}
2336

2437
#[derive(PartialEq)]
2538
enum Token<'a> {
2639
LeftParen,
2740
RightParen,
28-
Ident(&'a str),
41+
Ident(bool, &'a str),
2942
Comma,
3043
Equals,
3144
String(&'a str),
3245
}
3346

47+
/// The list of keywords.
48+
///
49+
/// We should consider all the keywords, but some are conditional on
50+
/// the edition so for now we just consider true/false.
51+
///
52+
/// <https://doc.rust-lang.org/reference/keywords.html>
53+
pub(crate) const KEYWORDS: &[&str; 2] = &["true", "false"];
54+
3455
#[derive(Clone)]
3556
struct Tokenizer<'a> {
3657
s: iter::Peekable<str::CharIndices<'a>>,
@@ -41,6 +62,45 @@ struct Parser<'a> {
4162
t: Tokenizer<'a>,
4263
}
4364

65+
impl Ident {
66+
pub fn as_str(&self) -> &str {
67+
&self.name
68+
}
69+
}
70+
71+
impl Hash for Ident {
72+
fn hash<H: Hasher>(&self, state: &mut H) {
73+
self.name.hash(state);
74+
}
75+
}
76+
77+
impl PartialEq<str> for Ident {
78+
fn eq(&self, other: &str) -> bool {
79+
self.name == other
80+
}
81+
}
82+
83+
impl PartialEq<&str> for Ident {
84+
fn eq(&self, other: &&str) -> bool {
85+
self.name == *other
86+
}
87+
}
88+
89+
impl PartialEq<Ident> for Ident {
90+
fn eq(&self, other: &Ident) -> bool {
91+
self.name == other.name
92+
}
93+
}
94+
95+
impl fmt::Display for Ident {
96+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97+
if self.raw {
98+
f.write_str("r#")?;
99+
}
100+
f.write_str(&*self.name)
101+
}
102+
}
103+
44104
impl FromStr for Cfg {
45105
type Err = ParseError;
46106

@@ -144,7 +204,8 @@ impl<'a> Parser<'a> {
144204

145205
fn expr(&mut self) -> Result<CfgExpr, ParseError> {
146206
match self.peek() {
147-
Some(Ok(Token::Ident(op @ "all"))) | Some(Ok(Token::Ident(op @ "any"))) => {
207+
Some(Ok(Token::Ident(false, op @ "all")))
208+
| Some(Ok(Token::Ident(false, op @ "any"))) => {
148209
self.t.next();
149210
let mut e = Vec::new();
150211
self.eat(&Token::LeftParen)?;
@@ -161,7 +222,7 @@ impl<'a> Parser<'a> {
161222
Ok(CfgExpr::Any(e))
162223
}
163224
}
164-
Some(Ok(Token::Ident("not"))) => {
225+
Some(Ok(Token::Ident(false, "not"))) => {
165226
self.t.next();
166227
self.eat(&Token::LeftParen)?;
167228
let e = self.expr()?;
@@ -179,7 +240,7 @@ impl<'a> Parser<'a> {
179240

180241
fn cfg(&mut self) -> Result<Cfg, ParseError> {
181242
match self.t.next() {
182-
Some(Ok(Token::Ident(name))) => {
243+
Some(Ok(Token::Ident(raw, name))) => {
183244
let e = if self.r#try(&Token::Equals) {
184245
let val = match self.t.next() {
185246
Some(Ok(Token::String(s))) => s,
@@ -197,9 +258,18 @@ impl<'a> Parser<'a> {
197258
return Err(ParseError::new(self.t.orig, IncompleteExpr("a string")))
198259
}
199260
};
200-
Cfg::KeyPair(name.to_string(), val.to_string())
261+
Cfg::KeyPair(
262+
Ident {
263+
name: name.to_string(),
264+
raw,
265+
},
266+
val.to_string(),
267+
)
201268
} else {
202-
Cfg::Name(name.to_string())
269+
Cfg::Name(Ident {
270+
name: name.to_string(),
271+
raw,
272+
})
203273
};
204274
Ok(e)
205275
}
@@ -279,14 +349,44 @@ impl<'a> Iterator for Tokenizer<'a> {
279349
return Some(Err(ParseError::new(self.orig, UnterminatedString)));
280350
}
281351
Some((start, ch)) if is_ident_start(ch) => {
352+
let (start, raw) = if ch == 'r' {
353+
if let Some(&(_pos, '#')) = self.s.peek() {
354+
// starts with `r#` is a raw ident
355+
self.s.next();
356+
if let Some((start, ch)) = self.s.next() {
357+
if is_ident_start(ch) {
358+
(start, true)
359+
} else {
360+
// not a starting ident character
361+
return Some(Err(ParseError::new(
362+
self.orig,
363+
UnexpectedChar(ch),
364+
)));
365+
}
366+
} else {
367+
// not followed by a ident, error out
368+
return Some(Err(ParseError::new(
369+
self.orig,
370+
IncompleteExpr("identifier"),
371+
)));
372+
}
373+
} else {
374+
// starts with `r` but not does continue with `#`
375+
// cannot be a raw ident
376+
(start, false)
377+
}
378+
} else {
379+
// do not start with `r`, cannot be a raw ident
380+
(start, false)
381+
};
282382
while let Some(&(end, ch)) = self.s.peek() {
283383
if !is_ident_rest(ch) {
284-
return Some(Ok(Token::Ident(&self.orig[start..end])));
384+
return Some(Ok(Token::Ident(raw, &self.orig[start..end])));
285385
} else {
286386
self.s.next();
287387
}
288388
}
289-
return Some(Ok(Token::Ident(&self.orig[start..])));
389+
return Some(Ok(Token::Ident(raw, &self.orig[start..])));
290390
}
291391
Some((_, ch)) => {
292392
return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch))));

Diff for: crates/cargo-platform/src/lib.rs

+47-2
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111
//!
1212
//! [`Platform`]: enum.Platform.html
1313
14-
use std::fmt;
1514
use std::str::FromStr;
15+
use std::{fmt, path::Path};
1616

1717
mod cfg;
1818
mod error;
1919

20-
pub use cfg::{Cfg, CfgExpr};
20+
use cfg::KEYWORDS;
21+
pub use cfg::{Cfg, CfgExpr, Ident};
2122
pub use error::{ParseError, ParseErrorKind};
2223

2324
/// Platform definition.
@@ -104,6 +105,50 @@ impl Platform {
104105
check_cfg_expr(cfg, warnings);
105106
}
106107
}
108+
109+
pub fn check_cfg_keywords(&self, warnings: &mut Vec<String>, path: &Path) {
110+
fn check_cfg_expr(expr: &CfgExpr, warnings: &mut Vec<String>, path: &Path) {
111+
match *expr {
112+
CfgExpr::Not(ref e) => check_cfg_expr(e, warnings, path),
113+
CfgExpr::All(ref e) | CfgExpr::Any(ref e) => {
114+
for e in e {
115+
check_cfg_expr(e, warnings, path);
116+
}
117+
}
118+
CfgExpr::Value(ref e) => match e {
119+
Cfg::Name(name) | Cfg::KeyPair(name, _) => {
120+
if !name.raw && KEYWORDS.contains(&name.as_str()) {
121+
if name.as_str() == "true" || name.as_str() == "false" {
122+
warnings.push(format!(
123+
"[{}] future-incompatibility: the meaning of `cfg({e})` will change in the future\n \
124+
| Cargo is erroneously allowing `cfg(true)` and `cfg(false)`, but both forms are interpreted as false unless manually overridden with `--cfg`.\n \
125+
| In the future these will be built-in defines that will have the corresponding true/false value.\n \
126+
| It is recommended to avoid using these configs until they are properly supported.\n \
127+
| See <https://github.com/rust-lang/rust/issues/131204> for more information.\n \
128+
|\n \
129+
| help: use raw-idents instead: `cfg(r#{name})`",
130+
path.display()
131+
));
132+
} else {
133+
warnings.push(format!(
134+
"[{}] future-incompatibility: `cfg({e})` is deprecated as `{name}` is a keyword \
135+
and not an identifier and should not have have been accepted in this position.\n \
136+
| this was previously accepted by Cargo but is being phased out; it will become a hard error in a future release!\n \
137+
|\n \
138+
| help: use raw-idents instead: `cfg(r#{name})`",
139+
path.display()
140+
));
141+
}
142+
}
143+
}
144+
},
145+
}
146+
}
147+
148+
if let Platform::Cfg(cfg) = self {
149+
check_cfg_expr(cfg, warnings, path);
150+
}
151+
}
107152
}
108153

109154
impl serde::Serialize for Platform {

Diff for: crates/cargo-platform/tests/test_cfg.rs

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,37 @@
1-
use cargo_platform::{Cfg, CfgExpr, Platform};
1+
use cargo_platform::{Cfg, CfgExpr, Ident, Platform};
22
use std::fmt;
33
use std::str::FromStr;
44

55
macro_rules! c {
66
($a:ident) => {
7-
Cfg::Name(stringify!($a).to_string())
7+
Cfg::Name(Ident {
8+
name: stringify!($a).to_string(),
9+
raw: false,
10+
})
11+
};
12+
(r # $a:ident) => {
13+
Cfg::Name(Ident {
14+
name: stringify!($a).to_string(),
15+
raw: true,
16+
})
817
};
918
($a:ident = $e:expr) => {
10-
Cfg::KeyPair(stringify!($a).to_string(), $e.to_string())
19+
Cfg::KeyPair(
20+
Ident {
21+
name: stringify!($a).to_string(),
22+
raw: false,
23+
},
24+
$e.to_string(),
25+
)
26+
};
27+
(r # $a:ident = $e:expr) => {
28+
Cfg::KeyPair(
29+
Ident {
30+
name: stringify!($a).to_string(),
31+
raw: true,
32+
},
33+
$e.to_string(),
34+
)
1135
};
1236
}
1337

@@ -56,10 +80,13 @@ fn cfg_syntax() {
5680
good("_bar", c!(_bar));
5781
good(" foo", c!(foo));
5882
good(" foo ", c!(foo));
83+
good("r#foo", c!(r # foo));
5984
good(" foo = \"bar\"", c!(foo = "bar"));
6085
good("foo=\"\"", c!(foo = ""));
86+
good("r#foo=\"\"", c!(r # foo = ""));
6187
good(" foo=\"3\" ", c!(foo = "3"));
6288
good("foo = \"3 e\"", c!(foo = "3 e"));
89+
good(" r#foo = \"3 e\"", c!(r # foo = "3 e"));
6390
}
6491

6592
#[test]
@@ -78,6 +105,10 @@ fn cfg_syntax_bad() {
78105
"foo, bar",
79106
"unexpected content `, bar` found after cfg expression",
80107
);
108+
bad::<Cfg>("r# foo", "unexpected character");
109+
bad::<Cfg>("r #foo", "unexpected content");
110+
bad::<Cfg>("r#\"foo\"", "unexpected character");
111+
bad::<Cfg>("foo = r#\"\"", "unexpected character");
81112
}
82113

83114
#[test]
@@ -126,6 +157,9 @@ fn cfg_matches() {
126157
assert!(e!(not(foo)).matches(&[]));
127158
assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(bar)]));
128159
assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo), c!(bar)]));
160+
assert!(e!(foo).matches(&[c!(r # foo)]));
161+
assert!(e!(r # foo).matches(&[c!(foo)]));
162+
assert!(e!(r # foo).matches(&[c!(r # foo)]));
129163

130164
assert!(!e!(foo).matches(&[]));
131165
assert!(!e!(foo).matches(&[c!(bar)]));

Diff for: src/cargo/core/compiler/custom_build.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,9 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
353353
// That is because Cargo queries rustc without any profile settings.
354354
continue;
355355
}
356-
let k = format!("CARGO_CFG_{}", super::envify(&k));
356+
// FIXME: We should handle raw-idents somehow instead of predenting they
357+
// don't exist here
358+
let k = format!("CARGO_CFG_{}", super::envify(k.as_str()));
357359
cmd.env(&k, v.join(","));
358360
}
359361

0 commit comments

Comments
 (0)