mirror of https://github.com/astral-sh/ruff
Enhance PEP 604 annotation handling by skipping dynamic Union creation in non-type definition contexts. Added support for implicit type aliases at the module level.
This commit is contained in:
parent
160b5f257b
commit
bb61ed58cc
|
|
@ -147,6 +147,18 @@ def f(types: tuple[type, ...]):
|
||||||
return Union[types]
|
return Union[types]
|
||||||
|
|
||||||
|
|
||||||
|
# Don't emit lint for dynamic Union creation with function calls (e.g., Union[foo()])
|
||||||
|
def get_types():
|
||||||
|
return (int, str, float)
|
||||||
|
|
||||||
|
|
||||||
|
def g():
|
||||||
|
return Union[get_types()]
|
||||||
|
|
||||||
|
|
||||||
|
# Implicit type alias at module level - should be flagged
|
||||||
|
IntOrStr = Union[int, str]
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
u = f((int, str, float))
|
u = f((int, str, float))
|
||||||
print(u) # typing.Union[int, str, float]
|
print(u) # typing.Union[int, str, float]
|
||||||
|
|
|
||||||
|
|
@ -205,9 +205,15 @@ pub(crate) fn non_pep604_annotation(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip dynamic Union creation (e.g., `Union[types]` where `types` is a variable).
|
// Only apply this rule in type annotation/definition positions, including implicit
|
||||||
// This cannot be converted to PEP 604 syntax because the types are determined at runtime.
|
// type aliases (e.g., `X = Union[int, str]` at module or class level).
|
||||||
if matches!(slice, Expr::Name(_)) {
|
// Skip dynamic Union creation (e.g., `Union[types]` or `Union[foo()]`) when not in
|
||||||
|
// type annotations, as these cannot be converted to PEP 604 syntax since the types
|
||||||
|
// are determined at runtime.
|
||||||
|
let in_type_definition = checker.semantic().in_type_definition();
|
||||||
|
let in_implicit_type_alias = is_implicit_type_alias(checker, expr);
|
||||||
|
|
||||||
|
if !in_type_definition && !in_implicit_type_alias {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,3 +333,24 @@ fn is_named_tuple(checker: &Checker, expr: &Expr) -> bool {
|
||||||
fn is_optional_none(operator: Pep604Operator, slice: &Expr) -> bool {
|
fn is_optional_none(operator: Pep604Operator, slice: &Expr) -> bool {
|
||||||
matches!(operator, Pep604Operator::Optional) && matches!(slice, Expr::NoneLiteral(_))
|
matches!(operator, Pep604Operator::Optional) && matches!(slice, Expr::NoneLiteral(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the expression is part of an implicit type alias (e.g., `X = Union[int, str]`
|
||||||
|
/// at module or class level).
|
||||||
|
fn is_implicit_type_alias(checker: &Checker, expr: &Expr) -> bool {
|
||||||
|
let semantic = checker.semantic();
|
||||||
|
let scope = semantic.current_scope();
|
||||||
|
|
||||||
|
// Only consider module-level or class-level assignments as potential type aliases
|
||||||
|
if scope.kind.is_function() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current statement is a simple assignment
|
||||||
|
if let ast::Stmt::Assign(ast::StmtAssign { value, .. }) = semantic.current_statement() {
|
||||||
|
// Check if the Union expression is part of the assignment value
|
||||||
|
// We check if the expression's range is within the value's range
|
||||||
|
expr.range().start() >= value.range().start() && expr.range().end() <= value.range().end()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -205,17 +205,6 @@ help: Convert to `X | Y`
|
||||||
43 |
|
43 |
|
||||||
44 |
|
44 |
|
||||||
|
|
||||||
UP007 Use `X | Y` for type annotations
|
|
||||||
--> UP007.py:46:9
|
|
||||||
|
|
|
||||||
45 | def f() -> None:
|
|
||||||
46 | x = Union[str, int]
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
47 | x = Union["str", "int"]
|
|
||||||
48 | x: Union[str, int]
|
|
||||||
|
|
|
||||||
help: Convert to `X | Y`
|
|
||||||
|
|
||||||
UP007 [*] Use `X | Y` for type annotations
|
UP007 [*] Use `X | Y` for type annotations
|
||||||
--> UP007.py:48:8
|
--> UP007.py:48:8
|
||||||
|
|
|
|
||||||
|
|
@ -342,4 +331,15 @@ help: Convert to `X | Y`
|
||||||
91 + def myfunc(param: "tuple[int | 'AClass' | None, str]"):
|
91 + def myfunc(param: "tuple[int | 'AClass' | None, str]"):
|
||||||
92 | print(param)
|
92 | print(param)
|
||||||
93 |
|
93 |
|
||||||
94 |
|
94 |
|
||||||
|
|
||||||
|
UP007 Use `X | Y` for type annotations
|
||||||
|
--> UP007.py:160:12
|
||||||
|
|
|
||||||
|
159 | # Implicit type alias at module level - should be flagged
|
||||||
|
160 | IntOrStr = Union[int, str]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
161 |
|
||||||
|
162 | if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
help: Convert to `X | Y`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue