1
1
use ruff_diagnostics:: { Applicability , Diagnostic , Edit , Fix , FixAvailability , Violation } ;
2
2
use ruff_macros:: { derive_message_formats, ViolationMetadata } ;
3
- use ruff_python_ast:: helpers:: is_dunder;
4
3
use ruff_python_ast:: { self as ast, Arguments , Expr , ExprContext , Identifier , Keyword , Stmt } ;
5
4
use ruff_python_codegen:: Generator ;
6
5
use ruff_python_semantic:: SemanticModel ;
@@ -15,12 +14,22 @@ use crate::checkers::ast::Checker;
15
14
/// Checks for `TypedDict` declarations that use functional syntax.
16
15
///
17
16
/// ## Why is this bad?
18
- /// `TypedDict` subclasses can be defined either through a functional syntax
17
+ /// `TypedDict` types can be defined either through a functional syntax
19
18
/// (`Foo = TypedDict(...)`) or a class syntax (`class Foo(TypedDict): ...`).
20
19
///
21
20
/// The class syntax is more readable and generally preferred over the
22
21
/// functional syntax.
23
22
///
23
+ /// Nonetheless, there are some situations in which it is impossible to use
24
+ /// the class-based syntax. This rule will not apply to those cases. Namely,
25
+ /// it is impossible to use the class-based syntax if any `TypedDict` fields are:
26
+ /// - Not valid [python identifiers] (for example, `@x`)
27
+ /// - [Python keywords] such as `in`
28
+ /// - [Private names] such as `__id` that would undergo [name mangling] at runtime
29
+ /// if the class-based syntax was used
30
+ /// - [Dunder names] such as `__int__` that can confuse type checkers if they're used
31
+ /// with the class-based syntax.
32
+ ///
24
33
/// ## Example
25
34
/// ```python
26
35
/// from typing import TypedDict
@@ -45,6 +54,12 @@ use crate::checkers::ast::Checker;
45
54
///
46
55
/// ## References
47
56
/// - [Python documentation: `typing.TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict)
57
+ ///
58
+ /// [Private names]: https://docs.python.org/3/tutorial/classes.html#private-variables
59
+ /// [name mangling]: https://docs.python.org/3/reference/expressions.html#private-name-mangling
60
+ /// [python identifiers]: https://docs.python.org/3/reference/lexical_analysis.html#identifiers
61
+ /// [Python keywords]: https://docs.python.org/3/reference/lexical_analysis.html#keywords
62
+ /// [Dunder names]: https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers
48
63
#[ derive( ViolationMetadata ) ]
49
64
pub ( crate ) struct ConvertTypedDictFunctionalToClass {
50
65
name : String ,
@@ -185,7 +200,10 @@ fn fields_from_dict_literal(items: &[ast::DictItem]) -> Option<Vec<Stmt>> {
185
200
if !is_identifier ( field. to_str ( ) ) {
186
201
return None ;
187
202
}
188
- if is_dunder ( field. to_str ( ) ) {
203
+ // Converting TypedDict to class-based syntax is not safe if fields contain
204
+ // private or dunder names, because private names will be mangled and dunder
205
+ // names can confuse type checkers.
206
+ if field. to_str ( ) . starts_with ( "__" ) {
189
207
return None ;
190
208
}
191
209
Some ( create_field_assignment_stmt ( field. to_str ( ) , value) )
0 commit comments