[red-knot] Make' Type::in_type_expression()' exhaustive for Type::KnownInstance (#16836)

<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

fixes #15048 
We want to handle more types from Type::KnownInstance 

## Test Plan

Add tests for each type added explicitly in the match

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
This commit is contained in:
Matthew Mckee 2025-03-19 14:36:28 +00:00 committed by GitHub
parent f3f3e55d97
commit 3a5f1d46c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 108 additions and 10 deletions

View file

@ -156,7 +156,7 @@ def f():
```py
from typing import Literal
# error: [invalid-type-form] "`Literal` requires at least one argument when used in a type expression"
# error: [invalid-type-form] "`typing.Literal` requires at least one argument when used in a type expression"
def _(x: Literal):
reveal_type(x) # revealed: Unknown
```

View file

@ -45,3 +45,13 @@ def f():
# revealed: int | None
reveal_type(a)
```
## Invalid
```py
from typing import Optional
# error: [invalid-type-form] "`typing.Optional` requires exactly one argument when used in a type expression"
def f(x: Optional) -> None:
reveal_type(x) # revealed: Unknown
```

View file

@ -59,3 +59,13 @@ def f():
# revealed: int | str
reveal_type(a)
```
## Invalid
```py
from typing import Union
# error: [invalid-type-form] "`typing.Union` requires at least one argument when used in a type expression"
def f(x: Union) -> None:
reveal_type(x) # revealed: Unknown
```

View file

@ -846,5 +846,19 @@ def mixed(
reveal_type(i4) # revealed: Any & Unknown
```
## Invalid
```py
from knot_extensions import Intersection, Not
# error: [invalid-type-form] "`knot_extensions.Intersection` requires at least one argument when used in a type expression"
def f(x: Intersection) -> None:
reveal_type(x) # revealed: Unknown
# error: [invalid-type-form] "`knot_extensions.Not` requires exactly one argument when used in a type expression"
def f(x: Not) -> None:
reveal_type(x) # revealed: Unknown
```
[complement laws]: https://en.wikipedia.org/wiki/Complement_(set_theory)
[de morgan's laws]: https://en.wikipedia.org/wiki/De_Morgan%27s_laws

View file

@ -13,3 +13,13 @@ def _(some_int: int, some_literal_int: Literal[1], some_indexable: SupportsIndex
b: SupportsIndex = some_literal_int
c: SupportsIndex = some_indexable
```
## Invalid
```py
from typing import Protocol
# error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions"
def f(x: Protocol) -> None:
reveal_type(x) # revealed: Unknown
```

View file

@ -392,6 +392,10 @@ def type_of_annotation() -> None:
# error: "Special form `knot_extensions.TypeOf` expected exactly one type parameter"
t: TypeOf[int, str, bytes]
# error: [invalid-type-form] "`knot_extensions.TypeOf` requires exactly one argument when used in a type expression"
def f(x: TypeOf) -> None:
reveal_type(x) # revealed: Unknown
```
## `CallableTypeFromFunction`
@ -418,6 +422,10 @@ def f3(x: int, y: str) -> None:
c1: CallableTypeFromFunction[f1, f2]
# error: [invalid-type-form] "Expected the first argument to `knot_extensions.CallableTypeFromFunction` to be a function literal, but got `Literal[int]`"
c2: CallableTypeFromFunction[int]
# error: [invalid-type-form] "`knot_extensions.CallableTypeFromFunction` requires exactly one argument when used in a type expression"
def f(x: CallableTypeFromFunction) -> None:
reveal_type(x) # revealed: Unknown
```
Using it in annotation to reveal the signature of the function:

View file

@ -3276,10 +3276,6 @@ impl<'db> Type<'db> {
],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(KnownInstanceType::Literal) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareLiteral],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(KnownInstanceType::Unknown) => Ok(Type::unknown()),
Type::KnownInstance(KnownInstanceType::AlwaysTruthy) => Ok(Type::AlwaysTruthy),
Type::KnownInstance(KnownInstanceType::AlwaysFalsy) => Ok(Type::AlwaysFalsy),
@ -3289,7 +3285,44 @@ impl<'db> Type<'db> {
GeneralCallableType::unknown(db),
)))
}
Type::KnownInstance(_) => Ok(todo_type!(
Type::KnownInstance(
KnownInstanceType::Literal
| KnownInstanceType::Union
| KnownInstanceType::Intersection,
) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::RequiresArguments(
*self
)],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(
KnownInstanceType::Optional
| KnownInstanceType::Not
| KnownInstanceType::TypeOf
| KnownInstanceType::CallableTypeFromFunction,
) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![
InvalidTypeExpression::RequiresOneArgument(*self)
],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(KnownInstanceType::Protocol) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![
InvalidTypeExpression::ProtocolInTypeExpression
],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(
KnownInstanceType::TypingSelf
| KnownInstanceType::ReadOnly
| KnownInstanceType::TypeAlias
| KnownInstanceType::NotRequired
| KnownInstanceType::Concatenate
| KnownInstanceType::TypeIs
| KnownInstanceType::TypeGuard
| KnownInstanceType::Unpack
| KnownInstanceType::Required,
) => Ok(todo_type!(
"Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`"
)),
Type::Instance(_) => Ok(todo_type!(
@ -3562,8 +3595,12 @@ impl<'db> InvalidTypeExpressionError<'db> {
enum InvalidTypeExpression<'db> {
/// `x: Annotated` is invalid as an annotation
BareAnnotated,
/// `x: Literal` is invalid as an annotation
BareLiteral,
/// Some types always require at least one argument when used in a type expression
RequiresArguments(Type<'db>),
/// Some types always require exactly one argument when used in a type expression
RequiresOneArgument(Type<'db>),
/// The `Protocol` type is invalid in type expressions
ProtocolInTypeExpression,
/// The `ClassVar` type qualifier was used in a type expression
ClassVarInTypeExpression,
/// The `Final` type qualifier was used in a type expression
@ -3585,8 +3622,17 @@ impl<'db> InvalidTypeExpression<'db> {
InvalidTypeExpression::BareAnnotated => f.write_str(
"`Annotated` requires at least two arguments when used in an annotation or type expression"
),
InvalidTypeExpression::BareLiteral => f.write_str(
"`Literal` requires at least one argument when used in a type expression"
InvalidTypeExpression::RequiresOneArgument(ty) => write!(
f,
"`{ty}` requires exactly one argument when used in a type expression",
ty = ty.display(self.db)),
InvalidTypeExpression::RequiresArguments(ty) => write!(
f,
"`{ty}` requires at least one argument when used in a type expression",
ty = ty.display(self.db)
),
InvalidTypeExpression::ProtocolInTypeExpression => f.write_str(
"`typing.Protocol` is not allowed in type expressions"
),
InvalidTypeExpression::ClassVarInTypeExpression => f.write_str(
"Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"