diff --git a/crates/ruff/resources/test/fixtures/flake8_type_checking/strict.py b/crates/ruff/resources/test/fixtures/flake8_type_checking/strict.py index d7f9bda984..409ca96ff8 100644 --- a/crates/ruff/resources/test/fixtures/flake8_type_checking/strict.py +++ b/crates/ruff/resources/test/fixtures/flake8_type_checking/strict.py @@ -55,3 +55,50 @@ def f(): def test(value: A): return pkg.B() + + +def f(): + # In un-strict mode, this shouldn't rase an error, since `pkg.bar` is used at runtime. + import pkg + import pkg.bar as B + + def test(value: pkg.A): + return B() + + +def f(): + # In un-strict mode, this shouldn't rase an error, since `pkg.foo.bar` is used at runtime. + import pkg.foo as F + import pkg.foo.bar as B + + def test(value: F.Foo): + return B() + + +def f(): + # In un-strict mode, this shouldn't rase an error, since `pkg.foo.bar` is used at runtime. + import pkg + import pkg.foo.bar as B + + def test(value: pkg.A): + return B() + + +def f(): + # In un-strict mode, this _should_ rase an error, since `pkgfoo.bar` is used at runtime. + # Note that `pkg` is a prefix of `pkgfoo` which are both different modules. This is + # testing the implementation. + import pkg + import pkgfoo.bar as B + + def test(value: pkg.A): + return B() + + +def f(): + # In un-strict mode, this shouldn't raise an error, since `pkg.bar` is used at runtime. + import pkg.bar as B + import pkg.foo as F + + def test(value: F.Foo): + return B.Bar() diff --git a/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs b/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs index df32a2dcbb..8e05803c39 100644 --- a/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs +++ b/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs @@ -186,8 +186,18 @@ fn is_implicit_import(this: &Binding, that: &Binding) -> bool { | BindingKind::SubmoduleImportation(SubmoduleImportation { name: that_name, .. }) => { - // Ex) `pkg.A` vs. `pkg.B` - this_name == that_name + // Submodule importation with an alias (`import pkg.A as B`) + // are represented as `Importation`. + match (this_name.find('.'), that_name.find('.')) { + // Ex) `pkg.A` vs. `pkg.B` + (Some(i), Some(j)) => this_name[..i] == that_name[..j], + // Ex) `pkg.A` vs. `pkg` + (Some(i), None) => this_name[..i] == **that_name, + // Ex) `pkg` vs. `pkg.B` + (None, Some(j)) => **this_name == that_name[..j], + // Ex) `pkg` vs. `pkg` + (None, None) => this_name == that_name, + } } _ => false, }, diff --git a/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__strict.snap b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__strict.snap index 4ee56e39e1..e110f3c9c3 100644 --- a/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__strict.snap +++ b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__strict.snap @@ -31,4 +31,50 @@ strict.py:54:25: TCH002 Move third-party import `pkg.bar.A` into a type-checking 58 | def test(value: A): | +strict.py:62:12: TCH002 Move third-party import `pkg` into a type-checking block + | +62 | def f(): +63 | # In un-strict mode, this shouldn't rase an error, since `pkg.bar` is used at runtime. +64 | import pkg + | ^^^ TCH002 +65 | import pkg.bar as B + | + +strict.py:71:12: TCH002 Move third-party import `pkg.foo` into a type-checking block + | +71 | def f(): +72 | # In un-strict mode, this shouldn't rase an error, since `pkg.foo.bar` is used at runtime. +73 | import pkg.foo as F + | ^^^^^^^^^^^^ TCH002 +74 | import pkg.foo.bar as B + | + +strict.py:80:12: TCH002 Move third-party import `pkg` into a type-checking block + | +80 | def f(): +81 | # In un-strict mode, this shouldn't rase an error, since `pkg.foo.bar` is used at runtime. +82 | import pkg + | ^^^ TCH002 +83 | import pkg.foo.bar as B + | + +strict.py:91:12: TCH002 Move third-party import `pkg` into a type-checking block + | +91 | # Note that `pkg` is a prefix of `pkgfoo` which are both different modules. This is +92 | # testing the implementation. +93 | import pkg + | ^^^ TCH002 +94 | import pkgfoo.bar as B + | + +strict.py:101:12: TCH002 Move third-party import `pkg.foo` into a type-checking block + | +101 | # In un-strict mode, this shouldn't raise an error, since `pkg.bar` is used at runtime. +102 | import pkg.bar as B +103 | import pkg.foo as F + | ^^^^^^^^^^^^ TCH002 +104 | +105 | def test(value: F.Foo): + | + diff --git a/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__typing-only-third-party-import_strict.py.snap b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__typing-only-third-party-import_strict.py.snap index a26d1222d3..b3044d4b63 100644 --- a/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__typing-only-third-party-import_strict.py.snap +++ b/crates/ruff/src/rules/flake8_type_checking/snapshots/ruff__rules__flake8_type_checking__tests__typing-only-third-party-import_strict.py.snap @@ -11,4 +11,13 @@ strict.py:54:25: TCH002 Move third-party import `pkg.bar.A` into a type-checking 58 | def test(value: A): | +strict.py:91:12: TCH002 Move third-party import `pkg` into a type-checking block + | +91 | # Note that `pkg` is a prefix of `pkgfoo` which are both different modules. This is +92 | # testing the implementation. +93 | import pkg + | ^^^ TCH002 +94 | import pkgfoo.bar as B + | +