tint/resolver: Support error cases with const-eval builtin tests
Also:
Print unrepresentable numbers with higher precision - otherwise values can round, and diagnostics can be very confusing.
Improve diagnostic distinction between `( )` `[ ]` interval ranges.
Change-Id: I9269fbf1738f0bce5f2ddb5a387687543fd5d0bb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/108700
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index 2554dc8..afb9f5d 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -15,6 +15,7 @@
#include "src/tint/resolver/const_eval.h"
#include <algorithm>
+#include <iomanip>
#include <limits>
#include <optional>
#include <string>
@@ -187,14 +188,24 @@
template <typename NumberT>
std::string OverflowErrorMessage(NumberT lhs, const char* op, NumberT rhs) {
std::stringstream ss;
+ ss << std::setprecision(20);
ss << "'" << lhs.value << " " << op << " " << rhs.value << "' cannot be represented as '"
<< FriendlyName<NumberT>() << "'";
return ss.str();
}
+template <typename VALUE_TY>
+std::string OverflowErrorMessage(VALUE_TY value, std::string_view target_ty) {
+ std::stringstream ss;
+ ss << std::setprecision(20);
+ ss << "value " << value << " cannot be represented as "
+ << "'" << target_ty << "'";
+ return ss.str();
+}
+
/// @returns the number of consecutive leading bits in `@p e` set to `@p bit_value_to_count`.
template <typename T>
-auto CountLeadingBits(T e, T bit_value_to_count) -> std::make_unsigned_t<T> {
+std::make_unsigned_t<T> CountLeadingBits(T e, T bit_value_to_count) {
using UT = std::make_unsigned_t<T>;
constexpr UT kNumBits = sizeof(UT) * 8;
constexpr UT kLeftMost = UT{1} << (kNumBits - 1);
@@ -211,7 +222,7 @@
/// @returns the number of consecutive trailing bits set to `@p bit_value_to_count` in `@p e`
template <typename T>
-auto CountTrailingBits(T e, T bit_value_to_count) -> std::make_unsigned_t<T> {
+std::make_unsigned_t<T> CountTrailingBits(T e, T bit_value_to_count) {
using UT = std::make_unsigned_t<T>;
constexpr UT kNumBits = sizeof(UT) * 8;
constexpr UT kRightMost = UT{1};
@@ -292,10 +303,9 @@
// --- Below this point are the failure cases ---
} else if constexpr (IsAbstract<FROM>) {
// [abstract-numeric -> x] - materialization failure
- std::stringstream ss;
- ss << "value " << value << " cannot be represented as ";
- ss << "'" << builder.FriendlyName(target_ty) << "'";
- builder.Diagnostics().add_error(tint::diag::System::Resolver, ss.str(), source);
+ builder.Diagnostics().add_error(
+ tint::diag::System::Resolver,
+ OverflowErrorMessage(value, builder.FriendlyName(target_ty)), source);
return utils::Failure;
} else if constexpr (IsFloatingPoint<TO>) {
// [x -> floating-point] - number not exactly representable
@@ -1602,7 +1612,7 @@
auto create = [&](auto i) -> ImplResult {
using NumberT = decltype(i);
if (i < NumberT(-1.0) || i > NumberT(1.0)) {
- AddError("acos must be called with a value in the range [-1, 1]", source);
+ AddError("acos must be called with a value in the range [-1 .. 1] (inclusive)", source);
return utils::Failure;
}
return CreateElement(builder, c0->Type(), NumberT(std::acos(i.value)));
@@ -1631,7 +1641,7 @@
auto create = [&](auto i) -> ImplResult {
using NumberT = decltype(i);
if (i < NumberT(-1.0) || i > NumberT(1.0)) {
- AddError("asin must be called with a value in the range [-1, 1]", source);
+ AddError("asin must be called with a value in the range [-1 .. 1] (inclusive)", source);
return utils::Failure;
}
return CreateElement(builder, c0->Type(), NumberT(std::asin(i.value)));
@@ -1677,7 +1687,7 @@
auto create = [&](auto i) -> ImplResult {
using NumberT = decltype(i);
if (i <= NumberT(-1.0) || i >= NumberT(1.0)) {
- AddError("atanh must be called with a value in the range (-1, 1)", source);
+ AddError("atanh must be called with a value in the range (-1 .. 1) (exclusive)", source);
return utils::Failure;
}
return CreateElement(builder, c0->Type(), NumberT(std::atanh(i.value)));
diff --git a/src/tint/resolver/const_eval_binary_op_test.cc b/src/tint/resolver/const_eval_binary_op_test.cc
index 05a1453..8708aa8 100644
--- a/src/tint/resolver/const_eval_binary_op_test.cc
+++ b/src/tint/resolver/const_eval_binary_op_test.cc
@@ -853,7 +853,7 @@
GlobalConst("c", Add(Source{{1, 1}}, Expr(AFloat::Highest()), AFloat::Highest()));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
- "1:1 error: '1.79769e+308 + 1.79769e+308' cannot be represented as 'abstract-float'");
+ "1:1 error: '1.7976931348623157081e+308 + 1.7976931348623157081e+308' cannot be represented as 'abstract-float'");
}
TEST_F(ResolverConstEvalTest, BinaryAbstractAddUnderflow_AFloat) {
@@ -861,7 +861,7 @@
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
- "1:1 error: '-1.79769e+308 + -1.79769e+308' cannot be represented as 'abstract-float'");
+ "1:1 error: '-1.7976931348623157081e+308 + -1.7976931348623157081e+308' cannot be represented as 'abstract-float'");
}
// Mixed AInt and AFloat args to test implicit conversion to AFloat
diff --git a/src/tint/resolver/const_eval_builtin_test.cc b/src/tint/resolver/const_eval_builtin_test.cc
index 74ac16f..3dc395b 100644
--- a/src/tint/resolver/const_eval_builtin_test.cc
+++ b/src/tint/resolver/const_eval_builtin_test.cc
@@ -14,6 +14,8 @@
#include "src/tint/resolver/const_eval_test.h"
+#include "src/tint/utils/result.h"
+
using namespace tint::number_suffixes; // NOLINT
namespace tint::resolver {
@@ -23,25 +25,39 @@
using resolver::operator<<;
struct Case {
- Case(utils::VectorRef<Types> in_args, Types in_expected)
- : args(std::move(in_args)), expected(std::move(in_expected)) {}
+ Case(utils::VectorRef<Types> in_args, Types expected_value)
+ : args(std::move(in_args)), expected(Success{std::move(expected_value), false, false}) {}
+
+ Case(utils::VectorRef<Types> in_args, const char* expected_err)
+ : args(std::move(in_args)), expected(Failure{expected_err}) {}
/// Expected value may be positive or negative
Case& PosOrNeg() {
- expected_pos_or_neg = true;
+ Success s = expected.Get();
+ s.pos_or_neg = true;
+ expected = s;
return *this;
}
/// Expected value should be compared using FLOAT_EQ instead of EQ
Case& FloatComp() {
- float_compare = true;
+ Success s = expected.Get();
+ s.float_compare = true;
+ expected = s;
return *this;
}
+ struct Success {
+ Types value;
+ bool pos_or_neg = false;
+ bool float_compare = false;
+ };
+ struct Failure {
+ const char* error = nullptr;
+ };
+
utils::Vector<Types, 8> args;
- Types expected;
- bool expected_pos_or_neg = false;
- bool float_compare = false;
+ utils::Result<Success, Failure> expected;
};
static std::ostream& operator<<(std::ostream& o, const Case& c) {
@@ -49,17 +65,24 @@
for (auto& a : c.args) {
o << a << ", ";
}
- o << "expected: " << c.expected << ", expected_pos_or_neg: " << c.expected_pos_or_neg;
+ o << "expected: ";
+ if (c.expected) {
+ auto s = c.expected.Get();
+ o << s.value << ", pos_or_neg: " << s.pos_or_neg;
+ } else {
+ o << "[ERROR: " << c.expected.Failure().error << "]";
+ }
return o;
}
+using ScalarTypes = std::variant<AInt, AFloat, u32, i32, f32, f16>;
+
/// Creates a Case with Values for args and result
static Case C(std::initializer_list<Types> args, Types result) {
return Case{utils::Vector<Types, 8>{args}, std::move(result)};
}
/// Convenience overload that creates a Case with just scalars
-using ScalarTypes = std::variant<AInt, AFloat, u32, i32, f32, f16>;
static Case C(std::initializer_list<ScalarTypes> sargs, ScalarTypes sresult) {
utils::Vector<Types, 8> args;
for (auto& sa : sargs) {
@@ -70,6 +93,20 @@
return Case{std::move(args), std::move(result)};
}
+/// Creates a Case with Values for args and expected error
+static Case E(std::initializer_list<Types> args, const char* err) {
+ return Case{utils::Vector<Types, 8>{args}, err};
+}
+
+/// Convenience overload that creates an expected-error Case with just scalars
+static Case E(std::initializer_list<ScalarTypes> sargs, const char* err) {
+ utils::Vector<Types, 8> args;
+ for (auto& sa : sargs) {
+ std::visit([&](auto&& v) { return args.Push(Val(v)); }, sa);
+ }
+ return Case{std::move(args), err};
+}
+
using ResolverConstEvalBuiltinTest = ResolverTestWithParam<std::tuple<sem::BuiltinType, Case>>;
TEST_P(ResolverConstEvalBuiltinTest, Test) {
@@ -83,58 +120,65 @@
std::visit([&](auto&& v) { args.Push(v.Expr(*this)); }, a);
}
- auto* expected = ToValueBase(c.expected);
- auto* expr = Call(sem::str(builtin), std::move(args));
+ auto* expr = Call(Source{{12, 34}}, sem::str(builtin), std::move(args));
GlobalConst("C", expr);
- auto* expected_expr = expected->Expr(*this);
- GlobalConst("E", expected_expr);
- ASSERT_TRUE(r()->Resolve()) << r()->error();
+ if (c.expected) {
+ auto expected = c.expected.Get();
- auto* sem = Sem().Get(expr);
- ASSERT_NE(sem, nullptr);
- const sem::Constant* value = sem->ConstantValue();
- ASSERT_NE(value, nullptr);
- EXPECT_TYPE(value->Type(), sem->Type());
+ auto* expected_expr = ToValueBase(expected.value)->Expr(*this);
+ GlobalConst("E", expected_expr);
- auto* expected_sem = Sem().Get(expected_expr);
- const sem::Constant* expected_value = expected_sem->ConstantValue();
- ASSERT_NE(expected_value, nullptr);
- EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
- // @TODO(amaiorano): Rewrite using ScalarArgsFrom()
- ForEachElemPair(value, expected_value, [&](const sem::Constant* a, const sem::Constant* b) {
- std::visit(
- [&](auto&& ct_expected) {
- using T = typename std::decay_t<decltype(ct_expected)>::ElementType;
+ auto* sem = Sem().Get(expr);
+ ASSERT_NE(sem, nullptr);
+ const sem::Constant* value = sem->ConstantValue();
+ ASSERT_NE(value, nullptr);
+ EXPECT_TYPE(value->Type(), sem->Type());
- auto v = a->As<T>();
- auto e = b->As<T>();
- if constexpr (std::is_same_v<bool, T>) {
- EXPECT_EQ(v, e);
- } else if constexpr (IsFloatingPoint<T>) {
- if (std::isnan(e)) {
- EXPECT_TRUE(std::isnan(v));
- } else {
- auto vf = (c.expected_pos_or_neg ? Abs(v) : v);
- if (c.float_compare) {
- EXPECT_FLOAT_EQ(vf, e);
+ auto* expected_sem = Sem().Get(expected_expr);
+ const sem::Constant* expected_value = expected_sem->ConstantValue();
+ ASSERT_NE(expected_value, nullptr);
+ EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
+
+ // @TODO(amaiorano): Rewrite using ScalarArgsFrom()
+ ForEachElemPair(value, expected_value, [&](const sem::Constant* a, const sem::Constant* b) {
+ std::visit(
+ [&](auto&& ct_expected) {
+ using T = typename std::decay_t<decltype(ct_expected)>::ElementType;
+
+ auto v = a->As<T>();
+ auto e = b->As<T>();
+ if constexpr (std::is_same_v<bool, T>) {
+ EXPECT_EQ(v, e);
+ } else if constexpr (IsFloatingPoint<T>) {
+ if (std::isnan(e)) {
+ EXPECT_TRUE(std::isnan(v));
} else {
- EXPECT_EQ(vf, e);
+ auto vf = (expected.pos_or_neg ? Abs(v) : v);
+ if (expected.float_compare) {
+ EXPECT_FLOAT_EQ(vf, e);
+ } else {
+ EXPECT_EQ(vf, e);
+ }
}
+ } else {
+ EXPECT_EQ((expected.pos_or_neg ? Abs(v) : v), e);
+ // Check that the constant's integer doesn't contain unexpected
+ // data in the MSBs that are outside of the bit-width of T.
+ EXPECT_EQ(a->As<AInt>(), b->As<AInt>());
}
- } else {
- EXPECT_EQ((c.expected_pos_or_neg ? Abs(v) : v), e);
- // Check that the constant's integer doesn't contain unexpected
- // data in the MSBs that are outside of the bit-width of T.
- EXPECT_EQ(a->As<AInt>(), b->As<AInt>());
- }
- },
- c.expected);
+ },
+ expected.value);
- return HasFailure() ? Action::kStop : Action::kContinue;
- });
+ return HasFailure() ? Action::kStop : Action::kContinue;
+ });
+ } else {
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(), c.expected.Failure().error);
+ }
}
INSTANTIATE_TEST_SUITE_P( //
@@ -374,6 +418,19 @@
C({Vec(T(0.0), T(0.9), -T(0.9))}, Vec(T(0.0), T(1.4722193), -T(1.4722193))).FloatComp(),
};
+ ConcatIntoIf<finite_only>( //
+ cases,
+ std::vector<Case>{
+ E({1.1_a},
+ "12:34 error: atanh must be called with a value in the range (-1 .. 1) (exclusive)"),
+ E({-1.1_a},
+ "12:34 error: atanh must be called with a value in the range (-1 .. 1) (exclusive)"),
+ E({T::Inf()},
+ "12:34 error: atanh must be called with a value in the range (-1 .. 1) (exclusive)"),
+ E({-T::Inf()},
+ "12:34 error: atanh must be called with a value in the range (-1 .. 1) (exclusive)"),
+ });
+
ConcatIntoIf<!finite_only>( //
cases, std::vector<Case>{
// If i is NaN, NaN is returned
@@ -393,38 +450,6 @@
AtanhCases<f32, false>(),
AtanhCases<f16, false>()))));
-TEST_F(ResolverConstEvalBuiltinTest, Atanh_OutsideRange_Positive) {
- auto* expr = Call(Source{{12, 24}}, "atanh", Expr(1.0_a));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: atanh must be called with a value in the range (-1, 1)");
-}
-
-TEST_F(ResolverConstEvalBuiltinTest, Atanh_OutsideRange_Negative) {
- auto* expr = Call(Source{{12, 24}}, "atanh", Negation(1.0_a));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: atanh must be called with a value in the range (-1, 1)");
-}
-
-TEST_F(ResolverConstEvalBuiltinTest, Atanh_OutsideRange_Positive_INF) {
- auto* expr = Call(Source{{12, 24}}, "atanh", Expr(f32::Inf()));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: atanh must be called with a value in the range (-1, 1)");
-}
-
-TEST_F(ResolverConstEvalBuiltinTest, Atanh_OutsideRange_Negative_INF) {
- auto* expr = Call(Source{{12, 24}}, "atanh", Negation(f32::Inf()));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: atanh must be called with a value in the range (-1, 1)");
-}
-
template <typename T, bool finite_only>
std::vector<Case> AcosCases() {
std::vector<Case> cases = {
@@ -438,6 +463,19 @@
C({Vec(T(1.0), -T(1.0))}, Vec(T(0), kPi<T>)).FloatComp(),
};
+ ConcatIntoIf<finite_only>( //
+ cases,
+ std::vector<Case>{
+ E({1.1_a},
+ "12:34 error: acos must be called with a value in the range [-1 .. 1] (inclusive)"),
+ E({-1.1_a},
+ "12:34 error: acos must be called with a value in the range [-1 .. 1] (inclusive)"),
+ E({T::Inf()},
+ "12:34 error: acos must be called with a value in the range [-1 .. 1] (inclusive)"),
+ E({-T::Inf()},
+ "12:34 error: acos must be called with a value in the range [-1 .. 1] (inclusive)"),
+ });
+
ConcatIntoIf<!finite_only>( //
cases, std::vector<Case>{
// If i is NaN, NaN is returned
@@ -457,38 +495,6 @@
AcosCases<f32, false>(),
AcosCases<f16, false>()))));
-TEST_F(ResolverConstEvalBuiltinTest, Acos_OutsideRange_Positive) {
- auto* expr = Call(Source{{12, 24}}, "acos", Expr(1.1_a));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: acos must be called with a value in the range [-1, 1]");
-}
-
-TEST_F(ResolverConstEvalBuiltinTest, Acos_OutsideRange_Negative) {
- auto* expr = Call(Source{{12, 24}}, "acos", Negation(1.1_a));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: acos must be called with a value in the range [-1, 1]");
-}
-
-TEST_F(ResolverConstEvalBuiltinTest, Acos_OutsideRange_Positive_INF) {
- auto* expr = Call(Source{{12, 24}}, "acos", Expr(f32::Inf()));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: acos must be called with a value in the range [-1, 1]");
-}
-
-TEST_F(ResolverConstEvalBuiltinTest, Acos_OutsideRange_Negative_INF) {
- auto* expr = Call(Source{{12, 24}}, "acos", Negation(f32::Inf()));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: acos must be called with a value in the range [-1, 1]");
-}
-
template <typename T, bool finite_only>
std::vector<Case> AsinCases() {
std::vector<Case> cases = {
@@ -503,6 +509,19 @@
C({Vec(T(0.0), T(1.0), -T(1.0))}, Vec(T(0.0), kPiOver2<T>, -kPiOver2<T>)).FloatComp(),
};
+ ConcatIntoIf<finite_only>( //
+ cases,
+ std::vector<Case>{
+ E({1.1_a},
+ "12:34 error: asin must be called with a value in the range [-1 .. 1] (inclusive)"),
+ E({-1.1_a},
+ "12:34 error: asin must be called with a value in the range [-1 .. 1] (inclusive)"),
+ E({T::Inf()},
+ "12:34 error: asin must be called with a value in the range [-1 .. 1] (inclusive)"),
+ E({-T::Inf()},
+ "12:34 error: asin must be called with a value in the range [-1 .. 1] (inclusive)"),
+ });
+
ConcatIntoIf<!finite_only>( //
cases, std::vector<Case>{
// If i is NaN, NaN is returned
@@ -522,38 +541,6 @@
AsinCases<f32, false>(),
AsinCases<f16, false>()))));
-TEST_F(ResolverConstEvalBuiltinTest, Asin_OutsideRange_Positive) {
- auto* expr = Call(Source{{12, 24}}, "asin", Expr(1.1_a));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: asin must be called with a value in the range [-1, 1]");
-}
-
-TEST_F(ResolverConstEvalBuiltinTest, Asin_OutsideRange_Negative) {
- auto* expr = Call(Source{{12, 24}}, "asin", Negation(1.1_a));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: asin must be called with a value in the range [-1, 1]");
-}
-
-TEST_F(ResolverConstEvalBuiltinTest, Asin_OutsideRange_Positive_INF) {
- auto* expr = Call(Source{{12, 24}}, "asin", Expr(f32::Inf()));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: asin must be called with a value in the range [-1, 1]");
-}
-
-TEST_F(ResolverConstEvalBuiltinTest, Asin_OutsideRange_Negative_INF) {
- auto* expr = Call(Source{{12, 24}}, "asin", Negation(f32::Inf()));
-
- GlobalConst("C", expr);
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:24 error: asin must be called with a value in the range [-1, 1]");
-}
-
template <typename T, bool finite_only>
std::vector<Case> AsinhCases() {
std::vector<Case> cases = {
@@ -980,12 +967,12 @@
ResolverTestWithParam<std::tuple<size_t, size_t>>;
TEST_P(ResolverConstEvalBuiltinTest_InsertBits_InvalidOffsetAndCount, Test) {
auto& p = GetParam();
- auto* expr = Call(Source{{12, 24}}, sem::str(sem::BuiltinType::kInsertBits), Expr(1_u),
+ auto* expr = Call(Source{{12, 34}}, sem::str(sem::BuiltinType::kInsertBits), Expr(1_u),
Expr(1_u), Expr(u32(std::get<0>(p))), Expr(u32(std::get<1>(p))));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
- "12:24 error: 'offset + 'count' must be less than or equal to the bit width of 'e'");
+ "12:34 error: 'offset + 'count' must be less than or equal to the bit width of 'e'");
}
INSTANTIATE_TEST_SUITE_P(InsertBits,
ResolverConstEvalBuiltinTest_InsertBits_InvalidOffsetAndCount,
@@ -1083,12 +1070,12 @@
ResolverTestWithParam<std::tuple<size_t, size_t>>;
TEST_P(ResolverConstEvalBuiltinTest_ExtractBits_InvalidOffsetAndCount, Test) {
auto& p = GetParam();
- auto* expr = Call(Source{{12, 24}}, sem::str(sem::BuiltinType::kExtractBits), Expr(1_u),
+ auto* expr = Call(Source{{12, 34}}, sem::str(sem::BuiltinType::kExtractBits), Expr(1_u),
Expr(u32(std::get<0>(p))), Expr(u32(std::get<1>(p))));
GlobalConst("C", expr);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
- "12:24 error: 'offset + 'count' must be less than or equal to the bit width of 'e'");
+ "12:34 error: 'offset + 'count' must be less than or equal to the bit width of 'e'");
}
INSTANTIATE_TEST_SUITE_P(ExtractBits,
ResolverConstEvalBuiltinTest_ExtractBits_InvalidOffsetAndCount,
@@ -1282,6 +1269,7 @@
StepCases<f16>()))));
std::vector<Case> QuantizeToF16Cases() {
+ (void)E({Vec(0_f, 0_f)}, ""); // Currently unused, but will be soon.
return {
C({0_f}, 0_f), //
C({-0_f}, -0_f), //