diff --git a/parser/src/error.rs b/parser/src/error.rs index c946ee854..cc34abac0 100644 --- a/parser/src/error.rs +++ b/parser/src/error.rs @@ -20,6 +20,7 @@ pub enum LexicalErrorType { StringError, UnicodeError, NestingError, + DefaultArgumentError, PositionalArgumentError, DuplicateKeywordArgumentError, UnrecognizedToken { tok: char }, @@ -34,6 +35,9 @@ impl fmt::Display for LexicalErrorType { LexicalErrorType::FStringError(error) => write!(f, "Got error in f-string: {}", error), LexicalErrorType::UnicodeError => write!(f, "Got unexpected unicode"), LexicalErrorType::NestingError => write!(f, "Got unexpected nesting"), + LexicalErrorType::DefaultArgumentError => { + write!(f, "non-default argument follows default argument") + } LexicalErrorType::DuplicateKeywordArgumentError => { write!(f, "keyword argument repeated") } diff --git a/parser/src/function.rs b/parser/src/function.rs index d20cf8a9a..a2fdecebc 100644 --- a/parser/src/function.rs +++ b/parser/src/function.rs @@ -3,6 +3,32 @@ use std::collections::HashSet; use crate::ast; use crate::error::{LexicalError, LexicalErrorType}; +type ParameterDefs = (Vec, Vec); +type ParameterDef = (ast::Parameter, Option); + +pub fn parse_params(params: Vec) -> Result { + let mut names = vec![]; + let mut defaults = vec![]; + + for (name, default) in params { + if let Some(default) = default { + defaults.push(default); + } else { + if !defaults.is_empty() { + // Once we have started with defaults, all remaining arguments must + // have defaults + return Err(LexicalError { + error: LexicalErrorType::DefaultArgumentError, + location: name.location.clone(), + }); + } + } + names.push(name); + } + + Ok((names, defaults)) +} + type FunctionArgument = (Option>, ast::Expression); pub fn parse_args(func_args: Vec) -> Result { diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 7e3cc6200..8e15645b2 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -7,7 +7,7 @@ use std::iter::FromIterator; use crate::ast; use crate::fstring::parse_located_fstring; -use crate::function::parse_args; +use crate::function::{parse_args, parse_params}; use crate::error::LexicalError; use crate::lexer; use crate::location; @@ -455,23 +455,23 @@ Parameters: ast::Parameters = { // Note that this is a macro which is used once for function defs, and // once for lambda defs. ParameterList: ast::Parameters = { - > )?> ","? => { - let (names, default_elements) = param1; + > )?> ","? =>? { + let (names, default_elements) = parse_params(param1)?; // Now gather rest of parameters: let (vararg, kwonlyargs, kw_defaults, kwarg) = args2.map_or((None, vec![], vec![], None), |x| x.1); - ast::Parameters { + Ok(ast::Parameters { args: names, kwonlyargs, vararg: vararg.into(), kwarg: kwarg.into(), defaults: default_elements, kw_defaults: kw_defaults, - } + }) }, - > )> ","? => { - let (names, default_elements) = param1; + > )> ","? =>? { + let (names, default_elements) = parse_params(param1)?; // Now gather rest of parameters: let vararg = None; @@ -479,14 +479,14 @@ ParameterList: ast::Parameters = { let kw_defaults = vec![]; let kwarg = Some(kw.1); - ast::Parameters { + Ok(ast::Parameters { args: names, kwonlyargs, vararg: vararg.into(), kwarg: kwarg.into(), defaults: default_elements, kw_defaults: kw_defaults, - } + }) }, > ","? => { let (vararg, kwonlyargs, kw_defaults, kwarg) = params; @@ -513,27 +513,9 @@ ParameterList: ast::Parameters = { // Use inline here to make sure the "," is not creating an ambiguity. #[inline] -ParameterDefs: (Vec, Vec) = { +ParameterDefs: Vec<(ast::Parameter, Option)> = { >> => { - let mut names = vec![]; - let mut default_elements = vec![]; - - for (name, default) in args.into_iter() { - if let Some(default) = default { - default_elements.push(default); - } else { - if default_elements.len() > 0 { - // Once we have started with defaults, all remaining arguments must - // have defaults - panic!("non-default argument follows default argument: {}", &name.arg); - } - } - names.push(name); - } - - //Ok( - (names, default_elements) - //) + args } }; diff --git a/tests/snippets/function_args.py b/tests/snippets/function_args.py index 4441c9416..7c2663e3f 100644 --- a/tests/snippets/function_args.py +++ b/tests/snippets/function_args.py @@ -102,3 +102,6 @@ def inc(n): with assert_raises(SyntaxError): exec("inc(n=1, n=2)") + +with assert_raises(SyntaxError): + exec("def f(a=1, b): pass")