@@ -66,6 +66,12 @@ class TypingAlias(NamedTuple):
66
66
67
67
ALIAS_NAMES = frozenset (key .split ("." )[1 ] for key in DEPRECATED_TYPING_ALIASES )
68
68
UNION_NAMES = ("Optional" , "Union" )
69
+ TYPING_NORETURN = frozenset (
70
+ (
71
+ "typing.NoReturn" ,
72
+ "typing_extensions.NoReturn" ,
73
+ )
74
+ )
69
75
70
76
71
77
class DeprecatedTypingAliasMsg (NamedTuple ):
@@ -101,6 +107,14 @@ class TypingChecker(BaseChecker):
101
107
"Emitted when 'typing.Union' or 'typing.Optional' is used "
102
108
"instead of the alternative Union syntax 'int | None'." ,
103
109
),
110
+ "E6004" : (
111
+ "'NoReturn' inside compound types is broken in 3.7.0 / 3.7.1" ,
112
+ "broken-noreturn" ,
113
+ "``typing.NoReturn`` inside compound types is broken in "
114
+ "Python 3.7.0 and 3.7.1. If not dependend on runtime introspection, "
115
+ "use string annotation instead. E.g. "
116
+ "``Callable[..., 'NoReturn']``. https://bugs.python.org/issue34921" ,
117
+ ),
104
118
}
105
119
options = (
106
120
(
@@ -151,6 +165,8 @@ def open(self) -> None:
151
165
self ._py37_plus and self .config .runtime_typing is False
152
166
)
153
167
168
+ self ._should_check_noreturn = py_version < (3 , 7 , 2 )
169
+
154
170
def _msg_postponed_eval_hint (self , node ) -> str :
155
171
"""Message hint if postponed evaluation isn't enabled."""
156
172
if self ._py310_plus or "annotations" in node .root ().future_imports :
@@ -161,23 +177,29 @@ def _msg_postponed_eval_hint(self, node) -> str:
161
177
"deprecated-typing-alias" ,
162
178
"consider-using-alias" ,
163
179
"consider-alternative-union-syntax" ,
180
+ "broken-noreturn" ,
164
181
)
165
182
def visit_name (self , node : nodes .Name ) -> None :
166
183
if self ._should_check_typing_alias and node .name in ALIAS_NAMES :
167
184
self ._check_for_typing_alias (node )
168
185
if self ._should_check_alternative_union_syntax and node .name in UNION_NAMES :
169
186
self ._check_for_alternative_union_syntax (node , node .name )
187
+ if self ._should_check_noreturn and node .name == "NoReturn" :
188
+ self ._check_broken_noreturn (node )
170
189
171
190
@check_messages (
172
191
"deprecated-typing-alias" ,
173
192
"consider-using-alias" ,
174
193
"consider-alternative-union-syntax" ,
194
+ "broken-noreturn" ,
175
195
)
176
196
def visit_attribute (self , node : nodes .Attribute ) -> None :
177
197
if self ._should_check_typing_alias and node .attrname in ALIAS_NAMES :
178
198
self ._check_for_typing_alias (node )
179
199
if self ._should_check_alternative_union_syntax and node .attrname in UNION_NAMES :
180
200
self ._check_for_alternative_union_syntax (node , node .attrname )
201
+ if self ._should_check_noreturn and node .attrname == "NoReturn" :
202
+ self ._check_broken_noreturn (node )
181
203
182
204
def _check_for_alternative_union_syntax (
183
205
self ,
@@ -281,6 +303,17 @@ def leave_module(self, node: nodes.Module) -> None:
281
303
self ._alias_name_collisions .clear ()
282
304
self ._consider_using_alias_msgs .clear ()
283
305
306
+ def _check_broken_noreturn (self , node : Union [nodes .Name , nodes .Attribute ]) -> None :
307
+ """Check for 'NoReturn' inside compound types."""
308
+ for inferred in node .infer ():
309
+ if (
310
+ isinstance (inferred , (nodes .FunctionDef , nodes .ClassDef ))
311
+ and inferred .qname () in TYPING_NORETURN
312
+ and isinstance (node .parent , nodes .BaseContainer )
313
+ ):
314
+ self .add_message ("broken-noreturn" , node = node )
315
+ break
316
+
284
317
285
318
def register (linter : PyLinter ) -> None :
286
319
linter .register_checker (TypingChecker (linter ))
0 commit comments