Compare commits
1 Commits
main
...
alex/subsc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68bdf9fa88 |
@@ -0,0 +1,43 @@
|
|||||||
|
# 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):
|
def f(x: Foo):
|
||||||
if isinstance(x, Bar):
|
if isinstance(x, Bar):
|
||||||
# TODO: should be `int`
|
# TODO: should be `int`
|
||||||
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions on intersections)
|
reveal_type(x["whatever"]) # revealed: @Todo(Subscript expressions with intersections)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -80,6 +80,17 @@ def _(m: int, n: int, s2: str):
|
|||||||
reveal_type(substring2) # revealed: 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
|
## Unsupported slice types
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|||||||
@@ -430,5 +430,5 @@ class Bar: ...
|
|||||||
|
|
||||||
def test4(val: Intersection[tuple[Foo], tuple[Bar]]):
|
def test4(val: Intersection[tuple[Foo], tuple[Bar]]):
|
||||||
# TODO: should be `Foo & Bar`
|
# TODO: should be `Foo & Bar`
|
||||||
reveal_type(val[0]) # revealed: @Todo(Subscript expressions on intersections)
|
reveal_type(val[0]) # revealed: @Todo(Subscript expressions with intersections)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
@@ -13321,16 +13321,36 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||||||
let value_node = subscript.value.as_ref();
|
let value_node = subscript.value.as_ref();
|
||||||
|
|
||||||
let inferred = match (value_ty, slice_ty) {
|
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| {
|
(Type::Union(union), _) => Some(union.map(db, |element| {
|
||||||
self.infer_subscript_expression_types(subscript, *element, slice_ty, expr_context)
|
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,
|
// 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__`
|
// 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`
|
// method but another does not. This means `infer_subscript_expression_types`
|
||||||
// needs to return a `Result` rather than eagerly emitting diagnostics.
|
// needs to return a `Result` rather than eagerly emitting diagnostics.
|
||||||
(Type::Intersection(_), _) => {
|
(Type::Intersection(_), _) | (_, Type::Intersection(_)) => {
|
||||||
Some(todo_type!("Subscript expressions on intersections"))
|
Some(todo_type!("Subscript expressions with intersections"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
||||||
@@ -13410,6 +13430,16 @@ 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")`)
|
// Ex) Given `b"value"[1]`, return `97` (i.e., `ord(b"a")`)
|
||||||
(Type::BytesLiteral(literal_ty), Type::IntLiteral(i64_int)) => {
|
(Type::BytesLiteral(literal_ty), Type::IntLiteral(i64_int)) => {
|
||||||
i32::try_from(i64_int).ok().map(|i32_int| {
|
i32::try_from(i64_int).ok().map(|i32_int| {
|
||||||
@@ -13541,7 +13571,39 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||||||
Some(todo_type!("Inference of subscript on special form"))
|
Some(todo_type!("Inference of subscript on special form"))
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => None,
|
(
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(inferred) = inferred {
|
if let Some(inferred) = inferred {
|
||||||
|
|||||||
Reference in New Issue
Block a user