From f0c409dfdf3147b039358ae8d2b4f8cfeedc3c23 Mon Sep 17 00:00:00 2001
From: Stephane Del Pino <stephane.delpino44@gmail.com>
Date: Mon, 13 Jan 2020 17:47:01 +0100
Subject: [PATCH] Add new mechanism to check if an implicit conversion is
 allowed

As discussed in issue #16, one will be allowed convert an integer to a real, but
not a real to an integer *implicitly*.

- this commit only deals with affection operators, it remains other implicit
  conversions
- tests have been updated likely
---
 .../ASTNodeAffectationExpressionBuilder.cpp   |   6 +
 src/language/ASTNodeDataType.cpp              |  21 +++
 src/language/ASTNodeDataType.hpp              |   4 +
 .../ASTNodeNaturalConversionChecker.cpp       |  13 ++
 .../ASTNodeNaturalConversionChecker.hpp       |  15 ++
 src/language/CMakeLists.txt                   |   1 +
 .../node_processor/AffectationProcessor.hpp   |   4 +-
 ...st_ASTNodeAffectationExpressionBuilder.cpp | 139 +-----------------
 tests/test_ASTNodeExpressionBuilder.cpp       |   6 +-
 tests/test_AffectationProcessor.cpp           |  94 +++++++++---
 ...t_BinaryExpressionProcessor_arithmetic.cpp |  17 +--
 11 files changed, 153 insertions(+), 167 deletions(-)
 create mode 100644 src/language/ASTNodeNaturalConversionChecker.cpp
 create mode 100644 src/language/ASTNodeNaturalConversionChecker.hpp

diff --git a/src/language/ASTNodeAffectationExpressionBuilder.cpp b/src/language/ASTNodeAffectationExpressionBuilder.cpp
index 3475e2c00..2b037d9ed 100644
--- a/src/language/ASTNodeAffectationExpressionBuilder.cpp
+++ b/src/language/ASTNodeAffectationExpressionBuilder.cpp
@@ -1,6 +1,8 @@
 #include <ASTNodeAffectationExpressionBuilder.hpp>
 #include <PEGGrammar.hpp>
 
+#include <ASTNodeNaturalConversionChecker.hpp>
+
 #include <node_processor/AffectationProcessor.hpp>
 
 ASTNodeAffectationExpressionBuilder::ASTNodeAffectationExpressionBuilder(ASTNode& n)
@@ -9,6 +11,7 @@ ASTNodeAffectationExpressionBuilder::ASTNodeAffectationExpressionBuilder(ASTNode
     auto set_affectation_processor_for_data = [&](const auto& value, const ASTNodeDataType& data_type) {
       using OperatorT = std::decay_t<decltype(operator_v)>;
       using ValueT    = std::decay_t<decltype(value)>;
+
       switch (data_type) {
       case ASTNodeDataType::bool_t: {
         n.m_node_processor = std::make_unique<AffectationProcessor<OperatorT, ValueT, bool>>(n);
@@ -70,6 +73,9 @@ ASTNodeAffectationExpressionBuilder::ASTNodeAffectationExpressionBuilder(ASTNode
 
     auto set_affectation_processor_for_value = [&](const ASTNodeDataType& value_type) {
       const ASTNodeDataType data_type = n.children[1]->m_data_type;
+
+      ASTNodeNaturalConversionChecker{n, data_type, value_type};
+
       switch (value_type) {
       case ASTNodeDataType::bool_t: {
         set_affectation_processor_for_data(bool{}, data_type);
diff --git a/src/language/ASTNodeDataType.cpp b/src/language/ASTNodeDataType.cpp
index bd935e1fc..b1e9a64d7 100644
--- a/src/language/ASTNodeDataType.cpp
+++ b/src/language/ASTNodeDataType.cpp
@@ -54,3 +54,24 @@ dataTypePromotion(const ASTNodeDataType& data_type_1, const ASTNodeDataType& dat
     return ASTNodeDataType::undefined_t;
   }
 }
+
+bool
+isNaturalConversion(const ASTNodeDataType& data_type, const ASTNodeDataType& target_data_type)
+{
+  if (target_data_type == data_type) {
+    return true;
+  } else if (target_data_type == ASTNodeDataType::bool_t) {
+    return false;
+  } else if (target_data_type == ASTNodeDataType::unsigned_int_t) {
+    return ((data_type == ASTNodeDataType::int_t) or (data_type == ASTNodeDataType::bool_t));
+  } else if (target_data_type == ASTNodeDataType::int_t) {
+    return ((data_type == ASTNodeDataType::unsigned_int_t) or (data_type == ASTNodeDataType::bool_t));
+  } else if (target_data_type == ASTNodeDataType::double_t) {
+    return ((data_type == ASTNodeDataType::unsigned_int_t) or (data_type == ASTNodeDataType::int_t) or
+            (data_type == ASTNodeDataType::bool_t));
+  } else if (target_data_type == ASTNodeDataType::string_t) {
+    return ((data_type == ASTNodeDataType::double_t) or (data_type == ASTNodeDataType::unsigned_int_t) or
+            (data_type == ASTNodeDataType::int_t) or (data_type == ASTNodeDataType::bool_t));
+  }
+  return false;
+}
diff --git a/src/language/ASTNodeDataType.hpp b/src/language/ASTNodeDataType.hpp
index 4bdfd7075..5b0b87071 100644
--- a/src/language/ASTNodeDataType.hpp
+++ b/src/language/ASTNodeDataType.hpp
@@ -4,6 +4,8 @@
 #include <limits>
 #include <string>
 
+class ASTNode;
+
 enum class ASTNodeDataType : int32_t
 {
   undefined_t    = -1,
@@ -22,6 +24,8 @@ std::string dataTypeName(const ASTNodeDataType& data_type);
 
 ASTNodeDataType dataTypePromotion(const ASTNodeDataType& data_type_1, const ASTNodeDataType& data_type_2);
 
+bool isNaturalConversion(const ASTNodeDataType& data_type, const ASTNodeDataType& target_data_type);
+
 // Traits ast_node_data_type_from_pod
 
 template <typename T>
diff --git a/src/language/ASTNodeNaturalConversionChecker.cpp b/src/language/ASTNodeNaturalConversionChecker.cpp
new file mode 100644
index 000000000..d625d639b
--- /dev/null
+++ b/src/language/ASTNodeNaturalConversionChecker.cpp
@@ -0,0 +1,13 @@
+#include <ASTNodeNaturalConversionChecker.hpp>
+
+ASTNodeNaturalConversionChecker::ASTNodeNaturalConversionChecker(const ASTNode& node,
+                                                                 const ASTNodeDataType& data_type,
+                                                                 const ASTNodeDataType& target_data_type)
+{
+  if (not isNaturalConversion(data_type, target_data_type)) {
+    std::ostringstream error_message;
+    error_message << "invalid implicit conversion: " << dataTypeName(data_type) << " -> "
+                  << dataTypeName(target_data_type);
+    throw parse_error(error_message.str(), node.begin());
+  }
+}
diff --git a/src/language/ASTNodeNaturalConversionChecker.hpp b/src/language/ASTNodeNaturalConversionChecker.hpp
new file mode 100644
index 000000000..fb2391895
--- /dev/null
+++ b/src/language/ASTNodeNaturalConversionChecker.hpp
@@ -0,0 +1,15 @@
+#ifndef AST_NODE_NATURAL_CONVERSION_CHECKER_HPP
+#define AST_NODE_NATURAL_CONVERSION_CHECKER_HPP
+
+#include <ASTNode.hpp>
+#include <ASTNodeDataType.hpp>
+
+class ASTNodeNaturalConversionChecker
+{
+ public:
+  ASTNodeNaturalConversionChecker(const ASTNode& ast_node,
+                                  const ASTNodeDataType& data_type,
+                                  const ASTNodeDataType& target_data_type);
+};
+
+#endif   //  AST_NODE_NATURAL_CONVERSION_CHECKER_HPP
diff --git a/src/language/CMakeLists.txt b/src/language/CMakeLists.txt
index ee0333d3c..368fa5a9f 100644
--- a/src/language/CMakeLists.txt
+++ b/src/language/CMakeLists.txt
@@ -25,6 +25,7 @@ add_library(
   ASTNodeIncDecExpressionBuilder.cpp
   ASTNodeJumpPlacementChecker.cpp
   ASTNodeListAffectationExpressionBuilder.cpp
+  ASTNodeNaturalConversionChecker.cpp
   ASTNodeUnaryOperatorExpressionBuilder.cpp
   ASTPrinter.cpp
   ASTSymbolTableBuilder.cpp
diff --git a/src/language/node_processor/AffectationProcessor.hpp b/src/language/node_processor/AffectationProcessor.hpp
index ef58ef32e..0c9b751ea 100644
--- a/src/language/node_processor/AffectationProcessor.hpp
+++ b/src/language/node_processor/AffectationProcessor.hpp
@@ -77,9 +77,11 @@ class AffectationExecutor final : public IAffectationExecutor
  public:
   AffectationExecutor(ASTNode& node, DataVariant* lhs) : m_lhs(lhs)
   {
+    // LCOV_EXCL_START
     if constexpr (not _is_defined) {
-      throw parse_error("invalid operands to affectation expression", std::vector{node.begin()});
+      throw parse_error("unexpected error: invalid operands to affectation expression", std::vector{node.begin()});
     }
+    // LCOV_EXCL_STOP
   }
 
   PUGS_INLINE void
diff --git a/tests/test_ASTNodeAffectationExpressionBuilder.cpp b/tests/test_ASTNodeAffectationExpressionBuilder.cpp
index 4883f0995..483a80595 100644
--- a/tests/test_ASTNodeAffectationExpressionBuilder.cpp
+++ b/tests/test_ASTNodeAffectationExpressionBuilder.cpp
@@ -64,54 +64,6 @@ B b=true;
 
         CHECK_AST(data, result);
       }
-
-      SECTION("B <- N")
-      {
-        std::string_view data = R"(
-N n; B b=n;
-)";
-
-        std::string_view result = R"(
-(root:ASTNodeListProcessor)
- `-(language::eq_op:AffectationProcessor<language::eq_op, bool, unsigned long>)
-     +-(language::name:b:NameProcessor)
-     `-(language::name:n:NameProcessor)
-)";
-
-        CHECK_AST(data, result);
-      }
-
-      SECTION("B <- Z")
-      {
-        std::string_view data = R"(
-Z z; B b=z;
-)";
-
-        std::string_view result = R"(
-(root:ASTNodeListProcessor)
- `-(language::eq_op:AffectationProcessor<language::eq_op, bool, long>)
-     +-(language::name:b:NameProcessor)
-     `-(language::name:z:NameProcessor)
-)";
-
-        CHECK_AST(data, result);
-      }
-
-      SECTION("B <- R")
-      {
-        std::string_view data = R"(
-R r; B b=r;
-)";
-
-        std::string_view result = R"(
-(root:ASTNodeListProcessor)
- `-(language::eq_op:AffectationProcessor<language::eq_op, bool, double>)
-     +-(language::name:b:NameProcessor)
-     `-(language::name:r:NameProcessor)
-)";
-
-        CHECK_AST(data, result);
-      }
     }
 
     SECTION("unsigned integer affectation")
@@ -163,40 +115,6 @@ Z z; N n=z;
 
         CHECK_AST(data, result);
       }
-
-      SECTION("N <- R")
-      {
-        std::string_view data = R"(
-R r; N n=r;
-)";
-
-        std::string_view result = R"(
-(root:ASTNodeListProcessor)
- `-(language::eq_op:AffectationProcessor<language::eq_op, unsigned long, double>)
-     +-(language::name:n:NameProcessor)
-     `-(language::name:r:NameProcessor)
-)";
-
-        CHECK_AST(data, result);
-      }
-
-      SECTION("N <- string (invalid)")
-      {
-        std::string_view data = R"(
-N n="foo";
-)";
-
-        string_input input{data, "test.pgs"};
-        auto ast = ASTBuilder::build(input);
-
-        ASTSymbolTableBuilder{*ast};
-        ASTNodeDataTypeBuilder{*ast};
-
-        ASTNodeDeclarationToAffectationConverter{*ast};
-        ASTNodeTypeCleaner<language::declaration>{*ast};
-
-        REQUIRE_THROWS_AS(ASTNodeExpressionBuilder{*ast}, parse_error);
-      }
     }
 
     SECTION("integer affectation")
@@ -248,40 +166,6 @@ Z q; Z z=q;
 
         CHECK_AST(data, result);
       }
-
-      SECTION("B <- R")
-      {
-        std::string_view data = R"(
-R r; Z z=r;
-)";
-
-        std::string_view result = R"(
-(root:ASTNodeListProcessor)
- `-(language::eq_op:AffectationProcessor<language::eq_op, long, double>)
-     +-(language::name:z:NameProcessor)
-     `-(language::name:r:NameProcessor)
-)";
-
-        CHECK_AST(data, result);
-      }
-
-      SECTION("Z <- string (invalid)")
-      {
-        std::string_view data = R"(
-Z z="foo";
-)";
-
-        string_input input{data, "test.pgs"};
-        auto ast = ASTBuilder::build(input);
-
-        ASTSymbolTableBuilder{*ast};
-        ASTNodeDataTypeBuilder{*ast};
-
-        ASTNodeDeclarationToAffectationConverter{*ast};
-        ASTNodeTypeCleaner<language::declaration>{*ast};
-
-        REQUIRE_THROWS_AS(ASTNodeExpressionBuilder{*ast}, parse_error);
-      }
     }
 
     SECTION("double affectation")
@@ -349,24 +233,6 @@ R s; R r=s;
 
         CHECK_AST(data, result);
       }
-
-      SECTION("R <- string (invalid)")
-      {
-        std::string_view data = R"(
-R r="foo";
-)";
-
-        string_input input{data, "test.pgs"};
-        auto ast = ASTBuilder::build(input);
-
-        ASTSymbolTableBuilder{*ast};
-        ASTNodeDataTypeBuilder{*ast};
-
-        ASTNodeDeclarationToAffectationConverter{*ast};
-        ASTNodeTypeCleaner<language::declaration>{*ast};
-
-        REQUIRE_THROWS_AS(ASTNodeExpressionBuilder{*ast}, parse_error);
-      }
     }
 
     SECTION("string affectation")
@@ -727,8 +593,7 @@ string s="foo"; s/="bar";
 
       ast->children.emplace_back(std::make_unique<ASTNode>());
       ast->children.emplace_back(std::make_unique<ASTNode>());
-      REQUIRE_THROWS_WITH(ASTNodeAffectationExpressionBuilder{*ast},
-                          "unexpected error: undefined operand type for affectation");
+      REQUIRE_THROWS_WITH(ASTNodeAffectationExpressionBuilder{*ast}, "invalid implicit conversion: undefined -> Z");
     }
 
     SECTION("Invalid string rhs")
@@ -740,7 +605,7 @@ string s="foo"; s/="bar";
       ast->children.emplace_back(std::make_unique<ASTNode>());
       ast->children.emplace_back(std::make_unique<ASTNode>());
       REQUIRE_THROWS_WITH(ASTNodeAffectationExpressionBuilder{*ast},
-                          "unexpected error: undefined operand type for string affectation");
+                          "invalid implicit conversion: undefined -> string");
     }
   }
 }
diff --git a/tests/test_ASTNodeExpressionBuilder.cpp b/tests/test_ASTNodeExpressionBuilder.cpp
index 53b7b963f..9c764a0bc 100644
--- a/tests/test_ASTNodeExpressionBuilder.cpp
+++ b/tests/test_ASTNodeExpressionBuilder.cpp
@@ -219,7 +219,7 @@ x /= 2;
     {
       std::string_view data = R"(
 N i = 1;
-i += 3.;
+i += 3;
 )";
 
       std::string result = R"(
@@ -227,9 +227,9 @@ i += 3.;
  +-(language::eq_op:AffectationProcessor<language::eq_op, unsigned long, long>)
  |   +-(language::name:i:NameProcessor)
  |   `-(language::integer:1:ValueProcessor)
- `-(language::pluseq_op:AffectationProcessor<language::pluseq_op, unsigned long, double>)
+ `-(language::pluseq_op:AffectationProcessor<language::pluseq_op, unsigned long, long>)
      +-(language::name:i:NameProcessor)
-     `-(language::real:3.:ValueProcessor)
+     `-(language::integer:3:ValueProcessor)
 )";
 
       CHECK_AST(data, result);
diff --git a/tests/test_AffectationProcessor.cpp b/tests/test_AffectationProcessor.cpp
index b37a47c71..bbbccadda 100644
--- a/tests/test_AffectationProcessor.cpp
+++ b/tests/test_AffectationProcessor.cpp
@@ -63,6 +63,20 @@
                    Catch::Matchers::Contains("invalid operands to affectation expression")); \
   }
 
+#define CHECK_AFFECTATION_THROWS_WITH(data, error_message)              \
+  {                                                                     \
+    string_input input{data, "test.pgs"};                               \
+    auto ast = ASTBuilder::build(input);                                \
+                                                                        \
+    ASTSymbolTableBuilder{*ast};                                        \
+    ASTNodeDataTypeBuilder{*ast};                                       \
+                                                                        \
+    ASTNodeDeclarationToAffectationConverter{*ast};                     \
+    ASTNodeTypeCleaner<language::declaration>{*ast};                    \
+                                                                        \
+    REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, error_message); \
+  }
+
 TEST_CASE("AffectationProcessor", "[language]")
 {
   SECTION("Affectations")
@@ -70,9 +84,6 @@ TEST_CASE("AffectationProcessor", "[language]")
     SECTION("B")
     {
       CHECK_AFFECTATION_RESULT("B b; b = true;", "b", true);
-      CHECK_AFFECTATION_RESULT("N n = 1; B b; b = n;", "b", true);
-      CHECK_AFFECTATION_RESULT("B b; b = 1;", "b", true);
-      CHECK_AFFECTATION_RESULT("B b; b = 2.3;", "b", true);
     }
 
     SECTION("N")
@@ -81,7 +92,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("N m = 2; N n = m;", "n", 2ul);
       CHECK_AFFECTATION_RESULT("N n = true;", "n", 1ul);
       CHECK_AFFECTATION_RESULT("N n = false;", "n", 0ul);
-      CHECK_AFFECTATION_RESULT("N n = 2.3;", "n", 2ul);
     }
 
     SECTION("Z")
@@ -89,7 +99,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("Z z = -1;", "z", -1l);
       CHECK_AFFECTATION_RESULT("Z z = true;", "z", 1l);
       CHECK_AFFECTATION_RESULT("Z z = false;", "z", 0l);
-      CHECK_AFFECTATION_RESULT("Z z = -2.3;", "z", -2l);
     }
 
     SECTION("R")
@@ -109,7 +118,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("N m = 2; N n = 1; n += m;", "n", 3ul);
       CHECK_AFFECTATION_RESULT("N n = 1; n += true;", "n", 2ul);
       CHECK_AFFECTATION_RESULT("N n = 3; n += false;", "n", 3ul);
-      CHECK_AFFECTATION_RESULT("N n = 2; n += 1.1;", "n", 3ul);
     }
 
     SECTION("Z")
@@ -118,7 +126,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("N m = 2; Z z = 1; z += m;", "z", 3l);
       CHECK_AFFECTATION_RESULT("Z z = 1; z += true;", "z", 2l);
       CHECK_AFFECTATION_RESULT("Z z = 3; z += false;", "z", 3l);
-      CHECK_AFFECTATION_RESULT("Z z = 2; z += 1.1;", "z", 3l);
     }
 
     SECTION("R")
@@ -139,7 +146,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("N m = 2; N n = 4; n -= m;", "n", 2ul);
       CHECK_AFFECTATION_RESULT("N n = 1; n -= true;", "n", 0ul);
       CHECK_AFFECTATION_RESULT("N n = 3; n -= false;", "n", 3ul);
-      CHECK_AFFECTATION_RESULT("N n = 2; n -= 1.1;", "n", 0ul);
     }
 
     SECTION("Z")
@@ -148,7 +154,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("N m = 2; Z z = 1; z -= m;", "z", -1l);
       CHECK_AFFECTATION_RESULT("Z z = 1; z -= true;", "z", 0l);
       CHECK_AFFECTATION_RESULT("Z z = 3; z -= false;", "z", 3l);
-      CHECK_AFFECTATION_RESULT("Z z = 2; z -= 2.1;", "z", 0l);
     }
 
     SECTION("R")
@@ -169,7 +174,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("N m = 2; N n = 4; n *= m;", "n", 8ul);
       CHECK_AFFECTATION_RESULT("N n = 1; n *= true;", "n", 1ul);
       CHECK_AFFECTATION_RESULT("N n = 3; n *= false;", "n", 0ul);
-      CHECK_AFFECTATION_RESULT("N n = 2; n *= 2.51;", "n", 5ul);
     }
 
     SECTION("Z")
@@ -178,7 +182,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("N m = 2; Z z = -2; z *= m;", "z", -4l);
       CHECK_AFFECTATION_RESULT("Z z = 1; z *= true;", "z", 1l);
       CHECK_AFFECTATION_RESULT("Z z = 3; z *= false;", "z", 0l);
-      CHECK_AFFECTATION_RESULT("Z z = 2; z *= -2.51;", "z", -5l);
     }
 
     SECTION("R")
@@ -198,7 +201,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("N n = 4; n /= 2;", "n", 2ul);
       CHECK_AFFECTATION_RESULT("N m = 2; N n = 6; n /= m;", "n", 3ul);
       CHECK_AFFECTATION_RESULT("N n = 1; n /= true;", "n", 1ul);
-      CHECK_AFFECTATION_RESULT("N n = 2; n /= 2.51;", "n", 0ul);
     }
 
     SECTION("Z")
@@ -206,7 +208,6 @@ TEST_CASE("AffectationProcessor", "[language]")
       CHECK_AFFECTATION_RESULT("Z z = 7; z /= -3;", "z", -2l);
       CHECK_AFFECTATION_RESULT("N m = 3; Z z = 6; z /= m;", "z", 2l);
       CHECK_AFFECTATION_RESULT("Z z = 6; z /= true;", "z", 6l);
-      CHECK_AFFECTATION_RESULT("Z z = 4; z /= -2.;", "z", -2l);
     }
 
     SECTION("R")
@@ -220,9 +221,68 @@ TEST_CASE("AffectationProcessor", "[language]")
 
   SECTION("errors")
   {
-    CHECK_AFFECTATION_THROWS("B b = true; b += 1;");
-    CHECK_AFFECTATION_THROWS("B b = true; b *= 1;");
-    CHECK_AFFECTATION_THROWS("B b = true; b -= 1;");
-    CHECK_AFFECTATION_THROWS("B b = true; b /= 1;");
+    SECTION("invalid B affections")
+    {
+      CHECK_AFFECTATION_THROWS("B b = true; b += 1;");
+      CHECK_AFFECTATION_THROWS("B b = true; b *= 1;");
+      CHECK_AFFECTATION_THROWS("B b = true; b -= 1;");
+      CHECK_AFFECTATION_THROWS("B b = true; b /= 1;");
+    }
+
+    SECTION("invalid implicit conversions")
+    {
+      SECTION("-> B")
+      {
+        CHECK_AFFECTATION_THROWS_WITH("N n = 1; B b; b = n;", "invalid implicit conversion: N -> B");
+        CHECK_AFFECTATION_THROWS_WITH("B b; b = 1;", "invalid implicit conversion: Z -> B");
+        CHECK_AFFECTATION_THROWS_WITH("B b; b = 2.3;", "invalid implicit conversion: R -> B");
+        CHECK_AFFECTATION_THROWS_WITH("B b; b = \"foo\";", "invalid implicit conversion: string -> B");
+      }
+
+      SECTION("-> N")
+      {
+        CHECK_AFFECTATION_THROWS_WITH("N n = 2.3;", "invalid implicit conversion: R -> N");
+        CHECK_AFFECTATION_THROWS_WITH("N n = \"bar\";", "invalid implicit conversion: string -> N");
+
+        CHECK_AFFECTATION_THROWS_WITH("N n = 2; n += 1.1;", "invalid implicit conversion: R -> N");
+        CHECK_AFFECTATION_THROWS_WITH("N n = 2; n += \"foo\";", "invalid implicit conversion: string -> N");
+
+        CHECK_AFFECTATION_THROWS_WITH("N n = 2; n -= 1.1;", "invalid implicit conversion: R -> N");
+        CHECK_AFFECTATION_THROWS_WITH("N n = 2; n -= \"bar\";", "invalid implicit conversion: string -> N");
+
+        CHECK_AFFECTATION_THROWS_WITH("N n = 2; n *= 2.51;", "invalid implicit conversion: R -> N");
+        CHECK_AFFECTATION_THROWS_WITH("N n = 2; n *= \"foobar\";", "invalid implicit conversion: string -> N");
+
+        CHECK_AFFECTATION_THROWS_WITH("N n = 2; n /= 2.51;", "invalid implicit conversion: R -> N");
+        CHECK_AFFECTATION_THROWS_WITH("N n = 2; n /= \"foo\";", "invalid implicit conversion: string -> N");
+      }
+
+      SECTION("-> Z")
+      {
+        CHECK_AFFECTATION_THROWS_WITH("Z z = -2.3;", "invalid implicit conversion: R -> Z");
+        CHECK_AFFECTATION_THROWS_WITH("Z z = \"foobar\";", "invalid implicit conversion: string -> Z");
+
+        CHECK_AFFECTATION_THROWS_WITH("Z z = 2; z += 1.1;", "invalid implicit conversion: R -> Z");
+        CHECK_AFFECTATION_THROWS_WITH("Z z = 2; z += \"foo\";", "invalid implicit conversion: string -> Z");
+
+        CHECK_AFFECTATION_THROWS_WITH("Z z = 2; z -= 2.1;", "invalid implicit conversion: R -> Z");
+        CHECK_AFFECTATION_THROWS_WITH("Z z = 2; z -= \"bar\";", "invalid implicit conversion: string -> Z");
+
+        CHECK_AFFECTATION_THROWS_WITH("Z z = 2; z *= -2.51;", "invalid implicit conversion: R -> Z");
+        CHECK_AFFECTATION_THROWS_WITH("Z z = 2; z *= \"foobar\";", "invalid implicit conversion: string -> Z");
+
+        CHECK_AFFECTATION_THROWS_WITH("Z z = 4; z /= -2.;", "invalid implicit conversion: R -> Z");
+        CHECK_AFFECTATION_THROWS_WITH("Z z = 2; z /= \"foo\";", "invalid implicit conversion: string -> Z");
+      }
+
+      SECTION("-> R")
+      {
+        CHECK_AFFECTATION_THROWS_WITH("R x = \"foobar\";", "invalid implicit conversion: string -> R");
+        CHECK_AFFECTATION_THROWS_WITH("R x = 2.3; x += \"foo\";", "invalid implicit conversion: string -> R");
+        CHECK_AFFECTATION_THROWS_WITH("R x = 2.1; x -= \"bar\";", "invalid implicit conversion: string -> R");
+        CHECK_AFFECTATION_THROWS_WITH("R x = 1.2; x *= \"foobar\";", "invalid implicit conversion: string -> R");
+        CHECK_AFFECTATION_THROWS_WITH("R x =-2.3; x /= \"foo\";", "invalid implicit conversion: string -> R");
+      }
+    }
   }
 }
diff --git a/tests/test_BinaryExpressionProcessor_arithmetic.cpp b/tests/test_BinaryExpressionProcessor_arithmetic.cpp
index edfd50dff..9c229c5d5 100644
--- a/tests/test_BinaryExpressionProcessor_arithmetic.cpp
+++ b/tests/test_BinaryExpressionProcessor_arithmetic.cpp
@@ -23,7 +23,7 @@ TEST_CASE("BinaryExpressionProcessor arithmetic", "[language]")
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 1; n = n + true;)", "n", 2ul);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 4; N m = 2; n = n + m;)", "n", 6ul);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 4; Z z = -1; n = n + z;)", "n", 3ul);
-      CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 1 + 2.3;)", "n", 3ul);
+      CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 1; R x = n + 2.3;)", "x", 3.3);
     }
 
     SECTION("lhs is Z")
@@ -32,7 +32,7 @@ TEST_CASE("BinaryExpressionProcessor arithmetic", "[language]")
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 4; Z z = -3 + n;)", "z", 1l);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 2; Z z = -3 + n;)", "z", -1l);
       CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = 1 + 2;)", "z", 3l);
-      CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = 3 + 2.5;)", "z", 5l);
+      CHECK_BINARY_EXPRESSION_RESULT(R"(R x = 3 + 2.5;)", "x", 5.5);
     }
 
     SECTION("lhs is R")
@@ -59,7 +59,7 @@ TEST_CASE("BinaryExpressionProcessor arithmetic", "[language]")
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3; n = n - true;)", "n", 2ul);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 4; N m = 2; n = n - m;)", "n", 2ul);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 5; n = n - 4;)", "n", 1ul);
-      CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3 - 2.3;)", "n", 0ul);
+      CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3; R x = n - 2.3;)", "x", double{3ul - 2.3});
     }
 
     SECTION("lhs is Z")
@@ -67,7 +67,7 @@ TEST_CASE("BinaryExpressionProcessor arithmetic", "[language]")
       CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = -1 - true;)", "z", -2l);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 4; Z z = 3 - n;)", "z", -1l);
       CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = 7 - 2;)", "z", 5l);
-      CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = 4 - 2.5;)", "z", 1l);
+      CHECK_BINARY_EXPRESSION_RESULT(R"(R x = 4 - 2.5;)", "x", 1.5);
     }
 
     SECTION("lhs is R")
@@ -94,8 +94,7 @@ TEST_CASE("BinaryExpressionProcessor arithmetic", "[language]")
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3; n = n * true;)", "n", 3ul);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 4; N m = 2; n = n * m;)", "n", 8ul);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 5; n = n * 4;)", "n", 20ul);
-      CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3 * 2.3;)", "n", 6ul);
-      CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3 * 2.5;)", "n", 7ul);
+      CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3; R x = n * 2.3;)", "x", double{3ul * 2.3});
     }
 
     SECTION("lhs is Z")
@@ -103,7 +102,7 @@ TEST_CASE("BinaryExpressionProcessor arithmetic", "[language]")
       CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = -1 * true;)", "z", -1l);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 4; Z z = 3 * n;)", "z", 12l);
       CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = 7 * 2;)", "z", 14l);
-      CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = 4 * 2.4;)", "z", 9l);
+      CHECK_BINARY_EXPRESSION_RESULT(R"(R x = 4 * 2.4;)", "x", double{4l * 2.4});
     }
 
     SECTION("lhs is R")
@@ -130,7 +129,7 @@ TEST_CASE("BinaryExpressionProcessor arithmetic", "[language]")
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3; n = n / true;)", "n", 3ul);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 4; N m = 2; n = n / m;)", "n", 2ul);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 5; n = n / 4;)", "n", 1ul);
-      CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3 / 2.3;)", "n", 1ul);
+      CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 3; R x = n / 2.3;)", "x", double{3ul / 2.3});
     }
 
     SECTION("lhs is Z")
@@ -138,7 +137,7 @@ TEST_CASE("BinaryExpressionProcessor arithmetic", "[language]")
       CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = -1 / true;)", "z", -1l);
       CHECK_BINARY_EXPRESSION_RESULT(R"(N n = 4; Z z = 3 / n;)", "z", 0l);
       CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = 7 / 2;)", "z", 3l);
-      CHECK_BINARY_EXPRESSION_RESULT(R"(Z z = 4 / 2.4;)", "z", 1l);
+      CHECK_BINARY_EXPRESSION_RESULT(R"(R x = 4 / 2.4;)", "x", double{4l / 2.4});
     }
 
     SECTION("lhs is R")
-- 
GitLab