Skip to content

Commit 83c1d4f

Browse files
Merge remote-tracking branch 'origin/master' into joey/convert-bytes
2 parents e0aca86 + c5c9181 commit 83c1d4f

File tree

16 files changed

+901
-787
lines changed

16 files changed

+901
-787
lines changed

Cargo.lock

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

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/builtin_format.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@
77
assert format({}) == "{}"
88

99
assert_raises(TypeError, lambda: format({}, 'b'), 'format_spec not empty for dict')
10+
11+
class BadFormat:
12+
def __format__(self, spec):
13+
return 42
14+
assert_raises(TypeError, lambda: format(BadFormat()))

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)

0 commit comments

Comments
 (0)