tint/resolver: Ensure materialized values are representable

by the materialized type.

Bug: tint:1504
Change-Id: I3534ce62308ba2ff32c52a2f5bc8480d102153a1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/91422
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index d2cb472..9f7be54 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -134,35 +134,65 @@
 
 struct Data {
     std::string target_type_name;
+    std::string target_element_type_name;
     builder::ast_type_func_ptr target_ast_ty;
     builder::sem_type_func_ptr target_sem_ty;
     builder::ast_expr_func_ptr target_expr;
-    std::string literal_type_name;
-    builder::ast_expr_func_ptr literal_value;
+    std::string source_type_name;
+    builder::ast_expr_func_ptr source_builder;
     std::variant<AInt, AFloat> materialized_value;
+    double literal_value;
 };
 
-template <typename TARGET_TYPE, typename LITERAL_TYPE, typename MATERIALIZED_TYPE = AInt>
-Data Types(MATERIALIZED_TYPE materialized_value = 0_a) {
+template <typename TARGET_TYPE, typename SOURCE_TYPE, typename MATERIALIZED_TYPE>
+Data Types(MATERIALIZED_TYPE materialized_value, double literal_value) {
+    using TargetDataType = builder::DataType<TARGET_TYPE>;
+    using SourceDataType = builder::DataType<SOURCE_TYPE>;
+    using TargetElementDataType = builder::DataType<typename TargetDataType::ElementType>;
     return {
-        builder::DataType<TARGET_TYPE>::Name(),   //
-        builder::DataType<TARGET_TYPE>::AST,      //
-        builder::DataType<TARGET_TYPE>::Sem,      //
-        builder::DataType<TARGET_TYPE>::Expr,     //
-        builder::DataType<LITERAL_TYPE>::Name(),  //
-        builder::DataType<LITERAL_TYPE>::Expr,    //
+        TargetDataType::Name(),         // target_type_name
+        TargetElementDataType::Name(),  // target_element_type_name
+        TargetDataType::AST,            // target_ast_ty
+        TargetDataType::Sem,            // target_sem_ty
+        TargetDataType::Expr,           // target_expr
+        SourceDataType::Name(),         // literal_type_name
+        SourceDataType::Expr,           // literal_builder
         materialized_value,
+        literal_value,
+    };
+}
+
+template <typename TARGET_TYPE, typename SOURCE_TYPE>
+Data Types() {
+    using TargetDataType = builder::DataType<TARGET_TYPE>;
+    using SourceDataType = builder::DataType<SOURCE_TYPE>;
+    using TargetElementDataType = builder::DataType<typename TargetDataType::ElementType>;
+    return {
+        TargetDataType::Name(),         // target_type_name
+        TargetElementDataType::Name(),  // target_element_type_name
+        TargetDataType::AST,            // target_ast_ty
+        TargetDataType::Sem,            // target_sem_ty
+        TargetDataType::Expr,           // target_expr
+        SourceDataType::Name(),         // literal_type_name
+        SourceDataType::Expr,           // literal_builder
+        0_a,
+        0.0,
     };
 }
 
 static std::ostream& operator<<(std::ostream& o, const Data& c) {
-    return o << "[" << c.target_type_name << " <- " << c.literal_type_name << "]";
+    auto print_value = [&](auto&& v) { o << v; };
+    o << "[" << c.target_type_name << " <- " << c.source_type_name << "] [";
+    std::visit(print_value, c.materialized_value);
+    o << " <- " << c.literal_value << "]";
+    return o;
 }
 
 enum class Expectation {
     kMaterialize,
     kNoMaterialize,
     kInvalidCast,
+    kValueCannotBeRepresented,
 };
 
 static std::ostream& operator<<(std::ostream& o, Expectation m) {
@@ -173,6 +203,8 @@
             return o << "no-materialize";
         case Expectation::kInvalidCast:
             return o << "invalid-cast";
+        case Expectation::kValueCannotBeRepresented:
+            return o << "value too low or high";
     }
     return o << "<unknown>";
 }
@@ -191,7 +223,7 @@
 
     auto target_ty = [&] { return data.target_ast_ty(*this); };
     auto target_expr = [&] { return data.target_expr(*this, 42); };
-    auto* literal = data.literal_value(*this, 1);
+    auto* literal = data.source_builder(*this, data.literal_value);
     switch (method) {
         case Method::kVar:
             WrapInFunction(Decl(Var("a", target_ty(), literal)));
@@ -283,110 +315,191 @@
             switch (method) {
                 case Method::kBuiltinArg:
                     expect = "error: no matching call to min(" + data.target_type_name + ", " +
-                             data.literal_type_name + ")";
+                             data.source_type_name + ")";
                     break;
                 case Method::kBinaryOp:
                     expect = "error: no matching overload for operator + (" +
-                             data.target_type_name + ", " + data.literal_type_name + ")";
+                             data.target_type_name + ", " + data.source_type_name + ")";
                     break;
                 default:
-                    expect = "error: cannot convert value of type '" + data.literal_type_name +
+                    expect = "error: cannot convert value of type '" + data.source_type_name +
                              "' to type '" + data.target_type_name + "'";
                     break;
             }
             EXPECT_THAT(r()->error(), testing::StartsWith(expect));
             break;
         }
+        case Expectation::kValueCannotBeRepresented:
+            ASSERT_FALSE(r()->Resolve());
+            EXPECT_THAT(r()->error(), testing::HasSubstr("cannot be represented as '" +
+                                                         data.target_element_type_name + "'"));
+            break;
     }
 }
 
-// TODO(crbug.com/tint/1504): Test for abstract-numeric values not fitting in materialized types.
+/// Methods that support scalar materialization
+constexpr Method kScalarMethods[] = {Method::kLet,         //
+                                     Method::kVar,         //
+                                     Method::kFnArg,       //
+                                     Method::kBuiltinArg,  //
+                                     Method::kReturn,      //
+                                     Method::kArray,       //
+                                     Method::kStruct,      //
+                                     Method::kBinaryOp};
 
-INSTANTIATE_TEST_SUITE_P(MaterializeScalar,
-                         MaterializeAbstractNumeric,                                        //
-                         testing::Combine(testing::Values(Expectation::kMaterialize),       //
-                                          testing::Values(Method::kLet,                     //
-                                                          Method::kVar,                     //
-                                                          Method::kFnArg,                   //
-                                                          Method::kBuiltinArg,              //
-                                                          Method::kReturn,                  //
-                                                          Method::kArray,                   //
-                                                          Method::kStruct,                  //
-                                                          Method::kBinaryOp),               //
-                                          testing::Values(Types<i32, AInt>(1_a),            //
-                                                          Types<u32, AInt>(1_a),            //
-                                                          Types<f32, AFloat>(1.0_a)         //
-                                                          /* Types<f16, AFloat>(1.0_a), */  //
-                                                          /* Types<f16, AFloat>(1.0_a), */)));
+/// Methods that support vector materialization
+constexpr Method kVectorMethods[] = {Method::kLet,         //
+                                     Method::kVar,         //
+                                     Method::kFnArg,       //
+                                     Method::kBuiltinArg,  //
+                                     Method::kReturn,      //
+                                     Method::kArray,       //
+                                     Method::kStruct,      //
+                                     Method::kBinaryOp};
 
-INSTANTIATE_TEST_SUITE_P(MaterializeVector,
-                         MaterializeAbstractNumeric,                                          //
-                         testing::Combine(testing::Values(Expectation::kMaterialize),         //
-                                          testing::Values(Method::kLet,                       //
-                                                          Method::kVar,                       //
-                                                          Method::kFnArg,                     //
-                                                          Method::kBuiltinArg,                //
-                                                          Method::kReturn,                    //
-                                                          Method::kArray,                     //
-                                                          Method::kStruct,                    //
-                                                          Method::kBinaryOp),                 //
-                                          testing::Values(Types<i32V, AIntV>(1_a),            //
-                                                          Types<u32V, AIntV>(1_a),            //
-                                                          Types<f32V, AFloatV>(1.0_a)         //
-                                                          /* Types<f16V, AFloatV>(1.0_a), */  //
-                                                          /* Types<f16V, AFloatV>(1.0_a), */)));
+/// Methods that support matrix materialization
+constexpr Method kMatrixMethods[] = {Method::kLet,     //
+                                     Method::kVar,     //
+                                     Method::kFnArg,   //
+                                     Method::kReturn,  //
+                                     Method::kArray,   //
+                                     Method::kStruct,  //
+                                     Method::kBinaryOp};
 
-INSTANTIATE_TEST_SUITE_P(MaterializeMatrix,
-                         MaterializeAbstractNumeric,                                          //
-                         testing::Combine(testing::Values(Expectation::kMaterialize),         //
-                                          testing::Values(Method::kLet,                       //
-                                                          Method::kVar,                       //
-                                                          Method::kFnArg,                     //
-                                                          Method::kReturn,                    //
-                                                          Method::kArray,                     //
-                                                          Method::kStruct,                    //
-                                                          Method::kBinaryOp),                 //
-                                          testing::Values(Types<f32M, AFloatM>(1.0_a)         //
-                                                          /* Types<f16V, AFloatM>(1.0_a), */  //
-                                                          )));
+/// Methods that support materialization for switch cases
+constexpr Method kSwitchMethods[] = {Method::kSwitchCond,                  //
+                                     Method::kSwitchCase,                  //
+                                     Method::kSwitchCondWithAbstractCase,  //
+                                     Method::kSwitchCaseWithAbstractCase};
 
-INSTANTIATE_TEST_SUITE_P(MaterializeSwitch,
-                         MaterializeAbstractNumeric,                                             //
-                         testing::Combine(testing::Values(Expectation::kMaterialize),            //
-                                          testing::Values(Method::kSwitchCond,                   //
-                                                          Method::kSwitchCase,                   //
-                                                          Method::kSwitchCondWithAbstractCase,   //
-                                                          Method::kSwitchCaseWithAbstractCase),  //
-                                          testing::Values(Types<i32, AInt>(1_a),                 //
-                                                          Types<u32, AInt>(1_a))));
+constexpr double kMaxF32 = static_cast<double>(f32::kHighest);
+constexpr double kPiF64 = 3.141592653589793;
+constexpr double kPiF32 = 3.1415927410125732;  // kPiF64 quantized to f32
+
+// (2^-127)×(1+(0xfffffffffffff÷0x10000000000000))
+constexpr double kTooSmallF32 = 1.1754943508222874e-38;
+
+INSTANTIATE_TEST_SUITE_P(
+    MaterializeScalar,
+    MaterializeAbstractNumeric,                                                       //
+    testing::Combine(testing::Values(Expectation::kMaterialize),                      //
+                     testing::ValuesIn(kScalarMethods),                               //
+                     testing::Values(Types<i32, AInt>(0_a, 0.0),                      //
+                                     Types<i32, AInt>(2147483647_a, 2147483647.0),    //
+                                     Types<i32, AInt>(-2147483648_a, -2147483648.0),  //
+                                     Types<u32, AInt>(0_a, 0.0),                      //
+                                     Types<u32, AInt>(4294967295_a, 4294967295.0),    //
+                                     Types<f32, AFloat>(0.0_a, 0.0),                  //
+                                     Types<f32, AFloat>(AFloat(kMaxF32), kMaxF32),    //
+                                     Types<f32, AFloat>(AFloat(-kMaxF32), -kMaxF32),  //
+                                     Types<f32, AFloat>(AFloat(kPiF32), kPiF64),      //
+                                     Types<f32, AFloat>(0.0_a, kTooSmallF32),         //
+                                     Types<f32, AFloat>(-0.0_a, -kTooSmallF32)        //
+                                     /* Types<f16, AFloat>(1.0_a), */                 //
+                                     /* Types<f16, AFloat>(1.0_a), */)));
+
+INSTANTIATE_TEST_SUITE_P(
+    MaterializeVector,
+    MaterializeAbstractNumeric,                                                         //
+    testing::Combine(testing::Values(Expectation::kMaterialize),                        //
+                     testing::ValuesIn(kVectorMethods),                                 //
+                     testing::Values(Types<i32V, AIntV>(0_a, 0.0),                      //
+                                     Types<i32V, AIntV>(2147483647_a, 2147483647.0),    //
+                                     Types<i32V, AIntV>(-2147483648_a, -2147483648.0),  //
+                                     Types<u32V, AIntV>(0_a, 0.0),                      //
+                                     Types<u32V, AIntV>(4294967295_a, 4294967295.0),    //
+                                     Types<f32V, AFloatV>(0.0_a, 0.0),                  //
+                                     Types<f32V, AFloatV>(AFloat(kMaxF32), kMaxF32),    //
+                                     Types<f32V, AFloatV>(AFloat(-kMaxF32), -kMaxF32),  //
+                                     Types<f32V, AFloatV>(AFloat(kPiF32), kPiF64),      //
+                                     Types<f32V, AFloatV>(0.0_a, kTooSmallF32),         //
+                                     Types<f32V, AFloatV>(-0.0_a, -kTooSmallF32)        //
+                                     /* Types<f16V, AFloatV>(1.0_a), */                 //
+                                     /* Types<f16V, AFloatV>(1.0_a), */)));
+
+INSTANTIATE_TEST_SUITE_P(
+    MaterializeMatrix,
+    MaterializeAbstractNumeric,                                                         //
+    testing::Combine(testing::Values(Expectation::kMaterialize),                        //
+                     testing::ValuesIn(kMatrixMethods),                                 //
+                     testing::Values(Types<f32M, AFloatM>(0.0_a, 0.0),                  //
+                                     Types<f32M, AFloatM>(AFloat(kMaxF32), kMaxF32),    //
+                                     Types<f32M, AFloatM>(AFloat(-kMaxF32), -kMaxF32),  //
+                                     Types<f32M, AFloatM>(AFloat(kPiF32), kPiF64),      //
+                                     Types<f32M, AFloatM>(0.0_a, kTooSmallF32),         //
+                                     Types<f32M, AFloatM>(-0.0_a, -kTooSmallF32)        //
+                                     /* Types<f16V, AFloatM>(1.0_a), */                 //
+                                     )));
+
+INSTANTIATE_TEST_SUITE_P(
+    MaterializeSwitch,
+    MaterializeAbstractNumeric,                                                       //
+    testing::Combine(testing::Values(Expectation::kMaterialize),                      //
+                     testing::ValuesIn(kSwitchMethods),                               //
+                     testing::Values(Types<i32, AInt>(0_a, 0.0),                      //
+                                     Types<i32, AInt>(2147483647_a, 2147483647.0),    //
+                                     Types<i32, AInt>(-2147483648_a, -2147483648.0),  //
+                                     Types<u32, AInt>(0_a, 0.0),                      //
+                                     Types<u32, AInt>(4294967295_a, 4294967295.0))));
 
 // TODO(crbug.com/tint/1504): Enable once we have abstract overloads of builtins / binary ops.
 INSTANTIATE_TEST_SUITE_P(DISABLED_NoMaterialize,
-                         MaterializeAbstractNumeric,                                       //
-                         testing::Combine(testing::Values(Expectation::kNoMaterialize),    //
-                                          testing::Values(Method::kBuiltinArg,             //
-                                                          Method::kBinaryOp),              //
-                                          testing::Values(Types<AInt, AInt>(1_a),          //
-                                                          Types<AFloat, AFloat>(1.0_a),    //
-                                                          Types<AIntV, AIntV>(1_a),        //
-                                                          Types<AFloatV, AFloatV>(1.0_a),  //
-                                                          Types<AFloatM, AFloatM>(1.0_a))));
+                         MaterializeAbstractNumeric,                                     //
+                         testing::Combine(testing::Values(Expectation::kNoMaterialize),  //
+                                          testing::Values(Method::kBuiltinArg,           //
+                                                          Method::kBinaryOp),            //
+                                          testing::Values(Types<AInt, AInt>(),           //
+                                                          Types<AFloat, AFloat>(),       //
+                                                          Types<AIntV, AIntV>(),         //
+                                                          Types<AFloatV, AFloatV>(),     //
+                                                          Types<AFloatM, AFloatM>())));
 INSTANTIATE_TEST_SUITE_P(InvalidCast,
                          MaterializeAbstractNumeric,                                   //
                          testing::Combine(testing::Values(Expectation::kInvalidCast),  //
-                                          testing::Values(Method::kLet,                //
-                                                          Method::kVar,                //
-                                                          Method::kFnArg,              //
-                                                          Method::kBuiltinArg,         //
-                                                          Method::kReturn,             //
-                                                          Method::kArray,              //
-                                                          Method::kStruct,             //
-                                                          Method::kBinaryOp),          //
+                                          testing::ValuesIn(kScalarMethods),           //
                                           testing::Values(Types<i32, AFloat>(),        //
                                                           Types<u32, AFloat>(),        //
                                                           Types<i32V, AFloatV>(),      //
                                                           Types<u32V, AFloatV>())));
 
+INSTANTIATE_TEST_SUITE_P(
+    ScalarValueCannotBeRepresented,
+    MaterializeAbstractNumeric,                                                //
+    testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented),  //
+                     testing::ValuesIn(kScalarMethods),                        //
+                     testing::Values(Types<i32, AInt>(0_a, 2147483648.0),      //
+                                     Types<i32, AInt>(0_a, -2147483649.0),     //
+                                     Types<u32, AInt>(0_a, 4294967296),        //
+                                     Types<u32, AInt>(0_a, -1.0),              //
+                                     Types<f32, AFloat>(0.0_a, 3.5e+38),       //
+                                     Types<f32, AFloat>(0.0_a, -3.5e+38)       //
+                                     /* Types<f16, AFloat>(), */               //
+                                     /* Types<f16, AFloat>(), */)));
+
+INSTANTIATE_TEST_SUITE_P(
+    VectorValueCannotBeRepresented,
+    MaterializeAbstractNumeric,                                                //
+    testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented),  //
+                     testing::ValuesIn(kVectorMethods),                        //
+                     testing::Values(Types<i32V, AIntV>(0_a, 2147483648.0),    //
+                                     Types<i32V, AIntV>(0_a, -2147483649.0),   //
+                                     Types<u32V, AIntV>(0_a, 4294967296),      //
+                                     Types<u32V, AIntV>(0_a, -1.0),            //
+                                     Types<f32V, AFloatV>(0.0_a, 3.5e+38),     //
+                                     Types<f32V, AFloatV>(0.0_a, -3.5e+38)     //
+                                     /* Types<f16V, AFloatV>(), */             //
+                                     /* Types<f16V, AFloatV>(), */)));
+
+INSTANTIATE_TEST_SUITE_P(
+    MatrixValueCannotBeRepresented,
+    MaterializeAbstractNumeric,                                                //
+    testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented),  //
+                     testing::ValuesIn(kMatrixMethods),                        //
+                     testing::Values(Types<f32M, AFloatM>(0.0_a, 3.5e+38),     //
+                                     Types<f32M, AFloatM>(0.0_a, -3.5e+38)     //
+                                     /* Types<f16M, AFloatM>(), */             //
+                                     /* Types<f16M, AFloatM>(), */)));
+
 }  // namespace MaterializeTests
 
 }  // namespace
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index afa41c4..1dcedf9 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -1110,19 +1110,27 @@
     // Helper for actually creating the the materialize node, performing the constant cast, updating
     // the ast -> sem binding, and performing validation.
     auto materialize = [&](const sem::Type* target_ty) -> sem::Materialize* {
-        auto expr_val = EvaluateConstantValue(expr->Declaration(), expr->Type());
-        if (!expr_val.IsValid()) {
+        auto* decl = expr->Declaration();
+        auto expr_val = EvaluateConstantValue(decl, expr->Type());
+        if (!expr_val) {
+            return nullptr;
+        }
+        if (!expr_val->IsValid()) {
             TINT_ICE(Resolver, builder_->Diagnostics())
-                << expr->Declaration()->source
+                << decl->source
                 << " EvaluateConstantValue() returned invalid value for materialized "
                    "value of type: "
                 << (expr->Type() ? expr->Type()->FriendlyName(builder_->Symbols()) : "<null>");
             return nullptr;
         }
-        auto materialized_val = ConvertValue(expr_val, target_ty);
-        auto* m = builder_->create<sem::Materialize>(expr, current_statement_, materialized_val);
+        auto materialized_val = ConvertValue(expr_val.Get(), target_ty, decl->source);
+        if (!materialized_val) {
+            return nullptr;
+        }
+        auto* m =
+            builder_->create<sem::Materialize>(expr, current_statement_, materialized_val.Get());
         m->Behaviors() = expr->Behaviors();
-        builder_->Sem().Replace(expr->Declaration(), m);
+        builder_->Sem().Replace(decl, m);
         return validator_.Materialize(m) ? m : nullptr;
     };
 
@@ -1215,8 +1223,11 @@
     }
 
     auto val = EvaluateConstantValue(expr, ty);
+    if (!val) {
+        return nullptr;
+    }
     bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
-    auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, val,
+    auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, val.Get(),
                                                   has_side_effects, obj->SourceVariable());
     sem->Behaviors() = idx->Behaviors() + obj->Behaviors();
     return sem;
@@ -1230,7 +1241,10 @@
     }
 
     auto val = EvaluateConstantValue(expr, ty);
-    auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, val,
+    if (!val) {
+        return nullptr;
+    }
+    auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, val.Get(),
                                                   inner->HasSideEffects());
 
     sem->Behaviors() = inner->Behaviors();
@@ -1277,9 +1291,12 @@
         if (!MaterializeArguments(args, call_target)) {
             return nullptr;
         }
-        auto value = EvaluateConstantValue(expr, call_target->ReturnType());
+        auto val = EvaluateConstantValue(expr, call_target->ReturnType());
+        if (!val) {
+            return nullptr;
+        }
         return builder_->create<sem::Call>(expr, call_target, std::move(args), current_statement_,
-                                           value, has_side_effects);
+                                           val.Get(), has_side_effects);
     };
 
     // ct_ctor_or_conv is a helper for building either a sem::TypeConstructor or sem::TypeConversion
@@ -1315,9 +1332,12 @@
                 if (!MaterializeArguments(args, call_target)) {
                     return nullptr;
                 }
-                auto value = EvaluateConstantValue(expr, call_target->ReturnType());
+                auto val = EvaluateConstantValue(expr, call_target->ReturnType());
+                if (!val) {
+                    return nullptr;
+                }
                 return builder_->create<sem::Call>(expr, call_target, std::move(args),
-                                                   current_statement_, value, has_side_effects);
+                                                   current_statement_, val.Get(), has_side_effects);
             },
             [&](const sem::Struct* str) -> sem::Call* {
                 auto* call_target = utils::GetOrCreate(
@@ -1337,9 +1357,12 @@
                 if (!MaterializeArguments(args, call_target)) {
                     return nullptr;
                 }
-                auto value = EvaluateConstantValue(expr, call_target->ReturnType());
+                auto val = EvaluateConstantValue(expr, call_target->ReturnType());
+                if (!val) {
+                    return nullptr;
+                }
                 return builder_->create<sem::Call>(expr, call_target, std::move(args),
-                                                   current_statement_, value, has_side_effects);
+                                                   current_statement_, val.Get(), has_side_effects);
             },
             [&](Default) {
                 AddError("type is not constructible", expr->source);
@@ -1616,7 +1639,10 @@
     }
 
     auto val = EvaluateConstantValue(literal, ty);
-    return builder_->create<sem::Expression>(literal, ty, current_statement_, val,
+    if (!val) {
+        return nullptr;
+    }
+    return builder_->create<sem::Expression>(literal, ty, current_statement_, val.Get(),
                                              /* has_side_effects */ false);
 }
 
@@ -1828,8 +1854,11 @@
     }
 
     auto val = EvaluateConstantValue(expr, op.result);
+    if (!val) {
+        return nullptr;
+    }
     bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
-    auto* sem = builder_->create<sem::Expression>(expr, op.result, current_statement_, val,
+    auto* sem = builder_->create<sem::Expression>(expr, op.result, current_statement_, val.Get(),
                                                   has_side_effects);
     sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors();
 
@@ -1902,7 +1931,10 @@
     }
 
     auto val = EvaluateConstantValue(unary, ty);
-    auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_, val,
+    if (!val) {
+        return nullptr;
+    }
+    auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_, val.Get(),
                                                   expr->HasSideEffects(), source_var);
     sem->Behaviors() = expr->Behaviors();
     return sem;
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 07e9636..7d01934 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -34,6 +34,7 @@
 #include "src/tint/sem/constant.h"
 #include "src/tint/sem/function.h"
 #include "src/tint/sem/struct.h"
+#include "src/tint/utils/result.h"
 #include "src/tint/utils/unique_vector.h"
 
 // Forward declarations
@@ -354,15 +355,19 @@
     //////////////////////////////////////////////////////////////////////////////
     /// Constant value evaluation methods
     //////////////////////////////////////////////////////////////////////////////
+    /// The result type of a ConstantEvaluation method. Holds the constant value and a boolean,
+    /// which is true on success, false on an error.
+    using ConstantResult = utils::Result<sem::Constant>;
 
     /// Convert the `value` to `target_type`
     /// @return the converted value
-    sem::Constant ConvertValue(const sem::Constant& value, const sem::Type* target_type);
-
-    sem::Constant EvaluateConstantValue(const ast::Expression* expr, const sem::Type* type);
-    sem::Constant EvaluateConstantValue(const ast::LiteralExpression* literal,
-                                        const sem::Type* type);
-    sem::Constant EvaluateConstantValue(const ast::CallExpression* call, const sem::Type* type);
+    ConstantResult ConvertValue(const sem::Constant& value,
+                                const sem::Type* target_type,
+                                const Source& source);
+    ConstantResult EvaluateConstantValue(const ast::Expression* expr, const sem::Type* type);
+    ConstantResult EvaluateConstantValue(const ast::LiteralExpression* literal,
+                                         const sem::Type* type);
+    ConstantResult EvaluateConstantValue(const ast::CallExpression* call, const sem::Type* type);
 
     /// @returns true if the symbol is the name of a builtin function.
     bool IsBuiltin(Symbol) const;
diff --git a/src/tint/resolver/resolver_constants.cc b/src/tint/resolver/resolver_constants.cc
index 5d1f389..dd846f1 100644
--- a/src/tint/resolver/resolver_constants.cc
+++ b/src/tint/resolver/resolver_constants.cc
@@ -14,7 +14,9 @@
 
 #include "src/tint/resolver/resolver.h"
 
-#include <optional>
+#include <cmath>
+// TODO(https://crbug.com/dawn/1379) Update cpplint and remove NOLINT
+#include <optional>  // NOLINT(build/include_order))
 
 #include "src/tint/sem/abstract_float.h"
 #include "src/tint/sem/abstract_int.h"
@@ -30,46 +32,53 @@
 
 namespace {
 
-/// Converts all the element values of `in` to the type `T`.
+/// Converts and returns all the element values of `in` to the type `T`, using the converter
+/// function `CONVERTER`.
 /// @param elements_in the vector of elements to be converted
+/// @param converter a function-like with the signature `void(TO&, FROM)`
 /// @returns the elements converted to type T.
-template <typename T, typename ELEMENTS_IN>
-sem::Constant::Elements Convert(const ELEMENTS_IN& elements_in) {
+template <typename T, typename ELEMENTS_IN, typename CONVERTER>
+sem::Constant::Elements Transform(const ELEMENTS_IN& elements_in, CONVERTER&& converter) {
     TINT_BEGIN_DISABLE_WARNING_UNREACHABLE_CODE();
 
-    using E = UnwrapNumber<T>;
     return utils::Transform(elements_in, [&](auto value_in) {
-        if constexpr (std::is_same_v<E, bool>) {
+        if constexpr (std::is_same_v<UnwrapNumber<T>, bool>) {
             return AInt(value_in != 0);
-        }
-
-        E converted = static_cast<E>(value_in);
-        if constexpr (IsFloatingPoint<E>) {
-            return AFloat(converted);
         } else {
-            return AInt(converted);
+            T converted{};
+            converter(converted, value_in);
+            if constexpr (IsFloatingPoint<UnwrapNumber<T>>) {
+                return AFloat(converted);
+            } else {
+                return AInt(converted);
+            }
         }
     });
 
     TINT_END_DISABLE_WARNING_UNREACHABLE_CODE();
 }
 
-/// Converts and returns all the element values of `in` to the semantic type `el_ty`.
+/// Converts and returns all the element values of `in` to the semantic type `el_ty`, using the
+/// converter function `CONVERTER`.
 /// @param in the constant to convert
 /// @param el_ty the target element type
-/// @returns the elements converted to `type`
-sem::Constant::Elements Convert(const sem::Constant::Elements& in, const sem::Type* el_ty) {
+/// @param converter a function-like with the signature `void(TO&, FROM)`
+/// @returns the elements converted to `el_ty`
+template <typename CONVERTER>
+sem::Constant::Elements Transform(const sem::Constant::Elements& in,
+                                  const sem::Type* el_ty,
+                                  CONVERTER&& converter) {
     return std::visit(
         [&](auto&& v) {
             return Switch(
                 el_ty,  //
-                [&](const sem::AbstractInt*) { return Convert<AInt>(v); },
-                [&](const sem::AbstractFloat*) { return Convert<AFloat>(v); },
-                [&](const sem::I32*) { return Convert<i32>(v); },
-                [&](const sem::U32*) { return Convert<u32>(v); },
-                [&](const sem::F32*) { return Convert<f32>(v); },
-                [&](const sem::F16*) { return Convert<f16>(v); },
-                [&](const sem::Bool*) { return Convert<bool>(v); },
+                [&](const sem::AbstractInt*) { return Transform<AInt>(v, converter); },
+                [&](const sem::AbstractFloat*) { return Transform<AFloat>(v, converter); },
+                [&](const sem::I32*) { return Transform<i32>(v, converter); },
+                [&](const sem::U32*) { return Transform<u32>(v, converter); },
+                [&](const sem::F32*) { return Transform<f32>(v, converter); },
+                [&](const sem::F16*) { return Transform<f16>(v, converter); },
+                [&](const sem::Bool*) { return Transform<bool>(v, converter); },
                 [&](Default) -> sem::Constant::Elements {
                     diag::List diags;
                     TINT_UNREACHABLE(Semantic, diags)
@@ -80,44 +89,91 @@
         in);
 }
 
+/// Converts and returns all the elements in `in` to the type `el_ty`, by performing a `static_cast`
+/// on each element value. No checks will be performed that the value fits in the target type.
+/// @param in the input elements
+/// @param el_ty the target element type
+/// @returns the elements converted to `el_ty`
+sem::Constant::Elements ConvertElements(const sem::Constant::Elements& in, const sem::Type* el_ty) {
+    return Transform(in, el_ty, [](auto& el_out, auto el_in) {
+        el_out = std::decay_t<decltype(el_out)>(el_in);
+    });
+}
+
+/// Converts and returns all the elements in `in` to the type `el_ty`, by performing a
+/// `CheckedConvert` on each element value. A single error diagnostic will be raised if an element
+/// value cannot be represented by the target type.
+/// @param in the input elements
+/// @param el_ty the target element type
+/// @returns the elements converted to `el_ty`, or a Failure if some elements could not be
+/// represented by the target type.
+utils::Result<sem::Constant::Elements> MaterializeElements(const sem::Constant::Elements& in,
+                                                           const sem::Type* el_ty,
+                                                           ProgramBuilder& builder,
+                                                           Source source) {
+    std::optional<std::string> failure;
+
+    auto out = Transform(in, el_ty, [&](auto& el_out, auto el_in) {
+        using OUT = std::decay_t<decltype(el_out)>;
+        if (auto conv = CheckedConvert<OUT>(el_in)) {
+            el_out = conv.Get();
+        } else if (conv.Failure() == ConversionFailure::kTooSmall) {
+            el_out = OUT(el_in < 0 ? -0.0 : 0.0);
+        } else if (!failure.has_value()) {
+            std::stringstream ss;
+            ss << "value " << el_in << " cannot be represented as ";
+            ss << "'" << builder.FriendlyName(el_ty) << "'";
+            failure = ss.str();
+        }
+    });
+
+    if (failure.has_value()) {
+        builder.Diagnostics().add_error(diag::System::Resolver, std::move(failure.value()), source);
+        return utils::Failure;
+    }
+
+    return out;
+}
+
 }  // namespace
 
-sem::Constant Resolver::EvaluateConstantValue(const ast::Expression* expr, const sem::Type* type) {
+utils::Result<sem::Constant> Resolver::EvaluateConstantValue(const ast::Expression* expr,
+                                                             const sem::Type* type) {
     if (auto* e = expr->As<ast::LiteralExpression>()) {
         return EvaluateConstantValue(e, type);
     }
     if (auto* e = expr->As<ast::CallExpression>()) {
         return EvaluateConstantValue(e, type);
     }
-    return {};
+    return sem::Constant{};
 }
 
-sem::Constant Resolver::EvaluateConstantValue(const ast::LiteralExpression* literal,
-                                              const sem::Type* type) {
+utils::Result<sem::Constant> Resolver::EvaluateConstantValue(const ast::LiteralExpression* literal,
+                                                             const sem::Type* type) {
     return Switch(
         literal,
+        [&](const ast::BoolLiteralExpression* lit) {
+            return sem::Constant{type, {AInt(lit->value ? 1 : 0)}};
+        },
         [&](const ast::IntLiteralExpression* lit) {
             return sem::Constant{type, {AInt(lit->value)}};
         },
         [&](const ast::FloatLiteralExpression* lit) {
             return sem::Constant{type, {AFloat(lit->value)}};
-        },
-        [&](const ast::BoolLiteralExpression* lit) {
-            return sem::Constant{type, {AInt(lit->value ? 1 : 0)}};
         });
 }
 
-sem::Constant Resolver::EvaluateConstantValue(const ast::CallExpression* call,
-                                              const sem::Type* ty) {
+utils::Result<sem::Constant> Resolver::EvaluateConstantValue(const ast::CallExpression* call,
+                                                             const sem::Type* ty) {
     uint32_t result_size = 0;
     auto* el_ty = sem::Type::ElementOf(ty, &result_size);
     if (!el_ty) {
-        return {};
+        return sem::Constant{};
     }
 
     // ElementOf() will also return the element type of array, which we do not support.
     if (ty->Is<sem::Array>()) {
-        return {};
+        return sem::Constant{};
     }
 
     // For zero value init, return 0s
@@ -142,15 +198,15 @@
     for (auto* expr : call->args) {
         auto* arg = builder_->Sem().Get(expr);
         if (!arg) {
-            return {};
+            return sem::Constant{};
         }
         auto value = arg->ConstantValue();
         if (!value) {
-            return {};
+            return sem::Constant{};
         }
 
         // Convert the elements to the desired type.
-        auto converted = Convert(value.GetElements(), el_ty);
+        auto converted = ConvertElements(value.GetElements(), el_ty);
 
         if (elements.has_value()) {
             // Append the converted vector to elements
@@ -180,20 +236,25 @@
     return sem::Constant(ty, std::move(elements.value()));
 }
 
-sem::Constant Resolver::ConvertValue(const sem::Constant& value, const sem::Type* ty) {
+utils::Result<sem::Constant> Resolver::ConvertValue(const sem::Constant& value,
+                                                    const sem::Type* ty,
+                                                    const Source& source) {
     if (value.Type() == ty) {
         return value;
     }
 
     auto* el_ty = sem::Type::ElementOf(ty);
     if (el_ty == nullptr) {
-        return {};
+        return sem::Constant{};
     }
     if (value.ElementType() == el_ty) {
         return sem::Constant(ty, value.GetElements());
     }
 
-    return sem::Constant(ty, Convert(value.GetElements(), el_ty));
+    if (auto res = MaterializeElements(value.GetElements(), el_ty, *builder_, source)) {
+        return sem::Constant(ty, std::move(res.Get()));
+    }
+    return utils::Failure;
 }
 
 }  // namespace tint::resolver