Merge pull request #1964 from TheAnyKey/TheAnyKey/p38_named_expression_completion_step2

Py3.8 Named expression completion - error handling and scoping
This commit is contained in:
Noah 2020-06-28 09:30:23 -05:00 committed by GitHub
commit 949c37620e

View file

@ -105,6 +105,10 @@ pub struct Symbol {
// 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,
// inidicates that the symbol is used a bound iterator variable. We distinguish this case
// from normal assignment to detect unallowed re-assignment to iterator variables.
pub is_iter: bool,
}
impl Symbol {
@ -118,6 +122,7 @@ impl Symbol {
is_parameter: false,
is_free: false,
is_assign_namedexpr_in_comprehension: false,
is_iter: false,
}
}
@ -217,6 +222,10 @@ impl<'a> SymbolTableAnalyzer<'a> {
if symbol.is_assign_namedexpr_in_comprehension
&& curr_st_typ == SymbolTableType::Comprehension
{
// propagate symbol to next higher level that can hold it,
// i.e., function or module. Comprehension is skipped and
// Class is not allowed and detected as error.
//symbol.scope = SymbolScope::Nonlocal;
self.analyze_symbol_comprehension(symbol, 0)?
} else {
match symbol.scope {
@ -303,16 +312,33 @@ impl<'a> SymbolTableAnalyzer<'a> {
let symbols = &mut last.0;
let table_type = last.1;
// it is not allowed to use an iterator variable as assignee in a named expression
if symbol.is_iter {
return Err(SymbolTableError {
error: format!(
"assignment expression cannot rebind comprehension iteration variable {}",
symbol.name
),
location: Default::default(),
});
}
match table_type {
SymbolTableType::Module => {
symbol.scope = SymbolScope::Global;
}
SymbolTableType::Class => {}
SymbolTableType::Class => {
// named expressions are forbidden in comprehensions on class scope
return Err(SymbolTableError {
error: "assignment expression within a comprehension cannot be used in a class body".to_string(),
location: Default::default(),
} );
}
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);
//self.analyze_unknown_symbol(symbol); // not needed, symbol is analyzed anyhow when its scope is analyzed
}
match symbol.scope {
@ -323,17 +349,34 @@ impl<'a> SymbolTableAnalyzer<'a> {
symbol.scope = SymbolScope::Nonlocal;
}
}
} else {
let mut cloned_sym = symbol.clone();
cloned_sym.scope = SymbolScope::Local;
last.0.insert(cloned_sym.name.to_owned(), cloned_sym);
}
}
SymbolTableType::Comprehension => {
// TODO check for conflicts - requires more context information about variables
match symbols.get_mut(&symbol.name) {
Some(parent_symbol) => {
// check if assignee is an iterator in top scope
if parent_symbol.is_iter {
return Err(SymbolTableError {
error: format!("assignment expression cannot rebind comprehension iteration variable {}", symbol.name),
location: Default::default(),
});
}
// we synthesize the assignment to the symbol from inner scope
parent_symbol.is_assigned = true; // more checks are required
}
None => {
let cloned_sym = symbol.clone();
// extend the scope of the inner symbol
// as we are in a nested comprehension, we expect that the symbol is needed
// ouside, too, and set it therefore to non-local scope. I.e., we expect to
// find a definition on a higher level
let mut cloned_sym = symbol.clone();
cloned_sym.scope = SymbolScope::Nonlocal;
last.0.insert(cloned_sym.name.to_owned(), cloned_sym);
}
}
@ -353,6 +396,7 @@ enum SymbolUsage {
Assigned,
Parameter,
AssignedNamedExprInCompr,
Iter,
}
#[derive(Default)]
@ -369,6 +413,8 @@ enum ExpressionContext {
Load,
Store,
Delete,
Iter,
IterDefinitionExp,
}
impl SymbolTableBuilder {
@ -705,11 +751,14 @@ impl SymbolTableBuilder {
let mut is_first_generator = true;
for generator in generators {
self.scan_expression(&generator.target, &ExpressionContext::Store)?;
self.scan_expression(&generator.target, &ExpressionContext::Iter)?;
if is_first_generator {
is_first_generator = false;
} else {
self.scan_expression(&generator.iter, &ExpressionContext::Load)?;
self.scan_expression(
&generator.iter,
&ExpressionContext::IterDefinitionExp,
)?;
}
for if_expr in &generator.ifs {
@ -721,14 +770,22 @@ impl SymbolTableBuilder {
// The first iterable is passed as an argument into the created function:
assert!(!generators.is_empty());
self.scan_expression(&generators[0].iter, &ExpressionContext::Load)?;
self.scan_expression(&generators[0].iter, &ExpressionContext::IterDefinitionExp)?;
}
Call {
function,
args,
keywords,
} => {
self.scan_expression(function, &ExpressionContext::Load)?;
match *context {
ExpressionContext::IterDefinitionExp => {
self.scan_expression(function, &ExpressionContext::IterDefinitionExp)?;
}
_ => {
self.scan_expression(function, &ExpressionContext::Load)?;
}
}
self.scan_expressions(args, &ExpressionContext::Load)?;
for keyword in keywords {
self.scan_expression(&keyword.value, &ExpressionContext::Load)?;
@ -743,17 +800,27 @@ impl SymbolTableBuilder {
ExpressionContext::Delete => {
self.register_name(name, SymbolUsage::Used)?;
}
ExpressionContext::Load => {
ExpressionContext::Load | ExpressionContext::IterDefinitionExp => {
self.register_name(name, SymbolUsage::Used)?;
}
ExpressionContext::Store => {
self.register_name(name, SymbolUsage::Assigned)?;
}
ExpressionContext::Iter => {
self.register_name(name, SymbolUsage::Iter)?;
}
}
}
Lambda { args, body } => {
self.enter_function("lambda", args, expression.location.row())?;
self.scan_expression(body, &ExpressionContext::Load)?;
match *context {
ExpressionContext::IterDefinitionExp => {
self.scan_expression(body, &ExpressionContext::IterDefinitionExp)?;
}
_ => {
self.scan_expression(body, &ExpressionContext::Load)?;
}
}
self.leave_scope();
}
IfExpression { test, body, orelse } => {
@ -763,6 +830,15 @@ impl SymbolTableBuilder {
}
NamedExpression { left, right } => {
// named expressions are not allowed in the definiton of
// comprehension iterator definitions
if let ExpressionContext::IterDefinitionExp = *context {
return Err(SymbolTableError {
error: "assignment expression cannot be used in a comprehension iterable expression".to_string(),
location: Default::default(),
});
}
self.scan_expression(right, &ExpressionContext::Load)?;
// special handling for assigned identifier in named expressions
@ -933,8 +1009,24 @@ impl SymbolTableBuilder {
SymbolUsage::Used => {
symbol.is_referenced = true;
}
SymbolUsage::Iter => {
symbol.is_iter = true;
symbol.scope = SymbolScope::Local;
}
}
// and even more checking
// it is not allowed to assign to iterator variables (by named expressions)
if symbol.is_iter && symbol.is_assigned
/*&& symbol.is_assign_namedexpr_in_comprehension*/
{
return Err(SymbolTableError {
error:
"assignment expression cannot be used in a comprehension iterable expression"
.to_string(),
location,
});
}
Ok(())
}
}