@@ -4,16 +4,16 @@ use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Vi
4
4
use ruff_macros:: { derive_message_formats, ViolationMetadata } ;
5
5
use ruff_python_ast:: name:: Name ;
6
6
use ruff_python_ast:: {
7
- self as ast, visitor:: Visitor , Expr , ExprCall , ExprName , Keyword , Stmt , StmtAnnAssign ,
8
- StmtAssign , StmtTypeAlias , TypeParam ,
7
+ visitor:: Visitor , Expr , ExprCall , ExprName , Keyword , StmtAnnAssign , StmtAssign ,
9
8
} ;
10
- use ruff_python_codegen:: Generator ;
11
9
use ruff_text_size:: { Ranged , TextRange } ;
12
10
13
11
use crate :: checkers:: ast:: Checker ;
14
12
use crate :: settings:: types:: PythonVersion ;
15
13
16
- use super :: { expr_name_to_type_var, TypeParamKind , TypeVar , TypeVarReferenceVisitor } ;
14
+ use super :: {
15
+ expr_name_to_type_var, DisplayTypeVars , TypeParamKind , TypeVar , TypeVarReferenceVisitor ,
16
+ } ;
17
17
18
18
/// ## What it does
19
19
/// Checks for use of `TypeAlias` annotations and `TypeAliasType` assignments
@@ -50,6 +50,13 @@ use super::{expr_name_to_type_var, TypeParamKind, TypeVar, TypeVarReferenceVisit
50
50
/// type PositiveInt = Annotated[int, Gt(0)]
51
51
/// ```
52
52
///
53
+ /// ## Fix safety
54
+ ///
55
+ /// This fix is marked unsafe for `TypeAlias` assignments outside of stub files because of the
56
+ /// runtime behavior around `isinstance()` calls noted above. The fix is also unsafe for
57
+ /// `TypeAliasType` assignments if there are any comments in the replacement range that would be
58
+ /// deleted.
59
+ ///
53
60
/// [PEP 695]: https://peps.python.org/pep-0695/
54
61
#[ derive( ViolationMetadata ) ]
55
62
pub ( crate ) struct NonPEP695TypeAlias {
@@ -145,13 +152,25 @@ pub(crate) fn non_pep695_type_alias_type(checker: &mut Checker, stmt: &StmtAssig
145
152
return ;
146
153
} ;
147
154
155
+ // it would be easier to check for comments in the whole `stmt.range`, but because
156
+ // `create_diagnostic` uses the full source text of `value`, comments within `value` are
157
+ // actually preserved. thus, we have to check for comments in `stmt` but outside of `value`
158
+ let pre_value = TextRange :: new ( stmt. start ( ) , value. start ( ) ) ;
159
+ let post_value = TextRange :: new ( value. end ( ) , stmt. end ( ) ) ;
160
+ let comment_ranges = checker. comment_ranges ( ) ;
161
+ let safety = if comment_ranges. intersects ( pre_value) || comment_ranges. intersects ( post_value) {
162
+ Applicability :: Unsafe
163
+ } else {
164
+ Applicability :: Safe
165
+ } ;
166
+
148
167
checker. diagnostics . push ( create_diagnostic (
149
- checker. generator ( ) ,
150
- stmt. range ( ) ,
151
- target_name. id . clone ( ) ,
168
+ checker. source ( ) ,
169
+ stmt. range ,
170
+ & target_name. id ,
152
171
value,
153
172
& vars,
154
- Applicability :: Safe ,
173
+ safety ,
155
174
TypeAliasKind :: TypeAliasType ,
156
175
) ) ;
157
176
}
@@ -184,8 +203,6 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
184
203
return ;
185
204
} ;
186
205
187
- // TODO(zanie): We should check for generic type variables used in the value and define them
188
- // as type params instead
189
206
let vars = {
190
207
let mut visitor = TypeVarReferenceVisitor {
191
208
vars : vec ! [ ] ,
@@ -208,9 +225,9 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
208
225
}
209
226
210
227
checker. diagnostics . push ( create_diagnostic (
211
- checker. generator ( ) ,
228
+ checker. source ( ) ,
212
229
stmt. range ( ) ,
213
- name. clone ( ) ,
230
+ name,
214
231
value,
215
232
& vars,
216
233
// The fix is only safe in a type stub because new-style aliases have different runtime behavior
@@ -226,46 +243,26 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
226
243
227
244
/// Generate a [`Diagnostic`] for a non-PEP 695 type alias or type alias type.
228
245
fn create_diagnostic (
229
- generator : Generator ,
246
+ source : & str ,
230
247
stmt_range : TextRange ,
231
- name : Name ,
248
+ name : & Name ,
232
249
value : & Expr ,
233
- vars : & [ TypeVar ] ,
250
+ type_vars : & [ TypeVar ] ,
234
251
applicability : Applicability ,
235
252
type_alias_kind : TypeAliasKind ,
236
253
) -> Diagnostic {
254
+ let content = format ! (
255
+ "type {name}{type_params} = {value}" ,
256
+ type_params = DisplayTypeVars { type_vars, source } ,
257
+ value = & source[ value. range( ) ]
258
+ ) ;
259
+ let edit = Edit :: range_replacement ( content, stmt_range) ;
237
260
Diagnostic :: new (
238
261
NonPEP695TypeAlias {
239
262
name : name. to_string ( ) ,
240
263
type_alias_kind,
241
264
} ,
242
265
stmt_range,
243
266
)
244
- . with_fix ( Fix :: applicable_edit (
245
- Edit :: range_replacement (
246
- generator. stmt ( & Stmt :: from ( StmtTypeAlias {
247
- range : TextRange :: default ( ) ,
248
- name : Box :: new ( Expr :: Name ( ExprName {
249
- range : TextRange :: default ( ) ,
250
- id : name,
251
- ctx : ast:: ExprContext :: Load ,
252
- } ) ) ,
253
- type_params : create_type_params ( vars) ,
254
- value : Box :: new ( value. clone ( ) ) ,
255
- } ) ) ,
256
- stmt_range,
257
- ) ,
258
- applicability,
259
- ) )
260
- }
261
-
262
- fn create_type_params ( vars : & [ TypeVar ] ) -> Option < ruff_python_ast:: TypeParams > {
263
- if vars. is_empty ( ) {
264
- return None ;
265
- }
266
-
267
- Some ( ast:: TypeParams {
268
- range : TextRange :: default ( ) ,
269
- type_params : vars. iter ( ) . map ( TypeParam :: from) . collect ( ) ,
270
- } )
267
+ . with_fix ( Fix :: applicable_edit ( edit, applicability) )
271
268
}
0 commit comments