@@ -325,6 +325,7 @@ pub struct Symbol { |
325 | 325 | pub name: String, |
326 | 326 | pub scope: SymbolScope, |
327 | 327 | pub flags: SymbolFlags, |
| 328 | + pub location: Option<SourceLocation>, |
328 | 329 | } |
329 | 330 |
|
330 | 331 | impl Symbol { |
@@ -334,6 +335,7 @@ impl Symbol { |
334 | 335 | // table, |
335 | 336 | scope: SymbolScope::Unknown, |
336 | 337 | flags: SymbolFlags::empty(), |
| 338 | + location: None, |
337 | 339 | } |
338 | 340 | } |
339 | 341 |
|
@@ -777,117 +779,98 @@ impl SymbolTableAnalyzer { |
777 | 779 | sub_tables: &[SymbolTable], |
778 | 780 | class_entry: Option<&SymbolMap>, |
779 | 781 | ) -> SymbolTableResult { |
780 | | - if symbol |
781 | | - .flags |
782 | | - .contains(SymbolFlags::ASSIGNED_IN_COMPREHENSION) |
783 | | - && st_typ == CompilerScope::Comprehension |
784 | | - { |
785 | | - // propagate symbol to next higher level that can hold it, |
786 | | - // i.e., function or module. Comprehension is skipped and |
787 | | - // Class is not allowed and detected as error. |
788 | | - self.analyze_symbol_comprehension(symbol, 0)? |
789 | | - } else { |
790 | | - match symbol.scope { |
791 | | - SymbolScope::Free => { |
792 | | - if !self.tables.as_ref().is_empty() { |
793 | | - let scope_depth = self.tables.as_ref().len(); |
794 | | - // check if the name is already defined in any outer scope |
795 | | - if scope_depth < 2 |
796 | | - || self.found_in_outer_scope( |
797 | | - &symbol.name, |
798 | | - st_typ, |
799 | | - skip_enclosing_function_scope, |
800 | | - ) != Some(SymbolScope::Free) |
801 | | - { |
802 | | - return Err(SymbolTableError { |
803 | | - error: format!("no binding for nonlocal '{}' found", symbol.name), |
804 | | - // TODO: accurate location info, somehow |
805 | | - location: None, |
806 | | - }); |
807 | | - } |
808 | | - // Check if the nonlocal binding refers to a type parameter |
809 | | - if symbol.flags.contains(SymbolFlags::NONLOCAL) { |
810 | | - for (symbols, _typ, _skip) in self.tables.iter().rev() { |
811 | | - if let Some(sym) = symbols.get(&symbol.name) { |
812 | | - if sym.flags.contains(SymbolFlags::TYPE_PARAM) { |
813 | | - return Err(SymbolTableError { |
814 | | - error: format!( |
815 | | - "nonlocal binding not allowed for type parameter '{}'", |
816 | | - symbol.name |
817 | | - ), |
818 | | - location: None, |
819 | | - }); |
820 | | - } |
821 | | - if sym.is_bound() { |
822 | | - break; |
823 | | - } |
| 782 | + match symbol.scope { |
| 783 | + SymbolScope::Free => { |
| 784 | + if !self.tables.as_ref().is_empty() { |
| 785 | + let scope_depth = self.tables.as_ref().len(); |
| 786 | + // check if the name is already defined in any outer scope |
| 787 | + if scope_depth < 2 |
| 788 | + || self.found_in_outer_scope( |
| 789 | + &symbol.name, |
| 790 | + st_typ, |
| 791 | + skip_enclosing_function_scope, |
| 792 | + ) != Some(SymbolScope::Free) |
| 793 | + { |
| 794 | + return Err(SymbolTableError { |
| 795 | + error: format!("no binding for nonlocal '{}' found", symbol.name), |
| 796 | + location: symbol.location, |
| 797 | + }); |
| 798 | + } |
| 799 | + // Check if the nonlocal binding refers to a type parameter |
| 800 | + if symbol.flags.contains(SymbolFlags::NONLOCAL) { |
| 801 | + for (symbols, _typ, _skip) in self.tables.iter().rev() { |
| 802 | + if let Some(sym) = symbols.get(&symbol.name) { |
| 803 | + if sym.flags.contains(SymbolFlags::TYPE_PARAM) { |
| 804 | + return Err(SymbolTableError { |
| 805 | + error: format!( |
| 806 | + "nonlocal binding not allowed for type parameter '{}'", |
| 807 | + symbol.name |
| 808 | + ), |
| 809 | + location: symbol.location, |
| 810 | + }); |
| 811 | + } |
| 812 | + if sym.is_bound() { |
| 813 | + break; |
824 | 814 | } |
825 | 815 | } |
826 | 816 | } |
827 | | - } else { |
828 | | - return Err(SymbolTableError { |
829 | | - error: format!( |
830 | | - "nonlocal {} defined at place without an enclosing scope", |
831 | | - symbol.name |
832 | | - ), |
833 | | - // TODO: accurate location info, somehow |
834 | | - location: None, |
835 | | - }); |
836 | 817 | } |
| 818 | + } else { |
| 819 | + return Err(SymbolTableError { |
| 820 | + error: format!( |
| 821 | + "nonlocal {} defined at place without an enclosing scope", |
| 822 | + symbol.name |
| 823 | + ), |
| 824 | + location: symbol.location, |
| 825 | + }); |
837 | 826 | } |
838 | | - SymbolScope::GlobalExplicit | SymbolScope::GlobalImplicit => { |
839 | | - // TODO: add more checks for globals? |
840 | | - } |
841 | | - SymbolScope::Local | SymbolScope::Cell => { |
842 | | - // all is well |
843 | | - } |
844 | | - SymbolScope::Unknown => { |
845 | | - // Try hard to figure out what the scope of this symbol is. |
846 | | - let scope = if symbol.is_bound() { |
847 | | - if symbol.flags.contains(SymbolFlags::COMP_CELL) |
848 | | - && matches!(st_typ, CompilerScope::Module | CompilerScope::Class) |
849 | | - { |
850 | | - // CPython keeps comprehension-only cells in |
851 | | - // module/class scopes as normal local/name |
852 | | - // bindings and uses DEF_COMP_CELL to allocate the |
853 | | - // synthetic cell slot. The spliced comp child |
854 | | - // should not force the outer name itself to CELL. |
855 | | - SymbolScope::Local |
856 | | - } else { |
857 | | - self.found_in_inner_scope(sub_tables, &symbol.name, st_typ) |
858 | | - .unwrap_or(SymbolScope::Local) |
859 | | - } |
860 | | - } else if let Some(scope) = class_entry |
861 | | - .and_then(|class_symbols| class_symbols.get(&symbol.name)) |
862 | | - .and_then(|class_sym| { |
863 | | - if class_sym.flags.contains(SymbolFlags::GLOBAL) { |
864 | | - Some(SymbolScope::GlobalExplicit) |
865 | | - } else if class_sym.is_bound() && class_sym.scope != SymbolScope::Free { |
866 | | - // If name is bound in enclosing class, use GlobalImplicit |
867 | | - // so it can be accessed via __classdict__ |
868 | | - Some(SymbolScope::GlobalImplicit) |
869 | | - } else { |
870 | | - None |
871 | | - } |
872 | | - }) |
| 827 | + } |
| 828 | + SymbolScope::GlobalExplicit | SymbolScope::GlobalImplicit => {} |
| 829 | + SymbolScope::Local | SymbolScope::Cell => {} |
| 830 | + SymbolScope::Unknown => { |
| 831 | + // Try hard to figure out what the scope of this symbol is. |
| 832 | + let scope = if symbol.is_bound() { |
| 833 | + if symbol.flags.contains(SymbolFlags::COMP_CELL) |
| 834 | + && matches!(st_typ, CompilerScope::Module | CompilerScope::Class) |
873 | 835 | { |
874 | | - scope |
875 | | - } else if let Some(scope) = self.found_in_outer_scope( |
876 | | - &symbol.name, |
877 | | - st_typ, |
878 | | - skip_enclosing_function_scope, |
879 | | - ) { |
880 | | - // If found in enclosing scope (function/TypeParams), use that |
881 | | - scope |
882 | | - } else if self.tables.is_empty() { |
883 | | - // Don't make assumptions when we don't know. |
884 | | - SymbolScope::Unknown |
| 836 | + // CPython keeps comprehension-only cells in |
| 837 | + // module/class scopes as normal local/name |
| 838 | + // bindings and uses DEF_COMP_CELL to allocate the |
| 839 | + // synthetic cell slot. The spliced comp child |
| 840 | + // should not force the outer name itself to CELL. |
| 841 | + SymbolScope::Local |
885 | 842 | } else { |
886 | | - // If there are scopes above we assume global. |
887 | | - SymbolScope::GlobalImplicit |
888 | | - }; |
889 | | - symbol.scope = scope; |
890 | | - } |
| 843 | + self.found_in_inner_scope(sub_tables, &symbol.name, st_typ) |
| 844 | + .unwrap_or(SymbolScope::Local) |
| 845 | + } |
| 846 | + } else if let Some(scope) = class_entry |
| 847 | + .and_then(|class_symbols| class_symbols.get(&symbol.name)) |
| 848 | + .and_then(|class_sym| { |
| 849 | + if class_sym.flags.contains(SymbolFlags::GLOBAL) { |
| 850 | + Some(SymbolScope::GlobalExplicit) |
| 851 | + } else if class_sym.is_bound() && class_sym.scope != SymbolScope::Free { |
| 852 | + // If name is bound in enclosing class, use GlobalImplicit |
| 853 | + // so it can be accessed via __classdict__ |
| 854 | + Some(SymbolScope::GlobalImplicit) |
| 855 | + } else { |
| 856 | + None |
| 857 | + } |
| 858 | + }) |
| 859 | + { |
| 860 | + scope |
| 861 | + } else if let Some(scope) = |
| 862 | + self.found_in_outer_scope(&symbol.name, st_typ, skip_enclosing_function_scope) |
| 863 | + { |
| 864 | + // If found in enclosing scope (function/TypeParams), use that |
| 865 | + scope |
| 866 | + } else if self.tables.is_empty() { |
| 867 | + // Don't make assumptions when we don't know. |
| 868 | + SymbolScope::Unknown |
| 869 | + } else { |
| 870 | + // If there are scopes above we assume global. |
| 871 | + SymbolScope::GlobalImplicit |
| 872 | + }; |
| 873 | + symbol.scope = scope; |
891 | 874 | } |
892 | 875 | } |
893 | 876 | Ok(()) |
@@ -1023,106 +1006,6 @@ impl SymbolTableAnalyzer { |
1023 | 1006 | } |
1024 | 1007 | }) |
1025 | 1008 | } |
1026 | | - |
1027 | | - // Implements the symbol analysis and scope extension for names |
1028 | | - // assigned by a named expression in a comprehension. See: |
1029 | | - // https://github.com/python/cpython/blob/7b78e7f9fd77bb3280ee39fb74b86772a7d46a70/Python/symtable.c#L1435 |
1030 | | - fn analyze_symbol_comprehension( |
1031 | | - &mut self, |
1032 | | - symbol: &mut Symbol, |
1033 | | - parent_offset: usize, |
1034 | | - ) -> SymbolTableResult { |
1035 | | - // when this is called, we expect to be in the direct parent scope of the scope that contains 'symbol' |
1036 | | - let last = self.tables.iter_mut().rev().nth(parent_offset).unwrap(); |
1037 | | - let symbols = &mut last.0; |
1038 | | - let table_type = last.1; |
1039 | | - |
1040 | | - // it is not allowed to use an iterator variable as assignee in a named expression |
1041 | | - if symbol.flags.contains(SymbolFlags::ITER) { |
1042 | | - return Err(SymbolTableError { |
1043 | | - error: format!( |
1044 | | - "assignment expression cannot rebind comprehension iteration variable {}", |
1045 | | - symbol.name |
1046 | | - ), |
1047 | | - // TODO: accurate location info, somehow |
1048 | | - location: None, |
1049 | | - }); |
1050 | | - } |
1051 | | - |
1052 | | - match table_type { |
1053 | | - CompilerScope::Module => { |
1054 | | - symbol.scope = SymbolScope::GlobalImplicit; |
1055 | | - } |
1056 | | - CompilerScope::Class => { |
1057 | | - // named expressions are forbidden in comprehensions on class scope |
1058 | | - return Err(SymbolTableError { |
1059 | | - error: "assignment expression within a comprehension cannot be used in a class body".to_string(), |
1060 | | - // TODO: accurate location info, somehow |
1061 | | - location: None, |
1062 | | - }); |
1063 | | - } |
1064 | | - CompilerScope::Function | CompilerScope::AsyncFunction | CompilerScope::Lambda => { |
1065 | | - if let Some(parent_symbol) = symbols.get_mut(&symbol.name) { |
1066 | | - if let SymbolScope::Unknown = parent_symbol.scope { |
1067 | | - // this information is new, as the assignment is done in inner scope |
1068 | | - parent_symbol.flags.insert(SymbolFlags::ASSIGNED); |
1069 | | - } |
1070 | | - |
1071 | | - symbol.scope = if parent_symbol.is_global() { |
1072 | | - parent_symbol.scope |
1073 | | - } else { |
1074 | | - SymbolScope::Free |
1075 | | - }; |
1076 | | - } else { |
1077 | | - let mut cloned_sym = symbol.clone(); |
1078 | | - cloned_sym.scope = SymbolScope::Cell; |
1079 | | - last.0.insert(cloned_sym.name.to_owned(), cloned_sym); |
1080 | | - } |
1081 | | - } |
1082 | | - CompilerScope::Comprehension => { |
1083 | | - // TODO check for conflicts - requires more context information about variables |
1084 | | - match symbols.get_mut(&symbol.name) { |
1085 | | - Some(parent_symbol) => { |
1086 | | - // check if assignee is an iterator in top scope |
1087 | | - if parent_symbol.flags.contains(SymbolFlags::ITER) { |
1088 | | - return Err(SymbolTableError { |
1089 | | - error: format!( |
1090 | | - "assignment expression cannot rebind comprehension iteration variable {}", |
1091 | | - symbol.name |
1092 | | - ), |
1093 | | - location: None, |
1094 | | - }); |
1095 | | - } |
1096 | | - |
1097 | | - // we synthesize the assignment to the symbol from inner scope |
1098 | | - parent_symbol.flags.insert(SymbolFlags::ASSIGNED); // more checks are required |
1099 | | - } |
1100 | | - None => { |
1101 | | - // extend the scope of the inner symbol |
1102 | | - // as we are in a nested comprehension, we expect that the symbol is needed |
1103 | | - // outside, too, and set it therefore to non-local scope. I.e., we expect to |
1104 | | - // find a definition on a higher level |
1105 | | - let mut cloned_sym = symbol.clone(); |
1106 | | - cloned_sym.scope = SymbolScope::Free; |
1107 | | - last.0.insert(cloned_sym.name.to_owned(), cloned_sym); |
1108 | | - } |
1109 | | - } |
1110 | | - |
1111 | | - self.analyze_symbol_comprehension(symbol, parent_offset + 1)?; |
1112 | | - } |
1113 | | - CompilerScope::TypeParams => { |
1114 | | - // Named expression in comprehension cannot be used in type params |
1115 | | - return Err(SymbolTableError { |
1116 | | - error: "assignment expression within a comprehension cannot be used within the definition of a generic".to_string(), |
1117 | | - location: None, |
1118 | | - }); |
1119 | | - } |
1120 | | - CompilerScope::Annotation | CompilerScope::TypeAlias | CompilerScope::TypeVariable => { |
1121 | | - self.analyze_symbol_comprehension(symbol, parent_offset + 1)?; |
1122 | | - } |
1123 | | - } |
1124 | | - Ok(()) |
1125 | | - } |
1126 | 1009 | } |
1127 | 1010 |
|
1128 | 1011 | #[derive(Clone, Copy, Debug)] |
@@ -3475,6 +3358,10 @@ impl SymbolTableBuilder { |
3475 | 3358 | table.symbols.entry(name.into_owned()).or_insert(symbol) |
3476 | 3359 | }; |
3477 | 3360 |
|
| 3361 | + if matches!(role, SymbolUsage::Global | SymbolUsage::Nonlocal) { |
| 3362 | + symbol.location = location; |
| 3363 | + } |
| 3364 | + |
3478 | 3365 | // Set proper scope and flags on symbol: |
3479 | 3366 | let flags = &mut symbol.flags; |
3480 | 3367 | match role { |
|