Compare commits
4 Commits
alex/subsc
...
charlie/ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70859e4ba7 | ||
|
|
0eb28df0b0 | ||
|
|
3dba88b2fe | ||
|
|
6de4b1daf7 |
@@ -797,7 +797,7 @@ class B(A):
|
||||
pass
|
||||
|
||||
class C[T]:
|
||||
def check(self, x: object) -> TypeIs[T]:
|
||||
def check(x: object) -> TypeIs[T]:
|
||||
# this is a bad check, but we only care about it type-checking
|
||||
return False
|
||||
|
||||
@@ -835,7 +835,7 @@ class B(A):
|
||||
pass
|
||||
|
||||
class C[T]:
|
||||
def check(self, x: object) -> TypeGuard[T]:
|
||||
def check(x: object) -> TypeGuard[T]:
|
||||
# this is a bad check, but we only care about it type-checking
|
||||
return False
|
||||
|
||||
|
||||
@@ -951,3 +951,17 @@ for x in Bar:
|
||||
# TODO: should reveal `Any`
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Iterating over a list with a negated type parameter
|
||||
|
||||
When we have a list with a negated type parameter (e.g., `list[~str]`), we should still be able to
|
||||
iterate over it correctly. The negated type parameter represents all types except `str`, and
|
||||
`list[~str]` is still a valid list that can be iterated.
|
||||
|
||||
```py
|
||||
from ty_extensions import Not
|
||||
|
||||
def _(value: list[Not[str]]):
|
||||
for x in value:
|
||||
reveal_type(x) # revealed: ~str
|
||||
```
|
||||
|
||||
@@ -14,8 +14,8 @@ def _(
|
||||
b: TypeIs[str | int],
|
||||
c: TypeGuard[bool],
|
||||
d: TypeIs[tuple[TypeOf[bytes]]],
|
||||
e: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
|
||||
f: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
|
||||
e: TypeGuard, # error: [invalid-type-form]
|
||||
f: TypeIs, # error: [invalid-type-form]
|
||||
):
|
||||
reveal_type(a) # revealed: TypeGuard[str]
|
||||
reveal_type(b) # revealed: TypeIs[str | int]
|
||||
@@ -46,23 +46,12 @@ A user-defined type guard must accept at least one positional argument (in addit
|
||||
for non-static methods).
|
||||
|
||||
```pyi
|
||||
from typing import Any, TypeVar
|
||||
from typing_extensions import TypeGuard, TypeIs
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# Multiple parameters are allowed
|
||||
def is_str_list(val: list[object], allow_empty: bool) -> TypeGuard[list[str]]: ...
|
||||
def is_set_of(val: set[Any], type: type[T]) -> TypeGuard[set[T]]: ...
|
||||
def is_two_element_tuple(val: tuple[object, ...], a: str, b: str) -> TypeIs[tuple[str, str]]: ...
|
||||
|
||||
# error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
# TODO: error: [invalid-type-guard-definition]
|
||||
def _() -> TypeGuard[str]: ...
|
||||
|
||||
# error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
def _(*args) -> TypeGuard[str]: ...
|
||||
|
||||
# error: [invalid-type-guard-definition] "`TypeIs` function must have a parameter to narrow"
|
||||
# TODO: error: [invalid-type-guard-definition]
|
||||
def _(**kwargs) -> TypeIs[str]: ...
|
||||
|
||||
class _:
|
||||
@@ -74,14 +63,14 @@ class _:
|
||||
def _(a) -> TypeIs[str]: ...
|
||||
|
||||
# errors
|
||||
def _(self) -> TypeGuard[str]: ... # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
def _(self, /, *, a) -> TypeGuard[str]: ... # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
def _(self) -> TypeGuard[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
def _(self, /, *, a) -> TypeGuard[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
@classmethod
|
||||
def _(cls) -> TypeIs[str]: ... # error: [invalid-type-guard-definition] "`TypeIs` function must have a parameter to narrow"
|
||||
def _(cls) -> TypeIs[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
@classmethod
|
||||
def _() -> TypeIs[str]: ... # error: [invalid-type-guard-definition] "`TypeIs` function must have a parameter to narrow"
|
||||
def _() -> TypeIs[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
@staticmethod
|
||||
def _(*, a) -> TypeGuard[str]: ... # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
def _(*, a) -> TypeGuard[str]: ... # TODO: error: [invalid-type-guard-definition]
|
||||
```
|
||||
|
||||
For `TypeIs` functions, the narrowed type must be assignable to the declared type of that parameter,
|
||||
@@ -97,10 +86,10 @@ def _(a: tuple[object]) -> TypeIs[tuple[str]]: ...
|
||||
def _(a: str | Any) -> TypeIs[str]: ...
|
||||
def _(a) -> TypeIs[str]: ...
|
||||
|
||||
# error: [invalid-type-guard-definition] "Narrowed type `str` is not assignable to the declared parameter type `int`"
|
||||
# TODO: error: [invalid-type-guard-definition]
|
||||
def _(a: int) -> TypeIs[str]: ...
|
||||
|
||||
# error: [invalid-type-guard-definition] "Narrowed type `int` is not assignable to the declared parameter type `bool | str`"
|
||||
# TODO: error: [invalid-type-guard-definition]
|
||||
def _(a: bool | str) -> TypeIs[int]: ...
|
||||
```
|
||||
|
||||
@@ -118,14 +107,12 @@ class C:
|
||||
@classmethod
|
||||
def g(cls, x: object) -> TypeGuard[int]:
|
||||
return True
|
||||
|
||||
def h(
|
||||
self,
|
||||
) -> TypeGuard[str]: # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
# TODO: this could error at definition time
|
||||
def h(self) -> TypeGuard[str]:
|
||||
return True
|
||||
|
||||
# TODO: this could error at definition time
|
||||
@classmethod
|
||||
def j(cls) -> TypeGuard[int]: # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow"
|
||||
def j(cls) -> TypeGuard[int]:
|
||||
return True
|
||||
|
||||
def _(x: object):
|
||||
@@ -234,7 +221,7 @@ def g(a: object) -> TypeIs[int]:
|
||||
return True
|
||||
|
||||
def _(d: Any):
|
||||
if f(): # error: [missing-argument] "No argument provided for required parameter `a` of function `f`"
|
||||
if f(): # error: [missing-argument]
|
||||
...
|
||||
|
||||
if g(*d):
|
||||
@@ -243,7 +230,7 @@ def _(d: Any):
|
||||
if f("foo"): # TODO: error: [invalid-type-guard-call]
|
||||
...
|
||||
|
||||
if g(a=d): # error: [invalid-type-guard-call] "Type guard call does not have a target"
|
||||
if g(a=d): # error: [invalid-type-guard-call]
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# Subscripts involving type aliases
|
||||
|
||||
Aliases are expanded during analysis of subscripts.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAlias, Literal
|
||||
|
||||
ImplicitTuple = tuple[str, int, int]
|
||||
PEP613Tuple: TypeAlias = tuple[str, int, int]
|
||||
type PEP695Tuple = tuple[str, int, int]
|
||||
|
||||
ImplicitZero = Literal[0]
|
||||
PEP613Zero: TypeAlias = Literal[0]
|
||||
type PEP695Zero = Literal[0]
|
||||
|
||||
def f(
|
||||
implicit_tuple: ImplicitTuple,
|
||||
pep_613_tuple: PEP613Tuple,
|
||||
pep_695_tuple: PEP695Tuple,
|
||||
implicit_zero: ImplicitZero,
|
||||
pep_613_zero: PEP613Zero,
|
||||
pep_695_zero: PEP695Zero,
|
||||
):
|
||||
reveal_type(implicit_tuple[:2]) # revealed: tuple[str, int]
|
||||
reveal_type(implicit_tuple[implicit_zero]) # revealed: str
|
||||
reveal_type(implicit_tuple[pep_613_zero]) # revealed: str
|
||||
reveal_type(implicit_tuple[pep_695_zero]) # revealed: str
|
||||
|
||||
reveal_type(pep_613_tuple[:2]) # revealed: tuple[str, int]
|
||||
reveal_type(pep_613_tuple[implicit_zero]) # revealed: str
|
||||
reveal_type(pep_613_tuple[pep_613_zero]) # revealed: str
|
||||
reveal_type(pep_613_tuple[pep_695_zero]) # revealed: str
|
||||
|
||||
reveal_type(pep_695_tuple[:2]) # revealed: tuple[str, int]
|
||||
reveal_type(pep_695_tuple[implicit_zero]) # revealed: str
|
||||
reveal_type(pep_695_tuple[pep_613_zero]) # revealed: str
|
||||
reveal_type(pep_695_tuple[pep_695_zero]) # revealed: str
|
||||
```
|
||||
@@ -106,5 +106,5 @@ class Bar:
|
||||
def f(x: Foo):
|
||||
if isinstance(x, Bar):
|
||||
# TODO: should be `int`
|
||||
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions with intersections)
|
||||
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions on intersections)
|
||||
```
|
||||
|
||||
@@ -80,17 +80,6 @@ def _(m: int, n: int, s2: str):
|
||||
reveal_type(substring2) # revealed: str
|
||||
```
|
||||
|
||||
## LiteralString
|
||||
|
||||
```py
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
def f(x: LiteralString):
|
||||
reveal_type(x[0]) # revealed: LiteralString
|
||||
reveal_type(x[True]) # revealed: LiteralString
|
||||
reveal_type(x[1:42]) # revealed: LiteralString
|
||||
```
|
||||
|
||||
## Unsupported slice types
|
||||
|
||||
```py
|
||||
|
||||
@@ -430,5 +430,5 @@ class Bar: ...
|
||||
|
||||
def test4(val: Intersection[tuple[Foo], tuple[Bar]]):
|
||||
# TODO: should be `Foo & Bar`
|
||||
reveal_type(val[0]) # revealed: @Todo(Subscript expressions with intersections)
|
||||
reveal_type(val[0]) # revealed: @Todo(Subscript expressions on intersections)
|
||||
```
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
# Subscripts involving type variables
|
||||
|
||||
## TypeVar bound/constrained to a tuple/int-literal/bool-literal
|
||||
|
||||
The upper bounds of type variables are considered when analysing subscripts.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAlias, Literal
|
||||
|
||||
ImplicitTuple = tuple[str, int, int]
|
||||
PEP613Tuple: TypeAlias = tuple[str, int, int]
|
||||
type PEP695Tuple = tuple[str, int, int]
|
||||
|
||||
ImplicitZero = Literal[0]
|
||||
PEP613Zero: TypeAlias = Literal[0]
|
||||
type PEP695Zero = Literal[0]
|
||||
|
||||
# fmt: off
|
||||
|
||||
def f[
|
||||
BoundedTupleT: tuple[str, int, bytes],
|
||||
ConstrainedTupleT: (tuple[str, int, bytes], tuple[int, bytes, str]),
|
||||
BoundedZeroT: Literal[0],
|
||||
ConstrainedIntLiteralT: (Literal[0], Literal[1])
|
||||
](
|
||||
tuple_1: BoundedTupleT,
|
||||
tuple_2: ConstrainedTupleT,
|
||||
zero: BoundedZeroT,
|
||||
some_integer: ConstrainedIntLiteralT,
|
||||
):
|
||||
# TODO: would ideally be `tuple[str, int]`
|
||||
reveal_type(tuple_1[:2]) # revealed: tuple[str | int | bytes, ...]
|
||||
reveal_type(tuple_1[zero]) # revealed: str
|
||||
|
||||
# TODO: ideally this might be `str | int`,
|
||||
# but it's hard to do that without introducing false positives elsewhere
|
||||
reveal_type(tuple_1[some_integer]) # revealed: str | int | bytes
|
||||
|
||||
# TODO: would ideally be `tuple[str, int] | tuple[int, bytes]`
|
||||
reveal_type(tuple_2[:2]) # revealed: tuple[str | int | bytes, ...]
|
||||
reveal_type(tuple_2[zero]) # revealed: str | int
|
||||
reveal_type(tuple_2[some_integer]) # revealed: str | int | bytes
|
||||
|
||||
# fmt: on
|
||||
```
|
||||
|
||||
## TypeVars
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class SupportsLessThan(Protocol):
|
||||
def __lt__(self, other, /) -> bool: ...
|
||||
|
||||
def f[K: SupportsLessThan](dictionary: dict[K, int], key: K):
|
||||
reveal_type(dictionary[key]) # revealed: int
|
||||
```
|
||||
|
||||
## ParamSpecs
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
def decorator[**P, T](func: Callable[P, T]) -> Callable[P, T]:
|
||||
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
if len(args) > 0:
|
||||
# error: [invalid-assignment]
|
||||
args = args[1:]
|
||||
|
||||
# `func` requires the full `ParamSpec` passed into `decorator`,
|
||||
# but here the first argument is skipped, so we should possibly emit an error here:
|
||||
return func(*args, **kwargs)
|
||||
return inner
|
||||
```
|
||||
@@ -68,8 +68,8 @@ use crate::types::diagnostic::{
|
||||
INVALID_GENERIC_ENUM, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_METACLASS,
|
||||
INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT,
|
||||
INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ARGUMENTS, INVALID_TYPE_FORM,
|
||||
INVALID_TYPE_GUARD_CALL, INVALID_TYPE_GUARD_DEFINITION, INVALID_TYPE_VARIABLE_CONSTRAINTS,
|
||||
INVALID_TYPED_DICT_STATEMENT, IncompatibleBases, NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE,
|
||||
INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, INVALID_TYPED_DICT_STATEMENT,
|
||||
IncompatibleBases, NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE,
|
||||
POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS,
|
||||
TypedDictDeleteErrorKind, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
|
||||
UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_DYNAMIC_BASE, UNSUPPORTED_OPERATOR,
|
||||
@@ -587,7 +587,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
if self.db().should_check_file(self.file()) {
|
||||
self.check_static_class_definitions();
|
||||
self.check_overloaded_functions(node);
|
||||
self.check_type_guard_definitions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1453,85 +1452,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check that all type guard function definitions have at least one positional parameter
|
||||
/// (in addition to `self`/`cls` for methods), and for `TypeIs`, that the narrowed type is
|
||||
/// assignable to the declared type of that parameter.
|
||||
fn check_type_guard_definitions(&mut self) {
|
||||
for (definition, ty) in self.declarations.iter() {
|
||||
// Only check actual function definitions, not imports.
|
||||
let DefinitionKind::Function(function_ref) = definition.kind(self.db()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(function) = ty.inner_type().as_function_literal() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for overload in function.iter_overloads_and_implementation(self.db()) {
|
||||
let signature = overload.signature(self.db());
|
||||
let return_ty = signature.return_ty;
|
||||
|
||||
// Check if this is a `TypeIs` or `TypeGuard` return type.
|
||||
let (type_guard_form_name, narrowed_type) = match return_ty {
|
||||
Type::TypeIs(type_is) => ("TypeIs", Some(type_is.return_type(self.db()))),
|
||||
Type::TypeGuard(_) => ("TypeGuard", None),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let function_node = function_ref.node(self.module());
|
||||
|
||||
// The return type annotation must exist since we matched `TypeIs`/`TypeGuard`.
|
||||
let Some(returns_expr) = function_node.returns.as_deref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Check if this is a non-static method (first parameter is implicit `self`/`cls`).
|
||||
let is_method = self
|
||||
.index
|
||||
.class_definition_of_method(
|
||||
overload.body_scope(self.db()).file_scope_id(self.db()),
|
||||
)
|
||||
.is_some();
|
||||
let has_implicit_receiver = is_method && !overload.is_staticmethod(self.db());
|
||||
|
||||
// Find the first positional parameter to narrow (skip implicit `self`/`cls`).
|
||||
let positional_params: Vec<_> = signature.parameters().positional().collect();
|
||||
let first_narrowed_param_index = usize::from(has_implicit_receiver);
|
||||
let first_narrowed_param = positional_params.get(first_narrowed_param_index);
|
||||
|
||||
let Some(first_narrowed_param) = first_narrowed_param else {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_TYPE_GUARD_DEFINITION, returns_expr)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"`{type_guard_form_name}` function must have a parameter to narrow"
|
||||
));
|
||||
}
|
||||
continue;
|
||||
};
|
||||
|
||||
// For `TypeIs`, check that the narrowed type is assignable to the parameter type.
|
||||
if let Some(narrowed_ty) = narrowed_type {
|
||||
let param_ty = first_narrowed_param.annotated_type();
|
||||
if !narrowed_ty.is_assignable_to(self.db(), param_ty) {
|
||||
if let Some(builder) = self
|
||||
.context
|
||||
.report_lint(&INVALID_TYPE_GUARD_DEFINITION, returns_expr)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Narrowed type `{narrowed}` is not assignable \
|
||||
to the declared parameter type `{param}`",
|
||||
narrowed = narrowed_ty.display(self.db()),
|
||||
param = param_ty.display(self.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_region_definition(&mut self, definition: Definition<'db>) {
|
||||
match definition.kind(self.db()) {
|
||||
DefinitionKind::Function(function) => {
|
||||
@@ -13321,36 +13241,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
let value_node = subscript.value.as_ref();
|
||||
|
||||
let inferred = match (value_ty, slice_ty) {
|
||||
(Type::Dynamic(_) | Type::Never, _) => Some(value_ty),
|
||||
|
||||
(Type::TypeAlias(alias), _) => Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
alias.value_type(self.db()),
|
||||
slice_ty,
|
||||
expr_context,
|
||||
)),
|
||||
|
||||
(_, Type::TypeAlias(alias)) => Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
value_ty,
|
||||
alias.value_type(self.db()),
|
||||
expr_context,
|
||||
)),
|
||||
|
||||
(Type::Union(union), _) => Some(union.map(db, |element| {
|
||||
self.infer_subscript_expression_types(subscript, *element, slice_ty, expr_context)
|
||||
})),
|
||||
|
||||
(_, Type::Union(union)) => Some(union.map(db, |element| {
|
||||
self.infer_subscript_expression_types(subscript, value_ty, *element, expr_context)
|
||||
})),
|
||||
|
||||
// TODO: we can map over the intersection and fold the results back into an intersection,
|
||||
// but we need to make sure we avoid emitting a diagnostic if one positive element has a `__getitem__`
|
||||
// method but another does not. This means `infer_subscript_expression_types`
|
||||
// needs to return a `Result` rather than eagerly emitting diagnostics.
|
||||
(Type::Intersection(_), _) | (_, Type::Intersection(_)) => {
|
||||
Some(todo_type!("Subscript expressions with intersections"))
|
||||
(Type::Intersection(_), _) => {
|
||||
Some(todo_type!("Subscript expressions on intersections"))
|
||||
}
|
||||
|
||||
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
||||
@@ -13430,16 +13330,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}),
|
||||
|
||||
(Type::LiteralString, Type::IntLiteral(_) | Type::BooleanLiteral(_)) => {
|
||||
Some(Type::LiteralString)
|
||||
}
|
||||
|
||||
(Type::LiteralString, Type::NominalInstance(nominal))
|
||||
if nominal.slice_literal(db).is_some() =>
|
||||
{
|
||||
Some(Type::LiteralString)
|
||||
}
|
||||
|
||||
// Ex) Given `b"value"[1]`, return `97` (i.e., `ord(b"a")`)
|
||||
(Type::BytesLiteral(literal_ty), Type::IntLiteral(i64_int)) => {
|
||||
i32::try_from(i64_int).ok().map(|i32_int| {
|
||||
@@ -13571,39 +13461,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Some(todo_type!("Inference of subscript on special form"))
|
||||
}
|
||||
|
||||
(
|
||||
Type::FunctionLiteral(_)
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::BoundMethod(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::Callable(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::GenericAlias(_)
|
||||
| Type::SubclassOf(_)
|
||||
| Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy
|
||||
| Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypeGuard(_)
|
||||
| Type::TypedDict(_)
|
||||
| Type::NewTypeInstance(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::TypeVar(_) // TODO: more complex logic required here!
|
||||
| Type::KnownBoundMethod(_),
|
||||
_,
|
||||
) => None,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(inferred) = inferred {
|
||||
|
||||
@@ -651,6 +651,48 @@ impl<'db> Type<'db> {
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// Fast path: `object` is not a subtype of any nominal instance type other than itself.
|
||||
// This is important for performance when checking intersections with no positive
|
||||
// elements (pure negations like `~str`), which are treated as having `object` as
|
||||
// the implicit positive element.
|
||||
(Type::NominalInstance(source), Type::NominalInstance(_)) if source.is_object() => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// Fast path: `object` (an instance type) is not a subtype of any `type[X]` (a class type).
|
||||
(Type::NominalInstance(source), Type::SubclassOf(_)) if source.is_object() => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// Fast path: `object` is not a subtype of any non-inferable type variable, since the
|
||||
// type variable could be specialized to a type smaller than `object`.
|
||||
(Type::NominalInstance(source), Type::TypeVar(typevar))
|
||||
if source.is_object() && !typevar.is_inferable(db, inferable) =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// Fast path: `object` is assignable to any inferable type variable with no upper bound
|
||||
// (or with `object` as its upper bound), which is the common case for generic
|
||||
// type parameters like `_T` in `Iterator[_T]`.
|
||||
(Type::NominalInstance(source), Type::TypeVar(typevar))
|
||||
if source.is_object()
|
||||
&& typevar.is_inferable(db, inferable)
|
||||
&& relation.is_assignability()
|
||||
&& typevar
|
||||
.typevar(db)
|
||||
.upper_bound(db)
|
||||
.is_none_or(|bound| bound.is_object()) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// Fast path: `object` is not a subtype of any callable type, since not all objects
|
||||
// are callable.
|
||||
(Type::NominalInstance(source), Type::Callable(_)) if source.is_object() => {
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
// `Never` is the bottom type, the empty set.
|
||||
(_, Type::Never) => ConstraintSet::from(false),
|
||||
|
||||
@@ -796,7 +838,37 @@ impl<'db> Type<'db> {
|
||||
})
|
||||
}),
|
||||
|
||||
// Fast path for pure negations (~X): these are semantically `object & ~X`, so they're
|
||||
// only assignable to types that `object` is assignable to. Since `object` is only
|
||||
// assignable to `object`, dynamic types, unions/protocols/intersections that might
|
||||
// contain `object`, we can short-circuit most cases directly.
|
||||
(Type::Intersection(intersection), _) if intersection.positive(db).is_empty() => {
|
||||
match target {
|
||||
// `object` is a subtype of `object`
|
||||
_ if target.is_object() => ConstraintSet::from(true),
|
||||
// `object` is a subtype of dynamic types
|
||||
Type::Dynamic(_) => ConstraintSet::from(true),
|
||||
// These cases need more complex checking - delegate to full machinery
|
||||
// (TypeVar needs special handling for inference)
|
||||
Type::Union(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
| Type::Intersection(_)
|
||||
| Type::TypeVar(_) => Type::object().has_relation_to_impl(
|
||||
db,
|
||||
target,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
),
|
||||
// `object` is not a subtype of any other type
|
||||
_ => ConstraintSet::from(false),
|
||||
}
|
||||
}
|
||||
|
||||
(Type::Intersection(intersection), _) => {
|
||||
// An intersection type is a subtype of another type if at least one of its
|
||||
// positive elements is a subtype of that type.
|
||||
intersection.positive(db).iter().when_any(db, |&elem_ty| {
|
||||
elem_ty.has_relation_to_impl(
|
||||
db,
|
||||
|
||||
Reference in New Issue
Block a user