diff --git a/.travis.yml b/.travis.yml index 5e2b531591..228aba298e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,6 +84,8 @@ matrix: # Set in the settings page of your repository, as a secure variable github-token: $WEBSITE_GITHUB_TOKEN keep-history: true + on: + branch: release - name: WASM online demo language: rust @@ -109,6 +111,8 @@ matrix: # Set in the settings page of your repository, as a secure variable github-token: $WEBSITE_GITHUB_TOKEN keep-history: true + on: + branch: release - name: Code Coverage language: python diff --git a/Cargo.lock b/Cargo.lock index f71f35a718..e05958928c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -588,6 +588,18 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nix" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.13" @@ -1019,6 +1031,7 @@ dependencies = [ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1913,6 +1926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" "checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" +"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" diff --git a/Dockerfile.wasm b/Dockerfile.wasm index cbc07803fc..921492b7e2 100644 --- a/Dockerfile.wasm +++ b/Dockerfile.wasm @@ -17,6 +17,7 @@ COPY parser parser COPY bytecode bytecode COPY compiler compiler COPY wasm/lib wasm/lib +COPY Lib Lib RUN cd wasm/lib/ && wasm-pack build --release diff --git a/parser/src/ast.rs b/parser/src/ast.rs index b063841ee3..56fc51ed8b 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -14,6 +14,7 @@ pub struct Node { } */ +#[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq)] pub enum Top { Program(Program), @@ -41,7 +42,6 @@ pub struct Located { pub type Statement = Located; /// Abstract syntax tree nodes for python statements. -#[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq)] pub enum StatementType { Break, @@ -100,8 +100,8 @@ pub enum StatementType { }, For { is_async: bool, - target: Expression, - iter: Expression, + target: Box, + iter: Box, body: Vec, orelse: Option>, }, @@ -125,7 +125,7 @@ pub enum StatementType { FunctionDef { is_async: bool, name: String, - args: Parameters, + args: Box, body: Vec, decorator_list: Vec, returns: Option, @@ -217,7 +217,7 @@ pub enum ExpressionType { name: String, }, Lambda { - args: Parameters, + args: Box, body: Box, }, IfExpression { @@ -293,6 +293,7 @@ pub struct Parameters { #[derive(Debug, PartialEq, Default)] pub struct Parameter { + pub location: Location, pub arg: String, pub annotation: Option>, } @@ -308,6 +309,7 @@ pub enum ComprehensionKind { #[derive(Debug, PartialEq)] pub struct Comprehension { + pub location: Location, pub target: Expression, pub iter: Expression, pub ifs: Vec, @@ -321,6 +323,7 @@ pub struct Keyword { #[derive(Debug, PartialEq)] pub struct ExceptHandler { + pub location: Location, pub typ: Option, pub name: Option, pub body: Vec, diff --git a/parser/src/parser.rs b/parser/src/parser.rs index b0e3ec5ab5..11dd61803a 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -233,13 +233,15 @@ mod tests { Ok(vec![as_statement(ast::Expression { location: ast::Location::new(1, 1), node: ast::ExpressionType::Lambda { - args: ast::Parameters { + args: Box::new(ast::Parameters { args: vec![ ast::Parameter { + location: ast::Location::new(1, 8), arg: String::from("x"), annotation: None, }, ast::Parameter { + location: ast::Location::new(1, 11), arg: String::from("y"), annotation: None, } @@ -249,7 +251,7 @@ mod tests { kwarg: ast::Varargs::None, defaults: vec![], kw_defaults: vec![], - }, + }), body: Box::new(ast::Expression { location: ast::Location::new(1, 16), node: ast::ExpressionType::Binop { @@ -308,8 +310,9 @@ mod tests { node: ast::StatementType::FunctionDef { is_async: false, name: String::from("__init__"), - args: ast::Parameters { + args: Box::new(ast::Parameters { args: vec![ast::Parameter { + location: ast::Location::new(2, 15), arg: String::from("self"), annotation: None, }], @@ -318,7 +321,7 @@ mod tests { kwarg: ast::Varargs::None, defaults: vec![], kw_defaults: vec![], - }, + }), body: vec![ast::Statement { location: ast::Location::new(3, 3), node: ast::StatementType::Pass, @@ -332,13 +335,15 @@ mod tests { node: ast::StatementType::FunctionDef { is_async: false, name: String::from("method_with_default"), - args: ast::Parameters { + args: Box::new(ast::Parameters { args: vec![ ast::Parameter { + location: ast::Location::new(4, 26), arg: String::from("self"), annotation: None, }, ast::Parameter { + location: ast::Location::new(4, 32), arg: String::from("arg"), annotation: None, } @@ -348,7 +353,7 @@ mod tests { kwarg: ast::Varargs::None, defaults: vec![make_string("default", 4, 37)], kw_defaults: vec![], - }, + }), body: vec![ast::Statement { location: ast::Location::new(5, 3), node: ast::StatementType::Pass, @@ -377,6 +382,7 @@ mod tests { element: mk_ident("x", 1, 2), }), generators: vec![ast::Comprehension { + location: ast::Location::new(1, 4), target: mk_ident("y", 1, 8), iter: mk_ident("z", 1, 13), ifs: vec![], @@ -400,6 +406,7 @@ mod tests { }), generators: vec![ ast::Comprehension { + location: ast::Location::new(1, 4), target: ast::Expression { location: ast::Location::new(1, 8), node: ast::ExpressionType::Tuple { @@ -410,6 +417,7 @@ mod tests { ifs: vec![], }, ast::Comprehension { + location: ast::Location::new(1, 19), target: mk_ident("a", 1, 23), iter: mk_ident("b", 1, 28), ifs: vec![ diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 8eb44ff2f7..def4bef4ac 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -66,33 +66,33 @@ SmallStatement: ast::Statement = { }; PassStatement: ast::Statement = { - "pass" => { + "pass" => { ast::Statement { - location: loc, + location, node: ast::StatementType::Pass, } }, }; DelStatement: ast::Statement = { - "del" => { + "del" => { ast::Statement { - location: loc, - node: ast::StatementType::Delete { targets: e }, + location, + node: ast::StatementType::Delete { targets }, } }, }; ExpressionStatement: ast::Statement = { - => { + => { // Just an expression, no assignment: if suffix.is_empty() { ast::Statement { location, - node: ast::StatementType::Expression { expression: expr } + node: ast::StatementType::Expression { expression } } } else { - let mut targets = vec![expr]; + let mut targets = vec![expression]; let mut values = suffix; while values.len() > 1 { @@ -177,10 +177,10 @@ FlowStatement: ast::Statement = { node: ast::StatementType::Return { value }, } }, - => { + => { ast::Statement { location, - node: ast::StatementType::Expression { expression: y }, + node: ast::StatementType::Expression { expression }, } }, RaiseStatement, @@ -283,10 +283,10 @@ NonlocalStatement: ast::Statement = { AssertStatement: ast::Statement = { "assert" => { ast::Statement { - location, - node: ast::StatementType::Assert { - test, msg: msg.map(|e| e.1) - } + location, + node: ast::StatementType::Assert { + test, msg: msg.map(|e| e.1) + } } }, }; @@ -302,32 +302,36 @@ CompoundStatement: ast::Statement = { }; IfStatement: ast::Statement = { - "if" ":" => { + "if" ":" => { // Determine last else: let mut last = s3.map(|s| s.2); // handle elif: for i in s2.into_iter().rev() { - let x = ast::Statement { - location: i.0, - node: ast::StatementType::If { test: i.2, body: i.4, orelse: last }, - }; - last = Some(vec![x]); + let x = ast::Statement { + location: i.0, + node: ast::StatementType::If { test: i.2, body: i.4, orelse: last }, + }; + last = Some(vec![x]); } ast::Statement { location, - node: ast::StatementType::If { test, body: s1, orelse: last } + node: ast::StatementType::If { test, body, orelse: last } } }, }; WhileStatement: ast::Statement = { "while" ":" => { - let or_else = s2.map(|s| s.2); + let orelse = s2.map(|s| s.2); ast::Statement { location, - node: ast::StatementType::While { test, body, orelse: or_else }, + node: ast::StatementType::While { + test, + body, + orelse + }, } }, }; @@ -338,40 +342,48 @@ ForStatement: ast::Statement = { let orelse = s2.map(|s| s.2); ast::Statement { location, - node: ast::StatementType::For { is_async, target, iter, body, orelse }, + node: ast::StatementType::For { + is_async, + target: Box::new(target), + iter: Box::new(iter), + body, + orelse + }, } }, }; TryStatement: ast::Statement = { "try" ":" => { - let or_else = else_suite.map(|s| s.2); + let orelse = else_suite.map(|s| s.2); let finalbody = finally.map(|s| s.2); ast::Statement { location, node: ast::StatementType::Try { - body: body, - handlers: handlers, - orelse: or_else, - finalbody: finalbody, + body, + handlers, + orelse, + finalbody, }, } }, }; ExceptClause: ast::ExceptHandler = { - "except" ":" => { + "except" ":" => { ast::ExceptHandler { - typ: typ, + location, + typ, name: None, - body: body, + body, } }, - "except" ":" => { + "except" ":" => { ast::ExceptHandler { + location, typ: Some(x.0), name: Some(x.2), - body: body, + body, } }, }; @@ -387,23 +399,23 @@ WithStatement: ast::Statement = { }; WithItem: ast::WithItem = { - => { + => { let optional_vars = n.map(|val| val.1); - ast::WithItem { context_expr: t, optional_vars } + ast::WithItem { context_expr, optional_vars } }, }; FuncDef: ast::Statement = { - "def" " Test)?> ":" => { + "def" " Test)?> ":" => { let is_async = is_async.is_some(); ast::Statement { location, node: ast::StatementType::FunctionDef { is_async, name, - args: a, + args: Box::new(args), body, - decorator_list: d, + decorator_list, returns: r.map(|x| x.1), } } @@ -411,7 +423,7 @@ FuncDef: ast::Statement = { }; Parameters: ast::Parameters = { - "(" )?> ")" => a.unwrap_or_else(Default::default), + "(" )?> ")" => a.unwrap_or_default(), }; // Note that this is a macro which is used once for function defs, and @@ -425,7 +437,7 @@ ParameterList: ast::Parameters = { ast::Parameters { args: names, - kwonlyargs: kwonlyargs, + kwonlyargs, vararg: vararg.into(), kwarg: kwarg.into(), defaults: default_elements, @@ -443,7 +455,7 @@ ParameterList: ast::Parameters = { ast::Parameters { args: names, - kwonlyargs: kwonlyargs, + kwonlyargs, vararg: vararg.into(), kwarg: kwarg.into(), defaults: default_elements, @@ -454,7 +466,7 @@ ParameterList: ast::Parameters = { let (vararg, kwonlyargs, kw_defaults, kwarg) = params; ast::Parameters { args: vec![], - kwonlyargs: kwonlyargs, + kwonlyargs, vararg: vararg.into(), kwarg: kwarg.into(), defaults: vec![], @@ -476,11 +488,7 @@ ParameterList: ast::Parameters = { // Use inline here to make sure the "," is not creating an ambiguity. #[inline] ParameterDefs: (Vec, Vec) = { - > )*> => { - // Combine first parameters: - let mut args = vec![param1]; - args.extend(param2.into_iter().map(|x| x.1)); - + >> => { let mut names = vec![]; let mut default_elements = vec![]; @@ -491,16 +499,15 @@ ParameterDefs: (Vec, Vec) = { if default_elements.len() > 0 { // Once we have started with defaults, all remaining arguments must // have defaults - panic!( - "non-default argument follows default argument: {}", - &name.arg - ); + panic!("non-default argument follows default argument: {}", &name.arg); } } names.push(name); } - (names, default_elements) + //Ok( + (names, default_elements) + //) } }; @@ -510,13 +517,13 @@ ParameterDef: (ast::Parameter, Option) = { }; UntypedParameter: ast::Parameter = { - => ast::Parameter { arg: i, annotation: None }, + => ast::Parameter { location, arg, annotation: None }, }; TypedParameter: ast::Parameter = { - => { + => { let annotation = a.map(|x| Box::new(x.1)); - ast::Parameter { arg, annotation } + ast::Parameter { location, arg, annotation } }, }; @@ -546,7 +553,7 @@ KwargParameter: Option = { }; ClassDef: ast::Statement = { - "class" ":" => { + "class" ":" => { let (bases, keywords) = match a { Some((_, args, _)) => args, None => (vec![], vec![]), @@ -558,7 +565,7 @@ ClassDef: ast::Statement = { bases, keywords, body, - decorator_list: d, + decorator_list, }, } }, @@ -634,7 +641,7 @@ LambdaDef: ast::Expression = { ast::Expression { location, node: ast::ExpressionType::Lambda { - args: p.unwrap_or(Default::default()), + args: Box::new(p.unwrap_or_default()), body: Box::new(body) } } @@ -720,7 +727,7 @@ AndExpression: ast::Expression = { ShiftExpression: ast::Expression = { => ast::Expression { location, - node: ast::ExpressionType::Binop { a: Box::new(e1), op: op, b: Box::new(e2) } + node: ast::ExpressionType::Binop { a: Box::new(e1), op, b: Box::new(e2) } }, ArithmaticExpression, }; @@ -733,7 +740,7 @@ ShiftOp: ast::Operator = { ArithmaticExpression: ast::Expression = { => ast::Expression { location, - node: ast::ExpressionType::Binop { a: Box::new(a), op: op, b: Box::new(b) } + node: ast::ExpressionType::Binop { a: Box::new(a), op, b: Box::new(b) } }, Term, }; @@ -870,14 +877,14 @@ Atom: ast::Expression = { node: ast::ExpressionType::Identifier { name } }, "[" "]" => { - let elements = e.unwrap_or(Vec::new()); + let elements = e.unwrap_or_default(); ast::Expression { location, node: ast::ExpressionType::List { elements } } }, "[" "]" => e, - "(" ")" => { + "(" ")" => { elements.unwrap_or(ast::Expression { location, node: ast::ExpressionType::Tuple { elements: Vec::new() } @@ -894,7 +901,7 @@ Atom: ast::Expression = { }, "{" "}" => ast::Expression { location, - node: ast::ExpressionType::Dict { elements: e.unwrap_or(Vec::new()) } + node: ast::ExpressionType::Dict { elements: e.unwrap_or_default() } }, "{" "}" => e, "{" "}" => ast::Expression { @@ -1016,8 +1023,8 @@ StarExpr: ast::Expression = { CompFor: Vec = => c; SingleForComprehension: ast::Comprehension = { - "for" "in" => { - ast::Comprehension { target, iter, ifs: c2 } + "for" "in" => { + ast::Comprehension { location, target, iter, ifs } } }; @@ -1071,6 +1078,7 @@ FunctionArgument: (Option>, ast::Expression) = { "**" => (Some(None), e), }; +#[inline] Comma: Vec = { ",")*> => { let mut items = items; diff --git a/tests/snippets/builtin_ascii.py b/tests/snippets/builtin_ascii.py new file mode 100644 index 0000000000..2132723c6b --- /dev/null +++ b/tests/snippets/builtin_ascii.py @@ -0,0 +1,7 @@ +assert ascii('hello world') == "'hello world'" +assert ascii('안녕 세상') == "'\\uc548\\ub155 \\uc138\\uc0c1'" +assert ascii('안녕 RustPython') == "'\\uc548\\ub155 RustPython'" +assert ascii(5) == '5' +assert ascii(chr(0x10001)) == "'\\U00010001'" +assert ascii(chr(0x9999)) == "'\\u9999'" +assert ascii(chr(0x0A)) == "'\\n'" \ No newline at end of file diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 1fcbae3abf..821d603254 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -322,7 +322,7 @@ # make trans # fmt: off -assert ( +assert ( bytes.maketrans(memoryview(b"abc"), bytearray(b"zzz")) == bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 122, 122, 122, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]) ) @@ -597,3 +597,11 @@ assert a * 1 == b'abcd' assert a * 3 == b'abcdabcdabcd' assert 3 * a == b'abcdabcdabcd' + +# decode +assert b'\x72\x75\x73\x74'.decode('ascii') == 'rust' +assert b'\xc2\xae\x75\x73\x74'.decode('ascii', 'replace') == '��ust' +assert b'\xc2\xae\x75\x73\x74'.decode('ascii', 'ignore') == 'ust' +assert b'\xc2\xae\x75\x73\x74'.decode('utf-8') == '®ust' +assert b'\xc2\xae\x75\x73\x74'.decode() == '®ust' +assert b'\xe4\xb8\xad\xe6\x96\x87\xe5\xad\x97'.decode('utf-8') == '中文字' diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index d2e39c1ce5..df163a1959 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -1,6 +1,7 @@ import os import time import stat +import sys from testutils import assert_raises @@ -251,3 +252,25 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert isinstance(os.supports_fd, set) assert isinstance(os.supports_dir_fd, set) assert isinstance(os.supports_follow_symlinks, set) + +# get pid +assert isinstance(os.getpid(), int) + +# unix +if "win" not in sys.platform: + assert isinstance(os.getegid(), int) + assert isinstance(os.getgid(), int) + assert isinstance(os.getsid(os.getpid()), int) + assert isinstance(os.getuid(), int) + assert isinstance(os.geteuid(), int) + assert isinstance(os.getppid(), int) + assert isinstance(os.getpgid(os.getpid()), int) + + assert os.getppid() < os.getpid() + + if os.getuid() != 0: + assert_raises(PermissionError, lambda: os.setgid(42)) + assert_raises(PermissionError, lambda: os.setegid(42)) + assert_raises(PermissionError, lambda: os.setpgid(os.getpid(), 42)) + assert_raises(PermissionError, lambda: os.setuid(42)) + assert_raises(PermissionError, lambda: os.seteuid(42)) diff --git a/tests/snippets/tuple.py b/tests/snippets/tuple.py index f7d880e901..b57346378d 100644 --- a/tests/snippets/tuple.py +++ b/tests/snippets/tuple.py @@ -39,6 +39,9 @@ def __eq__(self, x): a += 1, assert a == (1, 2, 3, 1) +b = (55, *a) +assert b == (55, 1, 2, 3, 1) + assert () is () a = () diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 0b15c0b2eb..d4913b782f 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -61,6 +61,8 @@ maplit = "1.0" proc-macro-hack = { version = "0.5", optional = true } bitflags = "1.1" libc = "0.2" +nix = "0.14.1" +wtf8 = "0.0.3" flame = { version = "0.2", optional = true } flamer = { version = "0.3", optional = true } diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index f422688e8e..36d96c5b98 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -59,7 +59,24 @@ fn builtin_any(iterable: PyIterable, vm: &VirtualMachine) -> PyResult PyResult { + let repr = vm.to_repr(&obj)?; + let mut ascii = String::new(); + for c in repr.value.chars() { + if c.is_ascii() { + ascii.push(c) + } else { + let c = c as i64; + let hex = if c < 0x10000 { + format!("\\u{:04x}", c) + } else { + format!("\\U{:08x}", c) + }; + ascii.push_str(&hex) + } + } + Ok(ascii) +} fn builtin_bin(x: PyIntRef, _vm: &VirtualMachine) -> String { let x = x.as_bigint(); @@ -788,6 +805,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "abs" => ctx.new_rustfunc(builtin_abs), "all" => ctx.new_rustfunc(builtin_all), "any" => ctx.new_rustfunc(builtin_any), + "ascii" => ctx.new_rustfunc(builtin_ascii), "bin" => ctx.new_rustfunc(builtin_bin), "bool" => ctx.bool_type(), "bytearray" => ctx.bytearray_type(), @@ -874,6 +892,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "FileExistsError" => ctx.exceptions.file_exists_error.clone(), "StopIteration" => ctx.exceptions.stop_iteration.clone(), "SystemError" => ctx.exceptions.system_error.clone(), + "PermissionError" => ctx.exceptions.permission_error.clone(), "UnicodeError" => ctx.exceptions.unicode_error.clone(), "UnicodeDecodeError" => ctx.exceptions.unicode_decode_error.clone(), "UnicodeEncodeError" => ctx.exceptions.unicode_encode_error.clone(), diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 8b20fd34a0..4442ea7c64 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -1,7 +1,9 @@ use crate::obj::objbool; +use crate::obj::objstr::PyString; use crate::pyhash; use crate::pyobject::{IdProtocol, PyObjectRef, PyResult}; use crate::vm::VirtualMachine; +use num_bigint::ToBigInt; /// Ordered dictionary implementation. /// Inspired by: https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html /// And: https://www.youtube.com/watch?v=p33CVV29OG8 @@ -93,7 +95,7 @@ impl Dict { } } - pub fn contains(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { + pub fn contains(&self, vm: &VirtualMachine, key: &K) -> PyResult { if let LookupResult::Existing(_) = self.lookup(vm, key)? { Ok(true) } else { @@ -111,7 +113,7 @@ impl Dict { /// Retrieve a key #[cfg_attr(feature = "flame-it", flame("Dict"))] - pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult> { + pub fn get(&self, vm: &VirtualMachine, key: &K) -> PyResult> { if let LookupResult::Existing(index) = self.lookup(vm, key)? { Ok(Some(self.unchecked_get(index))) } else { @@ -149,7 +151,7 @@ impl Dict { key: &PyObjectRef, value: T, ) -> PyResult<()> { - match self.lookup(vm, &key)? { + match self.lookup(vm, key)? { LookupResult::Existing(entry_index) => self.unchecked_delete(entry_index), LookupResult::NewIndex { hash_value, @@ -199,8 +201,8 @@ impl Dict { /// Lookup the index for the given key. #[cfg_attr(feature = "flame-it", flame("Dict"))] - fn lookup(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { - let hash_value = collection_hash(vm, key)?; + fn lookup(&self, vm: &VirtualMachine, key: &K) -> PyResult { + let hash_value = key.do_hash(vm)?; let perturb = hash_value; let mut hash_index: HashIndex = hash_value; loop { @@ -209,11 +211,11 @@ impl Dict { let index = self.indices[&hash_index]; if let Some(entry) = &self.entries[index] { // Okay, we have an entry at this place - if entry.key.is(key) { + if key.do_is(&entry.key) { // Literally the same object break Ok(LookupResult::Existing(index)); } else if entry.hash == hash_value { - if do_eq(vm, &entry.key, key)? { + if key.do_eq(vm, &entry.key)? { break Ok(LookupResult::Existing(index)); } else { // entry mismatch. @@ -242,7 +244,7 @@ impl Dict { } /// Retrieve and delete a key - pub fn pop(&mut self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult> { + pub fn pop(&mut self, vm: &VirtualMachine, key: &K) -> PyResult> { if let LookupResult::Existing(index) = self.lookup(vm, key)? { let value = self.unchecked_get(index); self.unchecked_delete(index); @@ -273,23 +275,68 @@ enum LookupResult { Existing(EntryIndex), // Existing record, index into entries } -#[cfg_attr(feature = "flame-it", flame())] -fn collection_hash(vm: &VirtualMachine, object: &PyObjectRef) -> PyResult { - let raw_hash = vm._hash(object)?; - let mut hasher = DefaultHasher::new(); - raw_hash.hash(&mut hasher); - Ok(hasher.finish() as HashValue) +/// Types implementing this trait can be used to index +/// the dictionary. Typical usecases are: +/// - PyObjectRef -> arbitrary python type used as key +/// - str -> string reference used as key, this is often used internally +pub trait DictKey { + fn do_hash(&self, vm: &VirtualMachine) -> PyResult; + fn do_is(&self, other: &PyObjectRef) -> bool; + fn do_eq(&self, vm: &VirtualMachine, other_key: &PyObjectRef) -> PyResult; } -/// Invoke __eq__ on two keys -fn do_eq(vm: &VirtualMachine, key1: &PyObjectRef, key2: &PyObjectRef) -> Result { - let result = vm._eq(key1.clone(), key2.clone())?; - objbool::boolval(vm, result) +/// Implement trait for PyObjectRef such that we can use python objects +/// to index dictionaries. +impl DictKey for PyObjectRef { + fn do_hash(&self, vm: &VirtualMachine) -> PyResult { + let raw_hash = vm._hash(self)?; + let mut hasher = DefaultHasher::new(); + raw_hash.hash(&mut hasher); + Ok(hasher.finish() as HashValue) + } + + fn do_is(&self, other: &PyObjectRef) -> bool { + self.is(other) + } + + fn do_eq(&self, vm: &VirtualMachine, other_key: &PyObjectRef) -> PyResult { + let result = vm._eq(self.clone(), other_key.clone())?; + objbool::boolval(vm, result) + } +} + +/// Implement trait for the str type, so that we can use strings +/// to index dictionaries. +impl DictKey for String { + fn do_hash(&self, _vm: &VirtualMachine) -> PyResult { + // follow a similar route as the hashing of PyStringRef + let raw_hash = pyhash::hash_value(self).to_bigint().unwrap(); + let raw_hash = pyhash::hash_bigint(&raw_hash); + let mut hasher = DefaultHasher::new(); + raw_hash.hash(&mut hasher); + Ok(hasher.finish() as HashValue) + } + + fn do_is(&self, _other: &PyObjectRef) -> bool { + // No matter who the other pyobject is, we are never the same thing, since + // we are a str, not a pyobject. + false + } + + fn do_eq(&self, vm: &VirtualMachine, other_key: &PyObjectRef) -> PyResult { + if let Some(py_str_value) = other_key.payload::() { + Ok(&py_str_value.value == self) + } else { + // Fall back to PyString implementation. + let s = vm.new_str(self.to_string()); + s.do_eq(vm, other_key) + } + } } #[cfg(test)] mod tests { - use super::{Dict, VirtualMachine}; + use super::{Dict, DictKey, VirtualMachine}; #[test] fn test_insert() { @@ -313,9 +360,40 @@ mod tests { dict.delete(&vm, &key1).unwrap(); assert_eq!(1, dict.len()); - dict.insert(&vm, &key1, value2).unwrap(); + dict.insert(&vm, &key1, value2.clone()).unwrap(); assert_eq!(2, dict.len()); assert_eq!(true, dict.contains(&vm, &key1).unwrap()); + assert_eq!(true, dict.contains(&vm, &"x".to_string()).unwrap()); + + let val = dict.get(&vm, &"x".to_string()).unwrap().unwrap(); + vm._eq(val, value2) + .expect("retrieved value must be equal to inserted value."); + } + + macro_rules! hash_tests { + ($($name:ident: $example_hash:expr,)*) => { + $( + #[test] + fn $name() { + check_hash_equivalence($example_hash); + } + )* + } + } + + hash_tests! { + test_abc: "abc", + test_x: "x", + } + + fn check_hash_equivalence(text: &str) { + let vm: VirtualMachine = Default::default(); + let value1 = text.to_string(); + let value2 = vm.new_str(value1.clone()); + + let hash1 = value1.do_hash(&vm).expect("Hash should not fail."); + let hash2 = value2.do_hash(&vm).expect("Hash should not fail."); + assert_eq!(hash1, hash2); } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 33016e511a..8e3149b17f 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -23,6 +23,8 @@ use super::objiter; use super::objtype::PyClassRef; +use wtf8; + /// "bytes(iterable_of_ints) -> bytes\n\ /// bytes(string, encoding[, errors]) -> bytes\n\ /// bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\ @@ -420,6 +422,113 @@ impl PyBytesRef { fn rmul(self, n: PyIntRef, vm: &VirtualMachine) -> PyResult { self.repeat(n, vm) } + + /// Return a string decoded from the given bytes. + /// Default encoding is 'utf-8'. + /// Default errors is 'strict', meaning that encoding errors raise a UnicodeError. + /// Other possible values are 'ignore', 'replace' + /// For a list of possible encodings, + /// see https://docs.python.org/3/library/codecs.html#standard-encodings + /// currently, only 'utf-8' and 'ascii' emplemented + #[pymethod(name = "decode")] + fn decode( + self, + encoding: OptionalArg, + errors: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let mut strict_mod = true; + let replacing_char = match errors { + OptionalArg::Present(ref input) => match input.as_str() { + "replace" => { + strict_mod = false; + Some('\u{FFFD}') + } + "ignore" => { + strict_mod = false; + None + } + _ => None, + }, + OptionalArg::Missing => None, + }; + let encoding_type = match encoding { + OptionalArg::Present(ref input) => input.as_str(), + OptionalArg::Missing => "utf-8", + }; + + let decode_error = Err(vm.new_value_error("DecodeError".to_string())); + + let mut decode_content = String::new(); + match encoding_type { + "ascii" => { + for &b in self.get_value() { + if b.is_ascii() { + decode_content.push(b as char) + } else if !strict_mod && replacing_char.is_some() { + decode_content.push(replacing_char.unwrap()) + } + } + } + "utf-8" | "utf8" | "" => { + let mut p: u32 = 0u32; + let mut remaining_bytes = 0; + for &b in self.get_value() { + if (b as u8) & 128 == 0 { + if b.is_ascii() { + decode_content.push(b as char) + } else if !strict_mod && replacing_char.is_some() { + decode_content.push(replacing_char.unwrap()) + } + } else if (b as u8) & 192 == 128 { + remaining_bytes -= 1; + + p += u32::from(b as u8 & 63) << (6 * remaining_bytes); + + if remaining_bytes == 0 { + match wtf8::CodePoint::from_u32(p) { + Some(cp) => { + if !strict_mod && replacing_char.is_some() { + decode_content.push(cp.to_char_lossy()); + } else { + match cp.to_char() { + Some(c) => decode_content.push(c), + None => { + if strict_mod { + return decode_error; + } + } + } + } + } + None => { + if replacing_char.is_none() { + decode_content.push(replacing_char.unwrap()) + } + } + } + p = 0u32; + } + } else if (b as u8) & 224 == 192 { + remaining_bytes = 1; + p = u32::from(b as u8 & 31) << 6; + } else if (b as u8) & 240 == 224 { + remaining_bytes = 2; + p = u32::from(b as u8 & 15) << 12; + } else if (b as u8) & 248 == 240 { + remaining_bytes = 3; + p = u32::from(b as u8 & 7) << 18; + } else if !strict_mod && replacing_char.is_some() { + decode_content.push(replacing_char.unwrap()) + } + } + } + _ => { + return Err(vm.new_lookup_error(format!("unknown encoding: {}", encoding_type))); + } + } + Ok(decode_content) + } } #[pyclass] diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 267678b206..e68c6b6a48 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -11,6 +11,7 @@ use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objiter; use super::objstr; +use super::objtype; use crate::dictdatatype; use crate::obj::objtype::PyClassRef; use crate::pyobject::PyClassImpl; @@ -316,6 +317,23 @@ impl PyDictRef { pub fn size(&self) -> dictdatatype::DictSize { self.entries.borrow().size() } + + pub fn get_item_option( + &self, + key: T, + vm: &VirtualMachine, + ) -> PyResult> { + match self.get_item(key, vm) { + Ok(value) => Ok(Some(value)), + Err(exc) => { + if objtype::isinstance(&exc, &vm.ctx.exceptions.key_error) { + Ok(None) + } else { + Err(exc) + } + } + } + } } impl ItemProtocol for PyDictRef { diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index bf52bd0a0f..a158eb344d 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -418,10 +418,7 @@ impl PyInt { #[pymethod(name = "__hash__")] pub fn hash(&self, _vm: &VirtualMachine) -> pyhash::PyHash { - match self.value.to_i64() { - Some(value) => (value % pyhash::MODULUS as i64), - None => (&self.value % pyhash::MODULUS).to_i64().unwrap(), - } + pyhash::hash_bigint(&self.value) } #[pymethod(name = "__abs__")] diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 016c2419c0..a9fb668cb8 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -11,7 +11,7 @@ use crate::obj::objfunction::PyMethod; use crate::obj::objstr; use crate::obj::objtype::{PyClass, PyClassRef}; use crate::pyobject::{ - ItemProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, + PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::scope::NameProtocol; use crate::vm::VirtualMachine; diff --git a/vm/src/pyhash.rs b/vm/src/pyhash.rs index 8bbd82168e..83776bd58b 100644 --- a/vm/src/pyhash.rs +++ b/vm/src/pyhash.rs @@ -1,3 +1,5 @@ +use num_bigint::BigInt; +use num_traits::ToPrimitive; use std::hash::{Hash, Hasher}; use crate::obj::objfloat; @@ -81,3 +83,10 @@ pub fn hash_iter<'a, I: std::iter::Iterator>( } Ok(hasher.finish() as PyHash) } + +pub fn hash_bigint(value: &BigInt) -> PyHash { + match value.to_i64() { + Some(i64_value) => (i64_value % MODULUS as i64), + None => (value % MODULUS).to_i64().unwrap(), + } +} diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index cf249835e4..ba40d54735 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1044,24 +1044,6 @@ pub trait ItemProtocol { vm: &VirtualMachine, ) -> PyResult; fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult; - - #[cfg_attr(feature = "flame-it", flame("ItemProtocol"))] - fn get_item_option( - &self, - key: T, - vm: &VirtualMachine, - ) -> PyResult> { - match self.get_item(key, vm) { - Ok(value) => Ok(Some(value)), - Err(exc) => { - if objtype::isinstance(&exc, &vm.ctx.exceptions.key_error) { - Ok(None) - } else { - Err(exc) - } - } - } - } } impl ItemProtocol for PyObjectRef { diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 80c3406b51..d510699c91 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -6,6 +6,10 @@ use std::time::{Duration, SystemTime}; use std::{env, fs}; use bitflags::bitflags; +#[cfg(unix)] +use nix::errno::Errno; +#[cfg(unix)] +use nix::unistd::{self, Gid, Pid, Uid}; use num_traits::cast::ToPrimitive; use crate::function::{IntoPyNativeFunc, PyFuncArgs}; @@ -174,6 +178,43 @@ fn convert_io_error(vm: &VirtualMachine, err: io::Error) -> PyObjectRef { os_error } +#[cfg(unix)] +fn convert_nix_error(vm: &VirtualMachine, err: nix::Error) -> PyObjectRef { + let nix_error = match err { + nix::Error::InvalidPath => { + let exc_type = vm.ctx.exceptions.file_not_found_error.clone(); + vm.new_exception(exc_type, err.to_string()) + } + nix::Error::InvalidUtf8 => { + let exc_type = vm.ctx.exceptions.unicode_error.clone(); + vm.new_exception(exc_type, err.to_string()) + } + nix::Error::UnsupportedOperation => { + let exc_type = vm.ctx.exceptions.runtime_error.clone(); + vm.new_exception(exc_type, err.to_string()) + } + nix::Error::Sys(errno) => { + let exc_type = convert_nix_errno(vm, errno); + vm.new_exception(exc_type, err.to_string()) + } + }; + + if let nix::Error::Sys(errno) = err { + vm.set_attr(&nix_error, "errno", vm.ctx.new_int(errno as i32)) + .unwrap(); + } + + nix_error +} + +#[cfg(unix)] +fn convert_nix_errno(vm: &VirtualMachine, errno: Errno) -> PyClassRef { + match errno { + Errno::EPERM => vm.ctx.exceptions.permission_error.clone(), + _ => vm.ctx.exceptions.os_error.clone(), + } +} + fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -709,6 +750,105 @@ fn os_rename(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResul fs::rename(&src.value, &dst.value).map_err(|err| convert_io_error(vm, err)) } +fn os_getpid(vm: &VirtualMachine) -> PyObjectRef { + let pid = std::process::id(); + vm.new_int(pid) +} + +#[cfg(unix)] +fn os_getppid(vm: &VirtualMachine) -> PyObjectRef { + let ppid = unistd::getppid().as_raw(); + vm.new_int(ppid) +} + +#[cfg(unix)] +fn os_getgid(vm: &VirtualMachine) -> PyObjectRef { + let gid = unistd::getgid().as_raw(); + vm.new_int(gid) +} + +#[cfg(unix)] +fn os_getegid(vm: &VirtualMachine) -> PyObjectRef { + let egid = unistd::getegid().as_raw(); + vm.new_int(egid) +} + +#[cfg(unix)] +fn os_getpgid(pid: PyIntRef, vm: &VirtualMachine) -> PyObjectRef { + let pid = pid.as_bigint().to_u32().unwrap(); + + match unistd::getpgid(Some(Pid::from_raw(pid as i32))) { + Ok(pgid) => vm.new_int(pgid.as_raw()), + Err(err) => convert_nix_error(vm, err), + } +} + +#[cfg(unix)] +fn os_getsid(pid: PyIntRef, vm: &VirtualMachine) -> PyObjectRef { + let pid = pid.as_bigint().to_u32().unwrap(); + + match unistd::getsid(Some(Pid::from_raw(pid as i32))) { + Ok(sid) => vm.new_int(sid.as_raw()), + Err(err) => convert_nix_error(vm, err), + } +} + +#[cfg(unix)] +fn os_getuid(vm: &VirtualMachine) -> PyObjectRef { + let uid = unistd::getuid().as_raw(); + vm.new_int(uid) +} + +#[cfg(unix)] +fn os_geteuid(vm: &VirtualMachine) -> PyObjectRef { + let euid = unistd::geteuid().as_raw(); + vm.new_int(euid) +} + +#[cfg(unix)] +fn os_setgid(gid: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let gid = gid.as_bigint().to_u32().unwrap(); + + unistd::setgid(Gid::from_raw(gid)).map_err(|err| convert_nix_error(vm, err)) +} + +#[cfg(unix)] +fn os_setegid(egid: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let egid = egid.as_bigint().to_u32().unwrap(); + + unistd::setegid(Gid::from_raw(egid)).map_err(|err| convert_nix_error(vm, err)) +} + +#[cfg(unix)] +fn os_setpgid(pid: PyIntRef, pgid: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let pid = pid.as_bigint().to_u32().unwrap(); + let pgid = pgid.as_bigint().to_u32().unwrap(); + + unistd::setpgid(Pid::from_raw(pid as i32), Pid::from_raw(pgid as i32)) + .map_err(|err| convert_nix_error(vm, err)) +} + +#[cfg(unix)] +fn os_setsid(vm: &VirtualMachine) -> PyResult<()> { + unistd::setsid() + .map(|_ok| ()) + .map_err(|err| convert_nix_error(vm, err)) +} + +#[cfg(unix)] +fn os_setuid(uid: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let uid = uid.as_bigint().to_u32().unwrap(); + + unistd::setuid(Uid::from_raw(uid)).map_err(|err| convert_nix_error(vm, err)) +} + +#[cfg(unix)] +fn os_seteuid(euid: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let euid = euid.as_bigint().to_u32().unwrap(); + + unistd::seteuid(Uid::from_raw(euid)).map_err(|err| convert_nix_error(vm, err)) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -832,6 +972,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "R_OK" => ctx.new_int(4), "W_OK" => ctx.new_int(2), "X_OK" => ctx.new_int(1), + "getpid" => ctx.new_rustfunc(os_getpid) }); for support in support_funcs { @@ -863,5 +1004,33 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "supports_follow_symlinks" => supports_follow_symlinks.into_object(), }); + extend_module_platform_specific(&vm, module) +} + +#[cfg(unix)] +fn extend_module_platform_specific(vm: &VirtualMachine, module: PyObjectRef) -> PyObjectRef { + let ctx = &vm.ctx; + + extend_module!(vm, module, { + "getppid" => ctx.new_rustfunc(os_getppid), + "getgid" => ctx.new_rustfunc(os_getgid), + "getegid" => ctx.new_rustfunc(os_getegid), + "getpgid" => ctx.new_rustfunc(os_getpgid), + "getsid" => ctx.new_rustfunc(os_getsid), + "getuid" => ctx.new_rustfunc(os_getuid), + "geteuid" => ctx.new_rustfunc(os_geteuid), + "setgid" => ctx.new_rustfunc(os_setgid), + "setegid" => ctx.new_rustfunc(os_setegid), + "setpgid" => ctx.new_rustfunc(os_setpgid), + "setsid" => ctx.new_rustfunc(os_setsid), + "setuid" => ctx.new_rustfunc(os_setuid), + "seteuid" => ctx.new_rustfunc(os_seteuid), + }); + + module +} + +#[cfg(not(unix))] +fn extend_module_platform_specific(_vm: &VirtualMachine, module: PyObjectRef) -> PyObjectRef { module } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 3c308002ed..4c231eebde 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -265,6 +265,11 @@ impl VirtualMachine { self.new_exception_obj(exc_type, vec![pystr_msg]).unwrap() } + pub fn new_lookup_error(&self, msg: String) -> PyObjectRef { + let lookup_error = self.ctx.exceptions.lookup_error.clone(); + self.new_exception(lookup_error, msg) + } + pub fn new_attribute_error(&self, msg: String) -> PyObjectRef { let attribute_error = self.ctx.exceptions.attribute_error.clone(); self.new_exception(attribute_error, msg)