diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 3242a4ec6..fb3cbb8b7 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -42,8 +42,6 @@ class TestSpecifics(unittest.TestCase): self.assertEqual(__debug__, prev) setattr(builtins, '__debug__', prev) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_argument_handling(self): # detect duplicate positional and keyword arguments self.assertRaises(SyntaxError, eval, 'lambda a,a:0') diff --git a/Lib/test/test_positional_only_arg.py b/Lib/test/test_positional_only_arg.py index 76a5df2a3..d9d8b0306 100644 --- a/Lib/test/test_positional_only_arg.py +++ b/Lib/test/test_positional_only_arg.py @@ -22,8 +22,6 @@ class PositionalOnlyTestCase(unittest.TestCase): with self.assertRaisesRegex(SyntaxError, regex): compile(codestr + "\n", "", "single") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_invalid_syntax_errors(self): check_syntax_error(self, "def f(a, b = 5, /, c): pass", "non-default argument follows default argument") check_syntax_error(self, "def f(a = 5, b, /, c): pass", "non-default argument follows default argument") @@ -45,8 +43,6 @@ class PositionalOnlyTestCase(unittest.TestCase): check_syntax_error(self, "def f(a, /, c, /, d, *, e): pass") check_syntax_error(self, "def f(a, *, c, /, d, e): pass") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_invalid_syntax_errors_async(self): check_syntax_error(self, "async def f(a, b = 5, /, c): pass", "non-default argument follows default argument") check_syntax_error(self, "async def f(a = 5, b, /, c): pass", "non-default argument follows default argument") @@ -240,8 +236,6 @@ class PositionalOnlyTestCase(unittest.TestCase): x = lambda a, b, /, : a + b self.assertEqual(x(1, 2), 3) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_invalid_syntax_lambda(self): check_syntax_error(self, "lambda a, b = 5, /, c: None", "non-default argument follows default argument") check_syntax_error(self, "lambda a = 5, b, /, c: None", "non-default argument follows default argument") diff --git a/compiler/parser/python.lalrpop b/compiler/parser/python.lalrpop index a73d83520..5652be1b6 100644 --- a/compiler/parser/python.lalrpop +++ b/compiler/parser/python.lalrpop @@ -6,7 +6,7 @@ use crate::{ ast, error::{LexicalError, LexicalErrorType}, - function::{ArgumentList, parse_args, parse_params}, + function::{ArgumentList, parse_args, parse_params, validate_arguments}, lexer, context::set_context, string::parse_strings, @@ -552,16 +552,20 @@ FuncDef: ast::Stmt = { }; Parameters: ast::Arguments = { - "(" )?> ")" => { - a.unwrap_or_else(|| ast::Arguments { - posonlyargs: vec![], - args: vec![], - vararg: None, - kwonlyargs: vec![], - kw_defaults: vec![], - kwarg: None, - defaults: vec![] - }) + "(" )?> ")" =>? { + let args = validate_arguments( + a.unwrap_or_else(|| ast::Arguments { + posonlyargs: vec![], + args: vec![], + vararg: None, + kwonlyargs: vec![], + kw_defaults: vec![], + kwarg: None, + defaults: vec![] + }) + )?; + + Ok(args) } }; @@ -663,7 +667,7 @@ TypedParameter: ast::Arg = { // TODO: figure out another grammar that makes this inline no longer required. #[inline] ParameterListStarArgs: (Option>, Vec, Vec, Option>) = { - "*" )*> )?> => { + "*" )*> )?> =>? { // Extract keyword arguments: let mut kwonlyargs = Vec::new(); let mut kw_defaults = Vec::new(); @@ -678,10 +682,17 @@ ParameterListStarArgs: (Option>, Vec, Vec "lambda" ?> ":" > => { - let p = p.unwrap_or_else(|| { - ast::Arguments { - posonlyargs: vec![], - args: vec![], - vararg: None, - kwonlyargs: vec![], - kw_defaults: vec![], - kwarg: None, - defaults: vec![] + "lambda" ?> ":" > =>? { + let p = validate_arguments( + p.unwrap_or_else(|| { + ast::Arguments { + posonlyargs: vec![], + args: vec![], + vararg: None, + kwonlyargs: vec![], + kw_defaults: vec![], + kwarg: None, + defaults: vec![] + } } - }); - ast::Expr { + ))?; + + Ok(ast::Expr { location, end_location: Some(end_location), custom: (), @@ -794,7 +808,7 @@ LambdaDef: ast::Expr = { args: Box::new(p), body: Box::new(body) } - } + }) } } diff --git a/compiler/parser/src/error.rs b/compiler/parser/src/error.rs index 47096a544..4f7b101f0 100644 --- a/compiler/parser/src/error.rs +++ b/compiler/parser/src/error.rs @@ -21,6 +21,7 @@ pub enum LexicalErrorType { TabError, TabsAfterSpaces, DefaultArgumentError, + DuplicateArgumentError(String), PositionalArgumentError, UnpackedArgumentError, DuplicateKeywordArgumentError, @@ -50,6 +51,9 @@ impl fmt::Display for LexicalErrorType { LexicalErrorType::DefaultArgumentError => { write!(f, "non-default argument follows default argument") } + LexicalErrorType::DuplicateArgumentError(arg_name) => { + write!(f, "duplicate argument '{arg_name}' in function definition") + } LexicalErrorType::DuplicateKeywordArgumentError => { write!(f, "keyword argument repeated") } diff --git a/compiler/parser/src/function.rs b/compiler/parser/src/function.rs index 466211304..1caa4598e 100644 --- a/compiler/parser/src/function.rs +++ b/compiler/parser/src/function.rs @@ -10,6 +10,36 @@ pub struct ArgumentList { type ParameterDefs = (Vec, Vec, Vec); type ParameterDef = (ast::Arg, Option); +pub fn validate_arguments(arguments: ast::Arguments) -> Result { + let mut all_args: Vec<&ast::Located> = vec![]; + + all_args.extend(arguments.posonlyargs.iter()); + all_args.extend(arguments.args.iter()); + + if let Some(a) = &arguments.vararg { + all_args.push(a); + } + + all_args.extend(arguments.kwonlyargs.iter()); + + if let Some(a) = &arguments.kwarg { + all_args.push(a); + } + + let mut all_arg_names = FxHashSet::with_hasher(Default::default()); + for arg in all_args { + let arg_name = &arg.node.arg; + if !all_arg_names.insert(arg_name) { + return Err(LexicalError { + error: LexicalErrorType::DuplicateArgumentError(arg_name.to_string()), + location: arg.location, + }); + } + } + + Ok(arguments) +} + pub fn parse_params( params: (Vec, Vec), ) -> Result {