Skip to content

Commit 62879c2

Browse files
committed
Add support for raw-idents in cfgs
1 parent dce3ea2 commit 62879c2

File tree

5 files changed

+145
-22
lines changed

5 files changed

+145
-22
lines changed

crates/cargo-platform/src/cfg.rs

+97-10
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,28 @@ pub enum CfgExpr {
1616
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
1717
pub enum Cfg {
1818
/// A named cfg value, like `unix`.
19-
Name(String),
19+
Name(Ident),
2020
/// A key/value cfg pair, like `target_os = "linux"`.
21-
KeyPair(String, String),
21+
KeyPair(Ident, String),
22+
}
23+
24+
/// A identifier
25+
#[derive(Hash, Ord, PartialOrd, Clone, Debug)]
26+
pub struct Ident {
27+
/// The identifier
28+
pub name: String,
29+
/// Is this a raw ident: `r#async`
30+
///
31+
/// It's mainly used for display and doesn't
32+
/// take part in the `PartialEq` as `foo` == `r#foo`.
33+
pub raw: bool,
2234
}
2335

2436
#[derive(PartialEq)]
2537
enum Token<'a> {
2638
LeftParen,
2739
RightParen,
28-
Ident(&'a str),
40+
Ident(bool, &'a str),
2941
Comma,
3042
Equals,
3143
String(&'a str),
@@ -49,6 +61,41 @@ struct Parser<'a> {
4961
t: Tokenizer<'a>,
5062
}
5163

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

@@ -152,7 +199,8 @@ impl<'a> Parser<'a> {
152199

153200
fn expr(&mut self) -> Result<CfgExpr, ParseError> {
154201
match self.peek() {
155-
Some(Ok(Token::Ident(op @ "all"))) | Some(Ok(Token::Ident(op @ "any"))) => {
202+
Some(Ok(Token::Ident(false, op @ "all")))
203+
| Some(Ok(Token::Ident(false, op @ "any"))) => {
156204
self.t.next();
157205
let mut e = Vec::new();
158206
self.eat(&Token::LeftParen)?;
@@ -169,7 +217,7 @@ impl<'a> Parser<'a> {
169217
Ok(CfgExpr::Any(e))
170218
}
171219
}
172-
Some(Ok(Token::Ident("not"))) => {
220+
Some(Ok(Token::Ident(false, "not"))) => {
173221
self.t.next();
174222
self.eat(&Token::LeftParen)?;
175223
let e = self.expr()?;
@@ -187,7 +235,7 @@ impl<'a> Parser<'a> {
187235

188236
fn cfg(&mut self) -> Result<Cfg, ParseError> {
189237
match self.t.next() {
190-
Some(Ok(Token::Ident(name))) => {
238+
Some(Ok(Token::Ident(raw, name))) => {
191239
let e = if self.r#try(&Token::Equals) {
192240
let val = match self.t.next() {
193241
Some(Ok(Token::String(s))) => s,
@@ -205,9 +253,18 @@ impl<'a> Parser<'a> {
205253
return Err(ParseError::new(self.t.orig, IncompleteExpr("a string")))
206254
}
207255
};
208-
Cfg::KeyPair(name.to_string(), val.to_string())
256+
Cfg::KeyPair(
257+
Ident {
258+
name: name.to_string(),
259+
raw,
260+
},
261+
val.to_string(),
262+
)
209263
} else {
210-
Cfg::Name(name.to_string())
264+
Cfg::Name(Ident {
265+
name: name.to_string(),
266+
raw,
267+
})
211268
};
212269
Ok(e)
213270
}
@@ -287,14 +344,44 @@ impl<'a> Iterator for Tokenizer<'a> {
287344
return Some(Err(ParseError::new(self.orig, UnterminatedString)));
288345
}
289346
Some((start, ch)) if is_ident_start(ch) => {
347+
let (start, raw) = if ch == 'r' {
348+
if let Some(&(_pos, '#')) = self.s.peek() {
349+
// starts with `r#` is a raw ident
350+
self.s.next();
351+
if let Some((start, ch)) = self.s.next() {
352+
if is_ident_start(ch) {
353+
(start, true)
354+
} else {
355+
// not a starting ident character
356+
return Some(Err(ParseError::new(
357+
self.orig,
358+
UnexpectedChar(ch),
359+
)));
360+
}
361+
} else {
362+
// not followed by a ident, error out
363+
return Some(Err(ParseError::new(
364+
self.orig,
365+
IncompleteExpr("identifier"),
366+
)));
367+
}
368+
} else {
369+
// starts with `r` but not does continue with `#`
370+
// cannot be a raw ident
371+
(start, false)
372+
}
373+
} else {
374+
// do not start with `r`, cannot be a raw ident
375+
(start, false)
376+
};
290377
while let Some(&(end, ch)) = self.s.peek() {
291378
if !is_ident_rest(ch) {
292-
return Some(Ok(Token::Ident(&self.orig[start..end])));
379+
return Some(Ok(Token::Ident(raw, &self.orig[start..end])));
293380
} else {
294381
self.s.next();
295382
}
296383
}
297-
return Some(Ok(Token::Ident(&self.orig[start..])));
384+
return Some(Ok(Token::Ident(raw, &self.orig[start..])));
298385
}
299386
Some((_, ch)) => {
300387
return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch))));

crates/cargo-platform/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ mod cfg;
1818
mod error;
1919

2020
use cfg::KEYWORDS;
21-
pub use cfg::{Cfg, CfgExpr};
21+
pub use cfg::{Cfg, CfgExpr, Ident};
2222
pub use error::{ParseError, ParseErrorKind};
2323

2424
/// Platform definition.

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)]));

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

tests/testsuite/cfg.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -543,12 +543,12 @@ fn cfg_raw_idents() {
543543
.build();
544544

545545
p.cargo("check")
546-
.with_status(101)
547546
.with_stderr_data(str![[r#"
548-
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
549-
550-
Caused by:
551-
failed to parse `any(r#fn, r#all, r#target_os = "<>")` as a cfg expression: unexpected character `#` in cfg, expected parens, a comma, an identifier, or a string
547+
[WARNING] [[ROOT]/foo/Cargo.toml] future-incompatibility: `cfg(r#fn)` is deprecated as `r#fn` is a keyword and not an identifier and should not have have been accepted in this position.
548+
| this was previously accepted by Cargo but is being phased out; it will become a hard error in a future release!
549+
[LOCKING] 1 package to latest compatible version
550+
[CHECKING] foo v0.1.0 ([ROOT]/foo)
551+
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
552552
553553
"#]])
554554
.run();
@@ -577,7 +577,7 @@ fn cfg_raw_idents_empty() {
577577
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
578578
579579
Caused by:
580-
failed to parse `r#)` as a cfg expression: unexpected content `#)` found after cfg expression
580+
failed to parse `r#)` as a cfg expression: unexpected character `)` in cfg, expected parens, a comma, an identifier, or a string
581581
582582
"#]])
583583
.run();
@@ -606,7 +606,7 @@ fn cfg_raw_idents_not_really() {
606606
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
607607
608608
Caused by:
609-
failed to parse `r#11)` as a cfg expression: unexpected content `#11)` found after cfg expression
609+
failed to parse `r#11)` as a cfg expression: unexpected character `1` in cfg, expected parens, a comma, an identifier, or a string
610610
611611
"#]])
612612
.run();

0 commit comments

Comments
 (0)