Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lint against unexpected cfgs in [target.'cfg(...)'] #14581

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/cargo-platform/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-platform"
version = "0.2.0"
version = "0.2.1"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
Expand Down
22 changes: 22 additions & 0 deletions crates/cargo-platform/src/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,28 @@ impl CfgExpr {
CfgExpr::Value(ref e) => cfg.contains(e),
}
}

/// Walk over all the `Cfg`s of the given `CfgExpr`, recursing into `not(...)`, `all(...)`,
/// `any(...)` and stopping at the first error and returning that error.
pub fn walk<E>(&self, mut f: impl FnMut(&Cfg) -> Result<(), E>) -> Result<(), E> {
fn walk_inner<E>(
cfg_expr: &CfgExpr,
f: &mut impl FnMut(&Cfg) -> Result<(), E>,
) -> Result<(), E> {
match *cfg_expr {
CfgExpr::Not(ref e) => walk_inner(e, &mut *f),
CfgExpr::All(ref e) | CfgExpr::Any(ref e) => {
for e in e {
let _ = walk_inner(e, &mut *f)?;
}
Ok(())
}
CfgExpr::Value(ref e) => f(e),
}
}

walk_inner(self, &mut f)
}
}

impl FromStr for CfgExpr {
Expand Down
98 changes: 98 additions & 0 deletions crates/cargo-platform/src/check_cfg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! check-cfg

use std::{
collections::{HashMap, HashSet},
error::Error,
fmt::Display,
};

/// Check Config (aka `--check-cfg`/`--print=check-cfg` representation)
#[derive(Debug, Default, Clone)]
pub struct CheckCfg {
Urgau marked this conversation as resolved.
Show resolved Hide resolved
/// Is `--check-cfg` activated
pub exhaustive: bool,
/// List of expected cfgs
pub expecteds: HashMap<String, ExpectedValues>,
}

/// List of expected check-cfg values
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExpectedValues {
/// List of expected values
///
/// - `#[cfg(foo)]` value is `None`
/// - `#[cfg(foo = "")]` value is `Some("")`
/// - `#[cfg(foo = "bar")]` value is `Some("bar")`
Some(HashSet<Option<String>>),
/// All values expected
Any,
}

/// Error when parse a line from `--print=check-cfg`
#[derive(Debug)]
#[non_exhaustive]
pub struct PrintCheckCfgParsingError;

impl Error for PrintCheckCfgParsingError {}

impl Display for PrintCheckCfgParsingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("error when parsing a `--print=check-cfg` line")
}
}

impl CheckCfg {
/// Parse a line from `--print=check-cfg`
pub fn parse_print_check_cfg_line(
&mut self,
line: &str,
) -> Result<(), PrintCheckCfgParsingError> {
if line == "any()=any()" || line == "any()" {
self.exhaustive = false;
return Ok(());
}

let mut value: HashSet<Option<String>> = HashSet::default();
let mut value_any_specified = false;
let name: String;

if let Some((n, val)) = line.split_once('=') {
name = n.to_string();

if val == "any()" {
value_any_specified = true;
} else if val.is_empty() {
// no value, nothing to add
} else if let Some(val) = maybe_quoted_value(val) {
value.insert(Some(val.to_string()));
} else {
// missing quotes and non-empty
return Err(PrintCheckCfgParsingError);
}
} else {
name = line.to_string();
value.insert(None);
}

self.expecteds
.entry(name)
.and_modify(|v| match v {
ExpectedValues::Some(_) if value_any_specified => *v = ExpectedValues::Any,
ExpectedValues::Some(v) => v.extend(value.clone()),
ExpectedValues::Any => {}
})
.or_insert_with(|| {
if value_any_specified {
ExpectedValues::Any
} else {
ExpectedValues::Some(value)
}
});
Ok(())
}
}

fn maybe_quoted_value<'a>(v: &'a str) -> Option<&'a str> {
// strip "" around the value, e.g. "linux" -> linux
v.strip_prefix('"').and_then(|v| v.strip_suffix('"'))
}
2 changes: 2 additions & 0 deletions crates/cargo-platform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ use std::str::FromStr;
use std::{fmt, path::Path};

mod cfg;
mod check_cfg;
mod error;

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

/// Platform definition.
Expand Down
123 changes: 123 additions & 0 deletions crates/cargo-platform/tests/test_print_check_cfg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use cargo_platform::{CheckCfg, ExpectedValues};
use std::collections::HashSet;

#[test]
fn print_check_cfg_none() {
let mut check_cfg = CheckCfg::default();

check_cfg.parse_print_check_cfg_line("cfg_a").unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg_a").unwrap(),
ExpectedValues::Some(HashSet::from([None]))
);
}

#[test]
fn print_check_cfg_empty() {
let mut check_cfg = CheckCfg::default();

check_cfg.parse_print_check_cfg_line("cfg_b=").unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg_b").unwrap(),
ExpectedValues::Some(HashSet::from([]))
);
}

#[test]
fn print_check_cfg_any() {
let mut check_cfg = CheckCfg::default();

check_cfg.parse_print_check_cfg_line("cfg_c=any()").unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg_c").unwrap(),
ExpectedValues::Any
);

check_cfg.parse_print_check_cfg_line("cfg_c").unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg_c").unwrap(),
ExpectedValues::Any
);
}

#[test]
fn print_check_cfg_value() {
let mut check_cfg = CheckCfg::default();

check_cfg
.parse_print_check_cfg_line("cfg_d=\"test\"")
.unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg_d").unwrap(),
ExpectedValues::Some(HashSet::from([Some("test".to_string())]))
);

check_cfg
.parse_print_check_cfg_line("cfg_d=\"tmp\"")
.unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg_d").unwrap(),
ExpectedValues::Some(HashSet::from([
Some("test".to_string()),
Some("tmp".to_string())
]))
);
}

#[test]
fn print_check_cfg_none_and_value() {
let mut check_cfg = CheckCfg::default();

check_cfg.parse_print_check_cfg_line("cfg").unwrap();
check_cfg.parse_print_check_cfg_line("cfg=\"foo\"").unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg").unwrap(),
ExpectedValues::Some(HashSet::from([None, Some("foo".to_string())]))
);
}

#[test]
fn print_check_cfg_quote_in_value() {
let mut check_cfg = CheckCfg::default();

check_cfg
.parse_print_check_cfg_line("cfg_e=\"quote_in_value\"\"")
.unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg_e").unwrap(),
ExpectedValues::Some(HashSet::from([Some("quote_in_value\"".to_string())]))
);
}

#[test]
fn print_check_cfg_value_and_any() {
let mut check_cfg = CheckCfg::default();

// having both a value and `any()` shouldn't be possible but better
// handle this correctly anyway

check_cfg
.parse_print_check_cfg_line("cfg_1=\"foo\"")
.unwrap();
check_cfg.parse_print_check_cfg_line("cfg_1=any()").unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg_1").unwrap(),
ExpectedValues::Any
);

check_cfg.parse_print_check_cfg_line("cfg_2=any()").unwrap();
check_cfg
.parse_print_check_cfg_line("cfg_2=\"foo\"")
.unwrap();
assert_eq!(
*check_cfg.expecteds.get("cfg_2").unwrap(),
ExpectedValues::Any
);
}

#[test]
#[should_panic]
fn print_check_cfg_missing_quote_value() {
let mut check_cfg = CheckCfg::default();
check_cfg.parse_print_check_cfg_line("foo=bar").unwrap();
}
Loading