Skip to content

Commit 0ab46fd

Browse files
committed
Add Positional argument error to LexicalError
Fixes RustPython#1046
1 parent 86103bf commit 0ab46fd

File tree

7 files changed

+76
-36
lines changed

7 files changed

+76
-36
lines changed

Cargo.lock

Lines changed: 5 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

parser/src/ast.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@ pub struct Comprehension {
321321
pub is_async: bool,
322322
}
323323

324+
#[derive(Debug, PartialEq)]
325+
pub struct ArgumentList {
326+
pub args: Vec<Expression>,
327+
pub keywords: Vec<Keyword>,
328+
}
329+
324330
#[derive(Debug, PartialEq)]
325331
pub struct Keyword {
326332
pub name: Option<String>,

parser/src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub enum LexicalErrorType {
2020
StringError,
2121
UnicodeError,
2222
NestingError,
23+
PositionalArgumentError,
2324
UnrecognizedToken { tok: char },
2425
FStringError(FStringErrorType),
2526
OtherError(String),
@@ -32,6 +33,9 @@ impl fmt::Display for LexicalErrorType {
3233
LexicalErrorType::FStringError(error) => write!(f, "Got error in f-string: {}", error),
3334
LexicalErrorType::UnicodeError => write!(f, "Got unexpected unicode"),
3435
LexicalErrorType::NestingError => write!(f, "Got unexpected nesting"),
36+
LexicalErrorType::PositionalArgumentError => {
37+
write!(f, "positional argument follows keyword argument")
38+
}
3539
LexicalErrorType::UnrecognizedToken { tok } => {
3640
write!(f, "Got unexpected token {}", tok)
3741
}
@@ -40,6 +44,12 @@ impl fmt::Display for LexicalErrorType {
4044
}
4145
}
4246

47+
impl From<LexicalError> for LalrpopError<Location, Tok, LexicalError> {
48+
fn from(err: LexicalError) -> Self {
49+
lalrpop_util::ParseError::User { error: err }
50+
}
51+
}
52+
4353
// TODO: consolidate these with ParseError
4454
#[derive(Debug, PartialEq)]
4555
pub struct FStringError {

parser/src/function.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use crate::ast;
2+
use crate::error::{LexicalError, LexicalErrorType};
3+
4+
type FunctionArgument = (Option<Option<String>>, ast::Expression);
5+
6+
pub fn parse_args(func_args: Vec<FunctionArgument>) -> Result<ast::ArgumentList, LexicalError> {
7+
let mut args = vec![];
8+
let mut keywords = vec![];
9+
for (name, value) in func_args {
10+
match name {
11+
Some(n) => {
12+
keywords.push(ast::Keyword { name: n, value });
13+
}
14+
None => {
15+
// Allow starred args after keyword arguments.
16+
if !keywords.is_empty() && !is_starred(&value) {
17+
return Err(LexicalError {
18+
error: LexicalErrorType::PositionalArgumentError,
19+
location: value.location.clone(),
20+
});
21+
}
22+
23+
args.push(value);
24+
}
25+
}
26+
}
27+
Ok(ast::ArgumentList { args, keywords })
28+
}
29+
30+
fn is_starred(exp: &ast::Expression) -> bool {
31+
if let ast::ExpressionType::Starred { .. } = exp.node {
32+
true
33+
} else {
34+
false
35+
}
36+
}

parser/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use lalrpop_util::lalrpop_mod;
88
pub mod ast;
99
pub mod error;
1010
mod fstring;
11+
mod function;
1112
pub mod lexer;
1213
pub mod location;
1314
pub mod parser;

parser/src/python.lalrpop

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::iter::FromIterator;
77

88
use crate::ast;
99
use crate::fstring::parse_located_fstring;
10+
use crate::function::parse_args;
1011
use crate::error::LexicalError;
1112
use crate::lexer;
1213
use crate::location;
@@ -580,7 +581,7 @@ KwargParameter<ArgType>: Option<ast::Parameter> = {
580581
ClassDef: ast::Statement = {
581582
<decorator_list:Decorator*> <location:@L> "class" <name:Identifier> <a:("(" ArgumentList ")")?> ":" <body:Suite> => {
582583
let (bases, keywords) = match a {
583-
Some((_, args, _)) => args,
584+
Some((_, arg, _)) => (arg.args, arg.keywords),
584585
None => (vec![], vec![]),
585586
};
586587
ast::Statement {
@@ -616,14 +617,13 @@ Path: ast::Expression = {
616617
Decorator: ast::Expression = {
617618
"@" <p:Path> <a: (@L "(" ArgumentList ")")?> "\n" => {
618619
match a {
619-
Some((location, _, args, _)) => {
620-
let (args, keywords) = args;
620+
Some((location, _, arg, _)) => {
621621
ast::Expression {
622622
location,
623623
node: ast::ExpressionType::Call {
624624
function: Box::new(p),
625-
args,
626-
keywords,
625+
args: arg.args,
626+
keywords: arg.keywords,
627627
}
628628
}
629629
},
@@ -633,7 +633,7 @@ Decorator: ast::Expression = {
633633
};
634634

635635
YieldExpr: ast::Expression = {
636-
<location:@L> "yield" <value:TestList?> => ast::Expression {
636+
<location:@L> "yield" <value:TestList?> => ast::Expression {
637637
location,
638638
node: ast::ExpressionType::Yield { value: value.map(Box::new) }
639639
},
@@ -847,10 +847,9 @@ AtomExpr: ast::Expression = {
847847
AtomExpr2: ast::Expression = {
848848
Atom,
849849
<f:AtomExpr2> <location:@L> "(" <a:ArgumentList> ")" => {
850-
let (args, keywords) = a;
851850
ast::Expression {
852851
location,
853-
node: ast::ExpressionType::Call { function: Box::new(f), args, keywords }
852+
node: ast::ExpressionType::Call { function: Box::new(f), args: a.args, keywords: a.keywords }
854853
}
855854
},
856855
<e:AtomExpr2> <location:@L> "[" <s:SubscriptList> "]" => ast::Expression {
@@ -1060,31 +1059,10 @@ SingleForComprehension: ast::Comprehension = {
10601059
ExpressionNoCond: ast::Expression = OrTest;
10611060
ComprehensionIf: ast::Expression = "if" <c:ExpressionNoCond> => c;
10621061

1063-
ArgumentList: (Vec<ast::Expression>, Vec<ast::Keyword>) = {
1064-
<e: Comma<FunctionArgument>> => {
1065-
let mut args = vec![];
1066-
let mut keywords = vec![];
1067-
for (name, value) in e {
1068-
match name {
1069-
Some(n) => {
1070-
keywords.push(ast::Keyword { name: n, value: value });
1071-
},
1072-
None => {
1073-
// Allow starred args after keyword arguments.
1074-
let is_starred = if let ast::ExpressionType::Starred { .. } = &value.node {
1075-
true
1076-
} else {
1077-
false
1078-
};
1079-
1080-
if keywords.len() > 0 && !is_starred {
1081-
panic!("positional argument follows keyword argument {:?}", keywords);
1082-
};
1083-
args.push(value);
1084-
},
1085-
}
1086-
}
1087-
(args, keywords)
1062+
ArgumentList: ast::ArgumentList = {
1063+
<e: Comma<FunctionArgument>> =>? {
1064+
let arg_list = parse_args(e)?;
1065+
Ok(arg_list)
10881066
}
10891067
};
10901068

tests/snippets/function.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from testutils import assertRaises
2+
3+
14
__name__ = "function"
25

36

@@ -88,3 +91,7 @@ def f8() -> int:
8891
return 10
8992

9093
assert f8() == 10
94+
95+
96+
with assertRaises(SyntaxError):
97+
exec('print(keyword=10, 20)')

0 commit comments

Comments
 (0)