mirror of
https://github.com/RustPython/Parser.git
synced 2025-07-24 21:44:45 +00:00
Implement Py38 named expression (PEP 572) (#1934)
* Initial implementation of named expression and import according CPython tests * added new instruction with inversed evaluation order for dict comprehension, in other cases use regular evaluation order * added further aspects to implementation, cleaned up, imported test from CPython * implemented first parts of scoping enhancement and extended checks * completion of name resolution ongoing, now more test passing, still warinings and cleanup required * further optimization of name resolution in nested scopes * Initialize the vm with imports from _io instead of io * Add the OpenBSD support that I am smart enough to * adapted grammer to full support, most test are passing now, esp. all invalids are passed. Cleaned up in symboltable * more conditional compiling, this time for errors * rustfmt was not pleased * premature push, whoops * Add expected_failure result type to jsontests * Initial implementation of named expression and import according CPython tests * added new instruction with inversed evaluation order for dict comprehension, in other cases use regular evaluation order * added further aspects to implementation, cleaned up, imported test from CPython * implemented first parts of scoping enhancement and extended checks * completion of name resolution ongoing, now more test passing, still warinings and cleanup required * further optimization of name resolution in nested scopes * adapted grammer to full support, most test are passing now, esp. all invalids are passed. Cleaned up in symboltable * Fixed nameing convention violation and removed unnecessary information from symbol resolution. Added some more comments. * Fixed nameing convention violation and removed unnecessary information from symbol resolution. Added some more comments. Co-authored-by: Noah <33094578+coolreader18@users.noreply.github.com> Co-authored-by: Reuben Staley <lighthousemaniac@gmail.com>
This commit is contained in:
parent
1257331101
commit
097b6a1889
2 changed files with 158 additions and 51 deletions
|
@ -1804,6 +1804,12 @@ impl<O: OutputStream> Compiler<O> {
|
||||||
// End
|
// End
|
||||||
self.set_label(end_label);
|
self.set_label(end_label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NamedExpression { left, right } => {
|
||||||
|
self.compile_expression(right)?;
|
||||||
|
self.emit(Instruction::Duplicate);
|
||||||
|
self.compile_store(left)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2066,10 +2072,11 @@ impl<O: OutputStream> Compiler<O> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ast::ComprehensionKind::Dict { key, value } => {
|
ast::ComprehensionKind::Dict { key, value } => {
|
||||||
self.compile_expression(value)?;
|
// changed evaluation order for Py38 named expression PEP 572
|
||||||
self.compile_expression(key)?;
|
self.compile_expression(key)?;
|
||||||
|
self.compile_expression(value)?;
|
||||||
|
|
||||||
self.emit(Instruction::MapAdd {
|
self.emit(Instruction::MapAddRev {
|
||||||
i: 1 + generators.len(),
|
i: 1 + generators.len(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ pub enum SymbolTableType {
|
||||||
Module,
|
Module,
|
||||||
Class,
|
Class,
|
||||||
Function,
|
Function,
|
||||||
|
Comprehension,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SymbolTableType {
|
impl fmt::Display for SymbolTableType {
|
||||||
|
@ -74,6 +75,7 @@ impl fmt::Display for SymbolTableType {
|
||||||
SymbolTableType::Module => write!(f, "module"),
|
SymbolTableType::Module => write!(f, "module"),
|
||||||
SymbolTableType::Class => write!(f, "class"),
|
SymbolTableType::Class => write!(f, "class"),
|
||||||
SymbolTableType::Function => write!(f, "function"),
|
SymbolTableType::Function => write!(f, "function"),
|
||||||
|
SymbolTableType::Comprehension => write!(f, "comprehension"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +101,10 @@ pub struct Symbol {
|
||||||
pub is_assigned: bool,
|
pub is_assigned: bool,
|
||||||
pub is_parameter: bool,
|
pub is_parameter: bool,
|
||||||
pub is_free: bool,
|
pub is_free: bool,
|
||||||
|
|
||||||
|
// indicates if the symbol gets a value assigned by a named expression in a comprehension
|
||||||
|
// this is required to correct the scope in the analysis.
|
||||||
|
pub is_assign_namedexpr_in_comprehension: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Symbol {
|
impl Symbol {
|
||||||
|
@ -111,6 +117,7 @@ impl Symbol {
|
||||||
is_assigned: false,
|
is_assigned: false,
|
||||||
is_parameter: false,
|
is_parameter: false,
|
||||||
is_free: false,
|
is_free: false,
|
||||||
|
is_assign_namedexpr_in_comprehension: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,72 +200,138 @@ impl<'a> SymbolTableAnalyzer<'a> {
|
||||||
for sub_table in sub_tables {
|
for sub_table in sub_tables {
|
||||||
self.analyze_symbol_table(sub_table)?;
|
self.analyze_symbol_table(sub_table)?;
|
||||||
}
|
}
|
||||||
let (symbols, _) = self.tables.pop().unwrap();
|
let (symbols, st_typ) = self.tables.pop().unwrap();
|
||||||
|
|
||||||
// Analyze symbols:
|
// Analyze symbols:
|
||||||
for symbol in symbols.values_mut() {
|
for symbol in symbols.values_mut() {
|
||||||
self.analyze_symbol(symbol)?;
|
self.analyze_symbol(symbol, st_typ)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn analyze_symbol(&self, symbol: &mut Symbol) -> SymbolTableResult {
|
fn analyze_symbol(
|
||||||
match symbol.scope {
|
&mut self,
|
||||||
SymbolScope::Nonlocal => {
|
symbol: &mut Symbol,
|
||||||
// check if name is defined in parent table!
|
curr_st_typ: SymbolTableType,
|
||||||
let parent_symbol_table = self.tables.last();
|
) -> SymbolTableResult {
|
||||||
// symbol.table.borrow().parent.clone();
|
if symbol.is_assign_namedexpr_in_comprehension
|
||||||
|
&& curr_st_typ == SymbolTableType::Comprehension
|
||||||
if let Some((symbols, _)) = parent_symbol_table {
|
{
|
||||||
let scope_depth = self.tables.len();
|
self.analyze_symbol_comprehension(symbol, 0)?
|
||||||
if !symbols.contains_key(&symbol.name) || scope_depth < 2 {
|
} else {
|
||||||
|
match symbol.scope {
|
||||||
|
SymbolScope::Nonlocal => {
|
||||||
|
// check if name is defined in parent table!
|
||||||
|
let parent_symbol_table = self.tables.last();
|
||||||
|
if let Some((symbols, _)) = parent_symbol_table {
|
||||||
|
let scope_depth = self.tables.len();
|
||||||
|
if !symbols.contains_key(&symbol.name) || scope_depth < 2 {
|
||||||
|
return Err(SymbolTableError {
|
||||||
|
error: format!("no binding for nonlocal '{}' found", symbol.name),
|
||||||
|
location: Default::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return Err(SymbolTableError {
|
return Err(SymbolTableError {
|
||||||
error: format!("no binding for nonlocal '{}' found", symbol.name),
|
error: format!(
|
||||||
|
"nonlocal {} defined at place without an enclosing scope",
|
||||||
|
symbol.name
|
||||||
|
),
|
||||||
location: Default::default(),
|
location: Default::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
return Err(SymbolTableError {
|
SymbolScope::Global => {
|
||||||
error: format!(
|
// TODO: add more checks for globals?
|
||||||
"nonlocal {} defined at place without an enclosing scope",
|
}
|
||||||
symbol.name
|
SymbolScope::Local => {
|
||||||
),
|
// all is well
|
||||||
location: Default::default(),
|
}
|
||||||
});
|
SymbolScope::Unknown => {
|
||||||
|
// Try hard to figure out what the scope of this symbol is.
|
||||||
|
self.analyze_unknown_symbol(symbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SymbolScope::Global => {
|
}
|
||||||
// TODO: add more checks for globals?
|
Ok(())
|
||||||
}
|
}
|
||||||
SymbolScope::Local => {
|
|
||||||
// all is well
|
|
||||||
}
|
|
||||||
SymbolScope::Unknown => {
|
|
||||||
// Try hard to figure out what the scope of this symbol is.
|
|
||||||
|
|
||||||
if symbol.is_assigned || symbol.is_parameter {
|
fn analyze_unknown_symbol(&self, symbol: &mut Symbol) {
|
||||||
symbol.scope = SymbolScope::Local;
|
if symbol.is_assigned || symbol.is_parameter {
|
||||||
} else {
|
symbol.scope = SymbolScope::Local;
|
||||||
// Interesting stuff about the __class__ variable:
|
} else {
|
||||||
// https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object
|
// Interesting stuff about the __class__ variable:
|
||||||
let found_in_outer_scope = symbol.name == "__class__"
|
// https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object
|
||||||
|| self.tables.iter().skip(1).any(|(symbols, typ)| {
|
let found_in_outer_scope = symbol.name == "__class__"
|
||||||
*typ != SymbolTableType::Class && symbols.contains_key(&symbol.name)
|
|| self.tables.iter().skip(1).any(|(symbols, typ)| {
|
||||||
});
|
*typ != SymbolTableType::Class && symbols.contains_key(&symbol.name)
|
||||||
|
});
|
||||||
|
|
||||||
if found_in_outer_scope {
|
if found_in_outer_scope {
|
||||||
// Symbol is in some outer scope.
|
// Symbol is in some outer scope.
|
||||||
symbol.is_free = true;
|
symbol.is_free = true;
|
||||||
} else if self.tables.is_empty() {
|
} else if self.tables.is_empty() {
|
||||||
// Don't make assumptions when we don't know.
|
// Don't make assumptions when we don't know.
|
||||||
symbol.scope = SymbolScope::Unknown;
|
symbol.scope = SymbolScope::Unknown;
|
||||||
} else {
|
} else {
|
||||||
// If there are scopes above we can assume global.
|
// If there are scopes above we can assume global.
|
||||||
symbol.scope = SymbolScope::Global;
|
symbol.scope = SymbolScope::Global;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the symbol analysis and scope extension for names
|
||||||
|
// assigned by a named expression in a comprehension. See:
|
||||||
|
// https://github.com/python/cpython/blob/7b78e7f9fd77bb3280ee39fb74b86772a7d46a70/Python/symtable.c#L1435
|
||||||
|
fn analyze_symbol_comprehension(
|
||||||
|
&mut self,
|
||||||
|
symbol: &mut Symbol,
|
||||||
|
parent_offset: usize,
|
||||||
|
) -> SymbolTableResult {
|
||||||
|
// TODO: quite C-ish way to implement the iteration
|
||||||
|
// when this is called, we expect to be in the direct parent scope of the scope that contains 'symbol'
|
||||||
|
let offs = self.tables.len() - 1 - parent_offset;
|
||||||
|
let last = self.tables.get_mut(offs).unwrap();
|
||||||
|
let symbols = &mut last.0;
|
||||||
|
let table_type = last.1;
|
||||||
|
|
||||||
|
match table_type {
|
||||||
|
SymbolTableType::Module => {
|
||||||
|
symbol.scope = SymbolScope::Global;
|
||||||
|
}
|
||||||
|
SymbolTableType::Class => {}
|
||||||
|
SymbolTableType::Function => {
|
||||||
|
if let Some(parent_symbol) = symbols.get_mut(&symbol.name) {
|
||||||
|
if let SymbolScope::Unknown = parent_symbol.scope {
|
||||||
|
parent_symbol.is_assigned = true; // this information is new, as the asignment is done in inner scope
|
||||||
|
self.analyze_unknown_symbol(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
match symbol.scope {
|
||||||
|
SymbolScope::Global => {
|
||||||
|
symbol.scope = SymbolScope::Global;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
symbol.scope = SymbolScope::Nonlocal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SymbolTableType::Comprehension => {
|
||||||
|
// TODO check for conflicts - requires more context information about variables
|
||||||
|
match symbols.get_mut(&symbol.name) {
|
||||||
|
Some(parent_symbol) => {
|
||||||
|
parent_symbol.is_assigned = true; // more checks are required
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let cloned_sym = symbol.clone();
|
||||||
|
|
||||||
|
last.0.insert(cloned_sym.name.to_owned(), cloned_sym);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.analyze_symbol_comprehension(symbol, parent_offset + 1)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -271,6 +344,7 @@ enum SymbolUsage {
|
||||||
Used,
|
Used,
|
||||||
Assigned,
|
Assigned,
|
||||||
Parameter,
|
Parameter,
|
||||||
|
AssignedNamedExprInCompr,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -602,7 +676,7 @@ impl SymbolTableBuilder {
|
||||||
|
|
||||||
self.enter_scope(
|
self.enter_scope(
|
||||||
scope_name,
|
scope_name,
|
||||||
SymbolTableType::Function,
|
SymbolTableType::Comprehension,
|
||||||
expression.location.row(),
|
expression.location.row(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -679,6 +753,28 @@ impl SymbolTableBuilder {
|
||||||
self.scan_expression(body, &ExpressionContext::Load)?;
|
self.scan_expression(body, &ExpressionContext::Load)?;
|
||||||
self.scan_expression(orelse, &ExpressionContext::Load)?;
|
self.scan_expression(orelse, &ExpressionContext::Load)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NamedExpression { left, right } => {
|
||||||
|
self.scan_expression(right, &ExpressionContext::Load)?;
|
||||||
|
|
||||||
|
// special handling for assigned identifier in named expressions
|
||||||
|
// that are used in comprehensions. This required to correctly
|
||||||
|
// propagate the scope of the named assigned named and not to
|
||||||
|
// propagate inner names.
|
||||||
|
if let Identifier { name } = &left.node {
|
||||||
|
let table = self.tables.last().unwrap();
|
||||||
|
if table.typ == SymbolTableType::Comprehension {
|
||||||
|
self.register_name(name, SymbolUsage::AssignedNamedExprInCompr)?;
|
||||||
|
} else {
|
||||||
|
// omit one recursion. When the handling of an store changes for
|
||||||
|
// Identifiers this needs adapted - more forward safe would be
|
||||||
|
// calling scan_expression directly.
|
||||||
|
self.register_name(name, SymbolUsage::Assigned)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.scan_expression(left, &ExpressionContext::Store)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -810,6 +906,10 @@ impl SymbolTableBuilder {
|
||||||
SymbolUsage::Assigned => {
|
SymbolUsage::Assigned => {
|
||||||
symbol.is_assigned = true;
|
symbol.is_assigned = true;
|
||||||
}
|
}
|
||||||
|
SymbolUsage::AssignedNamedExprInCompr => {
|
||||||
|
symbol.is_assigned = true;
|
||||||
|
symbol.is_assign_namedexpr_in_comprehension = true;
|
||||||
|
}
|
||||||
SymbolUsage::Global => {
|
SymbolUsage::Global => {
|
||||||
if let SymbolScope::Unknown = symbol.scope {
|
if let SymbolScope::Unknown = symbol.scope {
|
||||||
symbol.scope = SymbolScope::Global;
|
symbol.scope = SymbolScope::Global;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue