diff --git a/Lib/platform.py b/Lib/platform.py index 33d153f63..d8c387e24 100644 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -29,7 +29,7 @@ # # History: # -# +# # # 1.0.9 - added invalidate_caches() function to invalidate cached values # 1.0.8 - changed Windows support to read version from kernel32.dll @@ -110,8 +110,6 @@ __copyright__ = """ """ -__version__ = '1.0.9' - import collections import os import re @@ -129,7 +127,7 @@ except ImportError: # Based on the description of the PHP's version_compare(): # http://php.net/manual/en/function.version-compare.php -_ver_stages = { +_ver_stages = frozendict({ # any string not found in this dict, will get 0 assigned 'dev': 10, 'alpha': 20, 'a': 20, @@ -138,7 +136,7 @@ _ver_stages = { 'RC': 50, 'rc': 50, # number, will get 100 assigned 'pl': 200, 'p': 200, -} +}) def _comparable_version(version): @@ -307,8 +305,7 @@ def _syscmd_ver(system='', release='', version='', text=True, encoding="locale", shell=True) - except (OSError, subprocess.CalledProcessError) as why: - #print('Command %s failed: %s' % (cmd, why)) + except (OSError, subprocess.CalledProcessError): continue else: break @@ -539,53 +536,6 @@ def ios_ver(system="", release="", model="", is_simulator=False): return IOSVersionInfo(system, release, model, is_simulator) -def _java_getprop(name, default): - """This private helper is deprecated in 3.13 and will be removed in 3.15""" - from java.lang import System - try: - value = System.getProperty(name) - if value is None: - return default - return value - except AttributeError: - return default - -def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')): - - """ Version interface for Jython. - - Returns a tuple (release, vendor, vminfo, osinfo) with vminfo being - a tuple (vm_name, vm_release, vm_vendor) and osinfo being a - tuple (os_name, os_version, os_arch). - - Values which cannot be determined are set to the defaults - given as parameters (which all default to ''). - - """ - import warnings - warnings._deprecated('java_ver', remove=(3, 15)) - # Import the needed APIs - try: - import java.lang # noqa: F401 - except ImportError: - return release, vendor, vminfo, osinfo - - vendor = _java_getprop('java.vendor', vendor) - release = _java_getprop('java.version', release) - vm_name, vm_release, vm_vendor = vminfo - vm_name = _java_getprop('java.vm.name', vm_name) - vm_vendor = _java_getprop('java.vm.vendor', vm_vendor) - vm_release = _java_getprop('java.vm.version', vm_release) - vminfo = vm_name, vm_release, vm_vendor - os_name, os_version, os_arch = osinfo - os_arch = _java_getprop('java.os.arch', os_arch) - os_name = _java_getprop('java.os.name', os_name) - os_version = _java_getprop('java.os.version', os_version) - osinfo = os_name, os_version, os_arch - - return release, vendor, vminfo, osinfo - - AndroidVer = collections.namedtuple( "AndroidVer", "release api_level manufacturer model device is_emulator") @@ -670,6 +620,9 @@ def system_alias(system, release, version): ### Various internal helpers +# Table for cleaning up characters in filenames. +_SIMPLE_SUBSTITUTIONS = str.maketrans(r' /\:;"()', r'_-------') + def _platform(*args): """ Helper to format the platform string in a filename @@ -679,28 +632,13 @@ def _platform(*args): platform = '-'.join(x.strip() for x in filter(len, args)) # Cleanup some possible filename obstacles... - platform = platform.replace(' ', '_') - platform = platform.replace('/', '-') - platform = platform.replace('\\', '-') - platform = platform.replace(':', '-') - platform = platform.replace(';', '-') - platform = platform.replace('"', '-') - platform = platform.replace('(', '-') - platform = platform.replace(')', '-') + platform = platform.translate(_SIMPLE_SUBSTITUTIONS) # No need to report 'unknown' information... platform = platform.replace('unknown', '') # Fold '--'s and remove trailing '-' - while True: - cleaned = platform.replace('--', '-') - if cleaned == platform: - break - platform = cleaned - while platform and platform[-1] == '-': - platform = platform[:-1] - - return platform + return re.sub(r'-{2,}', '-', platform).rstrip('-') def _node(default=''): @@ -767,11 +705,11 @@ def _syscmd_file(target, default=''): # Default values for architecture; non-empty strings override the # defaults given as parameters -_default_architecture = { +_default_architecture = frozendict({ 'win32': ('', 'WindowsPE'), 'win16': ('', 'Windows'), 'dos': ('', 'MSDOS'), -} +}) def architecture(executable=sys.executable, bits='', linkage=''): @@ -1045,13 +983,6 @@ def uname(): version = '16bit' system = 'Windows' - elif system[:4] == 'java': - release, vendor, vminfo, osinfo = java_ver() - system = 'Java' - version = ', '.join(vminfo) - if not version: - version = vendor - # System specific extensions if system == 'OpenVMS': # OpenVMS seems to have release and version mixed up @@ -1386,15 +1317,6 @@ def platform(aliased=False, terse=False): platform = _platform(system, release, machine, processor, 'with', libcname+libcversion) - elif system == 'Java': - # Java platforms - r, v, vminfo, (os_name, os_version, os_arch) = java_ver() - if terse or not os_name: - platform = _platform(system, release, version) - else: - platform = _platform(system, release, version, - 'on', - os_name, os_version, os_arch) else: # Generic handler @@ -1483,7 +1405,7 @@ def invalidate_caches(): def _parse_args(args: list[str] | None): import argparse - parser = argparse.ArgumentParser(color=True) + parser = argparse.ArgumentParser() parser.add_argument("args", nargs="*", choices=["nonaliased", "terse"]) parser.add_argument( "--terse", @@ -1516,5 +1438,14 @@ def _main(args: list[str] | None = None): print(platform(aliased, terse)) +def __getattr__(name): + if name == "__version__": + from warnings import _deprecated + + _deprecated("__version__", remove=(3, 20)) + return "1.1.0" # Do not change + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + if __name__ == "__main__": _main() diff --git a/Lib/test/test_codeccallbacks.py b/Lib/test/test_codeccallbacks.py index f6f45b230..b456fcf65 100644 --- a/Lib/test/test_codeccallbacks.py +++ b/Lib/test/test_codeccallbacks.py @@ -1,3 +1,4 @@ +from _codecs import _unregister_error as _codecs_unregister_error import codecs import html.entities import itertools @@ -754,8 +755,7 @@ class CodecCallbackTest(unittest.TestCase): (s[:1], 1 + n) ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_badhandlerresults(self): results = ( 42, "foo", (1,2,3), ("foo", 1, 3), ("foo", None), ("foo",), ("foo", 1, 3), ("foo", None), ("foo",) ) encs = ("ascii", "latin-1", "iso-8859-1", "iso-8859-15") @@ -798,8 +798,7 @@ class CodecCallbackTest(unittest.TestCase): codecs.lookup_error("namereplace") ) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_encode_nonascii_replacement(self): def handle(exc): if isinstance(exc, UnicodeEncodeError): @@ -1041,8 +1040,7 @@ class CodecCallbackTest(unittest.TestCase): self.assertRaises(ValueError, codecs.charmap_encode, "\xff", err, D()) self.assertRaises(TypeError, codecs.charmap_encode, "\xff", err, {0xff: 300}) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_decodehelper_bug36819(self): handler = RepeatedPosReturn("x") codecs.register_error("test.bug36819", handler.handle) @@ -1130,7 +1128,7 @@ class CodecCallbackTest(unittest.TestCase): text = 'abcghi'*n text.translate(charmap) - def test_mutatingdecodehandler(self): + def test_mutating_decode_handler(self): baddata = [ ("ascii", b"\xff"), ("utf-7", b"++"), @@ -1165,6 +1163,42 @@ class CodecCallbackTest(unittest.TestCase): for (encoding, data) in baddata: self.assertEqual(data.decode(encoding, "test.mutating"), "\u4242") + def test_mutating_decode_handler_unicode_escape(self): + decode = codecs.unicode_escape_decode + def mutating(exc): + if isinstance(exc, UnicodeDecodeError): + r = data.get(exc.object[:exc.end]) + if r is not None: + exc.object = r[0] + exc.object[exc.end:] + return ('\u0404', r[1]) + raise AssertionError("don't know how to handle %r" % exc) + + codecs.register_error('test.mutating2', mutating) + data = { + br'\x0': (b'\\', 0), + br'\x3': (b'xxx\\', 3), + br'\x5': (b'x\\', 1), + } + def check(input, expected, msg): + with self.assertWarns(DeprecationWarning) as cm: + self.assertEqual(decode(input, 'test.mutating2'), (expected, len(input))) + self.assertIn(msg, str(cm.warning)) + + check(br'\x0n\z', '\u0404\n\\z', r'"\z" is an invalid escape sequence') + check(br'\x0n\501', '\u0404\n\u0141', r'"\501" is an invalid octal escape sequence') + check(br'\x0z', '\u0404\\z', r'"\z" is an invalid escape sequence') + + check(br'\x3n\zr', '\u0404\n\\zr', r'"\z" is an invalid escape sequence') + check(br'\x3zr', '\u0404\\zr', r'"\z" is an invalid escape sequence') + check(br'\x3z5', '\u0404\\z5', r'"\z" is an invalid escape sequence') + check(memoryview(br'\x3z5x')[:-1], '\u0404\\z5', r'"\z" is an invalid escape sequence') + check(memoryview(br'\x3z5xy')[:-2], '\u0404\\z5', r'"\z" is an invalid escape sequence') + + check(br'\x5n\z', '\u0404\n\\z', r'"\z" is an invalid escape sequence') + check(br'\x5n\501', '\u0404\n\u0141', r'"\501" is an invalid octal escape sequence') + check(br'\x5z', '\u0404\\z', r'"\z" is an invalid escape sequence') + check(memoryview(br'\x5zy')[:-1], '\u0404\\z', r'"\z" is an invalid escape sequence') + # issue32583 def test_crashing_decode_handler(self): # better generating one more character to fill the extra space slot @@ -1216,7 +1250,6 @@ class CodecCallbackTest(unittest.TestCase): '\ufffd\x00\x00' ) - def test_fake_error_class(self): handlers = [ codecs.strict_errors, @@ -1241,6 +1274,31 @@ class CodecCallbackTest(unittest.TestCase): with self.assertRaises((TypeError, FakeUnicodeError)): handler(FakeUnicodeError()) + def test_reject_unregister_builtin_error_handler(self): + for name in [ + 'strict', 'ignore', 'replace', 'backslashreplace', 'namereplace', + 'xmlcharrefreplace', 'surrogateescape', 'surrogatepass', + ]: + with self.subTest(name): + self.assertRaises(ValueError, _codecs_unregister_error, name) + + def test_unregister_custom_error_handler(self): + def custom_handler(exc): + raise exc + + custom_name = 'test.test_unregister_custom_error_handler' + self.assertRaises(LookupError, codecs.lookup_error, custom_name) + codecs.register_error(custom_name, custom_handler) + self.assertIs(codecs.lookup_error(custom_name), custom_handler) + self.assertTrue(_codecs_unregister_error(custom_name)) + self.assertRaises(LookupError, codecs.lookup_error, custom_name) + + def test_unregister_custom_unknown_error_handler(self): + unknown_name = 'test.test_unregister_custom_unknown_error_handler' + self.assertRaises(LookupError, codecs.lookup_error, unknown_name) + self.assertFalse(_codecs_unregister_error(unknown_name)) + self.assertRaises(LookupError, codecs.lookup_error, unknown_name) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_exception_group.py b/Lib/test/test_exception_group.py index db6ff9d33..325b1c91f 100644 --- a/Lib/test/test_exception_group.py +++ b/Lib/test/test_exception_group.py @@ -1,7 +1,9 @@ import collections import types import unittest -from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow, exceeds_recursion_limit +from test.support import (skip_emscripten_stack_overflow, + skip_wasi_stack_overflow, skip_if_huge_c_stack, + exceeds_recursion_limit) class TestExceptionGroupTypeHierarchy(unittest.TestCase): def test_exception_group_types(self): @@ -234,6 +236,18 @@ class StrAndReprTests(unittest.TestCase): "ExceptionGroup('test', deque([ValueError(1), TypeError(2)]))" ) + def test_repr_small_size_args(self): + eg = ExceptionGroup("msg", [ValueError()]) + eg.args = () + # repr of the ExceptionGroup with empty args should not crash + self.assertEqual(repr(eg), "ExceptionGroup('msg', (ValueError(),))") + + eg.args = (1,) + # repr of the ExceptionGroup with 1-size args should not crash + self.assertEqual(repr(eg), "ExceptionGroup('msg', (ValueError(),))") + + + def test_repr_raises(self): class MySeq(collections.abc.Sequence): def __init__(self, raises): @@ -255,7 +269,7 @@ class StrAndReprTests(unittest.TestCase): seq = MySeq(None) with self.assertRaisesRegex( TypeError, - r"__repr__ returned non-string \(type NoneType\)" + r".*MySeq\.__repr__\(\) must return a str, not NoneType" ): ExceptionGroup("test", seq) @@ -535,6 +549,7 @@ class DeepRecursionInSplitAndSubgroup(unittest.TestCase): e = ExceptionGroup('eg', [e]) return e + @skip_if_huge_c_stack() @skip_emscripten_stack_overflow() @skip_wasi_stack_overflow() def test_deep_split(self): @@ -542,6 +557,7 @@ class DeepRecursionInSplitAndSubgroup(unittest.TestCase): with self.assertRaises(RecursionError): e.split(TypeError) + @skip_if_huge_c_stack() @skip_emscripten_stack_overflow() @skip_wasi_stack_overflow() def test_deep_subgroup(self): diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index bb9c88efe..16a077da1 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -460,6 +460,24 @@ class BuiltinFunctionPropertiesTest(unittest.TestCase): # XXX Not sure where this should really go since I can't find a # test module specifically for builtin_function_or_method. + def test_builtin__module__(self): + import math + + # builtin function: + self.assertEqual(len.__module__, 'builtins') + self.assertEqual(math.sin.__module__, 'math') + + # instance method: + self.assertRaises(AttributeError, getattr, int.to_bytes, '__module__') + self.assertEqual(int.to_bytes.__objclass__.__module__, 'builtins') + + # builtin classmethod: + self.assertEqual(int.from_bytes.__module__, None) + self.assertEqual(int.from_bytes.__self__.__module__, 'builtins') + + # builtin staticmethod: + self.assertEqual(bytes.maketrans.__module__, 'builtins') + def test_builtin__qualname__(self): import time diff --git a/Lib/test/test_json/__init__.py b/Lib/test/test_json/__init__.py index f43bded1a..7a930b07f 100644 --- a/Lib/test/test_json/__init__.py +++ b/Lib/test/test_json/__init__.py @@ -49,6 +49,16 @@ class TestCTest(CTest): '_json') +class TestModule(unittest.TestCase): + def test_deprecated__version__(self): + with self.assertWarnsRegex( + DeprecationWarning, + "'__version__' is deprecated and slated for removal in Python 3.20", + ) as cm: + getattr(json, "__version__") + self.assertEqual(cm.filename, __file__) + + def load_tests(loader, _, pattern): suite = unittest.TestSuite() for mod in (json, json.encoder, json.decoder): diff --git a/Lib/test/test_json/test_fail.py b/Lib/test/test_json/test_fail.py index eec64b528..a7d5cacd0 100644 --- a/Lib/test/test_json/test_fail.py +++ b/Lib/test/test_json/test_fail.py @@ -146,6 +146,13 @@ class TestFail: ('"', 'Unterminated string starting at', 0), ('"spam', 'Unterminated string starting at', 0), ] + # A complete \uXXXX escape at end of input leaves it unterminated. + test_cases += [ + (r'"\u0041', 'Unterminated string starting at', 0), + (r'"\ud834', 'Unterminated string starting at', 0), + (r'"\ud834\udd1e', 'Unterminated string starting at', 0), + (r'{"a": "\u0041', 'Unterminated string starting at', 6), + ] for data, msg, idx in test_cases: with self.assertRaises(self.JSONDecodeError) as cm: self.loads(data) diff --git a/Lib/test/test_json/test_scanstring.py b/Lib/test/test_json/test_scanstring.py index baa43f936..7a6512b81 100644 --- a/Lib/test/test_json/test_scanstring.py +++ b/Lib/test/test_json/test_scanstring.py @@ -139,6 +139,9 @@ class TestScanstring: '"\\ud834\\u-123"', '"\\ud834\\u+123"', '"\\ud834\\u1_23"', + # Truncated or non-hex \uXXXX escape at end of input. + '"\\u004', + '"\\uXYZW', ] for s in bad_escapes: with self.assertRaises(self.JSONDecodeError, msg=s): diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index e879e4857..63c130813 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -11,7 +11,7 @@ import unittest from unittest import mock from test import support -from test.support import os_helper +from test.support import os_helper, warnings_helper try: # Some of the iOS tests need ctypes to operate. @@ -133,6 +133,22 @@ class PlatformTest(unittest.TestCase): for terse in (False, True): res = platform.platform(aliased, terse) + def test__platform(self): + for src, res in [ + ('foo bar', 'foo_bar'), + ( + '1/2\\3:4;5"6(7)8(7)6"5;4:3\\2/1', + '1-2-3-4-5-6-7-8-7-6-5-4-3-2-1' + ), + ('--', ''), + ('-f', '-f'), + ('-foo----', '-foo'), + ('--foo---', '-foo'), + ('---foo--', '-foo'), + ]: + with self.subTest(src=src): + self.assertEqual(platform._platform(src), res) + def test_system(self): res = platform.system() @@ -383,15 +399,6 @@ class PlatformTest(unittest.TestCase): finally: platform._uname_cache = None - def test_java_ver(self): - import re - msg = re.escape( - "'java_ver' is deprecated and slated for removal in Python 3.15" - ) - with self.assertWarnsRegex(DeprecationWarning, msg): - res = platform.java_ver() - self.assertEqual(len(res), 4) - @unittest.skipUnless(support.MS_WINDOWS, 'This test only makes sense on Windows') def test_win32_ver(self): release1, version1, csd1, ptype1 = 'a', 'b', 'c', 'd' @@ -458,7 +465,7 @@ class PlatformTest(unittest.TestCase): else: self.assertEqual(res[2], 'PowerPC') - + @warnings_helper.ignore_fork_in_thread_deprecation_warnings() @unittest.skipUnless(sys.platform == 'darwin', "OSX only test") def test_mac_ver_with_fork(self): # Issue7895: platform.mac_ver() crashes when using fork without exec @@ -527,7 +534,7 @@ class PlatformTest(unittest.TestCase): def test_libc_ver(self): if support.is_emscripten: - assert platform.libc_ver() == ("emscripten", "4.0.12") + assert platform.libc_ver() == ("emscripten", "4.0.19") return # check that libc_ver(executable) doesn't raise an exception if os.path.isdir(sys.executable) and \ @@ -769,13 +776,14 @@ class CommandLineTest(unittest.TestCase): platform._main(args=flags) return output.getvalue() + @support.force_not_colorized def test_unknown_flag(self): + output = io.StringIO() with self.assertRaises(SystemExit): - output = io.StringIO() # suppress argparse error message with contextlib.redirect_stderr(output): _ = self.invoke_platform('--unknown') - self.assertStartsWith(output, "usage: ") + self.assertStartsWith(output.getvalue(), "usage: ") def test_invocation(self): flags = ( diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py index 40997a34e..b615af508 100644 --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -188,7 +188,10 @@ class TestJointOps: self.assertEqual(type(i), self.basetype) self.assertRaises(PassThru, self.s.symmetric_difference, check_pass_thru()) self.assertRaises(TypeError, self.s.symmetric_difference, [[]]) - for C in set, frozenset, dict.fromkeys, str, list, tuple: + constructors = (set, frozenset, + dict.fromkeys, frozendict.fromkeys, + str, list, tuple) + for C in constructors: self.assertEqual(self.thetype('abcba').symmetric_difference(C('cdc')), set('abd')) self.assertEqual(self.thetype('abcba').symmetric_difference(C('efgfe')), set('abcefg')) self.assertEqual(self.thetype('abcba').symmetric_difference(C('ccb')), set('a')) @@ -670,7 +673,7 @@ class TestSet(TestJointOps, unittest.TestCase): with check_unhashable_element(): myset.discard(elem) - # Only TypeError exception is overriden, + # Only TypeError exception is overridden, # other exceptions are left unchanged. class HashError: def __hash__(self): @@ -1596,6 +1599,14 @@ class TestOnlySetsDict(TestOnlySetsInBinaryOps, unittest.TestCase): #------------------------------------------------------------------------------ +class TestOnlySetsFrozenDict(TestOnlySetsInBinaryOps, unittest.TestCase): + def setUp(self): + self.set = set((1, 2, 3)) + self.other = frozendict({1:2, 3:4}) + self.otherIsIterable = True + +#------------------------------------------------------------------------------ + class TestOnlySetsOperator(TestOnlySetsInBinaryOps, unittest.TestCase): def setUp(self): self.set = set((1, 2, 3)) diff --git a/Lib/test/test_userdict.py b/Lib/test/test_userdict.py index 13285c9b2..c60135ca5 100644 --- a/Lib/test/test_userdict.py +++ b/Lib/test/test_userdict.py @@ -245,7 +245,7 @@ class UserDictTest(mapping_tests.TestHashMappingProtocol): test_repr_deep = mapping_tests.TestHashMappingProtocol.test_repr_deep def test_mixed_or(self): - for t in UserDict, dict, types.MappingProxyType: + for t in UserDict, dict, frozendict, types.MappingProxyType: with self.subTest(t.__name__): u = UserDict({0: 'a', 1: 'b'}) | t({1: 'c', 2: 'd'}) self.assertEqual(u, {0: 'a', 1: 'c', 2: 'd'}) @@ -276,7 +276,7 @@ class UserDictTest(mapping_tests.TestHashMappingProtocol): self.assertIs(type(u), UserDictSubclass) def test_mixed_ior(self): - for t in UserDict, dict, types.MappingProxyType: + for t in UserDict, dict, frozendict, types.MappingProxyType: with self.subTest(t.__name__): u = u2 = UserDict({0: 'a', 1: 'b'}) u |= t({1: 'c', 2: 'd'}) diff --git a/crates/common/src/encodings/raw_unicode_escape.rs b/crates/common/src/encodings/raw_unicode_escape.rs index a2df01a26..a61c9dae4 100644 --- a/crates/common/src/encodings/raw_unicode_escape.rs +++ b/crates/common/src/encodings/raw_unicode_escape.rs @@ -41,6 +41,7 @@ where E: DecodeErrorHandler, { let mut out = Wtf8Buf::new(); + let mut consumed = ctx.full_data().len(); loop { let rest = ctx.remaining_data(); if rest.is_empty() { @@ -62,6 +63,7 @@ where let escape_start = ctx.position(); let digits_start = 2; if !final_decode && rest.len() < digits_start + want { + consumed = ctx.position(); break; } let available = (digits_start + want).min(rest.len()); @@ -99,6 +101,7 @@ where continue; } if kind.is_none() && !final_decode { + consumed = ctx.position(); break; } out.push_char(b as char); @@ -108,5 +111,5 @@ where ctx.advance(1); } } - Ok((out, ctx.position())) + Ok((out, consumed)) } diff --git a/crates/common/src/encodings/unicode_escape.rs b/crates/common/src/encodings/unicode_escape.rs index cea7533c1..2d62a0aee 100644 --- a/crates/common/src/encodings/unicode_escape.rs +++ b/crates/common/src/encodings/unicode_escape.rs @@ -68,6 +68,7 @@ where { let mut out = Wtf8Buf::new(); let mut warning = None; + let mut consumed = ctx.full_data().len(); loop { let rest = ctx.remaining_data(); if rest.is_empty() { @@ -84,6 +85,7 @@ where let escape_start = ctx.position(); if rest.len() == 1 { if !final_decode { + consumed = ctx.position(); break; } let end = ctx.full_data().len(); @@ -163,6 +165,7 @@ where _ => (8usize, "truncated \\UXXXXXXXX escape"), }; if !final_decode && rest.len() < 2 + digits { + consumed = ctx.position(); break; } ctx.advance(2); @@ -194,6 +197,7 @@ where } b'N' => { if rest.len() == 2 && !final_decode { + consumed = ctx.position(); break; } if rest.len() >= 3 && rest[2] == b'{' { @@ -204,6 +208,7 @@ where } if look >= rest.len() { if !final_decode { + consumed = ctx.position(); break; } let start = escape_start; @@ -268,5 +273,5 @@ where } } } - Ok((out, ctx.position(), warning)) + Ok((out, consumed, warning)) } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 4142acd13..5dd4d0a7a 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -575,12 +575,14 @@ fn sequence_contains_wrapper( fn repr_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult> { let ret = vm.call_special_method(zelf, identifier!(vm, __repr__), ())?; - ret.downcast::().map_err(|obj| { - vm.new_type_error(format!( - "__repr__ returned non-string (type {})", - obj.class() - )) - }) + match ret.downcast::() { + Ok(value) => Ok(value), + Err(obj) => Err(vm.new_type_error(format!( + "{}.__repr__() must return a str, not {}", + zelf.class().fully_qualified_name(vm)?, + obj.class().fully_qualified_name(vm)? + ))), + } } fn str_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult> {