diff --git a/pkg/compiler/binder/stmtexpr.go b/pkg/compiler/binder/stmtexpr.go index 3f50431f6..9322ac15f 100644 --- a/pkg/compiler/binder/stmtexpr.go +++ b/pkg/compiler/binder/stmtexpr.go @@ -569,9 +569,32 @@ func (a *astBinder) checkBinaryOperatorExpression(node *ast.BinaryOperatorExpres a.b.Diag().Errorf(errors.ErrorIllegalAssignmentTypes.At(node), rhs, lhs) } a.b.ctx.RegisterType(node, lhs) - case ast.OpAssignSum, ast.OpAssignDifference, ast.OpAssignProduct, ast.OpAssignQuotient, + case ast.OpAssignSum: + // Lhs and rhs can be numbers (for addition) or strings (for concatenation). + if !a.isLValue(node.Left) { + a.b.Diag().Errorf(errors.ErrorIllegalAssignmentLValue.At(node)) + a.b.ctx.RegisterType(node, types.Number) + } else if lhs == types.Number { + if rhs != types.Number { + a.b.Diag().Errorf(errors.ErrorBinaryOperatorInvalidForType.At(node), + node.Operator, "RHS", rhs, types.Number) + } + a.b.ctx.RegisterType(node, types.Number) + } else if lhs == types.String { + if rhs != types.String { + a.b.Diag().Errorf(errors.ErrorBinaryOperatorInvalidForType.At(node), + node.Operator, "RHS", rhs, types.String) + } + a.b.ctx.RegisterType(node, types.String) + } else { + a.b.Diag().Errorf(errors.ErrorBinaryOperatorInvalidForType.At(node), + node.Operator, "LHS", lhs, "string or number") + a.b.ctx.RegisterType(node, types.Number) + } + case ast.OpAssignDifference, ast.OpAssignProduct, ast.OpAssignQuotient, ast.OpAssignRemainder, ast.OpAssignExponentiation, ast.OpAssignBitwiseShiftLeft, ast.OpAssignBitwiseShiftRight, ast.OpAssignBitwiseAnd, ast.OpAssignBitwiseOr, ast.OpAssignBitwiseXor: + // These operators require numeric values. if !a.isLValue(node.Left) { a.b.Diag().Errorf(errors.ErrorIllegalAssignmentLValue.At(node)) } else if lhs != types.Number { diff --git a/pkg/eval/eval.go b/pkg/eval/eval.go index de4b3384e..f5f5fbcfa 100644 --- a/pkg/eval/eval.go +++ b/pkg/eval/eval.go @@ -1477,9 +1477,15 @@ func (e *evaluator) evalBinaryOperatorExpression(node *ast.BinaryOperatorExpress e.evalAssign(node.Left, *lhsloc, rhs) return rhs, nil case ast.OpAssignSum: - // The target is a numeric l-value; just += rhs to it, and yield the new value as the result. + var val *rt.Object ptr := lhs.PointerValue() - val := e.alloc.NewNumber(ptr.Obj().NumberValue() + rhs.NumberValue()) + if lhs.Type() == types.String { + // If the lhs/rhs are strings, just concatenate += and yield the new value as a result. + val = e.alloc.NewString(ptr.Obj().StringValue() + rhs.StringValue()) + } else { + // Otherwise, the target is a numeric l-value; just += to it, and yield the new value as the result. + val = e.alloc.NewNumber(ptr.Obj().NumberValue() + rhs.NumberValue()) + } e.evalAssign(node.Left, *lhsloc, val) return val, nil case ast.OpAssignDifference: