Skip to content

Commit ba300c7

Browse files
committed
Update pow tests to 3.8 version
1 parent 869e4a7 commit ba300c7

File tree

5 files changed

+85
-19
lines changed

5 files changed

+85
-19
lines changed

Lib/test/test_builtin.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import types
1818
import unittest
1919
import warnings
20+
from functools import partial
2021
from contextlib import ExitStack
2122
from operator import neg
2223
from test.support import (
@@ -1143,11 +1144,24 @@ def test_pow(self):
11431144
self.assertAlmostEqual(pow(-1, 0.5), 1j)
11441145
self.assertAlmostEqual(pow(-1, 1/3), 0.5 + 0.8660254037844386j)
11451146

1146-
self.assertRaises(ValueError, pow, -1, -2, 3)
1147+
# See test_pow for additional tests for three-argument pow.
1148+
self.assertEqual(pow(-1, -2, 3), 1)
11471149
self.assertRaises(ValueError, pow, 1, 2, 0)
11481150

11491151
self.assertRaises(TypeError, pow)
11501152

1153+
# Test passing in arguments as keywords.
1154+
self.assertEqual(pow(0, exp=0), 1)
1155+
self.assertEqual(pow(base=2, exp=4), 16)
1156+
self.assertEqual(pow(base=5, exp=2, mod=14), 11)
1157+
twopow = partial(pow, base=2)
1158+
self.assertEqual(twopow(exp=5), 32)
1159+
fifth_power = partial(pow, exp=5)
1160+
self.assertEqual(fifth_power(2), 32)
1161+
mod10 = partial(pow, mod=10)
1162+
self.assertEqual(mod10(2, 6), 4)
1163+
self.assertEqual(mod10(exp=6, base=2), 4)
1164+
11511165
def test_input(self):
11521166
self.write_testfile()
11531167
fp = open(TESTFN, 'r')

Lib/test/test_pow.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
import unittest
23

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

123+
def test_negative_exponent(self):
124+
for a in range(-50, 50):
125+
for m in range(-50, 50):
126+
with self.subTest(a=a, m=m):
127+
if m != 0 and math.gcd(a, m) == 1:
128+
# Exponent -1 should give an inverse, with the
129+
# same sign as m.
130+
inv = pow(a, -1, m)
131+
self.assertEqual(inv, inv % m)
132+
self.assertEqual((inv * a - 1) % m, 0)
133+
134+
# Larger exponents
135+
self.assertEqual(pow(a, -2, m), pow(inv, 2, m))
136+
self.assertEqual(pow(a, -3, m), pow(inv, 3, m))
137+
self.assertEqual(pow(a, -1001, m), pow(inv, 1001, m))
138+
139+
else:
140+
with self.assertRaises(ValueError):
141+
pow(a, -1, m)
142+
with self.assertRaises(ValueError):
143+
pow(a, -2, m)
144+
with self.assertRaises(ValueError):
145+
pow(a, -1001, m)
146+
147+
122148
if __name__ == "__main__":
123149
unittest.main()

derive/src/from_args.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ impl ParameterKind {
2828
}
2929

3030
struct ArgAttribute {
31+
name: Option<String>,
3132
kind: ParameterKind,
3233
default: Option<DefaultValue>,
3334
}
@@ -60,6 +61,7 @@ impl ArgAttribute {
6061
})?;
6162

6263
let mut attribute = ArgAttribute {
64+
name: None,
6365
kind,
6466
default: None,
6567
};
@@ -99,6 +101,15 @@ impl ArgAttribute {
99101
Lit::Str(ref val) => self.default = Some(Some(val.parse()?)),
100102
_ => bail_span!(name_value, "Expected string value for default argument"),
101103
}
104+
} else if path_eq(&name_value.path, "name") {
105+
if self.name.is_some() {
106+
bail_span!(name_value, "already have a name")
107+
}
108+
109+
match &name_value.lit {
110+
Lit::Str(val) => self.name = Some(val.value()),
111+
_ => bail_span!(name_value, "Expected string value for name argument"),
112+
}
102113
} else {
103114
bail_span!(name_value, "Unrecognised pyarg attribute");
104115
}
@@ -118,6 +129,7 @@ fn generate_field(field: &Field) -> Result<TokenStream2, Diagnostic> {
118129
.collect::<Result<Vec<_>, _>>()?;
119130
let attr = if pyarg_attrs.is_empty() {
120131
ArgAttribute {
132+
name: None,
121133
kind: ParameterKind::PositionalOrKeyword,
122134
default: None,
123135
}
@@ -127,19 +139,19 @@ fn generate_field(field: &Field) -> Result<TokenStream2, Diagnostic> {
127139
bail_span!(field, "Multiple pyarg attributes on field");
128140
};
129141

130-
let name = &field.ident;
131-
if let Some(name) = name {
132-
if name.to_string().starts_with("_phantom") {
133-
return Ok(quote! {
134-
#name: ::std::marker::PhantomData,
135-
});
136-
}
142+
let name = field.ident.as_ref().unwrap();
143+
let namestring = name.to_string();
144+
if namestring.starts_with("_phantom") {
145+
return Ok(quote! {
146+
#name: ::std::marker::PhantomData,
147+
});
137148
}
138149
if let ParameterKind::Flatten = attr.kind {
139150
return Ok(quote! {
140151
#name: ::rustpython_vm::function::FromArgs::from_args(vm, args)?,
141152
});
142153
}
154+
let pyname = attr.name.unwrap_or(namestring);
143155
let middle = quote! {
144156
.map(|x| ::rustpython_vm::pyobject::TryFromObject::try_from_object(vm, x)).transpose()?
145157
};
@@ -155,7 +167,7 @@ fn generate_field(field: &Field) -> Result<TokenStream2, Diagnostic> {
155167
::rustpython_vm::function::ArgumentError::TooFewArgs
156168
},
157169
ParameterKind::KeywordOnly => quote! {
158-
::rustpython_vm::function::ArgumentError::RequiredKeywordArgument(stringify!(#name))
170+
::rustpython_vm::function::ArgumentError::RequiredKeywordArgument(#pyname)
159171
},
160172
ParameterKind::Flatten => unreachable!(),
161173
};
@@ -172,12 +184,12 @@ fn generate_field(field: &Field) -> Result<TokenStream2, Diagnostic> {
172184
}
173185
ParameterKind::PositionalOrKeyword => {
174186
quote! {
175-
#name: args.take_positional_keyword(stringify!(#name))#middle#ending,
187+
#name: args.take_positional_keyword(#pyname)#middle#ending,
176188
}
177189
}
178190
ParameterKind::KeywordOnly => {
179191
quote! {
180-
#name: args.take_keyword(stringify!(#name))#middle#ending,
192+
#name: args.take_keyword(#pyname)#middle#ending,
181193
}
182194
}
183195
ParameterKind::Flatten => unreachable!(),

vm/src/builtins/int.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,11 @@ impl PyInt {
465465
None
466466
}
467467
}
468-
let a = inverse(a % modulus, modulus).unwrap();
468+
let a = inverse(a % modulus, modulus).ok_or_else(|| {
469+
vm.new_value_error(
470+
"base is not invertible for the given modulus".to_owned(),
471+
)
472+
})?;
469473
let b = -b;
470474
a.modpow(&b, modulus)
471475
} else {

vm/src/builtins/make_module.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -594,15 +594,25 @@ mod decl {
594594
}
595595
}
596596

597+
#[derive(FromArgs)]
598+
struct PowArgs {
599+
#[pyarg(any)]
600+
base: PyObjectRef,
601+
#[pyarg(any)]
602+
exp: PyObjectRef,
603+
#[pyarg(any, optional, name = "mod")]
604+
modulus: Option<PyObjectRef>,
605+
}
606+
597607
#[allow(clippy::suspicious_else_formatting)]
598608
#[pyfunction]
599-
fn pow(
600-
x: PyObjectRef,
601-
y: PyObjectRef,
602-
mod_value: OptionalOption<PyObjectRef>,
603-
vm: &VirtualMachine,
604-
) -> PyResult {
605-
match mod_value.flatten() {
609+
fn pow(args: PowArgs, vm: &VirtualMachine) -> PyResult {
610+
let PowArgs {
611+
base: x,
612+
exp: y,
613+
modulus,
614+
} = args;
615+
match modulus {
606616
None => vm.call_or_reflection(&x, &y, "__pow__", "__rpow__", |vm, x, y| {
607617
Err(vm.new_unsupported_binop_error(x, y, "pow"))
608618
}),

0 commit comments

Comments
 (0)