Skip to content

Commit 0f8ba73

Browse files
Merge pull request RustPython#100 from OddBloke/kwargs
Add support for function argument defaults
2 parents 0787115 + cb173c1 commit 0f8ba73

11 files changed

Lines changed: 222 additions & 38 deletions

File tree

Cargo.lock

Lines changed: 5 additions & 4 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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ pub enum Statement {
9595
ClassDef {
9696
name: String,
9797
body: Vec<LocatedStatement>,
98-
args: Vec<String>,
98+
args: Vec<(String, Option<Expression>)>,
9999
// TODO: docstring: String,
100100
},
101101
FunctionDef {
102102
name: String,
103-
args: Vec<String>,
103+
args: Vec<(String, Option<Expression>)>,
104104
// docstring: String,
105105
body: Vec<LocatedStatement>,
106106
},
@@ -161,7 +161,7 @@ pub enum Expression {
161161
name: String,
162162
},
163163
Lambda {
164-
args: Vec<String>,
164+
args: Vec<(String, Option<Expression>)>,
165165
body: Box<Expression>,
166166
},
167167
True,

parser/src/parser.rs

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ mod tests {
193193
location: ast::Location::new(1, 1),
194194
node: ast::Statement::Expression {
195195
expression: ast::Expression::Lambda {
196-
args: vec![String::from("x"), String::from("y")],
196+
args: vec![(String::from("x"), None), (String::from("y"), None)],
197197
body: Box::new(ast::Expression::Binop {
198198
a: Box::new(ast::Expression::Identifier {
199199
name: String::from("x"),
@@ -211,25 +211,46 @@ mod tests {
211211

212212
#[test]
213213
fn test_parse_class() {
214-
let source = String::from("class Foo(A, B):\n def __init__(self):\n pass\n");
214+
let source = String::from("class Foo(A, B):\n def __init__(self):\n pass\n def method_with_default(self, arg='default'):\n pass\n");
215215
assert_eq!(
216216
parse_statement(&source),
217217
Ok(ast::LocatedStatement {
218218
location: ast::Location::new(1, 1),
219219
node: ast::Statement::ClassDef {
220220
name: String::from("Foo"),
221-
args: vec![String::from("A"), String::from("B")],
222-
body: vec![ast::LocatedStatement {
223-
location: ast::Location::new(2, 2),
224-
node: ast::Statement::FunctionDef {
225-
name: String::from("__init__"),
226-
args: vec![String::from("self")],
227-
body: vec![ast::LocatedStatement {
228-
location: ast::Location::new(3, 3),
229-
node: ast::Statement::Pass,
230-
}],
221+
args: vec![(String::from("A"), None), (String::from("B"), None)],
222+
body: vec![
223+
ast::LocatedStatement {
224+
location: ast::Location::new(2, 2),
225+
node: ast::Statement::FunctionDef {
226+
name: String::from("__init__"),
227+
args: vec![(String::from("self"), None)],
228+
body: vec![ast::LocatedStatement {
229+
location: ast::Location::new(3, 3),
230+
node: ast::Statement::Pass,
231+
}],
232+
}
233+
},
234+
ast::LocatedStatement {
235+
location: ast::Location::new(4, 2),
236+
node: ast::Statement::FunctionDef {
237+
name: String::from("method_with_default"),
238+
args: vec![
239+
(String::from("self"), None),
240+
(
241+
String::from("arg"),
242+
Some(ast::Expression::String {
243+
value: "default".to_string()
244+
})
245+
)
246+
],
247+
body: vec![ast::LocatedStatement {
248+
location: ast::Location::new(5, 3),
249+
node: ast::Statement::Pass,
250+
}],
251+
}
231252
}
232-
}],
253+
],
233254
}
234255
})
235256
)

parser/src/python.lalrpop

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,12 +308,19 @@ FuncDef: ast::LocatedStatement = {
308308
},
309309
};
310310

311-
Parameters: Vec<String> = {
311+
Parameters: Vec<(String, Option<ast::Expression>)> = {
312312
"(" <a: TypedArgsList> ")" => a,
313313
};
314314

315-
TypedArgsList: Vec<String> = {
316-
<a: Comma<Identifier>> => a,
315+
// parameters are (String, None), kwargs are (String, Some(Test)) where Test is
316+
// the default
317+
TypedArgsList: Vec<(String, Option<ast::Expression>)> = {
318+
<a: Comma<Parameter>> => a
319+
};
320+
321+
Parameter: (String, Option<ast::Expression>) = {
322+
<i:Identifier> => (i.clone(), None),
323+
<i:Identifier> "=" <e:Expression> => (i.clone(), Some(e.clone())),
317324
};
318325

319326
ClassDef: ast::LocatedStatement = {

tests/snippets/func_defaults.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
def no_args():
2+
pass
3+
4+
no_args()
5+
6+
try:
7+
no_args('one_arg')
8+
except TypeError:
9+
pass
10+
else:
11+
assert False, 'no TypeError raised: 1 arg to no_args'
12+
13+
14+
def one_arg(arg):
15+
pass
16+
17+
one_arg('one_arg')
18+
19+
try:
20+
one_arg()
21+
except TypeError:
22+
pass
23+
else:
24+
assert False, 'no TypeError raised: no args to one_arg'
25+
26+
try:
27+
one_arg('one_arg', 'two_arg')
28+
except TypeError:
29+
pass
30+
else:
31+
assert False, 'no TypeError raised: two args to one_arg'
32+
33+
34+
def one_default_arg(arg="default"):
35+
return arg
36+
37+
assert 'default' == one_default_arg()
38+
assert 'arg' == one_default_arg('arg')
39+
40+
try:
41+
one_default_arg('one_arg', 'two_arg')
42+
except TypeError:
43+
pass
44+
else:
45+
assert False, 'no TypeError raised: two args to one_default_arg'
46+
47+
48+
def one_normal_one_default_arg(pos, arg="default"):
49+
return pos, arg
50+
51+
assert ('arg', 'default') == one_normal_one_default_arg('arg')
52+
assert ('arg', 'arg2') == one_normal_one_default_arg('arg', 'arg2')
53+
54+
try:
55+
one_normal_one_default_arg()
56+
except TypeError:
57+
pass
58+
else:
59+
assert False, 'no TypeError raised: two args to one_default_arg'
60+
61+
try:
62+
one_normal_one_default_arg('one', 'two', 'three')
63+
except TypeError:
64+
pass
65+
else:
66+
assert False, 'no TypeError raised: two args to one_default_arg'

vm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.1.0"
44
authors = ["Shing Lyu <shing.lyu@gmail.com>"]
55

66
[dependencies]
7+
bitflags = "1.0.4"
78
log = "0.3"
89
rustpython_parser = {path = "../parser"}
910
serde = "1.0.66"

vm/src/bytecode.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ impl CodeObject {
3737
}
3838
}
3939

40+
bitflags! {
41+
pub struct FunctionOpArg: u8 {
42+
const HAS_DEFAULTS = 0x01;
43+
}
44+
}
45+
4046
pub type Label = usize;
4147

4248
#[derive(Debug, Clone, PartialEq)]
@@ -88,7 +94,9 @@ pub enum Instruction {
8894
JumpIfFalse {
8995
target: Label,
9096
},
91-
MakeFunction,
97+
MakeFunction {
98+
flags: FunctionOpArg,
99+
},
92100
CallFunction {
93101
count: usize,
94102
},

vm/src/compile.rs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,28 @@ impl Compiler {
350350
}
351351
ast::Statement::FunctionDef { name, args, body } => {
352352
// Create bytecode for this function:
353-
self.code_object_stack
354-
.push(CodeObject::new(args.to_vec(), None));
353+
let mut names = vec![];
354+
let mut default_elements = vec![];
355+
356+
for (name, default) in args {
357+
names.push(name.clone());
358+
if let Some(default) = default {
359+
default_elements.push(default.clone());
360+
} else {
361+
if default_elements.len() > 0 {
362+
// Once we have started with defaults, all remaining arguments must
363+
// have defaults
364+
panic!("non-default argument follows default argument: {}", name);
365+
}
366+
}
367+
}
368+
369+
let have_kwargs = default_elements.len() > 0;
370+
self.compile_expression(&ast::Expression::Tuple {
371+
elements: default_elements,
372+
});
373+
374+
self.code_object_stack.push(CodeObject::new(names, None));
355375
self.compile_statements(body);
356376

357377
// Emit None at end:
@@ -371,7 +391,11 @@ impl Compiler {
371391
});
372392

373393
// Turn code object into function object:
374-
self.emit(Instruction::MakeFunction);
394+
let mut flags = bytecode::FunctionOpArg::empty();
395+
if have_kwargs {
396+
flags = flags | bytecode::FunctionOpArg::HAS_DEFAULTS;
397+
}
398+
self.emit(Instruction::MakeFunction { flags: flags });
375399
self.emit(Instruction::StoreName {
376400
name: name.to_string(),
377401
});
@@ -400,7 +424,9 @@ impl Compiler {
400424
},
401425
});
402426
// Turn code object into function object:
403-
self.emit(Instruction::MakeFunction);
427+
self.emit(Instruction::MakeFunction {
428+
flags: bytecode::FunctionOpArg::empty(),
429+
});
404430

405431
self.emit(Instruction::LoadConst {
406432
value: bytecode::Constant::String {
@@ -409,7 +435,9 @@ impl Compiler {
409435
});
410436

411437
for base in args {
412-
self.emit(Instruction::LoadName { name: base.clone() });
438+
self.emit(Instruction::LoadName {
439+
name: base.0.clone(),
440+
});
413441
}
414442
self.emit(Instruction::CallFunction {
415443
count: 2 + args.len(),
@@ -720,8 +748,10 @@ impl Compiler {
720748
});
721749
}
722750
ast::Expression::Lambda { args, body } => {
723-
self.code_object_stack
724-
.push(CodeObject::new(args.to_vec(), None));
751+
self.code_object_stack.push(CodeObject::new(
752+
args.iter().map(|(name, _default)| name.clone()).collect(),
753+
None,
754+
));
725755
self.compile_expression(body);
726756
self.emit(Instruction::ReturnValue);
727757
let code = self.code_object_stack.pop().unwrap();
@@ -734,7 +764,9 @@ impl Compiler {
734764
},
735765
});
736766
// Turn code object into function object:
737-
self.emit(Instruction::MakeFunction);
767+
self.emit(Instruction::MakeFunction {
768+
flags: bytecode::FunctionOpArg::empty(),
769+
});
738770
}
739771
}
740772
}

vm/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#[macro_use]
2+
extern crate bitflags;
3+
#[macro_use]
24
extern crate log;
35
// extern crate env_logger;
46
extern crate serde;

vm/src/pyobject.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,17 @@ impl PyContext {
267267
)
268268
}
269269

270-
pub fn new_function(&self, code_obj: PyObjectRef, scope: PyObjectRef) -> PyObjectRef {
270+
pub fn new_function(
271+
&self,
272+
code_obj: PyObjectRef,
273+
scope: PyObjectRef,
274+
defaults: PyObjectRef,
275+
) -> PyObjectRef {
271276
PyObject::new(
272277
PyObjectKind::Function {
273278
code: code_obj,
274279
scope: scope,
280+
defaults: defaults,
275281
},
276282
self.function_type(),
277283
)
@@ -566,6 +572,7 @@ pub enum PyObjectKind {
566572
Function {
567573
code: PyObjectRef,
568574
scope: PyObjectRef,
575+
defaults: PyObjectRef,
569576
},
570577
BoundMethod {
571578
function: PyObjectRef,
@@ -613,7 +620,7 @@ impl fmt::Debug for PyObjectKind {
613620
} => write!(f, "slice"),
614621
&PyObjectKind::NameError { name: _ } => write!(f, "NameError"),
615622
&PyObjectKind::Code { ref code } => write!(f, "code: {:?}", code),
616-
&PyObjectKind::Function { code: _, scope: _ } => write!(f, "function"),
623+
&PyObjectKind::Function { .. } => write!(f, "function"),
617624
&PyObjectKind::BoundMethod {
618625
ref function,
619626
ref object,
@@ -683,7 +690,7 @@ impl PyObject {
683690
} => format!("<class '{}'>", name),
684691
PyObjectKind::Instance { dict: _ } => format!("<instance>"),
685692
PyObjectKind::Code { code: _ } => format!("<code>"),
686-
PyObjectKind::Function { code: _, scope: _ } => format!("<func>"),
693+
PyObjectKind::Function { .. } => format!("<func>"),
687694
PyObjectKind::BoundMethod { .. } => format!("<bound-method>"),
688695
PyObjectKind::RustFunction { function: _ } => format!("<rustfunc>"),
689696
PyObjectKind::Module { ref name, dict: _ } => format!("<module '{}'>", name),

0 commit comments

Comments
 (0)