-
Notifications
You must be signed in to change notification settings - Fork 15
Prohibit combined unary operators #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kaankacar
wants to merge
6
commits into
Inferara:main
Choose a base branch
from
kaankacar:82-bug-fix-prohibit-combined-unary-clean
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
10f4d2e
Prohibit combined unary operators
kaankacar d703ced
Move combined unary operator check to new semantic-analysis crate
kaankacar a6c4295
Update core/semantic-analysis/Cargo.toml
0xGeorgii fd41dc3
Merge branch 'main' into 82-bug-fix-prohibit-combined-unary-clean
0xGeorgii f423d43
Update core/inference/src/lib.rs
0xGeorgii 0983a8d
Merge remote-tracking branch 'origin/main' into 82-bug-fix-prohibit-c…
kaankacar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| [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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<SemanticDiagnostic>, | ||
| } | ||
|
|
||
| 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::<Vec<_>>() | ||
| .join("\n") | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
|
|
||
| mod ast; | ||
| mod codegen; | ||
| mod semantic_analysis; | ||
| mod type_checker; | ||
| mod utils; | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| mod combined_unary; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.