Skip to content

Commit

Permalink
Add support for raw-idents in cfgs
Browse files Browse the repository at this point in the history
  • Loading branch information
Urgau committed Nov 1, 2024
1 parent dce3ea2 commit 62879c2
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 22 deletions.
107 changes: 97 additions & 10 deletions crates/cargo-platform/src/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,28 @@ pub enum CfgExpr {
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
pub enum Cfg {
/// A named cfg value, like `unix`.
Name(String),
Name(Ident),
/// A key/value cfg pair, like `target_os = "linux"`.
KeyPair(String, String),
KeyPair(Ident, String),
}

/// A identifier
#[derive(Hash, Ord, PartialOrd, Clone, Debug)]
pub struct Ident {
/// The identifier
pub name: String,
/// Is this a raw ident: `r#async`
///
/// It's mainly used for display and doesn't
/// take part in the `PartialEq` as `foo` == `r#foo`.
pub raw: bool,
}

#[derive(PartialEq)]
enum Token<'a> {
LeftParen,
RightParen,
Ident(&'a str),
Ident(bool, &'a str),
Comma,
Equals,
String(&'a str),
Expand All @@ -49,6 +61,41 @@ struct Parser<'a> {
t: Tokenizer<'a>,
}

impl Ident {
pub fn as_str(&self) -> &str {
&self.name
}
}

impl Eq for Ident {}

impl PartialEq<str> for Ident {
fn eq(&self, other: &str) -> bool {
self.name == other
}
}

impl PartialEq<&str> for Ident {
fn eq(&self, other: &&str) -> bool {
self.name == *other
}
}

impl PartialEq<Ident> for Ident {
fn eq(&self, other: &Ident) -> bool {
self.name == other.name
}
}

impl fmt::Display for Ident {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.raw {
f.write_str("r#")?;
}
f.write_str(&*self.name)
}
}

impl FromStr for Cfg {
type Err = ParseError;

Expand Down Expand Up @@ -152,7 +199,8 @@ impl<'a> Parser<'a> {

fn expr(&mut self) -> Result<CfgExpr, ParseError> {
match self.peek() {
Some(Ok(Token::Ident(op @ "all"))) | Some(Ok(Token::Ident(op @ "any"))) => {
Some(Ok(Token::Ident(false, op @ "all")))
| Some(Ok(Token::Ident(false, op @ "any"))) => {
self.t.next();
let mut e = Vec::new();
self.eat(&Token::LeftParen)?;
Expand All @@ -169,7 +217,7 @@ impl<'a> Parser<'a> {
Ok(CfgExpr::Any(e))
}
}
Some(Ok(Token::Ident("not"))) => {
Some(Ok(Token::Ident(false, "not"))) => {
self.t.next();
self.eat(&Token::LeftParen)?;
let e = self.expr()?;
Expand All @@ -187,7 +235,7 @@ impl<'a> Parser<'a> {

fn cfg(&mut self) -> Result<Cfg, ParseError> {
match self.t.next() {
Some(Ok(Token::Ident(name))) => {
Some(Ok(Token::Ident(raw, name))) => {
let e = if self.r#try(&Token::Equals) {
let val = match self.t.next() {
Some(Ok(Token::String(s))) => s,
Expand All @@ -205,9 +253,18 @@ impl<'a> Parser<'a> {
return Err(ParseError::new(self.t.orig, IncompleteExpr("a string")))
}
};
Cfg::KeyPair(name.to_string(), val.to_string())
Cfg::KeyPair(
Ident {
name: name.to_string(),
raw,
},
val.to_string(),
)
} else {
Cfg::Name(name.to_string())
Cfg::Name(Ident {
name: name.to_string(),
raw,
})
};
Ok(e)
}
Expand Down Expand Up @@ -287,14 +344,44 @@ impl<'a> Iterator for Tokenizer<'a> {
return Some(Err(ParseError::new(self.orig, UnterminatedString)));
}
Some((start, ch)) if is_ident_start(ch) => {
let (start, raw) = if ch == 'r' {
if let Some(&(_pos, '#')) = self.s.peek() {
// starts with `r#` is a raw ident
self.s.next();
if let Some((start, ch)) = self.s.next() {
if is_ident_start(ch) {
(start, true)
} else {
// not a starting ident character
return Some(Err(ParseError::new(
self.orig,
UnexpectedChar(ch),
)));
}
} else {
// not followed by a ident, error out
return Some(Err(ParseError::new(
self.orig,
IncompleteExpr("identifier"),
)));
}
} else {
// starts with `r` but not does continue with `#`
// cannot be a raw ident
(start, false)
}
} else {
// do not start with `r`, cannot be a raw ident
(start, false)
};
while let Some(&(end, ch)) = self.s.peek() {
if !is_ident_rest(ch) {
return Some(Ok(Token::Ident(&self.orig[start..end])));
return Some(Ok(Token::Ident(raw, &self.orig[start..end])));
} else {
self.s.next();
}
}
return Some(Ok(Token::Ident(&self.orig[start..])));
return Some(Ok(Token::Ident(raw, &self.orig[start..])));
}
Some((_, ch)) => {
return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch))));
Expand Down
2 changes: 1 addition & 1 deletion crates/cargo-platform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ mod cfg;
mod error;

use cfg::KEYWORDS;
pub use cfg::{Cfg, CfgExpr};
pub use cfg::{Cfg, CfgExpr, Ident};
pub use error::{ParseError, ParseErrorKind};

/// Platform definition.
Expand Down
40 changes: 37 additions & 3 deletions crates/cargo-platform/tests/test_cfg.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
use cargo_platform::{Cfg, CfgExpr, Platform};
use cargo_platform::{Cfg, CfgExpr, Ident, Platform};
use std::fmt;
use std::str::FromStr;

macro_rules! c {
($a:ident) => {
Cfg::Name(stringify!($a).to_string())
Cfg::Name(Ident {
name: stringify!($a).to_string(),
raw: false,
})
};
(r # $a:ident) => {
Cfg::Name(Ident {
name: stringify!($a).to_string(),
raw: true,
})
};
($a:ident = $e:expr) => {
Cfg::KeyPair(stringify!($a).to_string(), $e.to_string())
Cfg::KeyPair(
Ident {
name: stringify!($a).to_string(),
raw: false,
},
$e.to_string(),
)
};
(r # $a:ident = $e:expr) => {
Cfg::KeyPair(
Ident {
name: stringify!($a).to_string(),
raw: true,
},
$e.to_string(),
)
};
}

Expand Down Expand Up @@ -56,10 +80,13 @@ fn cfg_syntax() {
good("_bar", c!(_bar));
good(" foo", c!(foo));
good(" foo ", c!(foo));
good("r#foo", c!(r # foo));
good(" foo = \"bar\"", c!(foo = "bar"));
good("foo=\"\"", c!(foo = ""));
good("r#foo=\"\"", c!(r # foo = ""));
good(" foo=\"3\" ", c!(foo = "3"));
good("foo = \"3 e\"", c!(foo = "3 e"));
good(" r#foo = \"3 e\"", c!(r # foo = "3 e"));
}

#[test]
Expand All @@ -78,6 +105,10 @@ fn cfg_syntax_bad() {
"foo, bar",
"unexpected content `, bar` found after cfg expression",
);
bad::<Cfg>("r# foo", "unexpected character");
bad::<Cfg>("r #foo", "unexpected content");
bad::<Cfg>("r#\"foo\"", "unexpected character");
bad::<Cfg>("foo = r#\"\"", "unexpected character");
}

#[test]
Expand Down Expand Up @@ -126,6 +157,9 @@ fn cfg_matches() {
assert!(e!(not(foo)).matches(&[]));
assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(bar)]));
assert!(e!(any((not(foo)), (all(foo, bar)))).matches(&[c!(foo), c!(bar)]));
assert!(e!(foo).matches(&[c!(r # foo)]));
assert!(e!(r # foo).matches(&[c!(foo)]));
assert!(e!(r # foo).matches(&[c!(r # foo)]));

assert!(!e!(foo).matches(&[]));
assert!(!e!(foo).matches(&[c!(bar)]));
Expand Down
4 changes: 3 additions & 1 deletion src/cargo/core/compiler/custom_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,9 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
// That is because Cargo queries rustc without any profile settings.
continue;
}
let k = format!("CARGO_CFG_{}", super::envify(&k));
// FIXME: We should handle raw-idents somehow instead of predenting they
// don't exist here
let k = format!("CARGO_CFG_{}", super::envify(k.as_str()));
cmd.env(&k, v.join(","));
}

Expand Down
14 changes: 7 additions & 7 deletions tests/testsuite/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,12 +543,12 @@ fn cfg_raw_idents() {
.build();

p.cargo("check")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
Caused by:
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
[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.
| this was previously accepted by Cargo but is being phased out; it will become a hard error in a future release!
[LOCKING] 1 package to latest compatible version
[CHECKING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
Expand Down Expand Up @@ -577,7 +577,7 @@ fn cfg_raw_idents_empty() {
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
Caused by:
failed to parse `r#)` as a cfg expression: unexpected content `#)` found after cfg expression
failed to parse `r#)` as a cfg expression: unexpected character `)` in cfg, expected parens, a comma, an identifier, or a string
"#]])
.run();
Expand Down Expand Up @@ -606,7 +606,7 @@ fn cfg_raw_idents_not_really() {
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
Caused by:
failed to parse `r#11)` as a cfg expression: unexpected content `#11)` found after cfg expression
failed to parse `r#11)` as a cfg expression: unexpected character `1` in cfg, expected parens, a comma, an identifier, or a string
"#]])
.run();
Expand Down

0 comments on commit 62879c2

Please sign in to comment.