Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 1 addition & 34 deletions crates/ruff_benchmark/benches/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,40 +59,7 @@ type KeyDiagnosticFields = (
Severity,
);

// left: [
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to function `skip_until` is incorrect", Error),
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to function `skip_until` is incorrect", Error),
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to function `skip_until` is incorrect", Error),
// ]
//right: [
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(8224..8254), "Argument to this function is incorrect", Error),
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(16914..16948), "Argument to this function is incorrect", Error),
// (Lint(LintName("invalid-argument-type")), Some("/src/tomllib/_parser.py"), Some(17319..17363), "Argument to this function is incorrect", Error),
// ]

static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[
(
DiagnosticId::lint("invalid-argument-type"),
Some("/src/tomllib/_parser.py"),
Some(8224..8254),
"Argument to function `skip_until` is incorrect",
Severity::Error,
),
(
DiagnosticId::lint("invalid-argument-type"),
Some("/src/tomllib/_parser.py"),
Some(16914..16948),
"Argument to function `skip_until` is incorrect",
Severity::Error,
),
(
DiagnosticId::lint("invalid-argument-type"),
Some("/src/tomllib/_parser.py"),
Some(17319..17363),
"Argument to function `skip_until` is incorrect",
Severity::Error,
),
];
static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[];

fn tomllib_path(file: &TestFile) -> SystemPathBuf {
SystemPathBuf::from("src").join(file.name())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Shape:
reveal_type(self) # revealed: Self
return self

def nested_type(self) -> list[Self]:
def nested_type(self: Self) -> list[Self]:
return [self]

def nested_func(self: Self) -> Self:
Expand All @@ -33,9 +33,7 @@ class Shape:
reveal_type(self) # revealed: Unknown
return self

# TODO: should be `list[Shape]`
reveal_type(Shape().nested_type()) # revealed: list[Self]

reveal_type(Shape().nested_type()) # revealed: list[Shape]
reveal_type(Shape().nested_func()) # revealed: Shape

class Circle(Shape):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,76 @@ reveal_type(f("string")) # revealed: Literal["string"]
## Inferring “deep” generic parameter types

The matching up of call arguments and discovery of constraints on typevars can be a recursive
process for arbitrarily-nested generic types in parameters.
process for arbitrarily-nested generic classes and protocols in parameters.

TODO: Note that we can currently only infer a specialization for a generic protocol when the
argument _explicitly_ implements the protocol by listing it as a base class.

```py
from typing import TypeVar
from typing import Protocol, TypeVar

T = TypeVar("T")

def f(x: list[T]) -> T:
class CanIndex(Protocol[T]):
def __getitem__(self, index: int) -> T: ...

class ExplicitlyImplements(CanIndex[T]): ...

def takes_in_list(x: list[T]) -> list[T]:
return x

def takes_in_protocol(x: CanIndex[T]) -> T:
return x[0]

# TODO: revealed: float
reveal_type(f([1.0, 2.0])) # revealed: Unknown
def deep_list(x: list[str]) -> None:
# TODO: revealed: list[str]
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
# TODO: revealed: str
reveal_type(takes_in_protocol(x)) # revealed: Unknown

def deeper_list(x: list[set[str]]) -> None:
# TODO: revealed: list[set[str]]
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
# TODO: revealed: set[str]
reveal_type(takes_in_protocol(x)) # revealed: Unknown

def deep_explicit(x: ExplicitlyImplements[str]) -> None:
# TODO: revealed: str
reveal_type(takes_in_protocol(x)) # revealed: Unknown

def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
# TODO: revealed: set[str]
reveal_type(takes_in_protocol(x)) # revealed: Unknown

def takes_in_type(x: type[T]) -> type[T]:
return x

reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
```

This also works when passing in arguments that are subclasses of the parameter type.

```py
class Sub(list[int]): ...
class GenericSub(list[T]): ...

# TODO: revealed: list[int]
reveal_type(takes_in_list(Sub())) # revealed: list[Unknown]
# TODO: revealed: int
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown

# TODO: revealed: list[str]
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown]
# TODO: revealed: str
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown

class ExplicitSub(ExplicitlyImplements[int]): ...
class ExplicitGenericSub(ExplicitlyImplements[T]): ...

# TODO: revealed: int
reveal_type(takes_in_protocol(ExplicitSub())) # revealed: Unknown
# TODO: revealed: str
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: Unknown
```

## Inferring a bound typevar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,76 @@ reveal_type(f("string")) # revealed: Literal["string"]
## Inferring “deep” generic parameter types

The matching up of call arguments and discovery of constraints on typevars can be a recursive
process for arbitrarily-nested generic types in parameters.
process for arbitrarily-nested generic classes and protocols in parameters.

TODO: Note that we can currently only infer a specialization for a generic protocol when the
argument _explicitly_ implements the protocol by listing it as a base class.

```py
def f[T](x: list[T]) -> T:
from typing import Protocol, TypeVar

S = TypeVar("S")

class CanIndex(Protocol[S]):
def __getitem__(self, index: int) -> S: ...

class ExplicitlyImplements[T](CanIndex[T]): ...

def takes_in_list[T](x: list[T]) -> list[T]:
return x

def takes_in_protocol[T](x: CanIndex[T]) -> T:
return x[0]

# TODO: revealed: float
reveal_type(f([1.0, 2.0])) # revealed: Unknown
def deep_list(x: list[str]) -> None:
# TODO: revealed: list[str]
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
# TODO: revealed: str
reveal_type(takes_in_protocol(x)) # revealed: Unknown

def deeper_list(x: list[set[str]]) -> None:
# TODO: revealed: list[set[str]]
reveal_type(takes_in_list(x)) # revealed: list[Unknown]
# TODO: revealed: set[str]
reveal_type(takes_in_protocol(x)) # revealed: Unknown

def deep_explicit(x: ExplicitlyImplements[str]) -> None:
# TODO: revealed: str
reveal_type(takes_in_protocol(x)) # revealed: Unknown

def deeper_explicit(x: ExplicitlyImplements[set[str]]) -> None:
# TODO: revealed: set[str]
reveal_type(takes_in_protocol(x)) # revealed: Unknown

def takes_in_type[T](x: type[T]) -> type[T]:
return x

reveal_type(takes_in_type(int)) # revealed: @Todo(unsupported type[X] special form)
```

This also works when passing in arguments that are subclasses of the parameter type.

```py
class Sub(list[int]): ...
class GenericSub[T](list[T]): ...

# TODO: revealed: list[int]
reveal_type(takes_in_list(Sub())) # revealed: list[Unknown]
# TODO: revealed: int
reveal_type(takes_in_protocol(Sub())) # revealed: Unknown

# TODO: revealed: list[str]
reveal_type(takes_in_list(GenericSub[str]())) # revealed: list[Unknown]
# TODO: revealed: str
reveal_type(takes_in_protocol(GenericSub[str]())) # revealed: Unknown

class ExplicitSub(ExplicitlyImplements[int]): ...
class ExplicitGenericSub[T](ExplicitlyImplements[T]): ...

# TODO: revealed: int
reveal_type(takes_in_protocol(ExplicitSub())) # revealed: Unknown
# TODO: revealed: str
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: Unknown
```

## Inferring a bound typevar
Expand Down
31 changes: 19 additions & 12 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5048,7 +5048,7 @@ impl<'db> Type<'db> {
),

Type::ProtocolInstance(instance) => {
Type::ProtocolInstance(instance.apply_specialization(db, type_mapping))
Type::ProtocolInstance(instance.apply_type_mapping(db, type_mapping))
}

Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => {
Expand Down Expand Up @@ -5080,12 +5080,13 @@ impl<'db> Type<'db> {
}

Type::GenericAlias(generic) => {
let specialization = generic
.specialization(db)
.apply_type_mapping(db, type_mapping);
Type::GenericAlias(GenericAlias::new(db, generic.origin(db), specialization))
Type::GenericAlias(generic.apply_type_mapping(db, type_mapping))
}

Type::SubclassOf(subclass_of) => Type::SubclassOf(
subclass_of.apply_type_mapping(db, type_mapping),
),

Type::PropertyInstance(property) => {
Type::PropertyInstance(property.apply_type_mapping(db, type_mapping))
}
Expand Down Expand Up @@ -5125,9 +5126,6 @@ impl<'db> Type<'db> {
// explicitly (via a subscript expression) or implicitly (via a call), and not because
// some other generic context's specialization is applied to it.
| Type::ClassLiteral(_)
// SubclassOf contains a ClassType, which has already been specialized if needed, like
// above with BoundMethod's self_instance.
| Type::SubclassOf(_)
| Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::LiteralString
Expand Down Expand Up @@ -5202,7 +5200,19 @@ impl<'db> Type<'db> {
}

Type::GenericAlias(alias) => {
alias.specialization(db).find_legacy_typevars(db, typevars);
alias.find_legacy_typevars(db, typevars);
}

Type::NominalInstance(instance) => {
instance.find_legacy_typevars(db, typevars);
}

Type::ProtocolInstance(instance) => {
instance.find_legacy_typevars(db, typevars);
}

Type::SubclassOf(subclass_of) => {
subclass_of.find_legacy_typevars(db, typevars);
}

Type::Dynamic(_)
Expand All @@ -5215,15 +5225,12 @@ impl<'db> Type<'db> {
| Type::DataclassTransformer(_)
| Type::ModuleLiteral(_)
| Type::ClassLiteral(_)
| Type::SubclassOf(_)
| Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::LiteralString
| Type::StringLiteral(_)
| Type::BytesLiteral(_)
| Type::BoundSuper(_)
| Type::NominalInstance(_)
| Type::ProtocolInstance(_)
| Type::KnownInstance(_) => {}
}
}
Expand Down
28 changes: 26 additions & 2 deletions crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::types::generics::{GenericContext, Specialization, TypeMapping};
use crate::types::signatures::{Parameter, Parameters};
use crate::types::{
CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature,
TypeVarInstance,
};
use crate::{
module_resolver::file_to_module,
Expand All @@ -31,7 +32,7 @@ use crate::{
definition_expression_type, CallArgumentTypes, CallError, CallErrorKind, DynamicType,
MetaclassCandidate, TupleType, UnionBuilder, UnionType,
},
Db, KnownModule, Program,
Db, FxOrderSet, KnownModule, Program,
};
use indexmap::IndexSet;
use itertools::Itertools as _;
Expand Down Expand Up @@ -167,13 +168,25 @@ impl<'db> GenericAlias<'db> {
self.origin(db).definition(db)
}

fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
pub(super) fn apply_type_mapping<'a>(
self,
db: &'db dyn Db,
type_mapping: TypeMapping<'a, 'db>,
) -> Self {
Self::new(
db,
self.origin(db),
self.specialization(db).apply_type_mapping(db, type_mapping),
)
}

pub(super) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
) {
self.specialization(db).find_legacy_typevars(db, typevars);
}
}

impl<'db> From<GenericAlias<'db>> for Type<'db> {
Expand Down Expand Up @@ -262,6 +275,17 @@ impl<'db> ClassType<'db> {
}
}

pub(super) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
) {
match self {
Self::NonGeneric(_) => {}
Self::Generic(generic) => generic.find_legacy_typevars(db, typevars),
}
}

/// Iterate over the [method resolution order] ("MRO") of the class.
///
/// If the MRO could not be accurately resolved, this method falls back to iterating
Expand Down
Loading
Loading