From 10f4d2e4d504844eefce9f91741c5fd460463b85 Mon Sep 17 00:00:00 2001 From: kaankacar Date: Sat, 24 Jan 2026 14:02:02 +0300 Subject: [PATCH 1/4] 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 77ae331f..a172ff0c 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 82de1b2d..143097f3 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 a1c74344..80df4124 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 7b4bd031..0622a254 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 ); } } From d703ced454a78f7a8cf5ffbb0f2b49a15a2e7063 Mon Sep 17 00:00:00 2001 From: kaankacar Date: Wed, 28 Jan 2026 01:41:22 +0300 Subject: [PATCH 2/4] Move combined unary operator check to new semantic-analysis crate Extract the combined unary operator prohibition from the type-checker into a dedicated `core/semantic-analysis` crate, as this is a semantic check rather than a type check. The pipeline is now: parse -> type check -> semantic check -> codegen - Create `inference-semantic-analysis` crate with 3-level diagnostic reporting (info, warning, error) - Move `is_prefix_unary` / `CombinedUnaryOperators` logic out of the type-checker and into the new semantic analysis pass - Wire up the placeholder `analyze()` in `core/inference` to invoke the new crate - Handle parenthesized expressions like `-(~x)` and `~-(x)` by recursively unwrapping parenthesized nodes - Add dedicated semantic analysis tests; revert type-checker tests to their original expectations Fixes #82 --- Cargo.toml | 1 + core/inference/Cargo.toml | 1 + core/inference/src/lib.rs | 35 ++--- core/semantic-analysis/Cargo.toml | 12 ++ core/semantic-analysis/src/diagnostics.rs | 70 ++++++++++ core/semantic-analysis/src/lib.rs | 67 +++++++++ core/type-checker/src/errors.rs | 4 - core/type-checker/src/type_checker.rs | 17 --- tests/Cargo.toml | 1 + tests/src/lib.rs | 1 + tests/src/semantic_analysis/combined_unary.rs | 127 ++++++++++++++++++ tests/src/semantic_analysis/mod.rs | 1 + tests/src/type_checker/coverage.rs | 14 +- tests/src/type_checker/type_checker.rs | 45 ++----- 14 files changed, 311 insertions(+), 85 deletions(-) create mode 100644 core/semantic-analysis/Cargo.toml create mode 100644 core/semantic-analysis/src/diagnostics.rs create mode 100644 core/semantic-analysis/src/lib.rs create mode 100644 tests/src/semantic_analysis/combined_unary.rs create mode 100644 tests/src/semantic_analysis/mod.rs diff --git a/Cargo.toml b/Cargo.toml index aa8e63d2..5f14471b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ inference-ast = { path = "./core/ast", version = "0.0.1" } inference-type-checker = { path = "./core/type-checker", version = "0.0.1" } inference-cli = { path = "./core/cli", version = "0.0.1" } inference-wasm-to-v-translator = { path = "./core/wasm-to-v", version = "0.0.1" } +inference-semantic-analysis = { path = "./core/semantic-analysis", version = "0.0.1" } inference-wasm-codegen = { path = "./core/wasm-codegen", version = "0.0.1" } # IDE support crates diff --git a/core/inference/Cargo.toml b/core/inference/Cargo.toml index 62c2d2d5..b40f1db7 100644 --- a/core/inference/Cargo.toml +++ b/core/inference/Cargo.toml @@ -14,3 +14,4 @@ inference-ast.workspace = true inference-wasm-codegen.workspace = true inference-wasm-to-v-translator.workspace = true inference-type-checker.workspace = true +inference-semantic-analysis.workspace = true diff --git a/core/inference/src/lib.rs b/core/inference/src/lib.rs index 9527eb23..9a0d9b72 100644 --- a/core/inference/src/lib.rs +++ b/core/inference/src/lib.rs @@ -292,19 +292,14 @@ pub fn type_check(arena: Arena) -> anyhow::Result { /// Performs semantic analysis on the typed AST. /// -/// This function is currently a placeholder for future semantic analysis passes. -/// Planned analyses include: -/// - Dead code detection -/// - Unused variable warnings -/// - Unreachable code analysis -/// - Control flow validation -/// - Lifetime and borrow checking (if applicable) +/// This function runs semantic checks that are beyond the scope of type checking, +/// such as detecting prohibited combined unary operators, and other +/// scope-bounded language constraints. /// -/// # Current Status -/// -/// **Work in Progress**: This phase is under active development and currently -/// returns `Ok(())` without performing any checks. Once implemented, it will -/// provide additional semantic guarantees beyond type correctness. +/// Diagnostics are reported with three severity levels: +/// - `Error` — semantic violations that must be fixed +/// - `Warning` — suspicious patterns that may indicate bugs +/// - `Info` — informational notes about code style or usage /// /// # Examples /// @@ -314,21 +309,17 @@ pub fn type_check(arena: Arena) -> anyhow::Result { /// let source = r#"fn main() { return 0; }"#; /// let arena = parse(source)?; /// let typed_context = type_check(arena)?; -/// -/// // Currently a no-op, but will perform semantic checks in the future /// analyze(&typed_context)?; /// ``` /// /// # Errors /// -/// Currently always returns `Ok(())`. Future implementations will return errors -/// for semantic violations that are not type errors, such as: -/// - Use of uninitialized variables -/// - Unreachable code paths -/// - Dead code that should be removed -/// - Control flow violations (e.g., missing return statements) -pub fn analyze(_: &TypedContext) -> anyhow::Result<()> { - // todo!("Type analysis not yet implemented"); +/// Returns an error if any semantic diagnostic with `Error` severity is found. +pub fn analyze(ctx: &TypedContext) -> anyhow::Result<()> { + let result = inference_semantic_analysis::analyze(ctx); + if result.has_errors() { + return Err(anyhow::anyhow!("{}", result.format_errors())); + } Ok(()) } diff --git a/core/semantic-analysis/Cargo.toml b/core/semantic-analysis/Cargo.toml new file mode 100644 index 00000000..02a724d5 --- /dev/null +++ b/core/semantic-analysis/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "inference-semantic-analysis" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } + +[dependencies] +inference-ast.workspace = true +inference-type-checker.workspace = true +thiserror.workspace = true diff --git a/core/semantic-analysis/src/diagnostics.rs b/core/semantic-analysis/src/diagnostics.rs new file mode 100644 index 00000000..5ff328dd --- /dev/null +++ b/core/semantic-analysis/src/diagnostics.rs @@ -0,0 +1,70 @@ +use std::fmt; + +use inference_ast::nodes::Location; + +/// Severity level for semantic analysis diagnostics. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Severity { + Info, + Warning, + Error, +} + +impl fmt::Display for Severity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Severity::Info => write!(f, "info"), + Severity::Warning => write!(f, "warning"), + Severity::Error => write!(f, "error"), + } + } +} + +/// A diagnostic produced by semantic analysis. +#[derive(Debug, Clone)] +pub struct SemanticDiagnostic { + pub severity: Severity, + pub message: String, + pub location: Location, +} + +impl fmt::Display for SemanticDiagnostic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: [{}] {}", self.location, self.severity, self.message) + } +} + +/// Collection of diagnostics from a semantic analysis pass. +#[derive(Debug, Default)] +pub struct SemanticResult { + pub diagnostics: Vec, +} + +impl SemanticResult { + /// Returns `true` if any diagnostic has `Error` severity. + #[must_use] + pub fn has_errors(&self) -> bool { + self.diagnostics + .iter() + .any(|d| d.severity == Severity::Error) + } + + /// Returns only the error-level diagnostics. + #[must_use] + pub fn errors(&self) -> Vec<&SemanticDiagnostic> { + self.diagnostics + .iter() + .filter(|d| d.severity == Severity::Error) + .collect() + } + + /// Formats all diagnostics into a single error message string. + #[must_use] + pub fn format_errors(&self) -> String { + self.errors() + .iter() + .map(|d| d.to_string()) + .collect::>() + .join("\n") + } +} diff --git a/core/semantic-analysis/src/lib.rs b/core/semantic-analysis/src/lib.rs new file mode 100644 index 00000000..63009213 --- /dev/null +++ b/core/semantic-analysis/src/lib.rs @@ -0,0 +1,67 @@ +#![warn(clippy::pedantic)] +//! Semantic Analysis Crate +//! +//! Performs semantic checks on the typed AST that are beyond the scope of type checking. +//! These are scope-bounded checks for language-level constraints that are not type errors. +//! +//! ## Current Checks +//! +//! - **Combined unary operators**: Prohibits chained/unparenthesized unary operator +//! combinations such as `--x`, `-~x`, `!!x`, and parenthesized variants like `-(~x)`. +//! +//! ## Diagnostics +//! +//! The analysis produces diagnostics with three severity levels: +//! - `Error` — semantic violations that must be fixed +//! - `Warning` — suspicious patterns that may indicate bugs +//! - `Info` — informational notes about code style or usage + +pub mod diagnostics; + +use diagnostics::{SemanticDiagnostic, SemanticResult, Severity}; +use inference_ast::nodes::{AstNode, Expression, Literal}; +use inference_type_checker::typed_context::TypedContext; + +/// Runs all semantic analysis passes on the typed AST. +/// +/// Returns a [`SemanticResult`] containing any diagnostics found. +#[must_use] +pub fn analyze(ctx: &TypedContext) -> SemanticResult { + let mut result = SemanticResult::default(); + check_combined_unary_operators(ctx, &mut result); + result +} + +/// Checks for prohibited combined unary operators in expressions. +/// +/// Detects chained prefix unary operators like `--x`, `!!x`, `-~x`, +/// and parenthesized variants like `-(~x)`, `~(-x)`. +fn check_combined_unary_operators(ctx: &TypedContext, result: &mut SemanticResult) { + let prefix_unary_nodes = ctx.filter_nodes(|node| { + matches!(node, AstNode::Expression(Expression::PrefixUnary(_))) + }); + + for node in prefix_unary_nodes { + if let AstNode::Expression(Expression::PrefixUnary(ref prefix_expr)) = node { + if is_combined_unary(&prefix_expr.expression.borrow()) { + result.diagnostics.push(SemanticDiagnostic { + severity: Severity::Error, + message: "combined unary operators are prohibited".to_string(), + location: node.location(), + }); + } + } + } +} + +/// Returns `true` if the expression is itself a unary operator (directly or +/// through parentheses), or a negative numeric literal — indicating a +/// combined/chained unary operator usage. +fn is_combined_unary(expr: &Expression) -> bool { + match expr { + Expression::PrefixUnary(_) => true, + Expression::Parenthesized(inner) => is_combined_unary(&inner.expression.borrow()), + Expression::Literal(Literal::Number(num)) => num.value.starts_with('-'), + _ => false, + } +} diff --git a/core/type-checker/src/errors.rs b/core/type-checker/src/errors.rs index a172ff0c..77ae331f 100644 --- a/core/type-checker/src/errors.rs +++ b/core/type-checker/src/errors.rs @@ -227,9 +227,6 @@ 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}`" )] @@ -360,7 +357,6 @@ 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 143097f3..82de1b2d 100644 --- a/core/type-checker/src/type_checker.rs +++ b/core/type-checker/src/type_checker.rs @@ -1309,23 +1309,6 @@ 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/Cargo.toml b/tests/Cargo.toml index 47e3b0a2..8d5d5a3a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -15,6 +15,7 @@ inference-ast.workspace = true inference-wasm-codegen.workspace = true inference-type-checker.workspace = true inference.workspace = true +inference-semantic-analysis.workspace = true inf-wasmparser.workspace = true tree-sitter.workspace = true tree-sitter-inference.workspace = true diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 05d64024..3cd6e01f 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -4,6 +4,7 @@ mod ast; mod codegen; +mod semantic_analysis; mod type_checker; mod utils; diff --git a/tests/src/semantic_analysis/combined_unary.rs b/tests/src/semantic_analysis/combined_unary.rs new file mode 100644 index 00000000..44581d20 --- /dev/null +++ b/tests/src/semantic_analysis/combined_unary.rs @@ -0,0 +1,127 @@ +//! Tests for the semantic analysis pass that prohibits combined unary operators. +//! +//! These checks run after type checking and detect chained/combined prefix +//! unary operators such as `--x`, `!!x`, `-~x`, and parenthesized variants +//! like `-(~x)`. + +#[cfg(test)] +mod combined_unary_tests { + use crate::utils::build_ast; + use inference_semantic_analysis::diagnostics::Severity; + use inference_type_checker::TypeCheckerBuilder; + + /// Runs parse → type check → semantic analysis on the source, returning the + /// semantic result. + fn run_semantic_analysis( + source: &str, + ) -> inference_semantic_analysis::diagnostics::SemanticResult { + let arena = build_ast(source.to_string()); + let typed_context = TypeCheckerBuilder::build_typed_context(arena) + .expect("type checking should succeed before semantic analysis") + .typed_context(); + inference_semantic_analysis::analyze(&typed_context) + } + + // ── single unary operators should pass ────────────────────────────── + + #[test] + fn single_negate_succeeds() { + let result = run_semantic_analysis(r#"fn test(x: i32) -> i32 { return -(x); }"#); + assert!( + !result.has_errors(), + "Single negation should not produce errors" + ); + } + + #[test] + fn single_bitnot_succeeds() { + let result = run_semantic_analysis(r#"fn test(x: i32) -> i32 { return ~x; }"#); + assert!( + !result.has_errors(), + "Single bitwise NOT should not produce errors" + ); + } + + #[test] + fn single_logical_not_succeeds() { + let result = run_semantic_analysis(r#"fn test(x: bool) -> bool { return !x; }"#); + assert!( + !result.has_errors(), + "Single logical NOT should not produce errors" + ); + } + + #[test] + fn negate_parenthesized_arithmetic_succeeds() { + let result = + run_semantic_analysis(r#"fn test(a: i32, b: i32) -> i32 { return -(a + b); }"#); + assert!( + !result.has_errors(), + "Negation of parenthesized arithmetic should not produce errors" + ); + } + + // ── combined / chained unary operators should error ────────────────── + + #[test] + fn double_negate_is_prohibited() { + let result = run_semantic_analysis(r#"fn test(x: i32) -> i32 { return --(x); }"#); + assert!(result.has_errors(), "Double negation should be prohibited"); + let errors = result.errors(); + assert!( + errors + .iter() + .any(|d| d.message.contains("combined unary operators")), + "Error should mention combined unary operators" + ); + assert!( + errors.iter().all(|d| d.severity == Severity::Error), + "Combined unary diagnostics should be errors" + ); + } + + #[test] + fn double_negate_literal_is_prohibited() { + let result = run_semantic_analysis(r#"fn test() -> i32 { return --42; }"#); + assert!( + result.has_errors(), + "Double negation of literal should be prohibited" + ); + } + + #[test] + fn bitnot_combined_with_negate_is_prohibited() { + let result = run_semantic_analysis(r#"fn test(x: i32) -> i32 { return ~-(x); }"#); + assert!( + result.has_errors(), + "Combining BitNot and Neg should be prohibited" + ); + } + + #[test] + fn negate_combined_with_bitnot_is_prohibited() { + let result = run_semantic_analysis(r#"fn test(x: i32) -> i32 { return -(~x); }"#); + assert!( + result.has_errors(), + "Combining Neg and BitNot should be prohibited" + ); + } + + #[test] + fn bitnot_then_neg_literal_is_prohibited() { + let result = run_semantic_analysis(r#"fn test() -> i32 { return -~42; }"#); + assert!( + result.has_errors(), + "Combined unary operators on literal should be prohibited" + ); + } + + #[test] + fn double_logical_not_is_prohibited() { + let result = run_semantic_analysis(r#"fn test(x: bool) -> bool { return !!x; }"#); + assert!( + result.has_errors(), + "Double logical NOT should be prohibited" + ); + } +} diff --git a/tests/src/semantic_analysis/mod.rs b/tests/src/semantic_analysis/mod.rs new file mode 100644 index 00000000..f849f283 --- /dev/null +++ b/tests/src/semantic_analysis/mod.rs @@ -0,0 +1 @@ +mod combined_unary; diff --git a/tests/src/type_checker/coverage.rs b/tests/src/type_checker/coverage.rs index 80df4124..a1c74344 100644 --- a/tests/src/type_checker/coverage.rs +++ b/tests/src/type_checker/coverage.rs @@ -751,15 +751,11 @@ 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_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 - ); - } + assert!( + result.is_ok(), + "Double unary neg should work, got: {:?}", + result.err() + ); } // 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 0622a254..7b4bd031 100644 --- a/tests/src/type_checker/type_checker.rs +++ b/tests/src/type_checker/type_checker.rs @@ -2166,12 +2166,10 @@ 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!( - err_msg.contains("combined unary operators"), - "Error should mention combined unary operators, got: {}", - err_msg + result.is_ok(), + "Double negation should succeed, got: {:?}", + result.err() ); } } @@ -2284,12 +2282,10 @@ 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!( - err_msg.contains("combined unary operators"), - "Error should mention combined unary operators, got: {}", - err_msg + result.is_ok(), + "Combining BitNot and Neg should succeed, got: {:?}", + result.err() ); } @@ -2297,25 +2293,10 @@ 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!( - 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 + result.is_ok(), + "Combining Neg and BitNot should succeed, got: {:?}", + result.err() ); } } @@ -2351,12 +2332,10 @@ 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!( - err_msg.contains("combined unary operators"), - "Error should mention combined unary operators, got: {}", - err_msg + result.is_ok(), + "Double logical NOT should succeed, got: {:?}", + result.err() ); } } From a6c429502aae9f262c6ce16156fe991bddcc0b9e Mon Sep 17 00:00:00 2001 From: Georgii Plotnikov Date: Wed, 28 Jan 2026 04:56:05 +0500 Subject: [PATCH 3/4] Update core/semantic-analysis/Cargo.toml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Georgii Plotnikov --- core/semantic-analysis/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/core/semantic-analysis/Cargo.toml b/core/semantic-analysis/Cargo.toml index 02a724d5..bb346248 100644 --- a/core/semantic-analysis/Cargo.toml +++ b/core/semantic-analysis/Cargo.toml @@ -9,4 +9,3 @@ repository = { workspace = true } [dependencies] inference-ast.workspace = true inference-type-checker.workspace = true -thiserror.workspace = true From f423d437925a573ef5fef9535700831cd9d717b3 Mon Sep 17 00:00:00 2001 From: Georgii Plotnikov Date: Wed, 28 Jan 2026 14:41:32 +0500 Subject: [PATCH 4/4] Update core/inference/src/lib.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Georgii Plotnikov --- core/inference/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/inference/src/lib.rs b/core/inference/src/lib.rs index b47edcb7..4f33f521 100644 --- a/core/inference/src/lib.rs +++ b/core/inference/src/lib.rs @@ -514,6 +514,7 @@ pub fn analyze(ctx: &TypedContext) -> anyhow::Result<()> { if result.has_errors() { return Err(anyhow::anyhow!("{}", result.format_errors())); } + Ok(()) } /// Generates WebAssembly binary format from the typed AST. ///