Skip to content

Commit c5c9181

Browse files
Merge pull request #755 from skinny121/int_new_args
Use new style args for int.__new__
2 parents c724fda + ce9a909 commit c5c9181

File tree

5 files changed

+247
-39
lines changed

5 files changed

+247
-39
lines changed

derive/src/lib.rs

Lines changed: 178 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,198 @@ extern crate proc_macro;
33
use proc_macro::TokenStream;
44
use proc_macro2::TokenStream as TokenStream2;
55
use quote::quote;
6-
use syn::{Data, DeriveInput, Fields};
6+
use syn::{Attribute, Data, DeriveInput, Expr, Field, Fields, Ident, Lit, Meta, NestedMeta};
77

8-
#[proc_macro_derive(FromArgs)]
8+
#[proc_macro_derive(FromArgs, attributes(pyarg))]
99
pub fn derive_from_args(input: TokenStream) -> TokenStream {
1010
let ast: DeriveInput = syn::parse(input).unwrap();
1111

1212
let gen = impl_from_args(&ast);
1313
gen.to_string().parse().unwrap()
1414
}
1515

16+
/// The kind of the python parameter, this corresponds to the value of Parameter.kind
17+
/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind)
18+
enum ParameterKind {
19+
PositionalOnly,
20+
PositionalOrKeyword,
21+
KeywordOnly,
22+
}
23+
24+
impl ParameterKind {
25+
fn from_ident(ident: &Ident) -> ParameterKind {
26+
if ident == "positional_only" {
27+
ParameterKind::PositionalOnly
28+
} else if ident == "positional_or_keyword" {
29+
ParameterKind::PositionalOrKeyword
30+
} else if ident == "keyword_only" {
31+
ParameterKind::KeywordOnly
32+
} else {
33+
panic!("Unrecognised attribute")
34+
}
35+
}
36+
}
37+
38+
struct ArgAttribute {
39+
kind: ParameterKind,
40+
default: Option<Expr>,
41+
optional: bool,
42+
}
43+
44+
impl ArgAttribute {
45+
fn from_attribute(attr: &Attribute) -> Option<ArgAttribute> {
46+
if !attr.path.is_ident("pyarg") {
47+
return None;
48+
}
49+
50+
match attr.parse_meta().unwrap() {
51+
Meta::List(list) => {
52+
let mut iter = list.nested.iter();
53+
let first_arg = iter.next().expect("at least one argument in pyarg list");
54+
let kind = match first_arg {
55+
NestedMeta::Meta(Meta::Word(ident)) => ParameterKind::from_ident(ident),
56+
_ => panic!("Bad syntax for first pyarg attribute argument"),
57+
};
58+
59+
let mut attribute = ArgAttribute {
60+
kind,
61+
default: None,
62+
optional: false,
63+
};
64+
65+
while let Some(arg) = iter.next() {
66+
attribute.parse_argument(arg);
67+
}
68+
69+
assert!(
70+
attribute.default.is_none() || !attribute.optional,
71+
"Can't set both a default value and optional"
72+
);
73+
74+
Some(attribute)
75+
}
76+
_ => panic!("Bad syntax for pyarg attribute"),
77+
}
78+
}
79+
80+
fn parse_argument(&mut self, arg: &NestedMeta) {
81+
match arg {
82+
NestedMeta::Meta(Meta::Word(ident)) => {
83+
if ident == "default" {
84+
assert!(self.default.is_none(), "Default already set");
85+
let expr = syn::parse_str::<Expr>("Default::default()").unwrap();
86+
self.default = Some(expr);
87+
} else if ident == "optional" {
88+
self.optional = true;
89+
} else {
90+
panic!("Unrecognised pyarg attribute '{}'", ident);
91+
}
92+
}
93+
NestedMeta::Meta(Meta::NameValue(name_value)) => {
94+
if name_value.ident == "default" {
95+
assert!(self.default.is_none(), "Default already set");
96+
97+
match name_value.lit {
98+
Lit::Str(ref val) => {
99+
let expr = val
100+
.parse::<Expr>()
101+
.expect("a valid expression for default argument");
102+
self.default = Some(expr);
103+
}
104+
_ => panic!("Expected string value for default argument"),
105+
}
106+
} else if name_value.ident == "optional" {
107+
match name_value.lit {
108+
Lit::Bool(ref val) => {
109+
self.optional = val.value;
110+
}
111+
_ => panic!("Expected boolean value for optional argument"),
112+
}
113+
} else {
114+
panic!("Unrecognised pyarg attribute '{}'", name_value.ident);
115+
}
116+
}
117+
_ => panic!("Bad syntax for first pyarg attribute argument"),
118+
};
119+
}
120+
}
121+
122+
fn generate_field(field: &Field) -> TokenStream2 {
123+
let mut pyarg_attrs = field
124+
.attrs
125+
.iter()
126+
.filter_map(ArgAttribute::from_attribute)
127+
.collect::<Vec<_>>();
128+
let attr = if pyarg_attrs.is_empty() {
129+
ArgAttribute {
130+
kind: ParameterKind::PositionalOrKeyword,
131+
default: None,
132+
optional: false,
133+
}
134+
} else if pyarg_attrs.len() == 1 {
135+
pyarg_attrs.remove(0)
136+
} else {
137+
panic!(
138+
"Multiple pyarg attributes on field '{}'",
139+
field.ident.as_ref().unwrap()
140+
);
141+
};
142+
143+
let name = &field.ident;
144+
let middle = quote! {
145+
.map(|x| crate::pyobject::TryFromObject::try_from_object(vm, x)).transpose()?
146+
};
147+
let ending = if let Some(default) = attr.default {
148+
quote! {
149+
.unwrap_or_else(|| #default)
150+
}
151+
} else if attr.optional {
152+
quote! {
153+
.map(crate::function::OptionalArg::Present)
154+
.unwrap_or(crate::function::OptionalArg::Missing)
155+
}
156+
} else {
157+
let err = match attr.kind {
158+
ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! {
159+
crate::function::ArgumentError::TooFewArgs
160+
},
161+
ParameterKind::KeywordOnly => quote! {
162+
crate::function::ArgumentError::RequiredKeywordArgument(tringify!(#name))
163+
},
164+
};
165+
quote! {
166+
.ok_or_else(|| #err)?
167+
}
168+
};
169+
170+
match attr.kind {
171+
ParameterKind::PositionalOnly => {
172+
quote! {
173+
#name: args.take_positional()#middle#ending,
174+
}
175+
}
176+
ParameterKind::PositionalOrKeyword => {
177+
quote! {
178+
#name: args.take_positional_keyword(stringify!(#name))#middle#ending,
179+
}
180+
}
181+
ParameterKind::KeywordOnly => {
182+
quote! {
183+
#name: args.take_keyword(stringify!(#name))#middle#ending,
184+
}
185+
}
186+
}
187+
}
188+
16189
fn impl_from_args(input: &DeriveInput) -> TokenStream2 {
17190
// FIXME: This references types using `crate` instead of `rustpython_vm`
18191
// so that it can be used in the latter. How can we support both?
192+
// Can use extern crate self as rustpython_vm; once in stable.
193+
// https://github.com/rust-lang/rust/issues/56409
19194
let fields = match input.data {
20195
Data::Struct(ref data) => {
21196
match data.fields {
22-
Fields::Named(ref fields) => fields.named.iter().map(|field| {
23-
let name = &field.ident;
24-
quote! {
25-
#name: crate::pyobject::TryFromObject::try_from_object(
26-
vm,
27-
args.take_keyword(stringify!(#name)).unwrap_or_else(|| vm.ctx.none())
28-
)?,
29-
}
30-
}),
197+
Fields::Named(ref fields) => fields.named.iter().map(generate_field),
31198
Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message
32199
}
33200
}

tests/snippets/ints.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from testutils import assert_raises
1+
from testutils import assert_raises, assertRaises
22

33
# int to int comparisons
44

@@ -58,3 +58,19 @@
5858
assert (2).__rmul__(1.0) == NotImplemented
5959
assert (2).__truediv__(1.0) == NotImplemented
6060
assert (2).__rtruediv__(1.0) == NotImplemented
61+
62+
63+
assert int() == 0
64+
assert int("101", 2) == 5
65+
assert int("101", base=2) == 5
66+
assert int(1) == 1
67+
68+
with assertRaises(TypeError):
69+
int(base=2)
70+
71+
with assertRaises(TypeError):
72+
int(1, base=2)
73+
74+
with assertRaises(TypeError):
75+
# check that first parameter is truly positional only
76+
int(val_options=1)

vm/src/builtins.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,8 +537,11 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
537537

538538
#[derive(Debug, FromArgs)]
539539
pub struct PrintOptions {
540+
#[pyarg(keyword_only, default = "None")]
540541
sep: Option<PyStringRef>,
542+
#[pyarg(keyword_only, default = "None")]
541543
end: Option<PyStringRef>,
544+
#[pyarg(keyword_only, default = "false")]
542545
flush: bool,
543546
}
544547

vm/src/function.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use std::collections::HashMap;
22
use std::ops::RangeInclusive;
33

4-
use crate::obj::objtype;
4+
use crate::obj::objtype::{isinstance, PyClassRef};
55
use crate::pyobject::{IntoPyObject, PyObjectRef, PyResult, TryFromObject, TypeProtocol};
66
use crate::vm::VirtualMachine;
77

88
use self::OptionalArg::*;
9-
use crate::obj::objtype::PyClassRef;
109

1110
/// The `PyFuncArgs` struct is one of the most used structs then creating
1211
/// a rust function that can be called from python. It holds both positional
@@ -95,7 +94,7 @@ impl PyFuncArgs {
9594
) -> Result<Option<PyObjectRef>, PyObjectRef> {
9695
match self.get_optional_kwarg(key) {
9796
Some(kwarg) => {
98-
if objtype::isinstance(&kwarg, &ty) {
97+
if isinstance(&kwarg, &ty) {
9998
Ok(Some(kwarg))
10099
} else {
101100
let expected_ty_name = vm.to_pystr(&ty)?;
@@ -110,14 +109,18 @@ impl PyFuncArgs {
110109
}
111110
}
112111

113-
pub fn next_positional(&mut self) -> Option<PyObjectRef> {
112+
pub fn take_positional(&mut self) -> Option<PyObjectRef> {
114113
if self.args.is_empty() {
115114
None
116115
} else {
117116
Some(self.args.remove(0))
118117
}
119118
}
120119

120+
pub fn take_positional_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
121+
self.take_positional().or_else(|| self.take_keyword(name))
122+
}
123+
121124
pub fn take_keyword(&mut self, name: &str) -> Option<PyObjectRef> {
122125
// TODO: change kwarg representation so this scan isn't necessary
123126
if let Some(index) = self
@@ -164,6 +167,9 @@ impl PyFuncArgs {
164167
Err(ArgumentError::InvalidKeywordArgument(name)) => {
165168
return Err(vm.new_type_error(format!("{} is an invalid keyword argument", name)));
166169
}
170+
Err(ArgumentError::RequiredKeywordArgument(name)) => {
171+
return Err(vm.new_type_error(format!("Required keyqord only argument {}", name)));
172+
}
167173
Err(ArgumentError::Exception(ex)) => {
168174
return Err(ex);
169175
}
@@ -192,6 +198,8 @@ pub enum ArgumentError {
192198
TooManyArgs,
193199
/// The function doesn't accept a keyword argument with the given name.
194200
InvalidKeywordArgument(String),
201+
/// The function require a keyword argument with the given name, but one wasn't provided
202+
RequiredKeywordArgument(String),
195203
/// An exception was raised while binding arguments to the function
196204
/// parameters.
197205
Exception(PyObjectRef),
@@ -272,7 +280,7 @@ where
272280
{
273281
fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result<Self, ArgumentError> {
274282
let mut varargs = Vec::new();
275-
while let Some(value) = args.next_positional() {
283+
while let Some(value) = args.take_positional() {
276284
varargs.push(T::try_from_object(vm, value)?);
277285
}
278286
Ok(Args(varargs))
@@ -297,7 +305,7 @@ where
297305
}
298306

299307
fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result<Self, ArgumentError> {
300-
if let Some(value) = args.next_positional() {
308+
if let Some(value) = args.take_positional() {
301309
Ok(T::try_from_object(vm, value)?)
302310
} else {
303311
Err(ArgumentError::TooFewArgs)
@@ -308,6 +316,7 @@ where
308316
/// An argument that may or may not be provided by the caller.
309317
///
310318
/// This style of argument is not possible in pure Python.
319+
#[derive(Debug)]
311320
pub enum OptionalArg<T = PyObjectRef> {
312321
Present(T),
313322
Missing,
@@ -340,7 +349,7 @@ where
340349
}
341350

342351
fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result<Self, ArgumentError> {
343-
if let Some(value) = args.next_positional() {
352+
if let Some(value) = args.take_positional() {
344353
Ok(Present(T::try_from_object(vm, value)?))
345354
} else {
346355
Ok(Missing)

0 commit comments

Comments
 (0)