mirror of https://github.com/astral-sh/ruff
[ty] Allow PEP-604 unions in stubs and `TYPE_CHECKING` blocks prior to 3.10 (#21379)
This commit is contained in:
parent
7b237d316f
commit
44b0c9ebac
|
|
@ -272,6 +272,54 @@ def g(
|
||||||
): ...
|
): ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `|` unions in stubs and `TYPE_CHECKING` blocks
|
||||||
|
|
||||||
|
In runtime contexts, `|` unions are only permitted on Python 3.10+. But in suites of code that are
|
||||||
|
never executed at runtime (stub files, `if TYPE_CHECKING` blocks, and stringified annotations), they
|
||||||
|
are permitted even if the target version is set to Python 3.9 or earlier.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.9"
|
||||||
|
```
|
||||||
|
|
||||||
|
`bar.pyi`:
|
||||||
|
|
||||||
|
```pyi
|
||||||
|
Z = int | str
|
||||||
|
GLOBAL_CONSTANT: Z
|
||||||
|
```
|
||||||
|
|
||||||
|
`foo.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from bar import GLOBAL_CONSTANT
|
||||||
|
|
||||||
|
reveal_type(GLOBAL_CONSTANT) # revealed: int | str
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
class ItsQuiteCloudyInManchester:
|
||||||
|
X = int | str
|
||||||
|
|
||||||
|
def f(obj: X):
|
||||||
|
reveal_type(obj) # revealed: int | str
|
||||||
|
|
||||||
|
# TODO: we currently only understand code as being inside a `TYPE_CHECKING` block
|
||||||
|
# if a whole *scope* is inside the `if TYPE_CHECKING` block
|
||||||
|
# (like the `ItsQuiteCloudyInManchester` class above); this is a false-positive
|
||||||
|
Y = int | str # error: [unsupported-operator]
|
||||||
|
|
||||||
|
def g(obj: Y):
|
||||||
|
# TODO: should be `int | str`
|
||||||
|
reveal_type(obj) # revealed: Unknown
|
||||||
|
|
||||||
|
Y = list["int | str"]
|
||||||
|
|
||||||
|
def g(obj: Y):
|
||||||
|
reveal_type(obj) # revealed: list[int | str]
|
||||||
|
```
|
||||||
|
|
||||||
## Generic types
|
## Generic types
|
||||||
|
|
||||||
Implicit type aliases can also refer to generic types:
|
Implicit type aliases can also refer to generic types:
|
||||||
|
|
|
||||||
|
|
@ -8899,6 +8899,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
emitted_division_by_zero_diagnostic = self.check_division_by_zero(node, op, left_ty);
|
emitted_division_by_zero_diagnostic = self.check_division_by_zero(node, op, left_ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pep_604_unions_allowed = || {
|
||||||
|
Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY310
|
||||||
|
|| self.file().is_stub(self.db())
|
||||||
|
|| self.scope().scope(self.db()).in_type_checking_block()
|
||||||
|
};
|
||||||
|
|
||||||
match (left_ty, right_ty, op) {
|
match (left_ty, right_ty, op) {
|
||||||
(Type::Union(lhs_union), rhs, _) => lhs_union.try_map(self.db(), |lhs_element| {
|
(Type::Union(lhs_union), rhs, _) => lhs_union.try_map(self.db(), |lhs_element| {
|
||||||
self.infer_binary_expression_type(
|
self.infer_binary_expression_type(
|
||||||
|
|
@ -9160,7 +9166,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
| KnownInstanceType::Annotated(_),
|
| KnownInstanceType::Annotated(_),
|
||||||
),
|
),
|
||||||
ast::Operator::BitOr,
|
ast::Operator::BitOr,
|
||||||
) if Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY310 => {
|
) if pep_604_unions_allowed() => {
|
||||||
if left_ty.is_equivalent_to(self.db(), right_ty) {
|
if left_ty.is_equivalent_to(self.db(), right_ty) {
|
||||||
Some(left_ty)
|
Some(left_ty)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -9186,7 +9192,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
| Type::KnownInstance(..)
|
| Type::KnownInstance(..)
|
||||||
| Type::SpecialForm(..),
|
| Type::SpecialForm(..),
|
||||||
ast::Operator::BitOr,
|
ast::Operator::BitOr,
|
||||||
) if Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY310
|
) if pep_604_unions_allowed()
|
||||||
&& instance.has_known_class(self.db(), KnownClass::NoneType) =>
|
&& instance.has_known_class(self.db(), KnownClass::NoneType) =>
|
||||||
{
|
{
|
||||||
Some(Type::KnownInstance(KnownInstanceType::UnionType(
|
Some(Type::KnownInstance(KnownInstanceType::UnionType(
|
||||||
|
|
@ -9210,8 +9216,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
_,
|
_,
|
||||||
Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..),
|
Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::SubclassOf(..),
|
||||||
ast::Operator::BitOr,
|
ast::Operator::BitOr,
|
||||||
) if Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY310 => {
|
) if pep_604_unions_allowed() => Type::try_call_bin_op_with_policy(
|
||||||
Type::try_call_bin_op_with_policy(
|
|
||||||
self.db(),
|
self.db(),
|
||||||
left_ty,
|
left_ty,
|
||||||
ast::Operator::BitOr,
|
ast::Operator::BitOr,
|
||||||
|
|
@ -9219,8 +9224,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
|
MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|binding| binding.return_type(self.db()))
|
.map(|binding| binding.return_type(self.db())),
|
||||||
}
|
|
||||||
|
|
||||||
// We've handled all of the special cases that we support for literals, so we need to
|
// We've handled all of the special cases that we support for literals, so we need to
|
||||||
// fall back on looking for dunder methods on one of the operand types.
|
// fall back on looking for dunder methods on one of the operand types.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue