@@ -67,32 +67,14 @@ def import_name(self, node: ast.Import) -> str:
6767 )
6868
6969 def class_def (self , node : ast .ClassDef , * , visibility : str ) -> SemanticClass :
70- fields : list [SemanticArgument ] = []
71- methods : list [SemanticMethod ] = []
72-
73- for item in node .body :
74- if isinstance (item , ast .Pass ):
75- continue
76- if isinstance (item , ast .AnnAssign ):
77- fields .append (self .ann_assign (item , default_intent = "in" ))
78- continue
79- if isinstance (item , ast .FunctionDef ):
80- decorators = self .decorators (item .decorator_list , context = "class body" )
81- methods .append (
82- self .method_def (
83- item ,
84- visibility = decorators .visibility ,
85- projection = decorators .projection ,
86- )
87- )
88- continue
89- raise ValueError (f"Unsupported class body node: { _node_text (item )!r} " )
70+ body = _ClassBodyVisitor (self )
71+ body .visit_body (node .body )
9072
9173 return SemanticClass (
9274 name = node .name ,
9375 native_name = node .name ,
94- fields = fields ,
95- methods = methods ,
76+ fields = body . fields ,
77+ methods = body . methods ,
9678 base_classes = [ast .unparse (base ) for base in node .bases ],
9779 visibility = visibility ,
9880 )
@@ -152,10 +134,10 @@ def ann_assign(self, node: ast.AnnAssign, *, default_intent: str) -> SemanticArg
152134 def decorators (self , nodes : list [ast .expr ], * , context : str ) -> _Decorators :
153135 parsed = _Decorators ()
154136 for node in nodes :
155- if isinstance (node , ast . Name ) and node . id == "private" :
137+ if self . matches_name (node , "private" ) :
156138 parsed .visibility = "private"
157139 continue
158- if isinstance (node , ast .Call ) and isinstance (node .func , ast . Name ) and node . func . id == "native_call" :
140+ if isinstance (node , ast .Call ) and self . matches_name (node .func , "native_call" ) :
159141 parsed .has_native_call = True
160142 parsed .projection = self .native_call (node )
161143 continue
@@ -174,12 +156,12 @@ def native_call(self, node: ast.Call) -> list[ProjectionMapping]:
174156 ]
175157
176158 def native_projection_entry (self , node : ast .AST , native_position : int ) -> ProjectionMapping :
177- if not isinstance (node , ast .Call ) or not isinstance ( node . func , ast . Name ) :
159+ if not isinstance (node , ast .Call ):
178160 raise ValueError ("native_call expects projection entry calls" )
179161 if node .keywords :
180- raise ValueError (f"{ node .func . id } expects positional arguments only" )
162+ raise ValueError (f"{ self . required_name ( node .func ) } expects positional arguments only" )
181163
182- helper = node .func . id
164+ helper = self . required_name ( node .func )
183165 if helper == "Arg" :
184166 if len (node .args ) != 1 :
185167 raise ValueError ("Arg expects one positional index" )
@@ -239,27 +221,28 @@ def native_projection_entry(self, node: ast.AST, native_position: int) -> Projec
239221 raise ValueError (f"Unsupported native_call projection entry: { helper } " )
240222
241223 def native_value_ref (self , node : ast .AST ) -> dict [str , int | str ]:
242- if not isinstance (node , ast .Call ) or not isinstance ( node . func , ast . Name ) :
224+ if not isinstance (node , ast .Call ):
243225 raise ValueError ("Expected Arg(...), Return(...), or Work(...) value reference" )
244226 if node .keywords or len (node .args ) != 1 :
245- raise ValueError (f"{ node .func .id } value reference expects one positional argument" )
246- if node .func .id == "Arg" :
227+ raise ValueError (f"{ self .required_name (node .func )} value reference expects one positional argument" )
228+ helper = self .required_name (node .func )
229+ if helper == "Arg" :
247230 return {"kind" : "arg" , "position" : int (ast .literal_eval (node .args [0 ]))}
248- if node . func . id == "Return" :
231+ if helper == "Return" :
249232 return {"kind" : "return" , "position" : int (ast .literal_eval (node .args [0 ]))}
250- if node . func . id == "Work" :
233+ if helper == "Work" :
251234 return {"kind" : "work" , "name" : str (ast .literal_eval (node .args [0 ]))}
252235 raise ValueError ("Expected Arg(...), Return(...), or Work(...) value reference" )
253236
254237 def visible_type (self , node : ast .expr ) -> tuple [str , SemanticType , str | None ]:
255- if self .subscript_name (node ) == "private" :
238+ if self .is_subscript_of (node , "private" ) :
256239 semantic_type , original_name = self .semantic_type_annotation (self .subscript_slice (node ))
257240 return "private" , semantic_type , original_name
258241 semantic_type , original_name = self .semantic_type_annotation (node )
259242 return "public" , semantic_type , original_name
260243
261244 def semantic_type_annotation (self , node : ast .expr ) -> tuple [SemanticType , str | None ]:
262- if self .subscript_name (node ) != "Annotated" :
245+ if not self .is_subscript_of (node , "Annotated" ) :
263246 return self .semantic_type (node ), None
264247
265248 items = self .subscript_items (node )
@@ -274,7 +257,7 @@ def semantic_type_annotation(self, node: ast.expr) -> tuple[SemanticType, str |
274257 return self .semantic_type (items [0 ]), original_name
275258
276259 def semantic_type (self , node : ast .expr ) -> SemanticType :
277- if self .subscript_name (node ) == "Annotated" :
260+ if self .is_subscript_of (node , "Annotated" ) :
278261 semantic_type , _ = self .semantic_type_annotation (node )
279262 return semantic_type
280263
@@ -299,9 +282,9 @@ def semantic_type(self, node: ast.expr) -> SemanticType:
299282 def constraint (self , node : ast .expr ) -> SemanticConstraint :
300283 if isinstance (node , ast .Name ):
301284 return SemanticConstraint (node .id )
302- if isinstance (node , ast .Call ) and isinstance ( node . func , ast . Name ) :
285+ if isinstance (node , ast .Call ):
303286 return SemanticConstraint (
304- name = node .func . id ,
287+ name = self . required_name ( node .func ) ,
305288 arguments = [ast .literal_eval (arg ) for arg in node .args ],
306289 )
307290 raise ValueError (f"Unsupported semantic type constraint: { ast .unparse (node )!r} " )
@@ -338,7 +321,7 @@ def return_projection(self, node: ast.expr) -> tuple[SemanticType | None, list[S
338321 return return_type , returned_args
339322
340323 def returned_argument (self , node : ast .expr ) -> SemanticArgument | None :
341- if self .subscript_name (node ) != "Returns" :
324+ if not self .is_subscript_of (node , "Returns" ) :
342325 return None
343326 items = self .subscript_items (node )
344327 if len (items ) not in {2 , 3 }:
@@ -355,7 +338,7 @@ def returned_argument(self, node: ast.expr) -> SemanticArgument | None:
355338
356339 @staticmethod
357340 def name_metadata (node : ast .expr ) -> str | None :
358- if isinstance (node , ast .Call ) and isinstance (node .func , ast . Name ) and node . func . id == "Name" :
341+ if isinstance (node , ast .Call ) and _PyiAstParser . matches_name (node .func , "Name" ) :
359342 if len (node .args ) != 1 :
360343 raise ValueError (f"Name metadata expects one argument: { ast .unparse (node )!r} " )
361344 return str (ast .literal_eval (node .args [0 ]))
@@ -374,10 +357,31 @@ def default_marks_optional(node: ast.expr | None) -> bool:
374357 return isinstance (node , ast .Constant ) and node .value in {Ellipsis , None }
375358
376359 @staticmethod
377- def subscript_name (node : ast .AST ) -> str :
378- if isinstance (node , ast .Subscript ):
379- return ast .unparse (node .value )
380- return ""
360+ def qualified_name (node : ast .AST ) -> tuple [str , ...] | None :
361+ if isinstance (node , ast .Name ):
362+ return (node .id ,)
363+ if isinstance (node , ast .Attribute ):
364+ parent = _PyiAstParser .qualified_name (node .value )
365+ if parent is None :
366+ return None
367+ return (* parent , node .attr )
368+ return None
369+
370+ @staticmethod
371+ def matches_name (node : ast .AST , name : str ) -> bool :
372+ qualified = _PyiAstParser .qualified_name (node )
373+ return qualified is not None and qualified [- 1 ] == name
374+
375+ @staticmethod
376+ def required_name (node : ast .AST ) -> str :
377+ qualified = _PyiAstParser .qualified_name (node )
378+ if qualified is None :
379+ raise ValueError (f"Expected named helper: { ast .unparse (node )!r} " )
380+ return qualified [- 1 ]
381+
382+ @staticmethod
383+ def is_subscript_of (node : ast .AST , name : str ) -> bool :
384+ return isinstance (node , ast .Subscript ) and _PyiAstParser .matches_name (node .value , name )
381385
382386 @staticmethod
383387 def subscript_slice (node : ast .AST ) -> ast .expr :
@@ -527,11 +531,41 @@ def _apply_native_call_argument_names(
527531 mapping .result_position = return_positions .get (arg .name )
528532
529533 def return_items (self , node : ast .expr ) -> list [ast .expr ]:
530- if self .subscript_name (node ) == "tuple" :
534+ if self .is_subscript_of (node , "tuple" ) or self . is_subscript_of ( node , "Tuple" ) :
531535 return self .subscript_items (node )
532536 return [node ]
533537
534538
539+ class _ClassBodyVisitor (ast .NodeVisitor ):
540+ def __init__ (self , parser : _PyiAstParser ):
541+ self .parser = parser
542+ self .fields : list [SemanticArgument ] = []
543+ self .methods : list [SemanticMethod ] = []
544+
545+ def visit_body (self , nodes : list [ast .stmt ]) -> None :
546+ for node in nodes :
547+ self .visit (node )
548+
549+ def visit_Pass (self , node : ast .Pass ) -> None :
550+ return None
551+
552+ def visit_AnnAssign (self , node : ast .AnnAssign ) -> None :
553+ self .fields .append (self .parser .ann_assign (node , default_intent = "in" ))
554+
555+ def visit_FunctionDef (self , node : ast .FunctionDef ) -> None :
556+ decorators = self .parser .decorators (node .decorator_list , context = "class body" )
557+ self .methods .append (
558+ self .parser .method_def (
559+ node ,
560+ visibility = decorators .visibility ,
561+ projection = decorators .projection ,
562+ )
563+ )
564+
565+ def generic_visit (self , node : ast .AST ) -> None :
566+ raise ValueError (f"Unsupported class body node: { _node_text (node )!r} " )
567+
568+
535569class _ModuleVisitor (ast .NodeVisitor ):
536570 def __init__ (self , parser : _PyiAstParser ):
537571 self .parser = parser
0 commit comments