Skip to content

Commit 027a684

Browse files
Merge pull request RustPython#525 from RustPython/joey/extractor-pattern
Use "extractor" pattern for native functions
2 parents 9e03b69 + 2919d7f commit 027a684

File tree

5 files changed

+260
-34
lines changed

5 files changed

+260
-34
lines changed

vm/src/obj/objbool.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
use super::objtype;
22
use crate::pyobject::{
3-
PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
3+
IntoPyObject, PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
44
};
55
use crate::vm::VirtualMachine;
66
use num_traits::Zero;
77

8+
impl IntoPyObject for bool {
9+
fn into_pyobject(self, ctx: &PyContext) -> PyResult {
10+
Ok(ctx.new_bool(self))
11+
}
12+
}
13+
814
pub fn boolval(vm: &mut VirtualMachine, obj: PyObjectRef) -> Result<bool, PyObjectRef> {
915
let result = match obj.borrow().payload {
1016
PyObjectPayload::Integer { ref value } => !value.is_zero(),

vm/src/obj/objint.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use super::objstr;
33
use super::objtype;
44
use crate::format::FormatSpec;
55
use crate::pyobject::{
6-
FromPyObjectRef, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult,
7-
TypeProtocol,
6+
FromPyObjectRef, IntoPyObject, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef,
7+
PyResult, TypeProtocol,
88
};
99
use crate::vm::VirtualMachine;
1010
use num_bigint::{BigInt, ToBigInt};
@@ -15,6 +15,22 @@ use std::hash::{Hash, Hasher};
1515
// This proxy allows for easy switching between types.
1616
type IntType = BigInt;
1717

18+
pub type PyInt = BigInt;
19+
20+
impl IntoPyObject for PyInt {
21+
fn into_pyobject(self, ctx: &PyContext) -> PyResult {
22+
Ok(ctx.new_int(self))
23+
}
24+
}
25+
26+
// TODO: macro to impl for all primitive ints
27+
28+
impl IntoPyObject for usize {
29+
fn into_pyobject(self, ctx: &PyContext) -> PyResult {
30+
Ok(ctx.new_int(self))
31+
}
32+
}
33+
1834
fn int_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
1935
arg_check!(vm, args, required = [(int, Some(vm.ctx.int_type()))]);
2036
let v = get_value(int);

vm/src/obj/objrange.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use super::objint;
22
use super::objtype;
33
use crate::pyobject::{
4-
PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
4+
FromPyObject, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult,
5+
TypeProtocol,
56
};
67
use crate::vm::VirtualMachine;
78
use num_bigint::{BigInt, Sign};
@@ -18,6 +19,18 @@ pub struct RangeType {
1819
pub step: BigInt,
1920
}
2021

22+
type PyRange = RangeType;
23+
24+
impl FromPyObject for PyRange {
25+
fn typ(ctx: &PyContext) -> Option<PyObjectRef> {
26+
Some(ctx.range_type())
27+
}
28+
29+
fn from_pyobject(obj: PyObjectRef) -> PyResult<Self> {
30+
Ok(get_value(&obj))
31+
}
32+
}
33+
2134
impl RangeType {
2235
#[inline]
2336
pub fn try_len(&self) -> Option<usize> {
@@ -345,22 +358,12 @@ fn range_bool(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
345358
Ok(vm.ctx.new_bool(len > 0))
346359
}
347360

348-
fn range_contains(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
349-
arg_check!(
350-
vm,
351-
args,
352-
required = [(zelf, Some(vm.ctx.range_type())), (needle, None)]
353-
);
354-
355-
let range = get_value(zelf);
356-
357-
let result = if objtype::isinstance(needle, &vm.ctx.int_type()) {
358-
range.contains(&objint::get_value(needle))
361+
fn range_contains(vm: &mut VirtualMachine, zelf: PyRange, needle: PyObjectRef) -> bool {
362+
if objtype::isinstance(&needle, &vm.ctx.int_type()) {
363+
zelf.contains(&objint::get_value(&needle))
359364
} else {
360365
false
361-
};
362-
363-
Ok(vm.ctx.new_bool(result))
366+
}
364367
}
365368

366369
fn range_index(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {

vm/src/obj/objstr.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use super::objsequence::PySliceableSequence;
33
use super::objtype;
44
use crate::format::{FormatParseError, FormatPart, FormatString};
55
use crate::pyobject::{
6-
PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
6+
FromPyObject, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult,
7+
TypeProtocol,
78
};
89
use crate::vm::VirtualMachine;
910
use num_traits::ToPrimitive;
@@ -17,6 +18,16 @@ extern crate unicode_segmentation;
1718

1819
use self::unicode_segmentation::UnicodeSegmentation;
1920

21+
impl FromPyObject for String {
22+
fn typ(ctx: &PyContext) -> Option<PyObjectRef> {
23+
Some(ctx.str_type())
24+
}
25+
26+
fn from_pyobject(obj: PyObjectRef) -> PyResult<Self> {
27+
Ok(get_value(&obj))
28+
}
29+
}
30+
2031
pub fn init(context: &PyContext) {
2132
let str_type = &context.str_type;
2233
context.set_attr(&str_type, "__add__", context.new_rustfunc(str_add));
@@ -474,15 +485,8 @@ fn str_rstrip(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
474485
Ok(vm.ctx.new_str(value))
475486
}
476487

477-
fn str_endswith(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
478-
arg_check!(
479-
vm,
480-
args,
481-
required = [(s, Some(vm.ctx.str_type())), (pat, Some(vm.ctx.str_type()))]
482-
);
483-
let value = get_value(&s);
484-
let pat = get_value(&pat);
485-
Ok(vm.ctx.new_bool(value.ends_with(pat.as_str())))
488+
fn str_endswith(_vm: &mut VirtualMachine, zelf: String, suffix: String) -> bool {
489+
zelf.ends_with(&suffix)
486490
}
487491

488492
fn str_isidentifier(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {

vm/src/pyobject.rs

Lines changed: 203 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub type PyObjectWeakRef = Weak<RefCell<PyObject>>;
7373
/// Use this type for function which return a python object or and exception.
7474
/// Both the python object and the python exception are `PyObjectRef` types
7575
/// since exceptions are also python objects.
76-
pub type PyResult = Result<PyObjectRef, PyObjectRef>; // A valid value, or an exception
76+
pub type PyResult<T = PyObjectRef> = Result<T, PyObjectRef>; // A valid value, or an exception
7777

7878
/// For attributes we do not use a dict, but a hashmap. This is probably
7979
/// faster, unordered, and only supports strings as keys.
@@ -554,13 +554,13 @@ impl PyContext {
554554
)
555555
}
556556

557-
pub fn new_rustfunc<F: 'static + Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult>(
558-
&self,
559-
function: F,
560-
) -> PyObjectRef {
557+
pub fn new_rustfunc<F, T, R>(&self, factory: F) -> PyObjectRef
558+
where
559+
F: PyNativeFuncFactory<T, R>,
560+
{
561561
PyObject::new(
562562
PyObjectPayload::RustFunction {
563-
function: Box::new(function),
563+
function: factory.create(self),
564564
},
565565
self.builtin_function_or_method_type(),
566566
)
@@ -946,6 +946,203 @@ impl PyFuncArgs {
946946
}
947947
}
948948

949+
pub trait FromPyObject: Sized {
950+
fn typ(ctx: &PyContext) -> Option<PyObjectRef>;
951+
952+
fn from_pyobject(obj: PyObjectRef) -> PyResult<Self>;
953+
}
954+
955+
impl FromPyObject for PyObjectRef {
956+
fn typ(_ctx: &PyContext) -> Option<PyObjectRef> {
957+
None
958+
}
959+
960+
fn from_pyobject(obj: PyObjectRef) -> PyResult<Self> {
961+
Ok(obj)
962+
}
963+
}
964+
965+
pub trait IntoPyObject {
966+
fn into_pyobject(self, ctx: &PyContext) -> PyResult;
967+
}
968+
969+
impl IntoPyObject for PyObjectRef {
970+
fn into_pyobject(self, _ctx: &PyContext) -> PyResult {
971+
Ok(self)
972+
}
973+
}
974+
975+
impl IntoPyObject for PyResult {
976+
fn into_pyobject(self, _ctx: &PyContext) -> PyResult {
977+
self
978+
}
979+
}
980+
981+
pub trait FromPyFuncArgs: Sized {
982+
fn required_params(ctx: &PyContext) -> Vec<Parameter>;
983+
984+
fn from_py_func_args(args: &mut PyFuncArgs) -> PyResult<Self>;
985+
}
986+
987+
macro_rules! tuple_from_py_func_args {
988+
($($T:ident),+) => {
989+
impl<$($T),+> FromPyFuncArgs for ($($T,)+)
990+
where
991+
$($T: FromPyFuncArgs),+
992+
{
993+
fn required_params(ctx: &PyContext) -> Vec<Parameter> {
994+
vec![$($T::required_params(ctx),)+].into_iter().flatten().collect()
995+
}
996+
997+
fn from_py_func_args(args: &mut PyFuncArgs) -> PyResult<Self> {
998+
Ok(($($T::from_py_func_args(args)?,)+))
999+
}
1000+
}
1001+
};
1002+
}
1003+
1004+
tuple_from_py_func_args!(A);
1005+
tuple_from_py_func_args!(A, B);
1006+
tuple_from_py_func_args!(A, B, C);
1007+
tuple_from_py_func_args!(A, B, C, D);
1008+
tuple_from_py_func_args!(A, B, C, D, E);
1009+
1010+
impl<T> FromPyFuncArgs for T
1011+
where
1012+
T: FromPyObject,
1013+
{
1014+
fn required_params(ctx: &PyContext) -> Vec<Parameter> {
1015+
vec![Parameter {
1016+
kind: PositionalOnly,
1017+
typ: T::typ(ctx),
1018+
}]
1019+
}
1020+
1021+
fn from_py_func_args(args: &mut PyFuncArgs) -> PyResult<Self> {
1022+
Self::from_pyobject(args.shift())
1023+
}
1024+
}
1025+
1026+
pub type PyNativeFunc = Box<dyn Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult>;
1027+
1028+
pub trait PyNativeFuncFactory<T, R> {
1029+
fn create(self, ctx: &PyContext) -> PyNativeFunc;
1030+
}
1031+
1032+
impl<F> PyNativeFuncFactory<PyFuncArgs, PyResult> for F
1033+
where
1034+
F: Fn(&mut VirtualMachine, PyFuncArgs) -> PyResult + 'static,
1035+
{
1036+
fn create(self, _ctx: &PyContext) -> PyNativeFunc {
1037+
Box::new(self)
1038+
}
1039+
}
1040+
1041+
macro_rules! tuple_py_native_func_factory {
1042+
($($T:ident),+) => {
1043+
impl<F, $($T,)+ R> PyNativeFuncFactory<($($T,)+), R> for F
1044+
where
1045+
F: Fn(&mut VirtualMachine, $($T),+) -> R + 'static,
1046+
$($T: FromPyFuncArgs,)+
1047+
R: IntoPyObject,
1048+
{
1049+
fn create(self, ctx: &PyContext) -> PyNativeFunc {
1050+
let parameters = vec![$($T::required_params(ctx)),+]
1051+
.into_iter()
1052+
.flatten()
1053+
.collect();
1054+
let signature = Signature::new(parameters);
1055+
1056+
Box::new(move |vm, mut args| {
1057+
signature.check(vm, &mut args)?;
1058+
1059+
(self)(vm, $($T::from_py_func_args(&mut args)?,)+)
1060+
.into_pyobject(&vm.ctx)
1061+
})
1062+
}
1063+
}
1064+
};
1065+
}
1066+
1067+
tuple_py_native_func_factory!(A);
1068+
tuple_py_native_func_factory!(A, B);
1069+
tuple_py_native_func_factory!(A, B, C);
1070+
tuple_py_native_func_factory!(A, B, C, D);
1071+
tuple_py_native_func_factory!(A, B, C, D, E);
1072+
1073+
#[derive(Debug)]
1074+
pub struct Signature {
1075+
positional_params: Vec<Parameter>,
1076+
keyword_params: HashMap<String, Parameter>,
1077+
}
1078+
1079+
impl Signature {
1080+
fn new(params: Vec<Parameter>) -> Self {
1081+
let mut positional_params = Vec::new();
1082+
let mut keyword_params = HashMap::new();
1083+
for param in params {
1084+
match param.kind {
1085+
PositionalOnly => {
1086+
positional_params.push(param);
1087+
}
1088+
KeywordOnly { ref name } => {
1089+
keyword_params.insert(name.clone(), param);
1090+
}
1091+
}
1092+
}
1093+
1094+
Self {
1095+
positional_params,
1096+
keyword_params,
1097+
}
1098+
}
1099+
1100+
fn arg_type(&self, pos: usize) -> Option<&PyObjectRef> {
1101+
self.positional_params[pos].typ.as_ref()
1102+
}
1103+
1104+
#[allow(unused)]
1105+
fn kwarg_type(&self, name: &str) -> Option<&PyObjectRef> {
1106+
self.keyword_params[name].typ.as_ref()
1107+
}
1108+
1109+
fn check(&self, vm: &mut VirtualMachine, args: &PyFuncArgs) -> PyResult<()> {
1110+
// TODO: check arity
1111+
1112+
for (pos, arg) in args.args.iter().enumerate() {
1113+
if let Some(expected_type) = self.arg_type(pos) {
1114+
if !objtype::isinstance(arg, expected_type) {
1115+
let arg_typ = arg.typ();
1116+
let expected_type_name = vm.to_pystr(&expected_type)?;
1117+
let actual_type = vm.to_pystr(&arg_typ)?;
1118+
return Err(vm.new_type_error(format!(
1119+
"argument of type {} is required for parameter {} (got: {})",
1120+
expected_type_name,
1121+
pos + 1,
1122+
actual_type
1123+
)));
1124+
}
1125+
}
1126+
}
1127+
1128+
Ok(())
1129+
}
1130+
}
1131+
1132+
#[derive(Debug)]
1133+
pub struct Parameter {
1134+
typ: Option<PyObjectRef>,
1135+
kind: ParameterKind,
1136+
}
1137+
1138+
#[derive(Debug)]
1139+
pub enum ParameterKind {
1140+
PositionalOnly,
1141+
KeywordOnly { name: String },
1142+
}
1143+
1144+
use self::ParameterKind::*;
1145+
9491146
/// Rather than determining the type of a python object, this enum is more
9501147
/// a holder for the rust payload of a python object. It is more a carrier
9511148
/// of rust data for a particular python object. Determine the python type

0 commit comments

Comments
 (0)