@@ -160,7 +160,7 @@ def _parse_tree(
160
160
expected_parent_name : str | None = None ,
161
161
parent : ast .AST | None = None ,
162
162
) -> ast .AST | None :
163
- """Parse the given ast for an object with name "object_name"."""
163
+ """Parse the given ast for an object with name "object_name"."""
164
164
if hasattr (tree_or_leaf , "name" ) and tree_or_leaf .name == object_name :
165
165
if not expected_parent_name :
166
166
return tree_or_leaf
@@ -175,6 +175,8 @@ def _parse_tree(
175
175
if not hasattr (tree_or_leaf , "body" ):
176
176
return None
177
177
178
+ assert isinstance (tree_or_leaf , (ast .Module , ast .ClassDef ))
179
+
178
180
for obj in tree_or_leaf .body :
179
181
if hasattr (obj , "body" ) and (
180
182
res := _parse_tree (
@@ -201,14 +203,25 @@ def get_source_for_object(
201
203
expected_parent : str | None = None ,
202
204
) -> tuple [str , int ] | None :
203
205
"""Return the source code of object by parsing the input source."""
204
- if res := _parse_tree (
205
- ast .parse (source ),
206
+ tree : ast .Module = ast .parse (source )
207
+ ast_ = _parse_tree (
208
+ tree ,
206
209
object_name ,
207
210
expected_parent_name = expected_parent ,
208
- ):
209
- return ast .unparse (res ), res .lineno
211
+ )
210
212
211
- return None
213
+ if not ast_ :
214
+ return None
215
+
216
+ # Not all ast.AST types have a `lineno` attribute, for example:
217
+ # > ast.parse(code)
218
+ # produces an `ast.Module` instance which does not have `lineno`, but only a `body`
219
+ # attribute, which is a list of all the parsed ast.AST-derived objects which do
220
+ # have `lineno`. The following assert makes mypy happy about this.
221
+ # Note that ast.AST.lineno does exist: https://docs.python.org/3/library/ast.html#ast.AST.lineno
222
+ assert hasattr (ast_ , "lineno" )
223
+
224
+ return ast .unparse (ast_ ), ast_ .lineno
212
225
213
226
214
227
if __name__ == "__main__" :
0 commit comments