Skip to content

Conversation

@emilbon99
Copy link
Contributor

@emilbon99 emilbon99 commented Dec 24, 2025

Issue Pull Request

Linear Issue

SY-3484

Description

Basic Readiness

  • I have performed a self-review of my code.
  • I have added relevant tests to cover the changes to CI.
  • I have added needed QA steps to the release candidate template that cover these changes.
  • I have updated in-code documentation to reflect the changes.
  • I have updated user-facing documentation to reflect the changes.

Greptile Summary

This PR implements compound assignment operators (+=, -=, *=, /=, %=) for the Arc language compiler. The implementation spans grammar definitions, semantic analysis, and WASM code generation.

Key changes:

  • Added 5 compound assignment token definitions in ArcLexer.g4 and corresponding grammar rules in ArcParser.g4
  • Implemented analyzeCompoundAssignment() with comprehensive type checking that validates numeric types for all operators except += which also supports strings
  • Implemented compileCompoundAssignment() to generate correct WASM bytecode, handling local/input/stateful variables and string concatenation via runtime import
  • Added test coverage for all 5 operators across numeric types (i32, i64, f32, f64)
  • Correctly blocks unsupported cases: indexed compound assignments and compound assignments on channels

Design decisions:

  • Indexed compound assignment (e.g., array[i] += 5) is explicitly blocked with a clear error message, leaving room for future implementation
  • String types only support += for concatenation; other operators are rejected
  • Type casting is automatically applied when expression type differs from variable type
  • Stateful variables correctly trigger state persistence after compound assignment

The implementation is clean, well-structured, and follows existing patterns in the codebase.

Confidence Score: 4/5

  • Safe to merge with minor test coverage improvement recommended
  • The implementation is solid with proper type checking, error handling, and correct WASM code generation. All critical paths are covered. Score is 4 instead of 5 because test coverage is missing string concatenation (str += str) validation, which is an explicitly supported feature that should be tested.
  • arc/go/compiler/statement/statement_test.go needs a test case for string concatenation with +=

Important Files Changed

Filename Overview
arc/go/parser/ArcParser.g4 Added grammar rules for compound assignment operators (compoundOp production with 5 operator tokens). Clean implementation, properly integrated into assignment rule.
arc/go/parser/ArcLexer.g4 Added 5 compound assignment tokens (PLUS_ASSIGN, MINUS_ASSIGN, STAR_ASSIGN, SLASH_ASSIGN, PERCENT_ASSIGN). Positioned correctly before arithmetic operators.
arc/go/analyzer/statement/statement.go Implemented analyzeCompoundAssignment with proper type checking. Blocks indexed assignment, channels, validates numeric types, allows string +=. Type compatibility checking is thorough.
arc/go/compiler/statement/variable.go Implemented compileCompoundAssignment to emit WASM opcodes. Correctly handles local/input/stateful variables, string concatenation via import call, type casting, and binary operations.
arc/go/compiler/statement/statement_test.go Added comprehensive test table covering all 5 operators across numeric types (i64, f64, i32, f32 with +=, -=, *=, /=, %=). Missing string += test case.

Sequence Diagram

sequenceDiagram
    participant Parser as ANTLR Parser
    participant Lexer as ANTLR Lexer
    participant Analyzer as Statement Analyzer
    participant Compiler as Statement Compiler
    participant WASM as WASM Writer

    Note over Lexer: Tokenize x += 5
    Lexer->>Parser: IDENTIFIER, PLUS_ASSIGN, INTEGER_LITERAL
    Parser->>Parser: Build assignment AST with CompoundOp context
    
    Note over Analyzer: Type checking phase
    Parser->>Analyzer: analyzeAssignment(assignment context)
    Analyzer->>Analyzer: Resolve variable scope and type
    Analyzer->>Analyzer: Check CompoundOp exists
    Analyzer->>Analyzer: analyzeCompoundAssignment()
    
    alt Indexed assignment
        Analyzer->>Analyzer: Error: not yet supported
    end
    
    alt Channel type
        Analyzer->>Analyzer: Error: not supported on channels
    end
    
    alt String type
        Analyzer->>Analyzer: Check only += operator
    else Numeric type
        Analyzer->>Analyzer: Validate IsNumeric()
    end
    
    Analyzer->>Analyzer: Analyze expression type
    Analyzer->>Analyzer: Check type compatibility
    Analyzer-->>Parser: Type checking complete
    
    Note over Compiler: Code generation phase
    Parser->>Compiler: compileAssignment(assignment context)
    Compiler->>Compiler: Resolve variable scope
    Compiler->>Compiler: Check CompoundOp exists
    Compiler->>Compiler: compileCompoundAssignment()
    
    Compiler->>WASM: WriteLocalGet(scope.ID)
    Note over WASM: Load current value
    
    Compiler->>Compiler: Compile expression
    Compiler->>WASM: Emit expression opcodes
    
    alt Type mismatch
        Compiler->>WASM: EmitCast(exprType, varType)
    end
    
    alt String concatenation
        Compiler->>WASM: WriteCall(StringConcat import)
    else Numeric operation
        Compiler->>WASM: WriteBinaryOpInferred(op, varType)
    end
    
    alt Local/Input variable
        Compiler->>WASM: WriteLocalSet(scope.ID)
    else Stateful variable
        Compiler->>WASM: WriteLocalSet(scope.ID)
        Compiler->>WASM: Write state store operations
        Compiler->>WASM: WriteCall(StateStore import)
    end
    
    WASM-->>Compiler: WASM bytecode generated
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

13 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +507 to +560
DescribeTable("Compound assignment operators", func(source string, instructions ...any) {
Expect(compileBlock(source)).To(MatchOpcodes(instructions...))
},
Entry("i64 plus equals",
`x i64 := 10
x += 5`,
OpI64Const, int64(10),
OpLocalSet, 0,
OpLocalGet, 0,
OpI64Const, int64(5),
OpI64Add,
OpLocalSet, 0,
),
Entry("f64 minus equals",
`x f64 := 10.0
x -= 2.5`,
OpF64Const, float64(10.0),
OpLocalSet, 0,
OpLocalGet, 0,
OpF64Const, 2.5,
OpF64Sub,
OpLocalSet, 0,
),
Entry("i32 multiply equals",
`x i32 := 3
x *= 4`,
OpI32Const, int32(3),
OpLocalSet, 0,
OpLocalGet, 0,
OpI32Const, int32(4),
OpI32Mul,
OpLocalSet, 0,
),
Entry("f32 divide equals",
`x f32 := 10.0
x /= 2.0`,
OpF32Const, float32(10.0),
OpLocalSet, 0,
OpLocalGet, 0,
OpF32Const, float32(2.0),
OpF32Div,
OpLocalSet, 0,
),
Entry("i64 modulo equals",
`x i64 := 17
x %= 5`,
OpI64Const, int64(17),
OpLocalSet, 0,
OpLocalGet, 0,
OpI64Const, int64(5),
OpI64RemS,
OpLocalSet, 0,
),
)
Copy link

Choose a reason for hiding this comment

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

style: Missing test for string concatenation with +=.

Add test case:

Suggested change
DescribeTable("Compound assignment operators", func(source string, instructions ...any) {
Expect(compileBlock(source)).To(MatchOpcodes(instructions...))
},
Entry("i64 plus equals",
`x i64 := 10
x += 5`,
OpI64Const, int64(10),
OpLocalSet, 0,
OpLocalGet, 0,
OpI64Const, int64(5),
OpI64Add,
OpLocalSet, 0,
),
Entry("f64 minus equals",
`x f64 := 10.0
x -= 2.5`,
OpF64Const, float64(10.0),
OpLocalSet, 0,
OpLocalGet, 0,
OpF64Const, 2.5,
OpF64Sub,
OpLocalSet, 0,
),
Entry("i32 multiply equals",
`x i32 := 3
x *= 4`,
OpI32Const, int32(3),
OpLocalSet, 0,
OpLocalGet, 0,
OpI32Const, int32(4),
OpI32Mul,
OpLocalSet, 0,
),
Entry("f32 divide equals",
`x f32 := 10.0
x /= 2.0`,
OpF32Const, float32(10.0),
OpLocalSet, 0,
OpLocalGet, 0,
OpF32Const, float32(2.0),
OpF32Div,
OpLocalSet, 0,
),
Entry("i64 modulo equals",
`x i64 := 17
x %= 5`,
OpI64Const, int64(17),
OpLocalSet, 0,
OpLocalGet, 0,
OpI64Const, int64(5),
OpI64RemS,
OpLocalSet, 0,
),
)
DescribeTable("Compound assignment operators", func(source string, instructions ...any) {
Expect(compileBlock(source)).To(MatchOpcodes(instructions...))
},
Entry("i64 plus equals",
`x i64 := 10
x += 5`,
OpI64Const, int64(10),
OpLocalSet, 0,
OpLocalGet, 0,
OpI64Const, int64(5),
OpI64Add,
OpLocalSet, 0,
),
Entry("f64 minus equals",
`x f64 := 10.0
x -= 2.5`,
OpF64Const, float64(10.0),
OpLocalSet, 0,
OpLocalGet, 0,
OpF64Const, 2.5,
OpF64Sub,
OpLocalSet, 0,
),
Entry("i32 multiply equals",
`x i32 := 3
x *= 4`,
OpI32Const, int32(3),
OpLocalSet, 0,
OpLocalGet, 0,
OpI32Const, int32(4),
OpI32Mul,
OpLocalSet, 0,
),
Entry("f32 divide equals",
`x f32 := 10.0
x /= 2.0`,
OpF32Const, float32(10.0),
OpLocalSet, 0,
OpLocalGet, 0,
OpF32Const, float32(2.0),
OpF32Div,
OpLocalSet, 0,
),
Entry("i64 modulo equals",
`x i64 := 17
x %= 5`,
OpI64Const, int64(17),
OpLocalSet, 0,
OpLocalGet, 0,
OpI64Const, int64(5),
OpI64RemS,
OpLocalSet, 0,
),
Entry("string plus equals",
`s str := "hello"
s += " world"`,
OpStringConst, "hello",
OpLocalSet, 0,
OpLocalGet, 0,
OpStringConst, " world",
OpCall, /* StringConcat import index */,
OpLocalSet, 0,
),
)

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants