@@ -66,6 +66,7 @@ pub enum SymbolTableType {
6666 Module ,
6767 Class ,
6868 Function ,
69+ Comprehension ,
6970}
7071
7172impl fmt:: Display for SymbolTableType {
@@ -74,6 +75,7 @@ impl fmt::Display for SymbolTableType {
7475 SymbolTableType :: Module => write ! ( f, "module" ) ,
7576 SymbolTableType :: Class => write ! ( f, "class" ) ,
7677 SymbolTableType :: Function => write ! ( f, "function" ) ,
78+ SymbolTableType :: Comprehension => write ! ( f, "comprehension" ) ,
7779 }
7880 }
7981}
@@ -99,6 +101,10 @@ pub struct Symbol {
99101 pub is_assigned : bool ,
100102 pub is_parameter : bool ,
101103 pub is_free : bool ,
104+
105+ // indicates if the symbol gets a value assigned by a named expression in a comprehension
106+ // this is required to correct the scope in the analysis.
107+ pub is_assign_namedexpr_in_comprehension : bool ,
102108}
103109
104110impl Symbol {
@@ -111,6 +117,7 @@ impl Symbol {
111117 is_assigned : false ,
112118 is_parameter : false ,
113119 is_free : false ,
120+ is_assign_namedexpr_in_comprehension : false ,
114121 }
115122 }
116123
@@ -193,72 +200,138 @@ impl<'a> SymbolTableAnalyzer<'a> {
193200 for sub_table in sub_tables {
194201 self . analyze_symbol_table ( sub_table) ?;
195202 }
196- let ( symbols, _ ) = self . tables . pop ( ) . unwrap ( ) ;
203+ let ( symbols, st_typ ) = self . tables . pop ( ) . unwrap ( ) ;
197204
198205 // Analyze symbols:
199206 for symbol in symbols. values_mut ( ) {
200- self . analyze_symbol ( symbol) ?;
207+ self . analyze_symbol ( symbol, st_typ ) ?;
201208 }
202-
203209 Ok ( ( ) )
204210 }
205211
206- fn analyze_symbol ( & self , symbol : & mut Symbol ) -> SymbolTableResult {
207- match symbol. scope {
208- SymbolScope :: Nonlocal => {
209- // check if name is defined in parent table!
210- let parent_symbol_table = self . tables . last ( ) ;
211- // symbol.table.borrow().parent.clone();
212-
213- if let Some ( ( symbols, _) ) = parent_symbol_table {
214- let scope_depth = self . tables . len ( ) ;
215- if !symbols. contains_key ( & symbol. name ) || scope_depth < 2 {
212+ fn analyze_symbol (
213+ & mut self ,
214+ symbol : & mut Symbol ,
215+ curr_st_typ : SymbolTableType ,
216+ ) -> SymbolTableResult {
217+ if symbol. is_assign_namedexpr_in_comprehension
218+ && curr_st_typ == SymbolTableType :: Comprehension
219+ {
220+ self . analyze_symbol_comprehension ( symbol, 0 ) ?
221+ } else {
222+ match symbol. scope {
223+ SymbolScope :: Nonlocal => {
224+ // check if name is defined in parent table!
225+ let parent_symbol_table = self . tables . last ( ) ;
226+ if let Some ( ( symbols, _) ) = parent_symbol_table {
227+ let scope_depth = self . tables . len ( ) ;
228+ if !symbols. contains_key ( & symbol. name ) || scope_depth < 2 {
229+ return Err ( SymbolTableError {
230+ error : format ! ( "no binding for nonlocal '{}' found" , symbol. name) ,
231+ location : Default :: default ( ) ,
232+ } ) ;
233+ }
234+ } else {
216235 return Err ( SymbolTableError {
217- error : format ! ( "no binding for nonlocal '{}' found" , symbol. name) ,
236+ error : format ! (
237+ "nonlocal {} defined at place without an enclosing scope" ,
238+ symbol. name
239+ ) ,
218240 location : Default :: default ( ) ,
219241 } ) ;
220242 }
221- } else {
222- return Err ( SymbolTableError {
223- error : format ! (
224- "nonlocal {} defined at place without an enclosing scope" ,
225- symbol. name
226- ) ,
227- location : Default :: default ( ) ,
228- } ) ;
243+ }
244+ SymbolScope :: Global => {
245+ // TODO: add more checks for globals?
246+ }
247+ SymbolScope :: Local => {
248+ // all is well
249+ }
250+ SymbolScope :: Unknown => {
251+ // Try hard to figure out what the scope of this symbol is.
252+ self . analyze_unknown_symbol ( symbol) ;
229253 }
230254 }
231- SymbolScope :: Global => {
232- // TODO: add more checks for globals?
233- }
234- SymbolScope :: Local => {
235- // all is well
255+ }
256+ Ok ( ( ) )
257+ }
258+
259+ fn analyze_unknown_symbol ( & self , symbol : & mut Symbol ) {
260+ if symbol. is_assigned || symbol. is_parameter {
261+ symbol. scope = SymbolScope :: Local ;
262+ } else {
263+ // Interesting stuff about the __class__ variable:
264+ // https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object
265+ let found_in_outer_scope = symbol. name == "__class__"
266+ || self . tables . iter ( ) . skip ( 1 ) . any ( |( symbols, typ) | {
267+ * typ != SymbolTableType :: Class && symbols. contains_key ( & symbol. name )
268+ } ) ;
269+
270+ if found_in_outer_scope {
271+ // Symbol is in some outer scope.
272+ symbol. is_free = true ;
273+ } else if self . tables . is_empty ( ) {
274+ // Don't make assumptions when we don't know.
275+ symbol. scope = SymbolScope :: Unknown ;
276+ } else {
277+ // If there are scopes above we can assume global.
278+ symbol. scope = SymbolScope :: Global ;
236279 }
237- SymbolScope :: Unknown => {
238- // Try hard to figure out what the scope of this symbol is.
280+ }
281+ }
239282
240- if symbol. is_assigned || symbol. is_parameter {
241- symbol. scope = SymbolScope :: Local ;
242- } else {
243- // Interesting stuff about the __class__ variable:
244- // https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object
245- let found_in_outer_scope = symbol. name == "__class__"
246- || self . tables . iter ( ) . skip ( 1 ) . any ( |( symbols, typ) | {
247- * typ != SymbolTableType :: Class && symbols. contains_key ( & symbol. name )
248- } ) ;
283+ // Implements the symbol analysis and scope extension for names
284+ // assigned by a named expression in a comprehension. See:
285+ // https://github.com/python/cpython/blob/7b78e7f9fd77bb3280ee39fb74b86772a7d46a70/Python/symtable.c#L1435
286+ fn analyze_symbol_comprehension (
287+ & mut self ,
288+ symbol : & mut Symbol ,
289+ parent_offset : usize ,
290+ ) -> SymbolTableResult {
291+ // TODO: quite C-ish way to implement the iteration
292+ // when this is called, we expect to be in the direct parent scope of the scope that contains 'symbol'
293+ let offs = self . tables . len ( ) - 1 - parent_offset;
294+ let last = self . tables . get_mut ( offs) . unwrap ( ) ;
295+ let symbols = & mut last. 0 ;
296+ let table_type = last. 1 ;
297+
298+ match table_type {
299+ SymbolTableType :: Module => {
300+ symbol. scope = SymbolScope :: Global ;
301+ }
302+ SymbolTableType :: Class => { }
303+ SymbolTableType :: Function => {
304+ if let Some ( parent_symbol) = symbols. get_mut ( & symbol. name ) {
305+ if let SymbolScope :: Unknown = parent_symbol. scope {
306+ parent_symbol. is_assigned = true ; // this information is new, as the asignment is done in inner scope
307+ self . analyze_unknown_symbol ( symbol) ;
308+ }
249309
250- if found_in_outer_scope {
251- // Symbol is in some outer scope.
252- symbol. is_free = true ;
253- } else if self . tables . is_empty ( ) {
254- // Don't make assumptions when we don't know.
255- symbol. scope = SymbolScope :: Unknown ;
256- } else {
257- // If there are scopes above we can assume global.
258- symbol. scope = SymbolScope :: Global ;
310+ match symbol. scope {
311+ SymbolScope :: Global => {
312+ symbol. scope = SymbolScope :: Global ;
313+ }
314+ _ => {
315+ symbol. scope = SymbolScope :: Nonlocal ;
316+ }
259317 }
260318 }
261319 }
320+ SymbolTableType :: Comprehension => {
321+ // TODO check for conflicts - requires more context information about variables
322+ match symbols. get_mut ( & symbol. name ) {
323+ Some ( parent_symbol) => {
324+ parent_symbol. is_assigned = true ; // more checks are required
325+ }
326+ None => {
327+ let cloned_sym = symbol. clone ( ) ;
328+
329+ last. 0 . insert ( cloned_sym. name . to_owned ( ) , cloned_sym) ;
330+ }
331+ }
332+
333+ self . analyze_symbol_comprehension ( symbol, parent_offset + 1 ) ?;
334+ }
262335 }
263336 Ok ( ( ) )
264337 }
@@ -271,6 +344,7 @@ enum SymbolUsage {
271344 Used ,
272345 Assigned ,
273346 Parameter ,
347+ AssignedNamedExprInCompr ,
274348}
275349
276350#[ derive( Default ) ]
@@ -602,7 +676,7 @@ impl SymbolTableBuilder {
602676
603677 self . enter_scope (
604678 scope_name,
605- SymbolTableType :: Function ,
679+ SymbolTableType :: Comprehension ,
606680 expression. location . row ( ) ,
607681 ) ;
608682
@@ -679,6 +753,28 @@ impl SymbolTableBuilder {
679753 self . scan_expression ( body, & ExpressionContext :: Load ) ?;
680754 self . scan_expression ( orelse, & ExpressionContext :: Load ) ?;
681755 }
756+
757+ NamedExpression { left, right } => {
758+ self . scan_expression ( right, & ExpressionContext :: Load ) ?;
759+
760+ // special handling for assigned identifier in named expressions
761+ // that are used in comprehensions. This required to correctly
762+ // propagate the scope of the named assigned named and not to
763+ // propagate inner names.
764+ if let Identifier { name } = & left. node {
765+ let table = self . tables . last ( ) . unwrap ( ) ;
766+ if table. typ == SymbolTableType :: Comprehension {
767+ self . register_name ( name, SymbolUsage :: AssignedNamedExprInCompr ) ?;
768+ } else {
769+ // omit one recursion. When the handling of an store changes for
770+ // Identifiers this needs adapted - more forward safe would be
771+ // calling scan_expression directly.
772+ self . register_name ( name, SymbolUsage :: Assigned ) ?;
773+ }
774+ } else {
775+ self . scan_expression ( left, & ExpressionContext :: Store ) ?;
776+ }
777+ }
682778 }
683779 Ok ( ( ) )
684780 }
@@ -810,6 +906,10 @@ impl SymbolTableBuilder {
810906 SymbolUsage :: Assigned => {
811907 symbol. is_assigned = true ;
812908 }
909+ SymbolUsage :: AssignedNamedExprInCompr => {
910+ symbol. is_assigned = true ;
911+ symbol. is_assign_namedexpr_in_comprehension = true ;
912+ }
813913 SymbolUsage :: Global => {
814914 if let SymbolScope :: Unknown = symbol. scope {
815915 symbol. scope = SymbolScope :: Global ;
0 commit comments