diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4955fd95a..c901ada03 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -124,8 +124,8 @@ jobs: - name: Set up the Windows environment shell: bash run: | - choco install llvm openssl - echo "OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" >>$GITHUB_ENV + choco install llvm openssl --no-progress + echo "OPENSSL_DIR=C:\Program Files\OpenSSL" >>$GITHUB_ENV if: runner.os == 'Windows' - name: Set up the Mac environment run: brew install autoconf automake libtool @@ -263,8 +263,8 @@ jobs: - name: Set up the Windows environment shell: bash run: | - choco install llvm openssl - echo "OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" >>$GITHUB_ENV + choco install llvm openssl --no-progress + echo "OPENSSL_DIR=C:\Program Files\OpenSSL" >>$GITHUB_ENV if: runner.os == 'Windows' - name: Set up the Mac environment run: brew install autoconf automake libtool openssl@3 @@ -308,6 +308,11 @@ jobs: run: | target/release/rustpython -m ensurepip target/release/rustpython -c "import pip" + - if: runner.os != 'Windows' + name: Check if pip inside venv is functional + run: | + target/release/rustpython -m venv testvenv + testvenv/bin/rustpython -m pip install wheel - name: Check whats_left is not broken run: python -I whats_left.py diff --git a/.vscode/launch.json b/.vscode/launch.json index f0f4518df..6a632e0f1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,6 +17,9 @@ "preLaunchTask": "Build RustPython Debug", "program": "target/debug/rustpython", "args": [], + "env": { + "RUST_BACKTRACE": "1" + }, "cwd": "${workspaceFolder}" }, { diff --git a/Cargo.lock b/Cargo.lock index 83cf55763..23dcc4b2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,6 +950,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -2137,6 +2146,7 @@ dependencies = [ "foreign-types-shared", "gethostname", "hex", + "hmac", "itertools", "libc", "libsqlite3-sys", diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py index 51c942f51..493861d31 100644 --- a/Lib/concurrent/futures/thread.py +++ b/Lib/concurrent/futures/thread.py @@ -37,7 +37,8 @@ def _python_exit(): threading._register_atexit(_python_exit) # At fork, reinitialize the `_global_shutdown_lock` lock in the child process -if hasattr(os, 'register_at_fork'): +# TODO RUSTPYTHON - _at_fork_reinit is not implemented yet +if hasattr(os, 'register_at_fork') and hasattr(_global_shutdown_lock, '_at_fork_reinit'): os.register_at_fork(before=_global_shutdown_lock.acquire, after_in_child=_global_shutdown_lock._at_fork_reinit, after_in_parent=_global_shutdown_lock.release) diff --git a/Lib/hashlib.py b/Lib/hashlib.py new file mode 100644 index 000000000..52a4f8a77 --- /dev/null +++ b/Lib/hashlib.py @@ -0,0 +1,316 @@ +#. Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org) +# Licensed to PSF under a Contributor Agreement. +# + +__doc__ = """hashlib module - A common interface to many hash functions. + +new(name, data=b'', **kwargs) - returns a new hash object implementing the + given hash function; initializing the hash + using the given binary data. + +Named constructor functions are also available, these are faster +than using new(name): + +md5(), sha1(), sha224(), sha256(), sha384(), sha512(), blake2b(), blake2s(), +sha3_224, sha3_256, sha3_384, sha3_512, shake_128, and shake_256. + +More algorithms may be available on your platform but the above are guaranteed +to exist. See the algorithms_guaranteed and algorithms_available attributes +to find out what algorithm names can be passed to new(). + +NOTE: If you want the adler32 or crc32 hash functions they are available in +the zlib module. + +Choose your hash function wisely. Some have known collision weaknesses. +sha384 and sha512 will be slow on 32 bit platforms. + +Hash objects have these methods: + - update(data): Update the hash object with the bytes in data. Repeated calls + are equivalent to a single call with the concatenation of all + the arguments. + - digest(): Return the digest of the bytes passed to the update() method + so far as a bytes object. + - hexdigest(): Like digest() except the digest is returned as a string + of double length, containing only hexadecimal digits. + - copy(): Return a copy (clone) of the hash object. This can be used to + efficiently compute the digests of datas that share a common + initial substring. + +For example, to obtain the digest of the byte string 'Nobody inspects the +spammish repetition': + + >>> import hashlib + >>> m = hashlib.md5() + >>> m.update(b"Nobody inspects") + >>> m.update(b" the spammish repetition") + >>> m.digest() + b'\\xbbd\\x9c\\x83\\xdd\\x1e\\xa5\\xc9\\xd9\\xde\\xc9\\xa1\\x8d\\xf0\\xff\\xe9' + +More condensed: + + >>> hashlib.sha224(b"Nobody inspects the spammish repetition").hexdigest() + 'a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2' + +""" + +# This tuple and __get_builtin_constructor() must be modified if a new +# always available algorithm is added. +__always_supported = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', + 'blake2b', 'blake2s', + 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512', + 'shake_128', 'shake_256') + + +algorithms_guaranteed = set(__always_supported) +algorithms_available = set(__always_supported) + +__all__ = __always_supported + ('new', 'algorithms_guaranteed', + 'algorithms_available', 'pbkdf2_hmac', 'file_digest') + + +__builtin_constructor_cache = {} + +# Prefer our blake2 implementation +# OpenSSL 1.1.0 comes with a limited implementation of blake2b/s. The OpenSSL +# implementations neither support keyed blake2 (blake2 MAC) nor advanced +# features like salt, personalization, or tree hashing. OpenSSL hash-only +# variants are available as 'blake2b512' and 'blake2s256', though. +__block_openssl_constructor = { + 'blake2b', 'blake2s', +} + +def __get_builtin_constructor(name): + cache = __builtin_constructor_cache + constructor = cache.get(name) + if constructor is not None: + return constructor + try: + if name in {'SHA1', 'sha1'}: + import _sha1 + cache['SHA1'] = cache['sha1'] = _sha1.sha1 + elif name in {'MD5', 'md5'}: + import _md5 + cache['MD5'] = cache['md5'] = _md5.md5 + elif name in {'SHA256', 'sha256', 'SHA224', 'sha224'}: + import _sha256 + cache['SHA224'] = cache['sha224'] = _sha256.sha224 + cache['SHA256'] = cache['sha256'] = _sha256.sha256 + elif name in {'SHA512', 'sha512', 'SHA384', 'sha384'}: + import _sha512 + cache['SHA384'] = cache['sha384'] = _sha512.sha384 + cache['SHA512'] = cache['sha512'] = _sha512.sha512 + elif name in {'blake2b', 'blake2s'}: + import _blake2 + cache['blake2b'] = _blake2.blake2b + cache['blake2s'] = _blake2.blake2s + elif name in {'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512'}: + import _sha3 + cache['sha3_224'] = _sha3.sha3_224 + cache['sha3_256'] = _sha3.sha3_256 + cache['sha3_384'] = _sha3.sha3_384 + cache['sha3_512'] = _sha3.sha3_512 + elif name in {'shake_128', 'shake_256'}: + import _sha3 + cache['shake_128'] = _sha3.shake_128 + cache['shake_256'] = _sha3.shake_256 + except ImportError: + pass # no extension module, this hash is unsupported.''' + + constructor = cache.get(name) + if constructor is not None: + return constructor + + raise ValueError('unsupported hash type ' + name) + + +def __get_openssl_constructor(name): + if name in __block_openssl_constructor: + # Prefer our builtin blake2 implementation. + return __get_builtin_constructor(name) + try: + # MD5, SHA1, and SHA2 are in all supported OpenSSL versions + # SHA3/shake are available in OpenSSL 1.1.1+ + f = getattr(_hashlib, 'openssl_' + name) + # Allow the C module to raise ValueError. The function will be + # defined but the hash not actually available. Don't fall back to + # builtin if the current security policy blocks a digest, bpo#40695. + f(usedforsecurity=False) + # Use the C function directly (very fast) + return f + except (AttributeError, ValueError): + return __get_builtin_constructor(name) + + +def __py_new(name, data=b'', **kwargs): + """new(name, data=b'', **kwargs) - Return a new hashing object using the + named algorithm; optionally initialized with data (which must be + a bytes-like object). + """ + return __get_builtin_constructor(name)(data, **kwargs) + + +def __hash_new(name, data=b'', **kwargs): + """new(name, data=b'') - Return a new hashing object using the named algorithm; + optionally initialized with data (which must be a bytes-like object). + """ + if name in __block_openssl_constructor: + # Prefer our builtin blake2 implementation. + return __get_builtin_constructor(name)(data, **kwargs) + try: + return _hashlib.new(name, data, **kwargs) + except ValueError: + # If the _hashlib module (OpenSSL) doesn't support the named + # hash, try using our builtin implementations. + # This allows for SHA224/256 and SHA384/512 support even though + # the OpenSSL library prior to 0.9.8 doesn't provide them. + return __get_builtin_constructor(name)(data) + + +try: + import _hashlib + new = __hash_new + __get_hash = __get_openssl_constructor + # TODO: RUSTPYTHON set in _hashlib instance PyFrozenSet algorithms_available + '''algorithms_available = algorithms_available.union( + _hashlib.openssl_md_meth_names)''' +except ImportError: + _hashlib = None + new = __py_new + __get_hash = __get_builtin_constructor + +try: + # OpenSSL's PKCS5_PBKDF2_HMAC requires OpenSSL 1.0+ with HMAC and SHA + from _hashlib import pbkdf2_hmac +except ImportError: + from warnings import warn as _warn + _trans_5C = bytes((x ^ 0x5C) for x in range(256)) + _trans_36 = bytes((x ^ 0x36) for x in range(256)) + + def pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None): + """Password based key derivation function 2 (PKCS #5 v2.0) + + This Python implementations based on the hmac module about as fast + as OpenSSL's PKCS5_PBKDF2_HMAC for short passwords and much faster + for long passwords. + """ + _warn( + "Python implementation of pbkdf2_hmac() is deprecated.", + category=DeprecationWarning, + stacklevel=2 + ) + if not isinstance(hash_name, str): + raise TypeError(hash_name) + + if not isinstance(password, (bytes, bytearray)): + password = bytes(memoryview(password)) + if not isinstance(salt, (bytes, bytearray)): + salt = bytes(memoryview(salt)) + + # Fast inline HMAC implementation + inner = new(hash_name) + outer = new(hash_name) + blocksize = getattr(inner, 'block_size', 64) + if len(password) > blocksize: + password = new(hash_name, password).digest() + password = password + b'\x00' * (blocksize - len(password)) + inner.update(password.translate(_trans_36)) + outer.update(password.translate(_trans_5C)) + + def prf(msg, inner=inner, outer=outer): + # PBKDF2_HMAC uses the password as key. We can re-use the same + # digest objects and just update copies to skip initialization. + icpy = inner.copy() + ocpy = outer.copy() + icpy.update(msg) + ocpy.update(icpy.digest()) + return ocpy.digest() + + if iterations < 1: + raise ValueError(iterations) + if dklen is None: + dklen = outer.digest_size + if dklen < 1: + raise ValueError(dklen) + + dkey = b'' + loop = 1 + from_bytes = int.from_bytes + while len(dkey) < dklen: + prev = prf(salt + loop.to_bytes(4)) + # endianness doesn't matter here as long to / from use the same + rkey = from_bytes(prev) + for i in range(iterations - 1): + prev = prf(prev) + # rkey = rkey ^ prev + rkey ^= from_bytes(prev) + loop += 1 + dkey += rkey.to_bytes(inner.digest_size) + + return dkey[:dklen] + +try: + # OpenSSL's scrypt requires OpenSSL 1.1+ + from _hashlib import scrypt +except ImportError: + pass + + +def file_digest(fileobj, digest, /, *, _bufsize=2**18): + """Hash the contents of a file-like object. Returns a digest object. + + *fileobj* must be a file-like object opened for reading in binary mode. + It accepts file objects from open(), io.BytesIO(), and SocketIO objects. + The function may bypass Python's I/O and use the file descriptor *fileno* + directly. + + *digest* must either be a hash algorithm name as a *str*, a hash + constructor, or a callable that returns a hash object. + """ + # On Linux we could use AF_ALG sockets and sendfile() to archive zero-copy + # hashing with hardware acceleration. + if isinstance(digest, str): + digestobj = new(digest) + else: + digestobj = digest() + + if hasattr(fileobj, "getbuffer"): + # io.BytesIO object, use zero-copy buffer + digestobj.update(fileobj.getbuffer()) + return digestobj + + # Only binary files implement readinto(). + if not ( + hasattr(fileobj, "readinto") + and hasattr(fileobj, "readable") + and fileobj.readable() + ): + raise ValueError( + f"'{fileobj!r}' is not a file-like object in binary reading mode." + ) + + # binary file, socket.SocketIO object + # Note: socket I/O uses different syscalls than file I/O. + buf = bytearray(_bufsize) # Reusable buffer to reduce allocations. + view = memoryview(buf) + while True: + size = fileobj.readinto(buf) + if size == 0: + break # EOF + digestobj.update(view[:size]) + + return digestobj + + +for __func_name in __always_supported: + # try them all, some may not work due to the OpenSSL + # version not supporting that algorithm. + try: + globals()[__func_name] = __get_hash(__func_name) + except ValueError: + import logging + logging.exception('code for hash %s was not found.', __func_name) + + +# Cleanup locals() +del __always_supported, __func_name, __get_hash +del __py_new, __hash_new, __get_openssl_constructor diff --git a/Lib/site.py b/Lib/site.py index 06c103333..6d5dca641 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -275,7 +275,8 @@ def _getuserbase(): if os.name == "nt": base = os.environ.get("APPDATA") or "~" - return joinuser(base, "Python") + # XXX: RUSTPYTHON; please keep this change for site-packages + return joinuser(base, "RustPython") if sys.platform == "darwin" and sys._framework: return joinuser("~", "Library", sys._framework, @@ -368,7 +369,8 @@ def getsitepackages(prefixes=None): for libdir in libdirs: path = os.path.join(prefix, libdir, - "python%d.%d" % sys.version_info[:2], + # XXX: RUSTPYTHON; please keep this change for site-packages + "rustpython%d.%d" % sys.version_info[:2], "site-packages") sitepackages.append(path) else: diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py index f14b138bb..9999d6bbd 100644 --- a/Lib/sysconfig.py +++ b/Lib/sysconfig.py @@ -1,3 +1,5 @@ +# XXX: RUSTPYTHON; Trick to make sysconfig work as RustPython +exec(r''' """Access to Python's configuration information.""" import os @@ -851,10 +853,6 @@ def _main(): print() _print_dict('Variables', get_config_vars()) -# 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") - if __name__ == '__main__': _main() +'''.replace("Python", "RustPython").replace("/python", "/rustpython")) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 3d89f85a5..bfa2802fb 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -37,6 +37,8 @@ try: except ImportError: pty = signal = None +import threading # XXX: RUSTPYTHON; to skip _at_fork_reinit + class Squares: @@ -2269,6 +2271,7 @@ class PtyTests(unittest.TestCase): # Check stdin/stdout error handler is used when invoking PyOS_Readline() self.check_input_tty("prompté", b"quux\xe9", "ascii") + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_input_no_stdout_fileno(self): # Issue #24402: If stdin is the original terminal but stdout.fileno() # fails, do not use the original stdout file descriptor diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 053c7bff2..1cccd27de 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2889,9 +2889,7 @@ class TestIntFlag(unittest.TestCase): self.assertEqual(Color.ALL.value, 7) self.assertEqual(str(Color.BLUE), 'blue') - # TODO: RUSTPYTHON - @unittest.expectedFailure - @unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON, inconsistent test result on Windows due to threading") + @unittest.skip("TODO: RUSTPYTHON, inconsistent test result due to threading") @threading_helper.reap_threads def test_unique_composite(self): # override __eq__ to be identity only diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 06a84118e..84bdf83e9 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -4,6 +4,7 @@ import platform import os import struct import sys +import threading # XXX: RUSTPYTHON import unittest from multiprocessing import Process from test.support import verbose, cpython_only @@ -155,8 +156,9 @@ class TestFcntl(unittest.TestCase): self.assertRaises(ValueError, fcntl.flock, -1, fcntl.LOCK_SH) self.assertRaises(TypeError, fcntl.flock, 'spam', fcntl.LOCK_SH) - # TODO: RUSTPYTHON, AttributeError: module 'os' has no attribute 'fork' + # TODO: RUSTPYTHON @unittest.expectedFailure + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") def test_lockf_exclusive(self): self.f = open(TESTFN, 'wb+') @@ -167,9 +169,9 @@ class TestFcntl(unittest.TestCase): p.join() fcntl.lockf(self.f, fcntl.LOCK_UN) self.assertEqual(p.exitcode, 0) - - # TODO: RUSTPYTHON, AttributeError: module 'os' has no attribute 'fork' + # TODO: RUSTPYTHON @unittest.expectedFailure + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipIf(platform.system() == "AIX", "AIX returns PermissionError") def test_lockf_share(self): self.f = open(TESTFN, 'wb+') diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 5c2663e8e..6f12aaa93 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -10,6 +10,7 @@ import array from binascii import unhexlify import hashlib import importlib +import io import itertools import os import sys @@ -20,6 +21,7 @@ import warnings from test import support from test.support import _4G, bigmemtest from test.support.import_helper import import_fresh_module +from test.support import os_helper from test.support import threading_helper from test.support import warnings_helper from http.client import HTTPException @@ -102,8 +104,7 @@ class HashLibTestCase(unittest.TestCase): 'sha384', 'SHA384', 'sha512', 'SHA512', 'blake2b', 'blake2s', 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512', - 'shake_128', 'shake_256' - ) + 'shake_128', 'shake_256') shakes = {'shake_128', 'shake_256'} @@ -214,15 +215,11 @@ class HashLibTestCase(unittest.TestCase): else: c.hexdigest() - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_algorithms_guaranteed(self): self.assertEqual(hashlib.algorithms_guaranteed, set(_algo for _algo in self.supported_hash_names if _algo.islower())) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_algorithms_available(self): self.assertTrue(set(hashlib.algorithms_guaranteed). issubset(hashlib.algorithms_available)) @@ -262,8 +259,6 @@ class HashLibTestCase(unittest.TestCase): def test_new_upper_to_lower(self): self.assertEqual(hashlib.new("SHA256").name, "sha256") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_get_builtin_constructor(self): get_builtin_constructor = getattr(hashlib, '__get_builtin_constructor') @@ -384,6 +379,39 @@ class HashLibTestCase(unittest.TestCase): if not shake: self.assertEqual(len(digest), m.digest_size) + if not shake and kwargs.get("key") is None: + # skip shake and blake2 extended parameter tests + self.check_file_digest(name, data, hexdigest) + + def check_file_digest(self, name, data, hexdigest): + hexdigest = hexdigest.lower() + try: + hashlib.new(name) + except ValueError: + # skip, algorithm is blocked by security policy. + return + digests = [name] + digests.extend(self.constructors_to_test[name]) + + with open(os_helper.TESTFN, "wb") as f: + f.write(data) + + try: + for digest in digests: + buf = io.BytesIO(data) + buf.seek(0) + '''self.assertEqual( + dir(hashlib), None + )''' + self.assertEqual( + hashlib.file_digest(buf, digest).hexdigest(), hexdigest + ) + with open(os_helper.TESTFN, "rb") as f: + digestobj = hashlib.file_digest(f, digest) + self.assertEqual(digestobj.hexdigest(), hexdigest) + finally: + os.unlink(os_helper.TESTFN) + def check_no_unicode(self, algorithm_name): # Unicode objects are not allowed as input. constructors = self.constructors_to_test[algorithm_name] @@ -475,6 +503,8 @@ class HashLibTestCase(unittest.TestCase): self.check_sha3('shake_128', 256, 1344, b'\x1f') self.check_sha3('shake_256', 512, 1088, b'\x1f') + # TODO: RUSTPYTHON implement all blake2 params + @unittest.expectedFailure @requires_blake2 def test_blocksize_name_blake2(self): self.check_blocksize_name('blake2b', 128, 64) @@ -718,6 +748,8 @@ class HashLibTestCase(unittest.TestCase): outer.update(keyed.digest()) return outer.hexdigest() + # TODO: RUSTPYTHON add to constructor const value + @unittest.expectedFailure @requires_blake2 def test_blake2b(self): self.check_blake2(hashlib.blake2b, 16, 16, 64, 64, (1<<64)-1) @@ -739,6 +771,8 @@ class HashLibTestCase(unittest.TestCase): "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1"+ "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923") + # TODO: RUSTPYTHON implement all blake2 fields + @unittest.expectedFailure @requires_blake2 def test_case_blake2b_all_parameters(self): # This checks that all the parameters work in general, and also that @@ -763,6 +797,8 @@ class HashLibTestCase(unittest.TestCase): key = bytes.fromhex(key) self.check('blake2b', msg, md, key=key) + # TODO: RUSTPYTHON add to constructor const value + @unittest.expectedFailure @requires_blake2 def test_blake2s(self): self.check_blake2(hashlib.blake2s, 8, 8, 32, 32, (1<<48)-1) @@ -782,6 +818,8 @@ class HashLibTestCase(unittest.TestCase): self.check('blake2s', b"abc", "508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982") + # TODO: RUSTPYTHON implement all blake2 fields + @unittest.expectedFailure @requires_blake2 def test_case_blake2s_all_parameters(self): # This checks that all the parameters work in general, and also that @@ -898,6 +936,7 @@ class HashLibTestCase(unittest.TestCase): ) @threading_helper.reap_threads + @threading_helper.requires_working_threading() def test_threaded_hashing(self): # Updating the same hash object from several threads at once # using data chunk sizes containing the same byte sequences. @@ -1136,12 +1175,37 @@ class KDFTests(unittest.TestCase): hashlib.scrypt(b'password', salt=b'salt', n=2, r=8, p=1, dklen=dklen) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_normalized_name(self): self.assertNotIn("blake2b512", hashlib.algorithms_available) self.assertNotIn("sha3-512", hashlib.algorithms_available) + def test_file_digest(self): + data = b'a' * 65536 + d1 = hashlib.sha256() + self.addCleanup(os.unlink, os_helper.TESTFN) + with open(os_helper.TESTFN, "wb") as f: + for _ in range(10): + d1.update(data) + f.write(data) + + with open(os_helper.TESTFN, "rb") as f: + d2 = hashlib.file_digest(f, hashlib.sha256) + + self.assertEqual(d1.hexdigest(), d2.hexdigest()) + self.assertEqual(d1.name, d2.name) + self.assertIs(type(d1), type(d2)) + + with self.assertRaises(ValueError): + hashlib.file_digest(None, "sha256") + + with self.assertRaises(ValueError): + with open(os_helper.TESTFN, "r") as f: + hashlib.file_digest(f, "sha256") + + with self.assertRaises(ValueError): + with open(os_helper.TESTFN, "wb") as f: + hashlib.file_digest(f, "sha256") + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index 7cf99735c..bc2e02528 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -39,6 +39,8 @@ def ignore_warning(func): class TestVectorsTestCase(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure def assert_hmac_internals( self, h, digest, hashname, digest_size, block_size ): @@ -48,6 +50,8 @@ class TestVectorsTestCase(unittest.TestCase): self.assertEqual(h.digest_size, digest_size) self.assertEqual(h.block_size, block_size) + # TODO: RUSTPYTHON + @unittest.expectedFailure def assert_hmac( self, key, data, digest, hashfunc, hashname, digest_size, block_size ): @@ -122,6 +126,8 @@ class TestVectorsTestCase(unittest.TestCase): h, digest, hashname, digest_size, block_size ) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('md5', openssl=True) def test_md5_vectors(self): # Test the HMAC module against test vectors from the RFC. @@ -164,6 +170,8 @@ class TestVectorsTestCase(unittest.TestCase): b"and Larger Than One Block-Size Data"), "6f630fad67cda0ee1fb1f562db3aa53e") + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha1', openssl=True) def test_sha_vectors(self): def shatest(key, data, digest): @@ -323,18 +331,26 @@ class TestVectorsTestCase(unittest.TestCase): '134676fb6de0446065c97440fa8c6a58', }) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha224', openssl=True) def test_sha224_rfc4231(self): self._rfc4231_test_cases(hashlib.sha224, 'sha224', 28, 64) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256', openssl=True) def test_sha256_rfc4231(self): self._rfc4231_test_cases(hashlib.sha256, 'sha256', 32, 64) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha384', openssl=True) def test_sha384_rfc4231(self): self._rfc4231_test_cases(hashlib.sha384, 'sha384', 48, 128) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha512', openssl=True) def test_sha512_rfc4231(self): self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128) @@ -380,6 +396,8 @@ class ConstructorTestCase(unittest.TestCase): "6c845b47f52b3b47f6590c502db7825aad757bf4fadc8fa972f7cd2e76a5bdeb" ) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_normal(self): # Standard constructor call. @@ -402,6 +420,8 @@ class ConstructorTestCase(unittest.TestCase): with self.assertRaises(TypeError): h = hmac.new("key", digestmod='sha256') + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_withtext(self): # Constructor call with text. @@ -411,6 +431,8 @@ class ConstructorTestCase(unittest.TestCase): self.fail("Constructor call with text argument raised exception.") self.assertEqual(h.hexdigest(), self.expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_with_bytearray(self): try: @@ -420,6 +442,8 @@ class ConstructorTestCase(unittest.TestCase): self.fail("Constructor call with bytearray arguments raised exception.") self.assertEqual(h.hexdigest(), self.expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_with_memoryview_msg(self): try: @@ -428,6 +452,8 @@ class ConstructorTestCase(unittest.TestCase): self.fail("Constructor call with memoryview msg raised exception.") self.assertEqual(h.hexdigest(), self.expected) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_withmodule(self): # Constructor call with text and digest module. @@ -436,6 +462,8 @@ class ConstructorTestCase(unittest.TestCase): except Exception: self.fail("Constructor call with hashlib.sha256 raised exception.") + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipUnless(C_HMAC is not None, 'need _hashlib') def test_internal_types(self): # internal types like _hashlib.C_HMAC are not constructable @@ -443,6 +471,8 @@ class ConstructorTestCase(unittest.TestCase): with self.assertRaisesRegex(TypeError, "immutable type"): C_HMAC.value = None + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipUnless(sha256_module is not None, 'need _sha256') def test_with_sha256_module(self): h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256) @@ -455,6 +485,8 @@ class ConstructorTestCase(unittest.TestCase): class SanityTestCase(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_exercise_all_methods(self): # Exercising all methods once. @@ -496,6 +528,8 @@ class CopyTestCase(unittest.TestCase): "No real copy of the attribute 'outer'.") self.assertIs(h1._hmac, None) + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipIf(_hashopenssl is None, "test requires _hashopenssl") @hashlib_helper.requires_hashdigest('sha256') def test_realcopy_hmac(self): @@ -504,6 +538,8 @@ class CopyTestCase(unittest.TestCase): h2 = h1.copy() self.assertTrue(id(h1._hmac) != id(h2._hmac)) + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_equality(self): # Testing if the copy has the same digests. @@ -515,6 +551,8 @@ class CopyTestCase(unittest.TestCase): self.assertEqual(h1.hexdigest(), h2.hexdigest(), "Hexdigest of copy doesn't match original hexdigest.") + # TODO: RUSTPYTHON + @unittest.expectedFailure @hashlib_helper.requires_hashdigest('sha256') def test_equality_new(self): # Testing if the copy has the same digests with hmac.new(). @@ -532,6 +570,8 @@ class CopyTestCase(unittest.TestCase): class CompareDigestTestCase(unittest.TestCase): + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_hmac_compare_digest(self): self._test_compare_digest(hmac.compare_digest) if openssl_compare_digest is not None: @@ -542,6 +582,8 @@ class CompareDigestTestCase(unittest.TestCase): def test_operator_compare_digest(self): self._test_compare_digest(operator_compare_digest) + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipIf(openssl_compare_digest is None, "test requires _hashlib") def test_openssl_compare_digest(self): self._test_compare_digest(openssl_compare_digest) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index d31582c0d..7480a27ad 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -607,7 +607,8 @@ for k, v in os.environ.items(): print("") """ - +@unittest.skipIf(not hasattr(os, '_exit'), + "TODO: RUSTPYTHON, run_cgi in http/server.py gets stuck as os._exit(127) doesn't currently kill forked processes") @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, "This test can't be run reliably as root (issue #13308).") class CGIHTTPServerTestCase(BaseTestCase): @@ -765,6 +766,7 @@ class CGIHTTPServerTestCase(BaseTestCase): msg='path = %r\nGot: %r\nWanted: %r' % (path, actual, expected)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_headers_and_content(self): res = self.request('/cgi-bin/file1.py') self.assertEqual( @@ -775,6 +777,7 @@ class CGIHTTPServerTestCase(BaseTestCase): res = self.request('///////////nocgi.py/../cgi-bin/nothere.sh') self.assertEqual(res.status, HTTPStatus.NOT_FOUND) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_post(self): params = urllib.parse.urlencode( {'spam' : 1, 'eggs' : 'python', 'bacon' : 123456}) @@ -788,6 +791,7 @@ class CGIHTTPServerTestCase(BaseTestCase): res.read() self.assertEqual(res.status, HTTPStatus.NOT_FOUND) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_authorization(self): headers = {b'Authorization' : b'Basic ' + base64.b64encode(b'username:pass')} @@ -796,6 +800,7 @@ class CGIHTTPServerTestCase(BaseTestCase): (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_no_leading_slash(self): # http://bugs.python.org/issue2254 res = self.request('cgi-bin/file1.py') @@ -803,6 +808,7 @@ class CGIHTTPServerTestCase(BaseTestCase): (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_os_environ_is_not_altered(self): signature = "Test CGI Server" os.environ['SERVER_SOFTWARE'] = signature @@ -812,24 +818,28 @@ class CGIHTTPServerTestCase(BaseTestCase): (res.read(), res.getheader('Content-type'), res.status)) self.assertEqual(os.environ['SERVER_SOFTWARE'], signature) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_urlquote_decoding_in_cgi_check(self): res = self.request('/cgi-bin%2ffile1.py') self.assertEqual( (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_nested_cgi_path_issue21323(self): res = self.request('/cgi-bin/child-dir/file3.py') self.assertEqual( (b'Hello World' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_query_with_multiple_question_mark(self): res = self.request('/cgi-bin/file4.py?a=b?c=d') self.assertEqual( (b'a=b?c=d' + self.linesep, 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_query_with_continuous_slashes(self): res = self.request('/cgi-bin/file4.py?k=aa%2F%2Fbb&//q//p//=//a//b//') self.assertEqual( @@ -837,6 +847,7 @@ class CGIHTTPServerTestCase(BaseTestCase): 'text/html', HTTPStatus.OK), (res.read(), res.getheader('Content-type'), res.status)) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_cgi_path_in_sub_directories(self): try: CGIHTTPRequestHandler.cgi_directories.append('/sub/dir/cgi-bin') @@ -847,6 +858,7 @@ class CGIHTTPServerTestCase(BaseTestCase): finally: CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin') + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') def test_accept(self): browser_accept = \ 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' diff --git a/Lib/test/test_importlib/test_threaded_import.py b/Lib/test/test_importlib/test_threaded_import.py index c13195218..49c02484b 100644 --- a/Lib/test/test_importlib/test_threaded_import.py +++ b/Lib/test/test_importlib/test_threaded_import.py @@ -6,6 +6,7 @@ # randrange, and then Python hangs. import _imp as imp +import _multiprocessing # TODO: RUSTPYTHON import os import importlib import sys @@ -260,6 +261,7 @@ class ThreadedImportTests(unittest.TestCase): 'partial', 'cfimport.py') script_helper.assert_python_ok(fn) + @unittest.skipUnless(hasattr(_multiprocessing, "SemLock"), "TODO: RUSTPYTHON, pool_in_threads.py needs _multiprocessing.SemLock") @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_multiprocessing_pool_circular_import(self): # Regression test for bpo-41567 diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 34afabefe..d7776a025 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -69,6 +69,10 @@ try: except ImportError: INT_MAX = PY_SSIZE_T_MAX = sys.maxsize +try: + import _testcapi +except ImportError: + _testcapi = None from test.support.script_helper import assert_python_ok from test.support import unix_shell @@ -3067,11 +3071,13 @@ class PidTests(unittest.TestCase): self.assertEqual(pid2, pid) # TODO: RUSTPYTHON (AttributeError: module 'os' has no attribute 'spawnv') + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.expectedFailure def test_waitpid(self): self.check_waitpid(code='pass', exitcode=0) # TODO: RUSTPYTHON (AttributeError: module 'os' has no attribute 'spawnv') + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.expectedFailure def test_waitstatus_to_exitcode(self): exitcode = 23 @@ -3103,7 +3109,8 @@ class PidTests(unittest.TestCase): os.waitstatus_to_exitcode((max_exitcode + 1) << 8) with self.assertRaises(OverflowError): os.waitstatus_to_exitcode(-1) - + + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') # TODO: RUSTPYTHON (AttributeError: module 'os' has no attribute 'spawnv') @unittest.expectedFailure # Skip the test on Windows @@ -3146,31 +3153,36 @@ class SpawnTests(unittest.TestCase): for k, v in self.env.items()} return args - + + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnl') def test_spawnl(self): args = self.create_args() exitcode = os.spawnl(os.P_WAIT, args[0], *args) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnle') def test_spawnle(self): args = self.create_args(with_env=True) exitcode = os.spawnle(os.P_WAIT, args[0], *args, self.env) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnlp') def test_spawnlp(self): args = self.create_args() exitcode = os.spawnlp(os.P_WAIT, args[0], *args) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnlpe') def test_spawnlpe(self): args = self.create_args(with_env=True) exitcode = os.spawnlpe(os.P_WAIT, args[0], *args, self.env) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnv') def test_spawnv(self): args = self.create_args() @@ -3181,30 +3193,35 @@ class SpawnTests(unittest.TestCase): exitcode = os.spawnv(os.P_WAIT, FakePath(args[0]), args) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnve') def test_spawnve(self): args = self.create_args(with_env=True) exitcode = os.spawnve(os.P_WAIT, args[0], args, self.env) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnvp') def test_spawnvp(self): args = self.create_args() exitcode = os.spawnvp(os.P_WAIT, args[0], args) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnvpe') def test_spawnvpe(self): args = self.create_args(with_env=True) exitcode = os.spawnvpe(os.P_WAIT, args[0], args, self.env) self.assertEqual(exitcode, self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnv') def test_nowait(self): args = self.create_args() pid = os.spawnv(os.P_NOWAIT, args[0], args) support.wait_process(pid, exitcode=self.exitcode) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnve') def test_spawnve_bytes(self): # Test bytes handling in parse_arglist and parse_envlist (#28114) @@ -3286,10 +3303,12 @@ class SpawnTests(unittest.TestCase): exitcode = spawn(os.P_WAIT, args[0], args, newenv) self.assertEqual(exitcode, 0) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnve') def test_spawnve_invalid_env(self): self._test_invalid_env(os.spawnve) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @requires_os_func('spawnvpe') def test_spawnvpe_invalid_env(self): self._test_invalid_env(os.spawnvpe) @@ -4660,6 +4679,35 @@ class ForkTests(unittest.TestCase): assert_python_ok("-c", code) assert_python_ok("-c", code, PYTHONMALLOC="malloc_debug") + @unittest.skipIf(_testcapi is None, 'TODO: RUSTPYTHON; needs _testcapi') + @unittest.skipUnless(sys.platform in ("linux", "darwin"), + "Only Linux and macOS detect this today.") + def test_fork_warns_when_non_python_thread_exists(self): + code = """if 1: + import os, threading, warnings + from _testcapi import _spawn_pthread_waiter, _end_spawned_pthread + _spawn_pthread_waiter() + try: + with warnings.catch_warnings(record=True) as ws: + warnings.filterwarnings( + "always", category=DeprecationWarning) + if os.fork() == 0: + assert not ws, f"unexpected warnings in child: {ws}" + os._exit(0) # child + else: + assert ws[0].category == DeprecationWarning, ws[0] + assert 'fork' in str(ws[0].message), ws[0] + # Waiting allows an error in the child to hit stderr. + exitcode = os.wait()[1] + assert exitcode == 0, f"child exited {exitcode}" + assert threading.active_count() == 1, threading.enumerate() + finally: + _end_spawned_pthread() + """ + _, out, err = assert_python_ok("-c", code, PYTHONOPTIMIZE='0') + self.assertEqual(err.decode("utf-8"), "") + self.assertEqual(out.decode("utf-8"), "") + # Only test if the C version is provided, otherwise TestPEP519 already tested # the pure Python implementation. diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 0d2a04c79..c6a1cc341 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -346,8 +346,6 @@ class PlatformTest(unittest.TestCase): else: self.assertEqual(res[2], 'PowerPC') - # TODO: RUSTPYTHON - @unittest.expectedFailure @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 diff --git a/Lib/test/test_poll.py b/Lib/test/test_poll.py new file mode 100644 index 000000000..88eed5303 --- /dev/null +++ b/Lib/test/test_poll.py @@ -0,0 +1,240 @@ +# Test case for the os.poll() function + +import os +import subprocess +import random +import select +import threading +import time +import unittest +from test.support import ( + cpython_only, requires_subprocess, requires_working_socket +) +from test.support import threading_helper +from test.support.os_helper import TESTFN + + +try: + select.poll +except AttributeError: + raise unittest.SkipTest("select.poll not defined") + +requires_working_socket(module=True) + +def find_ready_matching(ready, flag): + match = [] + for fd, mode in ready: + if mode & flag: + match.append(fd) + return match + +class PollTests(unittest.TestCase): + + def test_poll1(self): + # Basic functional test of poll object + # Create a bunch of pipe and test that poll works with them. + + p = select.poll() + + NUM_PIPES = 12 + MSG = b" This is a test." + MSG_LEN = len(MSG) + readers = [] + writers = [] + r2w = {} + w2r = {} + + for i in range(NUM_PIPES): + rd, wr = os.pipe() + p.register(rd) + p.modify(rd, select.POLLIN) + p.register(wr, select.POLLOUT) + readers.append(rd) + writers.append(wr) + r2w[rd] = wr + w2r[wr] = rd + + bufs = [] + + while writers: + ready = p.poll() + ready_writers = find_ready_matching(ready, select.POLLOUT) + if not ready_writers: + raise RuntimeError("no pipes ready for writing") + wr = random.choice(ready_writers) + os.write(wr, MSG) + + ready = p.poll() + ready_readers = find_ready_matching(ready, select.POLLIN) + if not ready_readers: + raise RuntimeError("no pipes ready for reading") + rd = random.choice(ready_readers) + buf = os.read(rd, MSG_LEN) + self.assertEqual(len(buf), MSG_LEN) + bufs.append(buf) + os.close(r2w[rd]) ; os.close( rd ) + p.unregister( r2w[rd] ) + p.unregister( rd ) + writers.remove(r2w[rd]) + + self.assertEqual(bufs, [MSG] * NUM_PIPES) + + def test_poll_unit_tests(self): + # returns NVAL for invalid file descriptor + FD, w = os.pipe() + os.close(FD) + os.close(w) + p = select.poll() + p.register(FD) + r = p.poll() + self.assertEqual(r[0], (FD, select.POLLNVAL)) + + with open(TESTFN, 'w') as f: + fd = f.fileno() + p = select.poll() + p.register(f) + r = p.poll() + self.assertEqual(r[0][0], fd) + r = p.poll() + self.assertEqual(r[0], (fd, select.POLLNVAL)) + os.unlink(TESTFN) + + # type error for invalid arguments + p = select.poll() + self.assertRaises(TypeError, p.register, p) + self.assertRaises(TypeError, p.unregister, p) + + # can't unregister non-existent object + p = select.poll() + self.assertRaises(KeyError, p.unregister, 3) + + # Test error cases + pollster = select.poll() + class Nope: + pass + + class Almost: + def fileno(self): + return 'fileno' + + self.assertRaises(TypeError, pollster.register, Nope(), 0) + self.assertRaises(TypeError, pollster.register, Almost(), 0) + + # Another test case for poll(). This is copied from the test case for + # select(), modified to use poll() instead. + + @requires_subprocess() + def test_poll2(self): + cmd = 'for i in 0 1 2 3 4 5 6 7 8 9; do echo testing...; sleep 1; done' + proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, + bufsize=0) + self.enterContext(proc) + p = proc.stdout + pollster = select.poll() + pollster.register( p, select.POLLIN ) + for tout in (0, 1000, 2000, 4000, 8000, 16000) + (-1,)*10: + fdlist = pollster.poll(tout) + if (fdlist == []): + continue + fd, flags = fdlist[0] + if flags & select.POLLHUP: + line = p.readline() + if line != b"": + self.fail('error: pipe seems to be closed, but still returns data') + continue + + elif flags & select.POLLIN: + line = p.readline() + if not line: + break + self.assertEqual(line, b'testing...\n') + continue + else: + self.fail('Unexpected return value from select.poll: %s' % fdlist) + + # TODO: RUSTPYTHON int overflow + @unittest.expectedFailure + def test_poll3(self): + # test int overflow + pollster = select.poll() + pollster.register(1) + + self.assertRaises(OverflowError, pollster.poll, 1 << 64) + + x = 2 + 3 + if x != 5: + self.fail('Overflow must have occurred') + + # Issues #15989, #17919 + self.assertRaises(ValueError, pollster.register, 0, -1) + self.assertRaises(OverflowError, pollster.register, 0, 1 << 64) + self.assertRaises(ValueError, pollster.modify, 1, -1) + self.assertRaises(OverflowError, pollster.modify, 1, 1 << 64) + + @cpython_only + def test_poll_c_limits(self): + from _testcapi import USHRT_MAX, INT_MAX, UINT_MAX + pollster = select.poll() + pollster.register(1) + + # Issues #15989, #17919 + self.assertRaises(OverflowError, pollster.register, 0, USHRT_MAX + 1) + self.assertRaises(OverflowError, pollster.modify, 1, USHRT_MAX + 1) + self.assertRaises(OverflowError, pollster.poll, INT_MAX + 1) + self.assertRaises(OverflowError, pollster.poll, UINT_MAX + 1) + + @unittest.skip("TODO: RUSTPYTHON fd reallocation") + @threading_helper.reap_threads + def test_threaded_poll(self): + r, w = os.pipe() + self.addCleanup(os.close, r) + self.addCleanup(os.close, w) + rfds = [] + for i in range(10): + fd = os.dup(r) + self.addCleanup(os.close, fd) + rfds.append(fd) + pollster = select.poll() + for fd in rfds: + pollster.register(fd, select.POLLIN) + t = threading.Thread(target=pollster.poll) + t.start() + try: + time.sleep(0.5) + # trigger ufds array reallocation + for fd in rfds: + pollster.unregister(fd) + pollster.register(w, select.POLLOUT) + self.assertRaises(RuntimeError, pollster.poll) + finally: + # and make the call to poll() from the thread return + os.write(w, b'spam') + t.join() + + # TODO: RUSTPYTHON add support for negative timeout + @unittest.expectedFailure + @unittest.skipUnless(threading, 'Threading required for this test.') + @threading_helper.reap_threads + def test_poll_blocks_with_negative_ms(self): + for timeout_ms in [None, -1000, -1, -1.0, -0.1, -1e-100]: + # Create two file descriptors. This will be used to unlock + # the blocking call to poll.poll inside the thread + r, w = os.pipe() + pollster = select.poll() + pollster.register(r, select.POLLIN) + + poll_thread = threading.Thread(target=pollster.poll, args=(timeout_ms,)) + poll_thread.start() + poll_thread.join(timeout=0.1) + self.assertTrue(poll_thread.is_alive()) + + # Write to the pipe so pollster.poll unblocks and the thread ends. + os.write(w, b'spam') + poll_thread.join() + self.assertFalse(poll_thread.is_alive()) + os.close(r) + os.close(w) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_property.py b/Lib/test/test_property.py index 09123e08a..5312925d9 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -226,6 +226,23 @@ class PropertyTests(unittest.TestCase): ): p.__set_name__(*([0] * i)) + def test_property_setname_on_property_subclass(self): + # https://github.com/python/cpython/issues/100942 + # Copy was setting the name field without first + # verifying that the copy was an actual property + # instance. As a result, the code below was + # causing a segfault. + + class pro(property): + def __new__(typ, *args, **kwargs): + return "abcdef" + + class A: + pass + + p = property.__new__(pro) + p.__set_name__(A, 1) + np = p.getter(lambda self: 1) # Issue 5890: subclasses of property do not preserve method __doc__ strings class PropertySub(property): @@ -341,43 +358,35 @@ class _PropertyUnreachableAttribute: def setUpClass(cls): cls.obj = cls.cls() + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_get_property(self): - with self.assertRaisesRegex(AttributeError, self._format_exc_msg("unreadable attribute")): + with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")): self.obj.foo + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_set_property(self): - with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't set attribute")): + with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no setter")): self.obj.foo = None + # TODO: RUSTPYTHON + @unittest.expectedFailure def test_del_property(self): - with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't delete attribute")): + with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no deleter")): del self.obj.foo class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^{} 'foo'$" + msg_format = r"^property 'foo' of 'PropertyUnreachableAttributeWithName\.cls' object {}$" class cls: foo = property() - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_get_property(self): # TODO: RUSTPYTHON; remove this function when the test is fixed - super().test_get_property() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_set_property(self): # TODO: RUSTPYTHON; remove this function when the test is fixed - super().test_get_property() - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_del_property(self): # TODO: RUSTPYTHON; remove this function when the test is fixed - super().test_get_property() class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase): - msg_format = "^{}$" + msg_format = r"^property of 'PropertyUnreachableAttributeNoName\.cls' object {}$" class cls: pass diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index 7c38b64f7..7bd1d0877 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -1,3 +1,4 @@ +import threading # XXX: RUSTPYTHON from test.support import verbose, reap_children from test.support.import_helper import import_module @@ -211,6 +212,7 @@ class PtyTest(unittest.TestCase): self.assertEqual(b'For my pet fish, Eric.\n', normalize_output(s2)) # TODO: RUSTPYTHON + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.expectedFailure def test_fork(self): debug("calling pty.fork()") @@ -314,6 +316,7 @@ class PtyTest(unittest.TestCase): self.assertEqual(data, b"") # TODO: RUSTPYTHON; no os.fork + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.expectedFailure def test_spawn_doesnt_hang(self): pty.spawn([sys.executable, '-c', 'print("hi there")']) diff --git a/Lib/test/test_pulldom.py b/Lib/test/test_pulldom.py new file mode 100644 index 000000000..1308c73be --- /dev/null +++ b/Lib/test/test_pulldom.py @@ -0,0 +1,356 @@ +import io +import unittest +import xml.sax + +from xml.sax.xmlreader import AttributesImpl +from xml.sax.handler import feature_external_ges +from xml.dom import pulldom + +from test.support import findfile + + +tstfile = findfile("test.xml", subdir="xmltestdata") + +# A handy XML snippet, containing attributes, a namespace prefix, and a +# self-closing tag: +SMALL_SAMPLE = """ + + +Introduction to XSL +
+

A. Namespace

+""" + + +class PullDOMTestCase(unittest.TestCase): + # TODO: RUSTPYTHON FileNotFoundError: [Errno 2] No such file or directory (os error 2): 'xmltestdata/test.xml' -> 'None' + @unittest.expectedFailure + def test_parse(self): + """Minimal test of DOMEventStream.parse()""" + + # This just tests that parsing from a stream works. Actual parser + # semantics are tested using parseString with a more focused XML + # fragment. + + # Test with a filename: + handler = pulldom.parse(tstfile) + self.addCleanup(handler.stream.close) + list(handler) + + # Test with a file object: + with open(tstfile, "rb") as fin: + list(pulldom.parse(fin)) + + # TODO: RUSTPYTHON implement DOM semantic + @unittest.expectedFailure + def test_parse_semantics(self): + """Test DOMEventStream parsing semantics.""" + + items = pulldom.parseString(SMALL_SAMPLE) + evt, node = next(items) + # Just check the node is a Document: + self.assertTrue(hasattr(node, "createElement")) + self.assertEqual(pulldom.START_DOCUMENT, evt) + evt, node = next(items) + self.assertEqual(pulldom.START_ELEMENT, evt) + self.assertEqual("html", node.tagName) + self.assertEqual(2, len(node.attributes)) + self.assertEqual(node.attributes.getNamedItem("xmlns:xdc").value, + "http://www.xml.com/books") + evt, node = next(items) + self.assertEqual(pulldom.CHARACTERS, evt) # Line break + evt, node = next(items) + # XXX - A comment should be reported here! + # self.assertEqual(pulldom.COMMENT, evt) + # Line break after swallowed comment: + self.assertEqual(pulldom.CHARACTERS, evt) + evt, node = next(items) + self.assertEqual("title", node.tagName) + title_node = node + evt, node = next(items) + self.assertEqual(pulldom.CHARACTERS, evt) + self.assertEqual("Introduction to XSL", node.data) + evt, node = next(items) + self.assertEqual(pulldom.END_ELEMENT, evt) + self.assertEqual("title", node.tagName) + self.assertTrue(title_node is node) + evt, node = next(items) + self.assertEqual(pulldom.CHARACTERS, evt) + evt, node = next(items) + self.assertEqual(pulldom.START_ELEMENT, evt) + self.assertEqual("hr", node.tagName) + evt, node = next(items) + self.assertEqual(pulldom.END_ELEMENT, evt) + self.assertEqual("hr", node.tagName) + evt, node = next(items) + self.assertEqual(pulldom.CHARACTERS, evt) + evt, node = next(items) + self.assertEqual(pulldom.START_ELEMENT, evt) + self.assertEqual("p", node.tagName) + evt, node = next(items) + self.assertEqual(pulldom.START_ELEMENT, evt) + self.assertEqual("xdc:author", node.tagName) + evt, node = next(items) + self.assertEqual(pulldom.CHARACTERS, evt) + evt, node = next(items) + self.assertEqual(pulldom.END_ELEMENT, evt) + self.assertEqual("xdc:author", node.tagName) + evt, node = next(items) + self.assertEqual(pulldom.END_ELEMENT, evt) + evt, node = next(items) + self.assertEqual(pulldom.CHARACTERS, evt) + evt, node = next(items) + self.assertEqual(pulldom.END_ELEMENT, evt) + # XXX No END_DOCUMENT item is ever obtained: + #evt, node = next(items) + #self.assertEqual(pulldom.END_DOCUMENT, evt) + + # TODO: RUSTPYTHON pulldom.parseString(SMALL_SAMPLE) return iterator with tuple with 2 elements + @unittest.expectedFailure + def test_expandItem(self): + """Ensure expandItem works as expected.""" + items = pulldom.parseString(SMALL_SAMPLE) + # Loop through the nodes until we get to a "title" start tag: + for evt, item in items: + if evt == pulldom.START_ELEMENT and item.tagName == "title": + items.expandNode(item) + self.assertEqual(1, len(item.childNodes)) + break + else: + self.fail("No \"title\" element detected in SMALL_SAMPLE!") + # Loop until we get to the next start-element: + for evt, node in items: + if evt == pulldom.START_ELEMENT: + break + self.assertEqual("hr", node.tagName, + "expandNode did not leave DOMEventStream in the correct state.") + # Attempt to expand a standalone element: + items.expandNode(node) + self.assertEqual(next(items)[0], pulldom.CHARACTERS) + evt, node = next(items) + self.assertEqual(node.tagName, "p") + items.expandNode(node) + next(items) # Skip character data + evt, node = next(items) + self.assertEqual(node.tagName, "html") + with self.assertRaises(StopIteration): + next(items) + items.clear() + self.assertIsNone(items.parser) + self.assertIsNone(items.stream) + + @unittest.expectedFailure + def test_comment(self): + """PullDOM does not receive "comment" events.""" + items = pulldom.parseString(SMALL_SAMPLE) + for evt, _ in items: + if evt == pulldom.COMMENT: + break + else: + self.fail("No comment was encountered") + + @unittest.expectedFailure + def test_end_document(self): + """PullDOM does not receive "end-document" events.""" + items = pulldom.parseString(SMALL_SAMPLE) + # Read all of the nodes up to and including : + for evt, node in items: + if evt == pulldom.END_ELEMENT and node.tagName == "html": + break + try: + # Assert that the next node is END_DOCUMENT: + evt, node = next(items) + self.assertEqual(pulldom.END_DOCUMENT, evt) + except StopIteration: + self.fail( + "Ran out of events, but should have received END_DOCUMENT") + + def test_external_ges_default(self): + parser = pulldom.parseString(SMALL_SAMPLE) + saxparser = parser.parser + ges = saxparser.getFeature(feature_external_ges) + self.assertEqual(ges, False) + + +class ThoroughTestCase(unittest.TestCase): + """Test the hard-to-reach parts of pulldom.""" + + def test_thorough_parse(self): + """Test some of the hard-to-reach parts of PullDOM.""" + self._test_thorough(pulldom.parse(None, parser=SAXExerciser())) + + @unittest.expectedFailure + def test_sax2dom_fail(self): + """SAX2DOM can"t handle a PI before the root element.""" + pd = SAX2DOMTestHelper(None, SAXExerciser(), 12) + self._test_thorough(pd) + + def test_thorough_sax2dom(self): + """Test some of the hard-to-reach parts of SAX2DOM.""" + pd = SAX2DOMTestHelper(None, SAX2DOMExerciser(), 12) + self._test_thorough(pd, False) + + def _test_thorough(self, pd, before_root=True): + """Test some of the hard-to-reach parts of the parser, using a mock + parser.""" + + evt, node = next(pd) + self.assertEqual(pulldom.START_DOCUMENT, evt) + # Just check the node is a Document: + self.assertTrue(hasattr(node, "createElement")) + + if before_root: + evt, node = next(pd) + self.assertEqual(pulldom.COMMENT, evt) + self.assertEqual("a comment", node.data) + evt, node = next(pd) + self.assertEqual(pulldom.PROCESSING_INSTRUCTION, evt) + self.assertEqual("target", node.target) + self.assertEqual("data", node.data) + + evt, node = next(pd) + self.assertEqual(pulldom.START_ELEMENT, evt) + self.assertEqual("html", node.tagName) + + evt, node = next(pd) + self.assertEqual(pulldom.COMMENT, evt) + self.assertEqual("a comment", node.data) + evt, node = next(pd) + self.assertEqual(pulldom.PROCESSING_INSTRUCTION, evt) + self.assertEqual("target", node.target) + self.assertEqual("data", node.data) + + evt, node = next(pd) + self.assertEqual(pulldom.START_ELEMENT, evt) + self.assertEqual("p", node.tagName) + + evt, node = next(pd) + self.assertEqual(pulldom.CHARACTERS, evt) + self.assertEqual("text", node.data) + evt, node = next(pd) + self.assertEqual(pulldom.END_ELEMENT, evt) + self.assertEqual("p", node.tagName) + evt, node = next(pd) + self.assertEqual(pulldom.END_ELEMENT, evt) + self.assertEqual("html", node.tagName) + evt, node = next(pd) + self.assertEqual(pulldom.END_DOCUMENT, evt) + + +class SAXExerciser(object): + """A fake sax parser that calls some of the harder-to-reach sax methods to + ensure it emits the correct events""" + + def setContentHandler(self, handler): + self._handler = handler + + def parse(self, _): + h = self._handler + h.startDocument() + + # The next two items ensure that items preceding the first + # start_element are properly stored and emitted: + h.comment("a comment") + h.processingInstruction("target", "data") + + h.startElement("html", AttributesImpl({})) + + h.comment("a comment") + h.processingInstruction("target", "data") + + h.startElement("p", AttributesImpl({"class": "paraclass"})) + h.characters("text") + h.endElement("p") + h.endElement("html") + h.endDocument() + + def stub(self, *args, **kwargs): + """Stub method. Does nothing.""" + pass + setProperty = stub + setFeature = stub + + +class SAX2DOMExerciser(SAXExerciser): + """The same as SAXExerciser, but without the processing instruction and + comment before the root element, because S2D can"t handle it""" + + def parse(self, _): + h = self._handler + h.startDocument() + h.startElement("html", AttributesImpl({})) + h.comment("a comment") + h.processingInstruction("target", "data") + h.startElement("p", AttributesImpl({"class": "paraclass"})) + h.characters("text") + h.endElement("p") + h.endElement("html") + h.endDocument() + + +class SAX2DOMTestHelper(pulldom.DOMEventStream): + """Allows us to drive SAX2DOM from a DOMEventStream.""" + + def reset(self): + self.pulldom = pulldom.SAX2DOM() + # This content handler relies on namespace support + self.parser.setFeature(xml.sax.handler.feature_namespaces, 1) + self.parser.setContentHandler(self.pulldom) + + +class SAX2DOMTestCase(unittest.TestCase): + + def confirm(self, test, testname="Test"): + self.assertTrue(test, testname) + + # TODO: RUSTPYTHON read from stream io + @unittest.expectedFailure + def test_basic(self): + """Ensure SAX2DOM can parse from a stream.""" + with io.StringIO(SMALL_SAMPLE) as fin: + sd = SAX2DOMTestHelper(fin, xml.sax.make_parser(), + len(SMALL_SAMPLE)) + for evt, node in sd: + if evt == pulldom.START_ELEMENT and node.tagName == "html": + break + # Because the buffer is the same length as the XML, all the + # nodes should have been parsed and added: + self.assertGreater(len(node.childNodes), 0) + + def testSAX2DOM(self): + """Ensure SAX2DOM expands nodes as expected.""" + sax2dom = pulldom.SAX2DOM() + sax2dom.startDocument() + sax2dom.startElement("doc", {}) + sax2dom.characters("text") + sax2dom.startElement("subelm", {}) + sax2dom.characters("text") + sax2dom.endElement("subelm") + sax2dom.characters("text") + sax2dom.endElement("doc") + sax2dom.endDocument() + + doc = sax2dom.document + root = doc.documentElement + (text1, elm1, text2) = root.childNodes + text3 = elm1.childNodes[0] + + self.assertIsNone(text1.previousSibling) + self.assertIs(text1.nextSibling, elm1) + self.assertIs(elm1.previousSibling, text1) + self.assertIs(elm1.nextSibling, text2) + self.assertIs(text2.previousSibling, elm1) + self.assertIsNone(text2.nextSibling) + self.assertIsNone(text3.previousSibling) + self.assertIsNone(text3.nextSibling) + + self.assertIs(root.parentNode, doc) + self.assertIs(text1.parentNode, root) + self.assertIs(elm1.parentNode, root) + self.assertIs(text2.parentNode, root) + self.assertIs(text3.parentNode, elm1) + doc.unlink() + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index d8e046570..fb9aca659 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -288,13 +288,15 @@ class HelperFunctionsTests(unittest.TestCase): if sys.platlibdir != "lib": self.assertEqual(len(dirs), 2) wanted = os.path.join('xoxo', sys.platlibdir, - 'python%d.%d' % sys.version_info[:2], + # XXX: RUSTPYTHON + 'rustpython%d.%d' % sys.version_info[:2], 'site-packages') self.assertEqual(dirs[0], wanted) else: self.assertEqual(len(dirs), 1) wanted = os.path.join('xoxo', 'lib', - 'python%d.%d' % sys.version_info[:2], + # XXX: RUSTPYTHON + 'rustpython%d.%d' % sys.version_info[:2], 'site-packages') self.assertEqual(dirs[-1], wanted) else: diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py index 4e01c5bf5..113f959ff 100644 --- a/Lib/test/test_socketserver.py +++ b/Lib/test/test_socketserver.py @@ -55,7 +55,7 @@ if HAVE_UNIX_SOCKETS and HAVE_FORKING: socketserver.UnixDatagramServer): pass - +@test.support.requires_fork() # TODO: RUSTPYTHON, os.fork is currently only supported on Unix-based systems @contextlib.contextmanager def simple_subprocess(testcase): """Tests that a custom child process is not waited on (Issue 1540386)""" diff --git a/Lib/test/test_sqlite3/test_hooks.py b/Lib/test/test_sqlite3/test_hooks.py index 1ab62688c..21042b9bf 100644 --- a/Lib/test/test_sqlite3/test_hooks.py +++ b/Lib/test/test_sqlite3/test_hooks.py @@ -36,8 +36,6 @@ class CollationTests(unittest.TestCase): with self.assertRaises(TypeError): con.create_collation(None, lambda x, y: (x > y) - (x < y)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_create_collation_not_callable(self): con = sqlite.connect(":memory:") with self.assertRaises(TypeError) as cm: diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index d93eda773..0a855571f 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -9,6 +9,7 @@ import subprocess import sys import tempfile import textwrap +import threading # XXX: RUSTPYTHON import time import unittest import warnings @@ -453,6 +454,7 @@ class TestSupport(unittest.TestCase): # TODO: RUSTPYTHON @unittest.expectedFailure + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipUnless(hasattr(os, 'waitpid') and hasattr(os, 'WNOHANG'), 'need os.waitpid() and os.WNOHANG') @support.requires_fork() diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py index a2d06d323..fee196950 100644 --- a/Lib/test/test_tempfile.py +++ b/Lib/test/test_tempfile.py @@ -6,6 +6,7 @@ import os import pathlib import sys import re +import threading # XXX: RUSTPYTHON; to check `_at_fork_reinit` import warnings import contextlib import stat @@ -198,6 +199,7 @@ class TestRandomNameSequence(BaseTestCase): if i == 20: break + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipUnless(hasattr(os, 'fork'), "os.fork is required for this test") def test_process_awareness(self): @@ -465,6 +467,7 @@ class TestMkstempInner(TestBadTempdir, BaseTestCase): expected = user * (1 + 8 + 64) self.assertEqual(mode, expected) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipUnless(has_spawnl, 'os.spawnl not available') def test_noinherit(self): # _mkstemp_inner file handles are not inherited by child processes diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 0f6eceda7..420fbc2cb 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -509,6 +509,7 @@ class ThreadTests(BaseTestCase): t = threading.Thread(daemon=True) self.assertTrue(t.daemon) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, exit_handler needs lock._at_fork_reinit') @unittest.skipUnless(hasattr(os, 'fork'), 'needs os.fork()') def test_fork_at_exit(self): # bpo-42350: Calling os.fork() after threading._shutdown() must @@ -537,6 +538,7 @@ class ThreadTests(BaseTestCase): self.assertEqual(out, b'') self.assertEqual(err.rstrip(), b'child process ok') + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()') def test_dummy_thread_after_fork(self): # Issue #14308: a dummy thread in the active list doesn't mess up @@ -564,6 +566,7 @@ class ThreadTests(BaseTestCase): self.assertEqual(out, b'') self.assertEqual(err, b'') + @unittest.skipUnless(hasattr(sys, 'getswitchinterval'), "TODO: RUSTPYTHON, needs sys.getswitchinterval()") @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") def test_is_alive_after_fork(self): # Try hard to trigger #18418: is_alive() could sometimes be True on @@ -598,6 +601,7 @@ class ThreadTests(BaseTestCase): th.start() th.join() + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()") @unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()") def test_main_thread_after_fork(self): @@ -1003,6 +1007,7 @@ class ThreadJoinOnShutdown(BaseTestCase): """ self._run_and_join(script) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") def test_2_join_in_forked_process(self): @@ -1024,6 +1029,7 @@ class ThreadJoinOnShutdown(BaseTestCase): """ self._run_and_join(script) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") def test_3_join_in_forked_from_thread(self): @@ -1094,6 +1100,7 @@ class ThreadJoinOnShutdown(BaseTestCase): rc, out, err = assert_python_ok('-c', script) self.assertFalse(err) + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") def test_reinit_tls_after_fork(self): @@ -1118,6 +1125,7 @@ class ThreadJoinOnShutdown(BaseTestCase): for t in threads: t.join() + @unittest.skipUnless(hasattr(sys, '_current_frames'), "TODO: RUSTPYTHON, needs sys._current_frames()") @unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()") def test_clear_threads_states_after_fork(self): # Issue #17094: check that threads states are cleared after fork() diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index eb13657a2..66b8720ff 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1,3 +1,4 @@ +import threading # XXX: RUSTPYTHON; to check `_at_fork_reinit` import unittest from test import support from test.support import import_helper @@ -641,7 +642,8 @@ class BaseTestUUID: # TODO: RUSTPYTHON @unittest.expectedFailure - @unittest.skipUnless(os.name == 'posix', 'requires Posix') + @unittest.skipUnless(hasattr(threading.Lock(), '_at_fork_reinit'), 'TODO: RUSTPYTHON, test needs lock._at_fork_reinit') + @support.requires_fork() def testIssue8621(self): # On at least some versions of OSX self.uuid.uuid4 generates # the same sequence of UUIDs in the parent and any diff --git a/Lib/test/xmltestdata/test.xml b/Lib/test/xmltestdata/test.xml new file mode 100644 index 000000000..92136da76 --- /dev/null +++ b/Lib/test/xmltestdata/test.xml @@ -0,0 +1,115 @@ + + +Introduction to XSL +

Introduction to XSL

+ + + +
+

Overview +

+ + + + + + + +
+

Intro

+ + + + + + + +
+

History: XML and SGML

+ + + + + + + +
+

History: What are stylesheets?

+ + + + + + + +
+

History: FOSI

+ + + +µ + + diff --git a/Lib/test/xmltestdata/test.xml.out b/Lib/test/xmltestdata/test.xml.out new file mode 100644 index 000000000..f7e9ad293 --- /dev/null +++ b/Lib/test/xmltestdata/test.xml.out @@ -0,0 +1,115 @@ + + +Introduction to XSL +

Introduction to XSL

+ + + +
+

Overview +

+ + + + + + + +
+

Intro

+ + + + + + + +
+

History: XML and SGML

+ + + + + + + +
+

History: What are stylesheets?

+ + + + + + + +
+

History: FOSI

+ + + +µ + + \ No newline at end of file diff --git a/Lib/test/xmltests.py b/Lib/test/xmltests.py new file mode 100644 index 000000000..bf685a466 --- /dev/null +++ b/Lib/test/xmltests.py @@ -0,0 +1,21 @@ +# Convenience test module to run all of the XML-related tests in the +# standard library. + +import sys +import test.support + +test.support.verbose = 0 + +def runtest(name): + __import__(name) + module = sys.modules[name] + if hasattr(module, "test_main"): + module.test_main() + +runtest("test.test_minidom") +runtest("test.test_pyexpat") +runtest("test.test_sax") +runtest("test.test_xml_dom_minicompat") +runtest("test.test_xml_etree") +runtest("test.test_xml_etree_c") +runtest("test.test_xmlrpc") diff --git a/Lib/xml/dom/pulldom.py b/Lib/xml/dom/pulldom.py index 96a8d5951..913141cd7 100644 --- a/Lib/xml/dom/pulldom.py +++ b/Lib/xml/dom/pulldom.py @@ -216,19 +216,6 @@ class DOMEventStream: self.parser.setFeature(xml.sax.handler.feature_namespaces, 1) self.parser.setContentHandler(self.pulldom) - def __getitem__(self, pos): - import warnings - warnings.warn( - "DOMEventStream's __getitem__ method ignores 'pos' parameter. " - "Use iterator protocol instead.", - DeprecationWarning, - stacklevel=2 - ) - rc = self.getEvent() - if rc: - return rc - raise IndexError - def __next__(self): rc = self.getEvent() if rc: diff --git a/compiler/ast/asdl_rs.py b/compiler/ast/asdl_rs.py index b217e629a..c9b819dae 100755 --- a/compiler/ast/asdl_rs.py +++ b/compiler/ast/asdl_rs.py @@ -474,7 +474,7 @@ class ExtendModuleVisitor(EmitVisitor): def visitModule(self, mod): depth = 0 self.emit( - "pub fn extend_module_nodes(vm: &VirtualMachine, module: &PyObject) {", + "pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) {", depth, ) self.emit("extend_module!(vm, module, {", depth + 1) @@ -671,6 +671,24 @@ def write_ast_def(mod, typeinfo, f): pub fn new(location: Location, end_location: Location, node: T) -> Self { Self { location, end_location: Some(end_location), custom: (), node } } + + pub const fn start(&self) -> Location { + self.location + } + + /// Returns the node's [`end_location`](Located::end_location) or [`location`](Located::start) if + /// [`end_location`](Located::end_location) is `None`. + pub fn end(&self) -> Location { + self.end_location.unwrap_or(self.location) + } + } + + impl std::ops::Deref for Located { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.node + } } \n """.lstrip() diff --git a/compiler/ast/src/ast_gen.rs b/compiler/ast/src/ast_gen.rs index c11fd87da..6771dd0eb 100644 --- a/compiler/ast/src/ast_gen.rs +++ b/compiler/ast/src/ast_gen.rs @@ -24,6 +24,24 @@ impl Located { node, } } + + pub const fn start(&self) -> Location { + self.location + } + + /// Returns the node's [`end_location`](Located::end_location) or [`location`](Located::start) if + /// [`end_location`](Located::end_location) is `None`. + pub fn end(&self) -> Location { + self.end_location.unwrap_or(self.location) + } +} + +impl std::ops::Deref for Located { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.node + } } #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/parser/python.lalrpop b/compiler/parser/python.lalrpop index c8936d33d..45113a3c8 100644 --- a/compiler/parser/python.lalrpop +++ b/compiler/parser/python.lalrpop @@ -67,23 +67,21 @@ SmallStatement: ast::Stmt = { PassStatement: ast::Stmt = { "pass" => { - ast::Stmt { + ast::Stmt::new( location, - end_location: Some(end_location), - custom: (), - node: ast::StmtKind::Pass, - } + end_location, + ast::StmtKind::Pass, + ) }, }; DelStatement: ast::Stmt = { "del" => { - ast::Stmt { + ast::Stmt::new( location, - end_location: Some(end_location), - custom: (), - node: ast::StmtKind::Delete { targets: targets.into_iter().map(|expr| set_context(expr, ast::ExprContext::Del)).collect() }, - } + end_location, + ast::StmtKind::Delete { targets: targets.into_iter().map(|expr| set_context(expr, ast::ExprContext::Del)).collect() }, + ) }, }; @@ -91,12 +89,11 @@ ExpressionStatement: ast::Stmt = { => { // Just an expression, no assignment: if suffix.is_empty() { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Expr { value: Box::new(expression) } - } + end_location, + ast::StmtKind::Expr { value: Box::new(expression) } + ) } else { let mut targets = vec![set_context(expression, ast::ExprContext::Store)]; let mut values = suffix; @@ -107,39 +104,36 @@ ExpressionStatement: ast::Stmt = { let value = Box::new(values.into_iter().next().unwrap()); - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Assign { targets, value, type_comment: None }, - } + end_location, + ast::StmtKind::Assign { targets, value, type_comment: None }, + ) } }, => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::AugAssign { + end_location, + ast::StmtKind::AugAssign { target: Box::new(set_context(target, ast::ExprContext::Store)), op, value: Box::new(rhs) }, - } + ) }, > ":" > => { let simple = matches!(target.node, ast::ExprKind::Name { .. }); - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::AnnAssign { + end_location, + ast::StmtKind::AnnAssign { target: Box::new(set_context(target, ast::ExprContext::Store)), annotation: Box::new(annotation), value: rhs.map(Box::new), simple: if simple { 1 } else { 0 }, }, - } + ) }, }; @@ -191,80 +185,72 @@ AugAssign: ast::Operator = { FlowStatement: ast::Stmt = { "break" => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Break, - } + end_location, + ast::StmtKind::Break, + ) }, "continue" => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Continue, - } + end_location, + ast::StmtKind::Continue, + ) }, "return" => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Return { value: value.map(Box::new) }, - } + end_location, + ast::StmtKind::Return { value: value.map(Box::new) }, + ) }, => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Expr { value: Box::new(expression) }, - } + end_location, + ast::StmtKind::Expr { value: Box::new(expression) }, + ) }, RaiseStatement, }; RaiseStatement: ast::Stmt = { "raise" => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Raise { exc: None, cause: None }, - } + end_location, + ast::StmtKind::Raise { exc: None, cause: None }, + ) }, "raise" > )?> => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Raise { exc: Some(Box::new(t)), cause: c.map(|x| Box::new(x.1)) }, - } + end_location, + ast::StmtKind::Raise { exc: Some(Box::new(t)), cause: c.map(|x| Box::new(x.1)) }, + ) }, }; ImportStatement: ast::Stmt = { "import" >> => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Import { names }, - } + end_location, + ast::StmtKind::Import { names }, + ) }, "from" "import" => { let (level, module) = source; - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::ImportFrom { + end_location, + ast::StmtKind::ImportFrom { level, module, names }, - } + ) }, }; @@ -312,37 +298,34 @@ DottedName: String = { GlobalStatement: ast::Stmt = { "global" > => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Global { names } - } + end_location, + ast::StmtKind::Global { names } + ) }, }; NonlocalStatement: ast::Stmt = { "nonlocal" > => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Nonlocal { names } - } + end_location, + ast::StmtKind::Nonlocal { names } + ) }, }; AssertStatement: ast::Stmt = { "assert" > )?> => { - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Assert { + end_location, + ast::StmtKind::Assert { test: Box::new(test), msg: msg.map(|e| Box::new(e.1)) } - } + ) }, }; @@ -365,17 +348,15 @@ MatchStatement: ast::Stmt = { .body .last() .unwrap() - .end_location - .unwrap(); - ast::Stmt { + .end(); + ast::Stmt::new( location, - end_location: Some(end_location), - custom: (), - node: ast::StmtKind::Match { + end_location, + ast::StmtKind::Match { subject: Box::new(subject), cases } - } + ) }, "match" "," ":" "\n" Indent Dedent => { let end_location = cases @@ -384,17 +365,15 @@ MatchStatement: ast::Stmt = { .body .last() .unwrap() - .end_location - .unwrap(); - ast::Stmt { + .end(); + ast::Stmt::new( location, - end_location: Some(end_location), - custom: (), - node: ast::StmtKind::Match { + end_location, + ast::StmtKind::Match { subject: Box::new(subject), cases } - } + ) }, "match" "," > ","? ":" "\n" Indent Dedent => { let end_location = cases @@ -403,27 +382,24 @@ MatchStatement: ast::Stmt = { .body .last() .unwrap() - .end_location - .unwrap(); + .end(); let mut subjects = subjects; subjects.insert(0, subject); - ast::Stmt { - custom: (), + ast::Stmt::new( location, - end_location: Some(end_location), - node: ast::StmtKind::Match { - subject: Box::new(ast::Expr { + end_location, + ast::StmtKind::Match { + subject: Box::new(ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Tuple { + end_location, + ast::ExprKind::Tuple { elts: subjects, ctx: ast::ExprContext::Load, }, - }), + )), cases } - } + ) } } @@ -444,25 +420,23 @@ Guard: ast::Expr = { } Patterns: ast::Pattern = { - "," => ast::Pattern { + "," => ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), - node: ast::PatternKind::MatchSequence { + end_location, + ast::PatternKind::MatchSequence { patterns: vec![pattern] }, - }, + ), "," > ","? => { let mut patterns = patterns; patterns.insert(0, pattern); - ast::Pattern { + ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), - node: ast::PatternKind::MatchSequence { + end_location, + ast::PatternKind::MatchSequence { patterns }, - } + ) }, => pattern } @@ -480,15 +454,14 @@ AsPattern: ast::Pattern = { location, })? } else { - Ok(ast::Pattern { + Ok(ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), - node: ast::PatternKind::MatchAs { + end_location, + ast::PatternKind::MatchAs { pattern: Some(Box::new(pattern)), name: Some(name), }, - }) + )) } }, } @@ -498,58 +471,50 @@ OrPattern: ast::Pattern = { )+> => { let mut patterns = patterns; patterns.insert(0, pattern); - ast::Pattern { + ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), - node: ast::PatternKind::MatchOr { patterns } - } + end_location, + ast::PatternKind::MatchOr { patterns } + ) } } ClosedPattern: ast::Pattern = { - => ast::Pattern { + => ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), + end_location, node, - }, - => ast::Pattern { + ), + => ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), + end_location, node, - }, - => ast::Pattern { + ), + => ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), + end_location, node, - }, - => ast::Pattern { + ), + => ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), + end_location, node, - }, - => ast::Pattern { + ), + => ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), + end_location, node, - }, - => ast::Pattern { + ), + => ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), + end_location, node, - }, - => ast::Pattern { + ), + => ast::Pattern::new( location, - end_location: Some(end_location), - custom: (), + end_location, node, - }, + ), } SequencePattern: ast::PatternKind = { @@ -577,38 +542,35 @@ StarPattern: ast::PatternKind = { } ConstantAtom: ast::Expr = { - => ast::Expr { + => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Constant { value, kind: None } - }, + end_location, + ast::ExprKind::Constant { value, kind: None } + ), } ConstantExpr: ast::Expr = { ConstantAtom, - "-" => ast::Expr { + "-" => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::UnaryOp { + end_location, + ast::ExprKind::UnaryOp { op: ast::Unaryop::USub, operand: Box::new(operand) } - }, + ), } AddOpExpr: ast::Expr = { - => ast::Expr { + => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BinOp { + end_location, + ast::ExprKind::BinOp { left: Box::new(left), op, right: Box::new(right), } - }, + ), } LiteralPattern: ast::PatternKind = { @@ -640,35 +602,32 @@ CapturePattern: ast::PatternKind = { } MatchName: ast::Expr = { - => ast::Expr { + => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Name { id: name, ctx: ast::ExprContext::Load }, - }, + end_location, + ast::ExprKind::Name { id: name, ctx: ast::ExprContext::Load }, + ), } MatchNameOrAttr: ast::Expr = { - "." => ast::Expr { + "." => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Attribute { + end_location, + ast::ExprKind::Attribute { value: Box::new(name), attr, ctx: ast::ExprContext::Load, }, - }, - "." => ast::Expr { + ), + "." => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Attribute { + end_location, + ast::ExprKind::Attribute { value: Box::new(e), attr, ctx: ast::ExprContext::Load, } - } + ) } ValuePattern: ast::PatternKind = { @@ -681,33 +640,30 @@ MappingKey: ast::Expr = { ConstantExpr, AddOpExpr, MatchNameOrAttr, - "None" => ast::Expr { + "None" => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Constant { + end_location, + ast::ExprKind::Constant { value: ast::Constant::None, kind: None, }, - }, - "True" => ast::Expr { + ), + "True" => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Constant { + end_location, + ast::ExprKind::Constant { value: true.into(), kind: None, }, - }, - "False" => ast::Expr { + ), + "False" => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Constant { + end_location, + ast::ExprKind::Constant { value: false.into(), kind: None, }, - }, + ), =>? Ok(parse_strings(s)?), } @@ -844,24 +800,22 @@ IfStatement: ast::Stmt = { .or_else(|| s2.last().and_then(|last| last.4.last())) .or_else(|| body.last()) .unwrap() - .end_location; + .end(); // handle elif: for i in s2.into_iter().rev() { - let x = ast::Stmt { - custom: (), - location: i.0, + let x = ast::Stmt::new( + i.0, end_location, - node: ast::StmtKind::If { test: Box::new(i.2), body: i.4, orelse: last }, - }; + ast::StmtKind::If { test: Box::new(i.2), body: i.4, orelse: last }, + ); last = vec![x]; } - ast::Stmt { - custom: (), + ast::Stmt::new( location, end_location, - node: ast::StmtKind::If { test: Box::new(test), body, orelse: last } - } + ast::StmtKind::If { test: Box::new(test), body, orelse: last } + ) }, }; @@ -872,17 +826,16 @@ WhileStatement: ast::Stmt = { .last() .or_else(|| body.last()) .unwrap() - .end_location; - ast::Stmt { - custom: (), + .end(); + ast::Stmt::new( location, end_location, - node: ast::StmtKind::While { + ast::StmtKind::While { test: Box::new(test), body, orelse }, - } + ) }, }; @@ -893,8 +846,7 @@ ForStatement: ast::Stmt = { .last() .or_else(|| body.last()) .unwrap() - .end_location - .unwrap(); + .end(); let target = Box::new(set_context(target, ast::ExprContext::Store)); let iter = Box::new(iter); let type_comment = None; @@ -913,21 +865,20 @@ TryStatement: ast::Stmt = { let finalbody = finally.map(|s| s.2).unwrap_or_default(); let end_location = finalbody .last() - .map(|last| last.end_location) - .or_else(|| orelse.last().map(|last| last.end_location)) - .or_else(|| handlers.last().map(|last| last.end_location)) + .map(|last| last.end()) + .or_else(|| orelse.last().map(|last| last.end())) + .or_else(|| handlers.last().map(|last| last.end())) .unwrap(); - ast::Stmt { - custom: (), + ast::Stmt::new( location, end_location, - node: ast::StmtKind::Try { + ast::StmtKind::Try { body, handlers, orelse, finalbody, }, - } + ) }, "try" ":" => { let orelse = else_suite.map(|s| s.2).unwrap_or_default(); @@ -935,43 +886,41 @@ TryStatement: ast::Stmt = { let end_location = finalbody .last() .or_else(|| orelse.last()) - .map(|last| last.end_location) - .or_else(|| handlers.last().map(|last| last.end_location)) + .map(|last| last.end()) + .or_else(|| handlers.last().map(|last| last.end())) .unwrap(); - ast::Stmt { - custom: (), + ast::Stmt::new( location, end_location, - node: ast::StmtKind::TryStar { + ast::StmtKind::TryStar { body, handlers, orelse, finalbody, }, - } + ) }, "try" ":" => { let handlers = vec![]; let orelse = vec![]; let finalbody = finally.2; - let end_location = finalbody.last().unwrap().end_location; - ast::Stmt { - custom: (), + let end_location = finalbody.last().unwrap().end(); + ast::Stmt::new( location, end_location, - node: ast::StmtKind::Try { + ast::StmtKind::Try { body, handlers, orelse, finalbody, }, - } + ) }, }; ExceptStarClause: ast::Excepthandler = { "except" "*" > ":" => { - let end_location = body.last().unwrap().end_location.unwrap(); + let end_location = body.last().unwrap().end(); ast::Excepthandler::new( location, end_location, @@ -983,7 +932,7 @@ ExceptStarClause: ast::Excepthandler = { ) }, "except" "*" "as" Identifier)> ":" => { - let end_location = body.last().unwrap().end_location.unwrap(); + let end_location = body.last().unwrap().end(); ast::Excepthandler::new( location, end_location, @@ -999,7 +948,7 @@ ExceptStarClause: ast::Excepthandler = { ExceptClause: ast::Excepthandler = { "except" ?> ":" => { - let end_location = body.last().unwrap().end_location.unwrap(); + let end_location = body.last().unwrap().end(); ast::Excepthandler::new( location, end_location, @@ -1011,7 +960,7 @@ ExceptClause: ast::Excepthandler = { ) }, "except" "as" Identifier)> ":" => { - let end_location = body.last().unwrap().end_location.unwrap(); + let end_location = body.last().unwrap().end(); ast::Excepthandler::new( location, end_location, @@ -1026,7 +975,7 @@ ExceptClause: ast::Excepthandler = { WithStatement: ast::Stmt = { "with" ":" => { - let end_location = body.last().unwrap().end_location.unwrap(); + let end_location = body.last().unwrap().end(); let type_comment = None; let node = if is_async.is_some() { ast::StmtKind::AsyncWith { items, body, type_comment } @@ -1067,7 +1016,7 @@ FuncDef: ast::Stmt = { "def" " Test<"all">)?> ":" => { let args = Box::new(args); let returns = r.map(|x| Box::new(x.1)); - let end_location = body.last().unwrap().end_location.unwrap(); + let end_location = body.last().unwrap().end(); let type_comment = None; let node = if is_async.is_some() { ast::StmtKind::AsyncFunctionDef { name, args, body, decorator_list, returns, type_comment } @@ -1242,19 +1191,18 @@ ClassDef: ast::Stmt = { Some((_, arg, _)) => (arg.args, arg.keywords), None => (vec![], vec![]), }; - let end_location = body.last().unwrap().end_location; - ast::Stmt { - custom: (), + let end_location = body.last().unwrap().end(); + ast::Stmt::new( location, end_location, - node: ast::StmtKind::ClassDef { + ast::StmtKind::ClassDef { name, bases, keywords, body, decorator_list, }, - } + ) }, }; @@ -1266,31 +1214,28 @@ Decorator: ast::Expr = { }; YieldExpr: ast::Expr = { - "yield" => ast::Expr { + "yield" => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Yield { value: value.map(Box::new) } - }, - "yield" "from" > => ast::Expr { + end_location, + ast::ExprKind::Yield { value: value.map(Box::new) } + ), + "yield" "from" > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::YieldFrom { value: Box::new(e) } - }, + end_location, + ast::ExprKind::YieldFrom { value: Box::new(e) } + ), }; Test: ast::Expr = { - > "if" > "else" > => ast::Expr { + > "if" > "else" > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::IfExp { + end_location, + ast::ExprKind::IfExp { test: Box::new(test), body: Box::new(body), orelse: Box::new(orelse), } - }, + ), OrTest, LambdaDef, }; @@ -1302,11 +1247,10 @@ NamedExpressionTest: ast::Expr = { NamedExpression: ast::Expr = { ":=" > => { - ast::Expr { + ast::Expr::new( location, - end_location: value.end_location, - custom: (), - node: ast::ExprKind::NamedExpr { + value.end(), + ast::ExprKind::NamedExpr { target: Box::new(ast::Expr::new( location, end_location, @@ -1314,7 +1258,7 @@ NamedExpression: ast::Expr = { )), value: Box::new(value), } - } + ) }, }; @@ -1334,15 +1278,14 @@ LambdaDef: ast::Expr = { } ))?; - Ok(ast::Expr { + Ok(ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Lambda { + end_location, + ast::ExprKind::Lambda { args: Box::new(p), body: Box::new(body) } - }) + )) } } @@ -1350,12 +1293,11 @@ OrTest: ast::Expr = { > )+> => { let mut values = vec![e1]; values.extend(e2.into_iter().map(|e| e.1)); - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BoolOp { op: ast::Boolop::Or, values } - } + end_location, + ast::ExprKind::BoolOp { op: ast::Boolop::Or, values } + ) }, AndTest, }; @@ -1364,35 +1306,32 @@ AndTest: ast::Expr = { > )+> => { let mut values = vec![e1]; values.extend(e2.into_iter().map(|e| e.1)); - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BoolOp { op: ast::Boolop::And, values } - } + end_location, + ast::ExprKind::BoolOp { op: ast::Boolop::And, values } + ) }, NotTest, }; NotTest: ast::Expr = { - "not" > => ast::Expr { + "not" > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::UnaryOp { operand: Box::new(e), op: ast::Unaryop::Not } - }, + end_location, + ast::ExprKind::UnaryOp { operand: Box::new(e), op: ast::Unaryop::Not } + ), Comparison, }; Comparison: ast::Expr = { > )+> => { let (ops, comparators) = comparisons.into_iter().unzip(); - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Compare { left: Box::new(left), ops, comparators } - } + end_location, + ast::ExprKind::Compare { left: Box::new(left), ops, comparators } + ) }, Expression, }; @@ -1411,42 +1350,38 @@ CompOp: ast::Cmpop = { }; Expression: ast::Expr = { - > "|" > => ast::Expr { + > "|" > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BinOp { left: Box::new(e1), op: ast::Operator::BitOr, right: Box::new(e2) } - }, + end_location, + ast::ExprKind::BinOp { left: Box::new(e1), op: ast::Operator::BitOr, right: Box::new(e2) } + ), XorExpression, }; XorExpression: ast::Expr = { - > "^" > => ast::Expr { + > "^" > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BinOp { left: Box::new(e1), op: ast::Operator::BitXor, right: Box::new(e2) } - }, + end_location, + ast::ExprKind::BinOp { left: Box::new(e1), op: ast::Operator::BitXor, right: Box::new(e2) } + ), AndExpression, }; AndExpression: ast::Expr = { - > "&" > => ast::Expr { + > "&" > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BinOp { left: Box::new(e1), op: ast::Operator::BitAnd, right: Box::new(e2) } - }, + end_location, + ast::ExprKind::BinOp { left: Box::new(e1), op: ast::Operator::BitAnd, right: Box::new(e2) } + ), ShiftExpression, }; ShiftExpression: ast::Expr = { - > > => ast::Expr { + > > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BinOp { left: Box::new(e1), op, right: Box::new(e2) } - }, + end_location, + ast::ExprKind::BinOp { left: Box::new(e1), op, right: Box::new(e2) } + ), ArithmeticExpression, }; @@ -1456,12 +1391,11 @@ ShiftOp: ast::Operator = { }; ArithmeticExpression: ast::Expr = { - > > => ast::Expr { + > > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BinOp { left: Box::new(a), op, right: Box::new(b) } - }, + end_location, + ast::ExprKind::BinOp { left: Box::new(a), op, right: Box::new(b) } + ), Term, }; @@ -1471,12 +1405,11 @@ AddOp: ast::Operator = { }; Term: ast::Expr = { - > > => ast::Expr { + > > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BinOp { left: Box::new(a), op, right: Box::new(b) } - }, + end_location, + ast::ExprKind::BinOp { left: Box::new(a), op, right: Box::new(b) } + ), Factor, }; @@ -1489,12 +1422,11 @@ MulOp: ast::Operator = { }; Factor: ast::Expr = { - > => ast::Expr { + > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::UnaryOp { operand: Box::new(e), op } - }, + end_location, + ast::ExprKind::UnaryOp { operand: Box::new(e), op } + ), Power, }; @@ -1505,23 +1437,21 @@ UnaryOp: ast::Unaryop = { }; Power: ast::Expr = { - > "**" > => ast::Expr { + > "**" > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::BinOp { left: Box::new(e), op: ast::Operator::Pow, right: Box::new(b) } - }, + end_location, + ast::ExprKind::BinOp { left: Box::new(e), op: ast::Operator::Pow, right: Box::new(b) } + ), AtomExpr, }; AtomExpr: ast::Expr = { "await" > => { - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Await { value: Box::new(atom) } - } + end_location, + ast::ExprKind::Await { value: Box::new(atom) } + ) }, AtomExpr2, } @@ -1529,25 +1459,22 @@ AtomExpr: ast::Expr = { AtomExpr2: ast::Expr = { Atom, > "(" ")" => { - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Call { func: Box::new(f), args: a.args, keywords: a.keywords } - } + end_location, + ast::ExprKind::Call { func: Box::new(f), args: a.args, keywords: a.keywords } + ) }, - > "[" "]" => ast::Expr { + > "[" "]" => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Subscript { value: Box::new(e), slice: Box::new(s), ctx: ast::ExprContext::Load } - }, - > "." => ast::Expr { + end_location, + ast::ExprKind::Subscript { value: Box::new(e), slice: Box::new(s), ctx: ast::ExprContext::Load } + ), + > "." => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Attribute { value: Box::new(e), attr, ctx: ast::ExprContext::Load } - }, + end_location, + ast::ExprKind::Attribute { value: Box::new(e), attr, ctx: ast::ExprContext::Load } + ), }; SubscriptList: ast::Expr = { @@ -1560,12 +1487,11 @@ SubscriptList: ast::Expr = { dims.push(x.1) } - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Tuple { elts: dims, ctx: ast::ExprContext::Load }, - } + end_location, + ast::ExprKind::Tuple { elts: dims, ctx: ast::ExprContext::Load }, + ) } } }; @@ -1576,12 +1502,11 @@ Subscript: ast::Expr = { let lower = e1.map(Box::new); let upper = e2.map(Box::new); let step = e3.flatten().map(Box::new); - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Slice { lower, upper, step } - } + end_location, + ast::ExprKind::Slice { lower, upper, step } + ) } }; @@ -1591,34 +1516,30 @@ SliceOp: Option = { Atom: ast::Expr = { =>? Ok(parse_strings(s)?), - => ast::Expr { + => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Constant { value, kind: None } - }, - => ast::Expr { + end_location, + ast::ExprKind::Constant { value, kind: None } + ), + => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Name { id: name, ctx: ast::ExprContext::Load } - }, + end_location, + ast::ExprKind::Name { id: name, ctx: ast::ExprContext::Load } + ), "[" "]" => { let elts = e.unwrap_or_default(); - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::List { elts, ctx: ast::ExprContext::Load } - } + end_location, + ast::ExprKind::List { elts, ctx: ast::ExprContext::Load } + ) }, "[" "]" => { - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::ListComp { elt: Box::new(elt), generators } - } + end_location, + ast::ExprKind::ListComp { elt: Box::new(elt), generators } + ) }, "(" >> ")" if Goal != "no-withitems" => { if elts.len() == 1 && trailing_comma.is_none() { @@ -1636,7 +1557,7 @@ Atom: ast::Expr = { if matches!(mid.node, ast::ExprKind::Starred { .. }) { Err(LexicalError{ error: LexicalErrorType::OtherError("cannot use starred expression here".to_string()), - location: mid.location, + location: mid.start(), })? } Ok(mid) @@ -1656,12 +1577,11 @@ Atom: ast::Expr = { ), "(" ")" => e, "(" ")" => { - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::GeneratorExp { elt: Box::new(elt), generators } - } + end_location, + ast::ExprKind::GeneratorExp { elt: Box::new(elt), generators } + ) }, "(" "**" > ")" =>? { Err(LexicalError{ @@ -1675,38 +1595,34 @@ Atom: ast::Expr = { .into_iter() .map(|(k, v)| (k.map(|x| *x), v)) .unzip(); - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Dict { keys, values } - } + end_location, + ast::ExprKind::Dict { keys, values } + ) }, "{" "}" => { - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::DictComp { + end_location, + ast::ExprKind::DictComp { key: Box::new(e1.0), value: Box::new(e1.1), generators, } - } + ) }, - "{" "}" => ast::Expr { + "{" "}" => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Set { elts } - }, + end_location, + ast::ExprKind::Set { elts } + ), "{" "}" => { - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::SetComp { elt: Box::new(elt), generators } - } + end_location, + ast::ExprKind::SetComp { elt: Box::new(elt), generators } + ) }, "True" => ast::Expr::new(location, end_location, ast::ExprKind::Constant { value: true.into(), kind: None }), "False" => ast::Expr::new(location, end_location, ast::ExprKind::Constant { value: false.into(), kind: None }), @@ -1762,24 +1678,22 @@ GenericList: ast::Expr = { if elts.len() == 1 && trailing_comma.is_none() { elts.into_iter().next().unwrap() } else { - ast::Expr { + ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Tuple { elts, ctx: ast::ExprContext::Load } - } + end_location, + ast::ExprKind::Tuple { elts, ctx: ast::ExprContext::Load } + ) } } } // Test StarExpr: ast::Expr = { - "*" > => ast::Expr { + "*" > => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::Starred { value: Box::new(e), ctx: ast::ExprContext::Load }, - } + end_location, + ast::ExprKind::Starred { value: Box::new(e), ctx: ast::ExprContext::Load }, + ) }; // Comprehensions: @@ -1810,15 +1724,14 @@ ArgumentList: ArgumentList = { FunctionArgument: (Option<(ast::Location, ast::Location, Option)>, ast::Expr) = { => { let expr = match c { - Some(c) => ast::Expr { + Some(c) => ast::Expr::new( location, - end_location: Some(end_location), - custom: (), - node: ast::ExprKind::GeneratorExp { + end_location, + ast::ExprKind::GeneratorExp { elt: Box::new(e), generators: c, } - }, + ), None => e, }; (None, expr) diff --git a/compiler/parser/src/function.rs b/compiler/parser/src/function.rs index 0f580e77f..17b9882bf 100644 --- a/compiler/parser/src/function.rs +++ b/compiler/parser/src/function.rs @@ -35,12 +35,12 @@ pub(crate) fn validate_arguments( let mut all_arg_names = FxHashSet::with_hasher(Default::default()); for arg in all_args { - let arg_name = &arg.node.arg; + let arg_name = &arg.arg; // Check for duplicate arguments in the function definition. if !all_arg_names.insert(arg_name) { return Err(LexicalError { error: LexicalErrorType::DuplicateArgumentError(arg_name.to_string()), - location: arg.location, + location: arg.start(), }); } } @@ -64,7 +64,7 @@ pub(crate) fn parse_params( // have defaults. return Err(LexicalError { error: LexicalErrorType::DefaultArgumentError, - location: name.location, + location: name.start(), }); } Ok(()) @@ -126,14 +126,14 @@ pub(crate) fn parse_args(func_args: Vec) -> Result TokenStream { pub fn pyclass(attr: AttributeArgs, item: Item) -> TokenStream { if matches!(item, syn::Item::Impl(_) | syn::Item::Trait(_)) { - result_to_tokens(pyclass::impl_pyimpl(attr, item)) + result_to_tokens(pyclass::impl_pyclass_impl(attr, item)) } else { result_to_tokens(pyclass::impl_pyclass(attr, item)) } } -pub use pyclass::PyExceptionDef; -pub fn define_exception(exc_def: PyExceptionDef) -> TokenStream { - result_to_tokens(pyclass::impl_define_exception(exc_def)) -} - pub fn pyexception(attr: AttributeArgs, item: Item) -> TokenStream { - result_to_tokens(pyclass::impl_pyexception(attr, item)) + if matches!(item, syn::Item::Impl(_)) { + result_to_tokens(pyclass::impl_pyexception_impl(attr, item)) + } else { + result_to_tokens(pyclass::impl_pyexception(attr, item)) + } } pub fn pymodule(attr: AttributeArgs, item: Item) -> TokenStream { @@ -77,3 +77,7 @@ pub fn py_freeze(input: TokenStream, compiler: &dyn Compiler) -> TokenStream { pub fn pypayload(input: DeriveInput) -> TokenStream { result_to_tokens(pypayload::impl_pypayload(input)) } + +pub fn pytraverse(item: DeriveInput) -> TokenStream { + result_to_tokens(pytraverse::impl_pytraverse(item)) +} diff --git a/derive-impl/src/pyclass.rs b/derive-impl/src/pyclass.rs index 3e642ef3d..2b8689669 100644 --- a/derive-impl/src/pyclass.rs +++ b/derive-impl/src/pyclass.rs @@ -1,18 +1,15 @@ use super::Diagnostic; use crate::util::{ - format_doc, pyclass_ident_and_attrs, text_signature, ClassItemMeta, ContentItem, - ContentItemInner, ErrorVec, ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, - ALL_ALLOWED_NAMES, + format_doc, pyclass_ident_and_attrs, pyexception_ident_and_attrs, text_signature, + ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ExceptionItemMeta, ItemMeta, + ItemMetaInner, ItemNursery, SimpleItemMeta, ALL_ALLOWED_NAMES, }; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; use std::collections::HashMap; use std::str::FromStr; use syn::{ - parse::{Parse, ParseStream, Result as ParsingResult}, - parse_quote, - spanned::Spanned, - Attribute, AttributeArgs, Ident, Item, LitStr, Meta, NestedMeta, Result, Token, + parse_quote, spanned::Spanned, Attribute, AttributeArgs, Ident, Item, Meta, NestedMeta, Result, }; use syn_ext::ext::*; @@ -99,7 +96,7 @@ fn extract_items_into_context<'a, Item>( context.errors.ok_or_push(context.member_items.validate()); } -pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result { +pub(crate) fn impl_pyclass_impl(attr: AttributeArgs, item: Item) -> Result { let mut context = ImplContext::default(); let mut tokens = match item { Item::Impl(mut imp) => { @@ -340,8 +337,8 @@ fn generate_class_def( let base_class = if is_pystruct { Some(quote! { rustpython_vm::builtins::PyTuple }) } else { - base.map(|typ| { - let typ = Ident::new(&typ, ident.span()); + base.as_ref().map(|typ| { + let typ = Ident::new(typ, ident.span()); quote_spanned! { ident.span() => #typ } }) } @@ -364,6 +361,13 @@ fn generate_class_def( } }); + let base_or_object = if let Some(base) = base { + let base = Ident::new(&base, ident.span()); + quote! { #base } + } else { + quote! { ::rustpython_vm::builtins::PyBaseObject } + }; + let tokens = quote! { impl ::rustpython_vm::class::PyClassDef for #ident { const NAME: &'static str = #name; @@ -372,6 +376,8 @@ fn generate_class_def( const DOC: Option<&'static str> = #doc; const BASICSIZE: usize = #basicsize; const UNHASHABLE: bool = #unhashable; + + type Base = #base_or_object; } impl ::rustpython_vm::class::StaticType for #ident { @@ -413,9 +419,87 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { + ctx.types.#ctx_type_ident + } + } + } + } else { + quote! {} + }; + + let empty_impl = if let Some(attrs) = class_meta.impl_attrs()? { + let attrs: Meta = parse_quote! (#attrs); + quote! { + #[pyclass(#attrs)] + impl #ident {} + } + } else { + quote! {} + }; + let ret = quote! { + #derive_trace #item + #maybe_trace_code #class_def + #impl_payload + #empty_impl }; Ok(ret) } @@ -429,87 +513,125 @@ pub(crate) fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result Result { - let class_name = parse_vec_ident(&attr, &item, 0, "first 'class_name'")?; - let base_class_name = parse_vec_ident(&attr, &item, 1, "second 'base_class_name'")?; + let (ident, _attrs) = pyexception_ident_and_attrs(&item)?; + let fake_ident = Ident::new("pyclass", item.span()); + let class_meta = ExceptionItemMeta::from_nested(ident.clone(), fake_ident, attr.into_iter())?; + let class_name = class_meta.class_name()?; - // We also need to strip `Py` prefix from `class_name`, - // due to implementation and Python naming conventions mismatch: - // `PyKeyboardInterrupt` -> `KeyboardInterrupt` - let class_name = class_name - .strip_prefix("Py") - .ok_or_else(|| err_span!(item, "We require 'class_name' to have 'Py' prefix"))?; + let base_class_name = class_meta.base()?; + let impl_payload = if let Some(ctx_type_name) = class_meta.ctx_name()? { + let ctx_type_ident = Ident::new(&ctx_type_name, ident.span()); // FIXME span + + // We need this to make extend mechanism work: + quote! { + impl ::rustpython_vm::PyPayload for #ident { + fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { + ctx.exceptions.#ctx_type_ident + } + } + } + } else { + quote! {} + }; + let impl_pyclass = if class_meta.has_impl()? { + quote! { + #[pyexception] + impl #ident {} + } + } else { + quote! {} + }; - // We just "proxy" it into `pyclass` macro, because, exception is a class. let ret = quote! { #[pyclass(module = false, name = #class_name, base = #base_class_name)] #item + #impl_payload + #impl_pyclass }; Ok(ret) } -pub(crate) fn impl_define_exception(exc_def: PyExceptionDef) -> Result { - let PyExceptionDef { - class_name, - base_class, - ctx_name, - docs, - slot_new, - init, - } = exc_def; - - // We need this method, because of how `CPython` copies `__new__` - // from `BaseException` in `SimpleExtendsException` macro. - // See: `BaseException_new` - let slot_new_impl = match slot_new { - Some(slot_call) => quote! { #slot_call(cls, args, vm) }, - None => quote! { #base_class::slot_new(cls, args, vm) }, +pub(crate) fn impl_pyexception_impl(attr: AttributeArgs, item: Item) -> Result { + let Item::Impl(imp) = item else { + return Ok(item.into_token_stream()); }; - // We need this method, because of how `CPython` copies `__init__` - // from `BaseException` in `SimpleExtendsException` macro. - // See: `(initproc)BaseException_init` - // spell-checker:ignore initproc - let init_method = match init { - Some(init_def) => quote! { #init_def(zelf, args, vm) }, - None => quote! { #base_class::slot_init(zelf, args, vm) }, - }; + if !attr.is_empty() { + return Err(syn::Error::new_spanned( + &attr[0], + "#[pyexception] impl doesn't allow attrs. Use #[pyclass] instead.", + )); + } - let ret = quote! { - #[pyexception(#class_name, #base_class)] - #[derive(Debug)] - #[doc = #docs] - pub struct #class_name {} - - // We need this to make extend mechanism work: - impl ::rustpython_vm::PyPayload for #class_name { - fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { - ctx.exceptions.#ctx_name + let mut has_slot_new = false; + let mut has_slot_init = false; + let syn::ItemImpl { + generics, + self_ty, + items, + .. + } = &imp; + for item in items { + // FIXME: better detection or correct wrapper implementation + let Some(ident) = item.get_ident() else { + continue; + }; + let item_name = ident.to_string(); + match item_name.as_str() { + "slot_new" => { + has_slot_new = true; } + "slot_init" => { + has_slot_init = true; + } + _ => continue, } + } - #[pyclass(flags(BASETYPE, HAS_DICT))] - impl #class_name { + let slot_new = if has_slot_new { + quote!() + } else { + quote! { #[pyslot] pub(crate) fn slot_new( cls: ::rustpython_vm::builtins::PyTypeRef, args: ::rustpython_vm::function::FuncArgs, vm: &::rustpython_vm::VirtualMachine, ) -> ::rustpython_vm::PyResult { - #slot_new_impl - } - - #[pyslot] - #[pymethod(name="__init__")] - pub(crate) fn slot_init( - zelf: PyObjectRef, - args: ::rustpython_vm::function::FuncArgs, - vm: &::rustpython_vm::VirtualMachine, - ) -> ::rustpython_vm::PyResult<()> { - #init_method + ::Base::slot_new(cls, args, vm) } } }; - Ok(ret) + + // We need this method, because of how `CPython` copies `__init__` + // from `BaseException` in `SimpleExtendsException` macro. + // See: `(initproc)BaseException_init` + // spell-checker:ignore initproc + let slot_init = if has_slot_init { + quote!() + } else { + // FIXME: this is a generic logic for types not only for exceptions + quote! { + #[pyslot] + #[pymethod(name="__init__")] + pub(crate) fn slot_init( + zelf: ::rustpython_vm::PyObjectRef, + args: ::rustpython_vm::function::FuncArgs, + vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult<()> { + ::Base::slot_init(zelf, args, vm) + } + } + }; + Ok(quote! { + #[pyclass(flags(BASETYPE, HAS_DICT))] + impl #generics #self_ty { + #(#items)* + + #slot_new + #slot_init + } + }) } /// #[pymethod] and #[pyclassmethod] @@ -1425,50 +1547,7 @@ where Ok((result, cfgs)) } -#[derive(Debug)] -pub struct PyExceptionDef { - pub class_name: Ident, - pub base_class: Ident, - pub ctx_name: Ident, - pub docs: LitStr, - - /// Holds optional `slot_new` slot to be used instead of a default one: - pub slot_new: Option, - /// We also store `__init__` magic method, that can - pub init: Option, -} - -impl Parse for PyExceptionDef { - fn parse(input: ParseStream) -> ParsingResult { - let class_name: Ident = input.parse()?; - input.parse::()?; - - let base_class: Ident = input.parse()?; - input.parse::()?; - - let ctx_name: Ident = input.parse()?; - input.parse::()?; - - let docs: LitStr = input.parse()?; - input.parse::>()?; - - let slot_new: Option = input.parse()?; - input.parse::>()?; - - let init: Option = input.parse()?; - input.parse::>()?; // leading `,` - - Ok(PyExceptionDef { - class_name, - base_class, - ctx_name, - docs, - slot_new, - init, - }) - } -} - +#[allow(dead_code)] fn parse_vec_ident( attr: &[NestedMeta], item: &Item, diff --git a/derive-impl/src/pymodule.rs b/derive-impl/src/pymodule.rs index 47e373b7f..329c1dcd8 100644 --- a/derive-impl/src/pymodule.rs +++ b/derive-impl/src/pymodule.rs @@ -2,7 +2,7 @@ use crate::error::Diagnostic; use crate::util::{ format_doc, iter_use_idents, pyclass_ident_and_attrs, text_signature, AttrItemMeta, AttributeExt, ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ItemMeta, ItemNursery, - SimpleItemMeta, ALL_ALLOWED_NAMES, + ModuleItemMeta, SimpleItemMeta, ALL_ALLOWED_NAMES, }; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, ToTokens}; @@ -45,7 +45,9 @@ impl FromStr for AttrName { #[derive(Default)] struct ModuleContext { name: String, - module_extend_items: ItemNursery, + function_items: FunctionNursery, + attribute_items: ItemNursery, + has_extend_module: bool, // TODO: check if `fn extend_module` exists errors: Vec, } @@ -56,7 +58,7 @@ pub fn impl_pymodule(attr: AttributeArgs, module_item: Item) -> Result Result Result = #doc; + }, + parse_quote! { + pub(crate) fn __module_def( + ctx: &::rustpython_vm::Context, + ) -> &'static ::rustpython_vm::builtins::PyModuleDef { + DEF.get_or_init(|| { + let mut def = ::rustpython_vm::builtins::PyModuleDef { + name: ctx.intern_str(MODULE_NAME), + doc: DOC.map(|doc| ctx.intern_str(doc)), + slots: Default::default(), + }; + def.slots.exec = Some(extend_module); + def + }) + } + }, + parse_quote! { + #[allow(dead_code)] + pub(crate) fn make_module( + vm: &::rustpython_vm::VirtualMachine + ) -> ::rustpython_vm::PyRef<::rustpython_vm::builtins::PyModule> { + use ::rustpython_vm::PyPayload; + let module = ::rustpython_vm::builtins::PyModule::from_def(__module_def(&vm.ctx)).into_ref(&vm.ctx); + __init_dict(vm, &module); + extend_module(vm, &module).unwrap(); + module + } + }, + ]); + } + if !is_submodule && !context.has_extend_module { + items.push(parse_quote! { + pub(crate) fn extend_module(vm: &::rustpython_vm::VirtualMachine, module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>) -> ::rustpython_vm::PyResult<()> { + __extend_module(vm, module); + Ok(()) + } + }); + } items.extend(iter_chain![ parse_quote! { - pub(crate) const MODULE_NAME: &'static str = #module_name; - }, - parse_quote! { - pub(crate) const DOC: Option<&'static str> = #doc; - }, - parse_quote! { - pub(crate) fn extend_module( - vm: &::rustpython_vm::VirtualMachine, - module: &::rustpython_vm::PyObject, - ) { - #module_extend_items + ::rustpython_vm::common::static_cell! { + pub(crate) static DEF: ::rustpython_vm::builtins::PyModuleDef; } }, parse_quote! { - #[allow(dead_code)] - pub(crate) fn make_module( - vm: &::rustpython_vm::VirtualMachine - ) -> ::rustpython_vm::PyObjectRef { - let module = vm.new_module(MODULE_NAME, vm.ctx.new_dict(), DOC); - extend_module(vm, &module); - module + pub(crate) fn __init_attributes( + vm: &::rustpython_vm::VirtualMachine, + module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>, + ) { + #( + super::#withs::__init_attributes(vm, module); + )* + let ctx = &vm.ctx; + #attribute_items + } + }, + parse_quote! { + pub(crate) fn __extend_module( + vm: &::rustpython_vm::VirtualMachine, + module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>, + ) { + __init_methods(vm, module); + __init_attributes(vm, module); + } + }, + parse_quote! { + // TODO: remove once PyMethodDef done + pub(crate) fn __init_methods( + vm: &::rustpython_vm::VirtualMachine, + module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>, + ) { + #( + super::#withs::__init_methods(vm, module); + )* + let ctx = &vm.ctx; + #function_items + } + }, + parse_quote! { + pub(crate) fn __init_dict( + vm: &::rustpython_vm::VirtualMachine, + module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>, + ) { + ::rustpython_vm::builtins::PyModule::__init_dict_from_def(vm, module); } }, ]); @@ -248,6 +321,53 @@ where Ok((result, cfgs)) } +#[derive(Default)] +struct FunctionNursery { + items: Vec, +} + +struct FunctionNurseryItem { + py_names: Vec, + cfgs: Vec, + ident: Ident, + #[allow(dead_code)] + doc: String, + tokens: TokenStream, +} + +impl FunctionNursery { + fn add_item(&mut self, item: FunctionNurseryItem) { + self.items.push(item); + } + + fn validate(self) -> Result { + let mut name_set = HashSet::new(); + for item in &self.items { + for py_name in &item.py_names { + if !name_set.insert((py_name.to_owned(), &item.cfgs)) { + bail_span!(item.ident, "duplicate method name `{}`", py_name); + } + } + } + Ok(ValidatedFunctionNursery(self)) + } +} + +struct ValidatedFunctionNursery(FunctionNursery); + +impl ToTokens for ValidatedFunctionNursery { + fn to_tokens(&self, tokens: &mut TokenStream) { + for item in &self.0.items { + let cfgs = &item.cfgs; + let item_tokens = &item.tokens; + tokens.extend(quote! { + #(#cfgs)* + #item_tokens + }); + } + } +} + /// #[pyfunction] struct FunctionItem { inner: ContentItemInner, @@ -318,7 +438,7 @@ impl ModuleItem for FunctionItem { let py_name = item_meta.simple_name()?; let sig_doc = text_signature(func.sig(), &py_name); - let (tokens, py_names) = { + let (tokens, py_names, doc) = { let module = args.module_name(); let doc = args.attrs.doc().or_else(|| { crate::doc::Database::shared() @@ -332,10 +452,10 @@ impl ModuleItem for FunctionItem { } else { sig_doc }; - let doc = quote!(.with_doc(#doc.to_owned(), &vm.ctx)); + let with_doc = quote!(.with_doc(#doc.to_owned(), &vm.ctx)); let new_func = quote_spanned!(ident.span()=> vm.ctx.make_func_def(vm.ctx.intern_str(#py_name), #ident) - #doc + #with_doc .into_function() .with_module(vm.new_pyobj(#module.to_owned())) .into_ref(&vm.ctx) @@ -348,6 +468,7 @@ impl ModuleItem for FunctionItem { vm.__module_set_attr(module, #py_name, func).unwrap(); }}, vec![py_name], + doc, ) } else { let mut py_names = HashSet::new(); @@ -381,17 +502,18 @@ impl ModuleItem for FunctionItem { } }}, py_names, + doc, ) } }; - args.context.module_extend_items.add_item( - ident.clone(), + args.context.function_items.add_item(FunctionNurseryItem { + ident: ident.to_owned(), py_names, - args.cfgs.to_vec(), + cfgs: args.cfgs.to_vec(), + doc, tokens, - 10, - )?; + }); Ok(()) } } @@ -432,8 +554,8 @@ impl ModuleItem for ClassItem { class_meta.class_name()? }; let class_new = quote_spanned!(ident.span() => - let new_class = <#ident as ::rustpython_vm::class::PyClassImpl>::make_class(&vm.ctx); - new_class.set_attr(rustpython_vm::identifier!(vm, __module__), vm.new_pyobj(#module_name)); + let new_class = <#ident as ::rustpython_vm::class::PyClassImpl>::make_class(ctx); + new_class.set_attr(rustpython_vm::identifier!(ctx, __module__), vm.new_pyobj(#module_name)); ); (class_name, class_new) }; @@ -473,7 +595,7 @@ impl ModuleItem for ClassItem { }, }; - args.context.module_extend_items.add_item( + args.context.attribute_items.add_item( ident.clone(), py_names, args.cfgs.to_vec(), @@ -561,7 +683,7 @@ impl ModuleItem for AttributeItem { let tokens = quote_spanned! { ident.span() => vm.__module_set_attr(module, #py_name, vm.new_pyobj(#ident)).unwrap(); }; - args.context.module_extend_items.add_item( + args.context.attribute_items.add_item( ident.clone(), vec![py_name], cfgs.clone(), @@ -624,7 +746,7 @@ impl ModuleItem for AttributeItem { }; args.context - .module_extend_items + .attribute_items .add_item(ident, py_names, cfgs, tokens, 1)?; Ok(()) diff --git a/derive-impl/src/pytraverse.rs b/derive-impl/src/pytraverse.rs new file mode 100644 index 000000000..93aa233a1 --- /dev/null +++ b/derive-impl/src/pytraverse.rs @@ -0,0 +1,138 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Attribute, DeriveInput, Field, Meta, MetaList, NestedMeta, Result}; + +struct TraverseAttr { + /// set to `true` if the attribute is `#[pytraverse(skip)]` + skip: bool, +} + +const ATTR_TRAVERSE: &str = "pytraverse"; + +/// get the `#[pytraverse(..)]` attribute from the struct +fn valid_get_traverse_attr_from_meta_list(list: &MetaList) -> Result { + let find_skip_and_only_skip = || { + let len = list.nested.len(); + if len != 1 { + return None; + } + let mut iter = list.nested.iter(); + // we have checked the length, so unwrap is safe + let first_arg = iter.next().unwrap(); + let skip = match first_arg { + NestedMeta::Meta(Meta::Path(path)) => match path.is_ident("skip") { + true => true, + false => return None, + }, + _ => return None, + }; + Some(skip) + }; + let skip = find_skip_and_only_skip().ok_or_else(|| { + err_span!( + list, + "only support attr is #[pytraverse(skip)], got arguments: {:?}", + list.nested + ) + })?; + Ok(TraverseAttr { skip }) +} + +/// only accept `#[pytraverse(skip)]` for now +fn pytraverse_arg(attr: &Attribute) -> Option> { + if !attr.path.is_ident(ATTR_TRAVERSE) { + return None; + } + let ret = || { + let parsed = attr.parse_meta()?; + if let Meta::List(list) = parsed { + valid_get_traverse_attr_from_meta_list(&list) + } else { + bail_span!(attr, "pytraverse must be a list, like #[pytraverse(skip)]") + } + }; + Some(ret()) +} + +fn field_to_traverse_code(field: &Field) -> Result { + let pytraverse_attrs = field + .attrs + .iter() + .filter_map(pytraverse_arg) + .collect::, _>>()?; + let do_trace = if pytraverse_attrs.len() > 1 { + bail_span!( + field, + "found multiple #[pytraverse] attributes on the same field, expect at most one" + ) + } else if pytraverse_attrs.is_empty() { + // default to always traverse every field + true + } else { + !pytraverse_attrs[0].skip + }; + let name = field.ident.as_ref().ok_or_else(|| { + syn::Error::new_spanned( + field.clone(), + "Field should have a name in non-tuple struct", + ) + })?; + if do_trace { + Ok(quote!( + ::rustpython_vm::object::Traverse::traverse(&self.#name, tracer_fn); + )) + } else { + Ok(quote!()) + } +} + +/// not trace corresponding field +fn gen_trace_code(item: &mut DeriveInput) -> Result { + match &mut item.data { + syn::Data::Struct(s) => { + let fields = &mut s.fields; + match fields { + syn::Fields::Named(ref mut fields) => { + let res: Vec = fields + .named + .iter_mut() + .map(|f| -> Result { field_to_traverse_code(f) }) + .collect::>()?; + let res = res.into_iter().collect::(); + Ok(res) + } + syn::Fields::Unnamed(fields) => { + let res: TokenStream = (0..fields.unnamed.len()) + .map(|i| { + let i = syn::Index::from(i); + quote!( + ::rustpython_vm::object::Traverse::traverse(&self.#i, tracer_fn); + ) + }) + .collect(); + Ok(res) + } + _ => Err(syn::Error::new_spanned( + fields, + "Only named and unnamed fields are supported", + )), + } + } + _ => Err(syn::Error::new_spanned(item, "Only structs are supported")), + } +} + +pub(crate) fn impl_pytraverse(mut item: DeriveInput) -> Result { + let trace_code = gen_trace_code(&mut item)?; + + let ty = &item.ident; + + let ret = quote! { + unsafe impl ::rustpython_vm::object::Traverse for #ty { + fn traverse(&self, tracer_fn: &mut ::rustpython_vm::object::TraverseFn) { + #trace_code + } + } + }; + Ok(ret) +} diff --git a/derive-impl/src/util.rs b/derive-impl/src/util.rs index 92bb4629f..c0979f080 100644 --- a/derive-impl/src/util.rs +++ b/derive-impl/src/util.rs @@ -178,6 +178,10 @@ impl ItemMetaInner { Ok(value) } + pub fn _has_key(&self, key: &str) -> Result { + Ok(matches!(self.meta_map.get(key), Some((_, _)))) + } + pub fn _bool(&self, key: &str) -> Result { let value = if let Some((_, meta)) = self.meta_map.get(key) { match meta { @@ -193,6 +197,21 @@ impl ItemMetaInner { }; Ok(value) } + + pub fn _optional_list( + &self, + key: &str, + ) -> Result>> { + let value = if let Some((_, meta)) = self.meta_map.get(key) { + let Meta::List(syn::MetaList { path: _, nested, .. }) = meta else { + bail_span!(meta, "#[{}({}(...))] must be a list", self.meta_name(), key) + }; + Some(nested.into_iter()) + } else { + None + }; + Ok(value) + } } pub(crate) trait ItemMeta: Sized { @@ -247,6 +266,38 @@ impl ItemMeta for SimpleItemMeta { } } +pub(crate) struct ModuleItemMeta(pub ItemMetaInner); + +impl ItemMeta for ModuleItemMeta { + const ALLOWED_NAMES: &'static [&'static str] = &["name", "with", "sub"]; + + fn from_inner(inner: ItemMetaInner) -> Self { + Self(inner) + } + fn inner(&self) -> &ItemMetaInner { + &self.0 + } +} + +impl ModuleItemMeta { + pub fn sub(&self) -> Result { + self.inner()._bool("sub") + } + pub fn with(&self) -> Result> { + let mut withs = Vec::new(); + let Some(nested) = self.inner()._optional_list("with")? else { + return Ok(withs); + }; + for meta in nested { + let NestedMeta::Meta(Meta::Path(path)) = meta else { + bail_span!(meta, "#[pymodule(with(...))] arguments should be paths") + }; + withs.push(path); + } + Ok(withs) + } +} + pub(crate) struct AttrItemMeta(pub ItemMetaInner); impl ItemMeta for AttrItemMeta { @@ -263,8 +314,15 @@ impl ItemMeta for AttrItemMeta { pub(crate) struct ClassItemMeta(ItemMetaInner); impl ItemMeta for ClassItemMeta { - const ALLOWED_NAMES: &'static [&'static str] = - &["module", "name", "base", "metaclass", "unhashable"]; + const ALLOWED_NAMES: &'static [&'static str] = &[ + "module", + "name", + "base", + "metaclass", + "unhashable", + "impl", + "traverse", + ]; fn from_inner(inner: ItemMetaInner) -> Self { Self(inner) @@ -296,6 +354,10 @@ impl ClassItemMeta { ) } + pub fn ctx_name(&self) -> Result> { + self.inner()._optional_str("ctx") + } + pub fn base(&self) -> Result> { self.inner()._optional_str("base") } @@ -339,6 +401,10 @@ impl ClassItemMeta { Ok(value) } + pub fn impl_attrs(&self) -> Result> { + self.inner()._optional_str("impl") + } + // pub fn mandatory_module(&self) -> Result { // let inner = self.inner(); // let value = self.module().ok().flatten(). @@ -351,6 +417,64 @@ impl ClassItemMeta { // } } +pub(crate) struct ExceptionItemMeta(ClassItemMeta); + +impl ItemMeta for ExceptionItemMeta { + const ALLOWED_NAMES: &'static [&'static str] = &["name", "base", "unhashable", "ctx", "impl"]; + + fn from_inner(inner: ItemMetaInner) -> Self { + Self(ClassItemMeta(inner)) + } + fn inner(&self) -> &ItemMetaInner { + &self.0 .0 + } +} + +impl ExceptionItemMeta { + pub fn class_name(&self) -> Result { + const KEY: &str = "name"; + let inner = self.inner(); + if let Some((_, meta)) = inner.meta_map.get(KEY) { + match meta { + Meta::NameValue(syn::MetaNameValue { + lit: syn::Lit::Str(lit), + .. + }) => return Ok(lit.value()), + Meta::Path(_) => { + return Ok({ + let type_name = inner.item_name(); + let Some(py_name) = type_name.as_str().strip_prefix("Py") else { + bail_span!( + inner.item_ident, + "#[pyexception] expects its underlying type to be named `Py` prefixed" + ) + }; + py_name.to_string() + }) + } + _ => {} + } + } + bail_span!( + inner.meta_ident, + "#[{attr_name}(name = ...)] must exist as a string. Try \ + #[{attr_name}(name)] to use rust type name.", + attr_name = inner.meta_name() + ) + } + + pub fn has_impl(&self) -> Result { + self.inner()._bool("impl") + } +} + +impl std::ops::Deref for ExceptionItemMeta { + type Target = ClassItemMeta; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + pub(crate) trait AttributeExt: SynAttributeExt { fn promoted_nested(&self) -> Result; fn ident_and_promoted_nested(&self) -> Result<(&Ident, PunctuatedNestedMeta)>; @@ -458,6 +582,17 @@ pub(crate) fn pyclass_ident_and_attrs(item: &syn::Item) -> Result<(&Ident, &[Att }) } +pub(crate) fn pyexception_ident_and_attrs(item: &syn::Item) -> Result<(&Ident, &[Attribute])> { + use syn::Item::*; + Ok(match item { + Struct(syn::ItemStruct { ident, attrs, .. }) => (ident, attrs), + Enum(syn::ItemEnum { ident, attrs, .. }) => (ident, attrs), + other => { + bail_span!(other, "#[pyexception] can only be on a struct or enum",) + } + }) +} + pub(crate) trait ErrorVec: Sized { fn into_error(self) -> Option; fn into_result(self) -> Result<()> { diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 1839b06e4..76b7e2348 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -19,24 +19,14 @@ pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream { derive_impl::pyclass(attr, item).into() } +/// Helper macro to define `Exception` types. +/// More-or-less is an alias to `pyclass` macro. +/// /// This macro serves a goal of generating multiple /// `BaseException` / `Exception` /// subtypes in a uniform and convenient manner. /// It looks like `SimpleExtendsException` in `CPython`. /// -/// -/// We need `ctx` to be ready to add -/// `properties` / `custom` constructors / slots / methods etc. -/// So, we use `extend_class!` macro as the second -/// step in exception type definition. -#[proc_macro] -pub fn define_exception(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input); - derive_impl::define_exception(input).into() -} - -/// Helper macro to define `Exception` types. -/// More-or-less is an alias to `pyclass` macro. #[proc_macro_attribute] pub fn pyexception(attr: TokenStream, item: TokenStream) -> TokenStream { let attr = parse_macro_input!(attr); @@ -91,3 +81,27 @@ pub fn pypayload(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); derive_impl::pypayload(input).into() } + +/// use on struct with named fields like `struct A{x:PyRef, y:PyRef}` to impl `Traverse` for datatype. +/// +/// use `#[pytraverse(skip)]` on fields you wish not to trace +/// +/// add `trace` attr to `#[pyclass]` to make it impl `MaybeTraverse` that will call `Traverse`'s `traverse` method so make it +/// traceable(Even from type-erased PyObject)(i.e. write `#[pyclass(trace)]`). +/// # Example +/// ```rust, ignore +/// #[pyclass(module = false, traverse)] +/// #[derive(Default, Traverse)] +/// pub struct PyList { +/// elements: PyRwLock>, +/// #[pytraverse(skip)] +/// len: AtomicCell, +/// } +/// ``` +/// This create both `MaybeTraverse` that call `Traverse`'s `traverse` method and `Traverse` that impl `Traverse` +/// for `PyList` which call elements' `traverse` method and ignore `len` field. +#[proc_macro_derive(Traverse, attributes(pytraverse))] +pub fn pytraverse(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let item = parse_macro_input!(item); + derive_impl::pytraverse(item).into() +} diff --git a/extra_tests/snippets/builtin_exceptions.py b/extra_tests/snippets/builtin_exceptions.py index 5f92f044e..4bff9c009 100644 --- a/extra_tests/snippets/builtin_exceptions.py +++ b/extra_tests/snippets/builtin_exceptions.py @@ -239,13 +239,13 @@ assert BaseException.__new__.__qualname__ == 'BaseException.__new__' assert BaseException.__init__.__qualname__ == 'BaseException.__init__' assert BaseException().__dict__ == {} -assert Exception.__new__.__qualname__ == 'Exception.__new__' -assert Exception.__init__.__qualname__ == 'Exception.__init__' +assert Exception.__new__.__qualname__ == 'Exception.__new__', Exception.__new__.__qualname__ +assert Exception.__init__.__qualname__ == 'Exception.__init__', Exception.__init__.__qualname__ assert Exception().__dict__ == {} # Extends `BaseException`, simple: -assert KeyboardInterrupt.__new__.__qualname__ == 'KeyboardInterrupt.__new__' +assert KeyboardInterrupt.__new__.__qualname__ == 'KeyboardInterrupt.__new__', KeyboardInterrupt.__new__.__qualname__ assert KeyboardInterrupt.__init__.__qualname__ == 'KeyboardInterrupt.__init__' assert KeyboardInterrupt().__dict__ == {} diff --git a/extra_tests/snippets/stdlib_collections_deque.py b/extra_tests/snippets/stdlib_collections_deque.py index 00331ac88..44498633b 100644 --- a/extra_tests/snippets/stdlib_collections_deque.py +++ b/extra_tests/snippets/stdlib_collections_deque.py @@ -17,20 +17,20 @@ def test_deque_iterator__new__positional_index(): klass = type(iter(deque())) # index between 0 and len - for s in ('abcd', range(2000)): + for s in ('abcd', range(200)): for i in range(len(s)): d = klass(deque(s), i) assert (list(d) == list(s)[i:]) # negative index - for s in ('abcd', range(2000)): - for i in range(-1000, 0): + for s in ('abcd', range(200)): + for i in range(-100, 0): d = klass(deque(s), i) assert (list(d) == list(s)) # index ge len - for s in ('abcd', range(2000)): - for i in range(len(s), 4000): + for s in ('abcd', range(200)): + for i in range(len(s), 400): d = klass(deque(s), i) assert (list(d) == list()) @@ -41,8 +41,8 @@ test_deque_iterator__new__positional_index() def test_deque_iterator__new__not_using_keyword_index(): klass = type(iter(deque())) - for s in ('abcd', range(2000)): - for i in range(-1000, 4000): + for s in ('abcd', range(200)): + for i in range(-100, 400): d = klass(deque(s), index=i) assert (list(d) == list(s)) @@ -54,20 +54,20 @@ def test_deque_reverse_iterator__new__positional_index(): klass = type(reversed(deque())) # index between 0 and len - for s in ('abcd', range(2000)): + for s in ('abcd', range(200)): for i in range(len(s)): d = klass(deque(s), i) assert (list(d) == list(reversed(s))[i:]) # negative index - for s in ('abcd', range(2000)): - for i in range(-1000, 0): + for s in ('abcd', range(200)): + for i in range(-100, 0): d = klass(deque(s), i) assert (list(d) == list(reversed(s))) # index ge len - for s in ('abcd', range(2000)): - for i in range(len(s), 4000): + for s in ('abcd', range(200)): + for i in range(len(s), 400): d = klass(deque(s), i) assert (list(d) == list()) @@ -78,8 +78,8 @@ test_deque_reverse_iterator__new__positional_index() def test_deque_reverse_iterator__new__not_using_keyword_index(): klass = type(reversed(deque())) - for s in ('abcd', range(2000)): - for i in range(-1000, 4000): + for s in ('abcd', range(200)): + for i in range(-100, 400): d = klass(deque(s), index=i) assert (list(d) == list(reversed(s))) diff --git a/src/lib.rs b/src/lib.rs index 086b97827..4c83a0cd3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,15 +99,12 @@ fn setup_main_module(vm: &VirtualMachine) -> PyResult { let main_module = vm.new_module("__main__", scope.globals.clone(), None); main_module .dict() - .and_then(|d| { - d.set_item("__annotations__", vm.ctx.new_dict().into(), vm) - .ok() - }) + .set_item("__annotations__", vm.ctx.new_dict().into(), vm) .expect("Failed to initialize __main__.__annotations__"); vm.sys_module .get_attr("modules", vm)? - .set_item("__main__", main_module, vm)?; + .set_item("__main__", main_module.into(), vm)?; Ok(scope) } diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index f53eb4921..edf288ab9 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -55,6 +55,7 @@ sha-1 = "0.10.0" sha2 = "0.10.2" sha3 = "0.10.1" blake2 = "0.10.4" +hmac = "0.12.1" ## unicode stuff unicode_names2 = { workspace = true } diff --git a/stdlib/src/array.rs b/stdlib/src/array.rs index bed79c200..bb3a6d061 100644 --- a/stdlib/src/array.rs +++ b/stdlib/src/array.rs @@ -1,8 +1,8 @@ // spell-checker:ignore typecode tofile tolist fromfile -use rustpython_vm::{PyObjectRef, VirtualMachine}; +use rustpython_vm::{builtins::PyModule, PyRef, VirtualMachine}; -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { let module = array::make_module(vm); let array = module @@ -62,7 +62,7 @@ mod array { }, types::{ AsBuffer, AsMapping, AsSequence, Comparable, Constructor, IterNext, - IterNextIterable, Iterable, PyComparisonOp, + IterNextIterable, Iterable, PyComparisonOp, Representable, }, AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, }, @@ -721,7 +721,15 @@ mod array { #[pyclass( flags(BASETYPE), - with(Comparable, AsBuffer, AsMapping, AsSequence, Iterable, Constructor) + with( + Comparable, + AsBuffer, + AsMapping, + AsSequence, + Iterable, + Constructor, + Representable + ) )] impl PyArray { fn read(&self) -> PyRwLockReadGuard<'_, ArrayContentType> { @@ -1113,23 +1121,6 @@ mod array { Ok(zelf) } - #[pymethod(magic)] - fn repr(zelf: &Py, vm: &VirtualMachine) -> PyResult { - let class = zelf.class(); - let class_name = class.name(); - if zelf.read().typecode() == 'u' { - if zelf.len() == 0 { - return Ok(format!("{class_name}('u')")); - } - return Ok(format!( - "{}('u', {})", - class_name, - crate::common::str::repr(&zelf.tounicode(vm)?) - )); - } - zelf.read().repr(&class_name, vm) - } - #[pymethod(magic)] pub(crate) fn len(&self) -> usize { self.read().len() @@ -1292,6 +1283,25 @@ mod array { } } + impl Representable for PyArray { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let class = zelf.class(); + let class_name = class.name(); + if zelf.read().typecode() == 'u' { + if zelf.len() == 0 { + return Ok(format!("{class_name}('u')")); + } + return Ok(format!( + "{}('u', {})", + class_name, + crate::common::str::repr(&zelf.tounicode(vm)?) + )); + } + zelf.read().repr(&class_name, vm) + } + } + static BUFFER_METHODS: BufferMethods = BufferMethods { obj_bytes: |buffer| buffer.obj_as::().get_bytes().into(), obj_bytes_mut: |buffer| buffer.obj_as::().get_bytes_mut().into(), @@ -1389,7 +1399,7 @@ mod array { } #[pyattr] - #[pyclass(name = "arrayiterator")] + #[pyclass(name = "arrayiterator", traverse)] #[derive(Debug, PyPayload)] pub struct PyArrayIter { internal: PyMutex>, @@ -1424,12 +1434,13 @@ mod array { } } - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct ReconstructorArgs { #[pyarg(positional)] arraytype: PyTypeRef, #[pyarg(positional)] typecode: PyStrRef, + #[pytraverse(skip)] #[pyarg(positional)] mformat_code: MachineFormatCode, #[pyarg(positional)] diff --git a/stdlib/src/bisect.rs b/stdlib/src/bisect.rs index aaab65d78..c301559d8 100644 --- a/stdlib/src/bisect.rs +++ b/stdlib/src/bisect.rs @@ -8,7 +8,7 @@ mod _bisect { PyObjectRef, PyResult, VirtualMachine, }; - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct BisectArgs { a: PyObjectRef, x: PyObjectRef, diff --git a/stdlib/src/blake2.rs b/stdlib/src/blake2.rs new file mode 100644 index 000000000..9b7da3327 --- /dev/null +++ b/stdlib/src/blake2.rs @@ -0,0 +1,19 @@ +// spell-checker:ignore usedforsecurity HASHXOF + +pub(crate) use _blake2::make_module; + +#[pymodule] +mod _blake2 { + use crate::hashlib::_hashlib::{local_blake2b, local_blake2s, BlakeHashArgs}; + use crate::vm::{PyPayload, PyResult, VirtualMachine}; + + #[pyfunction] + fn blake2b(args: BlakeHashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_blake2b(args).into_pyobject(vm)) + } + + #[pyfunction] + fn blake2s(args: BlakeHashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_blake2s(args).into_pyobject(vm)) + } +} diff --git a/stdlib/src/contextvars.rs b/stdlib/src/contextvars.rs index d21c9bfb1..f8b0e7053 100644 --- a/stdlib/src/contextvars.rs +++ b/stdlib/src/contextvars.rs @@ -5,8 +5,8 @@ mod _contextvars { use crate::vm::{ builtins::{PyFunction, PyStrRef, PyTypeRef}, function::{ArgCallable, FuncArgs, OptionalArg}, - types::Initializer, - PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + types::{Initializer, Representable}, + Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, }; #[pyattr] @@ -80,9 +80,10 @@ mod _contextvars { } #[pyattr] - #[pyclass(name)] + #[pyclass(name, traverse)] #[derive(Debug, PyPayload)] struct ContextVar { + #[pytraverse(skip)] #[allow(dead_code)] // TODO: RUSTPYTHON name: String, #[allow(dead_code)] // TODO: RUSTPYTHON @@ -99,7 +100,7 @@ mod _contextvars { default: OptionalArg, } - #[pyclass(with(Initializer))] + #[pyclass(with(Initializer, Representable))] impl ContextVar { #[pygetset] fn name(&self) -> String { @@ -133,17 +134,6 @@ mod _contextvars { fn class_getitem(_cls: PyTypeRef, _key: PyStrRef, _vm: &VirtualMachine) -> PyResult<()> { unimplemented!("ContextVar.__class_getitem__() is currently under construction") } - - #[pymethod(magic)] - fn repr(_zelf: PyRef, _vm: &VirtualMachine) -> String { - unimplemented!("", - // zelf.name.as_str(), - // zelf.default.map_or("", |x| PyStr::from(*x).as_str()), - // zelf.get_id() - // ) - } } impl Initializer for ContextVar { @@ -154,12 +144,25 @@ mod _contextvars { } } + impl Representable for ContextVar { + #[inline] + fn repr_str(_zelf: &Py, _vm: &VirtualMachine) -> PyResult { + unimplemented!("", + // zelf.name.as_str(), + // zelf.default.map_or("", |x| PyStr::from(*x).as_str()), + // zelf.get_id() + // ) + } + } + #[pyattr] #[pyclass(name = "Token")] #[derive(Debug, PyPayload)] struct ContextToken {} - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct ContextTokenOptions { #[pyarg(positional)] #[allow(dead_code)] // TODO: RUSTPYTHON @@ -172,7 +175,7 @@ mod _contextvars { old_value: PyObjectRef, } - #[pyclass(with(Initializer))] + #[pyclass(with(Initializer, Representable))] impl ContextToken { #[pygetset] fn var(&self, _vm: &VirtualMachine) -> PyObjectRef { @@ -183,11 +186,6 @@ mod _contextvars { fn old_value(&self, _vm: &VirtualMachine) -> PyObjectRef { unimplemented!("Token.old_value() is currently under construction") } - - #[pymethod(magic)] - fn repr(_zelf: PyRef, _vm: &VirtualMachine) -> String { - unimplemented!("") - } } impl Initializer for ContextToken { @@ -198,6 +196,13 @@ mod _contextvars { } } + impl Representable for ContextToken { + #[inline] + fn repr_str(_zelf: &Py, _vm: &VirtualMachine) -> PyResult { + unimplemented!("") + } + } + #[pyfunction] fn copy_context() {} } diff --git a/stdlib/src/csv.rs b/stdlib/src/csv.rs index f6960f380..c49dec079 100644 --- a/stdlib/src/csv.rs +++ b/stdlib/src/csv.rs @@ -152,10 +152,11 @@ mod _csv { reader: csv_core::Reader, } - #[pyclass(no_attr, module = "_csv", name = "reader")] + #[pyclass(no_attr, module = "_csv", name = "reader", traverse)] #[derive(PyPayload)] pub(super) struct Reader { iter: PyIter, + #[pytraverse(skip)] state: PyMutex, } @@ -242,10 +243,11 @@ mod _csv { writer: csv_core::Writer, } - #[pyclass(no_attr, module = "_csv", name = "writer")] + #[pyclass(no_attr, module = "_csv", name = "writer", traverse)] #[derive(PyPayload)] pub(super) struct Writer { write: PyObjectRef, + #[pytraverse(skip)] state: PyMutex, } diff --git a/stdlib/src/grp.rs b/stdlib/src/grp.rs index 86b826b79..959c984be 100644 --- a/stdlib/src/grp.rs +++ b/stdlib/src/grp.rs @@ -13,11 +13,14 @@ mod grp { use std::ptr::NonNull; #[pyattr] - #[pyclass(module = "grp", name = "struct_group")] + #[pyclass(module = "grp", name = "struct_group", traverse)] #[derive(PyStructSequence)] struct Group { + #[pytraverse(skip)] gr_name: String, + #[pytraverse(skip)] gr_passwd: String, + #[pytraverse(skip)] gr_gid: u32, gr_mem: PyListRef, } diff --git a/stdlib/src/hashlib.rs b/stdlib/src/hashlib.rs index f3ddb0091..6944c37f9 100644 --- a/stdlib/src/hashlib.rs +++ b/stdlib/src/hashlib.rs @@ -1,13 +1,15 @@ // spell-checker:ignore usedforsecurity HASHXOF -pub(crate) use hashlib::make_module; +pub(crate) use _hashlib::make_module; #[pymodule] -mod hashlib { +pub mod _hashlib { use crate::common::lock::PyRwLock; use crate::vm::{ builtins::{PyBytes, PyStrRef, PyTypeRef}, - function::{ArgBytesLike, FuncArgs, OptionalArg}, + convert::ToPyObject, + function::{ArgBytesLike, ArgStrOrBytesLike, FuncArgs, OptionalArg}, + protocol::PyBuffer, PyObjectRef, PyPayload, PyResult, VirtualMachine, }; use blake2::{Blake2b512, Blake2s256}; @@ -19,7 +21,7 @@ mod hashlib { use sha2::{Sha224, Sha256, Sha384, Sha512}; use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; - #[derive(FromArgs)] + #[derive(FromArgs, Debug)] #[allow(unused)] struct NewHashArgs { #[pyarg(positional)] @@ -32,9 +34,9 @@ mod hashlib { #[derive(FromArgs)] #[allow(unused)] - struct BlakeHashArgs { + pub struct BlakeHashArgs { #[pyarg(positional, optional)] - data: OptionalArg, + pub data: OptionalArg, #[pyarg(named, default = "true")] usedforsecurity: bool, } @@ -48,11 +50,11 @@ mod hashlib { } } - #[derive(FromArgs)] + #[derive(FromArgs, Debug)] #[allow(unused)] - struct HashArgs { + pub struct HashArgs { #[pyarg(any, optional)] - string: OptionalArg, + pub string: OptionalArg, #[pyarg(named, default = "true")] usedforsecurity: bool, } @@ -81,11 +83,11 @@ mod hashlib { } #[pyattr] - #[pyclass(module = "hashlib", name = "HASH")] + #[pyclass(module = "_hashlib", name = "HASH")] #[derive(PyPayload)] - struct PyHasher { - name: String, - ctx: PyRwLock, + pub struct PyHasher { + pub name: String, + pub ctx: PyRwLock, } impl std::fmt::Debug for PyHasher { @@ -105,7 +107,7 @@ mod hashlib { #[pyslot] fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("cannot create 'hashlib.HASH' instances".into())) + Err(vm.new_type_error("cannot create '_hashlib.HASH' instances".into())) } #[pygetset] @@ -145,9 +147,9 @@ mod hashlib { } #[pyattr] - #[pyclass(module = "hashlib", name = "HASHXOF")] + #[pyclass(module = "_hashlib", name = "HASHXOF")] #[derive(PyPayload)] - struct PyHasherXof { + pub struct PyHasherXof { name: String, ctx: PyRwLock, } @@ -169,7 +171,7 @@ mod hashlib { #[pyslot] fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("cannot create 'hashlib.HASHXOF' instances".into())) + Err(vm.new_type_error("cannot create '_hashlib.HASHXOF' instances".into())) } #[pygetset] @@ -211,108 +213,147 @@ mod hashlib { #[pyfunction(name = "new")] fn hashlib_new(args: NewHashArgs, vm: &VirtualMachine) -> PyResult { match args.name.as_str().to_lowercase().as_str() { - "md5" => Ok(md5(args.into()).into_pyobject(vm)), - "sha1" => Ok(sha1(args.into()).into_pyobject(vm)), - "sha224" => Ok(sha224(args.into()).into_pyobject(vm)), - "sha256" => Ok(sha256(args.into()).into_pyobject(vm)), - "sha384" => Ok(sha384(args.into()).into_pyobject(vm)), - "sha512" => Ok(sha512(args.into()).into_pyobject(vm)), - "sha3_224" => Ok(sha3_224(args.into()).into_pyobject(vm)), - "sha3_256" => Ok(sha3_256(args.into()).into_pyobject(vm)), - "sha3_384" => Ok(sha3_384(args.into()).into_pyobject(vm)), - "sha3_512" => Ok(sha3_512(args.into()).into_pyobject(vm)), - "shake_128" => Ok(shake_128(args.into()).into_pyobject(vm)), - "shake_256" => Ok(shake_256(args.into()).into_pyobject(vm)), - "blake2b" => Ok(blake2b(args.into()).into_pyobject(vm)), - "blake2s" => Ok(blake2s(args.into()).into_pyobject(vm)), + "md5" => Ok(local_md5(args.into()).into_pyobject(vm)), + "sha1" => Ok(local_sha1(args.into()).into_pyobject(vm)), + "sha224" => Ok(local_sha224(args.into()).into_pyobject(vm)), + "sha256" => Ok(local_sha256(args.into()).into_pyobject(vm)), + "sha384" => Ok(local_sha384(args.into()).into_pyobject(vm)), + "sha512" => Ok(local_sha512(args.into()).into_pyobject(vm)), + "sha3_224" => Ok(local_sha3_224(args.into()).into_pyobject(vm)), + "sha3_256" => Ok(local_sha3_256(args.into()).into_pyobject(vm)), + "sha3_384" => Ok(local_sha3_384(args.into()).into_pyobject(vm)), + "sha3_512" => Ok(local_sha3_512(args.into()).into_pyobject(vm)), + "shake_128" => Ok(local_shake_128(args.into()).into_pyobject(vm)), + "shake_256" => Ok(local_shake_256(args.into()).into_pyobject(vm)), + "blake2b" => Ok(local_blake2b(args.into()).into_pyobject(vm)), + "blake2s" => Ok(local_blake2s(args.into()).into_pyobject(vm)), other => Err(vm.new_value_error(format!("Unknown hashing algorithm: {other}"))), } } - #[pyfunction] - fn md5(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_md5")] + pub fn local_md5(args: HashArgs) -> PyHasher { PyHasher::new("md5", HashWrapper::new::(args.string)) } - #[pyfunction] - fn sha1(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_sha1")] + pub fn local_sha1(args: HashArgs) -> PyHasher { PyHasher::new("sha1", HashWrapper::new::(args.string)) } - #[pyfunction] - fn sha224(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_sha224")] + pub fn local_sha224(args: HashArgs) -> PyHasher { PyHasher::new("sha224", HashWrapper::new::(args.string)) } - #[pyfunction] - fn sha256(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_sha256")] + pub fn local_sha256(args: HashArgs) -> PyHasher { PyHasher::new("sha256", HashWrapper::new::(args.string)) } - #[pyfunction] - fn sha384(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_sha384")] + pub fn local_sha384(args: HashArgs) -> PyHasher { PyHasher::new("sha384", HashWrapper::new::(args.string)) } - #[pyfunction] - fn sha512(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_sha512")] + pub fn local_sha512(args: HashArgs) -> PyHasher { PyHasher::new("sha512", HashWrapper::new::(args.string)) } - #[pyfunction] - fn sha3_224(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_sha3_224")] + pub fn local_sha3_224(args: HashArgs) -> PyHasher { PyHasher::new("sha3_224", HashWrapper::new::(args.string)) } - #[pyfunction] - fn sha3_256(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_sha3_256")] + pub fn local_sha3_256(args: HashArgs) -> PyHasher { PyHasher::new("sha3_256", HashWrapper::new::(args.string)) } - #[pyfunction] - fn sha3_384(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_sha3_384")] + pub fn local_sha3_384(args: HashArgs) -> PyHasher { PyHasher::new("sha3_384", HashWrapper::new::(args.string)) } - #[pyfunction] - fn sha3_512(args: HashArgs) -> PyHasher { + #[pyfunction(name = "openssl_sha3_512")] + pub fn local_sha3_512(args: HashArgs) -> PyHasher { PyHasher::new("sha3_512", HashWrapper::new::(args.string)) } - #[pyfunction] - fn shake_128(args: HashArgs) -> PyHasherXof { + #[pyfunction(name = "openssl_shake_128")] + pub fn local_shake_128(args: HashArgs) -> PyHasherXof { PyHasherXof::new("shake_128", HashXofWrapper::new_shake_128(args.string)) } - #[pyfunction] - fn shake_256(args: HashArgs) -> PyHasherXof { + #[pyfunction(name = "openssl_shake_256")] + pub fn local_shake_256(args: HashArgs) -> PyHasherXof { PyHasherXof::new("shake_256", HashXofWrapper::new_shake_256(args.string)) } - #[pyfunction] - fn blake2b(args: BlakeHashArgs) -> PyHasher { + #[pyfunction(name = "openssl_blake2b")] + pub fn local_blake2b(args: BlakeHashArgs) -> PyHasher { PyHasher::new("blake2b", HashWrapper::new::(args.data)) } - #[pyfunction] - fn blake2s(args: BlakeHashArgs) -> PyHasher { + #[pyfunction(name = "openssl_blake2s")] + pub fn local_blake2s(args: BlakeHashArgs) -> PyHasher { PyHasher::new("blake2s", HashWrapper::new::(args.data)) } - trait ThreadSafeDynDigest: DynClone + DynDigest + Sync + Send {} + #[pyfunction] + fn compare_digest( + a: ArgStrOrBytesLike, + b: ArgStrOrBytesLike, + vm: &VirtualMachine, + ) -> PyResult { + fn is_str(arg: &ArgStrOrBytesLike) -> bool { + matches!(arg, ArgStrOrBytesLike::Str(_)) + } + + if is_str(&a) != is_str(&b) { + return Err(vm.new_type_error(format!( + "a bytes-like object is required, not '{}'", + b.as_object().class().name() + ))); + } + + let a_hash = a.borrow_bytes().to_vec(); + let b_hash = b.borrow_bytes().to_vec(); + + Ok((a_hash == b_hash).to_pyobject(vm)) + } + + #[derive(FromArgs, Debug)] + #[allow(unused)] + pub struct NewHMACHashArgs { + #[pyarg(positional)] + name: PyBuffer, + #[pyarg(any, optional)] + data: OptionalArg, + #[pyarg(named, default = "true")] + digestmod: bool, // TODO: RUSTPYTHON support functions & name functions + } + + #[pyfunction] + fn hmac_new(_args: NewHMACHashArgs, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("cannot create 'hmac' instances".into())) // TODO: RUSTPYTHON support hmac + } + + pub trait ThreadSafeDynDigest: DynClone + DynDigest + Sync + Send {} impl ThreadSafeDynDigest for T where T: DynClone + DynDigest + Sync + Send {} clone_trait_object!(ThreadSafeDynDigest); /// Generic wrapper patching around the hashing libraries. #[derive(Clone)] - struct HashWrapper { + pub struct HashWrapper { block_size: usize, inner: Box, } impl HashWrapper { - fn new(data: OptionalArg) -> Self + pub fn new(data: OptionalArg) -> Self where D: ThreadSafeDynDigest + BlockSizeUser + Default + 'static, { @@ -345,13 +386,13 @@ mod hashlib { } #[derive(Clone)] - enum HashXofWrapper { + pub enum HashXofWrapper { Shake128(Shake128), Shake256(Shake256), } impl HashXofWrapper { - fn new_shake_128(data: OptionalArg) -> Self { + pub fn new_shake_128(data: OptionalArg) -> Self { let mut h = HashXofWrapper::Shake128(Shake128::default()); if let OptionalArg::Present(d) = data { d.with_ref(|bytes| h.update(bytes)); @@ -359,7 +400,7 @@ mod hashlib { h } - fn new_shake_256(data: OptionalArg) -> Self { + pub fn new_shake_256(data: OptionalArg) -> Self { let mut h = HashXofWrapper::Shake256(Shake256::default()); if let OptionalArg::Present(d) = data { d.with_ref(|bytes| h.update(bytes)); diff --git a/stdlib/src/json.rs b/stdlib/src/json.rs index 31871130c..450930e6a 100644 --- a/stdlib/src/json.rs +++ b/stdlib/src/json.rs @@ -16,9 +16,10 @@ mod _json { use std::str::FromStr; #[pyattr(name = "make_scanner")] - #[pyclass(name = "Scanner")] + #[pyclass(name = "Scanner", traverse)] #[derive(Debug, PyPayload)] struct JsonScanner { + #[pytraverse(skip)] strict: bool, object_hook: Option, object_pairs_hook: Option, diff --git a/stdlib/src/json/machinery.rs b/stdlib/src/json/machinery.rs index 0568bab61..fc6b53086 100644 --- a/stdlib/src/json/machinery.rs +++ b/stdlib/src/json/machinery.rs @@ -105,10 +105,8 @@ pub struct DecodeError { } impl DecodeError { fn new(msg: impl Into, pos: usize) -> Self { - Self { - msg: msg.into(), - pos, - } + let msg = msg.into(); + Self { msg, pos } } } diff --git a/stdlib/src/lib.rs b/stdlib/src/lib.rs index c78f74449..2a1a7d5ce 100644 --- a/stdlib/src/lib.rs +++ b/stdlib/src/lib.rs @@ -13,7 +13,14 @@ mod contextvars; mod csv; mod dis; mod gc; + +mod blake2; mod hashlib; +mod md5; +mod sha1; +mod sha256; +mod sha3; + mod json; #[cfg(not(any(target_os = "ios", target_os = "android", target_arch = "wasm32")))] mod locale; @@ -100,7 +107,13 @@ pub fn get_module_inits() -> impl Iterator, StdlibInit "_csv" => csv::make_module, "_dis" => dis::make_module, "gc" => gc::make_module, - "hashlib" => hashlib::make_module, + "_hashlib" => hashlib::make_module, + "_sha1" => sha1::make_module, + "_sha3" => sha3::make_module, + "_sha256" => sha256::make_module, + // "_sha512" => sha512::make_module, // TODO: RUSPYTHON fix strange fail on vm: 'static type has not been initialized' + "_md5" => md5::make_module, + "_blake2" => blake2::make_module, "_json" => json::make_module, "math" => math::make_module, "pyexpat" => pyexpat::make_module, diff --git a/stdlib/src/math.rs b/stdlib/src/math.rs index ea3c93a75..9183ea7aa 100644 --- a/stdlib/src/math.rs +++ b/stdlib/src/math.rs @@ -885,7 +885,7 @@ mod math { } } - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct ProdArgs { #[pyarg(positional)] iterable: ArgIterable, diff --git a/stdlib/src/md5.rs b/stdlib/src/md5.rs new file mode 100644 index 000000000..833d217f5 --- /dev/null +++ b/stdlib/src/md5.rs @@ -0,0 +1,12 @@ +pub(crate) use _md5::make_module; + +#[pymodule] +mod _md5 { + use crate::hashlib::_hashlib::{local_md5, HashArgs}; + use crate::vm::{PyPayload, PyResult, VirtualMachine}; + + #[pyfunction] + fn md5(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_md5(args).into_pyobject(vm)) + } +} diff --git a/stdlib/src/mmap.rs b/stdlib/src/mmap.rs index ae0dbac3c..058b90f15 100644 --- a/stdlib/src/mmap.rs +++ b/stdlib/src/mmap.rs @@ -112,9 +112,28 @@ mod mmap { #[pyattr] use libc::{ MADV_DODUMP, MADV_DOFORK, MADV_DONTDUMP, MADV_DONTFORK, MADV_HUGEPAGE, MADV_HWPOISON, - MADV_MERGEABLE, MADV_NOHUGEPAGE, MADV_REMOVE, MADV_SOFT_OFFLINE, MADV_UNMERGEABLE, + MADV_MERGEABLE, MADV_NOHUGEPAGE, MADV_REMOVE, MADV_UNMERGEABLE, }; + #[cfg(any( + target_os = "android", + all( + target_os = "linux", + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "powerpc64", + target_arch = "s390x", + target_arch = "x86", + target_arch = "x86_64", + target_arch = "sparc64" + ) + ) + ))] + #[pyattr] + use libc::MADV_SOFT_OFFLINE; + #[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))] #[pyattr] use libc::{MAP_DENYWRITE, MAP_EXECUTABLE, MAP_POPULATE}; @@ -225,8 +244,9 @@ mod mmap { } #[cfg(not(target_os = "redox"))] - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] pub struct AdviseOptions { + #[pytraverse(skip)] #[pyarg(positional)] option: libc::c_int, #[pyarg(positional, default)] diff --git a/stdlib/src/pyexpat.rs b/stdlib/src/pyexpat.rs index 916636b45..41113f0fd 100644 --- a/stdlib/src/pyexpat.rs +++ b/stdlib/src/pyexpat.rs @@ -3,12 +3,12 @@ * */ -use crate::vm::{extend_module, PyObjectRef, VirtualMachine}; +use crate::vm::{builtins::PyModule, extend_module, PyRef, VirtualMachine}; -pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyRef { let module = _pyexpat::make_module(vm); - extend_module!(vm, module, { + extend_module!(vm, &module, { "errors" => _errors::make_module(vm), "model" => _model::make_module(vm), }); @@ -43,7 +43,7 @@ mod _pyexpat { type MutableObject = PyRwLock; #[pyattr] - #[pyclass(name = "xmlparser", module = false)] + #[pyclass(name = "xmlparser", module = false, traverse)] #[derive(Debug, PyPayload)] pub struct PyExpatLikeXmlParser { start_element: MutableObject, @@ -156,7 +156,7 @@ mod _pyexpat { } } - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] #[allow(dead_code)] struct ParserCreateArgs { #[pyarg(any, optional)] diff --git a/stdlib/src/pystruct.rs b/stdlib/src/pystruct.rs index c5dd6d1cc..488ad158f 100644 --- a/stdlib/src/pystruct.rs +++ b/stdlib/src/pystruct.rs @@ -20,6 +20,7 @@ pub(crate) mod _struct { }; use crossbeam_utils::atomic::AtomicCell; + #[derive(Traverse)] struct IntoStructFormatBytes(PyStrRef); impl TryFromObject for IntoStructFormatBytes { @@ -133,9 +134,10 @@ pub(crate) mod _struct { buffer.with_ref(|buf| format_spec.unpack(buf, vm)) } - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct UpdateFromArgs { buffer: ArgBytesLike, + #[pytraverse(skip)] #[pyarg(any, default = "0")] offset: isize, } @@ -154,11 +156,13 @@ pub(crate) mod _struct { } #[pyattr] - #[pyclass(name = "unpack_iterator")] + #[pyclass(name = "unpack_iterator", traverse)] #[derive(Debug, PyPayload)] struct UnpackIterator { + #[pytraverse(skip)] format_spec: FormatSpec, buffer: ArgBytesLike, + #[pytraverse(skip)] offset: AtomicCell, } @@ -231,9 +235,10 @@ pub(crate) mod _struct { } #[pyattr] - #[pyclass(name = "Struct")] + #[pyclass(name = "Struct", traverse)] #[derive(Debug, PyPayload)] struct PyStruct { + #[pytraverse(skip)] spec: FormatSpec, format: PyStrRef, } diff --git a/stdlib/src/re.rs b/stdlib/src/re.rs index 1642007fe..5417e03fc 100644 --- a/stdlib/src/re.rs +++ b/stdlib/src/re.rs @@ -76,10 +76,11 @@ mod re { /// Inner data for a match object. #[pyattr] - #[pyclass(module = "re", name = "Match")] - #[derive(PyPayload)] + #[pyclass(module = "re", name = "Match", traverse)] + #[derive(PyPayload, Traverse)] struct PyMatch { haystack: PyStrRef, + #[pytraverse(skip)] captures: Vec>>, } diff --git a/stdlib/src/select.rs b/stdlib/src/select.rs index 9d3afa0e4..586305b1d 100644 --- a/stdlib/src/select.rs +++ b/stdlib/src/select.rs @@ -1,9 +1,10 @@ use crate::vm::{ - builtins::PyListRef, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, + builtins::PyListRef, builtins::PyModule, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, + VirtualMachine, }; use std::{io, mem}; -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { #[cfg(windows)] crate::vm::stdlib::nt::init_winsock(); @@ -67,8 +68,10 @@ mod platform { pub use platform::timeval; use platform::RawFd; +#[derive(Traverse)] struct Selectable { obj: PyObjectRef, + #[pytraverse(skip)] fno: RawFd, } diff --git a/stdlib/src/sha1.rs b/stdlib/src/sha1.rs new file mode 100644 index 000000000..3820e7d96 --- /dev/null +++ b/stdlib/src/sha1.rs @@ -0,0 +1,12 @@ +pub(crate) use _sha1::make_module; + +#[pymodule] +mod _sha1 { + use crate::hashlib::_hashlib::{local_sha1, HashArgs}; + use crate::vm::{PyPayload, PyResult, VirtualMachine}; + + #[pyfunction] + fn sha1(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_sha1(args).into_pyobject(vm)) + } +} diff --git a/stdlib/src/sha256.rs b/stdlib/src/sha256.rs new file mode 100644 index 000000000..bae22fa4c --- /dev/null +++ b/stdlib/src/sha256.rs @@ -0,0 +1,17 @@ +pub(crate) use _sha256::make_module; + +#[pymodule] +mod _sha256 { + use crate::hashlib::_hashlib::{local_sha224, local_sha256, HashArgs}; + use crate::vm::{PyPayload, PyResult, VirtualMachine}; + + #[pyfunction] + fn sha224(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_sha224(args).into_pyobject(vm)) + } + + #[pyfunction] + fn sha256(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_sha256(args).into_pyobject(vm)) + } +} diff --git a/stdlib/src/sha3.rs b/stdlib/src/sha3.rs new file mode 100644 index 000000000..f0c1c5ef6 --- /dev/null +++ b/stdlib/src/sha3.rs @@ -0,0 +1,40 @@ +pub(crate) use _sha3::make_module; + +#[pymodule] +mod _sha3 { + use crate::hashlib::_hashlib::{ + local_sha3_224, local_sha3_256, local_sha3_384, local_sha3_512, local_shake_128, + local_shake_256, HashArgs, + }; + use crate::vm::{PyPayload, PyResult, VirtualMachine}; + + #[pyfunction] + fn sha3_224(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_sha3_224(args).into_pyobject(vm)) + } + + #[pyfunction] + fn sha3_256(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_sha3_256(args).into_pyobject(vm)) + } + + #[pyfunction] + fn sha3_384(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_sha3_384(args).into_pyobject(vm)) + } + + #[pyfunction] + fn sha3_512(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_sha3_512(args).into_pyobject(vm)) + } + + #[pyfunction] + fn shake_128(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_shake_128(args).into_pyobject(vm)) + } + + #[pyfunction] + fn shake_256(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(local_shake_256(args).into_pyobject(vm)) + } +} diff --git a/stdlib/src/sha512.rs b/stdlib/src/sha512.rs new file mode 100644 index 000000000..bb93fc9db --- /dev/null +++ b/stdlib/src/sha512.rs @@ -0,0 +1,20 @@ +// spell-checker:ignore usedforsecurity HASHXOF + +pub(crate) use _sha512::make_module; + +#[pymodule] +mod _sha512 { + use crate::hashlib::_hashlib::{HashArgs, HashWrapper, PyHasher}; + use crate::vm::{PyObjectRef, PyPayload, PyResult, VirtualMachine}; + use sha2::{Sha384, Sha512}; + + #[pyfunction(name = "sha384")] + fn sha384(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha384", HashWrapper::new::(args.string)).into_pyobject(vm)) + } + + #[pyfunction(name = "sha512")] + fn sha512(args: HashArgs, vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha512", HashWrapper::new::(args.string)).into_pyobject(vm)) + } +} diff --git a/stdlib/src/socket.rs b/stdlib/src/socket.rs index 08ca8496f..1543d56e5 100644 --- a/stdlib/src/socket.rs +++ b/stdlib/src/socket.rs @@ -1,8 +1,8 @@ -use crate::vm::{PyObjectRef, VirtualMachine}; +use crate::vm::{builtins::PyModule, PyRef, VirtualMachine}; #[cfg(feature = "ssl")] pub(super) use _socket::{sock_select, timeout_error_msg, PySocket, SelectKind}; -pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyRef { #[cfg(windows)] crate::vm::stdlib::nt::init_winsock(); _socket::make_module(vm) @@ -15,9 +15,9 @@ mod _socket { builtins::{PyBaseExceptionRef, PyListRef, PyStrRef, PyTupleRef, PyTypeRef}, convert::{IntoPyException, ToPyObject, TryFromBorrowedObject, TryFromObject}, function::{ArgBytesLike, ArgMemoryBuffer, Either, FsPath, OptionalArg, OptionalOption}, - types::{DefaultConstructor, Initializer}, + types::{DefaultConstructor, Initializer, Representable}, utils::ToCString, - AsObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, }; use crossbeam_utils::atomic::AtomicCell; use num_traits::ToPrimitive; @@ -309,6 +309,7 @@ mod _socket { any( target_arch = "aarch64", target_arch = "i686", + target_arch = "loongarch64", target_arch = "mips", target_arch = "powerpc", target_arch = "powerpc64", @@ -359,6 +360,7 @@ mod _socket { any( target_arch = "aarch64", target_arch = "i686", + target_arch = "loongarch64", target_arch = "mips", target_arch = "powerpc", target_arch = "powerpc64", @@ -1015,7 +1017,21 @@ mod _socket { } } - #[pyclass(with(DefaultConstructor, Initializer), flags(BASETYPE))] + impl Representable for PySocket { + #[inline] + fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + Ok(format!( + "", + // cast because INVALID_SOCKET is unsigned, so would show usize::MAX instead of -1 + zelf.fileno() as i64, + zelf.family.load(), + zelf.kind.load(), + zelf.proto.load(), + )) + } + } + + #[pyclass(with(DefaultConstructor, Initializer, Representable), flags(BASETYPE))] impl PySocket { fn _init( zelf: PyRef, @@ -1463,18 +1479,6 @@ mod _socket { fn proto(&self) -> i32 { self.proto.load() } - - #[pymethod(magic)] - fn repr(&self) -> String { - format!( - "", - // cast because INVALID_SOCKET is unsigned, so would show usize::MAX instead of -1 - self.fileno() as i64, - self.family.load(), - self.kind.load(), - self.proto.load(), - ) - } } struct Address { diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index 9fae91fd6..4d1b1fa14 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -8,13 +8,13 @@ // spell-checker:ignore cantlock commithook foreignkey notnull primarykey gettemppath autoindex convpath // spell-checker:ignore dbmoved vnode nbytes -use rustpython_vm::{PyObjectRef, VirtualMachine}; +use rustpython_vm::{builtins::PyModule, AsObject, PyRef, VirtualMachine}; // pub(crate) use _sqlite::make_module; -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { // TODO: sqlite version check let module = _sqlite::make_module(vm); - _sqlite::setup_module(&module, vm); + _sqlite::setup_module(module.as_object(), vm); module } @@ -69,6 +69,7 @@ mod _sqlite { AsObject, Py, PyAtomicRef, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject, VirtualMachine, __exports::paste, + object::{Traverse, TraverseFn}, }; use std::{ ffi::{c_int, c_longlong, c_uint, c_void, CStr}, @@ -311,6 +312,13 @@ mod _sqlite { uri: bool, } + unsafe impl Traverse for ConnectArgs { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.isolation_level.traverse(tracer_fn); + self.factory.traverse(tracer_fn); + } + } + #[derive(FromArgs)] struct BackupArgs { #[pyarg(any)] @@ -325,36 +333,48 @@ mod _sqlite { sleep: f64, } - #[derive(FromArgs)] + unsafe impl Traverse for BackupArgs { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.progress.traverse(tracer_fn); + self.name.traverse(tracer_fn); + } + } + + #[derive(FromArgs, Traverse)] struct CreateFunctionArgs { #[pyarg(any)] name: PyStrRef, + #[pytraverse(skip)] #[pyarg(any)] narg: c_int, #[pyarg(any)] func: PyObjectRef, + #[pytraverse(skip)] #[pyarg(named, default)] deterministic: bool, } - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct CreateAggregateArgs { #[pyarg(any)] name: PyStrRef, + #[pytraverse(skip)] #[pyarg(positional)] narg: c_int, #[pyarg(positional)] aggregate_class: PyObjectRef, } - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct BlobOpenArgs { #[pyarg(positional)] table: PyStrRef, #[pyarg(positional)] column: PyStrRef, + #[pytraverse(skip)] #[pyarg(positional)] row: i64, + #[pytraverse(skip)] #[pyarg(named, default)] readonly: bool, #[pyarg(named, default = "vm.ctx.new_str(stringify!(main))")] @@ -1107,7 +1127,7 @@ mod _sqlite { ) -> PyResult<()> { let name = name.to_cstring(vm)?; let db = self.db_lock(vm)?; - let Some(data )= CallbackData::new(callable, vm) else { + let Some(data) = CallbackData::new(callable.clone(), vm) else { unsafe { sqlite3_create_collation_v2(db.db, name.as_ptr(), SQLITE_UTF8, null_mut(), None, None); } @@ -1115,6 +1135,10 @@ mod _sqlite { }; let data = Box::into_raw(Box::new(data)); + if !callable.is_callable() { + return Err(vm.new_type_error("parameter must be callable".to_owned())); + } + let ret = unsafe { sqlite3_create_collation_v2( db.db, @@ -1336,20 +1360,24 @@ mod _sqlite { } #[pyattr] - #[pyclass(name)] + #[pyclass(name, traverse)] #[derive(Debug, PyPayload)] struct Cursor { connection: PyRef, + #[pytraverse(skip)] arraysize: PyAtomic, + #[pytraverse(skip)] row_factory: PyAtomicRef>, inner: PyMutex>, } - #[derive(Debug)] + #[derive(Debug, Traverse)] struct CursorInner { description: Option, row_cast_map: Vec>, + #[pytraverse(skip)] lastrowid: i64, + #[pytraverse(skip)] rowcount: i64, statement: Option>, } @@ -1789,7 +1817,7 @@ mod _sqlite { } #[pyattr] - #[pyclass(name)] + #[pyclass(name, traverse)] #[derive(Debug, PyPayload)] struct Row { data: PyTupleRef, @@ -1918,10 +1946,11 @@ mod _sqlite { } #[pyattr] - #[pyclass(name)] + #[pyclass(name, traverse)] #[derive(Debug, PyPayload)] struct Blob { connection: PyRef, + #[pytraverse(skip)] inner: PyMutex>, } diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index e9ccde6a6..063dd6e76 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -1,26 +1,17 @@ -use crate::vm::{PyObjectRef, VirtualMachine}; +use crate::vm::{builtins::PyModule, PyRef, VirtualMachine}; -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { // if openssl is vendored, it doesn't know the locations of system certificates #[cfg(feature = "ssl-vendor")] if let None | Some("0") = option_env!("OPENSSL_NO_VENDOR") { openssl_probe::init_ssl_cert_env_vars(); } openssl::init(); - - let module = _ssl::make_module(vm); - #[cfg(ossl101)] - ossl101::extend_module(vm, &module); - #[cfg(ossl111)] - ossl101::extend_module(vm, &module); - #[cfg(windows)] - windows::extend_module(vm, &module); - - module + _ssl::make_module(vm) } #[allow(non_upper_case_globals)] -#[pymodule] +#[pymodule(with(ossl101, windows))] mod _ssl { use super::bio; use crate::{ @@ -797,9 +788,10 @@ mod _ssl { } } - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct WrapSocketArgs { sock: PyRef, + #[pytraverse(skip)] server_side: bool, #[pyarg(any, default)] server_hostname: Option, @@ -819,9 +811,11 @@ mod _ssl { cadata: Option>, } - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct LoadCertChainArgs { + #[pytraverse(skip)] certfile: FsPath, + #[pytraverse(skip)] #[pyarg(any, optional)] keyfile: Option, #[pyarg(any, optional)] @@ -902,11 +896,13 @@ mod _ssl { } #[pyattr] - #[pyclass(module = "ssl", name = "_SSLSocket")] + #[pyclass(module = "ssl", name = "_SSLSocket", traverse)] #[derive(PyPayload)] struct PySslSocket { ctx: PyRef, + #[pytraverse(skip)] stream: PyRwLock>, + #[pytraverse(skip)] socket_type: SslServerOrClient, server_hostname: Option, owner: PyRwLock>>, @@ -1400,9 +1396,21 @@ mod _ssl { } } +#[cfg(not(ossl101))] +#[pymodule(sub)] +mod ossl101 {} + +#[cfg(not(ossl111))] +#[pymodule(sub)] +mod ossl111 {} + +#[cfg(not(windows))] +#[pymodule(sub)] +mod windows {} + #[allow(non_upper_case_globals)] #[cfg(ossl101)] -#[pymodule] +#[pymodule(sub)] mod ossl101 { #[pyattr] use openssl_sys::{ @@ -1413,14 +1421,14 @@ mod ossl101 { #[allow(non_upper_case_globals)] #[cfg(ossl111)] -#[pymodule] +#[pymodule(sub)] mod ossl111 { #[pyattr] use openssl_sys::SSL_OP_NO_TLSv1_3 as OP_NO_TLSv1_3; } #[cfg(windows)] -#[pymodule] +#[pymodule(sub)] mod windows { use crate::{ common::ascii, diff --git a/stdlib/src/syslog.rs b/stdlib/src/syslog.rs index 9879f3ffa..e2176a7e2 100644 --- a/stdlib/src/syslog.rs +++ b/stdlib/src/syslog.rs @@ -95,7 +95,7 @@ mod syslog { Ok(()) } - #[derive(FromArgs)] + #[derive(FromArgs, Traverse)] struct SysLogArgs { #[pyarg(positional)] priority: PyObjectRef, diff --git a/stdlib/src/unicodedata.rs b/stdlib/src/unicodedata.rs index bb33d1f75..56105ceca 100644 --- a/stdlib/src/unicodedata.rs +++ b/stdlib/src/unicodedata.rs @@ -5,11 +5,11 @@ // spell-checker:ignore nfkc unistr unidata use crate::vm::{ - builtins::PyStr, convert::TryFromBorrowedObject, PyObject, PyObjectRef, PyPayload, PyResult, - VirtualMachine, + builtins::PyModule, builtins::PyStr, convert::TryFromBorrowedObject, PyObject, PyObjectRef, + PyPayload, PyRef, PyResult, VirtualMachine, }; -pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyRef { let module = unicodedata::make_module(vm); let ucd: PyObjectRef = unicodedata::Ucd::new(unic_ucd_age::UNICODE_VERSION) diff --git a/vm/src/builtins/bytearray.rs b/vm/src/builtins/bytearray.rs index 8d749e536..87757fae3 100644 --- a/vm/src/builtins/bytearray.rs +++ b/vm/src/builtins/bytearray.rs @@ -326,7 +326,8 @@ impl PyByteArray { fn fromhex(cls: PyTypeRef, string: PyStrRef, vm: &VirtualMachine) -> PyResult { let bytes = PyBytesInner::fromhex(string.as_str(), vm)?; let bytes = vm.ctx.new_bytes(bytes); - PyType::call(&cls, vec![bytes.into()].into(), vm) + let args = vec![bytes.into()].into(); + PyType::call(&cls, args, vm) } #[pymethod] diff --git a/vm/src/builtins/code.rs b/vm/src/builtins/code.rs index 52ed1c107..faf80a8f6 100644 --- a/vm/src/builtins/code.rs +++ b/vm/src/builtins/code.rs @@ -230,7 +230,7 @@ impl Representable for PyCode { } } -#[pyclass(with(Py))] +#[pyclass(with(Representable))] impl PyCode { #[pyslot] fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { @@ -418,21 +418,6 @@ impl PyCode { } } -#[pyclass] -impl Py { - #[pymethod(magic)] - fn repr(&self) -> String { - let code = &self.code; - format!( - "", - code.obj_name, - self.get_id(), - code.source_path.as_str(), - code.first_line_number - ) - } -} - impl fmt::Display for PyCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { (**self).fmt(f) diff --git a/vm/src/builtins/descriptor.rs b/vm/src/builtins/descriptor.rs index 6135904e3..2425f3ffe 100644 --- a/vm/src/builtins/descriptor.rs +++ b/vm/src/builtins/descriptor.rs @@ -32,7 +32,7 @@ pub enum MemberSetter { Offset(usize), } -pub struct MemberDef { +pub struct PyMemberDef { pub name: String, pub kind: MemberKind, pub getter: MemberGetter, @@ -40,7 +40,7 @@ pub struct MemberDef { pub doc: Option, } -impl MemberDef { +impl PyMemberDef { fn get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { match self.getter { MemberGetter::Getter(getter) => (getter)(vm, obj), @@ -64,9 +64,9 @@ impl MemberDef { } } -impl std::fmt::Debug for MemberDef { +impl std::fmt::Debug for PyMemberDef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MemberDef") + f.debug_struct("PyMemberDef") .field("name", &self.name) .field("kind", &self.kind) .field("doc", &self.doc) @@ -74,14 +74,15 @@ impl std::fmt::Debug for MemberDef { } } +// PyMemberDescrObject in CPython #[pyclass(name = "member_descriptor", module = false)] #[derive(Debug)] -pub struct MemberDescrObject { +pub struct PyMemberDescriptor { pub common: DescrObject, - pub member: MemberDef, + pub member: PyMemberDef, } -impl PyPayload for MemberDescrObject { +impl PyPayload for PyMemberDescriptor { fn class(ctx: &Context) -> &'static Py { ctx.types.member_descriptor_type } @@ -101,7 +102,7 @@ fn calculate_qualname(descr: &DescrObject, vm: &VirtualMachine) -> PyResult Option { self.member.doc.to_owned() @@ -136,7 +137,7 @@ impl MemberDescrObject { fn get_slot_from_object( obj: PyObjectRef, offset: usize, - member: &MemberDef, + member: &PyMemberDef, vm: &VirtualMachine, ) -> PyResult { let slot = match member.kind { @@ -158,7 +159,7 @@ fn get_slot_from_object( fn set_slot_at_object( obj: PyObjectRef, offset: usize, - member: &MemberDef, + member: &PyMemberDef, value: PySetterValue, vm: &VirtualMachine, ) -> PyResult<()> { @@ -186,9 +187,9 @@ fn set_slot_at_object( Ok(()) } -impl Unconstructible for MemberDescrObject {} +impl Unconstructible for PyMemberDescriptor {} -impl Representable for MemberDescrObject { +impl Representable for PyMemberDescriptor { #[inline] fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { Ok(format!( @@ -199,7 +200,7 @@ impl Representable for MemberDescrObject { } } -impl GetDescriptor for MemberDescrObject { +impl GetDescriptor for PyMemberDescriptor { fn descr_get( zelf: PyObjectRef, obj: Option, @@ -218,5 +219,5 @@ impl GetDescriptor for MemberDescrObject { pub fn init(context: &Context) { let member_descriptor_type = &context.types.member_descriptor_type; - MemberDescrObject::extend_class(context, member_descriptor_type); + PyMemberDescriptor::extend_class(context, member_descriptor_type); } diff --git a/vm/src/builtins/dict.rs b/vm/src/builtins/dict.rs index 52092c96f..b2a191fd9 100644 --- a/vm/src/builtins/dict.rs +++ b/vm/src/builtins/dict.rs @@ -32,7 +32,7 @@ use std::fmt; pub type DictContentType = dictdatatype::Dict; -#[pyclass(module = false, name = "dict", unhashable = true)] +#[pyclass(module = false, name = "dict", unhashable = true, traverse)] #[derive(Default)] pub struct PyDict { entries: DictContentType, diff --git a/vm/src/builtins/enumerate.rs b/vm/src/builtins/enumerate.rs index 5b8478dbe..c1582f08d 100644 --- a/vm/src/builtins/enumerate.rs +++ b/vm/src/builtins/enumerate.rs @@ -13,9 +13,10 @@ use crate::{ use num_bigint::BigInt; use num_traits::Zero; -#[pyclass(module = false, name = "enumerate")] +#[pyclass(module = false, name = "enumerate", traverse)] #[derive(Debug)] pub struct PyEnumerate { + #[pytraverse(skip)] counter: PyRwLock, iterator: PyIter, } @@ -84,7 +85,7 @@ impl IterNext for PyEnumerate { } } -#[pyclass(module = false, name = "reversed")] +#[pyclass(module = false, name = "reversed", traverse)] #[derive(Debug)] pub struct PyReverseSequenceIterator { internal: PyMutex>, diff --git a/vm/src/builtins/filter.rs b/vm/src/builtins/filter.rs index f166181bd..660be88ea 100644 --- a/vm/src/builtins/filter.rs +++ b/vm/src/builtins/filter.rs @@ -6,7 +6,7 @@ use crate::{ Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, }; -#[pyclass(module = false, name = "filter")] +#[pyclass(module = false, name = "filter", traverse)] #[derive(Debug)] pub struct PyFilter { predicate: PyObjectRef, diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index bf8fb46d2..696ac0802 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -10,6 +10,7 @@ use crate::common::lock::OnceCell; use crate::common::lock::PyMutex; use crate::convert::ToPyObject; use crate::function::ArgMapping; +use crate::object::{Traverse, TraverseFn}; use crate::{ bytecode, class::PyClassImpl, @@ -25,7 +26,7 @@ use itertools::Itertools; #[cfg(feature = "jit")] use rustpython_jit::CompiledCode; -#[pyclass(module = false, name = "function")] +#[pyclass(module = false, name = "function", traverse = "manual")] #[derive(Debug)] pub struct PyFunction { code: PyRef, @@ -38,6 +39,14 @@ pub struct PyFunction { jitted_code: OnceCell, } +unsafe impl Traverse for PyFunction { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.globals.traverse(tracer_fn); + self.closure.traverse(tracer_fn); + self.defaults_and_kwdefaults.traverse(tracer_fn); + } +} + impl PyFunction { pub(crate) fn new( code: PyRef, @@ -468,7 +477,7 @@ impl Representable for PyFunction { } } -#[pyclass(module = false, name = "method")] +#[pyclass(module = false, name = "method", traverse)] #[derive(Debug)] pub struct PyBoundMethod { object: PyObjectRef, @@ -513,7 +522,7 @@ impl GetAttr for PyBoundMethod { } } -#[derive(FromArgs)] +#[derive(FromArgs, Traverse)] pub struct PyBoundMethodNewArgs { #[pyarg(positional)] function: PyObjectRef, @@ -549,25 +558,11 @@ impl PyBoundMethod { } } -#[pyclass(with(Callable, Comparable, GetAttr, Constructor), flags(HAS_DICT))] +#[pyclass( + with(Callable, Comparable, GetAttr, Constructor, Representable), + flags(HAS_DICT) +)] impl PyBoundMethod { - #[pymethod(magic)] - fn repr(&self, vm: &VirtualMachine) -> PyResult { - #[allow(clippy::needless_match)] // False positive on nightly - let funcname = - if let Some(qname) = vm.get_attribute_opt(self.function.clone(), "__qualname__")? { - Some(qname) - } else { - vm.get_attribute_opt(self.function.clone(), "__name__")? - }; - let funcname: Option = funcname.and_then(|o| o.downcast().ok()); - Ok(format!( - "", - funcname.as_ref().map_or("?", |s| s.as_str()), - &self.object.repr(vm)?.as_str(), - )) - } - #[pymethod(magic)] fn reduce( &self, @@ -628,7 +623,26 @@ impl PyPayload for PyBoundMethod { } } -#[pyclass(module = false, name = "cell")] +impl Representable for PyBoundMethod { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + #[allow(clippy::needless_match)] // False positive on nightly + let funcname = + if let Some(qname) = vm.get_attribute_opt(zelf.function.clone(), "__qualname__")? { + Some(qname) + } else { + vm.get_attribute_opt(zelf.function.clone(), "__name__")? + }; + let funcname: Option = funcname.and_then(|o| o.downcast().ok()); + Ok(format!( + "", + funcname.as_ref().map_or("?", |s| s.as_str()), + &zelf.object.repr(vm)?.as_str(), + )) + } +} + +#[pyclass(module = false, name = "cell", traverse)] #[derive(Debug, Default)] pub(crate) struct PyCell { contents: PyMutex>, diff --git a/vm/src/builtins/iter.rs b/vm/src/builtins/iter.rs index 532fa9e4d..6f8a01e05 100644 --- a/vm/src/builtins/iter.rs +++ b/vm/src/builtins/iter.rs @@ -6,6 +6,7 @@ use super::{PyInt, PyTupleRef, PyType}; use crate::{ class::PyClassImpl, function::ArgCallable, + object::{Traverse, TraverseFn}, protocol::{PyIterReturn, PySequence, PySequenceMethods}, types::{IterNext, IterNextIterable}, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, @@ -24,12 +25,27 @@ pub enum IterStatus { Exhausted, } +unsafe impl Traverse for IterStatus { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + match self { + IterStatus::Active(ref r) => r.traverse(tracer_fn), + IterStatus::Exhausted => (), + } + } +} + #[derive(Debug)] pub struct PositionIterInternal { pub status: IterStatus, pub position: usize, } +unsafe impl Traverse for PositionIterInternal { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.status.traverse(tracer_fn) + } +} + impl PositionIterInternal { pub fn new(obj: T, position: usize) -> Self { Self { @@ -158,10 +174,11 @@ pub fn builtins_reversed(vm: &VirtualMachine) -> &PyObject { INSTANCE.get_or_init(|| vm.builtins.get_attr("reversed", vm).unwrap()) } -#[pyclass(module = false, name = "iterator")] +#[pyclass(module = false, name = "iterator", traverse)] #[derive(Debug)] pub struct PySequenceIterator { // cached sequence methods + #[pytraverse(skip)] seq_methods: &'static PySequenceMethods, internal: PyMutex>, } @@ -222,7 +239,7 @@ impl IterNext for PySequenceIterator { } } -#[pyclass(module = false, name = "callable_iterator")] +#[pyclass(module = false, name = "callable_iterator", traverse)] #[derive(Debug)] pub struct PyCallableIterator { sentinel: PyObjectRef, diff --git a/vm/src/builtins/list.rs b/vm/src/builtins/list.rs index 9928beab6..581c0fc9e 100644 --- a/vm/src/builtins/list.rs +++ b/vm/src/builtins/list.rs @@ -22,7 +22,7 @@ use crate::{ }; use std::{fmt, ops::DerefMut}; -#[pyclass(module = false, name = "list", unhashable = true)] +#[pyclass(module = false, name = "list", unhashable = true, traverse)] #[derive(Default)] pub struct PyList { elements: PyRwLock>, @@ -86,10 +86,11 @@ impl PyList { } } -#[derive(FromArgs, Default)] +#[derive(FromArgs, Default, Traverse)] pub(crate) struct SortOptions { #[pyarg(named, default)] key: Option, + #[pytraverse(skip)] #[pyarg(named, default = "false")] reverse: bool, } @@ -530,7 +531,7 @@ fn do_sort( Ok(()) } -#[pyclass(module = false, name = "list_iterator")] +#[pyclass(module = false, name = "list_iterator", traverse)] #[derive(Debug)] pub struct PyListIterator { internal: PyMutex>, @@ -575,7 +576,7 @@ impl IterNext for PyListIterator { } } -#[pyclass(module = false, name = "list_reverseiterator")] +#[pyclass(module = false, name = "list_reverseiterator", traverse)] #[derive(Debug)] pub struct PyListReverseIterator { internal: PyMutex>, diff --git a/vm/src/builtins/map.rs b/vm/src/builtins/map.rs index 754f21810..6fb842f41 100644 --- a/vm/src/builtins/map.rs +++ b/vm/src/builtins/map.rs @@ -8,7 +8,7 @@ use crate::{ Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, }; -#[pyclass(module = false, name = "map")] +#[pyclass(module = false, name = "map", traverse)] #[derive(Debug)] pub struct PyMap { mapper: PyObjectRef, diff --git a/vm/src/builtins/mappingproxy.rs b/vm/src/builtins/mappingproxy.rs index d0659ddf5..a5400ee6d 100644 --- a/vm/src/builtins/mappingproxy.rs +++ b/vm/src/builtins/mappingproxy.rs @@ -4,6 +4,7 @@ use crate::{ class::PyClassImpl, convert::ToPyObject, function::{ArgMapping, OptionalArg, PyComparisonValue}, + object::{Traverse, TraverseFn}, protocol::{PyMapping, PyMappingMethods, PyNumberMethods, PySequenceMethods}, types::{ AsMapping, AsNumber, AsSequence, Comparable, Constructor, Iterable, PyComparisonOp, @@ -13,7 +14,7 @@ use crate::{ }; use once_cell::sync::Lazy; -#[pyclass(module = false, name = "mappingproxy")] +#[pyclass(module = false, name = "mappingproxy", traverse)] #[derive(Debug)] pub struct PyMappingProxy { mapping: MappingProxyInner, @@ -25,6 +26,15 @@ enum MappingProxyInner { Mapping(ArgMapping), } +unsafe impl Traverse for MappingProxyInner { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + match self { + MappingProxyInner::Class(ref r) => r.traverse(tracer_fn), + MappingProxyInner::Mapping(ref arg) => arg.traverse(tracer_fn), + } + } +} + impl PyPayload for PyMappingProxy { fn class(ctx: &Context) -> &'static Py { ctx.types.mappingproxy_type diff --git a/vm/src/builtins/memory.rs b/vm/src/builtins/memory.rs index d4861bd2a..152bf49c1 100644 --- a/vm/src/builtins/memory.rs +++ b/vm/src/builtins/memory.rs @@ -33,7 +33,7 @@ use once_cell::sync::Lazy; use rustpython_common::lock::PyMutex; use std::{cmp::Ordering, fmt::Debug, mem::ManuallyDrop, ops::Range}; -#[derive(FromArgs)] +#[derive(FromArgs, Traverse)] pub struct PyMemoryViewNewArgs { object: PyObjectRef, } @@ -896,7 +896,7 @@ impl Py { } } -#[derive(FromArgs)] +#[derive(FromArgs, Traverse)] struct CastArgs { #[pyarg(any)] format: PyStrRef, @@ -1126,7 +1126,7 @@ impl Iterable for PyMemoryView { } #[pyclass(module = false, name = "memory_iterator")] -#[derive(Debug)] +#[derive(Debug, Traverse)] pub struct PyMemoryViewIterator { internal: PyMutex>>, } diff --git a/vm/src/builtins/mod.rs b/vm/src/builtins/mod.rs index f068e94a9..ae3b7eea2 100644 --- a/vm/src/builtins/mod.rs +++ b/vm/src/builtins/mod.rs @@ -47,7 +47,7 @@ pub use mappingproxy::PyMappingProxy; pub(crate) mod memory; pub use memory::PyMemoryView; pub(crate) mod module; -pub use module::PyModule; +pub use module::{PyModule, PyModuleDef}; pub(crate) mod namespace; pub use namespace::PyNamespace; pub(crate) mod object; diff --git a/vm/src/builtins/module.rs b/vm/src/builtins/module.rs index a6187382c..4c77bc267 100644 --- a/vm/src/builtins/module.rs +++ b/vm/src/builtins/module.rs @@ -5,12 +5,53 @@ use crate::{ convert::ToPyObject, function::FuncArgs, types::{GetAttr, Initializer, Representable}, - AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, + AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, }; #[pyclass(module = false, name = "module")] #[derive(Debug)] -pub struct PyModule {} +pub struct PyModuleDef { + // pub index: usize, + pub name: &'static PyStrInterned, + pub doc: Option<&'static PyStrInterned>, + // pub size: isize, + // pub methods: &'static [PyMethodDef], + pub slots: PyModuleSlots, + // traverse: traverseproc + // clear: inquiry + // free: freefunc +} + +pub type ModuleCreate = + fn(&VirtualMachine, &PyObject, &'static PyModuleDef) -> PyResult>; +pub type ModuleExec = fn(&VirtualMachine, &Py) -> PyResult<()>; + +#[derive(Default)] +pub struct PyModuleSlots { + pub create: Option, + pub exec: Option, +} + +impl std::fmt::Debug for PyModuleSlots { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PyModuleSlots") + .field("create", &self.create.is_some()) + .field("exec", &self.exec.is_some()) + .finish() + } +} + +#[allow(clippy::new_without_default)] // avoid Default implementation +#[pyclass(module = false, name = "module")] +#[derive(Debug)] +pub struct PyModule { + // PyObject *md_dict; + pub def: Option<&'static PyModuleDef>, + // state: Any + // weaklist + // for logging purposes after md_dict is cleared + pub name: Option<&'static PyStrInterned>, +} impl PyPayload for PyModule { fn class(ctx: &Context) -> &'static Py { @@ -26,9 +67,23 @@ pub struct ModuleInitArgs { } impl PyModule { - // pub(crate) fn new(d: PyDictRef) -> Self { - // PyModule { dict: d.into() } - // } + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + def: None, + name: None, + } + } + pub fn from_def(def: &'static PyModuleDef) -> Self { + Self { + def: Some(def), + name: Some(def.name), + } + } + pub fn __init_dict_from_def(vm: &VirtualMachine, module: &Py) { + let doc = module.def.unwrap().doc.map(|doc| doc.to_owned()); + module.init_module_dict(module.name.unwrap(), doc, vm); + } } impl Py { @@ -63,13 +118,13 @@ impl Py { pub(crate) fn init_module_dict( &self, name: &'static PyStrInterned, - doc: PyObjectRef, + doc: Option, vm: &VirtualMachine, ) { let dict = self.dict(); dict.set_item(identifier!(vm, __name__), name.to_object(), vm) .expect("Failed to set __name__ on module"); - dict.set_item(identifier!(vm, __doc__), doc, vm) + dict.set_item(identifier!(vm, __doc__), doc.to_pyobject(vm), vm) .expect("Failed to set __doc__ on module"); dict.set_item("__package__", vm.ctx.none(), vm) .expect("Failed to set __package__ on module"); @@ -80,7 +135,8 @@ impl Py { } pub fn get_attr<'a>(&self, attr_name: impl AsPyStr<'a>, vm: &VirtualMachine) -> PyResult { - self.getattr_inner(attr_name.as_pystr(&vm.ctx), vm) + let attr_name = attr_name.as_pystr(&vm.ctx); + self.getattr_inner(attr_name, vm) } pub fn set_attr<'a>( @@ -97,7 +153,7 @@ impl Py { impl PyModule { #[pyslot] fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { - PyModule {}.into_ref_with_type(vm, cls).map(Into::into) + PyModule::new().into_ref_with_type(vm, cls).map(Into::into) } #[pymethod(magic)] @@ -120,11 +176,7 @@ impl Initializer for PyModule { .slots .flags .has_feature(crate::types::PyTypeFlags::HAS_DICT)); - zelf.init_module_dict( - vm.ctx.intern_str(args.name.as_str()), - args.doc.to_pyobject(vm), - vm, - ); + zelf.init_module_dict(vm.ctx.intern_str(args.name.as_str()), args.doc, vm); Ok(()) } } diff --git a/vm/src/builtins/property.rs b/vm/src/builtins/property.rs index 7322ba7d0..40aa96996 100644 --- a/vm/src/builtins/property.rs +++ b/vm/src/builtins/property.rs @@ -11,7 +11,7 @@ use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, }; -#[pyclass(module = false, name = "property")] +#[pyclass(module = false, name = "property", traverse)] #[derive(Debug)] pub struct PyProperty { getter: PyRwLock>, @@ -51,7 +51,7 @@ impl GetDescriptor for PyProperty { } else if let Some(getter) = zelf.getter.read().as_ref() { getter.call((obj,), vm) } else { - Err(vm.new_attribute_error("unreadable attribute".to_string())) + Err(vm.new_attribute_error("property has no getter".to_string())) } } } @@ -73,14 +73,14 @@ impl PyProperty { if let Some(setter) = zelf.setter.read().as_ref() { setter.call((obj, value), vm).map(drop) } else { - Err(vm.new_attribute_error("can't set attribute".to_owned())) + Err(vm.new_attribute_error("property has no setter".to_owned())) } } PySetterValue::Delete => { if let Some(deleter) = zelf.deleter.read().as_ref() { deleter.call((obj,), vm).map(drop) } else { - Err(vm.new_attribute_error("can't delete attribute".to_owned())) + Err(vm.new_attribute_error("property has no deleter".to_owned())) } } } diff --git a/vm/src/builtins/set.rs b/vm/src/builtins/set.rs index 3e8546cb3..003b0496e 100644 --- a/vm/src/builtins/set.rs +++ b/vm/src/builtins/set.rs @@ -28,7 +28,7 @@ use std::{fmt, ops::Deref}; pub type SetContentType = dictdatatype::Dict<()>; -#[pyclass(module = false, name = "set", unhashable = true)] +#[pyclass(module = false, name = "set", unhashable = true, traverse)] #[derive(Default)] pub struct PySet { pub(super) inner: PySetInner, @@ -151,6 +151,13 @@ pub(super) struct PySetInner { content: PyRc, } +unsafe impl crate::object::Traverse for PySetInner { + fn traverse(&self, tracer_fn: &mut crate::object::TraverseFn) { + // FIXME(discord9): Rc means shared ref, so should it be traced? + self.content.traverse(tracer_fn) + } +} + impl PySetInner { pub(super) fn from_iter(iter: T, vm: &VirtualMachine) -> PyResult where diff --git a/vm/src/builtins/slice.rs b/vm/src/builtins/slice.rs index b4e0df750..5dbf960c4 100644 --- a/vm/src/builtins/slice.rs +++ b/vm/src/builtins/slice.rs @@ -12,7 +12,7 @@ use crate::{ use num_bigint::{BigInt, ToBigInt}; use num_traits::{One, Signed, Zero}; -#[pyclass(module = false, name = "slice", unhashable = true)] +#[pyclass(module = false, name = "slice", unhashable = true, traverse)] #[derive(Debug)] pub struct PySlice { pub start: Option, @@ -244,6 +244,7 @@ impl Comparable for PySlice { Ok(PyComparisonValue::Implemented(ret)) } } + impl Representable for PySlice { #[inline] fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/builtins/staticmethod.rs b/vm/src/builtins/staticmethod.rs index d9de4be69..bb99960ce 100644 --- a/vm/src/builtins/staticmethod.rs +++ b/vm/src/builtins/staticmethod.rs @@ -8,7 +8,7 @@ use crate::{ Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, }; -#[pyclass(module = false, name = "staticmethod")] +#[pyclass(module = false, name = "staticmethod", traverse)] #[derive(Debug)] pub struct PyStaticMethod { pub callable: PyMutex, diff --git a/vm/src/builtins/str.rs b/vm/src/builtins/str.rs index 022503a41..dff8e5aa9 100644 --- a/vm/src/builtins/str.rs +++ b/vm/src/builtins/str.rs @@ -15,6 +15,7 @@ use crate::{ format::{format, format_map}, function::{ArgIterable, ArgSize, FuncArgs, OptionalArg, OptionalOption, PyComparisonValue}, intern::PyInterned, + object::{Traverse, TraverseFn}, protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, sequence::SequenceExt, sliceable::{SequenceIndex, SliceableSequenceOp}, @@ -186,12 +187,19 @@ impl<'a> AsPyStr<'a> for &'a PyStrInterned { } } -#[pyclass(module = false, name = "str_iterator")] +#[pyclass(module = false, name = "str_iterator", traverse = "manual")] #[derive(Debug)] pub struct PyStrIterator { internal: PyMutex<(PositionIterInternal, usize)>, } +unsafe impl Traverse for PyStrIterator { + fn traverse(&self, tracer: &mut TraverseFn) { + // No need to worry about deadlock, for inner is a PyStr and can't make ref cycle + self.internal.lock().0.traverse(tracer); + } +} + impl PyPayload for PyStrIterator { fn class(ctx: &Context) -> &'static Py { ctx.types.str_iterator_type @@ -251,7 +259,7 @@ impl IterNext for PyStrIterator { } } -#[derive(FromArgs)] +#[derive(FromArgs, Traverse)] pub struct StrArgs { #[pyarg(any, optional)] object: OptionalArg, @@ -310,8 +318,9 @@ impl PyStr { Self::new_str_unchecked(bytes, PyStrKind::Ascii) } - pub fn new_ref(s: impl Into, ctx: &Context) -> PyRef { - PyRef::new_ref(s.into(), ctx.types.str_type.to_owned(), None) + pub fn new_ref(zelf: impl Into, ctx: &Context) -> PyRef { + let zelf = zelf.into(); + PyRef::new_ref(zelf, ctx.types.str_type.to_owned(), None) } fn new_substr(&self, s: String) -> Self { @@ -1392,7 +1401,7 @@ impl AsSequence for PyStr { } } -#[derive(FromArgs)] +#[derive(FromArgs, Traverse)] struct EncodeArgs { #[pyarg(any, default)] encoding: Option, @@ -1456,7 +1465,7 @@ impl ToPyObject for AsciiString { type SplitArgs = anystr::SplitArgs; -#[derive(FromArgs)] +#[derive(FromArgs, Traverse)] pub struct FindArgs { #[pyarg(positional)] sub: PyStrRef, diff --git a/vm/src/builtins/super.rs b/vm/src/builtins/super.rs index be8c0ed19..19377305e 100644 --- a/vm/src/builtins/super.rs +++ b/vm/src/builtins/super.rs @@ -6,40 +6,74 @@ See also [CPython source code.](https://github.com/python/cpython/blob/50b48572d use super::{PyStr, PyType, PyTypeRef}; use crate::{ class::PyClassImpl, - function::{IntoFuncArgs, OptionalArg}, - types::{Callable, Constructor, GetAttr, GetDescriptor, Representable}, - AsObject, Context, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, + common::lock::PyRwLock, + function::{FuncArgs, IntoFuncArgs, OptionalArg}, + types::{Callable, Constructor, GetAttr, GetDescriptor, Initializer, Representable}, + AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, }; -#[pyclass(module = false, name = "super")] +#[pyclass(module = false, name = "super", traverse)] #[derive(Debug)] pub struct PySuper { + inner: PyRwLock, +} + +#[derive(Debug, Traverse)] +struct PySuperInner { typ: PyTypeRef, obj: Option<(PyObjectRef, PyTypeRef)>, } +impl PySuperInner { + fn new(typ: PyTypeRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let obj = if vm.is_none(&obj) { + None + } else { + let obj_type = supercheck(typ.clone(), obj.clone(), vm)?; + Some((obj, obj_type)) + }; + Ok(Self { typ, obj }) + } +} + impl PyPayload for PySuper { fn class(ctx: &Context) -> &'static Py { ctx.types.super_type } } +impl Constructor for PySuper { + type Args = FuncArgs; + + fn py_new(cls: PyTypeRef, _args: Self::Args, vm: &VirtualMachine) -> PyResult { + let obj = PySuper { + inner: PyRwLock::new(PySuperInner::new( + vm.ctx.types.object_type.to_owned(), // is this correct? + vm.ctx.none(), + vm, + )?), + } + .into_ref_with_type(vm, cls)?; + Ok(obj.into()) + } +} + #[derive(FromArgs)] -pub struct PySuperNewArgs { +pub struct InitArgs { #[pyarg(positional, optional)] py_type: OptionalArg, #[pyarg(positional, optional)] py_obj: OptionalArg, } -impl Constructor for PySuper { - type Args = PySuperNewArgs; +impl Initializer for PySuper { + type Args = InitArgs; - fn py_new( - cls: PyTypeRef, + fn init( + zelf: PyRef, Self::Args { py_type, py_obj }: Self::Args, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<()> { // Get the type: let (typ, obj) = if let OptionalArg::Present(ty) = py_type { (ty, py_obj.unwrap_or_none(vm)) @@ -91,44 +125,36 @@ impl Constructor for PySuper { (typ, obj) }; - PySuper::new(typ, obj, vm)? - .into_ref_with_type(vm, cls) - .map(Into::into) + let mut inner = PySuperInner::new(typ, obj, vm)?; + std::mem::swap(&mut inner, &mut zelf.inner.write()); + + Ok(()) } } -#[pyclass(with(GetAttr, GetDescriptor, Constructor, Representable))] +#[pyclass(with(GetAttr, GetDescriptor, Constructor, Initializer, Representable))] impl PySuper { - fn new(typ: PyTypeRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let obj = if vm.is_none(&obj) { - None - } else { - let obj_type = supercheck(typ.clone(), obj.clone(), vm)?; - Some((obj, obj_type)) - }; - Ok(Self { typ, obj }) - } - #[pygetset(magic)] fn thisclass(&self) -> PyTypeRef { - self.typ.clone() + self.inner.read().typ.clone() } #[pygetset(magic)] fn self_class(&self) -> Option { - Some(self.obj.as_ref()?.1.clone()) + Some(self.inner.read().obj.as_ref()?.1.clone()) } #[pygetset] fn __self__(&self) -> Option { - Some(self.obj.as_ref()?.0.clone()) + Some(self.inner.read().obj.as_ref()?.0.clone()) } } impl GetAttr for PySuper { fn getattro(zelf: &Py, name: &Py, vm: &VirtualMachine) -> PyResult { let skip = |zelf: &Py, name| zelf.as_object().generic_getattr(name, vm); - let (obj, start_type): (PyObjectRef, PyTypeRef) = match zelf.obj.clone() { + let obj = zelf.inner.read().obj.clone(); + let (obj, start_type): (PyObjectRef, PyTypeRef) = match obj { Some(o) => o, None => return skip(zelf, name), }; @@ -143,7 +169,7 @@ impl GetAttr for PySuper { // skip the classes in start_type.mro up to and including zelf.typ let mro: Vec<_> = start_type .iter_mro() - .skip_while(|cls| !cls.is(&zelf.typ)) + .skip_while(|cls| !cls.is(&zelf.inner.read().typ)) .skip(1) // skip su->type (if any) .collect(); for cls in mro { @@ -171,15 +197,26 @@ impl GetDescriptor for PySuper { vm: &VirtualMachine, ) -> PyResult { let (zelf, obj) = Self::_unwrap(&zelf_obj, obj, vm)?; - if vm.is_none(&obj) || zelf.obj.is_some() { + if vm.is_none(&obj) || zelf.inner.read().obj.is_some() { return Ok(zelf_obj); } let zelf_class = zelf.as_object().class(); if zelf_class.is(vm.ctx.types.super_type) { - Ok(PySuper::new(zelf.typ.clone(), obj, vm)?.into_pyobject(vm)) + let typ = zelf.inner.read().typ.clone(); + Ok(PySuper { + inner: PyRwLock::new(PySuperInner::new(typ, obj, vm)?), + } + .into_ref(&vm.ctx) + .into()) } else { - let obj = vm.unwrap_or_none(zelf.obj.clone().map(|(o, _)| o)); - PyType::call(zelf.class(), (zelf.typ.clone(), obj).into_args(vm), vm) + let (obj, typ) = { + let lock = zelf.inner.read(); + let obj = lock.obj.as_ref().map(|(o, _)| o.to_owned()); + let typ = lock.typ.clone(); + (obj, typ) + }; + let obj = vm.unwrap_or_none(obj); + PyType::call(zelf.class(), (typ, obj).into_args(vm), vm) } } } @@ -187,10 +224,11 @@ impl GetDescriptor for PySuper { impl Representable for PySuper { #[inline] fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { - let type_name = &zelf.typ.name(); - let repr = match zelf.obj { + let type_name = zelf.inner.read().typ.name().to_owned(); + let obj = zelf.inner.read().obj.clone(); + let repr = match obj { Some((_, ref ty)) => { - format!(", <{} object>>", type_name, ty.name()) + format!(", <{} object>>", &type_name, ty.name()) } None => format!(", NULL>"), }; diff --git a/vm/src/builtins/traceback.rs b/vm/src/builtins/traceback.rs index d0972c751..3ab5ff8da 100644 --- a/vm/src/builtins/traceback.rs +++ b/vm/src/builtins/traceback.rs @@ -3,12 +3,14 @@ use rustpython_common::lock::PyMutex; use super::PyType; use crate::{class::PyClassImpl, frame::FrameRef, Context, Py, PyPayload, PyRef}; -#[pyclass(module = false, name = "traceback")] +#[pyclass(module = false, name = "traceback", traverse)] #[derive(Debug)] pub struct PyTraceback { pub next: PyMutex>, pub frame: FrameRef, + #[pytraverse(skip)] pub lasti: u32, + #[pytraverse(skip)] pub lineno: usize, } diff --git a/vm/src/builtins/tuple.rs b/vm/src/builtins/tuple.rs index 53cb61074..59133b087 100644 --- a/vm/src/builtins/tuple.rs +++ b/vm/src/builtins/tuple.rs @@ -1,5 +1,6 @@ use super::{PositionIterInternal, PyGenericAlias, PyStrRef, PyType, PyTypeRef}; use crate::common::{hash::PyHash, lock::PyMutex}; +use crate::object::{Traverse, TraverseFn}; use crate::{ atomic_func, class::PyClassImpl, @@ -21,7 +22,7 @@ use crate::{ use once_cell::sync::Lazy; use std::{fmt, marker::PhantomData}; -#[pyclass(module = false, name = "tuple")] +#[pyclass(module = false, name = "tuple", traverse)] pub struct PyTuple { elements: Box<[PyObjectRef]>, } @@ -421,7 +422,7 @@ impl Representable for PyTuple { } } -#[pyclass(module = false, name = "tuple_iterator")] +#[pyclass(module = false, name = "tuple_iterator", traverse)] #[derive(Debug)] pub(crate) struct PyTupleIterator { internal: PyMutex>, @@ -479,6 +480,15 @@ pub struct PyTupleTyped { _marker: PhantomData>, } +unsafe impl Traverse for PyTupleTyped +where + T: TransmuteFromObject + Traverse, +{ + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.tuple.traverse(tracer_fn); + } +} + impl TryFromObject for PyTupleTyped { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { let tuple = PyTupleRef::try_from_object(vm, obj)?; diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index ab6a44c43..5aca7ed07 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -5,7 +5,7 @@ use super::{ use crate::{ builtins::{ descriptor::{ - DescrObject, MemberDef, MemberDescrObject, MemberGetter, MemberKind, MemberSetter, + DescrObject, MemberGetter, MemberKind, MemberSetter, PyMemberDef, PyMemberDescriptor, }, function::PyCellRef, tuple::{IntoPyTuple, PyTupleTyped}, @@ -20,6 +20,7 @@ use crate::{ convert::ToPyResult, function::{FuncArgs, KwArgs, OptionalArg, PySetterValue}, identifier, + object::{Traverse, TraverseFn}, protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, types::{AsNumber, Callable, GetAttr, PyTypeFlags, PyTypeSlots, Representable, SetAttr}, AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, @@ -29,7 +30,7 @@ use indexmap::{map::Entry, IndexMap}; use itertools::Itertools; use std::{borrow::Borrow, collections::HashSet, fmt, ops::Deref, pin::Pin, ptr::NonNull}; -#[pyclass(module = false, name = "type")] +#[pyclass(module = false, name = "type", traverse = "manual")] pub struct PyType { pub base: Option, pub bases: Vec, @@ -40,6 +41,20 @@ pub struct PyType { pub heaptype_ext: Option>>, } +unsafe impl crate::object::Traverse for PyType { + fn traverse(&self, tracer_fn: &mut crate::object::TraverseFn) { + self.base.traverse(tracer_fn); + self.bases.traverse(tracer_fn); + self.mro.traverse(tracer_fn); + self.subclasses.traverse(tracer_fn); + self.attributes + .read_recursive() + .iter() + .map(|(_, v)| v.traverse(tracer_fn)) + .count(); + } +} + pub struct HeapTypeExt { pub name: PyRwLock, pub slots: Option>, @@ -100,6 +115,12 @@ cfg_if::cfg_if! { /// faster and only supports strings as keys. pub type PyAttributes = IndexMap<&'static PyStrInterned, PyObjectRef, ahash::RandomState>; +unsafe impl Traverse for PyAttributes { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.values().for_each(|v| v.traverse(tracer_fn)); + } +} + impl fmt::Display for PyType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.name(), f) @@ -301,7 +322,8 @@ impl PyType { value: V, ctx: impl AsRef, ) { - let attr_name = ctx.as_ref().intern_str(attr_name); + let ctx = ctx.as_ref(); + let attr_name = ctx.intern_str(attr_name); self.set_attr(attr_name, value.into()) } @@ -753,12 +775,11 @@ impl PyType { base.slots.member_count + heaptype_slots.as_ref().map(|x| x.len()).unwrap_or(0); let flags = PyTypeFlags::heap_type_flags() | PyTypeFlags::HAS_DICT; - let (slots, heaptype_ext) = unsafe { - // # Safety - // `slots.name` live long enough because `heaptype_ext` is alive. + let (slots, heaptype_ext) = { let slots = PyTypeSlots { member_count, - ..PyTypeSlots::new(&*(name.as_str() as *const _), flags) + flags, + ..PyTypeSlots::heap_default() }; let heaptype_ext = HeapTypeExt { name: PyRwLock::new(name), @@ -783,15 +804,15 @@ impl PyType { if let Some(ref slots) = heaptype_slots { let mut offset = base_member_count; for member in slots.as_slice() { - let member_def = MemberDef { + let member_def = PyMemberDef { name: member.to_string(), kind: MemberKind::ObjectEx, getter: MemberGetter::Offset(offset), setter: MemberSetter::Offset(offset), doc: None, }; - let member_descriptor: PyRef = - vm.ctx.new_pyref(MemberDescrObject { + let member_descriptor: PyRef = + vm.ctx.new_pyref(PyMemberDescriptor { common: DescrObject { typ: typ.clone(), name: vm.ctx.intern_str(member.as_str()), diff --git a/vm/src/builtins/union.rs b/vm/src/builtins/union.rs index 6c6a03a9d..668d87bdc 100644 --- a/vm/src/builtins/union.rs +++ b/vm/src/builtins/union.rs @@ -15,7 +15,7 @@ use std::fmt; const CLS_ATTRS: &[&str] = &["__module__"]; -#[pyclass(module = "types", name = "UnionType")] +#[pyclass(module = "types", name = "UnionType", traverse)] pub struct PyUnion { args: PyTupleRef, parameters: PyTupleRef, diff --git a/vm/src/builtins/weakproxy.rs b/vm/src/builtins/weakproxy.rs index fdd5c1b89..d503ef917 100644 --- a/vm/src/builtins/weakproxy.rs +++ b/vm/src/builtins/weakproxy.rs @@ -13,7 +13,7 @@ use crate::{ }; use once_cell::sync::Lazy; -#[pyclass(module = false, name = "weakproxy", unhashable = true)] +#[pyclass(module = false, name = "weakproxy", unhashable = true, traverse)] #[derive(Debug)] pub struct PyWeakProxy { weak: PyRef, diff --git a/vm/src/builtins/weakref.rs b/vm/src/builtins/weakref.rs index 1d52225a2..e85aba850 100644 --- a/vm/src/builtins/weakref.rs +++ b/vm/src/builtins/weakref.rs @@ -12,7 +12,7 @@ use crate::{ pub use crate::object::PyWeak; -#[derive(FromArgs)] +#[derive(FromArgs, Traverse)] pub struct WeakNewArgs { #[pyarg(positional)] referent: PyObjectRef, diff --git a/vm/src/builtins/zip.rs b/vm/src/builtins/zip.rs index 84e61a04f..d8b28e8eb 100644 --- a/vm/src/builtins/zip.rs +++ b/vm/src/builtins/zip.rs @@ -9,10 +9,11 @@ use crate::{ }; use rustpython_common::atomic::{self, PyAtomic, Radium}; -#[pyclass(module = false, name = "zip")] +#[pyclass(module = false, name = "zip", traverse)] #[derive(Debug)] pub struct PyZip { iterators: Vec, + #[pytraverse(skip)] strict: PyAtomic, } diff --git a/vm/src/class.rs b/vm/src/class.rs index eef9d9452..a9a00c4a8 100644 --- a/vm/src/class.rs +++ b/vm/src/class.rs @@ -60,6 +60,10 @@ pub trait PyClassDef { const DOC: Option<&'static str> = None; const BASICSIZE: usize; const UNHASHABLE: bool = false; + + // due to restriction of rust trait system, object.__base__ is None + // but PyBaseObject::Base will be PyBaseObject. + type Base: PyClassDef; } pub trait PyClassImpl: PyClassDef { @@ -98,14 +102,12 @@ pub trait PyClassImpl: PyClassDef { ctx.new_str(module_name).into(), ); } - if class.slots.new.load().is_some() { - let bound = PyBoundMethod::new_ref( - class.to_owned().into(), - ctx.slot_new_wrapper.clone().into(), - ctx, - ); - class.set_attr(identifier!(ctx, __new__), bound.into()); - } + let bound_new = PyBoundMethod::new_ref( + class.to_owned().into(), + ctx.slot_new_wrapper.clone().into(), + ctx, + ); + class.set_attr(identifier!(ctx, __new__), bound_new.into()); if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize { class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 66baee6ed..5e5ae9d58 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -3,15 +3,18 @@ //! And: https://www.youtube.com/watch?v=p33CVV29OG8 //! And: http://code.activestate.com/recipes/578375/ -use crate::common::{ - hash, - lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}, -}; use crate::{ builtins::{PyInt, PyStr, PyStrInterned, PyStrRef}, convert::ToPyObject, AsObject, Py, PyExact, PyObject, PyObjectRef, PyRefExact, PyResult, VirtualMachine, }; +use crate::{ + common::{ + hash, + lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}, + }, + object::{Traverse, TraverseFn}, +}; use num_traits::ToPrimitive; use std::{fmt, mem::size_of, ops::ControlFlow}; @@ -31,6 +34,12 @@ pub struct Dict { inner: PyRwLock>, } +unsafe impl Traverse for Dict { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.inner.traverse(tracer_fn); + } +} + impl fmt::Debug for Dict { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Debug").finish() @@ -69,6 +78,20 @@ struct DictInner { entries: Vec>>, } +unsafe impl Traverse for DictInner { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.entries + .iter() + .map(|v| { + if let Some(v) = v { + v.key.traverse(tracer_fn); + v.value.traverse(tracer_fn); + } + }) + .count(); + } +} + impl Clone for Dict { fn clone(&self) -> Self { Self { diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 773ac53cc..3762ee5d0 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1,5 +1,6 @@ use self::types::{PyBaseException, PyBaseExceptionRef}; use crate::common::{lock::PyRwLock, str::ReprOverflowError}; +use crate::object::{Traverse, TraverseFn}; use crate::{ builtins::{ traceback::PyTracebackRef, tuple::IntoPyTuple, PyNone, PyStr, PyStrRef, PyTuple, @@ -11,7 +12,7 @@ use crate::{ py_io::{self, Write}, stdlib::sys, suggestion::offer_suggestions, - types::{Callable, Constructor, Initializer}, + types::{Callable, Constructor, Initializer, Representable}, AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, }; use crossbeam_utils::atomic::AtomicCell; @@ -21,6 +22,15 @@ use std::{ io::{self, BufRead, BufReader}, }; +unsafe impl Traverse for PyBaseException { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.traceback.traverse(tracer_fn); + self.cause.traverse(tracer_fn); + self.context.traverse(tracer_fn); + self.args.traverse(tracer_fn); + } +} + impl std::fmt::Debug for PyBaseException { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { // TODO: implement more detailed, non-recursive Debug formatter @@ -430,7 +440,10 @@ impl PyBaseException { } } -#[pyclass(with(Constructor, Initializer), flags(BASETYPE, HAS_DICT))] +#[pyclass( + with(Constructor, Initializer, Representable), + flags(BASETYPE, HAS_DICT) +)] impl PyBaseException { #[pygetset] pub fn args(&self) -> PyTupleRef { @@ -502,13 +515,6 @@ impl PyBaseException { } } - #[pymethod(magic)] - fn repr(zelf: PyRef, vm: &VirtualMachine) -> String { - let repr_args = vm.exception_args_as_string(zelf.args(), false); - let cls = zelf.class(); - format!("{}({})", cls.name(), repr_args.iter().format(", ")) - } - #[pymethod(magic)] fn reduce(zelf: PyRef, vm: &VirtualMachine) -> PyTupleRef { if let Some(dict) = zelf.as_object().dict().filter(|x| !x.is_empty()) { @@ -538,6 +544,15 @@ impl Initializer for PyBaseException { } } +impl Representable for PyBaseException { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let repr_args = vm.exception_args_as_string(zelf.args(), false); + let cls = zelf.class(); + Ok(format!("{}({})", cls.name(), repr_args.iter().format(", "))) + } +} + impl ExceptionZoo { pub(crate) fn init() -> Self { use self::types::*; @@ -1138,7 +1153,7 @@ pub(super) mod types { // Sorted By Hierarchy then alphabetized. - #[pyclass(module = false, name = "BaseException")] + #[pyclass(module = false, name = "BaseException", traverse = "manual")] pub struct PyBaseException { pub(super) traceback: PyRwLock>, pub(super) cause: PyRwLock>>, @@ -1147,502 +1162,373 @@ pub(super) mod types { pub(super) args: PyRwLock, } - define_exception! { - PySystemExit, - PyBaseException, - system_exit, - "Request to exit from the interpreter." - } - define_exception! { - PyBaseExceptionGroup, - PyBaseException, - base_exception_group, - "A combination of multiple unrelated exceptions." - } - define_exception! { - PyGeneratorExit, - PyBaseException, - generator_exit, - "Request that a generator exit." - } - define_exception! { - PyKeyboardInterrupt, - PyBaseException, - keyboard_interrupt, - "Program interrupted by user." + #[pyexception(name, base = "PyBaseException", ctx = "system_exit", impl)] + #[derive(Debug)] + pub struct PySystemExit {} + + #[pyexception(name, base = "PyBaseException", ctx = "base_exception_group", impl)] + #[derive(Debug)] + pub struct PyBaseExceptionGroup {} + + #[pyexception(name, base = "PyBaseException", ctx = "generator_exit", impl)] + #[derive(Debug)] + pub struct PyGeneratorExit {} + + #[pyexception(name, base = "PyBaseException", ctx = "keyboard_interrupt", impl)] + #[derive(Debug)] + pub struct PyKeyboardInterrupt {} + + #[pyexception(name, base = "PyBaseException", ctx = "exception_type", impl)] + #[derive(Debug)] + pub struct PyException {} + + #[pyexception(name, base = "PyException", ctx = "stop_iteration")] + #[derive(Debug)] + pub struct PyStopIteration {} + + #[pyexception] + impl PyStopIteration { + #[pyslot] + #[pymethod(name = "__init__")] + pub(crate) fn slot_init( + zelf: PyObjectRef, + args: ::rustpython_vm::function::FuncArgs, + vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult<()> { + zelf.set_attr("value", vm.unwrap_or_none(args.args.get(0).cloned()), vm)?; + Ok(()) + } } - // Base `Exception` type - define_exception! { - PyException, - PyBaseException, - exception_type, - "Common base class for all non-exit exceptions." + #[pyexception(name, base = "PyException", ctx = "stop_async_iteration", impl)] + #[derive(Debug)] + pub struct PyStopAsyncIteration {} + + #[pyexception(name, base = "PyException", ctx = "arithmetic_error", impl)] + #[derive(Debug)] + pub struct PyArithmeticError {} + + #[pyexception(name, base = "PyArithmeticError", ctx = "floating_point_error", impl)] + #[derive(Debug)] + pub struct PyFloatingPointError {} + + #[pyexception(name, base = "PyArithmeticError", ctx = "overflow_error", impl)] + #[derive(Debug)] + pub struct PyOverflowError {} + + #[pyexception(name, base = "PyArithmeticError", ctx = "zero_division_error", impl)] + #[derive(Debug)] + pub struct PyZeroDivisionError {} + + #[pyexception(name, base = "PyException", ctx = "assertion_error", impl)] + #[derive(Debug)] + pub struct PyAssertionError {} + + #[pyexception(name, base = "PyException", ctx = "attribute_error", impl)] + #[derive(Debug)] + pub struct PyAttributeError {} + + #[pyexception(name, base = "PyException", ctx = "buffer_error", impl)] + #[derive(Debug)] + pub struct PyBufferError {} + + #[pyexception(name, base = "PyException", ctx = "eof_error", impl)] + #[derive(Debug)] + pub struct PyEOFError {} + + #[pyexception(name, base = "PyException", ctx = "import_error")] + #[derive(Debug)] + pub struct PyImportError {} + + #[pyexception] + impl PyImportError { + #[pyslot] + #[pymethod(name = "__init__")] + pub(crate) fn slot_init( + zelf: PyObjectRef, + args: ::rustpython_vm::function::FuncArgs, + vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult<()> { + zelf.set_attr( + "name", + vm.unwrap_or_none(args.kwargs.get("name").cloned()), + vm, + )?; + zelf.set_attr( + "path", + vm.unwrap_or_none(args.kwargs.get("path").cloned()), + vm, + )?; + Ok(()) + } } - define_exception! { - PyStopIteration, - PyException, - stop_iteration, - "Signal the end from iterator.__next__().", - base_exception_new, - stop_iteration_init - } - fn stop_iteration_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - zelf.set_attr("value", vm.unwrap_or_none(args.args.get(0).cloned()), vm)?; - Ok(()) - } + #[pyexception(name, base = "PyImportError", ctx = "module_not_found_error", impl)] + #[derive(Debug)] + pub struct PyModuleNotFoundError {} - define_exception! { - PyStopAsyncIteration, - PyException, - stop_async_iteration, - "Signal the end from iterator.__anext__()." - } + #[pyexception(name, base = "PyException", ctx = "lookup_error", impl)] + #[derive(Debug)] + pub struct PyLookupError {} - define_exception! { - PyArithmeticError, - PyException, - arithmetic_error, - "Base class for arithmetic errors." - } - define_exception! { - PyFloatingPointError, - PyArithmeticError, - floating_point_error, - "Floating point operation failed." - } - define_exception! { - PyOverflowError, - PyArithmeticError, - overflow_error, - "Result too large to be represented." - } - define_exception! { - PyZeroDivisionError, - PyArithmeticError, - zero_division_error, - "Second argument to a division or modulo operation was zero." - } + #[pyexception(name, base = "PyLookupError", ctx = "index_error", impl)] + #[derive(Debug)] + pub struct PyIndexError {} - define_exception! { - PyAssertionError, - PyException, - assertion_error, - "Assertion failed." - } - define_exception! { - PyAttributeError, - PyException, - attribute_error, - "Attribute not found." - } - define_exception! { - PyBufferError, - PyException, - buffer_error, - "Buffer error." - } - define_exception! { - PyEOFError, - PyException, - eof_error, - "Read beyond end of file." - } + #[pyexception(name, base = "PyLookupError", ctx = "key_error", impl)] + #[derive(Debug)] + pub struct PyKeyError {} - define_exception! { - PyImportError, - PyException, - import_error, - "Import can't find module, or can't find name in module.", - base_exception_new, - import_error_init, - } + #[pyexception(name, base = "PyException", ctx = "memory_error", impl)] + #[derive(Debug)] + pub struct PyMemoryError {} - fn base_exception_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - PyBaseException::slot_new(cls, args, vm) - } + #[pyexception(name, base = "PyException", ctx = "name_error", impl)] + #[derive(Debug)] + pub struct PyNameError {} - fn import_error_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - zelf.set_attr( - "name", - vm.unwrap_or_none(args.kwargs.get("name").cloned()), - vm, - )?; - zelf.set_attr( - "path", - vm.unwrap_or_none(args.kwargs.get("path").cloned()), - vm, - )?; - Ok(()) - } + #[pyexception(name, base = "PyNameError", ctx = "unbound_local_error", impl)] + #[derive(Debug)] + pub struct PyUnboundLocalError {} - define_exception! { - PyModuleNotFoundError, - PyImportError, - module_not_found_error, - "Module not found." - } - - define_exception! { - PyLookupError, - PyException, - lookup_error, - "Base class for lookup errors." - } - define_exception! { - PyIndexError, - PyLookupError, - index_error, - "Sequence index out of range." - } - define_exception! { - PyKeyError, - PyLookupError, - key_error, - "Mapping key not found." - } - - define_exception! { - PyMemoryError, - PyException, - memory_error, - "Out of memory." - } - - define_exception! { - PyNameError, - PyException, - name_error, - "Name not found globally." - } - define_exception! { - PyUnboundLocalError, - PyNameError, - unbound_local_error, - "Local name referenced but not bound to a value." - } + #[pyexception(name, base = "PyException", ctx = "os_error")] + #[derive(Debug)] + pub struct PyOSError {} // OS Errors: - define_exception! { - PyOSError, - PyException, - os_error, - "Base class for I/O related errors.", - os_error_new, - os_error_init, - } - #[cfg(not(target_arch = "wasm32"))] - fn os_error_optional_new( - args: Vec, - vm: &VirtualMachine, - ) -> Option { - let len = args.len(); - if (2..=5).contains(&len) { - let errno = &args[0]; - errno - .payload_if_subclass::(vm) - .and_then(|errno| errno.try_to_primitive::(vm).ok()) - .and_then(|errno| super::raw_os_error_to_exc_type(errno, vm)) - .and_then(|typ| vm.invoke_exception(typ.to_owned(), args.to_vec()).ok()) - } else { - None - } - } - #[cfg(not(target_arch = "wasm32"))] - fn os_error_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // We need this method, because of how `CPython` copies `init` - // from `BaseException` in `SimpleExtendsException` macro. - // See: `BaseException_new` - if *cls.name() == *vm.ctx.exceptions.os_error.name() { - match os_error_optional_new(args.args.to_vec(), vm) { - Some(error) => error.to_pyresult(vm), - None => PyBaseException::slot_new(cls, args, vm), + #[pyexception] + impl PyOSError { + #[cfg(not(target_arch = "wasm32"))] + fn optional_new(args: Vec, vm: &VirtualMachine) -> Option { + let len = args.len(); + if (2..=5).contains(&len) { + let errno = &args[0]; + errno + .payload_if_subclass::(vm) + .and_then(|errno| errno.try_to_primitive::(vm).ok()) + .and_then(|errno| super::raw_os_error_to_exc_type(errno, vm)) + .and_then(|typ| vm.invoke_exception(typ.to_owned(), args.to_vec()).ok()) + } else { + None } - } else { + } + #[cfg(not(target_arch = "wasm32"))] + #[pyslot] + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + // We need this method, because of how `CPython` copies `init` + // from `BaseException` in `SimpleExtendsException` macro. + // See: `BaseException_new` + if *cls.name() == *vm.ctx.exceptions.os_error.name() { + match Self::optional_new(args.args.to_vec(), vm) { + Some(error) => error.to_pyresult(vm), + None => PyBaseException::slot_new(cls, args, vm), + } + } else { + PyBaseException::slot_new(cls, args, vm) + } + } + #[cfg(target_arch = "wasm32")] + #[pyslot] + fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { PyBaseException::slot_new(cls, args, vm) } - } - #[cfg(target_arch = "wasm32")] - fn os_error_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - PyBaseException::slot_new(cls, args, vm) - } - fn os_error_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { - let len = args.args.len(); - let mut new_args = args; - if (3..=5).contains(&len) { - zelf.set_attr("filename", new_args.args[2].clone(), vm)?; - if len == 5 { - zelf.set_attr("filename2", new_args.args[4].clone(), vm)?; + #[pyslot] + #[pymethod(name = "__init__")] + fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { + let len = args.args.len(); + let mut new_args = args; + if (3..=5).contains(&len) { + zelf.set_attr("filename", new_args.args[2].clone(), vm)?; + if len == 5 { + zelf.set_attr("filename2", new_args.args[4].clone(), vm)?; + } + + new_args.args.truncate(2); } - - new_args.args.truncate(2); + PyBaseException::slot_init(zelf, new_args, vm) } - PyBaseException::slot_init(zelf, new_args, vm) } - define_exception! { - PyBlockingIOError, - PyOSError, - blocking_io_error, - "I/O operation would block." - } - define_exception! { - PyChildProcessError, - PyOSError, - child_process_error, - "Child process error." - } - define_exception! { - PyConnectionError, - PyOSError, - connection_error, - "Connection error." - } - define_exception! { - PyBrokenPipeError, - PyConnectionError, - broken_pipe_error, - "Broken pipe." - } - define_exception! { - PyConnectionAbortedError, - PyConnectionError, - connection_aborted_error, - "Connection aborted." - } - define_exception! { - PyConnectionRefusedError, - PyConnectionError, - connection_refused_error, - "Connection refused." - } - define_exception! { - PyConnectionResetError, - PyConnectionError, - connection_reset_error, - "Connection reset." - } - define_exception! { - PyFileExistsError, - PyOSError, - file_exists_error, - "File already exists." - } - define_exception! { - PyFileNotFoundError, - PyOSError, - file_not_found_error, - "File not found." - } - define_exception! { - PyInterruptedError, - PyOSError, - interrupted_error, - "Interrupted by signal." - } - define_exception! { - PyIsADirectoryError, - PyOSError, - is_a_directory_error, - "Operation doesn't work on directories." - } - define_exception! { - PyNotADirectoryError, - PyOSError, - not_a_directory_error, - "Operation only works on directories." - } - define_exception! { - PyPermissionError, - PyOSError, - permission_error, - "Not enough permissions." - } - define_exception! { - PyProcessLookupError, - PyOSError, - process_lookup_error, - "Process not found." - } - define_exception! { - PyTimeoutError, - PyOSError, - timeout_error, - "Timeout expired." - } + #[pyexception(name, base = "PyOSError", ctx = "blocking_io_error", impl)] + #[derive(Debug)] + pub struct PyBlockingIOError {} - define_exception! { - PyReferenceError, - PyException, - reference_error, - "Weak ref proxy used after referent went away." - } + #[pyexception(name, base = "PyOSError", ctx = "child_process_error", impl)] + #[derive(Debug)] + pub struct PyChildProcessError {} - define_exception! { - PyRuntimeError, - PyException, - runtime_error, - "Unspecified run-time error." - } - define_exception! { - PyNotImplementedError, - PyRuntimeError, - not_implemented_error, - "Method or function hasn't been implemented yet." - } - define_exception! { - PyRecursionError, - PyRuntimeError, - recursion_error, - "Recursion limit exceeded." - } + #[pyexception(name, base = "PyOSError", ctx = "connection_error", impl)] + #[derive(Debug)] + pub struct PyConnectionError {} - define_exception! { - PySyntaxError, - PyException, - syntax_error, - "Invalid syntax." - } - define_exception! { - PyIndentationError, - PySyntaxError, - indentation_error, - "Improper indentation." - } - define_exception! { - PyTabError, - PyIndentationError, - tab_error, - "Improper mixture of spaces and tabs." - } + #[pyexception(name, base = "PyConnectionError", ctx = "broken_pipe_error", impl)] + #[derive(Debug)] + pub struct PyBrokenPipeError {} - define_exception! { - PySystemError, - PyException, - system_error, - "Internal error in the Python interpreter.\n\nPlease report this to the Python maintainer, along with the traceback,\nthe Python version, and the hardware/OS platform and version." - } + #[pyexception( + name, + base = "PyConnectionError", + ctx = "connection_aborted_error", + impl + )] + #[derive(Debug)] + pub struct PyConnectionAbortedError {} - define_exception! { - PyTypeError, - PyException, - type_error, - "Inappropriate argument type." - } + #[pyexception( + name, + base = "PyConnectionError", + ctx = "connection_refused_error", + impl + )] + #[derive(Debug)] + pub struct PyConnectionRefusedError {} - define_exception! { - PyValueError, - PyException, - value_error, - "Inappropriate argument value (of correct type)." - } - define_exception! { - PyUnicodeError, - PyValueError, - unicode_error, - "Unicode related error." - } - define_exception! { - PyUnicodeDecodeError, - PyUnicodeError, - unicode_decode_error, - "Unicode decoding error." - } - define_exception! { - PyUnicodeEncodeError, - PyUnicodeError, - unicode_encode_error, - "Unicode encoding error." - } - define_exception! { - PyUnicodeTranslateError, - PyUnicodeError, - unicode_translate_error, - "Unicode translation error." - } + #[pyexception(name, base = "PyConnectionError", ctx = "connection_reset_error", impl)] + #[derive(Debug)] + pub struct PyConnectionResetError {} + #[pyexception(name, base = "PyOSError", ctx = "file_exists_error", impl)] + #[derive(Debug)] + pub struct PyFileExistsError {} + + #[pyexception(name, base = "PyOSError", ctx = "file_not_found_error", impl)] + #[derive(Debug)] + pub struct PyFileNotFoundError {} + + #[pyexception(name, base = "PyOSError", ctx = "interrupted_error", impl)] + #[derive(Debug)] + pub struct PyInterruptedError {} + + #[pyexception(name, base = "PyOSError", ctx = "is_a_directory_error", impl)] + #[derive(Debug)] + pub struct PyIsADirectoryError {} + + #[pyexception(name, base = "PyOSError", ctx = "not_a_directory_error", impl)] + #[derive(Debug)] + pub struct PyNotADirectoryError {} + + #[pyexception(name, base = "PyOSError", ctx = "permission_error", impl)] + #[derive(Debug)] + pub struct PyPermissionError {} + + #[pyexception(name, base = "PyOSError", ctx = "process_lookup_error", impl)] + #[derive(Debug)] + pub struct PyProcessLookupError {} + + #[pyexception(name, base = "PyOSError", ctx = "timeout_error", impl)] + #[derive(Debug)] + pub struct PyTimeoutError {} + + #[pyexception(name, base = "PyException", ctx = "reference_error", impl)] + #[derive(Debug)] + pub struct PyReferenceError {} + + #[pyexception(name, base = "PyException", ctx = "runtime_error", impl)] + #[derive(Debug)] + pub struct PyRuntimeError {} + + #[pyexception(name, base = "PyRuntimeError", ctx = "not_implemented_error", impl)] + #[derive(Debug)] + pub struct PyNotImplementedError {} + + #[pyexception(name, base = "PyRuntimeError", ctx = "recursion_error", impl)] + #[derive(Debug)] + pub struct PyRecursionError {} + + #[pyexception(name, base = "PyException", ctx = "syntax_error", impl)] + #[derive(Debug)] + pub struct PySyntaxError {} + + #[pyexception(name, base = "PySyntaxError", ctx = "indentation_error", impl)] + #[derive(Debug)] + pub struct PyIndentationError {} + + #[pyexception(name, base = "PyIndentationError", ctx = "tab_error", impl)] + #[derive(Debug)] + pub struct PyTabError {} + + #[pyexception(name, base = "PyException", ctx = "system_error", impl)] + #[derive(Debug)] + pub struct PySystemError {} + + #[pyexception(name, base = "PyException", ctx = "type_error", impl)] + #[derive(Debug)] + pub struct PyTypeError {} + + #[pyexception(name, base = "PyException", ctx = "value_error", impl)] + #[derive(Debug)] + pub struct PyValueError {} + + #[pyexception(name, base = "PyValueError", ctx = "unicode_error", impl)] + #[derive(Debug)] + pub struct PyUnicodeError {} + + #[pyexception(name, base = "PyUnicodeError", ctx = "unicode_decode_error", impl)] + #[derive(Debug)] + pub struct PyUnicodeDecodeError {} + + #[pyexception(name, base = "PyUnicodeError", ctx = "unicode_encode_error", impl)] + #[derive(Debug)] + pub struct PyUnicodeEncodeError {} + + #[pyexception(name, base = "PyUnicodeError", ctx = "unicode_translate_error", impl)] + #[derive(Debug)] + pub struct PyUnicodeTranslateError {} + + /// JIT error. #[cfg(feature = "jit")] - define_exception! { - PyJitError, - PyException, - jit_error, - "JIT error." - } + #[pyexception(name, base = "PyException", ctx = "jit_error", impl)] + #[derive(Debug)] + pub struct PyJitError {} // Warnings - define_exception! { - PyWarning, - PyException, - warning, - "Base class for warning categories." - } - define_exception! { - PyDeprecationWarning, - PyWarning, - deprecation_warning, - "Base class for warnings about deprecated features." - } - define_exception! { - PyPendingDeprecationWarning, - PyWarning, - pending_deprecation_warning, - "Base class for warnings about features which will be deprecated\nin the future." - } - define_exception! { - PyRuntimeWarning, - PyWarning, - runtime_warning, - "Base class for warnings about dubious runtime behavior." - } - define_exception! { - PySyntaxWarning, - PyWarning, - syntax_warning, - "Base class for warnings about dubious syntax." - } - define_exception! { - PyUserWarning, - PyWarning, - user_warning, - "Base class for warnings generated by user code." - } - define_exception! { - PyFutureWarning, - PyWarning, - future_warning, - "Base class for warnings about constructs that will change semantically\nin the future." - } - define_exception! { - PyImportWarning, - PyWarning, - import_warning, - "Base class for warnings about probable mistakes in module imports." - } - define_exception! { - PyUnicodeWarning, - PyWarning, - unicode_warning, - "Base class for warnings about Unicode related problems, mostly\nrelated to conversion problems." - } - define_exception! { - PyBytesWarning, - PyWarning, - bytes_warning, - "Base class for warnings about bytes and buffer related problems, mostly\nrelated to conversion from str or comparing to str." - } - define_exception! { - PyResourceWarning, - PyWarning, - resource_warning, - "Base class for warnings about resource usage." - } - define_exception! { - PyEncodingWarning, - PyWarning, - encoding_warning, - "Base class for warnings about encodings." - } + #[pyexception(name, base = "PyException", ctx = "warning", impl)] + #[derive(Debug)] + pub struct PyWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "deprecation_warning", impl)] + #[derive(Debug)] + pub struct PyDeprecationWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "pending_deprecation_warning", impl)] + #[derive(Debug)] + pub struct PyPendingDeprecationWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "runtime_warning", impl)] + #[derive(Debug)] + pub struct PyRuntimeWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "syntax_warning", impl)] + #[derive(Debug)] + pub struct PySyntaxWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "user_warning", impl)] + #[derive(Debug)] + pub struct PyUserWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "future_warning", impl)] + #[derive(Debug)] + pub struct PyFutureWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "import_warning", impl)] + #[derive(Debug)] + pub struct PyImportWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "unicode_warning", impl)] + #[derive(Debug)] + pub struct PyUnicodeWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "bytes_warning", impl)] + #[derive(Debug)] + pub struct PyBytesWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "resource_warning", impl)] + #[derive(Debug)] + pub struct PyResourceWarning {} + + #[pyexception(name, base = "PyWarning", ctx = "encoding_warning", impl)] + #[derive(Debug)] + pub struct PyEncodingWarning {} } impl ToPyException for ReprOverflowError { diff --git a/vm/src/function/argument.rs b/vm/src/function/argument.rs index f4df42c84..bcc711a1e 100644 --- a/vm/src/function/argument.rs +++ b/vm/src/function/argument.rs @@ -1,6 +1,7 @@ use crate::{ builtins::{PyBaseExceptionRef, PyTupleRef, PyTypeRef}, convert::ToPyObject, + object::{Traverse, TraverseFn}, AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, }; use indexmap::IndexMap; @@ -57,13 +58,19 @@ into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5)); /// The `FuncArgs` struct is one of the most used structs then creating /// a rust function that can be called from python. It holds both positional /// arguments, as well as keyword arguments passed to the function. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Traverse)] pub struct FuncArgs { pub args: Vec, // sorted map, according to https://www.python.org/dev/peps/pep-0468/ pub kwargs: IndexMap, } +unsafe impl Traverse for IndexMap { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.values().for_each(|v| v.traverse(tracer_fn)); + } +} + /// Conversion from vector of python objects to function arguments. impl From for FuncArgs where @@ -320,6 +327,15 @@ impl FromArgOptional for T { #[derive(Clone)] pub struct KwArgs(IndexMap); +unsafe impl Traverse for KwArgs +where + T: Traverse, +{ + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.0.iter().map(|(_, v)| v.traverse(tracer_fn)).count(); + } +} + impl KwArgs { pub fn new(map: IndexMap) -> Self { KwArgs(map) @@ -377,6 +393,15 @@ impl IntoIterator for KwArgs { #[derive(Clone)] pub struct PosArgs(Vec); +unsafe impl Traverse for PosArgs +where + T: Traverse, +{ + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.0.traverse(tracer_fn) + } +} + impl PosArgs { pub fn new(args: Vec) -> Self { Self(args) @@ -461,6 +486,18 @@ pub enum OptionalArg { Missing, } +unsafe impl Traverse for OptionalArg +where + T: Traverse, +{ + fn traverse(&self, tracer_fn: &mut TraverseFn) { + match self { + OptionalArg::Present(ref o) => o.traverse(tracer_fn), + OptionalArg::Missing => (), + } + } +} + impl OptionalArg { pub fn unwrap_or_none(self, vm: &VirtualMachine) -> PyObjectRef { self.unwrap_or_else(|| vm.ctx.none()) diff --git a/vm/src/function/buffer.rs b/vm/src/function/buffer.rs index 2938e915e..f5d0dd03d 100644 --- a/vm/src/function/buffer.rs +++ b/vm/src/function/buffer.rs @@ -2,13 +2,14 @@ use crate::{ builtins::{PyStr, PyStrRef}, common::borrow::{BorrowedValue, BorrowedValueMut}, protocol::PyBuffer, - PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject, VirtualMachine, + AsObject, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject, + VirtualMachine, }; // Python/getargs.c /// any bytes-like object. Like the `y*` format code for `PyArg_Parse` in CPython. -#[derive(Debug)] +#[derive(Debug, Traverse)] pub struct ArgBytesLike(PyBuffer); impl PyObject { @@ -57,6 +58,10 @@ impl ArgBytesLike { pub fn is_empty(&self) -> bool { self.len() == 0 } + + pub fn as_object(&self) -> &PyObject { + &self.0.obj + } } impl From for PyBuffer { @@ -77,7 +82,7 @@ impl<'a> TryFromBorrowedObject<'a> for ArgBytesLike { } /// A memory buffer, read-write access. Like the `w*` format code for `PyArg_Parse` in CPython. -#[derive(Debug)] +#[derive(Debug, Traverse)] pub struct ArgMemoryBuffer(PyBuffer); impl ArgMemoryBuffer { @@ -126,6 +131,15 @@ pub enum ArgStrOrBytesLike { Str(PyStrRef), } +impl ArgStrOrBytesLike { + pub fn as_object(&self) -> &PyObject { + match self { + Self::Buf(b) => b.as_object(), + Self::Str(s) => s.as_object(), + } + } +} + impl TryFromObject for ArgStrOrBytesLike { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { obj.downcast() diff --git a/vm/src/function/number.rs b/vm/src/function/number.rs index d01c0fe36..4292b82a0 100644 --- a/vm/src/function/number.rs +++ b/vm/src/function/number.rs @@ -130,7 +130,7 @@ impl TryFromObject for ArgIntoBool { } // Implement ArgIndex to separate between "true" int and int generated by index -#[derive(Debug)] +#[derive(Debug, Traverse)] #[repr(transparent)] pub struct ArgIndex { value: PyIntRef, diff --git a/vm/src/function/protocol.rs b/vm/src/function/protocol.rs index 5d6c0df8a..a70dd9e53 100644 --- a/vm/src/function/protocol.rs +++ b/vm/src/function/protocol.rs @@ -3,22 +3,25 @@ use crate::{ builtins::{iter::PySequenceIterator, PyDict, PyDictRef}, convert::ToPyObject, identifier, + object::{Traverse, TraverseFn}, protocol::{PyIter, PyIterIter, PyMapping, PyMappingMethods}, types::{AsMapping, GenericMethod}, AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, }; use std::{borrow::Borrow, marker::PhantomData, ops::Deref}; -#[derive(Clone)] +#[derive(Clone, Traverse)] pub struct ArgCallable { obj: PyObjectRef, + #[pytraverse(skip)] call: GenericMethod, } impl ArgCallable { #[inline(always)] pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult { - (self.call)(&self.obj, args.into_args(vm), vm) + let args = args.into_args(vm); + (self.call)(&self.obj, args, vm) } } @@ -75,6 +78,12 @@ pub struct ArgIterable { _item: PhantomData, } +unsafe impl Traverse for ArgIterable { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.iterable.traverse(tracer_fn) + } +} + impl ArgIterable { /// Returns an iterator over this sequence of objects. /// @@ -110,9 +119,10 @@ where } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Traverse)] pub struct ArgMapping { obj: PyObjectRef, + #[pytraverse(skip)] methods: &'static PyMappingMethods, } @@ -187,6 +197,12 @@ impl TryFromObject for ArgMapping { #[derive(Clone)] pub struct ArgSequence(Vec); +unsafe impl Traverse for ArgSequence { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.0.traverse(tracer_fn); + } +} + impl ArgSequence { #[inline(always)] pub fn into_vec(self) -> Vec { diff --git a/vm/src/import.rs b/vm/src/import.rs index f9e13f9bf..5d7c0da65 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -85,6 +85,7 @@ pub fn make_frozen(vm: &VirtualMachine, name: &str) -> PyResult> { pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { let frozen = make_frozen(vm, module_name)?; let module = import_codeobj(vm, module_name, frozen, false)?; + debug_assert!(module.get_attr(identifier!(vm, __name__), vm).is_ok()); // TODO: give a correct origname here module.set_attr("__origname__", vm.ctx.new_str(module_name.to_owned()), vm)?; Ok(module) @@ -99,8 +100,8 @@ pub fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult { })?; let module = make_module_func(vm); let sys_modules = vm.sys_module.get_attr("modules", vm)?; - sys_modules.set_item(module_name, module.clone(), vm)?; - Ok(module) + sys_modules.set_item(module_name, module.as_object().to_owned(), vm)?; + Ok(module.into()) } #[cfg(feature = "rustpython-compiler")] @@ -144,12 +145,12 @@ pub fn import_codeobj( // Store module in cache to prevent infinite loop with mutual importing libs: let sys_modules = vm.sys_module.get_attr("modules", vm)?; - sys_modules.set_item(module_name, module.clone(), vm)?; + sys_modules.set_item(module_name, module.clone().into(), vm)?; // Execute main code in module: let scope = Scope::with_builtins(None, attrs, vm); vm.run_code_obj(code_obj, scope)?; - Ok(module) + Ok(module.into()) } fn remove_importlib_frames_inner( diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 5f990d1a4..e92959198 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -1,10 +1,8 @@ #[macro_export] macro_rules! extend_module { ( $vm:expr, $module:expr, { $($name:expr => $value:expr),* $(,)? }) => {{ - #[allow(unused_variables)] - let module: &$crate::PyObject = &$module; $( - $vm.__module_set_attr(&module, $name, $value).unwrap(); + $vm.__module_set_attr($module, $name, $value).unwrap(); )* }}; } @@ -17,7 +15,8 @@ macro_rules! py_class { ( $ctx:expr, $class_name:expr, $class_base:expr, $flags:expr, { $($name:tt => $value:expr),* $(,)* }) => { { #[allow(unused_mut)] - let mut slots = $crate::types::PyTypeSlots::new($class_name, $crate::types::PyTypeFlags::DEFAULT | $flags); + let mut slots = $crate::types::PyTypeSlots::heap_default(); + slots.flags = $flags; $($crate::py_class!(@extract_slots($ctx, &mut slots, $name, $value));)* let py_class = $ctx.new_class(None, $class_name, $class_base, slots); $($crate::py_class!(@extract_attrs($ctx, &py_class, $name, $value));)* @@ -48,8 +47,9 @@ macro_rules! py_namespace { ( $vm:expr, { $($name:expr => $value:expr),* $(,)* }) => { { let namespace = $crate::builtins::PyNamespace::new_ref(&$vm.ctx); + let obj = $crate::object::AsObject::as_object(&namespace); $( - $vm.__module_set_attr($crate::object::AsObject::as_object(&namespace), $name, $value).unwrap(); + obj.generic_setattr($vm.ctx.intern_str($name), $crate::function::PySetterValue::Assign($value.into()), $vm).unwrap(); )* namespace } diff --git a/vm/src/object/core.rs b/vm/src/object/core.rs index c855509da..0ca1e248f 100644 --- a/vm/src/object/core.rs +++ b/vm/src/object/core.rs @@ -10,12 +10,13 @@ //! //! PyRef may looking like to be called as PyObjectWeak by the rule, //! but not to do to remember it is a PyRef object. - use super::{ ext::{AsObject, PyRefExact, PyResult}, payload::PyObjectPayload, PyAtomicRef, }; +use crate::object::traverse::{Traverse, TraverseFn}; +use crate::object::traverse_object::PyObjVTable; use crate::{ builtins::{PyDictRef, PyType, PyTypeRef}, common::{ @@ -73,52 +74,42 @@ use std::{ /// A type to just represent "we've erased the type of this object, cast it before you use it" #[derive(Debug)] -struct Erased; +pub(super) struct Erased; -struct PyObjVTable { - drop_dealloc: unsafe fn(*mut PyObject), - debug: unsafe fn(&PyObject, &mut fmt::Formatter) -> fmt::Result, -} -unsafe fn drop_dealloc_obj(x: *mut PyObject) { +pub(super) unsafe fn drop_dealloc_obj(x: *mut PyObject) { drop(Box::from_raw(x as *mut PyInner)); } -unsafe fn debug_obj(x: &PyObject, f: &mut fmt::Formatter) -> fmt::Result { +pub(super) unsafe fn debug_obj( + x: &PyObject, + f: &mut fmt::Formatter, +) -> fmt::Result { let x = &*(x as *const PyObject as *const PyInner); fmt::Debug::fmt(x, f) } -impl PyObjVTable { - pub fn of() -> &'static Self { - struct Helper(PhantomData); - trait VtableHelper { - const VTABLE: PyObjVTable; - } - impl VtableHelper for Helper { - const VTABLE: PyObjVTable = PyObjVTable { - drop_dealloc: drop_dealloc_obj::, - debug: debug_obj::, - }; - } - &Helper::::VTABLE - } +/// Call `try_trace` on payload +pub(super) unsafe fn try_trace_obj(x: &PyObject, tracer_fn: &mut TraverseFn) { + let x = &*(x as *const PyObject as *const PyInner); + let payload = &x.payload; + payload.try_traverse(tracer_fn) } /// This is an actual python object. It consists of a `typ` which is the /// python class, and carries some rust payload optionally. This rust /// payload can be a rust float or rust int in case of float and int objects. #[repr(C)] -struct PyInner { - ref_count: RefCount, +pub(super) struct PyInner { + pub(super) ref_count: RefCount, // TODO: move typeid into vtable once TypeId::of is const - typeid: TypeId, - vtable: &'static PyObjVTable, + pub(super) typeid: TypeId, + pub(super) vtable: &'static PyObjVTable, - typ: PyAtomicRef, // __class__ member - dict: Option, - weak_list: WeakRefList, - slots: Box<[PyRwLock>]>, + pub(super) typ: PyAtomicRef, // __class__ member + pub(super) dict: Option, + pub(super) weak_list: WeakRefList, + pub(super) slots: Box<[PyRwLock>]>, - payload: T, + pub(super) payload: T, } impl fmt::Debug for PyInner { @@ -127,7 +118,23 @@ impl fmt::Debug for PyInner { } } -struct WeakRefList { +unsafe impl Traverse for Py { + /// DO notice that call `trace` on `Py` means apply `tracer_fn` on `Py`'s children, + /// not like call `trace` on `PyRef` which apply `tracer_fn` on `PyRef` itself + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.0.traverse(tracer_fn) + } +} + +unsafe impl Traverse for PyObject { + /// DO notice that call `trace` on `PyObject` means apply `tracer_fn` on `PyObject`'s children, + /// not like call `trace` on `PyObjectRef` which apply `tracer_fn` on `PyObjectRef` itself + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.0.traverse(tracer_fn) + } +} + +pub(super) struct WeakRefList { inner: OncePtr>, } @@ -393,8 +400,8 @@ impl Py { } #[derive(Debug)] -struct InstanceDict { - d: PyRwLock, +pub(super) struct InstanceDict { + pub(super) d: PyRwLock, } impl From for InstanceDict { @@ -471,7 +478,6 @@ cfg_if::cfg_if! { } } -#[derive(Debug)] #[repr(transparent)] pub struct PyObject(PyInner); @@ -522,7 +528,7 @@ impl PyObjectRef { #[inline(always)] pub fn downcast(self) -> Result, Self> { if self.payload_is::() { - Ok(unsafe { PyRef::from_obj_unchecked(self) }) + Ok(unsafe { self.downcast_unchecked() }) } else { Err(self) } @@ -539,6 +545,8 @@ impl PyObjectRef { } } + /// Force to downcast this reference to a subclass. + /// /// # Safety /// T must be the exact payload type #[inline(always)] @@ -637,13 +645,22 @@ impl PyObject { self.0.typeid == TypeId::of::() } + /// Force to return payload as T. + /// + /// # Safety + /// The actual payload type must be T. + #[inline(always)] + pub unsafe fn payload_unchecked(&self) -> &T { + // we cast to a PyInner first because we don't know T's exact offset because of + // varying alignment, but once we get a PyInner the compiler can get it for us + let inner = unsafe { &*(&self.0 as *const PyInner as *const PyInner) }; + &inner.payload + } + #[inline(always)] pub fn payload(&self) -> Option<&T> { if self.payload_is::() { - // we cast to a PyInner first because we don't know T's exact offset because of - // varying alignment, but once we get a PyInner the compiler can get it for us - let inner = unsafe { &*(&self.0 as *const PyInner as *const PyInner) }; - Some(&inner.payload) + Some(unsafe { self.payload_unchecked() }) } else { None } @@ -854,7 +871,7 @@ impl Drop for PyObjectRef { } } -impl fmt::Debug for PyObjectRef { +impl fmt::Debug for PyObject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // SAFETY: the vtable contains functions that accept payload types that always match up // with the payload of the object @@ -862,6 +879,12 @@ impl fmt::Debug for PyObjectRef { } } +impl fmt::Debug for PyObjectRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_object().fmt(f) + } +} + #[repr(transparent)] pub struct Py(PyInner); diff --git a/vm/src/object/mod.rs b/vm/src/object/mod.rs index 285063583..034523afe 100644 --- a/vm/src/object/mod.rs +++ b/vm/src/object/mod.rs @@ -1,7 +1,10 @@ mod core; mod ext; mod payload; +mod traverse; +mod traverse_object; pub use self::core::*; pub use self::ext::*; pub use self::payload::*; +pub use traverse::{MaybeTraverse, Traverse, TraverseFn}; diff --git a/vm/src/object/payload.rs b/vm/src/object/payload.rs index 6bf21ecaf..0ea79c674 100644 --- a/vm/src/object/payload.rs +++ b/vm/src/object/payload.rs @@ -1,4 +1,4 @@ -use super::{Py, PyObjectRef, PyRef, PyResult}; +use crate::object::{MaybeTraverse, Py, PyObjectRef, PyRef, PyResult}; use crate::{ builtins::{PyBaseExceptionRef, PyType, PyTypeRef}, types::PyTypeFlags, @@ -16,7 +16,9 @@ cfg_if::cfg_if! { } } -pub trait PyPayload: std::fmt::Debug + PyThreadingConstraint + Sized + 'static { +pub trait PyPayload: + std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + Sized + 'static +{ fn class(ctx: &Context) -> &'static Py; #[inline] @@ -73,7 +75,7 @@ pub trait PyPayload: std::fmt::Debug + PyThreadingConstraint + Sized + 'static { } pub trait PyObjectPayload: - std::any::Any + std::fmt::Debug + PyThreadingConstraint + 'static + std::any::Any + std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + 'static { } diff --git a/vm/src/object/traverse.rs b/vm/src/object/traverse.rs new file mode 100644 index 000000000..572bacafc --- /dev/null +++ b/vm/src/object/traverse.rs @@ -0,0 +1,231 @@ +use std::ptr::NonNull; + +use rustpython_common::lock::{PyMutex, PyRwLock}; + +use crate::{function::Either, object::PyObjectPayload, AsObject, PyObject, PyObjectRef, PyRef}; + +pub type TraverseFn<'a> = dyn FnMut(&PyObject) + 'a; + +/// This trait is used as a "Optional Trait"(I 'd like to use `Trace?` but it's not allowed yet) for PyObjectPayload type +/// +/// impl for PyObjectPayload, `pyclass` proc macro will handle the actual dispatch if type impl `Trace` +/// Every PyObjectPayload impl `MaybeTrace`, which may or may not be traceable +pub trait MaybeTraverse { + /// if is traceable, will be used by vtable to determine + const IS_TRACE: bool = false; + // if this type is traceable, then call with tracer_fn, default to do nothing + fn try_traverse(&self, traverse_fn: &mut TraverseFn); +} + +/// Type that need traverse it's children should impl `Traverse`(Not `MaybeTraverse`) +/// # Safety +/// impl `traverse()` with caution! Following those guideline so traverse doesn't cause memory error!: +/// - Make sure that every owned object(Every PyObjectRef/PyRef) is called with traverse_fn **at most once**. +/// If some field is not called, the worst results is just memory leak, +/// but if some field is called repeatly, panic and deadlock can happen. +/// +/// - _**DO NOT**_ clone a `PyObjectRef` or `Pyef` in `traverse()` +pub unsafe trait Traverse { + /// impl `traverse()` with caution! Following those guideline so traverse doesn't cause memory error!: + /// - Make sure that every owned object(Every PyObjectRef/PyRef) is called with traverse_fn **at most once**. + /// If some field is not called, the worst results is just memory leak, + /// but if some field is called repeatly, panic and deadlock can happen. + /// + /// - _**DO NOT**_ clone a `PyObjectRef` or `Pyef` in `traverse()` + fn traverse(&self, traverse_fn: &mut TraverseFn); +} + +unsafe impl Traverse for PyObjectRef { + fn traverse(&self, traverse_fn: &mut TraverseFn) { + traverse_fn(self) + } +} + +unsafe impl Traverse for PyRef { + fn traverse(&self, traverse_fn: &mut TraverseFn) { + traverse_fn(self.as_object()) + } +} + +unsafe impl Traverse for () { + fn traverse(&self, _traverse_fn: &mut TraverseFn) {} +} + +unsafe impl Traverse for Option { + #[inline] + fn traverse(&self, traverse_fn: &mut TraverseFn) { + if let Some(v) = self { + v.traverse(traverse_fn); + } + } +} + +unsafe impl Traverse for [T] +where + T: Traverse, +{ + #[inline] + fn traverse(&self, traverse_fn: &mut TraverseFn) { + for elem in self { + elem.traverse(traverse_fn); + } + } +} + +unsafe impl Traverse for Box<[T]> +where + T: Traverse, +{ + #[inline] + fn traverse(&self, traverse_fn: &mut TraverseFn) { + for elem in &**self { + elem.traverse(traverse_fn); + } + } +} + +unsafe impl Traverse for Vec +where + T: Traverse, +{ + #[inline] + fn traverse(&self, traverse_fn: &mut TraverseFn) { + for elem in self { + elem.traverse(traverse_fn); + } + } +} + +unsafe impl Traverse for PyRwLock { + #[inline] + fn traverse(&self, traverse_fn: &mut TraverseFn) { + // if can't get a lock, this means something else is holding the lock, + // but since gc stopped the world, during gc the lock is always held + // so it is safe to ignore those in gc + if let Some(inner) = self.try_read_recursive() { + inner.traverse(traverse_fn) + } + } +} + +/// Safety: We can't hold lock during traverse it's child because it may cause deadlock. +/// TODO(discord9): check if this is thread-safe to do +/// (Outside of gc phase, only incref/decref will call trace, +/// and refcnt is atomic, so it should be fine?) +unsafe impl Traverse for PyMutex { + #[inline] + fn traverse(&self, traverse_fn: &mut TraverseFn) { + let mut chs: Vec> = Vec::new(); + if let Some(obj) = self.try_lock() { + obj.traverse(&mut |ch| { + chs.push(NonNull::from(ch)); + }) + } + chs.iter() + .map(|ch| { + // Safety: during gc, this should be fine, because nothing should write during gc's tracing? + let ch = unsafe { ch.as_ref() }; + traverse_fn(ch); + }) + .count(); + } +} + +macro_rules! trace_tuple { + ($(($NAME: ident, $NUM: tt)),*) => { + unsafe impl<$($NAME: Traverse),*> Traverse for ($($NAME),*) { + #[inline] + fn traverse(&self, traverse_fn: &mut TraverseFn) { + $( + self.$NUM.traverse(traverse_fn); + )* + } + } + + }; +} + +unsafe impl Traverse for Either { + #[inline] + fn traverse(&self, tracer_fn: &mut TraverseFn) { + match self { + Either::A(a) => a.traverse(tracer_fn), + Either::B(b) => b.traverse(tracer_fn), + } + } +} + +// only tuple with 12 elements or less is supported, +// because long tuple is extremly rare in almost every case +unsafe impl Traverse for (A,) { + #[inline] + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.0.traverse(tracer_fn); + } +} +trace_tuple!((A, 0), (B, 1)); +trace_tuple!((A, 0), (B, 1), (C, 2)); +trace_tuple!((A, 0), (B, 1), (C, 2), (D, 3)); +trace_tuple!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4)); +trace_tuple!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5)); +trace_tuple!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6)); +trace_tuple!( + (A, 0), + (B, 1), + (C, 2), + (D, 3), + (E, 4), + (F, 5), + (G, 6), + (H, 7) +); +trace_tuple!( + (A, 0), + (B, 1), + (C, 2), + (D, 3), + (E, 4), + (F, 5), + (G, 6), + (H, 7), + (I, 8) +); +trace_tuple!( + (A, 0), + (B, 1), + (C, 2), + (D, 3), + (E, 4), + (F, 5), + (G, 6), + (H, 7), + (I, 8), + (J, 9) +); +trace_tuple!( + (A, 0), + (B, 1), + (C, 2), + (D, 3), + (E, 4), + (F, 5), + (G, 6), + (H, 7), + (I, 8), + (J, 9), + (K, 10) +); +trace_tuple!( + (A, 0), + (B, 1), + (C, 2), + (D, 3), + (E, 4), + (F, 5), + (G, 6), + (H, 7), + (I, 8), + (J, 9), + (K, 10), + (L, 11) +); diff --git a/vm/src/object/traverse_object.rs b/vm/src/object/traverse_object.rs new file mode 100644 index 000000000..38d91b2ac --- /dev/null +++ b/vm/src/object/traverse_object.rs @@ -0,0 +1,78 @@ +use std::{fmt, marker::PhantomData}; + +use crate::{ + object::{ + debug_obj, drop_dealloc_obj, try_trace_obj, Erased, InstanceDict, PyInner, PyObjectPayload, + }, + PyObject, +}; + +use super::{Traverse, TraverseFn}; + +pub(in crate::object) struct PyObjVTable { + pub(in crate::object) drop_dealloc: unsafe fn(*mut PyObject), + pub(in crate::object) debug: unsafe fn(&PyObject, &mut fmt::Formatter) -> fmt::Result, + pub(in crate::object) trace: Option, +} + +impl PyObjVTable { + pub fn of() -> &'static Self { + struct Helper(PhantomData); + trait VtableHelper { + const VTABLE: PyObjVTable; + } + impl VtableHelper for Helper { + const VTABLE: PyObjVTable = PyObjVTable { + drop_dealloc: drop_dealloc_obj::, + debug: debug_obj::, + trace: { + if T::IS_TRACE { + Some(try_trace_obj::) + } else { + None + } + }, + }; + } + &Helper::::VTABLE + } +} + +unsafe impl Traverse for InstanceDict { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.d.traverse(tracer_fn) + } +} + +unsafe impl Traverse for PyInner { + /// Because PyObject hold a `PyInner`, so we need to trace it + fn traverse(&self, tracer_fn: &mut TraverseFn) { + // 1. trace `dict` and `slots` field(`typ` can't trace for it's a AtomicRef while is leaked by design) + // 2. call vtable's trace function to trace payload + // self.typ.trace(tracer_fn); + self.dict.traverse(tracer_fn); + // weak_list keeps a *pointer* to a struct for maintaince weak ref, so no ownership, no trace + self.slots.traverse(tracer_fn); + + if let Some(f) = self.vtable.trace { + unsafe { + let zelf = &*(self as *const PyInner as *const PyObject); + f(zelf, tracer_fn) + } + }; + } +} + +unsafe impl Traverse for PyInner { + /// Type is known, so we can call `try_trace` directly instead of using erased type vtable + fn traverse(&self, tracer_fn: &mut TraverseFn) { + // 1. trace `dict` and `slots` field(`typ` can't trace for it's a AtomicRef while is leaked by design) + // 2. call corrsponding `try_trace` function to trace payload + // (No need to call vtable's trace function because we already know the type) + // self.typ.trace(tracer_fn); + self.dict.traverse(tracer_fn); + // weak_list keeps a *pointer* to a struct for maintaince weak ref, so no ownership, no trace + self.slots.traverse(tracer_fn); + T::try_traverse(&self.payload, tracer_fn); + } +} diff --git a/vm/src/protocol/buffer.rs b/vm/src/protocol/buffer.rs index 1450c837c..820a7c712 100644 --- a/vm/src/protocol/buffer.rs +++ b/vm/src/protocol/buffer.rs @@ -32,10 +32,12 @@ impl Debug for BufferMethods { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Traverse)] pub struct PyBuffer { pub obj: PyObjectRef, + #[pytraverse(skip)] pub desc: BufferDescriptor, + #[pytraverse(skip)] methods: &'static BufferMethods, } diff --git a/vm/src/protocol/callable.rs b/vm/src/protocol/callable.rs index 481e4559f..eb404d021 100644 --- a/vm/src/protocol/callable.rs +++ b/vm/src/protocol/callable.rs @@ -17,7 +17,8 @@ impl PyObject { /// PyObject_Call*Arg* series pub fn call(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult { - self.call_with_args(args.into_args(vm), vm) + let args = args.into_args(vm); + self.call_with_args(args, vm) } /// PyObject_Call @@ -45,8 +46,9 @@ impl<'a> PyCallable<'a> { } pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult { + let args = args.into_args(vm); vm.trace_event(TraceEvent::Call)?; - let result = (self.call)(self.obj, args.into_args(vm), vm); + let result = (self.call)(self.obj, args, vm); vm.trace_event(TraceEvent::Return)?; result } diff --git a/vm/src/protocol/iter.rs b/vm/src/protocol/iter.rs index 30b97a7b1..36952916c 100644 --- a/vm/src/protocol/iter.rs +++ b/vm/src/protocol/iter.rs @@ -1,6 +1,7 @@ use crate::{ builtins::iter::PySequenceIterator, convert::{ToPyObject, ToPyResult}, + object::{Traverse, TraverseFn}, AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, }; use std::borrow::Borrow; @@ -14,6 +15,12 @@ pub struct PyIter(O) where O: Borrow; +unsafe impl> Traverse for PyIter { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.0.borrow().traverse(tracer_fn); + } +} + impl PyIter { pub fn check(obj: &PyObject) -> bool { obj.class() @@ -149,6 +156,16 @@ pub enum PyIterReturn { StopIteration(Option), } +unsafe impl Traverse for PyIterReturn { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + match self { + PyIterReturn::Return(r) => r.traverse(tracer_fn), + PyIterReturn::StopIteration(Some(obj)) => obj.traverse(tracer_fn), + _ => (), + } + } +} + impl PyIterReturn { pub fn from_pyresult(result: PyResult, vm: &VirtualMachine) -> PyResult { match result { @@ -212,6 +229,15 @@ where _phantom: std::marker::PhantomData, } +unsafe impl<'a, T, O> Traverse for PyIterIter<'a, T, O> +where + O: Traverse + Borrow, +{ + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.obj.traverse(tracer_fn) + } +} + impl<'a, T, O> PyIterIter<'a, T, O> where O: Borrow, diff --git a/vm/src/protocol/mapping.rs b/vm/src/protocol/mapping.rs index c0a072009..11cd03d44 100644 --- a/vm/src/protocol/mapping.rs +++ b/vm/src/protocol/mapping.rs @@ -5,6 +5,7 @@ use crate::{ PyDict, PyStrInterned, }, convert::ToPyResult, + object::{Traverse, TraverseFn}, AsObject, PyObject, PyObjectRef, PyResult, VirtualMachine, }; use crossbeam_utils::atomic::AtomicCell; @@ -62,6 +63,12 @@ pub struct PyMapping<'a> { pub methods: &'static PyMappingMethods, } +unsafe impl Traverse for PyMapping<'_> { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.obj.traverse(tracer_fn) + } +} + impl AsRef for PyMapping<'_> { #[inline(always)] fn as_ref(&self) -> &PyObject { diff --git a/vm/src/protocol/number.rs b/vm/src/protocol/number.rs index f23b5844f..0dbb5ca8d 100644 --- a/vm/src/protocol/number.rs +++ b/vm/src/protocol/number.rs @@ -6,6 +6,7 @@ use crate::{ builtins::{int, PyByteArray, PyBytes, PyComplex, PyFloat, PyInt, PyIntRef, PyStr}, common::int::bytes_to_int, function::ArgBytesLike, + object::{Traverse, TraverseFn}, stdlib::warnings, AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject, VirtualMachine, @@ -426,6 +427,12 @@ impl PyNumberSlots { #[derive(Copy, Clone)] pub struct PyNumber<'a>(&'a PyObject); +unsafe impl Traverse for PyNumber<'_> { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.0.traverse(tracer_fn) + } +} + impl<'a> Deref for PyNumber<'a> { type Target = PyObject; diff --git a/vm/src/protocol/object.rs b/vm/src/protocol/object.rs index a0e7221fb..bebfd2144 100644 --- a/vm/src/protocol/object.rs +++ b/vm/src/protocol/object.rs @@ -133,7 +133,8 @@ impl PyObject { vm: &VirtualMachine, ) -> PyResult<()> { let attr_name = attr_name.as_pystr(&vm.ctx); - self.call_set_attr(vm, attr_name, PySetterValue::Assign(attr_value.into())) + let attr_value = attr_value.into(); + self.call_set_attr(vm, attr_name, PySetterValue::Assign(attr_value)) } // int PyObject_GenericSetAttr(PyObject *o, PyObject *name, PyObject *value) diff --git a/vm/src/protocol/sequence.rs b/vm/src/protocol/sequence.rs index 36c66ad8b..7c53cc293 100644 --- a/vm/src/protocol/sequence.rs +++ b/vm/src/protocol/sequence.rs @@ -2,6 +2,7 @@ use crate::{ builtins::{type_::PointerSlot, PyList, PyListRef, PySlice, PyTuple, PyTupleRef}, convert::ToPyObject, function::PyArithmeticValue, + object::{Traverse, TraverseFn}, protocol::{PyMapping, PyNumberBinaryOp}, AsObject, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, }; @@ -65,6 +66,12 @@ pub struct PySequence<'a> { pub methods: &'static PySequenceMethods, } +unsafe impl Traverse for PySequence<'_> { + fn traverse(&self, tracer_fn: &mut TraverseFn) { + self.obj.traverse(tracer_fn) + } +} + impl<'a> PySequence<'a> { #[inline] pub fn with_methods(obj: &'a PyObject, methods: &'static PySequenceMethods) -> Self { diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index f976d2fc0..2c4254d3e 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -6,11 +6,11 @@ mod gen; use crate::{ - builtins::{self, PyStrRef, PyType}, + builtins::{self, PyModule, PyStrRef, PyType}, class::{PyClassImpl, StaticType}, compiler::CompileError, convert::ToPyException, - AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, + AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, }; use num_complex::Complex64; @@ -331,7 +331,7 @@ pub(crate) use _ast::AstNode; // Used by builtins::compile() pub const PY_COMPILE_FLAG_AST_ONLY: i32 = 0x0400; -pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyRef { let module = _ast::make_module(vm); gen::extend_module_nodes(vm, &module); module diff --git a/vm/src/stdlib/ast/gen.rs b/vm/src/stdlib/ast/gen.rs index a7244f9be..a942a3499 100644 --- a/vm/src/stdlib/ast/gen.rs +++ b/vm/src/stdlib/ast/gen.rs @@ -4535,7 +4535,7 @@ impl Node for ast::TypeIgnore { } } -pub fn extend_module_nodes(vm: &VirtualMachine, module: &PyObject) { +pub fn extend_module_nodes(vm: &VirtualMachine, module: &Py) { extend_module!(vm, module, { "mod" => NodeKindMod::make_class(&vm.ctx), "Module" => NodeModule::make_class(&vm.ctx), diff --git a/vm/src/stdlib/builtins.rs b/vm/src/stdlib/builtins.rs index 8a4d11298..a7522f097 100644 --- a/vm/src/stdlib/builtins.rs +++ b/vm/src/stdlib/builtins.rs @@ -1,7 +1,8 @@ //! Builtin function definitions. //! //! Implements the list of [builtin Python functions](https://docs.python.org/3/library/builtins.html). -use crate::{class::PyClassImpl, PyObjectRef, VirtualMachine}; +use crate::{builtins::PyModule, class::PyClassImpl, Py, VirtualMachine}; +pub use builtins::{ascii, print}; #[pymodule] mod builtins { @@ -933,14 +934,12 @@ mod builtins { } } -pub use builtins::{ascii, print}; - -pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { +pub fn init_module(vm: &VirtualMachine, module: &Py) { let ctx = &vm.ctx; crate::protocol::VecBuffer::make_class(&vm.ctx); - builtins::extend_module(vm, &module); + builtins::extend_module(vm, module).unwrap(); let debug_mode: bool = vm.state.settings.optimize == 0; extend_module!(vm, module, { diff --git a/vm/src/stdlib/collections.rs b/vm/src/stdlib/collections.rs index 5c26f1590..5935b124f 100644 --- a/vm/src/stdlib/collections.rs +++ b/vm/src/stdlib/collections.rs @@ -17,7 +17,7 @@ mod _collections { sliceable::SequenceIndexOp, types::{ AsSequence, Comparable, Constructor, Initializer, IterNext, IterNextIterable, Iterable, - PyComparisonOp, + PyComparisonOp, Representable, }, utils::collection_repr, AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, @@ -57,7 +57,14 @@ mod _collections { #[pyclass( flags(BASETYPE), - with(Constructor, Initializer, AsSequence, Comparable, Iterable) + with( + Constructor, + Initializer, + AsSequence, + Comparable, + Iterable, + Representable + ) )] impl PyDeque { #[pymethod] @@ -295,27 +302,6 @@ mod _collections { .ok_or_else(|| vm.new_index_error("deque index out of range".to_owned())) } - #[pymethod(magic)] - fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - let deque = zelf.borrow_deque().clone(); - let class = zelf.class(); - let class_name = class.name(); - let closing_part = zelf - .maxlen - .map(|maxlen| format!("], maxlen={maxlen}")) - .unwrap_or_else(|| "]".to_owned()); - - let s = if zelf.len() == 0 { - format!("{class_name}([{closing_part})") - } else if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { - collection_repr(Some(&class_name), "[", &closing_part, deque.iter(), vm)? - } else { - "[...]".to_owned() - }; - - Ok(s) - } - #[pymethod(magic)] fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self._contains(&needle, vm) @@ -580,6 +566,29 @@ mod _collections { } } + impl Representable for PyDeque { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let deque = zelf.borrow_deque().clone(); + let class = zelf.class(); + let class_name = class.name(); + let closing_part = zelf + .maxlen + .map(|maxlen| format!("], maxlen={maxlen}")) + .unwrap_or_else(|| "]".to_owned()); + + let s = if zelf.len() == 0 { + format!("{class_name}([{closing_part})") + } else if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { + collection_repr(Some(&class_name), "[", &closing_part, deque.iter(), vm)? + } else { + "[...]".to_owned() + }; + + Ok(s) + } + } + #[pyattr] #[pyclass(name = "_deque_iterator")] #[derive(Debug, PyPayload)] diff --git a/vm/src/stdlib/errno.rs b/vm/src/stdlib/errno.rs index 8a0ffd778..fe36b4965 100644 --- a/vm/src/stdlib/errno.rs +++ b/vm/src/stdlib/errno.rs @@ -1,13 +1,12 @@ -use crate::PyObjectRef; -use crate::VirtualMachine; +use crate::{builtins::PyModule, PyRef, VirtualMachine}; #[pymodule] mod errno {} -pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyRef { let module = errno::make_module(vm); let errorcode = vm.ctx.new_dict(); - extend_module!(vm, module, { + extend_module!(vm, &module, { "errorcode" => errorcode.clone(), }); for (name, code) in ERROR_CODES { diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index b39fccc67..dc1bc0fe8 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -1,14 +1,9 @@ use crate::bytecode::frozen_lib::FrozenModule; -use crate::{builtins::PyBaseExceptionRef, PyObjectRef, VirtualMachine}; - -pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { - let module = _imp::make_module(vm); - lock::extend_module(vm, &module); - module -} +use crate::{builtins::PyBaseExceptionRef, VirtualMachine}; +pub(crate) use _imp::make_module; #[cfg(feature = "threading")] -#[pymodule] +#[pymodule(sub)] mod lock { use crate::{stdlib::thread::RawRMutex, PyResult, VirtualMachine}; @@ -36,7 +31,7 @@ mod lock { } #[cfg(not(feature = "threading"))] -#[pymodule] +#[pymodule(sub)] mod lock { use crate::vm::VirtualMachine; #[pyfunction] @@ -82,12 +77,12 @@ fn find_frozen(name: &str, vm: &VirtualMachine) -> Result PyResult { let sys_modules = vm.sys_module.get_attr("modules", vm).unwrap(); - let name = spec.get_attr("name", vm)?; - let name = PyStrRef::try_from_object(vm, name)?; + let name: PyStrRef = spec.get_attr("name", vm)?.try_into_value(vm)?; - if let Ok(module) = sys_modules.get_item(&*name, vm) { - Ok(module) + let module = if let Ok(module) = sys_modules.get_item(&*name, vm) { + module } else if let Some(make_module_func) = vm.state.module_inits.get(name.as_str()) { - Ok(make_module_func(vm)) + make_module_func(vm).into() } else { - Ok(vm.ctx.none()) - } + vm.ctx.none() + }; + Ok(module) } #[pyfunction] diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 1fb47f255..3c98c146f 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -11,8 +11,9 @@ cfg_if::cfg_if! { use crate::{ builtins::PyBaseExceptionRef, + builtins::PyModule, convert::{IntoPyException, ToPyException, ToPyObject}, - PyObjectRef, PyResult, TryFromObject, VirtualMachine, + PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine, }; pub use _io::io_open as open; @@ -44,18 +45,18 @@ impl IntoPyException for std::io::Error { } } -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { let ctx = &vm.ctx; let module = _io::make_module(vm); #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] - fileio::extend_module(vm, &module); + fileio::extend_module(vm, &module).unwrap(); let unsupported_operation = _io::UNSUPPORTED_OPERATION .get_or_init(|| _io::make_unsupportedop(ctx)) .clone(); - extend_module!(vm, module, { + extend_module!(vm, &module, { "UnsupportedOperation" => unsupported_operation, "BlockingIOError" => ctx.exceptions.blocking_io_error.to_owned(), }); @@ -379,7 +380,7 @@ mod _io { #[pyattr] #[pyclass(name = "_IOBase")] #[derive(Debug, PyPayload)] - struct _IOBase; + pub struct _IOBase; #[pyclass(with(IterNext, Destructor), flags(BASETYPE, HAS_DICT))] impl _IOBase { @@ -1525,9 +1526,9 @@ mod _io { vm.call_method(self.lock(vm)?.check_init(vm)?, "isatty", ()) } - #[pymethod(magic)] - fn repr(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let name_repr = repr_fileobj_name(&zelf, vm)?; + #[pyslot] + fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult { + let name_repr = repr_fileobj_name(zelf, vm)?; let cls = zelf.class(); let slot_name = cls.slot_name(); let repr = if let Some(name_repr) = name_repr { @@ -1535,7 +1536,12 @@ mod _io { } else { format!("<{slot_name}>") }; - Ok(repr) + Ok(vm.ctx.new_str(repr)) + } + + #[pymethod(magic)] + fn repr(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Self::slot_repr(&zelf, vm) } fn close_strict(&self, vm: &VirtualMachine) -> PyResult { @@ -3632,6 +3638,7 @@ mod _io { } pub(super) fn make_unsupportedop(ctx: &Context) -> PyTypeRef { + use crate::types::PyTypeSlots; PyType::new_heap( "UnsupportedOperation", vec![ @@ -3639,7 +3646,7 @@ mod _io { ctx.exceptions.value_error.to_owned(), ], Default::default(), - Default::default(), + PyTypeSlots::heap_default(), ctx.types.type_type.to_owned(), ctx, ) @@ -3709,8 +3716,8 @@ mod fileio { convert::ToPyException, function::{ArgBytesLike, ArgMemoryBuffer, OptionalArg, OptionalOption}, stdlib::os, - types::{DefaultConstructor, Initializer}, - AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, + types::{DefaultConstructor, Initializer, Representable}, + AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, }; use crossbeam_utils::atomic::AtomicCell; use std::io::{Read, Write}; @@ -3896,7 +3903,29 @@ mod fileio { } } - #[pyclass(with(DefaultConstructor, Initializer), flags(BASETYPE, HAS_DICT))] + impl Representable for FileIO { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let fd = zelf.fd.load(); + if fd < 0 { + return Ok("<_io.FileIO [closed]>".to_owned()); + } + let name_repr = repr_fileobj_name(zelf.as_object(), vm)?; + let mode = zelf.mode(); + let closefd = if zelf.closefd.load() { "True" } else { "False" }; + let repr = if let Some(name_repr) = name_repr { + format!("<_io.FileIO name={name_repr} mode='{mode}' closefd={closefd}>") + } else { + format!("<_io.FileIO fd={fd} mode='{mode}' closefd={closefd}>") + }; + Ok(repr) + } + } + + #[pyclass( + with(DefaultConstructor, Initializer, Representable), + flags(BASETYPE, HAS_DICT) + )] impl FileIO { #[pygetset] fn closed(&self) -> bool { @@ -3956,23 +3985,6 @@ mod fileio { } } - #[pymethod(magic)] - fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - let fd = zelf.fd.load(); - if fd < 0 { - return Ok("<_io.FileIO [closed]>".to_owned()); - } - let name_repr = repr_fileobj_name(zelf.as_object(), vm)?; - let mode = zelf.mode(); - let closefd = if zelf.closefd.load() { "True" } else { "False" }; - let repr = if let Some(name_repr) = name_repr { - format!("<_io.FileIO name={name_repr} mode='{mode}' closefd={closefd}>") - } else { - format!("<_io.FileIO fd={fd} mode='{mode}' closefd={closefd}>") - }; - Ok(repr) - } - #[pymethod] fn read(&self, read_byte: OptionalSize, vm: &VirtualMachine) -> PyResult> { if !self.mode.load().contains(Mode::READABLE) { diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index 09bcd2696..66b4eb50d 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -16,7 +16,7 @@ mod decl { identifier, protocol::{PyIter, PyIterReturn, PyNumber}, stdlib::sys, - types::{Constructor, IterNext, IterNextIterable}, + types::{Constructor, IterNext, IterNextIterable, Representable}, AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, PyWeakRef, TryFromObject, VirtualMachine, }; @@ -258,7 +258,7 @@ mod decl { } } - #[pyclass(with(IterNext, Constructor))] + #[pyclass(with(IterNext, Constructor, Representable))] impl PyItertoolsCount { // TODO: Implement this // if (lz->cnt == PY_SSIZE_T_MAX) @@ -267,16 +267,6 @@ mod decl { fn reduce(zelf: PyRef) -> (PyTypeRef, (PyObjectRef,)) { (zelf.class().to_owned(), (zelf.cur.read().clone(),)) } - - #[pymethod(magic)] - fn repr(&self, vm: &VirtualMachine) -> PyResult { - let cur = format!("{}", self.cur.read().clone().repr(vm)?); - let step = &self.step; - if vm.bool_eq(step, vm.ctx.new_int(1).as_object())? { - return Ok(format!("count({cur})")); - } - Ok(format!("count({}, {})", cur, step.repr(vm)?)) - } } impl IterNextIterable for PyItertoolsCount {} impl IterNext for PyItertoolsCount { @@ -289,6 +279,18 @@ mod decl { } } + impl Representable for PyItertoolsCount { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let cur = format!("{}", zelf.cur.read().clone().repr(vm)?); + let step = &zelf.step; + if vm.bool_eq(step, vm.ctx.new_int(1).as_object())? { + return Ok(format!("count({cur})")); + } + Ok(format!("count({}, {})", cur, step.repr(vm)?)) + } + } + #[pyattr] #[pyclass(name = "cycle")] #[derive(Debug, PyPayload)] @@ -376,7 +378,7 @@ mod decl { } } - #[pyclass(with(IterNext, Constructor), flags(BASETYPE))] + #[pyclass(with(IterNext, Constructor, Representable), flags(BASETYPE))] impl PyItertoolsRepeat { #[pymethod(magic)] fn length_hint(&self, vm: &VirtualMachine) -> PyResult { @@ -396,16 +398,6 @@ mod decl { None => vm.new_tuple((cls, (zelf.object.clone(),))), }) } - - #[pymethod(magic)] - fn repr(&self, vm: &VirtualMachine) -> PyResult { - let mut fmt = format!("{}", &self.object.repr(vm)?); - if let Some(ref times) = self.times { - fmt.push_str(", "); - fmt.push_str(×.read().to_string()); - } - Ok(format!("repeat({fmt})")) - } } impl IterNextIterable for PyItertoolsRepeat {} @@ -422,6 +414,18 @@ mod decl { } } + impl Representable for PyItertoolsRepeat { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let mut fmt = format!("{}", &zelf.object.repr(vm)?); + if let Some(ref times) = zelf.times { + fmt.push_str(", "); + fmt.push_str(×.read().to_string()); + } + Ok(format!("repeat({fmt})")) + } + } + #[pyattr] #[pyclass(name = "starmap")] #[derive(Debug, PyPayload)] diff --git a/vm/src/stdlib/marshal.rs b/vm/src/stdlib/marshal.rs index db071243e..87e244a42 100644 --- a/vm/src/stdlib/marshal.rs +++ b/vm/src/stdlib/marshal.rs @@ -149,7 +149,8 @@ mod decl { self.0.ctx.new_int(value).into() } fn make_tuple(&self, elements: impl Iterator) -> Self::Value { - self.0.ctx.new_tuple(elements.collect()).into() + let elements = elements.collect(); + self.0.ctx.new_tuple(elements).into() } fn make_code(&self, code: CodeObject) -> Self::Value { self.0.ctx.new_code(code).into() diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 16936f1ab..5b177b4b0 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -48,10 +48,10 @@ mod winapi; #[cfg(windows)] mod winreg; -use crate::{PyObjectRef, VirtualMachine}; +use crate::{builtins::PyModule, PyRef, VirtualMachine}; use std::{borrow::Cow, collections::HashMap}; -pub type StdlibInitFunc = Box PyObjectRef)>; +pub type StdlibInitFunc = Box PyRef)>; pub type StdlibMap = HashMap, StdlibInitFunc, ahash::RandomState>; pub fn get_module_inits() -> StdlibMap { diff --git a/vm/src/stdlib/nt.rs b/vm/src/stdlib/nt.rs index cf1419671..713648933 100644 --- a/vm/src/stdlib/nt.rs +++ b/vm/src/stdlib/nt.rs @@ -1,14 +1,14 @@ -use crate::{PyObjectRef, VirtualMachine}; +use crate::{builtins::PyModule, PyRef, VirtualMachine}; pub use module::raw_set_handle_inheritable; -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { let module = module::make_module(vm); super::os::extend_module(vm, &module); module } -#[pymodule(name = "nt")] +#[pymodule(name = "nt", with(super::os::_os))] pub(crate) mod module { use crate::{ builtins::{PyStrRef, PyTupleRef}, diff --git a/vm/src/stdlib/operator.rs b/vm/src/stdlib/operator.rs index fde4bf94b..6035e002a 100644 --- a/vm/src/stdlib/operator.rs +++ b/vm/src/stdlib/operator.rs @@ -10,7 +10,7 @@ mod _operator { identifier, protocol::PyIter, recursion::ReprGuard, - types::{Callable, Constructor, PyComparisonOp}, + types::{Callable, Constructor, PyComparisonOp, Representable}, AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, }; @@ -356,22 +356,8 @@ mod _operator { attrs: Vec, } - #[pyclass(with(Callable, Constructor))] + #[pyclass(with(Callable, Constructor, Representable))] impl PyAttrGetter { - #[pymethod(magic)] - fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - let fmt = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { - let mut parts = Vec::with_capacity(zelf.attrs.len()); - for part in &zelf.attrs { - parts.push(part.as_object().repr(vm)?.as_str().to_owned()); - } - parts.join(", ") - } else { - "...".to_owned() - }; - Ok(format!("operator.attrgetter({fmt})")) - } - #[pymethod(magic)] fn reduce(zelf: PyRef, vm: &VirtualMachine) -> PyResult<(PyTypeRef, PyTupleRef)> { let attrs = vm @@ -441,6 +427,22 @@ mod _operator { } } + impl Representable for PyAttrGetter { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let fmt = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { + let mut parts = Vec::with_capacity(zelf.attrs.len()); + for part in &zelf.attrs { + parts.push(part.as_object().repr(vm)?.as_str().to_owned()); + } + parts.join(", ") + } else { + "...".to_owned() + }; + Ok(format!("operator.attrgetter({fmt})")) + } + } + /// itemgetter(item, ...) --> itemgetter object /// /// Return a callable object that fetches the given item(s) from its operand. @@ -453,22 +455,8 @@ mod _operator { items: Vec, } - #[pyclass(with(Callable, Constructor))] + #[pyclass(with(Callable, Constructor, Representable))] impl PyItemGetter { - #[pymethod(magic)] - fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - let fmt = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { - let mut items = Vec::with_capacity(zelf.items.len()); - for item in &zelf.items { - items.push(item.repr(vm)?.as_str().to_owned()); - } - items.join(", ") - } else { - "...".to_owned() - }; - Ok(format!("operator.itemgetter({fmt})")) - } - #[pymethod(magic)] fn reduce(zelf: PyRef, vm: &VirtualMachine) -> PyObjectRef { let items = vm.ctx.new_tuple(zelf.items.to_vec()); @@ -508,6 +496,22 @@ mod _operator { } } + impl Representable for PyItemGetter { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let fmt = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { + let mut items = Vec::with_capacity(zelf.items.len()); + for item in &zelf.items { + items.push(item.repr(vm)?.as_str().to_owned()); + } + items.join(", ") + } else { + "...".to_owned() + }; + Ok(format!("operator.itemgetter({fmt})")) + } + } + /// methodcaller(name, ...) --> methodcaller object /// /// Return a callable object that calls the given method on its operand. @@ -522,37 +526,8 @@ mod _operator { args: FuncArgs, } - #[pyclass(with(Callable, Constructor))] + #[pyclass(with(Callable, Constructor, Representable))] impl PyMethodCaller { - #[pymethod(magic)] - fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { - let fmt = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { - let args = &zelf.args.args; - let kwargs = &zelf.args.kwargs; - let mut fmt = vec![zelf.name.as_object().repr(vm)?.as_str().to_owned()]; - if !args.is_empty() { - let mut parts = Vec::with_capacity(args.len()); - for v in args { - parts.push(v.repr(vm)?.as_str().to_owned()); - } - fmt.push(parts.join(", ")); - } - // build name=value pairs from KwArgs. - if !kwargs.is_empty() { - let mut parts = Vec::with_capacity(kwargs.len()); - for (key, value) in kwargs { - let value_repr = value.repr(vm)?; - parts.push(format!("{key}={value_repr}")); - } - fmt.push(parts.join(", ")); - } - fmt.join(", ") - } else { - "...".to_owned() - }; - Ok(format!("operator.methodcaller({fmt})")) - } - #[pymethod(magic)] fn reduce(zelf: PyRef, vm: &VirtualMachine) -> PyResult { // With no kwargs, return (type(obj), (name, *args)) tuple. @@ -596,4 +571,35 @@ mod _operator { vm.call_method(&obj, zelf.name.as_str(), zelf.args.clone()) } } + + impl Representable for PyMethodCaller { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let fmt = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { + let args = &zelf.args.args; + let kwargs = &zelf.args.kwargs; + let mut fmt = vec![zelf.name.as_object().repr(vm)?.as_str().to_owned()]; + if !args.is_empty() { + let mut parts = Vec::with_capacity(args.len()); + for v in args { + parts.push(v.repr(vm)?.as_str().to_owned()); + } + fmt.push(parts.join(", ")); + } + // build name=value pairs from KwArgs. + if !kwargs.is_empty() { + let mut parts = Vec::with_capacity(kwargs.len()); + for (key, value) in kwargs { + let value_repr = value.repr(vm)?; + parts.push(format!("{key}={value_repr}")); + } + fmt.push(parts.join(", ")); + } + fmt.join(", ") + } else { + "...".to_owned() + }; + Ok(format!("operator.methodcaller({fmt})")) + } + } } diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 6c822822f..e016c21a1 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,9 +1,9 @@ use crate::{ - builtins::{PyBaseExceptionRef, PySet}, + builtins::{PyBaseExceptionRef, PyModule, PySet}, common::crt_fd::Fd, convert::IntoPyException, function::{ArgumentError, FromArgs, FsPath, FuncArgs}, - AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, + AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, }; use std::{ ffi, fs, io, @@ -54,8 +54,9 @@ pub struct OsPath { impl OsPath { pub fn new_str(path: impl Into) -> Self { + let path = path.into(); Self { - path: path.into(), + path, mode: OutputMode::String, } } @@ -178,11 +179,13 @@ impl IOErrorBuilder { } } pub(crate) fn filename(mut self, filename: impl Into) -> Self { - self.filename.replace(filename.into()); + let filename = filename.into(); + self.filename.replace(filename); self } pub(crate) fn filename2(mut self, filename: impl Into) -> Self { - self.filename2.replace(filename.into()); + let filename = filename.into(); + self.filename2.replace(filename); self } } @@ -296,28 +299,26 @@ fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a ffi::OsS .map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8".to_owned())) } -#[pymodule(name = "_os")] +#[pymodule(sub)] pub(super) mod _os { use super::{ errno_err, DirFd, FollowSymlinks, IOErrorBuilder, OsPath, OsPathOrFd, OutputMode, SupportFunc, }; - use crate::common::lock::{OnceCell, PyRwLock}; use crate::{ builtins::{ PyBytesRef, PyGenericAlias, PyIntRef, PyStrRef, PyTuple, PyTupleRef, PyTypeRef, }, - common::{ - crt_fd::{Fd, Offset}, - suppress_iph, - }, + common::crt_fd::{Fd, Offset}, + common::lock::{OnceCell, PyRwLock}, + common::suppress_iph, convert::{IntoPyException, ToPyObject}, function::{ArgBytesLike, Either, FsPath, FuncArgs, OptionalArg}, protocol::PyIterReturn, recursion::ReprGuard, - types::{IterNext, IterNextIterable, PyStructSequence}, + types::{IterNext, IterNextIterable, PyStructSequence, Representable}, vm::VirtualMachine, - AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, + AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, }; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; @@ -619,7 +620,7 @@ pub(super) mod _os { ino: AtomicCell>, } - #[pyclass] + #[pyclass(with(Representable))] impl DirEntry { #[pygetset] fn name(&self, vm: &VirtualMachine) -> PyResult { @@ -747,9 +748,16 @@ pub(super) mod _os { self.path(vm) } - #[pymethod(magic)] - fn repr(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let name = match zelf.get_attr("name", vm) { + #[pyclassmethod(magic)] + fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { + PyGenericAlias::new(cls, args, vm) + } + } + + impl Representable for DirEntry { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let name = match zelf.as_object().get_attr("name", vm) { Ok(name) => Some(name), Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) @@ -760,7 +768,7 @@ pub(super) mod _os { Err(e) => return Err(e), }; if let Some(name) = name { - if let Some(_guard) = ReprGuard::enter(vm, &zelf) { + if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { let repr = name.repr(vm)?; Ok(format!("<{} {}>", zelf.class(), repr)) } else { @@ -773,11 +781,6 @@ pub(super) mod _os { Ok(format!("<{}>", zelf.class())) } } - - #[pyclassmethod(magic)] - fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias { - PyGenericAlias::new(cls, args, vm) - } } #[pyattr] @@ -1167,7 +1170,7 @@ pub(super) mod _os { } #[pyfunction] - fn exit(code: i32) { + fn _exit(code: i32) { std::process::exit(code) } @@ -1706,9 +1709,7 @@ impl SupportFunc { } } -pub fn extend_module(vm: &VirtualMachine, module: &PyObject) { - _os::extend_module(vm, module); - +pub fn extend_module(vm: &VirtualMachine, module: &Py) { let support_funcs = _os::support_funcs(); let supports_fd = PySet::default().into_ref(&vm.ctx); let supports_dir_fd = PySet::default().into_ref(&vm.ctx); diff --git a/vm/src/stdlib/posix.rs b/vm/src/stdlib/posix.rs index a4270136e..5846aa000 100644 --- a/vm/src/stdlib/posix.rs +++ b/vm/src/stdlib/posix.rs @@ -1,4 +1,4 @@ -use crate::{PyObjectRef, VirtualMachine}; +use crate::{builtins::PyModule, PyRef, VirtualMachine}; use std::os::unix::io::RawFd; pub fn raw_set_inheritable(fd: RawFd, inheritable: bool) -> nix::Result<()> { @@ -12,25 +12,25 @@ pub fn raw_set_inheritable(fd: RawFd, inheritable: bool) -> nix::Result<()> { Ok(()) } -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { let module = module::make_module(vm); super::os::extend_module(vm, &module); module } -#[pymodule(name = "posix")] +#[pymodule(name = "posix", with(super::os::_os))] pub mod module { use crate::{ builtins::{PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyTypeRef}, convert::{IntoPyException, ToPyObject, TryFromObject}, - function::{Either, OptionalArg}, + function::{Either, KwArgs, OptionalArg}, stdlib::os::{ errno_err, DirFd, FollowSymlinks, OsPath, OsPathOrFd, SupportFunc, TargetIsDirectory, _os, fs_metadata, IOErrorBuilder, }, - types::Constructor, + types::{Constructor, Representable}, utils::ToCString, - AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, + AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, }; use bitflags::bitflags; use nix::{ @@ -421,6 +421,119 @@ pub mod module { ) } + #[derive(FromArgs)] + struct RegisterAtForkArgs { + #[pyarg(named, optional)] + before: OptionalArg, + #[pyarg(named, optional)] + after_in_parent: OptionalArg, + #[pyarg(named, optional)] + after_in_child: OptionalArg, + } + + impl RegisterAtForkArgs { + fn into_validated( + self, + vm: &VirtualMachine, + ) -> PyResult<( + Option, + Option, + Option, + )> { + fn into_option( + arg: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + match arg { + OptionalArg::Present(obj) => { + if !obj.is_callable() { + return Err(vm.new_type_error("Args must be callable".to_owned())); + } + Ok(Some(obj)) + } + OptionalArg::Missing => Ok(None), + } + } + let before = into_option(self.before, vm)?; + let after_in_parent = into_option(self.after_in_parent, vm)?; + let after_in_child = into_option(self.after_in_child, vm)?; + if before.is_none() && after_in_parent.is_none() && after_in_child.is_none() { + return Err(vm.new_type_error("At least one arg must be present".to_owned())); + } + Ok((before, after_in_parent, after_in_child)) + } + } + + #[pyfunction] + fn register_at_fork( + args: RegisterAtForkArgs, + _ignored: KwArgs, + vm: &VirtualMachine, + ) -> PyResult<()> { + let (before, after_in_parent, after_in_child) = args.into_validated(vm)?; + + if let Some(before) = before { + vm.state.before_forkers.lock().push(before); + } + if let Some(after_in_parent) = after_in_parent { + vm.state.after_forkers_parent.lock().push(after_in_parent); + } + if let Some(after_in_child) = after_in_child { + vm.state.after_forkers_child.lock().push(after_in_child); + } + Ok(()) + } + + fn run_at_forkers(mut funcs: Vec, reversed: bool, vm: &VirtualMachine) { + if !funcs.is_empty() { + if reversed { + funcs.reverse(); + } + for func in funcs.into_iter() { + if let Err(e) = func.call((), vm) { + let exit = e.fast_isinstance(vm.ctx.exceptions.system_exit); + vm.run_unraisable(e, Some("Exception ignored in".to_owned()), func); + if exit { + // Do nothing! + } + } + } + } + } + + fn py_os_before_fork(vm: &VirtualMachine) { + let before_forkers: Vec = vm.state.before_forkers.lock().clone(); + // functions must be executed in reversed order as they are registered + // only for before_forkers, refer: test_register_at_fork in test_posix + + run_at_forkers(before_forkers, true, vm); + } + + fn py_os_after_fork_child(vm: &VirtualMachine) { + let after_forkers_child: Vec = vm.state.after_forkers_child.lock().clone(); + run_at_forkers(after_forkers_child, false, vm); + } + + fn py_os_after_fork_parent(vm: &VirtualMachine) { + let after_forkers_parent: Vec = vm.state.after_forkers_parent.lock().clone(); + run_at_forkers(after_forkers_parent, false, vm); + } + + #[pyfunction] + fn fork(vm: &VirtualMachine) -> i32 { + let pid: i32; + py_os_before_fork(vm); + unsafe { + pid = libc::fork(); + } + if pid == 0 { + py_os_after_fork_child(vm); + } else { + py_os_after_fork_parent(vm); + } + pid + } + #[cfg(not(target_os = "redox"))] const MKNOD_DIR_FD: bool = cfg!(not(target_vendor = "apple")); @@ -540,22 +653,13 @@ pub mod module { } } - #[pyclass(with(Constructor))] + #[pyclass(with(Constructor, Representable))] impl SchedParam { #[pygetset] fn sched_priority(&self, vm: &VirtualMachine) -> PyObjectRef { self.sched_priority.clone().to_pyobject(vm) } - #[pymethod(magic)] - fn repr(&self, vm: &VirtualMachine) -> PyResult { - let sched_priority_repr = self.sched_priority.repr(vm)?; - Ok(format!( - "posix.sched_param(sched_priority = {})", - sched_priority_repr.as_str() - )) - } - #[cfg(any( target_os = "linux", target_os = "netbsd", @@ -591,6 +695,17 @@ pub mod module { } } + impl Representable for SchedParam { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let sched_priority_repr = zelf.sched_priority.repr(vm)?; + Ok(format!( + "posix.sched_param(sched_priority = {})", + sched_priority_repr.as_str() + )) + } + } + #[cfg(any( target_os = "linux", target_os = "netbsd", diff --git a/vm/src/stdlib/posix_compat.rs b/vm/src/stdlib/posix_compat.rs index 13af3b721..3f939dce1 100644 --- a/vm/src/stdlib/posix_compat.rs +++ b/vm/src/stdlib/posix_compat.rs @@ -1,14 +1,13 @@ //! `posix` compatible module for `not(any(unix, windows))` +use crate::{builtins::PyModule, PyRef, VirtualMachine}; -use crate::{PyObjectRef, VirtualMachine}; - -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { let module = module::make_module(vm); super::os::extend_module(vm, &module); module } -#[pymodule(name = "posix")] +#[pymodule(name = "posix", with(super::os::_os))] pub(crate) mod module { use crate::{ builtins::PyStrRef, diff --git a/vm/src/stdlib/signal.rs b/vm/src/stdlib/signal.rs index c64d9ae84..b30428546 100644 --- a/vm/src/stdlib/signal.rs +++ b/vm/src/stdlib/signal.rs @@ -1,6 +1,6 @@ -use crate::{PyObjectRef, VirtualMachine}; +use crate::{builtins::PyModule, PyRef, VirtualMachine}; -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { let module = _signal::make_module(vm); _signal::init_signal_handlers(&module, vm); @@ -11,8 +11,9 @@ pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { #[pymodule] pub(crate) mod _signal { use crate::{ + builtins::PyModule, convert::{IntoPyException, TryFromBorrowedObject}, - signal, PyObjectRef, PyResult, VirtualMachine, + signal, Py, PyObjectRef, PyResult, VirtualMachine, }; use std::sync::atomic::{self, Ordering}; @@ -80,7 +81,7 @@ pub(crate) mod _signal { #[pyattr] use libc::{SIGPWR, SIGSTKFLT}; - pub(super) fn init_signal_handlers(module: &PyObjectRef, vm: &VirtualMachine) { + pub(super) fn init_signal_handlers(module: &Py, vm: &VirtualMachine) { let sig_dfl = vm.new_pyobj(SIG_DFL as u8); let sig_ign = vm.new_pyobj(SIG_IGN as u8); diff --git a/vm/src/stdlib/sre.rs b/vm/src/stdlib/sre.rs index 3ae0b5c34..93ecd7c24 100644 --- a/vm/src/stdlib/sre.rs +++ b/vm/src/stdlib/sre.rs @@ -13,9 +13,9 @@ mod _sre { function::{ArgCallable, OptionalArg, PosArgs, PyComparisonValue}, protocol::{PyBuffer, PyMappingMethods}, stdlib::sys, - types::{AsMapping, Comparable, Hashable}, - PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject, TryFromObject, - VirtualMachine, + types::{AsMapping, Comparable, Hashable, Representable}, + Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromBorrowedObject, + TryFromObject, VirtualMachine, }; use core::str; use crossbeam_utils::atomic::AtomicCell; @@ -151,7 +151,7 @@ mod _sre { }; } - #[pyclass(with(Hashable, Comparable))] + #[pyclass(with(Hashable, Comparable, Representable))] impl Pattern { fn with_str(string: &PyObject, vm: &VirtualMachine, f: F) -> PyResult where @@ -336,51 +336,6 @@ mod _sre { }) } - #[pymethod(magic)] - fn repr(&self, vm: &VirtualMachine) -> PyResult { - let flag_names = [ - ("re.TEMPLATE", SreFlag::TEMPLATE), - ("re.IGNORECASE", SreFlag::IGNORECASE), - ("re.LOCALE", SreFlag::LOCALE), - ("re.MULTILINE", SreFlag::MULTILINE), - ("re.DOTALL", SreFlag::DOTALL), - ("re.UNICODE", SreFlag::UNICODE), - ("re.VERBOSE", SreFlag::VERBOSE), - ("re.DEBUG", SreFlag::DEBUG), - ("re.ASCII", SreFlag::ASCII), - ]; - - /* Omit re.UNICODE for valid string patterns. */ - let mut flags = self.flags; - if !self.isbytes - && (flags & (SreFlag::LOCALE | SreFlag::UNICODE | SreFlag::ASCII)) - == SreFlag::UNICODE - { - flags &= !SreFlag::UNICODE; - } - - let flags = flag_names - .iter() - .filter(|(_, flag)| flags.contains(*flag)) - .map(|(name, _)| name) - .join("|"); - - let pattern = self.pattern.repr(vm)?; - let truncated: String; - let s = if pattern.char_len() > 200 { - truncated = pattern.as_str().chars().take(200).collect(); - &truncated - } else { - pattern.as_str() - }; - - if flags.is_empty() { - Ok(format!("re.compile({s})")) - } else { - Ok(format!("re.compile({s}, {flags})")) - } - } - #[pygetset] fn flags(&self) -> u16 { self.flags.bits() @@ -512,6 +467,53 @@ mod _sre { } } + impl Representable for Pattern { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let flag_names = [ + ("re.TEMPLATE", SreFlag::TEMPLATE), + ("re.IGNORECASE", SreFlag::IGNORECASE), + ("re.LOCALE", SreFlag::LOCALE), + ("re.MULTILINE", SreFlag::MULTILINE), + ("re.DOTALL", SreFlag::DOTALL), + ("re.UNICODE", SreFlag::UNICODE), + ("re.VERBOSE", SreFlag::VERBOSE), + ("re.DEBUG", SreFlag::DEBUG), + ("re.ASCII", SreFlag::ASCII), + ]; + + /* Omit re.UNICODE for valid string patterns. */ + let mut flags = zelf.flags; + if !zelf.isbytes + && (flags & (SreFlag::LOCALE | SreFlag::UNICODE | SreFlag::ASCII)) + == SreFlag::UNICODE + { + flags &= !SreFlag::UNICODE; + } + + let flags = flag_names + .iter() + .filter(|(_, flag)| flags.contains(*flag)) + .map(|(name, _)| name) + .join("|"); + + let pattern = zelf.pattern.repr(vm)?; + let truncated: String; + let s = if pattern.char_len() > 200 { + truncated = pattern.as_str().chars().take(200).collect(); + &truncated + } else { + pattern.as_str() + }; + + if flags.is_empty() { + Ok(format!("re.compile({s})")) + } else { + Ok(format!("re.compile({s}, {flags})")) + } + } + } + #[pyattr] #[pyclass(name = "Match")] #[derive(Debug, PyPayload)] @@ -524,7 +526,7 @@ mod _sre { regs: Vec<(isize, isize)>, } - #[pyclass(with(AsMapping))] + #[pyclass(with(AsMapping, Representable))] impl Match { pub(crate) fn new( state: &State, @@ -703,18 +705,6 @@ mod _sre { }) } - #[pymethod(magic)] - fn repr(&self, vm: &VirtualMachine) -> PyResult { - with_sre_str!(self.pattern, &self.string, vm, |str_drive| { - Ok(format!( - "", - self.regs[0].0, - self.regs[0].1, - self.get_slice(0, str_drive, vm).unwrap().repr(vm)? - )) - }) - } - fn get_index(&self, group: PyObjectRef, vm: &VirtualMachine) -> Option { let i = if let Ok(i) = group.try_index(vm) { i @@ -768,6 +758,20 @@ mod _sre { } } + impl Representable for Match { + #[inline] + fn repr_str(zelf: &Py, vm: &VirtualMachine) -> PyResult { + with_sre_str!(zelf.pattern, &zelf.string, vm, |str_drive| { + Ok(format!( + "", + zelf.regs[0].0, + zelf.regs[0].1, + zelf.get_slice(0, str_drive, vm).unwrap().repr(vm)? + )) + }) + } + } + #[pyattr] #[pyclass(name = "SRE_Scanner")] #[derive(Debug, PyPayload)] diff --git a/vm/src/stdlib/sys.rs b/vm/src/stdlib/sys.rs index 9e4e3c720..780d7b4ef 100644 --- a/vm/src/stdlib/sys.rs +++ b/vm/src/stdlib/sys.rs @@ -1,4 +1,4 @@ -use crate::{convert::ToPyObject, PyObject, PyResult, VirtualMachine}; +use crate::{builtins::PyModule, convert::ToPyObject, Py, PyResult, VirtualMachine}; pub(crate) use sys::{UnraisableHookArgs, MAXSIZE, MULTIARCH}; @@ -916,13 +916,15 @@ mod sys { impl UnraisableHookArgs {} } -pub(crate) fn init_module(vm: &VirtualMachine, module: &PyObject, builtins: &PyObject) { - sys::extend_module(vm, module); +pub(crate) fn init_module(vm: &VirtualMachine, module: &Py, builtins: &Py) { + sys::extend_module(vm, module).unwrap(); let modules = vm.ctx.new_dict(); - modules.set_item("sys", module.to_owned(), vm).unwrap(); modules - .set_item("builtins", builtins.to_owned(), vm) + .set_item("sys", module.to_owned().into(), vm) + .unwrap(); + modules + .set_item("builtins", builtins.to_owned().into(), vm) .unwrap(); extend_module!(vm, module, { "__doc__" => sys::DOC.to_owned().to_pyobject(vm), diff --git a/vm/src/stdlib/thread.rs b/vm/src/stdlib/thread.rs index b9699e140..4d3d04c8c 100644 --- a/vm/src/stdlib/thread.rs +++ b/vm/src/stdlib/thread.rs @@ -8,7 +8,7 @@ pub(crate) mod _thread { builtins::{PyDictRef, PyStr, PyTupleRef, PyTypeRef}, convert::ToPyException, function::{ArgCallable, Either, FuncArgs, KwArgs, OptionalArg, PySetterValue}, - types::{Constructor, GetAttr, SetAttr}, + types::{Constructor, GetAttr, Representable, SetAttr}, AsObject, Py, PyPayload, PyRef, PyResult, VirtualMachine, }; use parking_lot::{ @@ -97,12 +97,12 @@ pub(crate) mod _thread { } else { "unlocked" }; - format!( + Ok(format!( "<{} {} object at {:#x}>", status, $zelf.class().name(), $zelf.get_id() - ) + )) }}; } @@ -119,7 +119,7 @@ pub(crate) mod _thread { } } - #[pyclass(with(Constructor))] + #[pyclass(with(Constructor, Representable))] impl Lock { #[pymethod] #[pymethod(name = "acquire_lock")] @@ -146,11 +146,6 @@ pub(crate) mod _thread { fn locked(&self) -> bool { self.mu.is_locked() } - - #[pymethod(magic)] - fn repr(zelf: PyRef) -> String { - repr_lock_impl!(zelf) - } } impl Constructor for Lock { @@ -160,6 +155,13 @@ pub(crate) mod _thread { } } + impl Representable for Lock { + #[inline] + fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + repr_lock_impl!(zelf) + } + } + pub type RawRMutex = RawReentrantMutex; #[pyattr] #[pyclass(module = "thread", name = "RLock")] @@ -174,7 +176,7 @@ pub(crate) mod _thread { } } - #[pyclass] + #[pyclass(with(Representable))] impl RLock { #[pyslot] fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult { @@ -210,9 +212,11 @@ pub(crate) mod _thread { fn exit(&self, _args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> { self.release(vm) } + } - #[pymethod(magic)] - fn repr(zelf: PyRef) -> String { + impl Representable for RLock { + #[inline] + fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { repr_lock_impl!(zelf) } } diff --git a/vm/src/stdlib/time.rs b/vm/src/stdlib/time.rs index 151b97acf..0f9f9e0bf 100644 --- a/vm/src/stdlib/time.rs +++ b/vm/src/stdlib/time.rs @@ -2,21 +2,9 @@ // See also: // https://docs.python.org/3/library/time.html -use crate::{PyObjectRef, VirtualMachine}; - pub use time::*; -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { - let module = time::make_module(vm); - #[cfg(unix)] - unix::extend_module(vm, &module); - #[cfg(windows)] - windows::extend_module(vm, &module); - - module -} - -#[pymodule(name = "time")] +#[pymodule(name = "time", with(platform))] mod time { use crate::{ builtins::{PyStrRef, PyTypeRef}, @@ -386,15 +374,12 @@ mod time { } #[allow(unused_imports)] - #[cfg(unix)] - use super::unix::*; - #[cfg(windows)] - use super::windows::*; + use super::platform::*; } #[cfg(unix)] -#[pymodule(name = "time")] -mod unix { +#[pymodule(sub)] +mod platform { #[allow(unused_imports)] use super::{SEC_TO_NS, US_TO_NS}; #[cfg_attr(target_os = "macos", allow(unused_imports))] @@ -631,8 +616,8 @@ mod unix { } #[cfg(windows)] -#[pymodule(name = "time")] -mod windows { +#[pymodule] +mod platform { use super::{time_muldiv, MS_TO_NS, SEC_TO_NS}; use crate::{ builtins::{PyNamespace, PyStrRef}, @@ -815,3 +800,8 @@ mod windows { Ok(Duration::from_nanos((k_time + u_time) * 100)) } } + +// mostly for wasm32 +#[cfg(not(any(unix, windows)))] +#[pymodule(sub)] +mod platform {} diff --git a/vm/src/stdlib/winreg.rs b/vm/src/stdlib/winreg.rs index efd4adcb6..f66dd30c7 100644 --- a/vm/src/stdlib/winreg.rs +++ b/vm/src/stdlib/winreg.rs @@ -1,13 +1,13 @@ #![allow(non_snake_case)] -use crate::{PyObjectRef, VirtualMachine}; +use crate::{builtins::PyModule, PyRef, VirtualMachine}; -pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { let module = winreg::make_module(vm); macro_rules! add_constants { ($($name:ident),*$(,)?) => { - extend_module!(vm, module, { + extend_module!(vm, &module, { $((stringify!($name)) => vm.new_pyobj(::winreg::enums::$name as usize)),* }) }; diff --git a/vm/src/types/slot.rs b/vm/src/types/slot.rs index b06db403d..0b8ddb6fc 100644 --- a/vm/src/types/slot.rs +++ b/vm/src/types/slot.rs @@ -98,6 +98,13 @@ impl PyTypeSlots { ..Default::default() } } + + pub fn heap_default() -> Self { + Self { + // init: AtomicCell::new(Some(init_wrapper)), + ..Default::default() + } + } } impl std::fmt::Debug for PyTypeSlots { @@ -815,10 +822,12 @@ pub trait Callable: PyPayload { #[inline] #[pyslot] fn slot_call(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - let zelf = zelf - .downcast_ref() - .ok_or_else(|| vm.new_type_error("unexpected payload for __call__".to_owned()))?; - Self::call(zelf, args.bind(vm)?, vm) + let Some(zelf) = zelf.downcast_ref() else { + let err = vm.new_downcast_type_error(Self::class(&vm.ctx), zelf); + return Err(err); + }; + let args = args.bind(vm)?; + Self::call(zelf, args, vm) } #[inline] diff --git a/vm/src/types/zoo.rs b/vm/src/types/zoo.rs index 442084dea..a2d2a5c79 100644 --- a/vm/src/types/zoo.rs +++ b/vm/src/types/zoo.rs @@ -177,7 +177,7 @@ impl TypeZoo { not_implemented_type: singletons::PyNotImplemented::init_builtin_type(), generic_alias_type: genericalias::PyGenericAlias::init_builtin_type(), union_type: union_::PyUnion::init_builtin_type(), - member_descriptor_type: descriptor::MemberDescrObject::init_builtin_type(), + member_descriptor_type: descriptor::PyMemberDescriptor::init_builtin_type(), } } diff --git a/vm/src/vm/context.rs b/vm/src/vm/context.rs index 596bd3a93..c50f2a83e 100644 --- a/vm/src/vm/context.rs +++ b/vm/src/vm/context.rs @@ -4,8 +4,8 @@ use crate::{ bytes, code::{self, PyCode}, descriptor::{ - DescrObject, MemberDef, MemberDescrObject, MemberGetter, MemberKind, MemberSetter, - MemberSetterFunc, + DescrObject, MemberGetter, MemberKind, MemberSetter, MemberSetterFunc, PyMemberDef, + PyMemberDescriptor, }, getset::PyGetSet, object, pystr, @@ -234,8 +234,8 @@ declare_const_name! { // Basic objects: impl Context { - pub const INT_CACHE_POOL_MIN: i32 = -5; - pub const INT_CACHE_POOL_MAX: i32 = 256; + pub const INT_CACHE_POOL_RANGE: std::ops::RangeInclusive = (-5)..=256; + const INT_CACHE_POOL_MIN: i32 = *Self::INT_CACHE_POOL_RANGE.start(); pub fn genesis() -> &'static PyRc { rustpython_common::static_cell! { @@ -261,7 +261,7 @@ impl Context { let ellipsis = create_object(PyEllipsis, PyEllipsis::static_type()); let not_implemented = create_object(PyNotImplemented, PyNotImplemented::static_type()); - let int_cache_pool = (Self::INT_CACHE_POOL_MIN..=Self::INT_CACHE_POOL_MAX) + let int_cache_pool = Self::INT_CACHE_POOL_RANGE .map(|v| { PyRef::new_ref( PyInt::from(BigInt::from(v)), @@ -358,37 +358,33 @@ impl Context { #[inline] pub fn new_int + ToPrimitive>(&self, i: T) -> PyIntRef { if let Some(i) = i.to_i32() { - if (Self::INT_CACHE_POOL_MIN..=Self::INT_CACHE_POOL_MAX).contains(&i) { + if Self::INT_CACHE_POOL_RANGE.contains(&i) { let inner_idx = (i - Self::INT_CACHE_POOL_MIN) as usize; return self.int_cache_pool[inner_idx].clone(); } } - PyRef::new_ref(PyInt::from(i), self.types.int_type.to_owned(), None) + PyInt::from(i).into_ref(self) } #[inline] pub fn new_bigint(&self, i: &BigInt) -> PyIntRef { if let Some(i) = i.to_i32() { - if (Self::INT_CACHE_POOL_MIN..=Self::INT_CACHE_POOL_MAX).contains(&i) { + if Self::INT_CACHE_POOL_RANGE.contains(&i) { let inner_idx = (i - Self::INT_CACHE_POOL_MIN) as usize; return self.int_cache_pool[inner_idx].clone(); } } - PyRef::new_ref(PyInt::from(i.clone()), self.types.int_type.to_owned(), None) + PyInt::from(i.clone()).into_ref(self) } #[inline] pub fn new_float(&self, value: f64) -> PyRef { - PyRef::new_ref(PyFloat::from(value), self.types.float_type.to_owned(), None) + PyFloat::from(value).into_ref(self) } #[inline] pub fn new_complex(&self, value: Complex64) -> PyRef { - PyRef::new_ref( - PyComplex::from(value), - self.types.complex_type.to_owned(), - None, - ) + PyComplex::from(value).into_ref(self) } #[inline] @@ -504,15 +500,15 @@ impl Context { getter: fn(&VirtualMachine, PyObjectRef) -> PyResult, setter: MemberSetterFunc, class: &'static Py, - ) -> PyRef { - let member_def = MemberDef { + ) -> PyRef { + let member_def = PyMemberDef { name: name.to_owned(), kind: member_kind, getter: MemberGetter::Getter(getter), setter: MemberSetter::Setter(setter), doc: None, }; - let member_descriptor = MemberDescrObject { + let member_descriptor = PyMemberDescriptor { common: DescrObject { typ: class.to_owned(), name: self.intern_str(name), @@ -558,11 +554,9 @@ impl Context { where F: IntoPyGetterFunc, { - PyRef::new_ref( - PyGetSet::new(name.into(), class).with_get(f), - self.types.getset_type.to_owned(), - None, - ) + let name = name.into(); + let getset = PyGetSet::new(name, class).with_get(f); + PyRef::new_ref(getset, self.types.getset_type.to_owned(), None) } pub fn new_getset( @@ -576,11 +570,9 @@ impl Context { G: IntoPyGetterFunc, S: IntoPySetterFunc, { - PyRef::new_ref( - PyGetSet::new(name.into(), class).with_get(g).with_set(s), - self.types.getset_type.to_owned(), - None, - ) + let name = name.into(); + let getset = PyGetSet::new(name, class).with_get(g).with_set(s); + PyRef::new_ref(getset, self.types.getset_type.to_owned(), None) } pub fn new_base_object(&self, class: PyTypeRef, dict: Option) -> PyObjectRef { diff --git a/vm/src/vm/method.rs b/vm/src/vm/method.rs index 9f55e28f8..3d09a9579 100644 --- a/vm/src/vm/method.rs +++ b/vm/src/vm/method.rs @@ -85,13 +85,18 @@ impl PyMethod { } } - pub(crate) fn get_special( + pub(crate) fn get_special( obj: &PyObject, name: &'static PyStrInterned, vm: &VirtualMachine, ) -> PyResult> { let obj_cls = obj.class(); - let func = match obj_cls.get_attr(name) { + let attr = if DIRECT { + obj_cls.get_direct_attr(name) + } else { + obj_cls.get_attr(name) + }; + let func = match attr { Some(f) => f, None => { return Ok(None); diff --git a/vm/src/vm/mod.rs b/vm/src/vm/mod.rs index 11991fa6c..2e546d3d6 100644 --- a/vm/src/vm/mod.rs +++ b/vm/src/vm/mod.rs @@ -98,6 +98,9 @@ pub struct PyGlobalState { pub finalizing: AtomicBool, pub warnings: WarningsState, pub override_frozen_modules: AtomicCell, + pub before_forkers: PyMutex>, + pub after_forkers_child: PyMutex>, + pub after_forkers_parent: PyMutex>, } pub fn process_hash_secret_seed() -> u32 { @@ -115,7 +118,7 @@ impl VirtualMachine { // set __spec__, __loader__, etc. attributes let new_module = || { PyRef::new_ref( - PyModule {}, + PyModule::new(), ctx.types.module_type.to_owned(), Some(ctx.new_dict()), ) @@ -175,6 +178,9 @@ impl VirtualMachine { finalizing: AtomicBool::new(false), warnings, override_frozen_modules: AtomicCell::new(0), + before_forkers: PyMutex::default(), + after_forkers_child: PyMutex::default(), + after_forkers_parent: PyMutex::default(), }), initialized: false, recursion_depth: Cell::new(0), @@ -194,9 +200,9 @@ impl VirtualMachine { PyRc::get_mut(&mut vm.state).unwrap().frozen = frozen; vm.builtins - .init_module_dict(vm.ctx.intern_str("builtins"), vm.ctx.none(), &vm); + .init_module_dict(vm.ctx.intern_str("builtins"), None, &vm); vm.sys_module - .init_module_dict(vm.ctx.intern_str("sys"), vm.ctx.none(), &vm); + .init_module_dict(vm.ctx.intern_str("sys"), None, &vm); vm } @@ -244,7 +250,7 @@ impl VirtualMachine { // add the current directory to sys.path self.state_mut().settings.path_list.insert(0, "".to_owned()); - stdlib::builtins::make_module(self, self.builtins.clone().into()); + stdlib::builtins::init_module(self, &self.builtins); stdlib::sys::init_module(self, self.sys_module.as_ref(), self.builtins.as_ref()); let mut essential_init = || -> PyResult { @@ -469,11 +475,12 @@ impl VirtualMachine { #[inline] pub fn import<'a>( &self, - module: impl AsPyStr<'a>, + module_name: impl AsPyStr<'a>, from_list: Option>, level: usize, ) -> PyResult { - self.import_inner(module.as_pystr(&self.ctx), from_list, level) + let module_name = module_name.as_pystr(&self.ctx); + self.import_inner(module_name, from_list, level) } fn import_inner( @@ -794,12 +801,12 @@ impl VirtualMachine { #[doc(hidden)] pub fn __module_set_attr( &self, - module: &PyObject, + module: &Py, attr_name: &str, attr_value: impl Into, ) -> PyResult<()> { let val = attr_value.into(); - module.generic_setattr( + module.as_object().generic_setattr( self.ctx.intern_str(attr_name), PySetterValue::Assign(val), self, diff --git a/vm/src/vm/vm_new.rs b/vm/src/vm/vm_new.rs index 5c796811a..38b37f81f 100644 --- a/vm/src/vm/vm_new.rs +++ b/vm/src/vm/vm_new.rs @@ -20,19 +20,18 @@ impl VirtualMachine { value.into_pytuple(self) } - pub fn new_module(&self, name: &str, dict: PyDictRef, doc: Option<&str>) -> PyObjectRef { + pub fn new_module(&self, name: &str, dict: PyDictRef, doc: Option<&str>) -> PyRef { let module = PyRef::new_ref( - PyModule {}, + PyModule::new(), self.ctx.types.module_type.to_owned(), Some(dict), ); module.init_module_dict( self.ctx.intern_str(name), - doc.map(|doc| self.new_pyobj(doc.to_owned())) - .unwrap_or_else(|| self.ctx.none()), + doc.map(|doc| self.ctx.new_str(doc)), self, ); - module.into() + module } pub fn new_scope_with_builtins(&self) -> Scope { diff --git a/vm/src/vm/vm_object.rs b/vm/src/vm/vm_object.rs index 056ded9bf..a957ed66c 100644 --- a/vm/src/vm/vm_object.rs +++ b/vm/src/vm/vm_object.rs @@ -135,7 +135,7 @@ impl VirtualMachine { obj: &PyObject, method: &'static PyStrInterned, ) -> PyResult> { - PyMethod::get_special(obj, method, self) + PyMethod::get_special::(obj, method, self) } /// NOT PUBLIC API diff --git a/vm/src/vm/vm_ops.rs b/vm/src/vm/vm_ops.rs index 65be5d415..0fc9a1953 100644 --- a/vm/src/vm/vm_ops.rs +++ b/vm/src/vm/vm_ops.rs @@ -523,7 +523,7 @@ impl VirtualMachine { } pub fn _contains(&self, haystack: &PyObject, needle: PyObjectRef) -> PyResult { - match PyMethod::get_special(haystack, identifier!(self, __contains__), self)? { + match PyMethod::get_special::(haystack, identifier!(self, __contains__), self)? { Some(method) => method.invoke((needle,), self), None => self ._membership_iter_search(haystack, needle) diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index e689b6037..e71130c5c 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -14,7 +14,7 @@ mod _js { convert::{IntoObject, ToPyObject}, function::{ArgCallable, OptionalArg, OptionalOption, PosArgs}, protocol::PyIterReturn, - types::{IterNext, IterNextIterable}, + types::{IterNext, IterNextIterable, Representable}, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, }; use std::{cell, fmt, future}; @@ -90,13 +90,12 @@ mod _js { } } - #[pyclass] + #[pyclass(with(Representable))] impl PyJsValue { #[inline] pub fn new(value: impl Into) -> PyJsValue { - PyJsValue { - value: value.into(), - } + let value = value.into(); + PyJsValue { value } } #[pymethod] @@ -265,10 +264,12 @@ mod _js { fn instanceof(&self, rhs: PyJsValueRef, vm: &VirtualMachine) -> PyResult { instance_of(&self.value, &rhs.value).map_err(|err| new_js_error(vm, err)) } + } - #[pymethod(magic)] - fn repr(&self) -> String { - format!("{:?}", self.value) + impl Representable for PyJsValue { + #[inline] + fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + Ok(format!("{:?}", zelf.value)) } } diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index feee879e0..25964efc2 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -5,8 +5,10 @@ use crate::{ }; use js_sys::{Object, TypeError}; use rustpython_vm::{ - builtins::PyWeak, compiler::Mode, scope::Scope, Interpreter, PyObjectRef, PyPayload, PyRef, - PyResult, Settings, VirtualMachine, + builtins::{PyModule, PyWeak}, + compiler::Mode, + scope::Scope, + Interpreter, PyObjectRef, PyPayload, PyRef, PyResult, Settings, VirtualMachine, }; use std::{ cell::RefCell, @@ -26,10 +28,10 @@ pub(crate) struct StoredVirtualMachine { #[pymodule] mod _window {} -fn init_window_module(vm: &VirtualMachine) -> PyObjectRef { +fn init_window_module(vm: &VirtualMachine) -> PyRef { let module = _window::make_module(vm); - extend_module!(vm, module, { + extend_module!(vm, &module, { "window" => js_module::PyJsValue::new(wasm_builtins::window()).into_ref(&vm.ctx), }); @@ -42,7 +44,7 @@ impl StoredVirtualMachine { let mut settings = Settings::default(); settings.allow_external_library = false; let interp = Interpreter::with_init(settings, |vm| { - #[cfg(feature = "stdlib")] + #[cfg(feature = "freeze-stdlib")] vm.add_native_modules(rustpython_stdlib::get_module_inits()); #[cfg(feature = "freeze-stdlib")] @@ -300,7 +302,7 @@ impl WASMVirtualMachine { let module = vm.new_module(&name, attrs, None); let sys_modules = vm.sys_module.get_attr("modules", vm).into_js(vm)?; - sys_modules.set_item(&name, module, vm).into_js(vm)?; + sys_modules.set_item(&name, module.into(), vm).into_js(vm)?; Ok(()) })? @@ -313,13 +315,15 @@ impl WASMVirtualMachine { for entry in convert::object_entries(&module) { let (key, value) = entry?; let key = Object::from(key).to_string(); - extend_module!(vm, py_module, { + extend_module!(vm, &py_module, { &String::from(key) => convert::js_to_py(vm, value), }); } let sys_modules = vm.sys_module.get_attr("modules", vm).into_js(vm)?; - sys_modules.set_item(&name, py_module, vm).into_js(vm)?; + sys_modules + .set_item(&name, py_module.into(), vm) + .into_js(vm)?; Ok(()) })?