diff --git a/crates/cargo-platform/src/cfg.rs b/crates/cargo-platform/src/cfg.rs index 5753813a5e0b..fe6d1c963592 100644 --- a/crates/cargo-platform/src/cfg.rs +++ b/crates/cargo-platform/src/cfg.rs @@ -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), @@ -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 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 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; @@ -152,7 +199,8 @@ impl<'a> Parser<'a> { fn expr(&mut self) -> Result { 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)?; @@ -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()?; @@ -187,7 +235,7 @@ impl<'a> Parser<'a> { fn cfg(&mut self) -> Result { 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, @@ -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) } @@ -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)))); diff --git a/crates/cargo-platform/src/lib.rs b/crates/cargo-platform/src/lib.rs index c1b42880438b..421085b61b77 100644 --- a/crates/cargo-platform/src/lib.rs +++ b/crates/cargo-platform/src/lib.rs @@ -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. diff --git a/crates/cargo-platform/tests/test_cfg.rs b/crates/cargo-platform/tests/test_cfg.rs index dd99d9a79b85..0fdf307fab72 100644 --- a/crates/cargo-platform/tests/test_cfg.rs +++ b/crates/cargo-platform/tests/test_cfg.rs @@ -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(), + ) }; } @@ -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] @@ -78,6 +105,10 @@ fn cfg_syntax_bad() { "foo, bar", "unexpected content `, bar` found after cfg expression", ); + bad::("r# foo", "unexpected character"); + bad::("r #foo", "unexpected content"); + bad::("r#\"foo\"", "unexpected character"); + bad::("foo = r#\"\"", "unexpected character"); } #[test] @@ -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)])); diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 4ee788df573e..a915bc817da7 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -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(",")); } diff --git a/tests/testsuite/cfg.rs b/tests/testsuite/cfg.rs index fd679a7cc163..f3c852bddb5f 100644 --- a/tests/testsuite/cfg.rs +++ b/tests/testsuite/cfg.rs @@ -543,12 +543,15 @@ 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#true, 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: the meaning of `cfg(r#true)` will change in the future + | Cargo is erroneously allowing `cfg(true)` and `cfg(false)`, but both forms are interpreted as false unless manually overridden with `--cfg`. + | In the future these will be built-in defines that will have the corresponding true/false value. + | It is recommended to avoid using these configs until they are properly supported. + | See for more information. +[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(); @@ -577,7 +580,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(); @@ -606,7 +609,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();