Skip to content

Commit 4222d87

Browse files
committed
Merge branch 'master' into coolreader18/py_class-proc-macro
2 parents ff85838 + c5c9181 commit 4222d87

File tree

6 files changed

+303
-168
lines changed

6 files changed

+303
-168
lines changed

derive/src/lib.rs

Lines changed: 177 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use proc_macro::TokenStream;
44
use proc_macro2::{Span, TokenStream as TokenStream2};
55
use quote::quote;
66
use syn::{
7-
parse_macro_input, AttributeArgs, Data, DeriveInput, Fields, Ident, ImplItem, Item, Lit, Meta,
8-
NestedMeta,
7+
parse_macro_input, Attribute, AttributeArgs, Data, DeriveInput, Expr, Field, Fields, Ident,
8+
ImplItem, Item, Lit, Meta, NestedMeta,
99
};
1010

1111
fn rustpython_path(inside_vm: bool) -> syn::Path {
@@ -42,28 +42,193 @@ fn rustpython_path_attr(attr: &AttributeArgs) -> syn::Path {
4242
}))
4343
}
4444

45-
#[proc_macro_derive(FromArgs, attributes(__inside_vm))]
45+
#[proc_macro_derive(FromArgs, attributes(__inside_vm, pyarg))]
4646
pub fn derive_from_args(input: TokenStream) -> TokenStream {
4747
let ast: DeriveInput = syn::parse(input).unwrap();
4848

4949
let gen = impl_from_args(ast);
5050
gen.to_string().parse().unwrap()
5151
}
5252

53+
/// The kind of the python parameter, this corresponds to the value of Parameter.kind
54+
/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind)
55+
enum ParameterKind {
56+
PositionalOnly,
57+
PositionalOrKeyword,
58+
KeywordOnly,
59+
}
60+
61+
impl ParameterKind {
62+
fn from_ident(ident: &Ident) -> ParameterKind {
63+
if ident == "positional_only" {
64+
ParameterKind::PositionalOnly
65+
} else if ident == "positional_or_keyword" {
66+
ParameterKind::PositionalOrKeyword
67+
} else if ident == "keyword_only" {
68+
ParameterKind::KeywordOnly
69+
} else {
70+
panic!("Unrecognised attribute")
71+
}
72+
}
73+
}
74+
75+
struct ArgAttribute {
76+
kind: ParameterKind,
77+
default: Option<Expr>,
78+
optional: bool,
79+
}
80+
81+
impl ArgAttribute {
82+
fn from_attribute(attr: &Attribute) -> Option<ArgAttribute> {
83+
if !attr.path.is_ident("pyarg") {
84+
return None;
85+
}
86+
87+
match attr.parse_meta().unwrap() {
88+
Meta::List(list) => {
89+
let mut iter = list.nested.iter();
90+
let first_arg = iter.next().expect("at least one argument in pyarg list");
91+
let kind = match first_arg {
92+
NestedMeta::Meta(Meta::Word(ident)) => ParameterKind::from_ident(ident),
93+
_ => panic!("Bad syntax for first pyarg attribute argument"),
94+
};
95+
96+
let mut attribute = ArgAttribute {
97+
kind,
98+
default: None,
99+
optional: false,
100+
};
101+
102+
while let Some(arg) = iter.next() {
103+
attribute.parse_argument(arg);
104+
}
105+
106+
assert!(
107+
attribute.default.is_none() || !attribute.optional,
108+
"Can't set both a default value and optional"
109+
);
110+
111+
Some(attribute)
112+
}
113+
_ => panic!("Bad syntax for pyarg attribute"),
114+
}
115+
}
116+
117+
fn parse_argument(&mut self, arg: &NestedMeta) {
118+
match arg {
119+
NestedMeta::Meta(Meta::Word(ident)) => {
120+
if ident == "default" {
121+
assert!(self.default.is_none(), "Default already set");
122+
let expr = syn::parse_str::<Expr>("Default::default()").unwrap();
123+
self.default = Some(expr);
124+
} else if ident == "optional" {
125+
self.optional = true;
126+
} else {
127+
panic!("Unrecognised pyarg attribute '{}'", ident);
128+
}
129+
}
130+
NestedMeta::Meta(Meta::NameValue(name_value)) => {
131+
if name_value.ident == "default" {
132+
assert!(self.default.is_none(), "Default already set");
133+
134+
match name_value.lit {
135+
Lit::Str(ref val) => {
136+
let expr = val
137+
.parse::<Expr>()
138+
.expect("a valid expression for default argument");
139+
self.default = Some(expr);
140+
}
141+
_ => panic!("Expected string value for default argument"),
142+
}
143+
} else if name_value.ident == "optional" {
144+
match name_value.lit {
145+
Lit::Bool(ref val) => {
146+
self.optional = val.value;
147+
}
148+
_ => panic!("Expected boolean value for optional argument"),
149+
}
150+
} else {
151+
panic!("Unrecognised pyarg attribute '{}'", name_value.ident);
152+
}
153+
}
154+
_ => panic!("Bad syntax for first pyarg attribute argument"),
155+
};
156+
}
157+
}
158+
159+
fn generate_field(field: &Field) -> TokenStream2 {
160+
let mut pyarg_attrs = field
161+
.attrs
162+
.iter()
163+
.filter_map(ArgAttribute::from_attribute)
164+
.collect::<Vec<_>>();
165+
let attr = if pyarg_attrs.is_empty() {
166+
ArgAttribute {
167+
kind: ParameterKind::PositionalOrKeyword,
168+
default: None,
169+
optional: false,
170+
}
171+
} else if pyarg_attrs.len() == 1 {
172+
pyarg_attrs.remove(0)
173+
} else {
174+
panic!(
175+
"Multiple pyarg attributes on field '{}'",
176+
field.ident.as_ref().unwrap()
177+
);
178+
};
179+
180+
let name = &field.ident;
181+
let middle = quote! {
182+
.map(|x| crate::pyobject::TryFromObject::try_from_object(vm, x)).transpose()?
183+
};
184+
let ending = if let Some(default) = attr.default {
185+
quote! {
186+
.unwrap_or_else(|| #default)
187+
}
188+
} else if attr.optional {
189+
quote! {
190+
.map(crate::function::OptionalArg::Present)
191+
.unwrap_or(crate::function::OptionalArg::Missing)
192+
}
193+
} else {
194+
let err = match attr.kind {
195+
ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! {
196+
crate::function::ArgumentError::TooFewArgs
197+
},
198+
ParameterKind::KeywordOnly => quote! {
199+
crate::function::ArgumentError::RequiredKeywordArgument(tringify!(#name))
200+
},
201+
};
202+
quote! {
203+
.ok_or_else(|| #err)?
204+
}
205+
};
206+
207+
match attr.kind {
208+
ParameterKind::PositionalOnly => {
209+
quote! {
210+
#name: args.take_positional()#middle#ending,
211+
}
212+
}
213+
ParameterKind::PositionalOrKeyword => {
214+
quote! {
215+
#name: args.take_positional_keyword(stringify!(#name))#middle#ending,
216+
}
217+
}
218+
ParameterKind::KeywordOnly => {
219+
quote! {
220+
#name: args.take_keyword(stringify!(#name))#middle#ending,
221+
}
222+
}
223+
}
224+
}
225+
53226
fn impl_from_args(input: DeriveInput) -> TokenStream2 {
54227
let rp_path = rustpython_path_derive(&input);
55228
let fields = match input.data {
56229
Data::Struct(ref data) => {
57230
match data.fields {
58-
Fields::Named(ref fields) => fields.named.iter().map(|field| {
59-
let name = &field.ident;
60-
quote! {
61-
#name: crate::pyobject::TryFromObject::try_from_object(
62-
vm,
63-
args.take_keyword(stringify!(#name)).unwrap_or_else(|| vm.ctx.none())
64-
)?,
65-
}
66-
}),
231+
Fields::Named(ref fields) => fields.named.iter().map(generate_field),
67232
Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message
68233
}
69234
}

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
@@ -538,8 +538,11 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
538538
#[derive(Debug, FromArgs)]
539539
#[__inside_vm]
540540
pub struct PrintOptions {
541+
#[pyarg(keyword_only, default = "None")]
541542
sep: Option<PyStringRef>,
543+
#[pyarg(keyword_only, default = "None")]
542544
end: Option<PyStringRef>,
545+
#[pyarg(keyword_only, default = "false")]
543546
flush: bool,
544547
}
545548

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)