Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Lib/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,14 @@ def joinuser(*args):
def _get_path(userbase):
version = sys.version_info

# XXX RUSTPYTHON: we replace pythonx.y with rustpythonx.y
if os.name == 'nt':
return f'{userbase}\\Python{version[0]}{version[1]}\\site-packages'
return f'{userbase}\\RustPython{version[0]}{version[1]}\\site-packages'

if sys.platform == 'darwin' and sys._framework:
return f'{userbase}/lib/python/site-packages'
return f'{userbase}/lib/rustpython/site-packages'

return f'{userbase}/lib/python{version[0]}.{version[1]}/site-packages'
return f'{userbase}/lib/rustpython{version[0]}.{version[1]}/site-packages'


def getuserbase():
Expand Down
6 changes: 6 additions & 0 deletions Lib/sysconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@
},
}

# XXX RUSTPYTHON: replace python with rustpython in all these paths
for group in _INSTALL_SCHEMES.values():
for key in group.keys():
group[key] = group[key].replace("Python", "RustPython").replace("python", "rustpython")


_SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include',
'scripts', 'data')

Expand Down
16 changes: 15 additions & 1 deletion Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import types
import unittest
import warnings
from functools import partial
from contextlib import ExitStack
from operator import neg
from test.support import (
Expand Down Expand Up @@ -1143,11 +1144,24 @@ def test_pow(self):
self.assertAlmostEqual(pow(-1, 0.5), 1j)
self.assertAlmostEqual(pow(-1, 1/3), 0.5 + 0.8660254037844386j)

self.assertRaises(ValueError, pow, -1, -2, 3)
# See test_pow for additional tests for three-argument pow.
self.assertEqual(pow(-1, -2, 3), 1)
self.assertRaises(ValueError, pow, 1, 2, 0)

self.assertRaises(TypeError, pow)

# Test passing in arguments as keywords.
self.assertEqual(pow(0, exp=0), 1)
self.assertEqual(pow(base=2, exp=4), 16)
self.assertEqual(pow(base=5, exp=2, mod=14), 11)
twopow = partial(pow, base=2)
self.assertEqual(twopow(exp=5), 32)
fifth_power = partial(pow, exp=5)
self.assertEqual(fifth_power(2), 32)
mod10 = partial(pow, mod=10)
self.assertEqual(mod10(2, 6), 4)
self.assertEqual(mod10(exp=6, base=2), 4)

def test_input(self):
self.write_testfile()
fp = open(TESTFN, 'r')
Expand Down
26 changes: 26 additions & 0 deletions Lib/test/test_pow.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import math
import unittest

class PowTest(unittest.TestCase):
Expand Down Expand Up @@ -119,5 +120,30 @@ def test_bug705231(self):
eq(pow(a, -fiveto), expected)
eq(expected, 1.0) # else we didn't push fiveto to evenness

def test_negative_exponent(self):
for a in range(-50, 50):
for m in range(-50, 50):
with self.subTest(a=a, m=m):
if m != 0 and math.gcd(a, m) == 1:
# Exponent -1 should give an inverse, with the
# same sign as m.
inv = pow(a, -1, m)
self.assertEqual(inv, inv % m)
self.assertEqual((inv * a - 1) % m, 0)

# Larger exponents
self.assertEqual(pow(a, -2, m), pow(inv, 2, m))
self.assertEqual(pow(a, -3, m), pow(inv, 3, m))
self.assertEqual(pow(a, -1001, m), pow(inv, 1001, m))

else:
with self.assertRaises(ValueError):
pow(a, -1, m)
with self.assertRaises(ValueError):
pow(a, -2, m)
with self.assertRaises(ValueError):
pow(a, -1001, m)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# [RustPython](https://rustpython.github.io/)

A Python-3 (CPython >= 3.5.0) Interpreter written in Rust :snake: :scream:
A Python-3 (CPython >= 3.8.0) Interpreter written in Rust :snake: :scream:
:metal:.

[![Build Status](https://github.com/RustPython/RustPython/workflows/CI/badge.svg)](https://github.com/RustPython/RustPython/actions?query=workflow%3ACI)
Expand Down
32 changes: 22 additions & 10 deletions derive/src/from_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl ParameterKind {
}

struct ArgAttribute {
name: Option<String>,
kind: ParameterKind,
default: Option<DefaultValue>,
}
Expand Down Expand Up @@ -60,6 +61,7 @@ impl ArgAttribute {
})?;

let mut attribute = ArgAttribute {
name: None,
kind,
default: None,
};
Expand Down Expand Up @@ -99,6 +101,15 @@ impl ArgAttribute {
Lit::Str(ref val) => self.default = Some(Some(val.parse()?)),
_ => bail_span!(name_value, "Expected string value for default argument"),
}
} else if path_eq(&name_value.path, "name") {
if self.name.is_some() {
bail_span!(name_value, "already have a name")
}

match &name_value.lit {
Lit::Str(val) => self.name = Some(val.value()),
_ => bail_span!(name_value, "Expected string value for name argument"),
}
} else {
bail_span!(name_value, "Unrecognised pyarg attribute");
}
Expand All @@ -118,6 +129,7 @@ fn generate_field(field: &Field) -> Result<TokenStream2, Diagnostic> {
.collect::<Result<Vec<_>, _>>()?;
let attr = if pyarg_attrs.is_empty() {
ArgAttribute {
name: None,
kind: ParameterKind::PositionalOrKeyword,
default: None,
}
Expand All @@ -127,19 +139,19 @@ fn generate_field(field: &Field) -> Result<TokenStream2, Diagnostic> {
bail_span!(field, "Multiple pyarg attributes on field");
};

let name = &field.ident;
if let Some(name) = name {
if name.to_string().starts_with("_phantom") {
return Ok(quote! {
#name: ::std::marker::PhantomData,
});
}
let name = field.ident.as_ref().unwrap();
let namestring = name.to_string();
if namestring.starts_with("_phantom") {
return Ok(quote! {
#name: ::std::marker::PhantomData,
});
}
if let ParameterKind::Flatten = attr.kind {
return Ok(quote! {
#name: ::rustpython_vm::function::FromArgs::from_args(vm, args)?,
});
}
let pyname = attr.name.unwrap_or(namestring);
let middle = quote! {
.map(|x| ::rustpython_vm::pyobject::TryFromObject::try_from_object(vm, x)).transpose()?
};
Expand All @@ -155,7 +167,7 @@ fn generate_field(field: &Field) -> Result<TokenStream2, Diagnostic> {
::rustpython_vm::function::ArgumentError::TooFewArgs
},
ParameterKind::KeywordOnly => quote! {
::rustpython_vm::function::ArgumentError::RequiredKeywordArgument(stringify!(#name))
::rustpython_vm::function::ArgumentError::RequiredKeywordArgument(#pyname)
},
ParameterKind::Flatten => unreachable!(),
};
Expand All @@ -172,12 +184,12 @@ fn generate_field(field: &Field) -> Result<TokenStream2, Diagnostic> {
}
ParameterKind::PositionalOrKeyword => {
quote! {
#name: args.take_positional_keyword(stringify!(#name))#middle#ending,
#name: args.take_positional_keyword(#pyname)#middle#ending,
}
}
ParameterKind::KeywordOnly => {
quote! {
#name: args.take_keyword(stringify!(#name))#middle#ending,
#name: args.take_keyword(#pyname)#middle#ending,
}
}
ParameterKind::Flatten => unreachable!(),
Expand Down
6 changes: 1 addition & 5 deletions extra_tests/snippets/math_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,7 @@
assert_raises(TypeError, pow, 2, 4, 5.0)
assert_raises(TypeError, pow, 2, 4.0, 5)
assert_raises(TypeError, pow, 2.0, 4, 5)
from sys import version_info
if version_info < (3, 8):
assert_raises(ValueError, pow, 2, -1, 5)
else: # https://docs.python.org/3/whatsnew/3.8.html#other-language-changes
assert pow(2, -1, 5) == 3
assert pow(2, -1, 5) == 3
assert_raises(ValueError, pow, 2, 2, 0)

# bitwise
Expand Down
34 changes: 30 additions & 4 deletions vm/src/builtins/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,11 +445,37 @@ impl PyInt {
self.general_op(
other,
|a, b| {
if b.is_negative() {
Err(vm.new_value_error("modular inverses not supported".to_owned()))
let i = if b.is_negative() {
// modular multiplicative inverse
// based on rust-num/num-integer#10, should hopefully be published soon
fn normalize(a: BigInt, n: &BigInt) -> BigInt {
let a = a % n;
if a.is_negative() {
a + n
} else {
a
}
}
fn inverse(a: BigInt, n: &BigInt) -> Option<BigInt> {
use num_integer::*;
let ExtendedGcd { gcd, x: c, .. } = a.extended_gcd(n);
if gcd.is_one() {
Some(normalize(c, n))
} else {
None
}
}
let a = inverse(a % modulus, modulus).ok_or_else(|| {
vm.new_value_error(
"base is not invertible for the given modulus".to_owned(),
)
})?;
let b = -b;
a.modpow(&b, modulus)
} else {
Ok(vm.ctx.new_int(a.modpow(b, modulus)))
}
a.modpow(b, modulus)
};
Ok(vm.ctx.new_int(i))
},
vm,
)
Expand Down
24 changes: 17 additions & 7 deletions vm/src/builtins/make_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,15 +594,25 @@ mod decl {
}
}

#[derive(FromArgs)]
struct PowArgs {
#[pyarg(any)]
base: PyObjectRef,
#[pyarg(any)]
exp: PyObjectRef,
#[pyarg(any, optional, name = "mod")]
modulus: Option<PyObjectRef>,
}

#[allow(clippy::suspicious_else_formatting)]
#[pyfunction]
fn pow(
x: PyObjectRef,
y: PyObjectRef,
mod_value: OptionalOption<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult {
match mod_value.flatten() {
fn pow(args: PowArgs, vm: &VirtualMachine) -> PyResult {
let PowArgs {
base: x,
exp: y,
modulus,
} = args;
match modulus {
None => vm.call_or_reflection(&x, &y, "__pow__", "__rpow__", |vm, x, y| {
Err(vm.new_unsupported_binop_error(x, y, "pow"))
}),
Expand Down
11 changes: 9 additions & 2 deletions vm/src/stdlib/sysconfigdata.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
use crate::pyobject::{ItemProtocol, PyObjectRef};
use crate::VirtualMachine;

use crate::sysmodule::MULTIARCH;

pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
let vars = vm.ctx.new_dict();
macro_rules! hashmap {
($($key:literal => $value:literal),*) => {{
$(vars.set_item($key, vm.ctx.new_str($value.to_owned()), vm).unwrap();)*
($($key:literal => $value:expr),*$(,)?) => {{
$(vars.set_item($key, vm.ctx.new_str($value), vm).unwrap();)*
}};
}
hashmap! {
// fake shared module extension
"EXT_SUFFIX" => format!(".rustpython-{}", MULTIARCH),
"MULTIARCH" => MULTIARCH,
}
include!(concat!(env!("OUT_DIR"), "/env_vars.rs"));

py_module!(vm, "_sysconfigdata", {
Expand Down
2 changes: 1 addition & 1 deletion vm/src/sysmodule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ const ABIFLAGS: &str = "";
// not the same as CPython (e.g. rust's x86_x64-unknown-linux-gnu is just x86_64-linux-gnu)
// but hopefully that's just an implementation detail? TODO: copy CPython's multiarch exactly,
// https://github.com/python/cpython/blob/3.8/configure.ac#L725
const MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE");
pub(crate) const MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE");

pub(crate) fn sysconfigdata_name() -> String {
format!("_sysconfigdata_{}_{}_{}", ABIFLAGS, PLATFORM, MULTIARCH)
Expand Down
2 changes: 1 addition & 1 deletion vm/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use chrono::Local;
use std::time::{Duration, UNIX_EPOCH};

const MAJOR: usize = 3;
const MINOR: usize = 5;
const MINOR: usize = 9;
const MICRO: usize = 0;
const RELEASELEVEL: &str = "alpha";
const RELEASELEVEL_N: usize = 0xA;
Expand Down
2 changes: 1 addition & 1 deletion wasm/lib/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# RustPython

A Python-3 (CPython >= 3.5.0) Interpreter written in Rust.
A Python-3 (CPython >= 3.8.0) Interpreter written in Rust.

[![Build Status](https://travis-ci.org/RustPython/RustPython.svg?branch=master)](https://travis-ci.org/RustPython/RustPython)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
Expand Down