Skip to content

Commit f19b5e7

Browse files
committed
Reject conflicting I002 + PYI025 configuration at startup
Fixes #20891. When `lint.isort.required-imports` includes `from collections.abc import Set` (unaliased) and PYI025 is enabled, the two rules conflict: I002 inserts the unaliased import, while PYI025 demands it be aliased as `AbstractSet`. This causes an infinite autofix loop. Rather than patching the import-insertion logic, reject this contradictory configuration at startup in `Configuration::into_settings`, following the existing pattern of `conflicting_import_settings()`. The new `conflicting_required_import_pyi025()` function checks for the specific conflict and emits a clear error message with a suggested fix. Aliasing the required import as `AbstractSet` (satisfying both rules) or disabling PYI025 are both accepted configurations.
1 parent be422f6 commit f19b5e7

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

crates/ruff_workspace/src/configuration.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ impl Configuration {
253253
.unwrap_or_default();
254254

255255
conflicting_import_settings(&isort, &flake8_import_conventions)?;
256+
conflicting_required_import_pyi025(&isort, &rules)?;
256257

257258
let future_annotations = lint.future_annotations.unwrap_or_default();
258259

@@ -1679,6 +1680,40 @@ fn conflicting_import_settings(
16791680
Ok(())
16801681
}
16811682

1683+
/// Detect conflicts between I002 (missing-required-import) and PYI025
1684+
/// (unaliased-collections-abc-set-import).
1685+
///
1686+
/// If `required-imports` includes `from collections.abc import Set` (without
1687+
/// aliasing it as `AbstractSet`) and PYI025 is enabled, the configuration is
1688+
/// contradictory: I002 requires the unaliased import, while PYI025 forbids it.
1689+
fn conflicting_required_import_pyi025(
1690+
isort: &isort::settings::Settings,
1691+
rules: &RuleTable,
1692+
) -> Result<()> {
1693+
if !rules.enabled(Rule::UnaliasedCollectionsAbcSetImport) {
1694+
return Ok(());
1695+
}
1696+
1697+
for required_import in &isort.required_imports {
1698+
let qualified_name = required_import.qualified_name();
1699+
if qualified_name.segments() == ["collections", "abc", "Set"]
1700+
&& required_import.bound_name() != "AbstractSet"
1701+
{
1702+
return Err(anyhow!(
1703+
"Required import `from collections.abc import Set` specified in \
1704+
`lint.isort.required-imports` (I002) conflicts with \
1705+
`unaliased-collections-abc-set-import` (PYI025), which requires \
1706+
this import to be aliased as `AbstractSet`.\n\n\
1707+
Help: Either alias the required import \
1708+
(`from collections.abc import Set as AbstractSet`), \
1709+
or disable PYI025."
1710+
));
1711+
}
1712+
}
1713+
1714+
Ok(())
1715+
}
1716+
16821717
#[cfg(test)]
16831718
mod tests {
16841719
use std::str::FromStr;
@@ -2181,4 +2216,60 @@ mod tests {
21812216

21822217
Ok(())
21832218
}
2219+
2220+
mod pyi025_conflict {
2221+
use std::collections::BTreeSet;
2222+
2223+
use ruff_linter::registry::Rule;
2224+
use ruff_linter::rules::isort;
2225+
use ruff_linter::settings::rule_table::RuleTable;
2226+
use ruff_python_semantic::{MemberNameImport, NameImport};
2227+
2228+
use super::super::conflicting_required_import_pyi025;
2229+
2230+
#[test]
2231+
fn unaliased_set_with_pyi025_is_error() {
2232+
let isort = isort::settings::Settings {
2233+
required_imports: BTreeSet::from_iter([NameImport::ImportFrom(
2234+
MemberNameImport::member("collections.abc".to_string(), "Set".to_string()),
2235+
)]),
2236+
..isort::settings::Settings::default()
2237+
};
2238+
let rules = RuleTable::from_iter([Rule::UnaliasedCollectionsAbcSetImport]);
2239+
2240+
let result = conflicting_required_import_pyi025(&isort, &rules);
2241+
assert!(result.is_err());
2242+
assert!(result.unwrap_err().to_string().contains("conflicts with"));
2243+
}
2244+
2245+
#[test]
2246+
fn aliased_as_abstract_set_is_ok() {
2247+
let isort = isort::settings::Settings {
2248+
required_imports: BTreeSet::from_iter([NameImport::ImportFrom(
2249+
MemberNameImport::alias(
2250+
"collections.abc".to_string(),
2251+
"Set".to_string(),
2252+
"AbstractSet".to_string(),
2253+
),
2254+
)]),
2255+
..isort::settings::Settings::default()
2256+
};
2257+
let rules = RuleTable::from_iter([Rule::UnaliasedCollectionsAbcSetImport]);
2258+
2259+
assert!(conflicting_required_import_pyi025(&isort, &rules).is_ok());
2260+
}
2261+
2262+
#[test]
2263+
fn pyi025_not_enabled_is_ok() {
2264+
let isort = isort::settings::Settings {
2265+
required_imports: BTreeSet::from_iter([NameImport::ImportFrom(
2266+
MemberNameImport::member("collections.abc".to_string(), "Set".to_string()),
2267+
)]),
2268+
..isort::settings::Settings::default()
2269+
};
2270+
let rules = RuleTable::empty();
2271+
2272+
assert!(conflicting_required_import_pyi025(&isort, &rules).is_ok());
2273+
}
2274+
}
21842275
}

0 commit comments

Comments
 (0)