code_richcompare() now uses the constants types

Issue #25843: When compiling code, don't merge constants if they are equal but
have a different types. For example, "f1, f2 = lambda: 1, lambda: 1.0" is now
correctly compiled to two different functions: f1() returns 1 (int) and f2()
returns 1.0 (int), even if 1 and 1.0 are equal.

Add a new _PyCode_ConstantKey() private function.
This commit is contained in:
Victor Stinner 2016-01-22 12:33:12 +01:00
parent d52513cb22
commit 3cdd5fb970
5 changed files with 245 additions and 49 deletions

View file

@ -572,6 +572,88 @@ if 1:
exec(memoryview(b"ax = 123")[1:-1], namespace)
self.assertEqual(namespace['x'], 12)
def check_constant(self, func, expected):
for const in func.__code__.co_consts:
if repr(const) == repr(expected):
break
else:
self.fail("unable to find constant %r in %r"
% (expected, func.__code__.co_consts))
# Merging equal constants is not a strict requirement for the Python
# semantics, it's a more an implementation detail.
@support.cpython_only
def test_merge_constants(self):
# Issue #25843: compile() must merge constants which are equal
# and have the same type.
def check_same_constant(const):
ns = {}
code = "f1, f2 = lambda: %r, lambda: %r" % (const, const)
exec(code, ns)
f1 = ns['f1']
f2 = ns['f2']
self.assertIs(f1.__code__, f2.__code__)
self.check_constant(f1, const)
self.assertEqual(repr(f1()), repr(const))
check_same_constant(None)
check_same_constant(0)
check_same_constant(0.0)
check_same_constant(b'abc')
check_same_constant('abc')
# Note: "lambda: ..." emits "LOAD_CONST Ellipsis",
# whereas "lambda: Ellipsis" emits "LOAD_GLOBAL Ellipsis"
f1, f2 = lambda: ..., lambda: ...
self.assertIs(f1.__code__, f2.__code__)
self.check_constant(f1, Ellipsis)
self.assertEqual(repr(f1()), repr(Ellipsis))
# {0} is converted to a constant frozenset({0}) by the peephole
# optimizer
f1, f2 = lambda x: x in {0}, lambda x: x in {0}
self.assertIs(f1.__code__, f2.__code__)
self.check_constant(f1, frozenset({0}))
self.assertTrue(f1(0))
def test_dont_merge_constants(self):
# Issue #25843: compile() must not merge constants which are equal
# but have a different type.
def check_different_constants(const1, const2):
ns = {}
exec("f1, f2 = lambda: %r, lambda: %r" % (const1, const2), ns)
f1 = ns['f1']
f2 = ns['f2']
self.assertIsNot(f1.__code__, f2.__code__)
self.check_constant(f1, const1)
self.check_constant(f2, const2)
self.assertEqual(repr(f1()), repr(const1))
self.assertEqual(repr(f2()), repr(const2))
check_different_constants(0, 0.0)
check_different_constants(+0.0, -0.0)
check_different_constants((0,), (0.0,))
# check_different_constants() cannot be used because repr(-0j) is
# '(-0-0j)', but when '(-0-0j)' is evaluated to 0j: we loose the sign.
f1, f2 = lambda: +0.0j, lambda: -0.0j
self.assertIsNot(f1.__code__, f2.__code__)
self.check_constant(f1, +0.0j)
self.check_constant(f2, -0.0j)
self.assertEqual(repr(f1()), repr(+0.0j))
self.assertEqual(repr(f2()), repr(-0.0j))
# {0} is converted to a constant frozenset({0}) by the peephole
# optimizer
f1, f2 = lambda x: x in {0}, lambda x: x in {0.0}
self.assertIsNot(f1.__code__, f2.__code__)
self.check_constant(f1, frozenset({0}))
self.check_constant(f2, frozenset({0.0}))
self.assertTrue(f1(0))
self.assertTrue(f2(0.0))
class TestStackSize(unittest.TestCase):
# These tests check that the computed stack size for a code object