mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:24 +00:00
[red-knot] No errors for definitions of TypedDict
s (#17674)
## Summary
Do not emit errors when defining `TypedDict`s:
```py
from typing_extensions import TypedDict
# No error here
class Person(TypedDict):
name: str
age: int | None
# No error for this alternative syntax
Message = TypedDict("Message", {"id": int, "content": str})
```
## Ecosystem analysis
* Removes ~ 450 false positives for `TypedDict` definitions.
* Changes a few diagnostic messages.
* Adds a few (< 10) false positives, for example:
```diff
+ error[lint:unresolved-attribute]
/tmp/mypy_primer/projects/hydra-zen/src/hydra_zen/structured_configs/_utils.py:262:5:
Type `Literal[DataclassOptions]` has no attribute `__required_keys__`
+ error[lint:unresolved-attribute]
/tmp/mypy_primer/projects/hydra-zen/src/hydra_zen/structured_configs/_utils.py:262:42:
Type `Literal[DataclassOptions]` has no attribute `__optional_keys__`
```
* New true positive
4f8263cd7f/corporate/lib/remote_billing_util.py (L155-L157)
```diff
+ error[lint:invalid-assignment]
/tmp/mypy_primer/projects/zulip/corporate/lib/remote_billing_util.py:155:5:
Object of type `RemoteBillingIdentityDict | LegacyServerIdentityDict |
None` is not assignable to `LegacyServerIdentityDict | None`
```
## Test Plan
New Markdown tests
This commit is contained in:
parent
74081032d9
commit
f521358033
10 changed files with 84 additions and 9 deletions
|
@ -38,8 +38,12 @@ bad_nesting: Literal[LiteralString] # error: [invalid-type-form]
|
|||
```py
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
a: LiteralString[str] # error: [invalid-type-form]
|
||||
b: LiteralString["foo"] # error: [invalid-type-form]
|
||||
# error: [invalid-type-form]
|
||||
a: LiteralString[str]
|
||||
|
||||
# error: [invalid-type-form]
|
||||
# error: [unresolved-reference] "Name `foo` used when not defined"
|
||||
b: LiteralString["foo"]
|
||||
```
|
||||
|
||||
### As a base class
|
||||
|
|
|
@ -89,9 +89,12 @@ python-version = "3.12"
|
|||
Some of these are not subscriptable:
|
||||
|
||||
```py
|
||||
from typing_extensions import Self, TypeAlias
|
||||
from typing_extensions import Self, TypeAlias, TypeVar
|
||||
|
||||
X: TypeAlias[T] = int # error: [invalid-type-form]
|
||||
T = TypeVar("T")
|
||||
|
||||
# error: [invalid-type-form] "Special form `typing.TypeAlias` expected no type parameter"
|
||||
X: TypeAlias[T] = int
|
||||
|
||||
class Foo[T]:
|
||||
# error: [invalid-type-form] "Special form `typing.Self` expected no type parameter"
|
||||
|
|
|
@ -11,8 +11,6 @@ from typing_extensions import Final, Required, NotRequired, ReadOnly, TypedDict
|
|||
X: Final = 42
|
||||
Y: Final[int] = 42
|
||||
|
||||
# TODO: `TypedDict` is actually valid as a base
|
||||
# error: [invalid-base]
|
||||
class Bar(TypedDict):
|
||||
x: Required[int]
|
||||
y: NotRequired[str]
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# `TypedDict`
|
||||
|
||||
We do not support `TypedDict`s yet. This test mainly exists to make sure that we do not emit any
|
||||
errors for the definition of a `TypedDict`.
|
||||
|
||||
```py
|
||||
from typing_extensions import TypedDict, Required
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
age: int | None
|
||||
|
||||
# TODO: This should not be an error:
|
||||
# error: [invalid-assignment]
|
||||
alice: Person = {"name": "Alice", "age": 30}
|
||||
|
||||
# Alternative syntax
|
||||
Message = TypedDict("Message", {"id": Required[int], "content": str}, total=False)
|
||||
|
||||
msg = Message(id=1, content="Hello")
|
||||
|
||||
# No errors for yet-unsupported features (`closed`):
|
||||
OtherMessage = TypedDict("OtherMessage", {"id": int, "content": str}, closed=True)
|
||||
```
|
|
@ -3890,6 +3890,28 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
},
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::TypedDict) => {
|
||||
Signatures::single(CallableSignature::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("typename")))
|
||||
.with_annotated_type(KnownClass::Str.to_instance(db)),
|
||||
Parameter::positional_only(Some(Name::new_static("fields")))
|
||||
.with_annotated_type(KnownClass::Dict.to_instance(db))
|
||||
.with_default_type(Type::any()),
|
||||
Parameter::keyword_only(Name::new_static("total"))
|
||||
.with_annotated_type(KnownClass::Bool.to_instance(db))
|
||||
.with_default_type(Type::BooleanLiteral(true)),
|
||||
// Future compatibility, in case new keyword arguments will be added:
|
||||
Parameter::keyword_variadic(Name::new_static("kwargs"))
|
||||
.with_annotated_type(Type::any()),
|
||||
]),
|
||||
None,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
Type::GenericAlias(_) => {
|
||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||
// TODO check call vs signatures of `__new__` and/or `__init__`
|
||||
|
@ -4471,6 +4493,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
KnownInstanceType::TypingSelf => Ok(todo_type!("Support for `typing.Self`")),
|
||||
KnownInstanceType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")),
|
||||
KnownInstanceType::TypedDict => Ok(todo_type!("Support for `typing.TypedDict`")),
|
||||
|
||||
KnownInstanceType::Protocol => Err(InvalidTypeExpressionError {
|
||||
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol],
|
||||
|
|
|
@ -19,9 +19,9 @@ use crate::types::diagnostic::{
|
|||
use crate::types::generics::{Specialization, SpecializationBuilder};
|
||||
use crate::types::signatures::{Parameter, ParameterForm};
|
||||
use crate::types::{
|
||||
BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, KnownClass,
|
||||
KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, TupleType,
|
||||
UnionType, WrapperDescriptorKind,
|
||||
todo_type, BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators,
|
||||
KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType,
|
||||
TupleType, UnionType, WrapperDescriptorKind,
|
||||
};
|
||||
use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic};
|
||||
use ruff_python_ast as ast;
|
||||
|
@ -772,6 +772,10 @@ impl<'db> Bindings<'db> {
|
|||
_ => {}
|
||||
},
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::TypedDict) => {
|
||||
overload.set_return_type(todo_type!("TypedDict"));
|
||||
}
|
||||
|
||||
// Not a special case
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -169,6 +169,9 @@ impl<'db> ClassBase<'db> {
|
|||
KnownInstanceType::OrderedDict => {
|
||||
Self::try_from_type(db, KnownClass::OrderedDict.to_class_literal(db))
|
||||
}
|
||||
KnownInstanceType::TypedDict => {
|
||||
Self::try_from_type(db, KnownClass::Dict.to_class_literal(db))
|
||||
}
|
||||
KnownInstanceType::Callable => {
|
||||
Self::try_from_type(db, todo_type!("Support for Callable as a base class"))
|
||||
}
|
||||
|
|
|
@ -7710,6 +7710,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
| KnownInstanceType::Any
|
||||
| KnownInstanceType::AlwaysTruthy
|
||||
| KnownInstanceType::AlwaysFalsy => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Type `{}` expected no type parameter",
|
||||
|
@ -7720,7 +7722,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
}
|
||||
KnownInstanceType::TypingSelf
|
||||
| KnownInstanceType::TypeAlias
|
||||
| KnownInstanceType::TypedDict
|
||||
| KnownInstanceType::Unknown => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Special form `{}` expected no type parameter",
|
||||
|
@ -7730,6 +7735,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Type::unknown()
|
||||
}
|
||||
KnownInstanceType::LiteralString => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||
let mut diag = builder.into_diagnostic(format_args!(
|
||||
"Type `{}` expected no type parameter",
|
||||
|
|
|
@ -95,6 +95,7 @@ pub enum KnownInstanceType<'db> {
|
|||
NotRequired,
|
||||
TypeAlias,
|
||||
TypeGuard,
|
||||
TypedDict,
|
||||
TypeIs,
|
||||
ReadOnly,
|
||||
// TODO: fill this enum out with more special forms, etc.
|
||||
|
@ -125,6 +126,7 @@ impl<'db> KnownInstanceType<'db> {
|
|||
| Self::NotRequired
|
||||
| Self::TypeAlias
|
||||
| Self::TypeGuard
|
||||
| Self::TypedDict
|
||||
| Self::TypeIs
|
||||
| Self::List
|
||||
| Self::Dict
|
||||
|
@ -172,6 +174,7 @@ impl<'db> KnownInstanceType<'db> {
|
|||
Self::NotRequired => "typing.NotRequired",
|
||||
Self::TypeAlias => "typing.TypeAlias",
|
||||
Self::TypeGuard => "typing.TypeGuard",
|
||||
Self::TypedDict => "typing.TypedDict",
|
||||
Self::TypeIs => "typing.TypeIs",
|
||||
Self::List => "typing.List",
|
||||
Self::Dict => "typing.Dict",
|
||||
|
@ -220,6 +223,7 @@ impl<'db> KnownInstanceType<'db> {
|
|||
Self::NotRequired => KnownClass::SpecialForm,
|
||||
Self::TypeAlias => KnownClass::SpecialForm,
|
||||
Self::TypeGuard => KnownClass::SpecialForm,
|
||||
Self::TypedDict => KnownClass::SpecialForm,
|
||||
Self::TypeIs => KnownClass::SpecialForm,
|
||||
Self::ReadOnly => KnownClass::SpecialForm,
|
||||
Self::List => KnownClass::StdlibAlias,
|
||||
|
@ -293,6 +297,7 @@ impl<'db> KnownInstanceType<'db> {
|
|||
"Required" => Self::Required,
|
||||
"TypeAlias" => Self::TypeAlias,
|
||||
"TypeGuard" => Self::TypeGuard,
|
||||
"TypedDict" => Self::TypedDict,
|
||||
"TypeIs" => Self::TypeIs,
|
||||
"ReadOnly" => Self::ReadOnly,
|
||||
"Concatenate" => Self::Concatenate,
|
||||
|
@ -350,6 +355,7 @@ impl<'db> KnownInstanceType<'db> {
|
|||
| Self::NotRequired
|
||||
| Self::TypeAlias
|
||||
| Self::TypeGuard
|
||||
| Self::TypedDict
|
||||
| Self::TypeIs
|
||||
| Self::ReadOnly
|
||||
| Self::TypeAliasType(_)
|
||||
|
|
|
@ -221,6 +221,9 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
|||
(KnownInstanceType::TypeGuard, _) => Ordering::Less,
|
||||
(_, KnownInstanceType::TypeGuard) => Ordering::Greater,
|
||||
|
||||
(KnownInstanceType::TypedDict, _) => Ordering::Less,
|
||||
(_, KnownInstanceType::TypedDict) => Ordering::Greater,
|
||||
|
||||
(KnownInstanceType::List, _) => Ordering::Less,
|
||||
(_, KnownInstanceType::List) => Ordering::Greater,
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue