From 10f4d2e4d504844eefce9f91741c5fd460463b85 Mon Sep 17 00:00:00 2001 From: kaankacar Date: Sat, 24 Jan 2026 14:02:02 +0300 Subject: [PATCH] Prohibit combined unary operators --- core/type-checker/src/errors.rs | 4 +++ core/type-checker/src/type_checker.rs | 17 ++++++++++ tests/src/type_checker/coverage.rs | 14 +++++--- tests/src/type_checker/type_checker.rs | 45 +++++++++++++++++++------- 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/core/type-checker/src/errors.rs b/core/type-checker/src/errors.rs index 77ae331..a172ff0 100644 --- a/core/type-checker/src/errors.rs +++ b/core/type-checker/src/errors.rs @@ -227,6 +227,9 @@ pub enum TypeCheckError { location: Location, }, + #[error("{location}: combined unary operators are prohibited")] + CombinedUnaryOperators { location: Location }, + #[error( "{location}: cannot apply operator `{operator:?}` to operands of different types: `{left}` and `{right}`" )] @@ -357,6 +360,7 @@ impl TypeCheckError { | TypeCheckError::MissingTypeParameters { location, .. } | TypeCheckError::InvalidBinaryOperand { location, .. } | TypeCheckError::InvalidUnaryOperand { location, .. } + | TypeCheckError::CombinedUnaryOperators { location } | TypeCheckError::BinaryOperandTypeMismatch { location, .. } | TypeCheckError::SelfReferenceInFunction { location, .. } | TypeCheckError::SelfReferenceOutsideMethod { location } diff --git a/core/type-checker/src/type_checker.rs b/core/type-checker/src/type_checker.rs index 82de1b2..143097f 100644 --- a/core/type-checker/src/type_checker.rs +++ b/core/type-checker/src/type_checker.rs @@ -1309,6 +1309,23 @@ impl TypeChecker { None } Expression::PrefixUnary(prefix_unary_expression) => { + fn is_prefix_unary(expr: &Expression) -> bool { + match expr { + Expression::PrefixUnary(_) => true, + Expression::Parenthesized(inner) => { + is_prefix_unary(&inner.expression.borrow()) + } + Expression::Literal(Literal::Number(num)) => num.value.starts_with('-'), + _ => false, + } + } + + if is_prefix_unary(&prefix_unary_expression.expression.borrow()) { + self.errors.push(TypeCheckError::CombinedUnaryOperators { + location: prefix_unary_expression.location, + }); + return None; + } match prefix_unary_expression.operator { UnaryOperatorKind::Not => { let expression_type_op = self diff --git a/tests/src/type_checker/coverage.rs b/tests/src/type_checker/coverage.rs index a1c7434..80df412 100644 --- a/tests/src/type_checker/coverage.rs +++ b/tests/src/type_checker/coverage.rs @@ -751,11 +751,15 @@ mod expression_coverage { fn test_unary_neg_nested() { let source = r#"fn test() -> i32 { return --42; }"#; let result = try_type_check(source); - assert!( - result.is_ok(), - "Double unary neg should work, got: {:?}", - result.err() - ); + assert!(result.is_err(), "Double unary neg should be prohibited"); + if let Err(error) = result { + let error_msg = error.to_string(); + assert!( + error_msg.contains("combined unary operators"), + "Error should mention combined unary operators: {}", + error_msg + ); + } } // FIXME: Test disabled due to parser or type checker limitation diff --git a/tests/src/type_checker/type_checker.rs b/tests/src/type_checker/type_checker.rs index 7b4bd03..0622a25 100644 --- a/tests/src/type_checker/type_checker.rs +++ b/tests/src/type_checker/type_checker.rs @@ -2166,10 +2166,12 @@ mod unary_operator_tests { fn test_double_negate() { let source = r#"fn test(x: i32) -> i32 { return --(x); }"#; let result = try_type_check(source); + assert!(result.is_err(), "Double negation should be prohibited"); + let err_msg = result.err().unwrap().to_string(); assert!( - result.is_ok(), - "Double negation should succeed, got: {:?}", - result.err() + err_msg.contains("combined unary operators"), + "Error should mention combined unary operators, got: {}", + err_msg ); } } @@ -2282,10 +2284,12 @@ mod unary_operator_tests { fn test_bitnot_combined_with_negate() { let source = r#"fn test(x: i32) -> i32 { return ~-(x); }"#; let result = try_type_check(source); + assert!(result.is_err(), "Combined unary operators should be prohibited"); + let err_msg = result.err().unwrap().to_string(); assert!( - result.is_ok(), - "Combining BitNot and Neg should succeed, got: {:?}", - result.err() + err_msg.contains("combined unary operators"), + "Error should mention combined unary operators, got: {}", + err_msg ); } @@ -2293,10 +2297,25 @@ mod unary_operator_tests { fn test_negate_combined_with_bitnot() { let source = r#"fn test(x: i32) -> i32 { return -(~x); }"#; let result = try_type_check(source); + assert!(result.is_err(), "Combined unary operators should be prohibited"); + let err_msg = result.err().unwrap().to_string(); assert!( - result.is_ok(), - "Combining Neg and BitNot should succeed, got: {:?}", - result.err() + err_msg.contains("combined unary operators"), + "Error should mention combined unary operators, got: {}", + err_msg + ); + } + + #[test] + fn test_bitnot_then_neg_literal_combination_errors() { + let source = r#"fn test() -> i32 { return -~42; }"#; + let result = try_type_check(source); + assert!(result.is_err(), "Combined unary operators should be prohibited"); + let err_msg = result.err().unwrap().to_string(); + assert!( + err_msg.contains("combined unary operators"), + "Error should mention combined unary operators, got: {}", + err_msg ); } } @@ -2332,10 +2351,12 @@ mod unary_operator_tests { fn test_double_logical_not() { let source = r#"fn test(x: bool) -> bool { return !!x; }"#; let result = try_type_check(source); + assert!(result.is_err(), "Double logical NOT should be prohibited"); + let err_msg = result.err().unwrap().to_string(); assert!( - result.is_ok(), - "Double logical NOT should succeed, got: {:?}", - result.err() + err_msg.contains("combined unary operators"), + "Error should mention combined unary operators, got: {}", + err_msg ); } }