Skip to content

Conversation

@ascandone
Copy link
Contributor

@ascandone ascandone commented Dec 5, 2025

This PR fixes the grammar of the prefix - operator.
Before, it was part of the NUMBER token.
Now, it's a prefix operator.
This allows the parser to correctly recognise infix -. (Which would previously require the usage of () in order not to emit a parsing error)

Unlike the old machine impl, this will also allow variable expressions: -$x

The PR addresses all the involved parts: interpreter, static check (including asset inference), hovering, goto, etc.

Semantics

The prefix - only works with numbers and monetaries

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Decoupled negative-number lexing from NUMBER, added unary prefix-minus grammar and AST node, and implemented evaluation/type-checking/hover support and runtime negation for numeric and monetary values; regenerated parser artifacts and added tests for prefix and infix minus scenarios.

Changes

Cohort / File(s) Summary
Grammar & Lexer
Lexer.g4, Numscript.g4
Removed optional leading MINUS from NUMBER; added prefixExpr alternative (op=MINUS valueExpr) to valueExpr to support unary minus.
Generated Parser Artifacts
internal/parser/antlrParser/...
Lexer.interp, Numscript.interp, lexer.go, numscript_parser.go, numscript_listener.go, numscript_base_listener.go
Regenerated parser outputs to reflect grammar changes: added PrefixExprContext, listener hooks EnterPrefixExpr/ExitPrefixExpr, and updated serialized ATN/state data.
AST & Parser Integration
internal/parser/ast.go, internal/parser/parser.go
Added PrefixOperator and Prefix AST node; parser constructs Prefix from PrefixExprContext and wires it into valueExpr cases.
Interpreter & Evaluation
internal/interpreter/evaluate_expr.go, internal/interpreter/infix_overloads.go
Added Prefix handling in evaluateExpr, new unaryNegOp flow, introduced opNeg interface and evalNeg implementations for MonetaryInt and Monetary to perform arithmetic negation.
Static Analysis & Type Checking
internal/analysis/check.go
Added type-checking for Prefix (ensure inner expr is Number or Monetary), helper checkHasOneOfTypes, and static evaluation support to negate constant numeric expressions.
Hover / Editor Integration
internal/analysis/hover.go
Added hover support for Prefix by delegating to the inner expression for hover information.
Tests / Testdata
internal/interpreter/testdata/script-tests/*
minus-infix-monetary.num, minus-infix-number.num, minus-prefix-number.num
Added tests covering minus as infix for monetary/number and prefix minus cases (including monetary ranges with leading minus).

Sequence Diagram

sequenceDiagram
    participant Lexer
    participant Parser
    participant AST as "AST Builder"
    participant Checker as "Type Checker"
    participant Interpreter
    participant Evaluator

    Lexer->>Lexer: Tokenize "-5" -> MINUS, NUMBER
    note over Lexer: NUMBER no longer includes leading '-'
    Lexer->>Parser: emit tokens
    Parser->>Parser: match prefixExpr (MINUS valueExpr)
    Parser->>AST: create Prefix node (op='-', expr=NumberLiteral(5))
    AST->>Checker: checkType(Prefix)
    Checker->>Checker: ensure inner is Number|Monetary
    AST->>Interpreter: evaluate Prefix
    Interpreter->>Evaluator: unaryNegOp(innerExpr)
    Evaluator->>Evaluator: evaluate inner -> Number or Monetary
    Evaluator->>Evaluator: evalNeg() -> negated value
    Evaluator->>Interpreter: return negated Value
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • internal/parser/antlrParser/* — verify regenerated ATN/state changes and no manual edits.
  • internal/analysis/check.go — new helper(s) and duplicate/helper placement; ensure no accidental duplicate declarations.
  • internal/interpreter/infix_overloads.go — big.Int handling and preservation of monetary Asset when negating.
  • Precedence/associativity — confirm prefix minus interacts correctly with infix operators and parsing precedence.

Poem

🐇 I bit a minus off the number's tail,
Now it hops alone along the trail.
Prefix makes the AST bow down,
Evaluator turns smiles to frowns.
Numbers, monies—negated with a twitch. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: refactored grammar of prefix negation' directly and concisely describes the main change: refactoring the grammar to handle prefix negation properly.
Description check ✅ Passed The description comprehensively explains the change, rationale, and scope—covering the grammar refactoring, parsing improvements, variable support, and semantic constraints.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/fix-minus-prefix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ascandone ascandone requested a review from Azorlogh December 5, 2025 12:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/interpreter/testdata/script-tests/prefix-minus-monetary.num (1)

1-4: Test file name may be misleading.

This test demonstrates infix subtraction ([EUR/2 100] - [EUR/2 1]), not prefix negation. The PR enables both features, but the filename prefix-minus-monetary.num suggests prefix usage (e.g., -[EUR/2 1]).

Consider either:

  1. Renaming to infix-minus-monetary.num, or
  2. Adding a true prefix test case such as send -[EUR/2 -100] if semantically valid
internal/interpreter/testdata/script-tests/prefix-minus-number.num (1)

1-4: Test file name may be misleading.

Similar to the monetary test, this demonstrates infix subtraction (100-1) rather than prefix negation. For a prefix test, consider expressions like [EUR/2 -$offset] where $offset is a variable.

The current tests are valuable for validating infix parsing without parentheses, but the naming doesn't reflect the content.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 06a7199 and cccfc2c.

⛔ Files ignored due to path filters (3)
  • internal/interpreter/testdata/script-tests/prefix-minus-monetary.num.specs.json is excluded by !**/*.json
  • internal/interpreter/testdata/script-tests/prefix-minus-number.num.specs.json is excluded by !**/*.json
  • internal/parser/__snapshots__/parser_test.snap is excluded by !**/*.snap, !**/*.snap
📒 Files selected for processing (15)
  • Lexer.g4 (1 hunks)
  • Numscript.g4 (1 hunks)
  • internal/analysis/hover.go (1 hunks)
  • internal/interpreter/evaluate_expr.go (2 hunks)
  • internal/interpreter/infix_overloads.go (1 hunks)
  • internal/interpreter/testdata/script-tests/prefix-minus-monetary.num (1 hunks)
  • internal/interpreter/testdata/script-tests/prefix-minus-number.num (1 hunks)
  • internal/parser/antlrParser/Lexer.interp (1 hunks)
  • internal/parser/antlrParser/Numscript.interp (1 hunks)
  • internal/parser/antlrParser/lexer.go (2 hunks)
  • internal/parser/antlrParser/numscript_base_listener.go (1 hunks)
  • internal/parser/antlrParser/numscript_listener.go (2 hunks)
  • internal/parser/antlrParser/numscript_parser.go (57 hunks)
  • internal/parser/ast.go (2 hunks)
  • internal/parser/parser.go (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: ascandone
Repo: formancehq/numscript PR: 55
File: internal/parser/antlrParser/numscript_parser.go:2953-2967
Timestamp: 2025-04-22T16:45:07.842Z
Learning: In numscript, type constraints for color expressions are not enforced at the grammar level but are instead handled during static analysis or at runtime. The grammar is intentionally kept permissive (accepting any valueExpr after the RESTRICT token) to maintain separation of concerns.
📚 Learning: 2025-02-06T17:48:48.962Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 31
File: internal/parser/parser_test.go:115-126
Timestamp: 2025-02-06T17:48:48.962Z
Learning: The numscript parser's fault tolerance is built into the infrastructure and handles errors gracefully for all clause types. Tests in parser_fault_tolerance_test.go cover both inorder and oneof constructs as they share the same parsing infrastructure.

Applied to files:

  • internal/parser/antlrParser/numscript_parser.go
📚 Learning: 2025-04-22T16:45:07.842Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 55
File: internal/parser/antlrParser/numscript_parser.go:2953-2967
Timestamp: 2025-04-22T16:45:07.842Z
Learning: In numscript, type constraints for color expressions are not enforced at the grammar level but are instead handled during static analysis or at runtime. The grammar is intentionally kept permissive (accepting any valueExpr after the RESTRICT token) to maintain separation of concerns.

Applied to files:

  • internal/parser/antlrParser/numscript_parser.go
📚 Learning: 2025-02-06T17:48:48.961Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 31
File: internal/parser/parser_test.go:115-126
Timestamp: 2025-02-06T17:48:48.961Z
Learning: Edge cases for oneof destination parsing in numscript are covered by the existing inorder parsing fault tolerance tests, as both constructs share similar parsing logic.

Applied to files:

  • internal/parser/antlrParser/numscript_parser.go
🧬 Code graph analysis (8)
internal/interpreter/infix_overloads.go (1)
internal/interpreter/interpreter.go (1)
  • InterpreterError (73-76)
internal/parser/antlrParser/numscript_base_listener.go (1)
internal/parser/antlrParser/numscript_parser.go (1)
  • PrefixExprContext (737-740)
internal/parser/antlrParser/numscript_listener.go (1)
internal/parser/antlrParser/numscript_parser.go (1)
  • PrefixExprContext (737-740)
internal/parser/ast.go (1)
internal/parser/range.go (1)
  • Range (13-16)
internal/parser/parser.go (3)
internal/parser/antlrParser/numscript_parser.go (1)
  • PrefixExprContext (737-740)
internal/parser/ast.go (4)
  • Prefix (21-21)
  • Prefix (76-80)
  • PrefixOperator (24-24)
  • ValueExpr (8-11)
internal/parser/range.go (1)
  • Range (13-16)
internal/analysis/hover.go (1)
internal/parser/ast.go (2)
  • Prefix (21-21)
  • Prefix (76-80)
internal/interpreter/evaluate_expr.go (4)
internal/parser/ast.go (5)
  • Prefix (21-21)
  • Prefix (76-80)
  • PrefixOperator (24-24)
  • PrefixOperatorMinus (27-27)
  • ValueExpr (8-11)
internal/utils/utils.go (1)
  • NonExhaustiveMatchPanic (8-10)
internal/interpreter/value.go (5)
  • Value (12-15)
  • Monetary (22-25)
  • Monetary (30-30)
  • MonetaryInt (21-21)
  • MonetaryInt (29-29)
internal/interpreter/interpreter.go (1)
  • InterpreterError (73-76)
internal/parser/antlrParser/numscript_parser.go (1)
internal/parser/ast.go (6)
  • ValueExpr (8-11)
  • Statement (266-269)
  • Source (135-138)
  • Destination (202-205)
  • KeptOrDestination (247-249)
  • SentValue (286-289)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Dirty
  • GitHub Check: Tests
🔇 Additional comments (13)
Lexer.g4 (1)

41-41: LGTM!

Removing the optional leading minus from NUMBER enables the parser to correctly distinguish between prefix negation (e.g., -$x) and infix subtraction (e.g., a - b). This aligns with the PR objectives.

internal/parser/antlrParser/Lexer.interp (1)

145-145: Generated file looks consistent with grammar changes.

The ATN data reflects the regenerated lexer from the updated Lexer.g4. No issues detected.

internal/analysis/hover.go (1)

186-188: LGTM!

The Prefix handling correctly delegates hover resolution to the inner expression. This follows the established pattern for composite expressions in this function.

internal/parser/antlrParser/numscript_listener.go (1)

22-24: Generated listener interface updated correctly.

The EnterPrefixExpr and ExitPrefixExpr methods are correctly added to the listener interface, matching the new prefixExpr grammar production.

Also applies to: 157-159

internal/parser/antlrParser/Numscript.interp (1)

115-115: Generated file looks consistent with grammar changes.

The ATN data reflects the regenerated parser from the updated Numscript.g4 with the new prefixExpr production. No issues detected.

Numscript.g4 (1)

22-22: Prefix minus operator is correctly implemented and functional.

The prefixExpr rule successfully adds support for unary minus expressions. The parser correctly generates PrefixExprContext and handles cases like [USD/2 -50] and [COIN -10] as shown in the test files. The operator properly binds to its operand with expected behavior.

internal/parser/antlrParser/numscript_base_listener.go (1)

47-52: Generated code additions look correct.

The new EnterPrefixExpr and ExitPrefixExpr listener hooks follow the established pattern for other expression types and are correctly placed in the listener interface.

internal/parser/antlrParser/lexer.go (1)

71-231: Generated ATN data updated correctly.

The serialized ATN data has been regenerated to reflect the grammar changes. The MINUS token (line 300: LexerMINUS = 30) remains correctly defined, and the ATN now properly handles the minus sign as a separate token rather than part of the NUMBER token.

internal/parser/parser.go (1)

365-370: LGTM - Prefix expression parsing is correctly implemented.

The new case for PrefixExprContext follows the established pattern used for InfixExprContext. The implementation correctly:

  • Extracts the range from the context
  • Converts the operator token text to PrefixOperator
  • Recursively parses the inner ValueExpr
internal/interpreter/evaluate_expr.go (1)

226-243: LGTM - Unary negation operator implementation is correct.

The unaryNegOp method follows the established pattern used by plusOp and subOp, correctly handling both Monetary and MonetaryInt types through the opNeg interface.

internal/parser/ast.go (1)

21-21: LGTM - Prefix AST node is well-structured.

The Prefix struct correctly:

  • Implements ValueExpr via the marker method (line 21)
  • Embeds Range for source location tracking
  • Follows the same pattern as BinaryInfix for consistency

Also applies to: 76-80

internal/interpreter/infix_overloads.go (1)

83-105: LGTM! Clean implementation of unary negation support.

The opNeg interface and implementations follow the established patterns from opAdd and opSub. The unused st *programState parameter maintains interface consistency with other operators, which is a reasonable design choice. The negation logic correctly preserves the asset type for Monetary values while negating only the amount.

internal/parser/antlrParser/numscript_parser.go (1)

1-1: Auto-generated parser code - no manual review required.

This file is generated from Numscript.g4 by ANTLR 4.13.2. The generated code correctly introduces PrefixExprContext for handling unary minus expressions with appropriate precedence (level 5, higher than infix operators at levels 3-4). Any issues should be addressed in the grammar file rather than this generated code.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 18 files

Prompt for AI agents (all 1 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="internal/interpreter/testdata/script-tests/prefix-minus-monetary.num">

<violation number="1" location="internal/interpreter/testdata/script-tests/prefix-minus-monetary.num:1">
P2: Test file name suggests prefix minus (negation) test but content tests infix minus (subtraction). For a file named `prefix-minus-monetary.num`, consider testing actual prefix negation like `-[EUR/2 100]` rather than infix subtraction `[EUR/2 100] - [EUR/2 1]`. Note: `sub-monetaries.num` already exists for testing infix subtraction between monetaries.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

@ascandone ascandone force-pushed the fix/fix-minus-prefix branch from cccfc2c to 77caca1 Compare December 5, 2025 13:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
internal/parser/ast.go (1)

24-28: Type mismatch: PrefixOperatorMinus declared as InfixOperator instead of PrefixOperator.

The constant PrefixOperatorMinus is typed as InfixOperator, but the Prefix.Operator field expects PrefixOperator. This will require type conversion or cause type-check issues.

Apply this fix:

 const (
-	PrefixOperatorMinus InfixOperator = "-"
+	PrefixOperatorMinus PrefixOperator = "-"
 )
🧹 Nitpick comments (1)
internal/interpreter/testdata/script-tests/minus-infix-number.num (1)

1-4: Test case correctly validates infix subtraction.

This test appropriately demonstrates that the refactored grammar now parses 100-1 as infix subtraction without requiring parentheses, which was the core objective of the PR.

Consider adding test cases for edge cases to strengthen coverage:

  • Chained subtraction: 100-1-2
  • Prefix negation followed by infix: -100-1
  • Double minus (prefix on negative): 100--1
  • Prefix negation of variables: -$x (mentioned in PR objectives)

These could be separate test files or additional test cases if the test harness supports multiple scenarios per file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cccfc2c and 77caca1.

⛔ Files ignored due to path filters (3)
  • internal/interpreter/testdata/script-tests/minus-infix-monetary.num.specs.json is excluded by !**/*.json
  • internal/interpreter/testdata/script-tests/minus-infix-number.num.specs.json is excluded by !**/*.json
  • internal/parser/__snapshots__/parser_test.snap is excluded by !**/*.snap, !**/*.snap
📒 Files selected for processing (15)
  • Lexer.g4 (1 hunks)
  • Numscript.g4 (1 hunks)
  • internal/analysis/hover.go (1 hunks)
  • internal/interpreter/evaluate_expr.go (2 hunks)
  • internal/interpreter/infix_overloads.go (1 hunks)
  • internal/interpreter/testdata/script-tests/minus-infix-monetary.num (1 hunks)
  • internal/interpreter/testdata/script-tests/minus-infix-number.num (1 hunks)
  • internal/parser/antlrParser/Lexer.interp (1 hunks)
  • internal/parser/antlrParser/Numscript.interp (1 hunks)
  • internal/parser/antlrParser/lexer.go (2 hunks)
  • internal/parser/antlrParser/numscript_base_listener.go (1 hunks)
  • internal/parser/antlrParser/numscript_listener.go (2 hunks)
  • internal/parser/antlrParser/numscript_parser.go (57 hunks)
  • internal/parser/ast.go (2 hunks)
  • internal/parser/parser.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • internal/analysis/hover.go
  • internal/parser/antlrParser/numscript_listener.go
  • internal/parser/antlrParser/Lexer.interp
  • internal/interpreter/infix_overloads.go
  • internal/interpreter/evaluate_expr.go
  • internal/parser/parser.go
  • Lexer.g4
  • Numscript.g4
  • internal/parser/antlrParser/Numscript.interp
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-02-06T17:48:48.962Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 31
File: internal/parser/parser_test.go:115-126
Timestamp: 2025-02-06T17:48:48.962Z
Learning: The numscript parser's fault tolerance is built into the infrastructure and handles errors gracefully for all clause types. Tests in parser_fault_tolerance_test.go cover both inorder and oneof constructs as they share the same parsing infrastructure.

Applied to files:

  • internal/parser/antlrParser/numscript_parser.go
📚 Learning: 2025-04-22T16:45:07.842Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 55
File: internal/parser/antlrParser/numscript_parser.go:2953-2967
Timestamp: 2025-04-22T16:45:07.842Z
Learning: In numscript, type constraints for color expressions are not enforced at the grammar level but are instead handled during static analysis or at runtime. The grammar is intentionally kept permissive (accepting any valueExpr after the RESTRICT token) to maintain separation of concerns.

Applied to files:

  • internal/parser/antlrParser/numscript_parser.go
📚 Learning: 2025-02-06T17:48:48.961Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 31
File: internal/parser/parser_test.go:115-126
Timestamp: 2025-02-06T17:48:48.961Z
Learning: Edge cases for oneof destination parsing in numscript are covered by the existing inorder parsing fault tolerance tests, as both constructs share similar parsing logic.

Applied to files:

  • internal/parser/antlrParser/numscript_parser.go
🧬 Code graph analysis (3)
internal/parser/ast.go (1)
internal/parser/range.go (1)
  • Range (13-16)
internal/parser/antlrParser/numscript_parser.go (1)
internal/parser/ast.go (7)
  • ValueExpr (8-11)
  • VarDeclaration (321-326)
  • Statement (266-269)
  • Source (135-138)
  • Destination (202-205)
  • KeptOrDestination (247-249)
  • SentValue (286-289)
internal/parser/antlrParser/numscript_base_listener.go (1)
internal/parser/antlrParser/numscript_parser.go (1)
  • PrefixExprContext (737-740)
🔇 Additional comments (6)
internal/interpreter/testdata/script-tests/minus-infix-monetary.num (1)

1-4: Infix monetary subtraction test matches naming and PR intent

The script correctly exercises infix - between two monetary literals with standard source/destination bindings, and the filename now clearly reflects that this is an infix-minus monetary test rather than prefix negation. Looks good as a focused regression/coverage case for the new grammar behavior.

internal/parser/ast.go (1)

76-80: LGTM!

The Prefix struct is well-designed with appropriate fields: Range for source location, Operator for the prefix operator type, and Expr for the operand expression.

internal/parser/antlrParser/numscript_base_listener.go (1)

47-51: LGTM!

Auto-generated listener methods for PrefixExpr follow the established pattern and correctly implement the NumscriptListener interface.

internal/parser/antlrParser/numscript_parser.go (2)

737-790: LGTM!

The auto-generated PrefixExprContext correctly implements the context for prefix expressions with proper token handling and listener integration.


1407-1425: LGTM!

The prefix expression parsing logic correctly handles the unary minus operator with appropriate precedence (5), ensuring proper binding relative to infix operators.

internal/parser/antlrParser/lexer.go (1)

1-1: Generated lexer code correctly reflects grammar changes — LGTM.

This file is auto-generated from Lexer.g4 by ANTLR 4.13.2. The NUMBER token rule excludes a leading minus sign ([0-9]+ ('_' [0-9]+)*), while MINUS is defined as a separate token ('-'). This allows proper parsing of prefix negation (e.g., -5 parses as unary MINUS followed by NUMBER) versus infix usage. The updated serializedATN data correctly encodes these grammar rules.

@ascandone ascandone marked this pull request as draft December 5, 2025 13:16
@ascandone ascandone force-pushed the fix/fix-minus-prefix branch from 77caca1 to eff50a4 Compare December 5, 2025 13:24
@codecov
Copy link

codecov bot commented Dec 5, 2025

Codecov Report

❌ Patch coverage is 46.77419% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.83%. Comparing base (06a7199) to head (eff50a4).

Files with missing lines Patch % Lines
internal/analysis/check.go 0.00% 25 Missing ⚠️
internal/interpreter/evaluate_expr.go 72.22% 4 Missing and 1 partial ⚠️
internal/analysis/hover.go 0.00% 2 Missing ⚠️
internal/parser/ast.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #103      +/-   ##
==========================================
- Coverage   68.13%   67.83%   -0.31%     
==========================================
  Files          45       45              
  Lines        4290     4352      +62     
==========================================
+ Hits         2923     2952      +29     
- Misses       1209     1241      +32     
- Partials      158      159       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ascandone
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

@ascandone: I'll review the changes in this PR.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Numscript.g4 (1)

14-26: Check unary-minus precedence: op=MINUS valueExpr currently binds to entire remaining expression

With the current grammar where all valueExpr alternatives are at the same precedence level, expressions like -1 + 2 are parsed as -(1 + 2) rather than (-1) + 2. This occurs because the prefixExpr alternative on line 22 calls valueExpr recursively for its operand, which can match the entire additive expression including the + 2.

To preserve standard arithmetic precedence where unary minus binds only to its immediate operand and +/- remain left-associative, restructure into explicit precedence layers:

valueExpr        : additiveExpr ;
additiveExpr     : multiplicativeExpr ( (PLUS | MINUS) multiplicativeExpr )* ;
multiplicativeExpr
                 : prefixExpr ( DIV prefixExpr )* ;
prefixExpr       : MINUS prefixExpr
                 | primary ;
primary          : VARIABLE_NAME
                 | ASSET
                 | STRING
                 | ACCOUNT_START accountLiteralPart (COLON accountLiteralPart)*
                 | NUMBER
                 | PERCENTAGE_PORTION_LITERAL
                 | monetaryLit
                 | LPARENS valueExpr RPARENS
                 | functionCall ;
♻️ Duplicate comments (1)
internal/parser/ast.go (1)

13-28: Prefix AST node and operator typing are consistent and fix the earlier mismatch

  • Prefix implements ValueExpr via func (*Prefix) valueExpr(), so it’s fully part of the expression AST.
  • Introducing PrefixOperator and declaring PrefixOperatorMinus as PrefixOperator = "-" fixes the earlier type mismatch where it was an InfixOperator.
  • The Prefix struct (Range, Operator, Expr) matches how other expression nodes are modeled.

Looks good from the AST side.

Also applies to: 76-80

🧹 Nitpick comments (1)
internal/analysis/check.go (1)

471-479: Type checking for prefix minus is consistent with binary minus and constrained to numeric/monetary

The new *parser.Prefix handling in checkTypeOf:

  • Unifies the prefix node’s type with its inner expression (res.unifyNodeWith(lit, res.GetExprType(lit.Expr))), matching how other composite expressions are treated.
  • Uses checkHasOneOfTypes(lit.Expr, []string{TypeNumber, TypeMonetary}) to restrict the operand of unary minus to number or monetary, and emits a TypeMismatch if it isn’t.

The helper checkHasOneOfTypes mirrors checkInfixOverload’s semantics for a single operand and centralises the “one of these types or any” pattern, which keeps the code clear and avoids duplicating the diagnostic logic.

One minor nuance: checkHasOneOfTypes passes allowed[0] as the type hint into checkTypeOf, so for -<monetary> the hint used in some diagnostics will be number, not monetary, but assertHasType still enforces the actual required type at the call sites. This is mostly cosmetic and probably acceptable.

Also applies to: 524-536

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 77caca1 and eff50a4.

⛔ Files ignored due to path filters (4)
  • internal/interpreter/testdata/script-tests/minus-infix-monetary.num.specs.json is excluded by !**/*.json
  • internal/interpreter/testdata/script-tests/minus-infix-number.num.specs.json is excluded by !**/*.json
  • internal/interpreter/testdata/script-tests/minus-prefix-number.num.specs.json is excluded by !**/*.json
  • internal/parser/__snapshots__/parser_test.snap is excluded by !**/*.snap, !**/*.snap
📒 Files selected for processing (17)
  • Lexer.g4 (1 hunks)
  • Numscript.g4 (1 hunks)
  • internal/analysis/check.go (3 hunks)
  • internal/analysis/hover.go (1 hunks)
  • internal/interpreter/evaluate_expr.go (2 hunks)
  • internal/interpreter/infix_overloads.go (1 hunks)
  • internal/interpreter/testdata/script-tests/minus-infix-monetary.num (1 hunks)
  • internal/interpreter/testdata/script-tests/minus-infix-number.num (1 hunks)
  • internal/interpreter/testdata/script-tests/minus-prefix-number.num (1 hunks)
  • internal/parser/antlrParser/Lexer.interp (1 hunks)
  • internal/parser/antlrParser/Numscript.interp (1 hunks)
  • internal/parser/antlrParser/lexer.go (2 hunks)
  • internal/parser/antlrParser/numscript_base_listener.go (1 hunks)
  • internal/parser/antlrParser/numscript_listener.go (2 hunks)
  • internal/parser/antlrParser/numscript_parser.go (57 hunks)
  • internal/parser/ast.go (2 hunks)
  • internal/parser/parser.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/interpreter/testdata/script-tests/minus-prefix-number.num
🚧 Files skipped from review as they are similar to previous changes (8)
  • Lexer.g4
  • internal/parser/antlrParser/Lexer.interp
  • internal/interpreter/testdata/script-tests/minus-infix-monetary.num
  • internal/parser/antlrParser/numscript_base_listener.go
  • internal/parser/antlrParser/Numscript.interp
  • internal/interpreter/testdata/script-tests/minus-infix-number.num
  • internal/analysis/hover.go
  • internal/parser/parser.go
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-03-18T11:14:21.411Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 45
File: internal/parser/ast.go:21-21
Timestamp: 2025-03-18T11:14:21.411Z
Learning: In Go, empty methods like `valueExpr()` are commonly used as interface markers and don't require dedicated unit tests as they have no functionality to test. The actual behavior is tested through integration tests that use the types in context.

Applied to files:

  • internal/parser/ast.go
📚 Learning: 2025-05-30T13:51:20.130Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 62
File: internal/interpreter/evaluate_expr.go:143-144
Timestamp: 2025-05-30T13:51:20.130Z
Learning: In the numscript interpreter color system: `nil` color means "any color" (no color filter applied), while an empty string `""` color is treated as a specific color value, distinct from nil. The `evaluateColor` function in `internal/interpreter/evaluate_expr.go` should return a pointer to an empty string when no color expression is provided, not nil, to maintain this semantic distinction.

Applied to files:

  • internal/interpreter/evaluate_expr.go
📚 Learning: 2025-02-06T17:48:48.962Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 31
File: internal/parser/parser_test.go:115-126
Timestamp: 2025-02-06T17:48:48.962Z
Learning: The numscript parser's fault tolerance is built into the infrastructure and handles errors gracefully for all clause types. Tests in parser_fault_tolerance_test.go cover both inorder and oneof constructs as they share the same parsing infrastructure.

Applied to files:

  • internal/parser/antlrParser/numscript_parser.go
📚 Learning: 2025-04-22T16:45:07.842Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 55
File: internal/parser/antlrParser/numscript_parser.go:2953-2967
Timestamp: 2025-04-22T16:45:07.842Z
Learning: In numscript, type constraints for color expressions are not enforced at the grammar level but are instead handled during static analysis or at runtime. The grammar is intentionally kept permissive (accepting any valueExpr after the RESTRICT token) to maintain separation of concerns.

Applied to files:

  • internal/parser/antlrParser/numscript_parser.go
📚 Learning: 2025-02-06T17:48:48.961Z
Learnt from: ascandone
Repo: formancehq/numscript PR: 31
File: internal/parser/parser_test.go:115-126
Timestamp: 2025-02-06T17:48:48.961Z
Learning: Edge cases for oneof destination parsing in numscript are covered by the existing inorder parsing fault tolerance tests, as both constructs share similar parsing logic.

Applied to files:

  • internal/parser/antlrParser/numscript_parser.go
🧬 Code graph analysis (5)
internal/interpreter/infix_overloads.go (1)
internal/interpreter/interpreter.go (1)
  • InterpreterError (73-76)
internal/parser/ast.go (1)
internal/parser/range.go (1)
  • Range (13-16)
internal/parser/antlrParser/numscript_listener.go (1)
internal/parser/antlrParser/numscript_parser.go (1)
  • PrefixExprContext (737-740)
internal/parser/antlrParser/numscript_parser.go (1)
internal/parser/ast.go (7)
  • ValueExpr (8-11)
  • VarDeclaration (321-326)
  • Statement (266-269)
  • Source (135-138)
  • Destination (202-205)
  • KeptOrDestination (247-249)
  • SentValue (286-289)
internal/analysis/check.go (2)
internal/parser/ast.go (4)
  • Prefix (21-21)
  • Prefix (76-80)
  • PrefixOperatorMinus (27-27)
  • ValueExpr (8-11)
internal/analysis/diagnostic_kind.go (2)
  • TypeMismatch (132-135)
  • TypeMismatch (141-143)
🔇 Additional comments (8)
internal/parser/antlrParser/lexer.go (1)

71-231: Regenerated lexer ATN looks consistent with grammar changes

The only modification here is the serialized ATN; assuming this was regenerated from the updated Lexer.g4 (NUMBER without leading - and separate MINUS token), it’s fine and shouldn’t be edited by hand.

internal/parser/antlrParser/numscript_parser.go (2)

737-790: PrefixExprContext wiring matches the new grammar

The PrefixExprContext struct, its ValueExpr() accessor, MINUS token accessor, and Enter/Exit hooks are consistent with the new prefixExpr alternative in the grammar and the existing pattern used for other labeled alternatives. Nothing to change here.


1259-1579: Generated valueExpr parser logic aligns with the new prefix-minus alternative

The regenerated valueExpr method correctly:

  • Treats MINUS as a base alternative that creates a PrefixExprContext, capturing the operator token.
  • Recurses into valueExpr(5) for the operand, and
  • Leaves infix DIV / PLUS / MINUS handling unchanged in the left-recursive section.

This matches the updated grammar; any precedence/associativity tuning should be done in Numscript.g4 rather than editing this generated code directly.

internal/parser/antlrParser/numscript_listener.go (1)

22-24: Listener interface correctly extended for prefixExpr

The additions of EnterPrefixExpr and ExitPrefixExpr are consistent with the new PrefixExprContext and follow the existing listener pattern. No further changes needed here.

Also applies to: 157-159

internal/interpreter/evaluate_expr.go (2)

87-96: Prefix evaluation wiring matches new AST operator

The new case *parser.Prefix branch correctly:

  • Switches on expr.Operator with a dedicated PrefixOperatorMinus arm.
  • Delegates to unaryNegOp and treats any future operators as a panic via NonExhaustiveMatchPanic.

This is consistent with how binary infix operators are handled.


226-243: unaryNegOp correctly restricts prefix minus to numeric/monetary operands

unaryNegOp mirrors the existing plusOp/subOp helpers:

  • Uses evaluateExprAs with expectOneOf(expectMonetary, expectNumber) and maps both to opNeg implementations (Monetary / MonetaryInt).
  • Propagates any type/interpretation errors from evaluateExprAs.
  • Calls evalNeg on the resolved operand value.

This cleanly enforces “only numbers and monetaries are negatable” at runtime and keeps the operator extensible via opNeg.

internal/interpreter/infix_overloads.go (1)

84-105: Unary negation interface and implementations look correct

  • opNeg cleanly abstracts unary negation, parallel to opAdd/opSub.
  • MonetaryInt.evalNeg and Monetary.evalNeg both:
    • Convert to big.Int, call Neg, and wrap the result in the appropriate value type.
    • Do not mutate the original values, returning fresh instances.
  • The interface satisfaction checks with (*MonetaryInt)(nil) / (*Monetary)(nil) are appropriate given the receiver signatures.

This provides a solid foundation for the interpreter’s unary minus semantics.

internal/analysis/check.go (1)

698-709: Static evaluation support for numeric prefix-minus expressions is sensible

Extending tryEvaluatingNumberExpr with a *parser.Prefix/PrefixOperatorMinus case allows compile-time evaluation of simple numeric negations like -1 and -(1 - 2) where the inner expression is itself statically evaluable. It correctly returns nil for other operators (future-proofing) or non-evaluable subexpressions, matching the existing behaviour for binary infix expressions.

@ascandone ascandone marked this pull request as ready for review December 5, 2025 14:57
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 21 files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants