diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.pyi b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.pyi index f97ba8887f..a721f4771a 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.pyi +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.pyi @@ -12,3 +12,13 @@ x: TypeAlias = tuple[ int, # preserved float, ] + +T: TypeAlias = ( # comment0 + # comment1 + int # comment2 + # comment3 + | # comment4 + # comment5 + str # comment6 + # comment7 +) # comment8 diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs index ff9215f962..e3a1cb96c1 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs @@ -170,25 +170,12 @@ pub(crate) fn non_pep695_type_alias_type(checker: &Checker, stmt: &StmtAssign) { return; }; - // it would be easier to check for comments in the whole `stmt.range`, but because - // `create_diagnostic` uses the full source text of `value`, comments within `value` are - // actually preserved. thus, we have to check for comments in `stmt` but outside of `value` - let pre_value = TextRange::new(stmt.start(), value.start()); - let post_value = TextRange::new(value.end(), stmt.end()); - let comment_ranges = checker.comment_ranges(); - let safety = if comment_ranges.intersects(pre_value) || comment_ranges.intersects(post_value) { - Applicability::Unsafe - } else { - Applicability::Safe - }; - checker.report_diagnostic(create_diagnostic( checker, stmt.into(), &target_name.id, value, &vars, - safety, TypeAliasKind::TypeAliasType, )); } @@ -248,13 +235,6 @@ pub(crate) fn non_pep695_type_alias(checker: &Checker, stmt: &StmtAnnAssign) { name, value, &vars, - // The fix is only safe in a type stub because new-style aliases have different runtime behavior - // See https://github.com/astral-sh/ruff/issues/6434 - if checker.source_type.is_stub() { - Applicability::Safe - } else { - Applicability::Unsafe - }, TypeAliasKind::TypeAlias, )); } @@ -266,19 +246,45 @@ fn create_diagnostic( name: &Name, value: &Expr, type_vars: &[TypeVar], - applicability: Applicability, type_alias_kind: TypeAliasKind, ) -> Diagnostic { let source = checker.source(); + let comment_ranges = checker.comment_ranges(); + let range_with_parentheses = - parenthesized_range(value.into(), stmt.into(), checker.comment_ranges(), source) + parenthesized_range(value.into(), stmt.into(), comment_ranges, source) .unwrap_or(value.range()); + let content = format!( "type {name}{type_params} = {value}", type_params = DisplayTypeVars { type_vars, source }, value = &source[range_with_parentheses] ); let edit = Edit::range_replacement(content, stmt.range()); + + let applicability = + if type_alias_kind == TypeAliasKind::TypeAlias && !checker.source_type.is_stub() { + // The fix is always unsafe in non-stubs + // because new-style aliases have different runtime behavior. + // See https://github.com/astral-sh/ruff/issues/6434 + Applicability::Unsafe + } else { + // In stub files, or in non-stub files for `TypeAliasType` assignments, + // the fix is only unsafe if it would delete comments. + // + // it would be easier to check for comments in the whole `stmt.range`, but because + // `create_diagnostic` uses the full source text of `value`, comments within `value` are + // actually preserved. thus, we have to check for comments in `stmt` but outside of `value` + let pre_value = TextRange::new(stmt.start(), range_with_parentheses.start()); + let post_value = TextRange::new(range_with_parentheses.end(), stmt.end()); + + if comment_ranges.intersects(pre_value) || comment_ranges.intersects(post_value) { + Applicability::Unsafe + } else { + Applicability::Safe + } + }; + Diagnostic::new( NonPEP695TypeAlias { name: name.to_string(), diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.pyi.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.pyi.snap index 30496127fa..7132708896 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.pyi.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.pyi.snap @@ -48,6 +48,8 @@ UP040.pyi:11:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of 13 | | float, 14 | | ] | |_^ UP040 +15 | +16 | T: TypeAlias = ( # comment0 | = help: Use the `type` keyword @@ -60,3 +62,30 @@ UP040.pyi:11:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of 12 12 | int, # preserved 13 13 | float, 14 14 | ] + +UP040.pyi:16:1: UP040 [*] Type alias `T` uses `TypeAlias` annotation instead of the `type` keyword + | +14 | ] +15 | +16 | / T: TypeAlias = ( # comment0 +17 | | # comment1 +18 | | int # comment2 +19 | | # comment3 +20 | | | # comment4 +21 | | # comment5 +22 | | str # comment6 +23 | | # comment7 +24 | | ) # comment8 + | |_^ UP040 + | + = help: Use the `type` keyword + +ℹ Safe fix +13 13 | float, +14 14 | ] +15 15 | +16 |-T: TypeAlias = ( # comment0 + 16 |+type T = ( # comment0 +17 17 | # comment1 +18 18 | int # comment2 +19 19 | # comment3