Skip to content

Commit d150663

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

File tree

5 files changed

+144
-22
lines changed

5 files changed

+144
-22
lines changed

crates/cargo-platform/src/cfg.rs

+96-10
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,27 @@ 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+
/// Only used for display
32+
pub raw: bool,
2233
}
2334

2435
#[derive(PartialEq)]
2536
enum Token<'a> {
2637
LeftParen,
2738
RightParen,
28-
Ident(&'a str),
39+
Ident(bool, &'a str),
2940
Comma,
3041
Equals,
3142
String(&'a str),
@@ -52,6 +63,41 @@ struct Parser<'a> {
5263
t: Tokenizer<'a>,
5364
}
5465

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

@@ -155,7 +201,8 @@ impl<'a> Parser<'a> {
155201

156202
fn expr(&mut self) -> Result<CfgExpr, ParseError> {
157203
match self.peek() {
158-
Some(Ok(Token::Ident(op @ "all"))) | Some(Ok(Token::Ident(op @ "any"))) => {
204+
Some(Ok(Token::Ident(false, op @ "all")))
205+
| Some(Ok(Token::Ident(false, op @ "any"))) => {
159206
self.t.next();
160207
let mut e = Vec::new();
161208
self.eat(&Token::LeftParen)?;
@@ -172,7 +219,7 @@ impl<'a> Parser<'a> {
172219
Ok(CfgExpr::Any(e))
173220
}
174221
}
175-
Some(Ok(Token::Ident("not"))) => {
222+
Some(Ok(Token::Ident(false, "not"))) => {
176223
self.t.next();
177224
self.eat(&Token::LeftParen)?;
178225
let e = self.expr()?;
@@ -190,7 +237,7 @@ impl<'a> Parser<'a> {
190237

191238
fn cfg(&mut self) -> Result<Cfg, ParseError> {
192239
match self.t.next() {
193-
Some(Ok(Token::Ident(name))) => {
240+
Some(Ok(Token::Ident(raw, name))) => {
194241
let e = if self.r#try(&Token::Equals) {
195242
let val = match self.t.next() {
196243
Some(Ok(Token::String(s))) => s,
@@ -208,9 +255,18 @@ impl<'a> Parser<'a> {
208255
return Err(ParseError::new(self.t.orig, IncompleteExpr("a string")))
209256
}
210257
};
211-
Cfg::KeyPair(name.to_string(), val.to_string())
258+
Cfg::KeyPair(
259+
Ident {
260+
name: name.to_string(),
261+
raw,
262+
},
263+
val.to_string(),
264+
)
212265
} else {
213-
Cfg::Name(name.to_string())
266+
Cfg::Name(Ident {
267+
name: name.to_string(),
268+
raw,
269+
})
214270
};
215271
Ok(e)
216272
}
@@ -290,14 +346,44 @@ impl<'a> Iterator for Tokenizer<'a> {
290346
return Some(Err(ParseError::new(self.orig, UnterminatedString)));
291347
}
292348
Some((start, ch)) if is_ident_start(ch) => {
349+
let (start, raw) = if ch == 'r' {
350+
if let Some(&(_pos, '#')) = self.s.peek() {
351+
// starts with `r#` is a raw ident
352+
self.s.next();
353+
if let Some((start, ch)) = self.s.next() {
354+
if is_ident_start(ch) {
355+
(start, true)
356+
} else {
357+
// not a starting ident character
358+
return Some(Err(ParseError::new(
359+
self.orig,
360+
UnexpectedChar(ch),
361+
)));
362+
}
363+
} else {
364+
// not followed by a ident, error out
365+
return Some(Err(ParseError::new(
366+
self.orig,
367+
IncompleteExpr("identifier"),
368+
)));
369+
}
370+
} else {
371+
// starts with `r` but not does continue with `#`
372+
// cannot be a raw ident
373+
(start, false)
374+
}
375+
} else {
376+
// do not start with `r`, cannot be a raw ident
377+
(start, false)
378+
};
293379
while let Some(&(end, ch)) = self.s.peek() {
294380
if !is_ident_rest(ch) {
295-
return Some(Ok(Token::Ident(&self.orig[start..end])));
381+
return Some(Ok(Token::Ident(raw, &self.orig[start..end])));
296382
} else {
297383
self.s.next();
298384
}
299385
}
300-
return Some(Ok(Token::Ident(&self.orig[start..])));
386+
return Some(Ok(Token::Ident(raw, &self.orig[start..])));
301387
}
302388
Some((_, ch)) => {
303389
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
@@ -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

+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)