From 7f9eb505f8a1af221effb490f9a3f54f27ecb86e Mon Sep 17 00:00:00 2001 From: Louis Capitanchik <contact@louiscap.co> Date: Wed, 17 May 2023 14:39:29 +0100 Subject: [PATCH] Use 'test_case' for paramatised tests --- Cargo.lock | 83 +++++++++- forge-script-lang/Cargo.toml | 5 +- forge-script-lang/src/error/mod.rs | 4 +- forge-script-lang/src/lexer/tokens.rs | 154 ++++++------------ .../src/runtime/executor/simple.rs | 121 +++++--------- forge-script-lang/src/runtime/value.rs | 51 ++++++ 6 files changed, 221 insertions(+), 197 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7203c79..9324f86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,7 @@ dependencies = [ "nom_locate", "peg", "serde", + "test-case", ] [[package]] @@ -138,6 +139,30 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.56" @@ -184,7 +209,18 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] @@ -198,12 +234,53 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "test-case" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1d6e7bde536b0412f20765b76e921028059adfd1b90d8974d33fd3c91b25df" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d10394d5d1e27794f772b6fc854c7e91a2dc26e2cbf807ad523370c2a59c0cee" +dependencies = [ + "cfg-if", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "test-case-macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb9a44b1c6a54c1ba58b152797739dba2a83ca74e18168a68c980eb142f9404" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", + "test-case-core", +] + [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasm-bindgen" version = "0.2.85" @@ -225,7 +302,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.15", "wasm-bindgen-shared", ] @@ -247,7 +324,7 @@ checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/forge-script-lang/Cargo.toml b/forge-script-lang/Cargo.toml index 13235fc..ecf2488 100644 --- a/forge-script-lang/Cargo.toml +++ b/forge-script-lang/Cargo.toml @@ -19,4 +19,7 @@ verbose-serde = [] nom.workspace = true nom_locate.workspace = true peg.workspace = true -serde = { version = "1.0", optional = true, features = ["derive"] } \ No newline at end of file +serde = { version = "1.0", optional = true, features = ["derive"] } + +[dev-dependencies] +test-case = "3.1.0" \ No newline at end of file diff --git a/forge-script-lang/src/error/mod.rs b/forge-script-lang/src/error/mod.rs index 7019a93..b3986af 100644 --- a/forge-script-lang/src/error/mod.rs +++ b/forge-script-lang/src/error/mod.rs @@ -53,7 +53,7 @@ pub struct ParseError<'a> { pub kind: ParseErrorKind<'a>, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum ForgeErrorKind<'a> { IncompleteInput, LexerError(nom::error::Error<Span<'a>>), @@ -63,7 +63,7 @@ pub enum ForgeErrorKind<'a> { }, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct ForgeError<'a> { pub kind: ForgeErrorKind<'a>, } diff --git a/forge-script-lang/src/lexer/tokens.rs b/forge-script-lang/src/lexer/tokens.rs index bb2731c..ac79700 100644 --- a/forge-script-lang/src/lexer/tokens.rs +++ b/forge-script-lang/src/lexer/tokens.rs @@ -241,7 +241,7 @@ impl<'a> TryFrom<&'a str> for ScriptTokenType<'a> { } } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct ScriptToken<'a> { pub position: Span<'a>, pub token_type: ScriptTokenType<'a>, @@ -267,112 +267,52 @@ impl<'a> Debug for ScriptToken<'a> { #[cfg(test)] mod token_tests { - use super::ScriptTokenType; + use super::{ScriptTokenType, TokenFromStringError}; + use test_case::test_case; - #[test] - fn match_type_from_string() { - assert_eq!( - ScriptTokenType::try_from("("), - Ok(ScriptTokenType::LeftParen) - ); - assert_eq!( - ScriptTokenType::try_from(")"), - Ok(ScriptTokenType::RightParen) - ); - assert_eq!( - ScriptTokenType::try_from("{"), - Ok(ScriptTokenType::LeftBrace) - ); - assert_eq!( - ScriptTokenType::try_from("}"), - Ok(ScriptTokenType::RightBrace) - ); - assert_eq!(ScriptTokenType::try_from(","), Ok(ScriptTokenType::Comma)); - assert_eq!(ScriptTokenType::try_from("."), Ok(ScriptTokenType::Dot)); - assert_eq!(ScriptTokenType::try_from("-"), Ok(ScriptTokenType::Minus)); - assert_eq!(ScriptTokenType::try_from("+"), Ok(ScriptTokenType::Plus)); - assert_eq!( - ScriptTokenType::try_from(";"), - Ok(ScriptTokenType::Semicolon) - ); - assert_eq!(ScriptTokenType::try_from("/"), Ok(ScriptTokenType::Slash)); - assert_eq!( - ScriptTokenType::try_from("*"), - Ok(ScriptTokenType::Asterisk) - ); - assert_eq!(ScriptTokenType::try_from("!"), Ok(ScriptTokenType::Bang)); - assert_eq!( - ScriptTokenType::try_from("!="), - Ok(ScriptTokenType::BangEqual) - ); - assert_eq!(ScriptTokenType::try_from("="), Ok(ScriptTokenType::Equal)); - assert_eq!( - ScriptTokenType::try_from("=="), - Ok(ScriptTokenType::EqualEqual) - ); - assert_eq!(ScriptTokenType::try_from(">"), Ok(ScriptTokenType::Greater)); - assert_eq!( - ScriptTokenType::try_from(">="), - Ok(ScriptTokenType::GreaterEqual) - ); - assert_eq!(ScriptTokenType::try_from("<"), Ok(ScriptTokenType::Less)); - assert_eq!( - ScriptTokenType::try_from("<="), - Ok(ScriptTokenType::LessEqual) - ); - assert_eq!( - ScriptTokenType::try_from("||"), - Ok(ScriptTokenType::DoublePipe) - ); - assert_eq!( - ScriptTokenType::try_from("&&"), - Ok(ScriptTokenType::DoubleAmpersand) - ); - assert_eq!(ScriptTokenType::try_from("%"), Ok(ScriptTokenType::Modulo)); - assert_eq!(ScriptTokenType::try_from("^"), Ok(ScriptTokenType::Caret)); - assert_eq!( - ScriptTokenType::try_from("struct"), - Ok(ScriptTokenType::Class) - ); - assert_eq!(ScriptTokenType::try_from("else"), Ok(ScriptTokenType::Else)); - assert_eq!( - ScriptTokenType::try_from("fn"), - Ok(ScriptTokenType::Function) - ); - assert_eq!(ScriptTokenType::try_from("for"), Ok(ScriptTokenType::For)); - assert_eq!(ScriptTokenType::try_from("if"), Ok(ScriptTokenType::If)); - assert_eq!(ScriptTokenType::try_from("null"), Ok(ScriptTokenType::Null)); - assert_eq!( - ScriptTokenType::try_from("print"), - Ok(ScriptTokenType::Print) - ); - assert_eq!( - ScriptTokenType::try_from("return"), - Ok(ScriptTokenType::Return) - ); - assert_eq!( - ScriptTokenType::try_from("super"), - Ok(ScriptTokenType::Super) - ); - assert_eq!(ScriptTokenType::try_from("this"), Ok(ScriptTokenType::This)); - assert_eq!(ScriptTokenType::try_from("let"), Ok(ScriptTokenType::Let)); - assert_eq!( - ScriptTokenType::try_from("while"), - Ok(ScriptTokenType::While) - ); - assert_eq!( - ScriptTokenType::try_from("export"), - Ok(ScriptTokenType::Export) - ); - assert_eq!( - ScriptTokenType::try_from("import"), - Ok(ScriptTokenType::Import) - ); - assert_eq!(ScriptTokenType::try_from("as"), Ok(ScriptTokenType::Alias)); - assert_eq!(ScriptTokenType::try_from("from"), Ok(ScriptTokenType::From)); - assert_eq!( - ScriptTokenType::try_from("typeof"), - Ok(ScriptTokenType::Typeof) - ); + #[test_case("(" => Ok(ScriptTokenType::LeftParen); r#"Parse LeftParen"#)] + #[test_case(")" => Ok(ScriptTokenType::RightParen); r#"Parse RightParen"#)] + #[test_case("{" => Ok(ScriptTokenType::LeftBrace); r#"Parse LeftBrace"#)] + #[test_case("}" => Ok(ScriptTokenType::RightBrace); r#"Parse RightBrace"#)] + #[test_case("," => Ok(ScriptTokenType::Comma); r#"Parse Comma"#)] + #[test_case("." => Ok(ScriptTokenType::Dot); r#"Parse Dot"#)] + #[test_case("-" => Ok(ScriptTokenType::Minus); r#"Parse Minus"#)] + #[test_case("+" => Ok(ScriptTokenType::Plus); r#"Parse Plus"#)] + #[test_case(";" => Ok(ScriptTokenType::Semicolon); r#"Parse Semicolon"#)] + #[test_case("/" => Ok(ScriptTokenType::Slash); r#"Parse Slash"#)] + #[test_case("*" => Ok(ScriptTokenType::Asterisk); r#"Parse Asterisk"#)] + #[test_case("!" => Ok(ScriptTokenType::Bang); r#"Parse Bang"#)] + #[test_case("!=" => Ok(ScriptTokenType::BangEqual); r#"Parse BangEqual"#)] + #[test_case("=" => Ok(ScriptTokenType::Equal); r#"Parse Equal"#)] + #[test_case("==" => Ok(ScriptTokenType::EqualEqual); r#"Parse EqualEqual"#)] + #[test_case(">" => Ok(ScriptTokenType::Greater); r#"Parse Greater"#)] + #[test_case(">=" => Ok(ScriptTokenType::GreaterEqual); r#"Parse GreaterEqual"#)] + #[test_case("<" => Ok(ScriptTokenType::Less); r#"Parse Less"#)] + #[test_case("<=" => Ok(ScriptTokenType::LessEqual); r#"Parse LessEqual"#)] + #[test_case("||" => Ok(ScriptTokenType::DoublePipe); r#"Parse DoublePipe"#)] + #[test_case("&&" => Ok(ScriptTokenType::DoubleAmpersand); r#"Parse DoubleAmpersand"#)] + #[test_case("%" => Ok(ScriptTokenType::Modulo); r#"Parse Modulo"#)] + #[test_case("^" => Ok(ScriptTokenType::Caret); r#"Parse Caret"#)] + #[test_case("struct" => Ok(ScriptTokenType::Class); r#"Parse Class"#)] + #[test_case("else" => Ok(ScriptTokenType::Else); r#"Parse Else"#)] + #[test_case("fn" => Ok(ScriptTokenType::Function); r#"Parse Function"#)] + #[test_case("for" => Ok(ScriptTokenType::For); r#"Parse For"#)] + #[test_case("if" => Ok(ScriptTokenType::If); r#"Parse If"#)] + #[test_case("null" => Ok(ScriptTokenType::Null); r#"Parse Null"#)] + #[test_case("print" => Ok(ScriptTokenType::Print); r#"Parse Print"#)] + #[test_case("return" => Ok(ScriptTokenType::Return); r#"Parse Return"#)] + #[test_case("super" => Ok(ScriptTokenType::Super); r#"Parse Super"#)] + #[test_case("this" => Ok(ScriptTokenType::This); r#"Parse This"#)] + #[test_case("let" => Ok(ScriptTokenType::Let); r#"Parse Let"#)] + #[test_case("while" => Ok(ScriptTokenType::While); r#"Parse While"#)] + #[test_case("export" => Ok(ScriptTokenType::Export); r#"Parse Export"#)] + #[test_case("import" => Ok(ScriptTokenType::Import); r#"Parse Import"#)] + #[test_case("as" => Ok(ScriptTokenType::Alias); r#"Parse Alias"#)] + #[test_case("from" => Ok(ScriptTokenType::From); r#"Parse From"#)] + #[test_case("typeof" => Ok(ScriptTokenType::Typeof); r#"Parse Typeof"#)] + #[test_case("true" => Ok(ScriptTokenType::Boolean(true)); r#"Parse true"#)] + #[test_case("false" => Ok(ScriptTokenType::Boolean(false)); r#"Parse false"#)] + fn parse_token_type(value: &'static str) -> Result<ScriptTokenType, TokenFromStringError> { + ScriptTokenType::try_from(value) } } diff --git a/forge-script-lang/src/runtime/executor/simple.rs b/forge-script-lang/src/runtime/executor/simple.rs index 46fb554..a0b728d 100644 --- a/forge-script-lang/src/runtime/executor/simple.rs +++ b/forge-script-lang/src/runtime/executor/simple.rs @@ -238,85 +238,44 @@ impl Visitor for SimpleExecutor { #[cfg(test)] mod interpreter_test { use crate::parse::parse_program; - use crate::runtime::executor::simple::{RuntimeError, SimpleExecutor}; + use crate::runtime::executor::simple::{RuntimeError, RuntimeResult, SimpleExecutor}; use crate::runtime::executor::Visitor; - use crate::runtime::value::ForgeValue; - - #[test] - fn the_basics() { - let add_numbers = parse_program("1 + 1").expect("Failed to parse"); - let add_strings = parse_program("\"foo\" + \" \" + \"bar\"").expect("Failed to parse"); - let concat_string_num = parse_program("\"#\" + 1").expect("Failed to parse"); - let bad_mult = parse_program("false * 123").expect("Failed to parse"); + use crate::runtime::value::{ForgeValue, UnsupportedOperation}; + use test_case::test_case; + + #[test_case("1 + 1" => Ok(ForgeValue::from(2)) ; "numeric addition")] + #[test_case(r#""foo" + " " + "bar""# => Ok(ForgeValue::from("foo bar")) ; "string concatenation")] + #[test_case( + r#"false * 132"# => Err(RuntimeError::BadOperands(UnsupportedOperation::mul("non-number", "number"))); + "bad multiplication error" + )] + #[test_case("\"#\" + 1" => Ok(ForgeValue::from("#1")) ; "coerce number to string with concat")] + #[test_case("1 + -1" => Ok(ForgeValue::from(0)) ; "unary nested in binary")] + #[test_case("print 2 + 2" => Ok(ForgeValue::Null) ; "print is nullish")] + fn simple_expr(prog: &'static str) -> RuntimeResult<ForgeValue> { + let ast = parse_program(prog).expect("Failed to parse program"); let mut vm = SimpleExecutor::default(); - - assert_eq!(vm.evaluate_program(&add_numbers), Ok(ForgeValue::from(2))); - assert_eq!( - vm.evaluate_program(&add_strings), - Ok(ForgeValue::from("foo bar")) - ); - assert_eq!( - vm.evaluate_program(&concat_string_num), - Ok(ForgeValue::from("#1")) - ); - assert!(matches!( - vm.evaluate_program(&bad_mult), - Err(RuntimeError::BadOperands(_)) - )) + vm.evaluate_program(&ast) } - #[test] - fn combos() { - let add_numbers = parse_program("1 + -1").expect("Failed to parse"); + #[test_case("let foo" => Ok(ForgeValue::Null); "Initialise as null")] + #[test_case("let bar = 123" => Ok(ForgeValue::from(123)); "Initialise with value")] + #[test_case("let bar = 123; let bar = 456" => Err(RuntimeError::VariableAlreadyDefined(String::from("bar"))); "Double initialise error")] + #[test_case("print missing" => Err(RuntimeError::UndefinedVariable(String::from("missing"))); "Access undefined error")] + #[test_case("let bar = 123; let foo = bar * 2" =>Ok(ForgeValue::from(246)); "Initialise from other var")] + fn declare_variables(prog: &'static str) -> RuntimeResult<ForgeValue> { + let ast = parse_program(prog).expect("Failed to parse program"); let mut vm = SimpleExecutor::default(); - assert_eq!(vm.evaluate_program(&add_numbers), Ok(ForgeValue::from(0))); + vm.evaluate_program(&ast) } - #[test] - fn print() { - let print_4 = parse_program("print 2 + 2").expect("Failed to parse"); - + #[test_case("let value = true; if value { 123 } else { 456 }" => Ok(ForgeValue::from(123)); "eval if branch")] + #[test_case("let value = true; if !value { 123 } else { 456 }" => Ok(ForgeValue::from(456)); "eval else branch")] + #[test_case("let value = 2; if value == 1 { 123 } else if value == 2 { 456 } else { \"Nothing\" }" => Ok(ForgeValue::from(456)); "eval middle if else branch")] + fn conditional_blocks(prog: &'static str) -> RuntimeResult<ForgeValue> { + let ast = parse_program(prog).expect("Failed to parse program"); let mut vm = SimpleExecutor::default(); - assert_eq!(vm.evaluate_program(&print_4), Ok(ForgeValue::Null)); - } - - #[test] - fn define_variables() { - let simple_define = parse_program("let foo").expect("Failed to parse"); - let assignment = parse_program("let bar = 123").expect("Failed to parse"); - let redecl_err = parse_program("let foo = 123").expect("Failed to parse"); - let missing_err = parse_program("print missing").expect("Failed to parse"); - let use_vars = parse_program("let splop = bar * 2").expect("Failed to parse"); - - let mut vm = SimpleExecutor::default(); - - assert_eq!(vm.evaluate_program(&simple_define), Ok(ForgeValue::Null)); - assert_eq!(vm.evaluate_program(&assignment), Ok(ForgeValue::from(123))); - assert_eq!( - vm.evaluate_program(&redecl_err), - Err(RuntimeError::VariableAlreadyDefined(String::from("foo"))) - ); - assert_eq!( - vm.evaluate_program(&missing_err), - Err(RuntimeError::UndefinedVariable(String::from("missing"))) - ); - assert_eq!(vm.evaluate_program(&use_vars), Ok(ForgeValue::from(246))); - assert_eq!(vm.get_variable("splop"), Some(&ForgeValue::from(246))); - } - - #[test] - fn conditional_blocks() { - let tests = [ - ("let value = true; if value { 123 } else { 456 }", Ok(ForgeValue::from(123))), - ("let value = true; if !value { 123 } else { 456 }", Ok(ForgeValue::from(456))), - ("let value = 2; if value == 1 { 123 } else if value == 2 { 456 } else { \"Nothing\" }", Ok(ForgeValue::from(456))), - ]; - - for (prog, expected) in tests { - let ast = parse_program(prog).expect("Failed to parse"); - let mut vm = SimpleExecutor::default(); - assert_eq!(vm.evaluate_program(&ast), expected); - } + vm.evaluate_program(&ast) } #[test] @@ -328,18 +287,12 @@ mod interpreter_test { assert!(vm.get_variable("bar").is_none()); } - #[test] - fn variable_type() { - let tests = [ - ("typeof 123", Ok(ForgeValue::from("number"))), - (r#"typeof "some string""#, Ok(ForgeValue::from("string"))), - ("typeof false", Ok(ForgeValue::from("boolean"))), - ]; - - for (prog, expected) in tests { - let ast = parse_program(prog).expect("Failed to parse"); - let mut vm = SimpleExecutor::default(); - assert_eq!(vm.evaluate_program(&ast), expected); - } + #[test_case("typeof 123" => Ok(ForgeValue::from("number")); "typeof number")] + #[test_case(r#"typeof "some string""# => Ok(ForgeValue::from("string")); "typeof string")] + #[test_case("typeof false" => Ok(ForgeValue::from("boolean")); "typeof bool")] + fn variable_type(prog: &'static str) -> RuntimeResult<ForgeValue> { + let ast = parse_program(prog).expect("Failed to parse program"); + let mut vm = SimpleExecutor::default(); + vm.evaluate_program(&ast) } } diff --git a/forge-script-lang/src/runtime/value.rs b/forge-script-lang/src/runtime/value.rs index 3619e10..39e8030 100644 --- a/forge-script-lang/src/runtime/value.rs +++ b/forge-script-lang/src/runtime/value.rs @@ -275,6 +275,37 @@ pub struct UnsupportedOperation { right_type: &'static str, } +impl UnsupportedOperation { + pub fn mul(left: &'static str, right: &'static str) -> Self { + Self { + operation: BinaryOp::Multiply, + left_type: left, + right_type: right, + } + } + pub fn add(left: &'static str, right: &'static str) -> Self { + Self { + operation: BinaryOp::Add, + left_type: left, + right_type: right, + } + } + pub fn sub(left: &'static str, right: &'static str) -> Self { + Self { + operation: BinaryOp::Subtract, + left_type: left, + right_type: right, + } + } + pub fn div(left: &'static str, right: &'static str) -> Self { + Self { + operation: BinaryOp::Divide, + left_type: left, + right_type: right, + } + } +} + impl Display for UnsupportedOperation { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( @@ -296,6 +327,11 @@ impl Sub for ForgeValue { left_type: "number", right_type: "non-number", }), + (_, ForgeValue::Number(_)) => Err(UnsupportedOperation { + operation: BinaryOp::Subtract, + left_type: "non-number", + right_type: "number", + }), (_, _) => Err(UnsupportedOperation { operation: BinaryOp::Subtract, left_type: "non-number", @@ -316,6 +352,11 @@ impl Div for ForgeValue { left_type: "number", right_type: "non-number", }), + (_, ForgeValue::Number(_)) => Err(UnsupportedOperation { + operation: BinaryOp::Divide, + left_type: "non-number", + right_type: "number", + }), (_, _) => Err(UnsupportedOperation { operation: BinaryOp::Divide, left_type: "non-number", @@ -336,6 +377,11 @@ impl Mul for ForgeValue { left_type: "number", right_type: "non-number", }), + (_, ForgeValue::Number(_)) => Err(UnsupportedOperation { + operation: BinaryOp::Multiply, + left_type: "non-number", + right_type: "number", + }), (_, _) => Err(UnsupportedOperation { operation: BinaryOp::Multiply, left_type: "non-number", @@ -356,6 +402,11 @@ impl Rem for ForgeValue { left_type: "number", right_type: "non-number", }), + (_, ForgeValue::Number(_)) => Err(UnsupportedOperation { + operation: BinaryOp::Modulo, + left_type: "non-number", + right_type: "number", + }), (_, _) => Err(UnsupportedOperation { operation: BinaryOp::Modulo, left_type: "non-number", -- GitLab