@@ -66,6 +66,7 @@ pub enum SymbolTableType {
66
66
Module ,
67
67
Class ,
68
68
Function ,
69
+ Comprehension ,
69
70
}
70
71
71
72
impl fmt:: Display for SymbolTableType {
@@ -74,6 +75,7 @@ impl fmt::Display for SymbolTableType {
74
75
SymbolTableType :: Module => write ! ( f, "module" ) ,
75
76
SymbolTableType :: Class => write ! ( f, "class" ) ,
76
77
SymbolTableType :: Function => write ! ( f, "function" ) ,
78
+ SymbolTableType :: Comprehension => write ! ( f, "comprehension" ) ,
77
79
}
78
80
}
79
81
}
@@ -99,6 +101,10 @@ pub struct Symbol {
99
101
pub is_assigned : bool ,
100
102
pub is_parameter : bool ,
101
103
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 ,
102
108
}
103
109
104
110
impl Symbol {
@@ -111,6 +117,7 @@ impl Symbol {
111
117
is_assigned : false ,
112
118
is_parameter : false ,
113
119
is_free : false ,
120
+ is_assign_namedexpr_in_comprehension : false ,
114
121
}
115
122
}
116
123
@@ -193,72 +200,138 @@ impl<'a> SymbolTableAnalyzer<'a> {
193
200
for sub_table in sub_tables {
194
201
self . analyze_symbol_table ( sub_table) ?;
195
202
}
196
- let ( symbols, _ ) = self . tables . pop ( ) . unwrap ( ) ;
203
+ let ( symbols, st_typ ) = self . tables . pop ( ) . unwrap ( ) ;
197
204
198
205
// Analyze symbols:
199
206
for symbol in symbols. values_mut ( ) {
200
- self . analyze_symbol ( symbol) ?;
207
+ self . analyze_symbol ( symbol, st_typ ) ?;
201
208
}
202
-
203
209
Ok ( ( ) )
204
210
}
205
211
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 {
216
235
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
+ ) ,
218
240
location : Default :: default ( ) ,
219
241
} ) ;
220
242
}
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) ;
229
253
}
230
254
}
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 ;
236
279
}
237
- SymbolScope :: Unknown => {
238
- // Try hard to figure out what the scope of this symbol is.
280
+ }
281
+ }
239
282
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
+ }
249
309
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
+ }
259
317
}
260
318
}
261
319
}
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
+ }
262
335
}
263
336
Ok ( ( ) )
264
337
}
@@ -271,6 +344,7 @@ enum SymbolUsage {
271
344
Used ,
272
345
Assigned ,
273
346
Parameter ,
347
+ AssignedNamedExprInCompr ,
274
348
}
275
349
276
350
#[ derive( Default ) ]
@@ -602,7 +676,7 @@ impl SymbolTableBuilder {
602
676
603
677
self . enter_scope (
604
678
scope_name,
605
- SymbolTableType :: Function ,
679
+ SymbolTableType :: Comprehension ,
606
680
expression. location . row ( ) ,
607
681
) ;
608
682
@@ -679,6 +753,28 @@ impl SymbolTableBuilder {
679
753
self . scan_expression ( body, & ExpressionContext :: Load ) ?;
680
754
self . scan_expression ( orelse, & ExpressionContext :: Load ) ?;
681
755
}
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
+ }
682
778
}
683
779
Ok ( ( ) )
684
780
}
@@ -810,6 +906,10 @@ impl SymbolTableBuilder {
810
906
SymbolUsage :: Assigned => {
811
907
symbol. is_assigned = true ;
812
908
}
909
+ SymbolUsage :: AssignedNamedExprInCompr => {
910
+ symbol. is_assigned = true ;
911
+ symbol. is_assign_namedexpr_in_comprehension = true ;
912
+ }
813
913
SymbolUsage :: Global => {
814
914
if let SymbolScope :: Unknown = symbol. scope {
815
915
symbol. scope = SymbolScope :: Global ;
0 commit comments