Skip to content

Commit 4029160

Browse files
Improve the implication check algorithm
1 parent 71d5405 commit 4029160

2 files changed

Lines changed: 46 additions & 30 deletions

File tree

clippy_utils/src/lib.rs

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![feature(macro_metavar_expr)]
33
#![feature(rustc_private)]
44
#![feature(unwrap_infallible)]
5+
#![feature(control_flow_into_value)]
56
#![recursion_limit = "512"]
67
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
78
#![warn(
@@ -2475,36 +2476,51 @@ pub fn is_test_function(tcx: TyCtxt<'_>, fn_def_id: LocalDefId) -> bool {
24752476
}
24762477
}
24772478

2478-
/// Checks if the provided `CfgEntry` evaluation to `true` implies something one the value of the
2479-
/// `test` attribute.
2479+
/// Checks if the provided `CfgEntry` evaluation to `true` implies the `test` attribute is equal to
2480+
/// `value`.
24802481
///
2481-
/// When the predicate can be determined statically, returns `Some(true)` or `Some(false)`,
2482-
/// depending on whether the entry implies `test` or `not(test)` respectively.
2483-
/// If nothing can be said regarding the value of `test`, returns `None`.
2484-
fn cfg_implies_test(cfg: &CfgEntry) -> Option<bool> {
2485-
match cfg {
2486-
CfgEntry::NameValue { name: sym::test, .. } => Some(true),
2487-
CfgEntry::All(subs, _) => subs
2488-
.iter()
2489-
.map(cfg_implies_test)
2490-
.reduce(|a, b| match (a, b) {
2491-
(Some(true), Some(true)) => Some(true),
2492-
(Some(false), Some(false)) => Some(false),
2493-
(None, Some(a)) | (Some(a), None) => Some(a),
2494-
_ => None,
2495-
})
2496-
.unwrap_or(None),
2497-
CfgEntry::Any(subs, _) => subs
2498-
.iter()
2499-
.map(cfg_implies_test)
2500-
.reduce(|a, b| match (a, b) {
2501-
(Some(true), Some(true)) => Some(true),
2502-
(Some(false), Some(false)) => Some(false),
2503-
_ => None,
2504-
})
2505-
.unwrap_or(None),
2506-
_ => None,
2482+
/// Returns `Some(true)` if $$cfg \implies test = value$$ is a tautology.
2483+
/// Returns `Some(false)` if $$cfg \implies test = value$$ is a contradiction.
2484+
/// If the algorithm cannot determine the implication, returns `None`.
2485+
///
2486+
/// # Note
2487+
/// This function is not a generic implication check and might return `None` even if the implication
2488+
/// could be proven. But itshould be sufficient for most real world configuration predicates.
2489+
fn cfg_implies_test_is(value: bool, cfg: &CfgEntry) -> Option<bool> {
2490+
/// Evaluates the Shannon cofactor of `cfg` with respect to `test = value`.
2491+
///
2492+
/// Returns `Some(true)` if the cofactor is a tautology, `Some(false)` if it's a contradiction,
2493+
/// and `None` if the algorithm cannot determine the cofactor's value.
2494+
fn evaluate_shannon_cofactor(cfg: &CfgEntry, value: bool) -> Option<bool> {
2495+
match cfg {
2496+
CfgEntry::NameValue { name: sym::test, .. } => Some(value),
2497+
CfgEntry::All(subs, _) => subs
2498+
.iter()
2499+
.try_fold(Some(true), |a, c| match evaluate_shannon_cofactor(c, value) {
2500+
Some(false) => ControlFlow::Break(Some(false)),
2501+
Some(true) => ControlFlow::Continue(a),
2502+
None => ControlFlow::Continue(None),
2503+
})
2504+
.into_value(),
2505+
CfgEntry::Any(subs, _) => subs
2506+
.iter()
2507+
.try_fold(Some(false), |a, c| match evaluate_shannon_cofactor(c, value) {
2508+
Some(true) => ControlFlow::Break(Some(true)),
2509+
Some(false) => ControlFlow::Continue(a),
2510+
None => ControlFlow::Continue(None),
2511+
})
2512+
.into_value(),
2513+
CfgEntry::Not(sub, _) => evaluate_shannon_cofactor(sub, value).map(|x| !x),
2514+
CfgEntry::Bool(val, _) => Some(*val),
2515+
CfgEntry::NameValue { .. } | CfgEntry::Version { .. } => None,
2516+
}
25072517
}
2518+
2519+
// According to Boole's expansion theorem, $$cfg = (test \land cfg_{test}) \lor (\lnot test
2520+
// \land cfg_{\lnot test})$$
2521+
// Therefore, $$cfg \implies test = value$$ is equivalent to $$cfg_{test=\lnot value}$$ being a
2522+
// contradiction.
2523+
evaluate_shannon_cofactor(cfg, !value).map(|x| !x)
25082524
}
25092525

25102526
/// Checks if `id` has a `ConfigurationPredicate` attribute applied that evaluates to true only if
@@ -2518,7 +2534,7 @@ pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
25182534
.iter()
25192535
// If the dependency cannot be determined statically, we conservatively assume that it does compile even
25202536
// without `test` enabled.
2521-
.any(|(cfg, _)| matches!(cfg_implies_test(cfg), Some(true)))
2537+
.any(|(cfg, _)| matches!(cfg_implies_test_is(true, cfg), Some(true)))
25222538
{
25232539
true
25242540
} else {

tests/ui-toml/unwrap_used/unwrap_used_in_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ mod issue16369 {
2222

2323
#[cfg(any(test, true))]
2424
mod any {
25-
// should lint modules that can can be compiled without the `test` attribute
25+
// should lint modules that can be compiled without the `test` attribute
2626
fn test_fn() {
2727
let _a: u8 = Some(2).unwrap(); //~ unwrap_used
2828
let _a: u8 = Some(3).expect(""); //~ expect_used

0 commit comments

Comments
 (0)