Skip to content

Commit d47a5c9

Browse files
committed
Add support for raw-idents in cfgs
1 parent fa6a78c commit d47a5c9

File tree

5 files changed

+145
-22
lines changed

5 files changed

+145
-22
lines changed

crates/cargo-platform/src/cfg.rs

Lines changed: 97 additions & 10 deletions
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),
@@ -52,6 +64,41 @@ struct Parser<'a> {
5264
t: Tokenizer<'a>,
5365
}
5466

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

@@ -155,7 +202,8 @@ impl<'a> Parser<'a> {
155202

156203
fn expr(&mut self) -> Result<CfgExpr, ParseError> {
157204
match self.peek() {
158-
Some(Ok(Token::Ident(op @ "all"))) | Some(Ok(Token::Ident(op @ "any"))) => {
205+
Some(Ok(Token::Ident(false, op @ "all")))
206+
| Some(Ok(Token::Ident(false, op @ "any"))) => {
159207
self.t.next();
160208
let mut e = Vec::new();
161209
self.eat(&Token::LeftParen)?;
@@ -172,7 +220,7 @@ impl<'a> Parser<'a> {
172220
Ok(CfgExpr::Any(e))
173221
}
174222
}
175-
Some(Ok(Token::Ident("not"))) => {
223+
Some(Ok(Token::Ident(false, "not"))) => {
176224
self.t.next();
177225
self.eat(&Token::LeftParen)?;
178226
let e = self.expr()?;
@@ -190,7 +238,7 @@ impl<'a> Parser<'a> {
190238

191239
fn cfg(&mut self) -> Result<Cfg, ParseError> {
192240
match self.t.next() {
193-
Some(Ok(Token::Ident(name))) => {
241+
Some(Ok(Token::Ident(raw, name))) => {
194242
let e = if self.r#try(&Token::Equals) {
195243
let val = match self.t.next() {
196244
Some(Ok(Token::String(s))) => s,
@@ -208,9 +256,18 @@ impl<'a> Parser<'a> {
208256
return Err(ParseError::new(self.t.orig, IncompleteExpr("a string")))
209257
}
210258
};
211-
Cfg::KeyPair(name.to_string(), val.to_string())
259+
Cfg::KeyPair(
260+
Ident {
261+
name: name.to_string(),
262+
raw,
263+
},
264+
val.to_string(),
265+
)
212266
} else {
213-
Cfg::Name(name.to_string())
267+
Cfg::Name(Ident {
268+
name: name.to_string(),
269+
raw,
270+
})
214271
};
215272
Ok(e)
216273
}
@@ -290,14 +347,44 @@ impl<'a> Iterator for Tokenizer<'a> {
290347
return Some(Err(ParseError::new(self.orig, UnterminatedString)));
291348
}
292349
Some((start, ch)) if is_ident_start(ch) => {
350+
let (start, raw) = if ch == 'r' {
351+
if let Some(&(_pos, '#')) = self.s.peek() {
352+
// starts with `r#` is a raw ident
353+
self.s.next();
354+
if let Some((start, ch)) = self.s.next() {
355+
if is_ident_start(ch) {
356+
(start, true)
357+
} else {
358+
// not a starting ident character
359+
return Some(Err(ParseError::new(
360+
self.orig,
361+
UnexpectedChar(ch),
362+
)));
363+
}
364+
} else {
365+
// not followed by a ident, error out
366+
return Some(Err(ParseError::new(
367+
self.orig,
368+
IncompleteExpr("identifier"),
369+
)));
370+
}
371+
} else {
372+
// starts with `r` but not does continue with `#`
373+
// cannot be a raw ident
374+
(start, false)
375+
}
376+
} else {
377+
// do not start with `r`, cannot be a raw ident
378+
(start, false)
379+
};
293380
while let Some(&(end, ch)) = self.s.peek() {
294381
if !is_ident_rest(ch) {
295-
return Some(Ok(Token::Ident(&self.orig[start..end])));
382+
return Some(Ok(Token::Ident(raw, &self.orig[start..end])));
296383
} else {
297384
self.s.next();
298385
}
299386
}
300-
return Some(Ok(Token::Ident(&self.orig[start..])));
387+
return Some(Ok(Token::Ident(raw, &self.orig[start..])));
301388
}
302389
Some((_, ch)) => {
303390
return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch))));

crates/cargo-platform/src/lib.rs

Lines changed: 1 addition & 1 deletion
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

Lines changed: 37 additions & 3 deletions
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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,9 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
337337
// That is because Cargo queries rustc without any profile settings.
338338
continue;
339339
}
340-
let k = format!("CARGO_CFG_{}", super::envify(&k));
340+
// FIXME: We should handle raw-idents somehow instead of predenting they
341+
// don't exist here
342+
let k = format!("CARGO_CFG_{}", super::envify(k.as_str()));
341343
cmd.env(&k, v.join(","));
342344
}
343345

tests/testsuite/cfg.rs

Lines changed: 7 additions & 7 deletions
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)