mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Honor generator close, throw context, and docs (#8701)
* Honor close unraisable and deallocator messages A failed close lookup on a yield-from target is unraisable. Ignored GeneratorExit from finalize gets a traceback and the closing-generator err_msg. __del__ uses the deallocator message. Assisted-by: Grok:grok-4.6 * address review: claim, ag_running, frame steal Keep the running claim through close() cleanup. ag_running reads running_async. aclose ignore-yield does not mark the generator closed. Steal the frame slot before uniqueness and expose it with try_to_owned. Assisted-by: Grok:grok-4.6 * Honor wrapper docs, yield assign, throw context method-wrapper exposes the slot __doc__. Parenthesized yield assignment uses the invalid-target messages. throw() restores without overwriting __context__, then chains only from the generator's own exc_info slot. Assisted-by: Grok:grok-4.6 * Remove passing expectedFailure markers method-wrapper docs, deallocator messages, and help pager output now match, so drop the leftover markers. Assisted-by: Grok:grok-4.6 * Honor wrapper signatures and assignment diagnostics Slot docs include text signatures. method-wrapper compares and hashes by identity. Keyword genexp and if/while assignment use the invalid-target messages. Assisted-by: Grok:grok-4.6 * address review: aclose, unraisable, frame publish aclose ignore-yield does not close the generator. Format unraisable object is None. close() leaves traceback to the caller; finalize attaches the generator frame. Publish the iframe before the lock-free slot. Assisted-by: Grok:grok-4.6 * Honor member descriptors and wrapper annotations Assisted-by: Grok:grok-4.6 * address review: getitem, bool doc, if assign Assisted-by: Grok:grok-4.6 * Honor eval-mode assignment invalid syntax Assisted-by: Grok:grok-4.6 * Supply getset and member docs from rustpython-doc Assisted-by: Grok:grok-4.6 * Enable rustls SSL on WASI targets Compile the rustls _ssl module for WASI and stub socket waits plus the native certificate store. Assisted-by: Grok:grok-4.6 * Add browser TLS _ssl in rustpython-wasm Register rustls _ssl and a MemoryBIO-oriented _socket from rustpython-wasm. Use rustls-rustcrypto because ring cannot compile for wasm32-unknown-unknown. Assisted-by: Grok:grok-4.6 * Define SSL protocol and cert constants as enums Keep OpenSSL bitmask defines as integer constants. Assisted-by: Grok:grok-4.6 * Ignore rustls deps cargo-shear cannot see The rustpython-wasm _ssl sources are compiled through a symlink, so shear does not count their crate uses. Assisted-by: Grok:grok-4.6 * Replace wasm ssl symlink with a BIO module Drop the stdlib ssl.rs link and keep a rustpython-wasm _ssl that only covers MemoryBIO wrap_bio TLS. Assisted-by: Grok:grok-4.6
This commit is contained in:
1 parent
d7e41ebff5
commit
73e547a9c3
46 files changed
+3334
-460
No files matched your search
Generated
+701
-61
File diff suppressed because it is too large.
Load diff
Vendored
-1
@@ -1014,7 +1014,6 @@ class TestBaseExitStack:
|
||||
else:
|
||||
self.fail("Expected KeyError, but no exception was raised")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_exit_exception_with_correct_context(self):
|
||||
# http://bugs.python.org/issue20317
|
||||
@contextmanager
|
||||
|
||||
Vendored
-7
@@ -1553,7 +1553,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
else:
|
||||
self.fail("finding the most derived metaclass should have failed")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_classmethods(self):
|
||||
# Testing class methods...
|
||||
class C(object):
|
||||
@@ -1615,7 +1614,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
del cm.x
|
||||
self.assertNotHasAttr(cm, "x")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_classmethod_staticmethod_annotations(self):
|
||||
for deco in (classmethod, staticmethod):
|
||||
@deco
|
||||
@@ -2355,7 +2353,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
self.assertEqual(r(), None)
|
||||
del r
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_properties(self):
|
||||
# Testing property...
|
||||
class C(object):
|
||||
@@ -3329,7 +3326,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
eval("x %s y" % op),
|
||||
"x=%d, y=%d" % (x, y))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_descrdoc(self):
|
||||
# Testing descriptor doc strings...
|
||||
from _io import FileIO
|
||||
@@ -4084,7 +4080,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
y = x ** 2
|
||||
self.assertIn('unsupported operand type(s) for **', str(cm.exception))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_pow_wrapper_error_messages(self):
|
||||
self.assertRaisesRegex(TypeError,
|
||||
'expected 1 or 2 arguments, got 0',
|
||||
@@ -4654,7 +4649,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
a >= b
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_method_wrapper(self):
|
||||
# Testing method-wrapper objects...
|
||||
# <type 'method-wrapper'> did not support any reflection before 2.5
|
||||
@@ -4981,7 +4975,6 @@ class ClassPropertiesAndMethods(unittest.TestCase):
|
||||
self.assertIn("cannot delete '__doc__' attribute of immutable type 'X'", str(cm.exception))
|
||||
self.assertEqual(X.__doc__, "banana")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_qualname(self):
|
||||
descriptors = [str.lower, complex.real, float.real, int.__add__]
|
||||
types = ['method', 'member', 'getset', 'wrapper']
|
||||
|
||||
Vendored
-1
@@ -1724,7 +1724,6 @@ class ExceptionTests(unittest.TestCase):
|
||||
os.listdir(__file__)
|
||||
self.assertEqual(cm.exception.errno, errno.ENOTDIR, cm.exception)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: None != 'Exception ignored while calling dealloca[83 chars]200>'
|
||||
def test_unraisable(self):
|
||||
# Issue #22836: PyErr_WriteUnraisable() should give sensible reports
|
||||
class BrokenDel:
|
||||
|
||||
Vendored
+5
-5
@@ -1369,7 +1369,7 @@ From the Iterators list, about the types of these things.
|
||||
>>> [s for s in dir(i) if not s.startswith('_')]
|
||||
['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw']
|
||||
>>> from test.support import HAVE_DOCSTRINGS
|
||||
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).')
|
||||
Implement next(self).
|
||||
>>> iter(i) is i
|
||||
True
|
||||
@@ -2526,12 +2526,12 @@ Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: assignment to yield expression not possible
|
||||
|
||||
>>> def f(): (yield bar) = y # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> def f(): (yield bar) = y
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: cannot assign to yield expression here. Maybe you meant '==' instead of '='?
|
||||
|
||||
>>> def f(): (yield bar) += y # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> def f(): (yield bar) += y
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: 'yield expression' is an illegal expression for augmented assignment
|
||||
@@ -2727,7 +2727,7 @@ RuntimeError: generator ignored GeneratorExit
|
||||
|
||||
Our ill-behaved code should be invoked during GC:
|
||||
|
||||
>>> with support.catch_unraisable_exception() as cm: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> with support.catch_unraisable_exception() as cm:
|
||||
... g = f()
|
||||
... next(g)
|
||||
... gen_repr = repr(g)
|
||||
@@ -2845,7 +2845,7 @@ to test.
|
||||
... raise RuntimeError(message)
|
||||
... invoke("del failed")
|
||||
...
|
||||
>>> with support.catch_unraisable_exception() as cm: # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> with support.catch_unraisable_exception() as cm:
|
||||
... leaker = Leaker()
|
||||
... del_repr = repr(type(leaker).__del__)
|
||||
... del leaker
|
||||
|
||||
Vendored
+2
-2
@@ -105,7 +105,7 @@ Verify that parenthesis are required in a statement
|
||||
|
||||
Verify that parenthesis are required when used as a keyword argument value
|
||||
|
||||
>>> dict(a = i for i in range(10)) # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> dict(a = i for i in range(10))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
|
||||
@@ -242,7 +242,7 @@ Check that generator attributes are present
|
||||
True
|
||||
|
||||
>>> from test.support import HAVE_DOCSTRINGS
|
||||
>>> print(g.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE
|
||||
>>> print(g.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).')
|
||||
Implement next(self).
|
||||
>>> import types
|
||||
>>> isinstance(g, types.GeneratorType)
|
||||
|
||||
-1
@@ -3954,7 +3954,6 @@ class TestSignatureObject(unittest.TestCase):
|
||||
('b', ..., ..., "positional_or_keyword")),
|
||||
...))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_signature_on_class(self):
|
||||
class C:
|
||||
def __init__(self, a):
|
||||
|
||||
Vendored
-1
@@ -787,7 +787,6 @@ class PydocDocTest(unittest.TestCase):
|
||||
run_pydoc_for_request(pydoc.Helper.help, 'Help on function help in module pydoc:')
|
||||
# test for pydoc.Helper() instance skipped because it is always meant to be interactive
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
|
||||
'trace function introduces __locals__ unexpectedly')
|
||||
@requires_docstrings
|
||||
|
||||
Vendored
-1
@@ -730,7 +730,6 @@ class StructTest(ComplexesAreIdenticalMixin, unittest.TestCase):
|
||||
s2 = struct.Struct(s.format.encode())
|
||||
self.assertEqual(s2.format, s.format)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_struct_cleans_up_at_runtime_shutdown(self):
|
||||
code = """if 1:
|
||||
import struct
|
||||
|
||||
Vendored
+10
-10
@@ -1264,22 +1264,22 @@ Missing ':' before suites:
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to __debug__
|
||||
|
||||
>>> if x = 3: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> if x = 3:
|
||||
... pass
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
|
||||
|
||||
>>> while x = 3: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> while x = 3:
|
||||
... pass
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
|
||||
|
||||
>>> if x.a = 3: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> if x.a = 3:
|
||||
... pass
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?
|
||||
|
||||
>>> while x.a = 3: # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> while x.a = 3:
|
||||
... pass
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?
|
||||
@@ -1885,19 +1885,19 @@ Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: keyword argument repeated: a
|
||||
|
||||
>>> {1, 2, 3} = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> {1, 2, 3} = 42
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to set display here. Maybe you meant '==' instead of '='?
|
||||
|
||||
>>> {1: 2, 3: 4} = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> {1: 2, 3: 4} = 42
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to dict literal here. Maybe you meant '==' instead of '='?
|
||||
|
||||
>>> f'{x}' = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> f'{x}' = 42
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to f-string expression here. Maybe you meant '==' instead of '='?
|
||||
|
||||
>>> f'{x}-{y}' = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> f'{x}-{y}' = 42
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to f-string expression here. Maybe you meant '==' instead of '='?
|
||||
|
||||
@@ -1973,11 +1973,11 @@ SyntaxError: 'u' and 'b' prefixes are incompatible
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: 'b' and 'f' prefixes are incompatible
|
||||
|
||||
>>> t'{x}' = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> t'{x}' = 42
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to t-string expression here. Maybe you meant '==' instead of '='?
|
||||
|
||||
>>> t'{x}-{y}' = 42 # TODO: RUSTPYTHON; Wrong error message # doctest: +EXPECTED_FAILURE
|
||||
>>> t'{x}-{y}' = 42
|
||||
Traceback (most recent call last):
|
||||
SyntaxError: cannot assign to t-string expression here. Maybe you meant '==' instead of '='?
|
||||
|
||||
|
||||
Vendored
-1
@@ -635,7 +635,6 @@ class TypesTests(unittest.TestCase):
|
||||
self.assertIsInstance(object.__lt__, types.WrapperDescriptorType)
|
||||
self.assertIsInstance(int.__lt__, types.WrapperDescriptorType)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: no signature found for builtin <method-wrapper '__get__' of wrapper_descriptor object
|
||||
@unittest.skipIf(MISSING_C_DOCSTRINGS,
|
||||
"Signature information for builtins requires docstrings")
|
||||
def test_dunder_get_signature(self):
|
||||
|
||||
Vendored
-1
@@ -538,7 +538,6 @@ class TestPEP380Operation(unittest.TestCase):
|
||||
"finishing g",
|
||||
])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_broken_getattr_handling(self):
|
||||
"""
|
||||
Test subiterator with a broken getattr implementation
|
||||
|
||||
+427
-35
@@ -326,29 +326,39 @@ fn cpython_parse_diagnostic_override(
|
||||
}
|
||||
|
||||
source_error!(invalid_dict_error(source_text));
|
||||
source_error!(invalid_collection_assignment_error(source_text));
|
||||
if !matches!(mode, Mode::Eval) {
|
||||
source_error!(invalid_collection_assignment_error(source_text));
|
||||
}
|
||||
source_error!(invalid_group_error(source_text));
|
||||
source_error!(invalid_def_type_params_error(source_text));
|
||||
source_error!(invalid_expression_error(source_text));
|
||||
source_error!(invalid_named_expression_error(source_text));
|
||||
source_error!(invalid_plain_assignment_error(source_text));
|
||||
source_error!(expression_assignment_error(source_text));
|
||||
source_error!(invalid_annotation_target_error(source_text));
|
||||
source_error!(invalid_assignment_target_error(source_text));
|
||||
source_error!(invalid_augassign_target_error(source_text));
|
||||
source_error!(invalid_for_target_error(source_text));
|
||||
source_error!(invalid_with_target_error(source_text));
|
||||
source_error!(invalid_delete_target_error(source_text));
|
||||
source_error!(invalid_standalone_except_error(source_text));
|
||||
source_error!(invalid_import_statement_error(source_text));
|
||||
source_error!(invalid_import_target_error(source_text));
|
||||
source_error!(invalid_except_as_target_error(source_text));
|
||||
source_error!(invalid_match_mapping_rest_wildcard_error(source_text));
|
||||
source_error!(invalid_match_as_target_error(source_text));
|
||||
source_error!(invalid_for_if_clause_error(source_text));
|
||||
source_error!(invalid_if_expression_statement_error(source_text));
|
||||
source_error!(invalid_else_elif_error(source_text));
|
||||
source_error!(mixed_except_handlers_error(source_text));
|
||||
// Assignment and other statements are not expressions. Eval keeps the
|
||||
// generic parse error rather than a statement-target rewrite.
|
||||
if !matches!(mode, Mode::Eval) {
|
||||
source_error!(invalid_plain_assignment_error(source_text));
|
||||
source_error!(expression_assignment_error(source_text));
|
||||
source_error!(invalid_annotation_target_error(source_text));
|
||||
source_error!(invalid_assignment_target_error(source_text));
|
||||
source_error!(invalid_condition_assignment_error(
|
||||
source_text,
|
||||
error.location.start().to_usize()
|
||||
));
|
||||
source_error!(invalid_augassign_target_error(source_text));
|
||||
source_error!(invalid_for_target_error(source_text));
|
||||
source_error!(invalid_with_target_error(source_text));
|
||||
source_error!(invalid_delete_target_error(source_text));
|
||||
source_error!(invalid_standalone_except_error(source_text));
|
||||
source_error!(invalid_import_statement_error(source_text));
|
||||
source_error!(invalid_import_target_error(source_text));
|
||||
source_error!(invalid_except_as_target_error(source_text));
|
||||
source_error!(invalid_match_mapping_rest_wildcard_error(source_text));
|
||||
source_error!(invalid_match_as_target_error(source_text));
|
||||
source_error!(invalid_for_if_clause_error(source_text));
|
||||
source_error!(invalid_if_expression_statement_error(source_text));
|
||||
source_error!(invalid_else_elif_error(source_text));
|
||||
source_error!(mixed_except_handlers_error(source_text));
|
||||
}
|
||||
|
||||
if matches!(
|
||||
&error.error,
|
||||
@@ -387,7 +397,11 @@ fn cpython_parse_diagnostic_override(
|
||||
// into the generic "invalid syntax" message. rustpython-vm's `vm_new.rs`
|
||||
// does this same collapse for its own callers; rustpython-compiler has no
|
||||
// vm dependency, so mirror it here.
|
||||
if matches!(&error.error, parser::ParseErrorType::ExpectedExpression) {
|
||||
if matches!(
|
||||
&error.error,
|
||||
parser::ParseErrorType::ExpectedExpression
|
||||
| parser::ParseErrorType::UnexpectedExpressionToken
|
||||
) {
|
||||
let (loc, end_loc) = adjusted_error_locations(source_file, error.location);
|
||||
return Some(NormalizedParseDiagnostic::new(
|
||||
parser::ParseErrorType::OtherError("invalid syntax".into()),
|
||||
@@ -436,6 +450,10 @@ fn invalid_assignment_target_diagnostic(
|
||||
ast::Expr::BinOp(_) => "cannot assign to expression".into(),
|
||||
ast::Expr::If(_) => "cannot assign to conditional expression".into(),
|
||||
ast::Expr::Generator(_) => "cannot assign to generator expression".into(),
|
||||
ast::Expr::Yield(_) | ast::Expr::YieldFrom(_) => {
|
||||
"cannot assign to yield expression here. Maybe you meant '==' instead of '='?"
|
||||
.into()
|
||||
}
|
||||
ast::Expr::FString(_) => "invalid syntax".into(),
|
||||
ast::Expr::StringLiteral(_)
|
||||
| ast::Expr::BytesLiteral(_)
|
||||
@@ -1893,6 +1911,14 @@ fn invalid_call_argument_assignment_error(
|
||||
}
|
||||
}
|
||||
if is_simple_keyword_name(bytes, target_start, target_end) {
|
||||
// NAME '=' expression for_if_clauses
|
||||
if unparenthesized_comprehension(bytes, equal + 1) {
|
||||
return Some(CpythonDiagnostic::new(
|
||||
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?".to_owned(),
|
||||
target_start,
|
||||
equal + 1,
|
||||
));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
Some(CpythonDiagnostic::new(
|
||||
@@ -1902,6 +1928,37 @@ fn invalid_call_argument_assignment_error(
|
||||
))
|
||||
}
|
||||
|
||||
fn unparenthesized_comprehension(bytes: &[u8], mut index: usize) -> bool {
|
||||
index = next_non_horizontal_whitespace(bytes, index);
|
||||
if index >= bytes.len() {
|
||||
return false;
|
||||
}
|
||||
let start = index;
|
||||
let mut level = 0usize;
|
||||
while index < bytes.len() {
|
||||
match bytes[index] {
|
||||
b'\'' | b'"' => index = skip_quoted_string(bytes, index),
|
||||
b'(' | b'[' | b'{' => {
|
||||
level += 1;
|
||||
index += 1;
|
||||
}
|
||||
b')' | b']' | b'}' => {
|
||||
if level == 0 {
|
||||
return false;
|
||||
}
|
||||
level -= 1;
|
||||
index += 1;
|
||||
}
|
||||
b',' | b':' if level == 0 => return false,
|
||||
_ if level == 0 && index > start && starts_identifier(bytes, index, b"for") => {
|
||||
return true;
|
||||
}
|
||||
_ => index += 1,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn invalid_call_star_expression_error(
|
||||
bytes: &[u8],
|
||||
arg_start: usize,
|
||||
@@ -2711,6 +2768,30 @@ fn parenthesized_single_starred_delete_target(bytes: &[u8], start: usize, end: u
|
||||
false
|
||||
}
|
||||
|
||||
fn assignment_target_expr_range(source: &str, start: usize, end: usize) -> Option<(usize, usize)> {
|
||||
let bytes = source.as_bytes();
|
||||
let (target_start, target_end) = trim_target_range(bytes, start, end);
|
||||
if target_start >= target_end {
|
||||
return None;
|
||||
}
|
||||
if parser::parse(
|
||||
&source[target_start..target_end],
|
||||
parser::Mode::Expression.into(),
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
return Some((target_start, target_end));
|
||||
}
|
||||
// `def f(): (yield bar)` — skip the suite header so the remaining
|
||||
// text is the assignment target expression.
|
||||
let colon = top_level_colon(bytes, target_start, target_end)?;
|
||||
let after = skip_horizontal_whitespace(bytes, colon + 1);
|
||||
if after >= target_end {
|
||||
return None;
|
||||
}
|
||||
Some((after, target_end))
|
||||
}
|
||||
|
||||
fn trim_target_range(bytes: &[u8], mut start: usize, mut end: usize) -> (usize, usize) {
|
||||
while start < end
|
||||
&& matches!(
|
||||
@@ -2745,10 +2826,7 @@ fn assignment_target_error_for_slice(
|
||||
end: usize,
|
||||
) -> Option<CpythonDiagnostic> {
|
||||
let bytes = source.as_bytes();
|
||||
let (target_start, target_end) = trim_target_range(bytes, start, end);
|
||||
if target_start >= target_end {
|
||||
return None;
|
||||
}
|
||||
let (target_start, target_end) = assignment_target_expr_range(source, start, end)?;
|
||||
if starts_identifier(bytes, target_start, b"yield") {
|
||||
return Some(CpythonDiagnostic::new(
|
||||
"assignment to yield expression not possible".to_owned(),
|
||||
@@ -2766,13 +2844,6 @@ fn assignment_target_error_for_slice(
|
||||
let invalid_target = invalid_assignment_target(&expression.body)?;
|
||||
let invalid_start = target_start + invalid_target.range().start().to_usize();
|
||||
let invalid_end = target_start + invalid_target.range().end().to_usize();
|
||||
if matches!(invalid_target, ast::Expr::FString(_)) {
|
||||
return Some(CpythonDiagnostic::new(
|
||||
"invalid syntax".to_owned(),
|
||||
invalid_start,
|
||||
invalid_end,
|
||||
));
|
||||
}
|
||||
let name = delete_target_expr_name(invalid_target);
|
||||
let top_level = invalid_target.range() == expression.body.range();
|
||||
let bitwise_like = matches!(
|
||||
@@ -2785,6 +2856,12 @@ fn assignment_target_error_for_slice(
|
||||
| ast::Expr::StringLiteral(_)
|
||||
| ast::Expr::BytesLiteral(_)
|
||||
| ast::Expr::EllipsisLiteral(_)
|
||||
| ast::Expr::Yield(_)
|
||||
| ast::Expr::YieldFrom(_)
|
||||
| ast::Expr::Set(_)
|
||||
| ast::Expr::Dict(_)
|
||||
| ast::Expr::FString(_)
|
||||
| ast::Expr::TString(_)
|
||||
);
|
||||
Some(CpythonDiagnostic::new(
|
||||
invalid_assignment_message(name, top_level && bitwise_like),
|
||||
@@ -2919,6 +2996,161 @@ fn top_level_plain_assignment_offsets(bytes: &[u8]) -> Vec<usize> {
|
||||
offsets
|
||||
}
|
||||
|
||||
fn invalid_condition_assignment_error(
|
||||
source: &str,
|
||||
parse_error_offset: usize,
|
||||
) -> Option<CpythonDiagnostic> {
|
||||
let bytes = source.as_bytes();
|
||||
let mut index = 0usize;
|
||||
let mut line_start = 0usize;
|
||||
let mut level = 0usize;
|
||||
while index < bytes.len() {
|
||||
match bytes[index] {
|
||||
b'#' if level == 0 => {
|
||||
while index < bytes.len() && bytes[index] != b'\n' {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
b'\n' => {
|
||||
line_start = index + 1;
|
||||
index += 1;
|
||||
}
|
||||
b'\'' | b'"' => index = skip_quoted_string(bytes, index),
|
||||
b'(' | b'[' | b'{' => {
|
||||
level += 1;
|
||||
index += 1;
|
||||
}
|
||||
b')' | b']' | b'}' => {
|
||||
level = level.saturating_sub(1);
|
||||
index += 1;
|
||||
}
|
||||
_ if level == 0
|
||||
&& index == skip_horizontal_whitespace(bytes, line_start)
|
||||
&& (starts_identifier(bytes, index, b"if")
|
||||
|| starts_identifier(bytes, index, b"elif")
|
||||
|| starts_identifier(bytes, index, b"while")) =>
|
||||
{
|
||||
let keyword_len = if starts_identifier(bytes, index, b"while") {
|
||||
5
|
||||
} else if starts_identifier(bytes, index, b"elif") {
|
||||
4
|
||||
} else {
|
||||
2
|
||||
};
|
||||
let cond_start = skip_horizontal_whitespace(bytes, index + keyword_len);
|
||||
let Some(colon) = top_level_colon(bytes, cond_start, bytes.len()) else {
|
||||
index += keyword_len;
|
||||
continue;
|
||||
};
|
||||
if parse_error_offset < index || parse_error_offset > colon {
|
||||
index += keyword_len;
|
||||
continue;
|
||||
}
|
||||
let Some(equal) = condition_plain_assignment(bytes, cond_start, colon) else {
|
||||
index += keyword_len;
|
||||
continue;
|
||||
};
|
||||
let (target_start, target_end) = trim_target_range(bytes, cond_start, equal);
|
||||
if target_start >= target_end {
|
||||
index += keyword_len;
|
||||
continue;
|
||||
}
|
||||
if is_simple_keyword_name(bytes, target_start, target_end) {
|
||||
return Some(CpythonDiagnostic::new(
|
||||
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?".to_owned(),
|
||||
target_start,
|
||||
equal + 1,
|
||||
));
|
||||
}
|
||||
if let Some((expr_name, start, end, _)) =
|
||||
expression_name_and_range(&source[target_start..target_end])
|
||||
{
|
||||
return Some(CpythonDiagnostic::new(
|
||||
format!(
|
||||
"cannot assign to {expr_name} here. Maybe you meant '==' instead of '='?"
|
||||
),
|
||||
target_start + start,
|
||||
target_start + end,
|
||||
));
|
||||
}
|
||||
return Some(CpythonDiagnostic::new(
|
||||
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?".to_owned(),
|
||||
target_start,
|
||||
equal + 1,
|
||||
));
|
||||
}
|
||||
_ => index += 1,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn condition_plain_assignment(bytes: &[u8], start: usize, end: usize) -> Option<usize> {
|
||||
let mut index = start;
|
||||
let mut nest = Vec::new();
|
||||
while index < end {
|
||||
match bytes[index] {
|
||||
b'\'' | b'"' => index = skip_quoted_string(bytes, index),
|
||||
b'(' => {
|
||||
nest.push(if is_call_open(bytes, start, index) {
|
||||
b'c'
|
||||
} else {
|
||||
b'g'
|
||||
});
|
||||
index += 1;
|
||||
}
|
||||
b'[' => {
|
||||
nest.push(b'[');
|
||||
index += 1;
|
||||
}
|
||||
b'{' => {
|
||||
nest.push(b'{');
|
||||
index += 1;
|
||||
}
|
||||
b')' | b']' | b'}' => {
|
||||
nest.pop();
|
||||
index += 1;
|
||||
}
|
||||
b'=' if is_plain_assignment_operator(bytes, index) && !nest.contains(&b'c') => {
|
||||
return Some(index);
|
||||
}
|
||||
_ => index += 1,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_call_open(bytes: &[u8], start: usize, open: usize) -> bool {
|
||||
let mut index = open;
|
||||
while index > start {
|
||||
index -= 1;
|
||||
match bytes[index] {
|
||||
b' ' | b'\t' | b'\n' | b'\r' | b'\x0c' => {}
|
||||
b')' | b']' => return true,
|
||||
byte if byte >= 0x80 || is_ascii_identifier_char(byte) => {
|
||||
let mut ident_start = index;
|
||||
while ident_start > start
|
||||
&& bytes
|
||||
.get(ident_start - 1)
|
||||
.is_some_and(|b| *b >= 0x80 || is_ascii_identifier_char(*b))
|
||||
{
|
||||
ident_start -= 1;
|
||||
}
|
||||
return !is_condition_keyword(&bytes[ident_start..=index]);
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_condition_keyword(word: &[u8]) -> bool {
|
||||
matches!(
|
||||
word,
|
||||
b"not" | b"and" | b"or" | b"in" | b"is" | b"if" | b"elif" | b"while" | b"await" | b"lambda"
|
||||
)
|
||||
}
|
||||
|
||||
fn invalid_assignment_target_error(source: &str) -> Option<CpythonDiagnostic> {
|
||||
let bytes = source.as_bytes();
|
||||
let offsets = top_level_plain_assignment_offsets(bytes);
|
||||
@@ -2987,10 +3219,7 @@ fn top_level_augassign_offset(bytes: &[u8]) -> Option<(usize, usize)> {
|
||||
fn invalid_augassign_target_error(source: &str) -> Option<CpythonDiagnostic> {
|
||||
let bytes = source.as_bytes();
|
||||
let (operator, _) = top_level_augassign_offset(bytes)?;
|
||||
let (target_start, target_end) = trim_target_range(bytes, 0, operator);
|
||||
if target_start >= target_end {
|
||||
return None;
|
||||
}
|
||||
let (target_start, target_end) = assignment_target_expr_range(source, 0, operator)?;
|
||||
let target_text = &source[target_start..target_end];
|
||||
let Ok(parsed) = parser::parse(target_text, parser::Mode::Expression.into()) else {
|
||||
return None;
|
||||
@@ -5383,6 +5612,20 @@ fn missing_comma_expression_error(source: &str) -> Option<CpythonDiagnostic> {
|
||||
index = identifier_end(bytes, index, bytes.len());
|
||||
}
|
||||
byte if !stack.is_empty() && expression_atom_start_byte(byte) => {
|
||||
// `yield x` / `await x` are prefix expressions, not two
|
||||
// adjacent atoms missing a comma.
|
||||
if starts_identifier(bytes, index, b"yield") {
|
||||
index = identifier_end(bytes, index, bytes.len());
|
||||
let after_yield = skip_ascii_whitespace(bytes, index, bytes.len());
|
||||
if starts_identifier(bytes, after_yield, b"from") {
|
||||
index = identifier_end(bytes, after_yield, bytes.len());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if starts_identifier(bytes, index, b"await") {
|
||||
index = identifier_end(bytes, index, bytes.len());
|
||||
continue;
|
||||
}
|
||||
let atom_end = adjacent_atom_end(bytes, index).unwrap_or(index + 1);
|
||||
let next = skip_ascii_whitespace(bytes, atom_end, bytes.len());
|
||||
if next > atom_end
|
||||
@@ -7486,6 +7729,155 @@ mod tests {
|
||||
assert_eq!(span("[\u{3b1} \u{3b2}]"), (2, 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parenthesized_yield_assignment_uses_invalid_target_message() {
|
||||
let err = compile(
|
||||
"def f(): (yield bar) = y\n",
|
||||
Mode::Exec,
|
||||
"<yield>",
|
||||
CompileOpts::default(),
|
||||
)
|
||||
.expect_err("parenthesized yield is not an assignment target");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"cannot assign to yield expression here. Maybe you meant '==' instead of '='?"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parenthesized_yield_augassign_uses_illegal_expression_message() {
|
||||
let err = compile(
|
||||
"def f(): (yield bar) += y\n",
|
||||
Mode::Exec,
|
||||
"<yield>",
|
||||
CompileOpts::default(),
|
||||
)
|
||||
.expect_err("parenthesized yield is not an augmented assignment target");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"'yield expression' is an illegal expression for augmented assignment"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kwarg_unparenthesized_genexp_uses_eq_or_walrus_message() {
|
||||
let err = compile(
|
||||
"dict(a = i for i in range(10))\n",
|
||||
Mode::Exec,
|
||||
"<kwarg>",
|
||||
CompileOpts::default(),
|
||||
)
|
||||
.expect_err("unparenthesized genexp after '=' is invalid");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_fstring_assignment_keeps_invalid_syntax() {
|
||||
for source in ["f'' = 3", "f'{0}' = x", "f'{x}' = x"] {
|
||||
let err = compile(source, Mode::Eval, "<eval>", CompileOpts::default())
|
||||
.expect_err("assignment is invalid in eval");
|
||||
assert_eq!(err.to_string(), "invalid syntax", "{source}");
|
||||
}
|
||||
let err = compile("f'' = 3", Mode::Exec, "<exec>", CompileOpts::default())
|
||||
.expect_err("f-string is not an assignment target");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"cannot assign to f-string expression here. Maybe you meant '==' instead of '='?"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_assignment_uses_eq_or_walrus_message() {
|
||||
let err = compile(
|
||||
"if x = 3: pass\n",
|
||||
Mode::Exec,
|
||||
"<if>",
|
||||
CompileOpts::default(),
|
||||
)
|
||||
.expect_err("assignment in if condition is invalid");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parenthesized_if_assignment_uses_eq_or_walrus_message() {
|
||||
for source in [
|
||||
"if (x = 3): pass\n",
|
||||
"if ((x = 3)): pass\n",
|
||||
"if (x = 3) and y: pass\n",
|
||||
] {
|
||||
let err = compile(source, Mode::Exec, "<if>", CompileOpts::default())
|
||||
.expect_err("parenthesized assignment in if condition is invalid");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"invalid syntax. Maybe you meant '==' or ':=' instead of '='?"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn earlier_syntax_error_is_not_replaced_by_later_condition() {
|
||||
let err = compile(
|
||||
"@@@\nif x = 3: pass\n",
|
||||
Mode::Exec,
|
||||
"<if>",
|
||||
CompileOpts::default(),
|
||||
)
|
||||
.expect_err("the first invalid token is the syntax error");
|
||||
assert_eq!(err.to_string(), "invalid syntax");
|
||||
assert_eq!(err.python_location().0, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_attribute_assignment_uses_invalid_target_hint() {
|
||||
let err = compile(
|
||||
"if x.a = 3: pass\n",
|
||||
Mode::Exec,
|
||||
"<if>",
|
||||
CompileOpts::default(),
|
||||
)
|
||||
.expect_err("attribute assignment in if condition is invalid");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"cannot assign to attribute here. Maybe you meant '==' instead of '='?"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parenthesized_yield_from_assignment_uses_invalid_target_message() {
|
||||
let err = compile(
|
||||
"def f(): (yield from value) = target\n",
|
||||
Mode::Exec,
|
||||
"<yield>",
|
||||
CompileOpts::default(),
|
||||
)
|
||||
.expect_err("parenthesized yield from is not an assignment target");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"cannot assign to yield expression here. Maybe you meant '==' instead of '='?"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_display_assignment_uses_invalid_target_hint() {
|
||||
let err = compile(
|
||||
"{1, 2, 3} = 42\n",
|
||||
Mode::Exec,
|
||||
"<set>",
|
||||
CompileOpts::default(),
|
||||
)
|
||||
.expect_err("set display is not an assignment target");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"cannot assign to set display here. Maybe you meant '==' instead of '='?"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn obsolete_not_equal_diagnostic_spans_the_whole_operator() {
|
||||
let err = compile("2 <> 3\n", Mode::Exec, "<obsolete>", CompileOpts::default())
|
||||
|
||||
@@ -168,6 +168,8 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
|
||||
itemsize,
|
||||
} = extract_impl_attrs(attr, &impl_ty)?;
|
||||
let payload_ty = attr_payload.unwrap_or(payload_guess);
|
||||
context.getset_items.type_name = Some(payload_ty.to_string());
|
||||
context.member_items.type_name = Some(payload_ty.to_string());
|
||||
let method_def = &context.method_items;
|
||||
let getset_impl = &context.getset_items;
|
||||
let member_impl = &context.member_items;
|
||||
@@ -265,6 +267,8 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
|
||||
..
|
||||
} = extract_impl_attrs(attr, &trai.ident)?;
|
||||
|
||||
context.getset_items.type_name = Some(trai.ident.to_string());
|
||||
context.member_items.type_name = Some(trai.ident.to_string());
|
||||
let method_def = &context.method_items;
|
||||
let getset_impl = &context.getset_items;
|
||||
let member_impl = &context.member_items;
|
||||
@@ -415,6 +419,21 @@ fn type_matches_path(ty: &syn::Type, path: &syn::Path) -> bool {
|
||||
type_last.ident == path_last.ident
|
||||
}
|
||||
|
||||
fn cpython_attr_doc(rust_type: &str, attr: &str) -> Option<String> {
|
||||
let stripped = rust_type.strip_prefix("Py").unwrap_or(rust_type);
|
||||
let lower = stripped.to_ascii_lowercase();
|
||||
let underscored = format!("_{stripped}");
|
||||
let class_names = [rust_type, stripped, lower.as_str(), underscored.as_str()];
|
||||
for (key, doc) in &DB {
|
||||
if class_names.iter().any(|class| {
|
||||
*key == format!("{class}.{attr}") || key.ends_with(&format!(".{class}.{attr}"))
|
||||
}) {
|
||||
return Some((*doc).to_owned());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_class_def(
|
||||
ident: &Ident,
|
||||
name: &str,
|
||||
@@ -978,10 +997,12 @@ where
|
||||
let item_meta = MethodItemMeta::from_attr(ident.clone(), &item_attr)?;
|
||||
|
||||
let py_name = item_meta.method_name()?;
|
||||
let coexist = item_meta.coexist()?;
|
||||
|
||||
// Disallow slot methods - they should be defined via trait implementations
|
||||
// These are exposed as wrapper_descriptor via add_operators from SLOT_DEFS
|
||||
if !args.context.is_trait {
|
||||
// unless the method is METH_COEXIST.
|
||||
if !args.context.is_trait && !coexist {
|
||||
const FORBIDDEN_SLOT_METHODS: &[(&str, &str)] = &[
|
||||
// Constructor/Initializer traits
|
||||
("__new__", "Constructor"),
|
||||
@@ -1123,6 +1144,7 @@ where
|
||||
ident: ident.to_owned(),
|
||||
doc,
|
||||
raw,
|
||||
coexist,
|
||||
attr_name: self.inner.attr_name,
|
||||
call_flags,
|
||||
});
|
||||
@@ -1328,11 +1350,13 @@ where
|
||||
args.attrs.push(allow_attr);
|
||||
}
|
||||
|
||||
let doc = args.attrs.doc();
|
||||
args.context.member_items.add_item(
|
||||
&py_name,
|
||||
member_item_kind,
|
||||
member_kind,
|
||||
ident.clone(),
|
||||
doc,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1348,6 +1372,7 @@ struct MethodNurseryItem {
|
||||
cfgs: Vec<Attribute>,
|
||||
ident: Ident,
|
||||
raw: bool,
|
||||
coexist: bool,
|
||||
doc: Option<String>,
|
||||
attr_name: AttrName,
|
||||
call_flags: TokenStream,
|
||||
@@ -1394,9 +1419,14 @@ impl ToTokens for MethodNursery {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let call_flags = &item.call_flags;
|
||||
let coexist_flags = if item.coexist {
|
||||
quote! { | rustpython_vm::function::PyMethodFlags::COEXIST.bits() }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
let flags = quote! {
|
||||
rustpython_vm::function::PyMethodFlags::from_bits_retain(
|
||||
(#binding_flags).bits() | (#call_flags).bits()
|
||||
(#binding_flags).bits() | (#call_flags).bits() #coexist_flags
|
||||
)
|
||||
};
|
||||
// TODO: intern
|
||||
@@ -1437,6 +1467,7 @@ struct GetSetEntry {
|
||||
struct GetSetNursery {
|
||||
map: HashMap<(String, Vec<Attribute>), GetSetEntry>,
|
||||
validated: bool,
|
||||
type_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
@@ -1510,7 +1541,12 @@ impl ToTokens for GetSetNursery {
|
||||
Some(setter) => quote_spanned! { setter.span() => .with_set(Self::#setter)},
|
||||
None => quote! {},
|
||||
};
|
||||
let doc = match &entry.doc {
|
||||
let doc = entry.doc.clone().or_else(|| {
|
||||
self.type_name
|
||||
.as_deref()
|
||||
.and_then(|ty| cpython_attr_doc(ty, name))
|
||||
});
|
||||
let doc = match &doc {
|
||||
Some(doc) => quote! { .with_doc(#doc) },
|
||||
None => quote! {},
|
||||
};
|
||||
@@ -1540,12 +1576,14 @@ type MemberKindStr = Option<String>;
|
||||
struct MemberNursery {
|
||||
map: HashMap<String, MemberNurseryEntry>,
|
||||
validated: bool,
|
||||
type_name: Option<String>,
|
||||
}
|
||||
|
||||
struct MemberNurseryEntry {
|
||||
kind: MemberKindStr,
|
||||
getter: Option<Ident>,
|
||||
setter: Option<Ident>,
|
||||
doc: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
@@ -1561,6 +1599,7 @@ impl MemberNursery {
|
||||
kind: MemberItemKind,
|
||||
member_kind: MemberKindStr,
|
||||
item_ident: Ident,
|
||||
doc: Option<String>,
|
||||
) -> Result<()> {
|
||||
assert!(!self.validated, "new item is not allowed after validation");
|
||||
let entry = self
|
||||
@@ -1570,6 +1609,7 @@ impl MemberNursery {
|
||||
kind: member_kind,
|
||||
getter: None,
|
||||
setter: None,
|
||||
doc: None,
|
||||
});
|
||||
let func = match kind {
|
||||
MemberItemKind::Get => &mut entry.getter,
|
||||
@@ -1579,6 +1619,11 @@ impl MemberNursery {
|
||||
bail_span!(item_ident, "Multiple member accessors with name '{}'", name);
|
||||
}
|
||||
*func = Some(item_ident);
|
||||
if matches!(kind, MemberItemKind::Get)
|
||||
&& let Some(doc) = doc
|
||||
{
|
||||
entry.doc = Some(doc);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1625,10 +1670,19 @@ impl ToTokens for MemberNursery {
|
||||
}
|
||||
};
|
||||
let getter = entry.getter.as_ref().unwrap();
|
||||
let doc = entry.doc.clone().or_else(|| {
|
||||
self.type_name
|
||||
.as_deref()
|
||||
.and_then(|ty| cpython_attr_doc(ty, name))
|
||||
});
|
||||
let doc = match &doc {
|
||||
Some(doc) => quote! { Some(#doc) },
|
||||
None => quote! { None },
|
||||
};
|
||||
quote_spanned! { getter.span() =>
|
||||
class.set_str_attr(
|
||||
#name,
|
||||
ctx.new_member(#name, #member_kind, Self::#getter, #setter, class),
|
||||
ctx.new_member(#name, #member_kind, Self::#getter, #setter, class, #doc),
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
@@ -1640,7 +1694,7 @@ impl ToTokens for MemberNursery {
|
||||
struct MethodItemMeta(ItemMetaInner);
|
||||
|
||||
impl ItemMeta for MethodItemMeta {
|
||||
const ALLOWED_NAMES: &'static [&'static str] = &["name", "raw"];
|
||||
const ALLOWED_NAMES: &'static [&'static str] = &["name", "raw", "coexist"];
|
||||
|
||||
fn from_inner(inner: ItemMetaInner) -> Self {
|
||||
Self(inner)
|
||||
@@ -1656,6 +1710,10 @@ impl MethodItemMeta {
|
||||
self.inner()._bool("raw")
|
||||
}
|
||||
|
||||
fn coexist(&self) -> Result<bool> {
|
||||
self.inner()._bool("coexist")
|
||||
}
|
||||
|
||||
fn method_name(&self) -> Result<String> {
|
||||
let inner = self.inner();
|
||||
let name = inner._optional_str("name")?;
|
||||
|
||||
@@ -96,7 +96,7 @@ impl Drop for Owned {
|
||||
fn drop(&mut self) {}
|
||||
}
|
||||
|
||||
impl<'fd> Borrowed<'fd> {
|
||||
impl Borrowed<'_> {
|
||||
/// Create a `crt_fd::Borrowed` from a raw file descriptor.
|
||||
///
|
||||
/// # Safety
|
||||
|
||||
@@ -26,11 +26,10 @@ pub mod crt_fd;
|
||||
|
||||
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
|
||||
pub mod fileutils;
|
||||
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
|
||||
pub mod fs;
|
||||
#[cfg(any(unix, windows))]
|
||||
pub mod locale;
|
||||
#[cfg(all(feature = "native-certs", not(target_arch = "wasm32")))]
|
||||
#[cfg(feature = "native-certs")]
|
||||
pub mod native_certs;
|
||||
pub mod readline;
|
||||
#[cfg(feature = "ssl")]
|
||||
|
||||
@@ -42,6 +42,7 @@ pub fn default_verify_paths() -> (String, String) {
|
||||
(cafile, capath)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn load() -> LoadResult {
|
||||
let result = rustls_native_certs::load_native_certs();
|
||||
LoadResult {
|
||||
@@ -58,6 +59,14 @@ pub fn load() -> LoadResult {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn load() -> LoadResult {
|
||||
LoadResult {
|
||||
certs: Vec::new(),
|
||||
errors: vec!["native certificate store is unavailable".to_owned()],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::first_existing;
|
||||
|
||||
@@ -1,17 +1,56 @@
|
||||
//! Protocol, verification, and option values published by `_ssl`.
|
||||
|
||||
pub const PROTOCOL_TLS: i32 = 2;
|
||||
pub const PROTOCOL_TLS_CLIENT: i32 = 16;
|
||||
pub const PROTOCOL_TLS_SERVER: i32 = 17;
|
||||
pub const PROTOCOL_TLSV1: i32 = 3;
|
||||
pub const PROTOCOL_TLSV1_1: i32 = 4;
|
||||
pub const PROTOCOL_TLSV1_2: i32 = 5;
|
||||
pub const PROTOCOL_TLSV1_3: i32 = 6;
|
||||
/// `_SSLContext` constructor protocol. Exclusive selector, not an `SSL_OP_*` mask.
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum SslVersion {
|
||||
Tls = 2,
|
||||
Tls1 = 3,
|
||||
Tls1_1 = 4,
|
||||
Tls1_2 = 5,
|
||||
Tls1_3 = 6,
|
||||
TlsClient = 0x10,
|
||||
TlsServer = 0x11,
|
||||
}
|
||||
|
||||
pub const CERT_NONE: i32 = 0;
|
||||
pub const CERT_OPTIONAL: i32 = 1;
|
||||
pub const CERT_REQUIRED: i32 = 2;
|
||||
pub const PROTOCOL_TLS: i32 = SslVersion::Tls as i32;
|
||||
pub const PROTOCOL_TLS_CLIENT: i32 = SslVersion::TlsClient as i32;
|
||||
pub const PROTOCOL_TLS_SERVER: i32 = SslVersion::TlsServer as i32;
|
||||
pub const PROTOCOL_TLSV1: i32 = SslVersion::Tls1 as i32;
|
||||
pub const PROTOCOL_TLSV1_1: i32 = SslVersion::Tls1_1 as i32;
|
||||
pub const PROTOCOL_TLSV1_2: i32 = SslVersion::Tls1_2 as i32;
|
||||
pub const PROTOCOL_TLSV1_3: i32 = SslVersion::Tls1_3 as i32;
|
||||
|
||||
/// `verify_mode`. Exclusive selector, not an `SSL_VERIFY_*` bitmask.
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum CertRequirements {
|
||||
None = 0,
|
||||
Optional = 1,
|
||||
Required = 2,
|
||||
}
|
||||
|
||||
pub const CERT_NONE: i32 = CertRequirements::None as i32;
|
||||
pub const CERT_OPTIONAL: i32 = CertRequirements::Optional as i32;
|
||||
pub const CERT_REQUIRED: i32 = CertRequirements::Required as i32;
|
||||
|
||||
/// TLS record version (`SSL3_VERSION` / `TLS1_*_VERSION` in `prov_ssl.h`).
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ProtoVersion {
|
||||
MinSupported = -2,
|
||||
Ssl3 = 0x0300,
|
||||
Tls1 = 0x0301,
|
||||
Tls1_1 = 0x0302,
|
||||
Tls1_2 = 0x0303,
|
||||
Tls1_3 = 0x0304,
|
||||
MaxSupported = -1,
|
||||
}
|
||||
|
||||
pub const PROTO_TLSV1_2: i32 = ProtoVersion::Tls1_2 as i32;
|
||||
pub const PROTO_TLSV1_3: i32 = ProtoVersion::Tls1_3 as i32;
|
||||
|
||||
/// `X509_V_FLAG_*` bitmasks from `x509_vfy.h`.
|
||||
pub const VERIFY_DEFAULT: i32 = 0;
|
||||
pub const VERIFY_CRL_CHECK_LEAF: i32 = 4;
|
||||
pub const VERIFY_CRL_CHECK_CHAIN: i32 = 12;
|
||||
@@ -24,7 +63,30 @@ pub const VERIFY_X509_PARTIAL_CHAIN: i32 = 0x80000;
|
||||
/// use SAN only and never fall back to the certificate Common Name.
|
||||
pub const HOSTFLAG_NEVER_CHECK_SUBJECT: i32 = 0x20;
|
||||
|
||||
pub const PROTO_TLSV1_2: i32 = 0x0303;
|
||||
pub const PROTO_TLSV1_3: i32 = 0x0304;
|
||||
/// `SSL_OP_NO_TLSv1_2` / `SSL_OP_NO_TLSv1_3` from `ssl.h`.
|
||||
pub const OP_NO_TLSV1_2: i32 = 0x0800_0000;
|
||||
pub const OP_NO_TLSV1_3: i32 = 0x2000_0000;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn protocol_and_cert_discriminants_match_published_consts() {
|
||||
assert_eq!(PROTOCOL_TLS, 2);
|
||||
assert_eq!(PROTOCOL_TLSV1, 3);
|
||||
assert_eq!(PROTOCOL_TLSV1_1, 4);
|
||||
assert_eq!(PROTOCOL_TLSV1_2, 5);
|
||||
assert_eq!(PROTOCOL_TLSV1_3, 6);
|
||||
assert_eq!(PROTOCOL_TLS_CLIENT, 0x10);
|
||||
assert_eq!(PROTOCOL_TLS_SERVER, 0x11);
|
||||
assert_eq!(CERT_NONE, 0);
|
||||
assert_eq!(CERT_OPTIONAL, 1);
|
||||
assert_eq!(CERT_REQUIRED, 2);
|
||||
assert_eq!(PROTO_TLSV1_2, 0x0303);
|
||||
assert_eq!(PROTO_TLSV1_3, 0x0304);
|
||||
assert_eq!(ProtoVersion::Ssl3 as i32, 0x0300);
|
||||
assert_eq!(ProtoVersion::Tls1 as i32, 0x0301);
|
||||
assert_eq!(ProtoVersion::Tls1_1 as i32, 0x0302);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use rustls::version::{TLS12, TLS13};
|
||||
|
||||
use super::constants::{OP_NO_TLSV1_2, OP_NO_TLSV1_3, PROTO_TLSV1_2, PROTO_TLSV1_3};
|
||||
use super::constants::{OP_NO_TLSV1_2, OP_NO_TLSV1_3, PROTO_TLSV1_2, PROTO_TLSV1_3, ProtoVersion};
|
||||
|
||||
/// Why `server_hostname` was rejected.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -52,12 +52,12 @@ pub fn rustls_versions(
|
||||
static TLS12_ONLY: &[&rustls::SupportedProtocolVersion] = &[&TLS12];
|
||||
static TLS13_ONLY: &[&rustls::SupportedProtocolVersion] = &[&TLS13];
|
||||
|
||||
let min = if minimum == -2 {
|
||||
let min = if minimum == ProtoVersion::MinSupported as i32 {
|
||||
PROTO_TLSV1_2
|
||||
} else {
|
||||
minimum
|
||||
};
|
||||
let max = if maximum == -1 {
|
||||
let max = if maximum == ProtoVersion::MaxSupported as i32 {
|
||||
PROTO_TLSV1_3
|
||||
} else {
|
||||
maximum
|
||||
|
||||
@@ -79,15 +79,14 @@ widestring = { workspace = true, optional = true }
|
||||
[target.'cfg(not(any(target_os = "ios", target_os = "android", target_os = "windows", target_arch = "wasm32", target_os = "redox")))'.dependencies]
|
||||
uuid = { workspace = true, features = ["v1"] }
|
||||
|
||||
# mmap + socket dependencies
|
||||
# mmap + socket + OpenSSL dependencies
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
||||
# OpenSSL dependencies (optional, for ssl-openssl feature)
|
||||
openssl = { workspace = true, optional = true }
|
||||
openssl-sys = { workspace = true, optional = true }
|
||||
foreign-types-shared = { workspace = true, optional = true }
|
||||
|
||||
# Rustls dependencies (optional, for ssl-rustls feature)
|
||||
# Rustls is the WASI TLS engine as well as the native one.
|
||||
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies]
|
||||
rustls = { workspace = true, default-features = false, features = ["std", "tls12"], optional = true }
|
||||
rustls-pemfile = { workspace = true, optional = true }
|
||||
x509-parser = { workspace = true, optional = true }
|
||||
|
||||
@@ -129,8 +129,8 @@ mod openssl;
|
||||
|
||||
#[cfg(all(
|
||||
feature = "host_env",
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "__ssl-rustls"
|
||||
feature = "__ssl-rustls",
|
||||
any(not(target_arch = "wasm32"), target_os = "wasi"),
|
||||
))]
|
||||
pub mod ssl;
|
||||
|
||||
@@ -255,8 +255,8 @@ pub fn stdlib_module_defs(ctx: &Context) -> Vec<&'static builtins::PyModuleDef>
|
||||
_sqlite3::module_def(ctx),
|
||||
#[cfg(all(
|
||||
feature = "host_env",
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "__ssl-rustls"
|
||||
feature = "__ssl-rustls",
|
||||
any(not(target_arch = "wasm32"), target_os = "wasi"),
|
||||
))]
|
||||
ssl::module_def(ctx),
|
||||
statistics::module_def(ctx),
|
||||
|
||||
+61
-22
@@ -27,6 +27,23 @@ mod msg;
|
||||
|
||||
pub use rustpython_host_env::ssl::{chain, cipher, oid, providers};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod sock_wait_stub {
|
||||
use rustpython_vm::{PyRef, VirtualMachine, builtins::PyOSError};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum SockWaitKind {
|
||||
Read,
|
||||
Write,
|
||||
#[allow(dead_code, reason = "kept in lockstep with the native wait kinds")]
|
||||
Connect,
|
||||
}
|
||||
|
||||
pub(crate) fn timeout_error_msg(vm: &VirtualMachine, msg: String) -> PyRef<PyOSError> {
|
||||
vm.new_os_subtype_error(vm.ctx.exceptions.timeout_error.to_owned(), None, msg)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use _ssl::module_def;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
@@ -38,7 +55,6 @@ mod _ssl {
|
||||
hash::PyHash,
|
||||
lock::{PyMutex, PyRwLock},
|
||||
},
|
||||
socket::{PySocket, SockWaitKind, sock_wait, timeout_error_msg},
|
||||
vm::{
|
||||
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
|
||||
VirtualMachine,
|
||||
@@ -56,6 +72,11 @@ mod _ssl {
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use super::sock_wait_stub::{SockWaitKind, timeout_error_msg};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::socket::{PySocket, SockWaitKind, sock_wait, timeout_error_msg};
|
||||
|
||||
// Import error types used in this module (others are exposed via pymodule(with(...)))
|
||||
use super::error::{
|
||||
PySSLError, PySSLWantReadError, PySSLWantWriteError, create_ssl_eof_error,
|
||||
@@ -2484,24 +2505,33 @@ mod _ssl {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Get timeout
|
||||
let timeout = self.get_socket_timeout(vm)?;
|
||||
|
||||
// Check for non-blocking mode (timeout = 0)
|
||||
if let Some(t) = timeout
|
||||
&& t.is_zero()
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
// Non-blocking mode - don't use select
|
||||
let _ = (wait_kind, vm);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Use select with the effective timeout
|
||||
let py_socket: PyRef<PySocket> = self.io.socket_object(vm).try_into_value(vm)?;
|
||||
let socket = py_socket
|
||||
.sock()
|
||||
.map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
// Get timeout
|
||||
let timeout = self.get_socket_timeout(vm)?;
|
||||
|
||||
sock_wait(&socket, wait_kind, timeout, vm)
|
||||
// Check for non-blocking mode (timeout = 0)
|
||||
if let Some(t) = timeout
|
||||
&& t.is_zero()
|
||||
{
|
||||
// Non-blocking mode - don't use select
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Use select with the effective timeout
|
||||
let py_socket: PyRef<PySocket> = self.io.socket_object(vm).try_into_value(vm)?;
|
||||
let socket = py_socket
|
||||
.sock()
|
||||
.map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?;
|
||||
|
||||
sock_wait(&socket, wait_kind, timeout, vm)
|
||||
}
|
||||
}
|
||||
|
||||
// Internal implementation with explicit timeout override
|
||||
@@ -2516,19 +2546,28 @@ mod _ssl {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(t) = timeout
|
||||
&& t.is_zero()
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
// Non-blocking mode - don't use select
|
||||
let _ = (wait_kind, timeout, vm);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let py_socket: PyRef<PySocket> = self.io.socket_object(vm).try_into_value(vm)?;
|
||||
let socket = py_socket
|
||||
.sock()
|
||||
.map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
if let Some(t) = timeout
|
||||
&& t.is_zero()
|
||||
{
|
||||
// Non-blocking mode - don't use select
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
sock_wait(&socket, wait_kind, timeout, vm).map_err(|e| e.into_pyexception(vm))
|
||||
let py_socket: PyRef<PySocket> = self.io.socket_object(vm).try_into_value(vm)?;
|
||||
let socket = py_socket
|
||||
.sock()
|
||||
.map_err(|e| vm.new_os_error(format!("Failed to get socket: {e}")))?;
|
||||
|
||||
sock_wait(&socket, wait_kind, timeout, vm).map_err(|e| e.into_pyexception(vm))
|
||||
}
|
||||
}
|
||||
|
||||
fn handshake_completed(&self) -> bool {
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#[path = "../openssl/ssl_data_31.rs"]
|
||||
mod ssl_data;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use super::sock_wait_stub::{SockWaitKind, timeout_error_msg};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::socket::{SockWaitKind, timeout_error_msg};
|
||||
use crate::vm::VirtualMachine;
|
||||
use rustpython_host_env::ssl::{TlsConnection, TlsError};
|
||||
|
||||
@@ -144,7 +144,7 @@ impl PyAsyncGen {
|
||||
}
|
||||
#[pygetset]
|
||||
fn ag_running(&self, _vm: &VirtualMachine) -> bool {
|
||||
self.inner.running()
|
||||
self.running_async.load()
|
||||
}
|
||||
#[pygetset]
|
||||
fn ag_code(&self, _vm: &VirtualMachine) -> PyRef<PyCode> {
|
||||
@@ -501,12 +501,11 @@ impl PyAsyncGenAThrow {
|
||||
|
||||
let (ty, val, tb) = self.value.clone();
|
||||
let ret = self.ag.inner.throw(self.ag.as_object(), ty, val, tb, vm);
|
||||
if self.aclose && self.ignored_close(&ret) {
|
||||
return Err(self.yield_close(vm));
|
||||
}
|
||||
let ret = if self.aclose {
|
||||
if self.ignored_close(&ret) {
|
||||
Err(self.yield_close(vm))
|
||||
} else {
|
||||
ret.and_then(|o| o.into_async_pyresult(vm))
|
||||
}
|
||||
ret.and_then(|o| o.into_async_pyresult(vm))
|
||||
} else {
|
||||
PyAsyncGenWrappedValue::unbox(&self.ag, ret, vm)
|
||||
};
|
||||
@@ -572,12 +571,11 @@ impl PyAsyncGenAThrow {
|
||||
exc_tb.unwrap_or_none(vm),
|
||||
vm,
|
||||
);
|
||||
if self.aclose && self.ignored_close(&ret) {
|
||||
return Err(self.yield_close(vm));
|
||||
}
|
||||
let res = if self.aclose {
|
||||
if self.ignored_close(&ret) {
|
||||
Err(self.yield_close(vm))
|
||||
} else {
|
||||
ret.and_then(|o| o.into_async_pyresult(vm))
|
||||
}
|
||||
ret.and_then(|o| o.into_async_pyresult(vm))
|
||||
} else {
|
||||
PyAsyncGenWrappedValue::unbox(&self.ag, ret, vm)
|
||||
};
|
||||
@@ -616,7 +614,6 @@ impl PyAsyncGenAThrow {
|
||||
}
|
||||
fn yield_close(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
|
||||
self.ag.running_async.store(false);
|
||||
self.ag.inner.closed.store(true);
|
||||
self.state.store(AwaitableState::Closed);
|
||||
vm.new_runtime_error("async generator ignored GeneratorExit")
|
||||
}
|
||||
@@ -829,14 +826,16 @@ impl IterNext for PyAnextAwaitable {
|
||||
/// _PyGen_Finalize for async generators
|
||||
impl Destructor for PyAsyncGen {
|
||||
fn del(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
// Generator is already closed, nothing to do
|
||||
if zelf.inner.closed.load() {
|
||||
if zelf.inner.closed() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Call the async generator finalizer hook if set.
|
||||
Self::call_finalizer(zelf, vm);
|
||||
|
||||
if zelf.ag_finalizer.lock().clone().is_some() {
|
||||
Self::call_finalizer(zelf, vm);
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(e) = zelf.inner.close(zelf.as_object(), vm) {
|
||||
crate::coroutine::unraisable_while_closing(zelf.as_object(), &zelf.inner, e, vm);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::{PyBoundMethod, PyGenericAlias, PyStr, PyType, PyTypeRef};
|
||||
use super::{
|
||||
PyBoundMethod, PyGenericAlias, PyStr, PyStrInterned, PyType, PyTypeRef, object_get_dict,
|
||||
};
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
class::{PyClassDef, PyClassImpl},
|
||||
common::lock::PyMutex,
|
||||
function::{FuncArgs, PySetterValue},
|
||||
@@ -88,20 +90,7 @@ impl Initializer for PyClassMethod {
|
||||
|
||||
fn init(zelf: PyRef<Self>, callable: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
|
||||
*zelf.callable.lock() = callable.clone();
|
||||
// Copy wrapper attributes from the callable, mirroring functools.wraps.
|
||||
let dict = zelf.as_object().dict().expect("classmethod has __dict__");
|
||||
for attr in [
|
||||
identifier!(vm.ctx, __doc__),
|
||||
identifier!(vm.ctx, __name__),
|
||||
identifier!(vm.ctx, __qualname__),
|
||||
identifier!(vm.ctx, __module__),
|
||||
identifier!(vm.ctx, __annotations__),
|
||||
] {
|
||||
if let Ok(value) = callable.get_attr(attr, vm) {
|
||||
dict.set_item(attr, value, vm)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
functools_wraps(zelf.as_object(), &callable, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,55 +106,68 @@ impl PyClassMethod {
|
||||
flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
|
||||
)]
|
||||
impl PyClassMethod {
|
||||
#[pygetset]
|
||||
fn __func__(&self) -> PyObjectRef {
|
||||
self.callable.lock().clone()
|
||||
#[pymember]
|
||||
fn __func__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(zelf.callable.lock().clone())
|
||||
}
|
||||
|
||||
#[pymember]
|
||||
fn __wrapped__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(zelf.callable.lock().clone())
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __wrapped__(&self) -> PyObjectRef {
|
||||
self.callable.lock().clone()
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __module__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__module__", vm)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __qualname__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__qualname__", vm)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __name__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__name__", vm)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __annotations__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__annotations__", vm)
|
||||
fn __annotations__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
|
||||
let callable = zelf.callable.lock().clone();
|
||||
descriptor_get_wrapped_attribute(
|
||||
callable,
|
||||
zelf.as_object(),
|
||||
identifier!(vm.ctx, __annotations__),
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[pygetset(setter)]
|
||||
fn set___annotations__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
|
||||
match value {
|
||||
PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotations__", v, vm),
|
||||
PySetterValue::Delete => Ok(()), // Silently ignore delete like CPython
|
||||
}
|
||||
fn set___annotations__(
|
||||
zelf: &Py<Self>,
|
||||
value: PySetterValue,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
descriptor_set_wrapped_attribute(
|
||||
zelf.as_object(),
|
||||
identifier!(vm.ctx, __annotations__),
|
||||
value,
|
||||
"classmethod",
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __annotate__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__annotate__", vm)
|
||||
fn __annotate__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
|
||||
let callable = zelf.callable.lock().clone();
|
||||
descriptor_get_wrapped_attribute(
|
||||
callable,
|
||||
zelf.as_object(),
|
||||
identifier!(vm.ctx, __annotate__),
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[pygetset(setter)]
|
||||
fn set___annotate__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
|
||||
match value {
|
||||
PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotate__", v, vm),
|
||||
PySetterValue::Delete => Ok(()), // Silently ignore delete like CPython
|
||||
}
|
||||
fn set___annotate__(
|
||||
zelf: &Py<Self>,
|
||||
value: PySetterValue,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
descriptor_set_wrapped_attribute(
|
||||
zelf.as_object(),
|
||||
identifier!(vm.ctx, __annotate__),
|
||||
value,
|
||||
"classmethod",
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
@@ -225,3 +227,58 @@ impl Representable for PyClassMethod {
|
||||
pub(crate) fn init(context: &'static Context) {
|
||||
PyClassMethod::extend_class(context, context.types.classmethod_type);
|
||||
}
|
||||
|
||||
pub(crate) fn functools_wraps(
|
||||
wrapper: &PyObject,
|
||||
wrapped: &PyObject,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
for attr in [
|
||||
identifier!(vm.ctx, __module__),
|
||||
identifier!(vm.ctx, __name__),
|
||||
identifier!(vm.ctx, __qualname__),
|
||||
identifier!(vm.ctx, __doc__),
|
||||
] {
|
||||
if let Some(value) = vm.get_attribute_opt(wrapped.to_owned(), attr)? {
|
||||
wrapper.set_attr(attr, value, vm)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn descriptor_get_wrapped_attribute(
|
||||
wrapped: PyObjectRef,
|
||||
obj: &PyObject,
|
||||
name: &'static PyStrInterned,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let dict = object_get_dict(obj.to_owned(), vm)?;
|
||||
if let Some(res) = dict.get_item_opt(name, vm)? {
|
||||
return Ok(res);
|
||||
}
|
||||
let res = wrapped.get_attr(name, vm)?;
|
||||
dict.set_item(name, res.clone(), vm)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub(crate) fn descriptor_set_wrapped_attribute(
|
||||
obj: &PyObject,
|
||||
name: &'static PyStrInterned,
|
||||
value: PySetterValue,
|
||||
type_name: &str,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let dict = object_get_dict(obj.to_owned(), vm)?;
|
||||
match value {
|
||||
PySetterValue::Delete => match dict.del_item(name, vm) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => Err(vm
|
||||
.new_attribute_error(format!(
|
||||
"'{type_name}' object has no attribute '{}'",
|
||||
name.as_str()
|
||||
))),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
PySetterValue::Assign(value) => dict.set_item(name, value, vm),
|
||||
}
|
||||
}
|
||||
@@ -539,14 +539,16 @@ impl PyComplex {
|
||||
with(PyRef, Comparable, Hashable, Constructor, AsNumber, Representable)
|
||||
)]
|
||||
impl PyComplex {
|
||||
#[pygetset]
|
||||
const fn real(&self) -> f64 {
|
||||
self.value.re
|
||||
#[pymember]
|
||||
fn real(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(vm.ctx.new_float(zelf.value.re).into())
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
const fn imag(&self) -> f64 {
|
||||
self.value.im
|
||||
#[pymember]
|
||||
fn imag(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(vm.ctx.new_float(zelf.value.im).into())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
|
||||
@@ -185,7 +185,7 @@ impl Destructor for PyCoroutine {
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(e) = zelf.inner.close(zelf.as_object(), vm) {
|
||||
vm.run_unraisable(e, None, zelf.as_object().to_owned());
|
||||
crate::coroutine::unraisable_while_closing(zelf.as_object(), &zelf.inner, e, vm);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -762,16 +762,12 @@ impl SlotFunc {
|
||||
func(&other, &obj, vm) // Swapped: other op obj
|
||||
}
|
||||
Self::NumTernary(func) => {
|
||||
let (y, z): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) =
|
||||
args.bind(vm)?;
|
||||
let z = z.unwrap_or_else(|| vm.ctx.none());
|
||||
let (y, z) = pow_args(args, vm)?;
|
||||
func(&obj, &y, &z, vm)
|
||||
}
|
||||
Self::NumTernaryRight(func) => {
|
||||
let (y, z): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) =
|
||||
args.bind(vm)?;
|
||||
let z = z.unwrap_or_else(|| vm.ctx.none());
|
||||
func(&y, &obj, &z, vm) // Swapped: y ** obj % z
|
||||
let (y, z) = pow_args(args, vm)?;
|
||||
func(&y, &obj, &z, vm)
|
||||
}
|
||||
// Buffer protocol
|
||||
Self::GetBuffer(func) => {
|
||||
@@ -792,6 +788,24 @@ impl SlotFunc {
|
||||
}
|
||||
}
|
||||
|
||||
/// wrap_ternaryfunc / check_pow_args
|
||||
fn pow_args(args: FuncArgs, vm: &VirtualMachine) -> PyResult<(PyObjectRef, PyObjectRef)> {
|
||||
if let Some(err) = args.check_kwargs_empty(vm) {
|
||||
return Err(err);
|
||||
}
|
||||
let size = args.args.len();
|
||||
if !(1..=2).contains(&size) {
|
||||
return Err(vm.new_type_error(format!("expected 1 or 2 arguments, got {size}")));
|
||||
}
|
||||
let y = args.args[0].clone();
|
||||
let z = if size == 2 {
|
||||
args.args[1].clone()
|
||||
} else {
|
||||
vm.ctx.none()
|
||||
};
|
||||
Ok((y, z))
|
||||
}
|
||||
|
||||
/// Parse the `flags` argument of `__buffer__`. wrap_buffer
|
||||
fn parse_buffer_flags(
|
||||
arg: PyObjectRef,
|
||||
@@ -884,7 +898,16 @@ impl PyWrapper {
|
||||
|
||||
#[pygetset]
|
||||
fn __doc__(&self) -> Option<&'static str> {
|
||||
self.doc
|
||||
let doc = self.doc?;
|
||||
type_::get_doc_from_internal_doc(self.name.as_str(), doc)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __text_signature__(&self) -> Option<String> {
|
||||
self.doc.and_then(|doc| {
|
||||
type_::get_text_signature_from_internal_doc(self.name.as_str(), doc)
|
||||
.map(|signature| signature.to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,6 +977,25 @@ impl PyMethodWrapper {
|
||||
self.wrapper.typ.to_owned()
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __qualname__(&self) -> String {
|
||||
format!("{}.{}", self.wrapper.typ.name(), self.wrapper.name)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __doc__(&self) -> Option<&'static str> {
|
||||
let doc = self.wrapper.doc?;
|
||||
type_::get_doc_from_internal_doc(self.wrapper.name.as_str(), doc)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __text_signature__(&self) -> Option<String> {
|
||||
self.wrapper.doc.and_then(|doc| {
|
||||
type_::get_text_signature_from_internal_doc(self.wrapper.name.as_str(), doc)
|
||||
.map(|signature| signature.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn __reduce__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
|
||||
let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
|
||||
@@ -985,10 +1027,14 @@ impl Representable for PyMethodWrapper {
|
||||
}
|
||||
|
||||
impl Hashable for PyMethodWrapper {
|
||||
fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
|
||||
let obj_hash = zelf.obj.hash(vm)?;
|
||||
let wrapper_hash = zelf.wrapper.as_object().get_id() as PyHash;
|
||||
Ok(obj_hash ^ wrapper_hash)
|
||||
fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyHash> {
|
||||
// wrapperobject_hash: pointer hash of descr xor self
|
||||
let mut hash =
|
||||
(zelf.wrapper.as_object().get_id() as PyHash) ^ (zelf.obj.get_id() as PyHash);
|
||||
if hash == -1 {
|
||||
hash = -2;
|
||||
}
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -997,11 +1043,11 @@ impl Comparable for PyMethodWrapper {
|
||||
zelf: &Py<Self>,
|
||||
other: &PyObject,
|
||||
op: PyComparisonOp,
|
||||
vm: &VirtualMachine,
|
||||
_vm: &VirtualMachine,
|
||||
) -> PyResult<crate::function::PyComparisonValue> {
|
||||
op.eq_only(|| {
|
||||
let other = class_or_notimplemented!(Self, other);
|
||||
let eq = zelf.wrapper.is(&other.wrapper) && vm.bool_eq(&zelf.obj, &other.obj)?;
|
||||
let eq = zelf.wrapper.is(&other.wrapper) && zelf.obj.is(&other.obj);
|
||||
Ok(eq.into())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -579,7 +579,9 @@ impl Py<PyDict> {
|
||||
Ok(PyArithmeticValue::Implemented(true))
|
||||
}
|
||||
|
||||
/// Return self[key].
|
||||
#[cfg_attr(feature = "flame-it", flame("PyDictRef"))]
|
||||
#[pymethod(coexist)]
|
||||
fn __getitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
self.inner_getitem(&*key, vm)
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ impl Destructor for PyGenerator {
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(e) = zelf.inner.close(zelf.as_object(), vm) {
|
||||
vm.run_unraisable(e, None, zelf.as_object().to_owned());
|
||||
crate::coroutine::unraisable_while_closing(zelf.as_object(), &zelf.inner, e, vm);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -288,8 +288,10 @@ impl PyList {
|
||||
}
|
||||
}
|
||||
|
||||
fn __getitem__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
self._getitem(&needle, vm)
|
||||
/// Return self[index].
|
||||
#[pymethod(coexist)]
|
||||
fn __getitem__(&self, index: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
self._getitem(&index, vm)
|
||||
}
|
||||
|
||||
fn _setitem(&self, needle: &PyObject, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
|
||||
@@ -135,22 +135,33 @@ impl PyProperty {
|
||||
|
||||
// Access functions
|
||||
|
||||
#[pygetset]
|
||||
fn fget(&self) -> Option<PyObjectRef> {
|
||||
self.getter.read().clone()
|
||||
#[pymember]
|
||||
fn fget(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(vm.unwrap_or_none(zelf.getter.read().clone()))
|
||||
}
|
||||
|
||||
pub(crate) fn get_fget(&self) -> Option<PyObjectRef> {
|
||||
self.getter.read().clone()
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn fset(&self) -> Option<PyObjectRef> {
|
||||
#[pymember]
|
||||
fn fset(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(vm.unwrap_or_none(zelf.setter.read().clone()))
|
||||
}
|
||||
|
||||
fn get_fset(&self) -> Option<PyObjectRef> {
|
||||
self.setter.read().clone()
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn fdel(&self) -> Option<PyObjectRef> {
|
||||
#[pymember]
|
||||
fn fdel(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(vm.unwrap_or_none(zelf.deleter.read().clone()))
|
||||
}
|
||||
|
||||
fn get_fdel(&self) -> Option<PyObjectRef> {
|
||||
self.deleter.read().clone()
|
||||
}
|
||||
|
||||
@@ -215,9 +226,9 @@ impl PyProperty {
|
||||
|
||||
// Create property args with updated values
|
||||
let args = PropertyArgs {
|
||||
fget: new_getter.or_else(|| zelf.fget()),
|
||||
fset: new_setter.or_else(|| zelf.fset()),
|
||||
fdel: new_deleter.or_else(|| zelf.fdel()),
|
||||
fget: new_getter.or_else(|| zelf.get_fget()),
|
||||
fset: new_setter.or_else(|| zelf.get_fset()),
|
||||
fdel: new_deleter.or_else(|| zelf.get_fdel()),
|
||||
doc,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use super::{PyGenericAlias, PyStr, PyType, PyTypeRef};
|
||||
use super::{
|
||||
PyGenericAlias, PyStr, PyType, PyTypeRef,
|
||||
classmethod::{
|
||||
descriptor_get_wrapped_attribute, descriptor_set_wrapped_attribute, functools_wraps,
|
||||
},
|
||||
};
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
class::{PyClassDef, PyClassImpl},
|
||||
@@ -80,10 +85,7 @@ impl Initializer for PyStaticMethod {
|
||||
|
||||
fn init(zelf: PyRef<Self>, callable: Self::Args, vm: &VirtualMachine) -> PyResult<()> {
|
||||
*zelf.callable.lock() = callable.clone();
|
||||
if let Ok(doc) = callable.get_attr("__doc__", vm) {
|
||||
zelf.as_object().set_attr("__doc__", doc, vm)?;
|
||||
}
|
||||
Ok(())
|
||||
functools_wraps(zelf.as_object(), &callable, vm)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,55 +94,68 @@ impl Initializer for PyStaticMethod {
|
||||
flags(BASETYPE, HAS_DICT, HAS_WEAKREF)
|
||||
)]
|
||||
impl PyStaticMethod {
|
||||
#[pygetset]
|
||||
fn __func__(&self) -> PyObjectRef {
|
||||
self.callable.lock().clone()
|
||||
#[pymember]
|
||||
fn __func__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(zelf.callable.lock().clone())
|
||||
}
|
||||
|
||||
#[pymember]
|
||||
fn __wrapped__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
|
||||
let zelf: &Py<Self> = zelf.try_to_value(vm)?;
|
||||
Ok(zelf.callable.lock().clone())
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __wrapped__(&self) -> PyObjectRef {
|
||||
self.callable.lock().clone()
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __module__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__module__", vm)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __qualname__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__qualname__", vm)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __name__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__name__", vm)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __annotations__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__annotations__", vm)
|
||||
fn __annotations__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
|
||||
let callable = zelf.callable.lock().clone();
|
||||
descriptor_get_wrapped_attribute(
|
||||
callable,
|
||||
zelf.as_object(),
|
||||
identifier!(vm.ctx, __annotations__),
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[pygetset(setter)]
|
||||
fn set___annotations__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
|
||||
match value {
|
||||
PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotations__", v, vm),
|
||||
PySetterValue::Delete => Ok(()), // Silently ignore delete like CPython
|
||||
}
|
||||
fn set___annotations__(
|
||||
zelf: &Py<Self>,
|
||||
value: PySetterValue,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
descriptor_set_wrapped_attribute(
|
||||
zelf.as_object(),
|
||||
identifier!(vm.ctx, __annotations__),
|
||||
value,
|
||||
"staticmethod",
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn __annotate__(&self, vm: &VirtualMachine) -> PyResult {
|
||||
self.callable.lock().get_attr("__annotate__", vm)
|
||||
fn __annotate__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult {
|
||||
let callable = zelf.callable.lock().clone();
|
||||
descriptor_get_wrapped_attribute(
|
||||
callable,
|
||||
zelf.as_object(),
|
||||
identifier!(vm.ctx, __annotate__),
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[pygetset(setter)]
|
||||
fn set___annotate__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
|
||||
match value {
|
||||
PySetterValue::Assign(v) => self.callable.lock().set_attr("__annotate__", v, vm),
|
||||
PySetterValue::Delete => Ok(()), // Silently ignore delete like CPython
|
||||
}
|
||||
fn set___annotate__(
|
||||
zelf: &Py<Self>,
|
||||
value: PySetterValue,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
descriptor_set_wrapped_attribute(
|
||||
zelf.as_object(),
|
||||
identifier!(vm.ctx, __annotate__),
|
||||
value,
|
||||
"staticmethod",
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
|
||||
+46
-31
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
|
||||
builtins::{PyCode, PyStrRef, PyTupleRef},
|
||||
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
|
||||
builtins::{PyCode, PyStrRef, PyTraceback, PyTupleRef},
|
||||
common::lock::PyMutex,
|
||||
exceptions::types::PyBaseException,
|
||||
frame::{ExecutionResult, FrameObject, FrameObjectRef, FrameOwner, InterpreterFrame},
|
||||
@@ -8,6 +8,7 @@ use crate::{
|
||||
object::{PyAtomicRef, Traverse, TraverseFn},
|
||||
protocol::PyIterReturn,
|
||||
};
|
||||
use core::sync::atomic::Ordering;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
|
||||
impl ExecutionResult {
|
||||
@@ -86,6 +87,7 @@ fn gen_name(jen: &PyObject, vm: &VirtualMachine) -> &'static str {
|
||||
impl Coro {
|
||||
pub fn new(frame: FrameObjectRef, name: PyStrRef, qualname: PyStrRef) -> Self {
|
||||
let code = frame.iframe().code().to_owned();
|
||||
frame.as_object().mark_cache_published();
|
||||
Self {
|
||||
frame: Some(frame).into(),
|
||||
code,
|
||||
@@ -97,30 +99,21 @@ impl Coro {
|
||||
}
|
||||
}
|
||||
|
||||
/// `_PyFrame_ClearExceptCode`. Clear locals unless another reference
|
||||
/// still holds the frame object; then take_ownership instead.
|
||||
/// `_PyFrame_ClearExceptCode`. Steal the frame slot first, then
|
||||
/// clear locals only if this was the last reference.
|
||||
fn clear_except_code(&self) {
|
||||
let Some(frame) = self.frame.deref() else {
|
||||
return;
|
||||
};
|
||||
if frame.as_object().strong_count() == 1 {
|
||||
frame.clear_locals_and_stack();
|
||||
} else {
|
||||
self.take_ownership();
|
||||
}
|
||||
}
|
||||
|
||||
/// Steal the iframe's `frame_obj` pointer (`take_ownership`). The live
|
||||
/// locals stay on that frame object; this generator no longer roots it.
|
||||
fn take_ownership(&self) {
|
||||
let Some(frame) = (unsafe { self.frame.swap(None) }) else {
|
||||
return;
|
||||
};
|
||||
frame.iframe().owner.store(
|
||||
FrameOwner::FrameObject as i8,
|
||||
core::sync::atomic::Ordering::Release,
|
||||
);
|
||||
frame.clear_generator();
|
||||
if frame.as_object().strong_count() == 1 {
|
||||
frame.clear_locals_and_stack();
|
||||
} else {
|
||||
frame.iframe().owner.store(
|
||||
FrameOwner::FrameObject as i8,
|
||||
core::sync::atomic::Ordering::Release,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Retire the generator if the frame it just ran came to an end. The claim
|
||||
@@ -331,20 +324,18 @@ impl Coro {
|
||||
vm.ctx.none(),
|
||||
)
|
||||
});
|
||||
if !matches!(&result, Ok(ExecutionResult::Yield(_))) {
|
||||
self.closed.store(true);
|
||||
self.clear_except_code();
|
||||
}
|
||||
drop(claim);
|
||||
match result {
|
||||
Ok(ExecutionResult::Yield(_)) => {
|
||||
Err(vm.new_runtime_error(format!("{} ignored GeneratorExit", gen_name(jen, vm))))
|
||||
}
|
||||
other => {
|
||||
self.closed.store(true);
|
||||
self.clear_except_code();
|
||||
match other {
|
||||
Err(e) if !is_gen_exit(&e, vm) => Err(e),
|
||||
Ok(ExecutionResult::Return(value)) => Ok(value),
|
||||
_ => Ok(vm.ctx.none()),
|
||||
}
|
||||
}
|
||||
Err(e) if !is_gen_exit(&e, vm) => Err(e),
|
||||
Ok(ExecutionResult::Return(value)) => Ok(value),
|
||||
_ => Ok(vm.ctx.none()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,7 +358,7 @@ impl Coro {
|
||||
}
|
||||
|
||||
pub fn frame_opt(&self) -> Option<FrameObjectRef> {
|
||||
self.frame.to_owned()
|
||||
self.frame.try_to_owned(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn code(&self) -> PyRef<PyCode> {
|
||||
@@ -494,6 +485,30 @@ pub(crate) fn get_awaitable_iter(obj: PyObjectRef, vm: &VirtualMachine) -> PyRes
|
||||
Err(vm.new_type_error(format!("'{}' object can't be awaited", obj.class().name())))
|
||||
}
|
||||
|
||||
pub(crate) fn unraisable_while_closing(
|
||||
jen: &PyObject,
|
||||
coro: &Coro,
|
||||
e: crate::builtins::PyBaseExceptionRef,
|
||||
vm: &VirtualMachine,
|
||||
) {
|
||||
// Explicit close() leaves the traceback to the caller frame.
|
||||
// Finalize has no caller frame, so attach the generator site here.
|
||||
if e.__traceback__().is_none()
|
||||
&& let Some(frame) = coro.frame_opt()
|
||||
{
|
||||
let lasti = frame.lasti().saturating_mul(2);
|
||||
let lineno = rustpython_compiler_core::OneIndexed::new(frame.f_lineno().max(1))
|
||||
.unwrap_or(rustpython_compiler_core::OneIndexed::MIN);
|
||||
let tb = PyTraceback::new(None, frame, lasti, lineno);
|
||||
e.set_traceback_typed(Some(tb.into_ref(&vm.ctx)));
|
||||
}
|
||||
let msg = jen
|
||||
.repr(vm)
|
||||
.ok()
|
||||
.map(|r| format!("Exception ignored while closing generator {r}"));
|
||||
vm.run_unraisable(e, msg, vm.ctx.none());
|
||||
}
|
||||
|
||||
/// Emit DeprecationWarning for the deprecated 3-argument throw() signature.
|
||||
pub(crate) fn warn_deprecated_throw_signature(
|
||||
exc_val: &OptionalArg,
|
||||
|
||||
+20
-9
@@ -3390,13 +3390,24 @@ impl ExecutingFrame<'_> {
|
||||
};
|
||||
|
||||
if is_gen_exit {
|
||||
// gen_close_iter: close the sub-iterator
|
||||
// gen_close_iter: close the sub-iterator. A failed
|
||||
// lookup of close is unraisable; a failed close()
|
||||
// call is raised into this generator.
|
||||
let close_result = if let Some(coro) = self.builtin_coro(jen) {
|
||||
coro.close(jen, vm).map(|_| ())
|
||||
} else if let Some(close_meth) = vm.get_attribute_opt(jen.to_owned(), "close")? {
|
||||
close_meth.call((), vm).map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
match vm.get_attribute_opt(jen.to_owned(), "close") {
|
||||
Ok(Some(close_meth)) => close_meth.call((), vm).map(|_| ()),
|
||||
Ok(None) => Ok(()),
|
||||
Err(e) => {
|
||||
let msg = jen
|
||||
.repr(vm)
|
||||
.ok()
|
||||
.map(|r| format!("Exception ignored while closing generator {r}"));
|
||||
vm.run_unraisable(e, msg, vm.ctx.none());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Err(err) = close_result {
|
||||
let idx = self.lasti().saturating_sub(1) as usize;
|
||||
@@ -3409,7 +3420,7 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
|
||||
self.push_value(vm.ctx.none());
|
||||
vm.contextualize_exception(&err);
|
||||
vm.chain_stack_item(&err);
|
||||
return match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) {
|
||||
Ok(None) => {
|
||||
self.prev_line.set(0);
|
||||
@@ -3454,7 +3465,7 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
|
||||
self.push_value(vm.ctx.none());
|
||||
vm.contextualize_exception(&err);
|
||||
vm.chain_stack_item(&err);
|
||||
match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) {
|
||||
Ok(None) => {
|
||||
self.prev_line.set(0);
|
||||
@@ -3518,9 +3529,9 @@ impl ExecutingFrame<'_> {
|
||||
}
|
||||
};
|
||||
|
||||
// when raising an exception, set __context__ to the current exception
|
||||
// This is done in _PyErr_SetObject
|
||||
vm.contextualize_exception(&exception);
|
||||
// PyErr_Restore: do not touch __context__. Chain only when this
|
||||
// generator's own exc_info slot is occupied (_PyErr_ChainStackItem).
|
||||
vm.chain_stack_item(&exception);
|
||||
|
||||
// always pushes Py_None before calling gen_send_ex with exc=1
|
||||
// This is needed for exception handler to have correct stack state
|
||||
|
||||
@@ -29,7 +29,7 @@ bitflags::bitflags! {
|
||||
// already filled the entry. When defined, the flag allows a separate
|
||||
// method, "__contains__" for example, to coexist with a defined
|
||||
// slot like sq_contains.
|
||||
// const COEXIST = 0x0040;
|
||||
const COEXIST = 0x0040;
|
||||
|
||||
// if not Py_LIMITED_API
|
||||
const FASTCALL = 0x0080;
|
||||
|
||||
@@ -1951,7 +1951,11 @@ impl PyObject {
|
||||
|
||||
if let Err(e) = slot_del(zelf, vm) {
|
||||
let del_method = zelf.get_class_attr(identifier!(vm, __del__)).unwrap();
|
||||
vm.run_unraisable(e, None, del_method);
|
||||
let msg = del_method
|
||||
.repr(vm)
|
||||
.ok()
|
||||
.map(|r| format!("Exception ignored while calling deallocator {r}"));
|
||||
vm.run_unraisable(e, msg, del_method);
|
||||
}
|
||||
|
||||
// Undo the temporary resurrection. Always remove both
|
||||
@@ -2047,7 +2051,11 @@ impl PyObject {
|
||||
if let Err(e) = slot_del(self, vm)
|
||||
&& let Some(del_method) = self.get_class_attr(identifier!(vm, __del__))
|
||||
{
|
||||
vm.run_unraisable(e, None, del_method);
|
||||
let msg = del_method
|
||||
.repr(vm)
|
||||
.ok()
|
||||
.map(|r| format!("Exception ignored while calling deallocator {r}"));
|
||||
vm.run_unraisable(e, msg, del_method);
|
||||
}
|
||||
});
|
||||
let _ = result;
|
||||
|
||||
@@ -26,6 +26,12 @@ mod errno_mod {
|
||||
#[cfg(any(unix, windows, target_os = "wasi"))]
|
||||
pub use rustpython_host_env::errno::errors;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
|
||||
pub mod errors {
|
||||
pub const ECONNABORTED: i32 = 103;
|
||||
pub const ECONNRESET: i32 = 104;
|
||||
}
|
||||
|
||||
#[cfg(any(unix, windows, target_os = "wasi"))]
|
||||
macro_rules! e {
|
||||
($name:ident) => {
|
||||
|
||||
@@ -1021,149 +1021,149 @@ pub static SLOT_DEFS: &[SlotDef] = &[
|
||||
name: "__buffer__",
|
||||
accessor: SlotAccessor::BfGetBuffer,
|
||||
op: None,
|
||||
doc: "Return a buffer object that exposes the underlying memory of the object.",
|
||||
doc: "__buffer__($self, flags, /)\n--\n\nReturn a buffer object that exposes the underlying memory of the object.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__release_buffer__",
|
||||
accessor: SlotAccessor::BfReleaseBuffer,
|
||||
op: None,
|
||||
doc: "Release the buffer object that exposes the underlying memory of the object.",
|
||||
doc: "__release_buffer__($self, buffer, /)\n--\n\nRelease the buffer object that exposes the underlying memory of the object.",
|
||||
},
|
||||
// Type slots (tp_*)
|
||||
SlotDef {
|
||||
name: "__init__",
|
||||
accessor: SlotAccessor::TpInit,
|
||||
op: None,
|
||||
doc: "Initialize self. See help(type(self)) for accurate signature.",
|
||||
doc: "__init__($self, /, *args, **kwargs)\n--\n\nInitialize self. See help(type(self)) for accurate signature.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__new__",
|
||||
accessor: SlotAccessor::TpNew,
|
||||
op: None,
|
||||
doc: "Create and return a new object. See help(type) for accurate signature.",
|
||||
doc: "__new__($type, /, *args, **kwargs)\n--\n\nCreate and return a new object. See help(type) for accurate signature.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__del__",
|
||||
accessor: SlotAccessor::TpDel,
|
||||
op: None,
|
||||
doc: "Called when the instance is about to be destroyed.",
|
||||
doc: "__del__($self, /)\n--\n\nCalled when the instance is about to be destroyed.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__repr__",
|
||||
accessor: SlotAccessor::TpRepr,
|
||||
op: None,
|
||||
doc: "Return repr(self).",
|
||||
doc: "__repr__($self, /)\n--\n\nReturn repr(self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__str__",
|
||||
accessor: SlotAccessor::TpStr,
|
||||
op: None,
|
||||
doc: "Return str(self).",
|
||||
doc: "__str__($self, /)\n--\n\nReturn str(self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__hash__",
|
||||
accessor: SlotAccessor::TpHash,
|
||||
op: None,
|
||||
doc: "Return hash(self).",
|
||||
doc: "__hash__($self, /)\n--\n\nReturn hash(self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__call__",
|
||||
accessor: SlotAccessor::TpCall,
|
||||
op: None,
|
||||
doc: "Call self as a function.",
|
||||
doc: "__call__($self, /, *args, **kwargs)\n--\n\nCall self as a function.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__iter__",
|
||||
accessor: SlotAccessor::TpIter,
|
||||
op: None,
|
||||
doc: "Implement iter(self).",
|
||||
doc: "__iter__($self, /)\n--\n\nImplement iter(self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__next__",
|
||||
accessor: SlotAccessor::TpIternext,
|
||||
op: None,
|
||||
doc: "Implement next(self).",
|
||||
doc: "__next__($self, /)\n--\n\nImplement next(self).",
|
||||
},
|
||||
// Attribute access
|
||||
SlotDef {
|
||||
name: "__getattribute__",
|
||||
accessor: SlotAccessor::TpGetattro,
|
||||
op: None,
|
||||
doc: "Return getattr(self, name).",
|
||||
doc: "__getattribute__($self, name, /)\n--\n\nReturn getattr(self, name).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__getattr__",
|
||||
accessor: SlotAccessor::TpGetattro,
|
||||
op: None,
|
||||
doc: "Implement getattr(self, name).",
|
||||
doc: "__getattr__($self, name, /)\n--\n\nImplement getattr(self, name).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__setattr__",
|
||||
accessor: SlotAccessor::TpSetattro,
|
||||
op: None,
|
||||
doc: "Implement setattr(self, name, value).",
|
||||
doc: "__setattr__($self, name, value, /)\n--\n\nImplement setattr(self, name, value).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__delattr__",
|
||||
accessor: SlotAccessor::TpSetattro,
|
||||
op: Some(SlotOp::Delete),
|
||||
doc: "Implement delattr(self, name).",
|
||||
doc: "__delattr__($self, name, /)\n--\n\nImplement delattr(self, name).",
|
||||
},
|
||||
// Rich comparison - all map to TpRichcompare with different op
|
||||
SlotDef {
|
||||
name: "__eq__",
|
||||
accessor: SlotAccessor::TpRichcompare,
|
||||
op: Some(SlotOp::Eq),
|
||||
doc: "Return self==value.",
|
||||
doc: "__eq__($self, value, /)\n--\n\nReturn self==value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__ne__",
|
||||
accessor: SlotAccessor::TpRichcompare,
|
||||
op: Some(SlotOp::Ne),
|
||||
doc: "Return self!=value.",
|
||||
doc: "__ne__($self, value, /)\n--\n\nReturn self!=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__lt__",
|
||||
accessor: SlotAccessor::TpRichcompare,
|
||||
op: Some(SlotOp::Lt),
|
||||
doc: "Return self<value.",
|
||||
doc: "__lt__($self, value, /)\n--\n\nReturn self<value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__le__",
|
||||
accessor: SlotAccessor::TpRichcompare,
|
||||
op: Some(SlotOp::Le),
|
||||
doc: "Return self<=value.",
|
||||
doc: "__le__($self, value, /)\n--\n\nReturn self<=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__gt__",
|
||||
accessor: SlotAccessor::TpRichcompare,
|
||||
op: Some(SlotOp::Gt),
|
||||
doc: "Return self>value.",
|
||||
doc: "__gt__($self, value, /)\n--\n\nReturn self>value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__ge__",
|
||||
accessor: SlotAccessor::TpRichcompare,
|
||||
op: Some(SlotOp::Ge),
|
||||
doc: "Return self>=value.",
|
||||
doc: "__ge__($self, value, /)\n--\n\nReturn self>=value.",
|
||||
},
|
||||
// Descriptor protocol
|
||||
SlotDef {
|
||||
name: "__get__",
|
||||
accessor: SlotAccessor::TpDescrGet,
|
||||
op: None,
|
||||
doc: "Return an attribute of instance, which is of type owner.",
|
||||
doc: "__get__($self, instance, owner=None, /)\n--\n\nReturn an attribute of instance, which is of type owner.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__set__",
|
||||
accessor: SlotAccessor::TpDescrSet,
|
||||
op: None,
|
||||
doc: "Set an attribute of instance to value.",
|
||||
doc: "__set__($self, instance, value, /)\n--\n\nSet an attribute of instance to value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__delete__",
|
||||
accessor: SlotAccessor::TpDescrSet,
|
||||
op: Some(SlotOp::Delete),
|
||||
doc: "Delete an attribute of instance.",
|
||||
doc: "__delete__($self, instance, /)\n--\n\nDelete an attribute of instance.",
|
||||
},
|
||||
// Mapping protocol (mp_*) - must come before Sequence protocol
|
||||
// so that mp_subscript wins over sq_item for __getitem__
|
||||
@@ -1172,383 +1172,383 @@ pub static SLOT_DEFS: &[SlotDef] = &[
|
||||
name: "__len__",
|
||||
accessor: SlotAccessor::MpLength,
|
||||
op: None,
|
||||
doc: "Return len(self).",
|
||||
doc: "__len__($self, /)\n--\n\nReturn len(self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__getitem__",
|
||||
accessor: SlotAccessor::MpSubscript,
|
||||
op: None,
|
||||
doc: "Return self[key].",
|
||||
doc: "__getitem__($self, key, /)\n--\n\nReturn self[key].",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__setitem__",
|
||||
accessor: SlotAccessor::MpAssSubscript,
|
||||
op: None,
|
||||
doc: "Set self[key] to value.",
|
||||
doc: "__setitem__($self, key, value, /)\n--\n\nSet self[key] to value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__delitem__",
|
||||
accessor: SlotAccessor::MpAssSubscript,
|
||||
op: Some(SlotOp::Delete),
|
||||
doc: "Delete self[key].",
|
||||
doc: "__delitem__($self, key, /)\n--\n\nDelete self[key].",
|
||||
},
|
||||
// Sequence protocol (sq_*)
|
||||
SlotDef {
|
||||
name: "__len__",
|
||||
accessor: SlotAccessor::SqLength,
|
||||
op: None,
|
||||
doc: "Return len(self).",
|
||||
doc: "__len__($self, /)\n--\n\nReturn len(self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__getitem__",
|
||||
accessor: SlotAccessor::SqItem,
|
||||
op: None,
|
||||
doc: "Return self[key].",
|
||||
doc: "__getitem__($self, key, /)\n--\n\nReturn self[key].",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__setitem__",
|
||||
accessor: SlotAccessor::SqAssItem,
|
||||
op: None,
|
||||
doc: "Set self[key] to value.",
|
||||
doc: "__setitem__($self, key, value, /)\n--\n\nSet self[key] to value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__delitem__",
|
||||
accessor: SlotAccessor::SqAssItem,
|
||||
op: Some(SlotOp::Delete),
|
||||
doc: "Delete self[key].",
|
||||
doc: "__delitem__($self, key, /)\n--\n\nDelete self[key].",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__contains__",
|
||||
accessor: SlotAccessor::SqContains,
|
||||
op: None,
|
||||
doc: "Return key in self.",
|
||||
doc: "__contains__($self, key, /)\n--\n\nReturn key in self.",
|
||||
},
|
||||
// Number protocol - binary ops with left/right variants
|
||||
SlotDef {
|
||||
name: "__add__",
|
||||
accessor: SlotAccessor::NbAdd,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self+value.",
|
||||
doc: "__add__($self, value, /)\n--\n\nReturn self+value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__radd__",
|
||||
accessor: SlotAccessor::NbAdd,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value+self.",
|
||||
doc: "__radd__($self, value, /)\n--\n\nReturn value+self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__iadd__",
|
||||
accessor: SlotAccessor::NbInplaceAdd,
|
||||
op: None,
|
||||
doc: "Implement self+=value.",
|
||||
doc: "__iadd__($self, value, /)\n--\n\nImplement self+=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__sub__",
|
||||
accessor: SlotAccessor::NbSubtract,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self-value.",
|
||||
doc: "__sub__($self, value, /)\n--\n\nReturn self-value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rsub__",
|
||||
accessor: SlotAccessor::NbSubtract,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value-self.",
|
||||
doc: "__rsub__($self, value, /)\n--\n\nReturn value-self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__isub__",
|
||||
accessor: SlotAccessor::NbInplaceSubtract,
|
||||
op: None,
|
||||
doc: "Implement self-=value.",
|
||||
doc: "__isub__($self, value, /)\n--\n\nImplement self-=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__mul__",
|
||||
accessor: SlotAccessor::NbMultiply,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self*value.",
|
||||
doc: "__mul__($self, value, /)\n--\n\nReturn self*value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rmul__",
|
||||
accessor: SlotAccessor::NbMultiply,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value*self.",
|
||||
doc: "__rmul__($self, value, /)\n--\n\nReturn value*self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__imul__",
|
||||
accessor: SlotAccessor::NbInplaceMultiply,
|
||||
op: None,
|
||||
doc: "Implement self*=value.",
|
||||
doc: "__imul__($self, value, /)\n--\n\nImplement self*=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__mod__",
|
||||
accessor: SlotAccessor::NbRemainder,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self%value.",
|
||||
doc: "__mod__($self, value, /)\n--\n\nReturn self%value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rmod__",
|
||||
accessor: SlotAccessor::NbRemainder,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value%self.",
|
||||
doc: "__rmod__($self, value, /)\n--\n\nReturn value%self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__imod__",
|
||||
accessor: SlotAccessor::NbInplaceRemainder,
|
||||
op: None,
|
||||
doc: "Implement self%=value.",
|
||||
doc: "__imod__($self, value, /)\n--\n\nImplement self%=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__divmod__",
|
||||
accessor: SlotAccessor::NbDivmod,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return divmod(self, value).",
|
||||
doc: "__divmod__($self, value, /)\n--\n\nReturn divmod(self, value).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rdivmod__",
|
||||
accessor: SlotAccessor::NbDivmod,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return divmod(value, self).",
|
||||
doc: "__rdivmod__($self, value, /)\n--\n\nReturn divmod(value, self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__pow__",
|
||||
accessor: SlotAccessor::NbPower,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return pow(self, value, mod).",
|
||||
doc: "__pow__($self, value, mod=None, /)\n--\n\nReturn pow(self, value, mod).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rpow__",
|
||||
accessor: SlotAccessor::NbPower,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return pow(value, self, mod).",
|
||||
doc: "__rpow__($self, value, mod=None, /)\n--\n\nReturn pow(value, self, mod).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__ipow__",
|
||||
accessor: SlotAccessor::NbInplacePower,
|
||||
op: None,
|
||||
doc: "Implement self**=value.",
|
||||
doc: "__ipow__($self, value, /)\n--\n\nImplement self**=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__lshift__",
|
||||
accessor: SlotAccessor::NbLshift,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self<<value.",
|
||||
doc: "__lshift__($self, value, /)\n--\n\nReturn self<<value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rlshift__",
|
||||
accessor: SlotAccessor::NbLshift,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value<<self.",
|
||||
doc: "__rlshift__($self, value, /)\n--\n\nReturn value<<self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__ilshift__",
|
||||
accessor: SlotAccessor::NbInplaceLshift,
|
||||
op: None,
|
||||
doc: "Implement self<<=value.",
|
||||
doc: "__ilshift__($self, value, /)\n--\n\nImplement self<<=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rshift__",
|
||||
accessor: SlotAccessor::NbRshift,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self>>value.",
|
||||
doc: "__rshift__($self, value, /)\n--\n\nReturn self>>value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rrshift__",
|
||||
accessor: SlotAccessor::NbRshift,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value>>self.",
|
||||
doc: "__rrshift__($self, value, /)\n--\n\nReturn value>>self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__irshift__",
|
||||
accessor: SlotAccessor::NbInplaceRshift,
|
||||
op: None,
|
||||
doc: "Implement self>>=value.",
|
||||
doc: "__irshift__($self, value, /)\n--\n\nImplement self>>=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__and__",
|
||||
accessor: SlotAccessor::NbAnd,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self&value.",
|
||||
doc: "__and__($self, value, /)\n--\n\nReturn self&value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rand__",
|
||||
accessor: SlotAccessor::NbAnd,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value&self.",
|
||||
doc: "__rand__($self, value, /)\n--\n\nReturn value&self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__iand__",
|
||||
accessor: SlotAccessor::NbInplaceAnd,
|
||||
op: None,
|
||||
doc: "Implement self&=value.",
|
||||
doc: "__iand__($self, value, /)\n--\n\nImplement self&=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__xor__",
|
||||
accessor: SlotAccessor::NbXor,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self^value.",
|
||||
doc: "__xor__($self, value, /)\n--\n\nReturn self^value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rxor__",
|
||||
accessor: SlotAccessor::NbXor,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value^self.",
|
||||
doc: "__rxor__($self, value, /)\n--\n\nReturn value^self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__ixor__",
|
||||
accessor: SlotAccessor::NbInplaceXor,
|
||||
op: None,
|
||||
doc: "Implement self^=value.",
|
||||
doc: "__ixor__($self, value, /)\n--\n\nImplement self^=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__or__",
|
||||
accessor: SlotAccessor::NbOr,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self|value.",
|
||||
doc: "__or__($self, value, /)\n--\n\nReturn self|value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__ror__",
|
||||
accessor: SlotAccessor::NbOr,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value|self.",
|
||||
doc: "__ror__($self, value, /)\n--\n\nReturn value|self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__ior__",
|
||||
accessor: SlotAccessor::NbInplaceOr,
|
||||
op: None,
|
||||
doc: "Implement self|=value.",
|
||||
doc: "__ior__($self, value, /)\n--\n\nImplement self|=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__floordiv__",
|
||||
accessor: SlotAccessor::NbFloorDivide,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self//value.",
|
||||
doc: "__floordiv__($self, value, /)\n--\n\nReturn self//value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rfloordiv__",
|
||||
accessor: SlotAccessor::NbFloorDivide,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value//self.",
|
||||
doc: "__rfloordiv__($self, value, /)\n--\n\nReturn value//self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__ifloordiv__",
|
||||
accessor: SlotAccessor::NbInplaceFloorDivide,
|
||||
op: None,
|
||||
doc: "Implement self//=value.",
|
||||
doc: "__ifloordiv__($self, value, /)\n--\n\nImplement self//=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__truediv__",
|
||||
accessor: SlotAccessor::NbTrueDivide,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self/value.",
|
||||
doc: "__truediv__($self, value, /)\n--\n\nReturn self/value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rtruediv__",
|
||||
accessor: SlotAccessor::NbTrueDivide,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value/self.",
|
||||
doc: "__rtruediv__($self, value, /)\n--\n\nReturn value/self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__itruediv__",
|
||||
accessor: SlotAccessor::NbInplaceTrueDivide,
|
||||
op: None,
|
||||
doc: "Implement self/=value.",
|
||||
doc: "__itruediv__($self, value, /)\n--\n\nImplement self/=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__matmul__",
|
||||
accessor: SlotAccessor::NbMatrixMultiply,
|
||||
op: Some(SlotOp::Left),
|
||||
doc: "Return self@value.",
|
||||
doc: "__matmul__($self, value, /)\n--\n\nReturn self@value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rmatmul__",
|
||||
accessor: SlotAccessor::NbMatrixMultiply,
|
||||
op: Some(SlotOp::Right),
|
||||
doc: "Return value@self.",
|
||||
doc: "__rmatmul__($self, value, /)\n--\n\nReturn value@self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__imatmul__",
|
||||
accessor: SlotAccessor::NbInplaceMatrixMultiply,
|
||||
op: None,
|
||||
doc: "Implement self@=value.",
|
||||
doc: "__imatmul__($self, value, /)\n--\n\nImplement self@=value.",
|
||||
},
|
||||
// Number unary operations
|
||||
SlotDef {
|
||||
name: "__neg__",
|
||||
accessor: SlotAccessor::NbNegative,
|
||||
op: None,
|
||||
doc: "Return -self.",
|
||||
doc: "__neg__($self, /)\n--\n\nReturn -self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__pos__",
|
||||
accessor: SlotAccessor::NbPositive,
|
||||
op: None,
|
||||
doc: "Return +self.",
|
||||
doc: "__pos__($self, /)\n--\n\nReturn +self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__abs__",
|
||||
accessor: SlotAccessor::NbAbsolute,
|
||||
op: None,
|
||||
doc: "Return abs(self).",
|
||||
doc: "__abs__($self, /)\n--\n\nReturn abs(self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__invert__",
|
||||
accessor: SlotAccessor::NbInvert,
|
||||
op: None,
|
||||
doc: "Return ~self.",
|
||||
doc: "__invert__($self, /)\n--\n\nReturn ~self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__bool__",
|
||||
accessor: SlotAccessor::NbBool,
|
||||
op: None,
|
||||
doc: "Return self != 0.",
|
||||
doc: "__bool__($self, /)\n--\n\nTrue if self else False",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__int__",
|
||||
accessor: SlotAccessor::NbInt,
|
||||
op: None,
|
||||
doc: "Return int(self).",
|
||||
doc: "__int__($self, /)\n--\n\nReturn int(self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__float__",
|
||||
accessor: SlotAccessor::NbFloat,
|
||||
op: None,
|
||||
doc: "Return float(self).",
|
||||
doc: "__float__($self, /)\n--\n\nReturn float(self).",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__index__",
|
||||
accessor: SlotAccessor::NbIndex,
|
||||
op: None,
|
||||
doc: "Return self converted to an integer, if self is suitable for use as an index into a list.",
|
||||
doc: "__index__($self, /)\n--\n\nReturn self converted to an integer, if self is suitable for use as an index into a list.",
|
||||
},
|
||||
// Sequence inplace operations (also map to number slots for some types)
|
||||
SlotDef {
|
||||
name: "__add__",
|
||||
accessor: SlotAccessor::SqConcat,
|
||||
op: None,
|
||||
doc: "Return self+value.",
|
||||
doc: "__add__($self, value, /)\n--\n\nReturn self+value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__mul__",
|
||||
accessor: SlotAccessor::SqRepeat,
|
||||
op: None,
|
||||
doc: "Return self*value.",
|
||||
doc: "__mul__($self, value, /)\n--\n\nReturn self*value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__rmul__",
|
||||
accessor: SlotAccessor::SqRepeat,
|
||||
op: None,
|
||||
doc: "Return value*self.",
|
||||
doc: "__rmul__($self, value, /)\n--\n\nReturn value*self.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__iadd__",
|
||||
accessor: SlotAccessor::SqInplaceConcat,
|
||||
op: None,
|
||||
doc: "Implement self+=value.",
|
||||
doc: "__iadd__($self, value, /)\n--\n\nImplement self+=value.",
|
||||
},
|
||||
SlotDef {
|
||||
name: "__imul__",
|
||||
accessor: SlotAccessor::SqInplaceRepeat,
|
||||
op: None,
|
||||
doc: "Implement self*=value.",
|
||||
doc: "__imul__($self, value, /)\n--\n\nImplement self*=value.",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -628,13 +628,14 @@ impl Context {
|
||||
getter: fn(&VirtualMachine, PyObjectRef) -> PyResult,
|
||||
setter: MemberSetterFunc,
|
||||
class: &'static Py<PyType>,
|
||||
doc: Option<&str>,
|
||||
) -> PyRef<PyMemberDescriptor> {
|
||||
let member_def = PyMemberDef {
|
||||
name: name.to_owned(),
|
||||
kind: member_kind,
|
||||
getter: MemberGetter::Getter(getter),
|
||||
setter: MemberSetter::Setter(setter),
|
||||
doc: None,
|
||||
doc: doc.map(str::to_owned),
|
||||
};
|
||||
let member_descriptor = PyMemberDescriptor {
|
||||
common: PyDescriptorOwned {
|
||||
|
||||
@@ -3123,6 +3123,15 @@ impl VirtualMachine {
|
||||
}
|
||||
}
|
||||
|
||||
/// `_PyErr_ChainStackItem`: if the current `exc_info` slot is occupied,
|
||||
/// set that handled exception as `__context__` of `exception`. A vacant
|
||||
/// current slot must not walk to an outer frame's exception.
|
||||
pub(crate) fn chain_stack_item(&self, exception: &Py<PyBaseException>) {
|
||||
if self.current_exception().is_some() {
|
||||
self.contextualize_exception(exception);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn contextualize_exception(&self, exception: &Py<PyBaseException>) {
|
||||
if let Some(context_exc) = self.topmost_exception()
|
||||
&& !context_exc.is(exception)
|
||||
|
||||
@@ -42,6 +42,14 @@ web-sys = { workspace = true, features = [
|
||||
"Response"
|
||||
] }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))'.dependencies]
|
||||
getrandom = { workspace = true, features = ["wasm_js"] }
|
||||
getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] }
|
||||
rustls = { workspace = true, default-features = false, features = ["std", "tls12", "custom-provider"] }
|
||||
rustls-pki-types = { version = "1", default-features = false, features = ["alloc", "std", "web"] }
|
||||
rustls-rustcrypto = "0.0.2-alpha"
|
||||
rustpython-host_env = { workspace = true, features = ["ssl"] }
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false#["-O1"]
|
||||
|
||||
@@ -49,4 +57,4 @@ wasm-opt = false#["-O1"]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
ignored = ["serde"]
|
||||
ignored = ["serde", "getrandom", "rustls-pki-types"]
|
||||
@@ -3,6 +3,10 @@ extern crate alloc;
|
||||
pub mod browser_module;
|
||||
pub mod convert;
|
||||
pub mod js_module;
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
|
||||
mod socket;
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
|
||||
mod ssl;
|
||||
pub mod vm_class;
|
||||
pub mod wasm_builtins;
|
||||
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
pub(crate) use _socket::module_def;
|
||||
|
||||
#[pymodule]
|
||||
mod _socket {
|
||||
use rustpython_vm::{
|
||||
Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
|
||||
builtins::{PyType, PyTypeRef},
|
||||
common::lock::PyMutex,
|
||||
function::{OptionalArg, OptionalOption},
|
||||
types::{Constructor, Initializer},
|
||||
};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
|
||||
#[pyattr]
|
||||
const AF_UNSPEC: i32 = 0;
|
||||
#[pyattr]
|
||||
const AF_UNIX: i32 = 1;
|
||||
#[pyattr]
|
||||
const AF_INET: i32 = 2;
|
||||
#[pyattr]
|
||||
const AF_INET6: i32 = 10;
|
||||
|
||||
#[pyattr]
|
||||
const SOCK_STREAM: i32 = 1;
|
||||
#[pyattr]
|
||||
const SOCK_DGRAM: i32 = 2;
|
||||
#[pyattr]
|
||||
const SOCK_RAW: i32 = 3;
|
||||
|
||||
#[pyattr]
|
||||
const SOL_SOCKET: i32 = 1;
|
||||
#[pyattr]
|
||||
const SO_REUSEADDR: i32 = 2;
|
||||
#[pyattr]
|
||||
const SO_TYPE: i32 = 3;
|
||||
#[pyattr]
|
||||
const SO_ERROR: i32 = 4;
|
||||
#[pyattr]
|
||||
const SO_BROADCAST: i32 = 6;
|
||||
#[pyattr]
|
||||
const SO_KEEPALIVE: i32 = 9;
|
||||
#[pyattr]
|
||||
const SO_RCVBUF: i32 = 8;
|
||||
#[pyattr]
|
||||
const SO_SNDBUF: i32 = 7;
|
||||
|
||||
#[pyattr]
|
||||
const IPPROTO_IP: i32 = 0;
|
||||
#[pyattr]
|
||||
const IPPROTO_TCP: i32 = 6;
|
||||
#[pyattr]
|
||||
const IPPROTO_UDP: i32 = 17;
|
||||
#[pyattr]
|
||||
const IPPROTO_IPV6: i32 = 41;
|
||||
#[pyattr]
|
||||
const SOL_TCP: i32 = IPPROTO_TCP;
|
||||
|
||||
#[pyattr]
|
||||
const SHUT_RD: i32 = 0;
|
||||
#[pyattr]
|
||||
const SHUT_WR: i32 = 1;
|
||||
#[pyattr]
|
||||
const SHUT_RDWR: i32 = 2;
|
||||
|
||||
#[pyattr]
|
||||
const AI_PASSIVE: i32 = 1;
|
||||
#[pyattr]
|
||||
const AI_CANONNAME: i32 = 2;
|
||||
#[pyattr]
|
||||
const AI_NUMERICHOST: i32 = 4;
|
||||
#[pyattr]
|
||||
const AI_NUMERICSERV: i32 = 8;
|
||||
#[pyattr]
|
||||
const AI_ADDRCONFIG: i32 = 32;
|
||||
|
||||
#[pyattr]
|
||||
const NI_NUMERICHOST: i32 = 1;
|
||||
#[pyattr]
|
||||
const NI_NUMERICSERV: i32 = 2;
|
||||
#[pyattr]
|
||||
const NI_NOFQDN: i32 = 4;
|
||||
#[pyattr]
|
||||
const NI_NAMEREQD: i32 = 8;
|
||||
#[pyattr]
|
||||
const NI_DGRAM: i32 = 16;
|
||||
|
||||
#[pyattr]
|
||||
const INADDR_ANY: u32 = 0;
|
||||
#[pyattr]
|
||||
const INADDR_LOOPBACK: u32 = 0x7f00_0001;
|
||||
#[pyattr]
|
||||
const INADDR_BROADCAST: u32 = 0xffff_ffff;
|
||||
#[pyattr]
|
||||
const INADDR_NONE: u32 = 0xffff_ffff;
|
||||
#[pyattr]
|
||||
const IPPORT_RESERVED: i32 = 1024;
|
||||
#[pyattr]
|
||||
const IPPORT_USERRESERVED: i32 = 5000;
|
||||
#[pyattr]
|
||||
const TCP_NODELAY: i32 = 1;
|
||||
#[pyattr(name = "has_ipv6")]
|
||||
const HAS_IPV6: bool = true;
|
||||
|
||||
static DEFAULT_TIMEOUT: AtomicI32 = AtomicI32::new(-1);
|
||||
|
||||
fn unsupported(
|
||||
vm: &VirtualMachine,
|
||||
op: &str,
|
||||
) -> PyRef<rustpython_vm::builtins::PyBaseException> {
|
||||
vm.new_os_error(format!("{op} is not available"))
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
fn error(vm: &VirtualMachine) -> PyTypeRef {
|
||||
vm.ctx.exceptions.os_error.to_owned()
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
fn timeout(vm: &VirtualMachine) -> PyTypeRef {
|
||||
vm.ctx.exceptions.timeout_error.to_owned()
|
||||
}
|
||||
|
||||
#[pyattr(once)]
|
||||
fn herror(vm: &VirtualMachine) -> PyTypeRef {
|
||||
vm.ctx.new_exception_type(
|
||||
"socket",
|
||||
"herror",
|
||||
Some(vec![vm.ctx.exceptions.os_error.to_owned()]),
|
||||
)
|
||||
}
|
||||
|
||||
#[pyattr(once)]
|
||||
fn gaierror(vm: &VirtualMachine) -> PyTypeRef {
|
||||
vm.ctx.new_exception_type(
|
||||
"socket",
|
||||
"gaierror",
|
||||
Some(vec![vm.ctx.exceptions.os_error.to_owned()]),
|
||||
)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
const fn htonl(x: u32) -> u32 {
|
||||
u32::to_be(x)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
const fn htons(x: u16) -> u16 {
|
||||
u16::to_be(x)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
const fn ntohl(x: u32) -> u32 {
|
||||
u32::from_be(x)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
const fn ntohs(x: u16) -> u16 {
|
||||
u16::from_be(x)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn inet_aton(
|
||||
ip: rustpython_vm::builtins::PyUtf8StrRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Vec<u8>> {
|
||||
ip.as_str()
|
||||
.parse::<Ipv4Addr>()
|
||||
.map(|addr| addr.octets().to_vec())
|
||||
.map_err(|_| vm.new_os_error("illegal IP address string passed to inet_aton"))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn inet_ntoa(
|
||||
packed: rustpython_vm::function::ArgBytesLike,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<String> {
|
||||
let buf = packed.borrow_buf();
|
||||
let octets: [u8; 4] = (&*buf)
|
||||
.try_into()
|
||||
.map_err(|_| vm.new_os_error("packed IP wrong length for inet_ntoa"))?;
|
||||
Ok(Ipv4Addr::from(octets).to_string())
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn inet_pton(
|
||||
af: i32,
|
||||
ip: rustpython_vm::builtins::PyUtf8StrRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Vec<u8>> {
|
||||
match af {
|
||||
AF_INET => ip
|
||||
.as_str()
|
||||
.parse::<Ipv4Addr>()
|
||||
.map(|addr| addr.octets().to_vec())
|
||||
.map_err(|_| vm.new_os_error("illegal IP address string passed to inet_pton")),
|
||||
AF_INET6 => ip
|
||||
.as_str()
|
||||
.parse::<Ipv6Addr>()
|
||||
.map(|addr| addr.octets().to_vec())
|
||||
.map_err(|_| vm.new_os_error("illegal IP address string passed to inet_pton")),
|
||||
_ => Err(vm.new_os_error("Address family not supported")),
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn inet_ntop(
|
||||
af: i32,
|
||||
packed: rustpython_vm::function::ArgBytesLike,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<String> {
|
||||
let buf = packed.borrow_buf();
|
||||
match af {
|
||||
AF_INET => {
|
||||
let octets: [u8; 4] = (&*buf).try_into().map_err(|_| {
|
||||
vm.new_value_error("invalid length of packed IP address string")
|
||||
})?;
|
||||
Ok(Ipv4Addr::from(octets).to_string())
|
||||
}
|
||||
AF_INET6 => {
|
||||
let octets: [u8; 16] = (&*buf).try_into().map_err(|_| {
|
||||
vm.new_value_error("invalid length of packed IP address string")
|
||||
})?;
|
||||
Ok(Ipv6Addr::from(octets).to_string())
|
||||
}
|
||||
_ => Err(vm.new_value_error("unknown address family")),
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn getdefaulttimeout() -> Option<f64> {
|
||||
let timeout = DEFAULT_TIMEOUT.load(Ordering::Relaxed);
|
||||
(timeout >= 0).then_some(f64::from(timeout))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn setdefaulttimeout(timeout: OptionalOption<f64>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
match timeout.flatten() {
|
||||
None => DEFAULT_TIMEOUT.store(-1, Ordering::Relaxed),
|
||||
Some(value) if value >= 0.0 => {
|
||||
DEFAULT_TIMEOUT.store(value as i32, Ordering::Relaxed);
|
||||
}
|
||||
Some(_) => return Err(vm.new_value_error("Timeout value out of range")),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct SocketInitArgs {
|
||||
#[pyarg(any, optional)]
|
||||
family: OptionalArg<i32>,
|
||||
#[pyarg(any, optional)]
|
||||
r#type: OptionalArg<i32>,
|
||||
#[pyarg(any, optional)]
|
||||
proto: OptionalArg<i32>,
|
||||
#[pyarg(any, optional)]
|
||||
fileno: OptionalArg<Option<PyObjectRef>>,
|
||||
}
|
||||
|
||||
#[pyattr(name = "socket")]
|
||||
#[pyattr(name = "SocketType")]
|
||||
#[pyclass(name = "socket")]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct PySocket {
|
||||
family: AtomicI32,
|
||||
kind: AtomicI32,
|
||||
proto: AtomicI32,
|
||||
timeout: PyMutex<Option<f64>>,
|
||||
closed: PyMutex<bool>,
|
||||
}
|
||||
|
||||
impl Default for PySocket {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
family: AtomicI32::new(AF_INET),
|
||||
kind: AtomicI32::new(SOCK_STREAM),
|
||||
proto: AtomicI32::new(0),
|
||||
timeout: PyMutex::new(None),
|
||||
closed: PyMutex::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(with(Constructor, Initializer), flags(BASETYPE))]
|
||||
impl PySocket {
|
||||
fn ensure_open(&self, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if *self.closed.lock() {
|
||||
return Err(vm.new_os_error("Bad file descriptor"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn family(&self) -> i32 {
|
||||
self.family.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn r#type(&self) -> i32 {
|
||||
self.kind.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
fn proto(&self) -> i32 {
|
||||
self.proto.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn fileno(&self) -> i32 {
|
||||
if *self.closed.lock() { -1 } else { 0 }
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn close(&self) {
|
||||
*self.closed.lock() = true;
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn detach(&self) -> i32 {
|
||||
*self.closed.lock() = true;
|
||||
-1
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn gettimeout(&self) -> Option<f64> {
|
||||
*self.timeout.lock()
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn settimeout(&self, timeout: OptionalOption<f64>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
*self.timeout.lock() = match timeout.flatten() {
|
||||
None => None,
|
||||
Some(value) if value >= 0.0 => Some(value),
|
||||
Some(_) => return Err(vm.new_value_error("Timeout value out of range")),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn setblocking(&self, blocking: bool) {
|
||||
*self.timeout.lock() = if blocking { None } else { Some(0.0) };
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn getblocking(&self) -> bool {
|
||||
!matches!(*self.timeout.lock(), Some(t) if t == 0.0)
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn getsockopt(&self, level: i32, optname: i32, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
self.ensure_open(vm)?;
|
||||
if level == SOL_SOCKET && optname == SO_TYPE {
|
||||
return Ok(self.kind.load(Ordering::Relaxed));
|
||||
}
|
||||
if level == SOL_SOCKET && optname == SO_ERROR {
|
||||
return Ok(0);
|
||||
}
|
||||
Err(unsupported(vm, "getsockopt").into())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn setsockopt(
|
||||
&self,
|
||||
_level: i32,
|
||||
_optname: i32,
|
||||
_value: OptionalArg<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
self.ensure_open(vm)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn bind(&self, _address: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
self.ensure_open(vm)?;
|
||||
Err(unsupported(vm, "bind").into())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn connect(&self, _address: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
self.ensure_open(vm)?;
|
||||
Err(unsupported(vm, "connect").into())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn listen(&self, _backlog: OptionalArg<i32>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
self.ensure_open(vm)?;
|
||||
Err(unsupported(vm, "listen").into())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn accept(&self, vm: &VirtualMachine) -> PyResult<(PyObjectRef, PyObjectRef)> {
|
||||
self.ensure_open(vm)?;
|
||||
Err(unsupported(vm, "accept").into())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn send(
|
||||
&self,
|
||||
_data: rustpython_vm::function::ArgBytesLike,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<usize> {
|
||||
self.ensure_open(vm)?;
|
||||
Err(unsupported(vm, "send").into())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn recv(&self, _bufsize: i32, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
|
||||
self.ensure_open(vm)?;
|
||||
Err(unsupported(vm, "recv").into())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn shutdown(&self, _how: i32, vm: &VirtualMachine) -> PyResult<()> {
|
||||
self.ensure_open(vm)?;
|
||||
Err(unsupported(vm, "shutdown").into())
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn getsockname(&self, vm: &VirtualMachine) -> PyResult<(String, i32)> {
|
||||
self.ensure_open(vm)?;
|
||||
Ok(("0.0.0.0".to_owned(), 0))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn getpeername(&self, vm: &VirtualMachine) -> PyResult<(String, i32)> {
|
||||
self.ensure_open(vm)?;
|
||||
Err(unsupported(vm, "getpeername").into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Constructor for PySocket {
|
||||
type Args = ();
|
||||
|
||||
fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Initializer for PySocket {
|
||||
type Args = SocketInitArgs;
|
||||
|
||||
fn init(zelf: PyRef<Self>, args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
|
||||
let family = args.family.unwrap_or(AF_INET);
|
||||
let kind = args.r#type.unwrap_or(SOCK_STREAM);
|
||||
let proto = args.proto.unwrap_or(0);
|
||||
let _ = args.fileno;
|
||||
zelf.family.store(family, Ordering::Relaxed);
|
||||
zelf.kind.store(kind, Ordering::Relaxed);
|
||||
zelf.proto.store(proto, Ordering::Relaxed);
|
||||
*zelf.closed.lock() = false;
|
||||
let default = DEFAULT_TIMEOUT.load(Ordering::Relaxed);
|
||||
*zelf.timeout.lock() = (default >= 0).then_some(f64::from(default));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -86,10 +86,20 @@ impl StoredVirtualMachine {
|
||||
.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB);
|
||||
}
|
||||
|
||||
// Add wasm-specific modules
|
||||
// Add wasm-specific modules. Browser `_ssl` / `_socket` live here
|
||||
// rather than rustpython-stdlib: there are no BSD sockets, and rustls
|
||||
// is driven through MemoryBIO / wrap_bio.
|
||||
let js_def = js_module::module_def(&builder.ctx);
|
||||
builder = builder.add_native_module(js_def);
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
|
||||
{
|
||||
install_browser_tls_provider();
|
||||
let ssl_def = crate::ssl::module_def(&builder.ctx);
|
||||
let socket_def = crate::socket::module_def(&builder.ctx);
|
||||
builder = builder.add_native_modules(&[ssl_def, socket_def]);
|
||||
}
|
||||
|
||||
if inject_browser_module {
|
||||
let window_def = _window::module_def(&builder.ctx);
|
||||
let browser_def = browser_module::module_def(&builder.ctx);
|
||||
@@ -115,6 +125,26 @@ impl StoredVirtualMachine {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
|
||||
fn install_browser_tls_provider() {
|
||||
use rustls::Error;
|
||||
use rustpython_host_env::ssl::providers::CryptoExt;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn no_ticketer() -> Result<Arc<dyn rustls::server::ProducesTickets>, Error> {
|
||||
Err(Error::General("TLS session tickets are unavailable".into()))
|
||||
}
|
||||
|
||||
let ext = CryptoExt {
|
||||
all_cipher_suites: None,
|
||||
default_cipher_suites: None,
|
||||
all_kx_groups: None,
|
||||
any_supported_key: Some(rustls_rustcrypto::sign::any_supported_type),
|
||||
ticketer: no_ticketer,
|
||||
};
|
||||
let _ = CryptoExt::set_provider(rustls_rustcrypto::provider(), ext);
|
||||
}
|
||||
|
||||
// It's fine that it's thread local, since WASM doesn't even have threads yet. thread_local!
|
||||
// probably gets compiled down to a normal-ish static variable, like Atomic* types do:
|
||||
// https://rustwasm.github.io/2018/10/24/multithreading-rust-and-wasm.html#atomic-instructions
|
||||
|
||||
Reference in new issue
Block a user