[ty] Support attribute-expression TYPE_CHECKING conditionals (#21449)

This commit is contained in:
Alex Waygood 2025-11-14 13:11:49 +00:00 committed by GitHub
parent e9a5337136
commit 5f501374c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 6 deletions

View file

@ -132,8 +132,29 @@ def f(x: int | str):
Inside an `if TYPE_CHECKING` block, we allow "stub" style function definitions with empty bodies,
since these functions will never actually be called.
`compat/__init__.py`:
```py
```
`compat/sub/__init__.py`:
```py
```
`compat/sub/sub.py`:
```py
from typing import TYPE_CHECKING
```
`main.py`:
```py
from typing import TYPE_CHECKING
import typing
import typing as t
import compat.sub.sub
if TYPE_CHECKING:
def f() -> int: ...
@ -199,6 +220,24 @@ if get_bool():
if TYPE_CHECKING:
if not TYPE_CHECKING:
def n() -> str: ...
if typing.TYPE_CHECKING:
def o() -> str: ...
if not typing.TYPE_CHECKING:
def p() -> str: ... # error: [invalid-return-type]
if compat.sub.sub.TYPE_CHECKING:
def q() -> str: ...
if not compat.sub.sub.TYPE_CHECKING:
def r() -> str: ... # error: [invalid-return-type]
if t.TYPE_CHECKING:
def s() -> str: ...
if not t.TYPE_CHECKING:
def t() -> str: ... # error: [invalid-return-type]
```
## Conditional return type

View file

@ -3131,15 +3131,31 @@ impl ExpressionsScopeMapBuilder {
/// Returns if the expression is a `TYPE_CHECKING` expression.
fn is_if_type_checking(expr: &ast::Expr) -> bool {
matches!(expr, ast::Expr::Name(ast::ExprName { id, .. }) if id == "TYPE_CHECKING")
fn is_dotted_name(expr: &ast::Expr) -> bool {
match expr {
ast::Expr::Name(_) => true,
ast::Expr::Attribute(ast::ExprAttribute { value, .. }) => is_dotted_name(value),
_ => false,
}
}
match expr {
ast::Expr::Name(ast::ExprName { id, .. }) => id == "TYPE_CHECKING",
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
attr == "TYPE_CHECKING" && is_dotted_name(value)
}
_ => false,
}
}
/// Returns if the expression is a `not TYPE_CHECKING` expression.
fn is_if_not_type_checking(expr: &ast::Expr) -> bool {
matches!(expr, ast::Expr::UnaryOp(ast::ExprUnaryOp { op, operand, .. }) if *op == ruff_python_ast::UnaryOp::Not
&& matches!(
&**operand,
ast::Expr::Name(ast::ExprName { id, .. }) if id == "TYPE_CHECKING"
)
matches!(
expr,
ast::Expr::UnaryOp(ast::ExprUnaryOp {
op: ast::UnaryOp::Not,
operand,
..
}) if is_if_type_checking(operand)
)
}