tint/sem: Rename Expression to ValueExpression

ast::IdentifierExpression may also resolve to a type or core enumerator

Bug: tint:1810
Change-Id: I85e3bea67e1146215079ec47430784f2fb39043d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/118402
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 7b114ea..066d656 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -367,7 +367,6 @@
     "sem/call.h",
     "sem/call_target.h",
     "sem/evaluation_stage.h",
-    "sem/expression.h",
     "sem/for_loop_statement.h",
     "sem/function.h",
     "sem/if_statement.h",
@@ -388,6 +387,7 @@
     "sem/type_conversion.h",
     "sem/type_initializer.h",
     "sem/type_mappings.h",
+    "sem/value_expression.h",
     "sem/variable.h",
     "sem/while_statement.h",
   ]
@@ -760,8 +760,6 @@
     "sem/call_target.cc",
     "sem/call_target.h",
     "sem/evaluation_stage.h",
-    "sem/expression.cc",
-    "sem/expression.h",
     "sem/for_loop_statement.cc",
     "sem/for_loop_statement.h",
     "sem/function.cc",
@@ -799,6 +797,8 @@
     "sem/type_initializer.cc",
     "sem/type_initializer.h",
     "sem/type_mappings.h",
+    "sem/value_expression.cc",
+    "sem/value_expression.h",
     "sem/variable.cc",
     "sem/variable.h",
     "sem/while_statement.cc",
@@ -1484,8 +1484,8 @@
     sources = [
       "sem/builtin_test.cc",
       "sem/diagnostic_severity_test.cc",
-      "sem/expression_test.cc",
       "sem/struct_test.cc",
+      "sem/value_expression_test.cc",
     ]
   }
 
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 2c8d022..dfd33ee 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -328,8 +328,6 @@
   sem/call.cc
   sem/call.h
   sem/evaluation_stage.h
-  sem/expression.cc
-  sem/expression.h
   sem/for_loop_statement.cc
   sem/for_loop_statement.h
   sem/function.cc
@@ -363,6 +361,8 @@
   sem/type_conversion.h
   sem/type_mappings.h
   sem/variable.cc
+  sem/value_expression.cc
+  sem/value_expression.h
   sem/while_statement.cc
   sem/while_statement.h
   symbol_table.cc
@@ -972,8 +972,8 @@
     scope_stack_test.cc
     sem/builtin_test.cc
     sem/diagnostic_severity_test.cc
-    sem/expression_test.cc
     sem/struct_test.cc
+    sem/value_expression_test.cc
     source_test.cc
     symbol_table_test.cc
     symbol_test.cc
diff --git a/src/tint/ast/expression.cc b/src/tint/ast/expression.cc
index 17b3dc2..8eacff1 100644
--- a/src/tint/ast/expression.cc
+++ b/src/tint/ast/expression.cc
@@ -14,8 +14,8 @@
 
 #include "src/tint/ast/expression.h"
 
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/info.h"
+#include "src/tint/sem/value_expression.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ast::Expression);
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.cc
index f1d7e4d..90c0820 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.cc
@@ -19,8 +19,8 @@
 #include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/sem/variable.h"
 
 namespace tint::fuzzers::ast_fuzzer {
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc
index 8146aa4..7560cf7 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.cc
@@ -19,8 +19,8 @@
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
 
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/sem/variable.h"
 
 namespace tint::fuzzers::ast_fuzzer {
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc
index 12fe82c..11c8cd6 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc
@@ -20,8 +20,8 @@
 #include "src/tint/fuzzers/tint_ast_fuzzer/expression_size.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/sem/value_expression.h"
 
 namespace tint::fuzzers::ast_fuzzer {
 
@@ -51,7 +51,7 @@
             continue;
         }
 
-        const auto* expr_sem_node = tint::As<sem::Expression>(program.Sem().Get(expr_ast_node));
+        const auto* expr_sem_node = program.Sem().Get<sem::ValueExpression>(expr_ast_node);
 
         // Transformation applies only when the semantic node for the given
         // expression is present.
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
index 3420e45..9d65d78 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
@@ -50,8 +50,7 @@
         return false;
     }
 
-    const auto* expression_sem_node =
-        tint::As<sem::Expression>(program.Sem().Get(expression_ast_node));
+    const auto* expression_sem_node = program.Sem().Get<sem::ValueExpression>(expression_ast_node);
 
     if (!expression_sem_node) {
         // Semantic information for the expression ast node is not present
@@ -94,7 +93,7 @@
 }
 
 std::vector<ast::UnaryOp> MutationWrapUnaryOperator::GetValidUnaryWrapper(
-    const sem::Expression& expr) {
+    const sem::ValueExpression& expr) {
     const auto* expr_type = expr.Type();
     if (expr_type->is_bool_scalar_or_vector()) {
         return {ast::UnaryOp::kNot};
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h
index 96d0926..0ca6213 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h
@@ -68,7 +68,7 @@
     /// expression.
     /// @param expr - an `ast::Expression` instance from node id map.
     /// @return a list of unary operators.
-    static std::vector<ast::UnaryOp> GetValidUnaryWrapper(const sem::Expression& expr);
+    static std::vector<ast::UnaryOp> GetValidUnaryWrapper(const sem::ValueExpression& expr);
 
   private:
     protobufs::MutationWrapUnaryOperator message_;
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 2a8ed06e..cde71e2 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -47,9 +47,9 @@
 #include "src/tint/ir/switch.h"
 #include "src/tint/ir/terminator.h"
 #include "src/tint/program.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/module.h"
 #include "src/tint/sem/switch_statement.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/type/void.h"
 
 namespace tint::ir {
diff --git a/src/tint/program.cc b/src/tint/program.cc
index 5003caa..f2a766a 100644
--- a/src/tint/program.cc
+++ b/src/tint/program.cc
@@ -18,7 +18,7 @@
 
 #include "src/tint/demangler.h"
 #include "src/tint/resolver/resolver.h"
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 
 namespace tint {
 namespace {
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
index 9b9e1d2..1a0faf6 100644
--- a/src/tint/program_builder.cc
+++ b/src/tint/program_builder.cc
@@ -19,7 +19,7 @@
 #include "src/tint/ast/variable_decl_statement.h"
 #include "src/tint/debug.h"
 #include "src/tint/demangler.h"
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/sem/variable.h"
 #include "src/tint/utils/compiler_macros.h"
 
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index 9164276..0517629 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -1165,7 +1165,7 @@
 }
 
 ConstEval::Result ConstEval::ArrayOrStructInit(const type::Type* ty,
-                                               utils::VectorRef<const sem::Expression*> args) {
+                                               utils::VectorRef<const sem::ValueExpression*> args) {
     if (args.IsEmpty()) {
         return ZeroValue(builder, ty);
     }
@@ -1277,8 +1277,8 @@
     return builder.create<constant::Composite>(ty, args);
 }
 
-ConstEval::Result ConstEval::Index(const sem::Expression* obj_expr,
-                                   const sem::Expression* idx_expr) {
+ConstEval::Result ConstEval::Index(const sem::ValueExpression* obj_expr,
+                                   const sem::ValueExpression* idx_expr) {
     auto idx_val = idx_expr->ConstantValue();
     if (!idx_val) {
         return nullptr;
@@ -1306,7 +1306,7 @@
     return obj_val->Index(static_cast<size_t>(idx));
 }
 
-ConstEval::Result ConstEval::MemberAccess(const sem::Expression* obj_expr,
+ConstEval::Result ConstEval::MemberAccess(const sem::ValueExpression* obj_expr,
                                           const type::StructMember* member) {
     auto obj_val = obj_expr->ConstantValue();
     if (!obj_val) {
@@ -1316,7 +1316,7 @@
 }
 
 ConstEval::Result ConstEval::Swizzle(const type::Type* ty,
-                                     const sem::Expression* vec_expr,
+                                     const sem::ValueExpression* vec_expr,
                                      utils::VectorRef<uint32_t> indices) {
     auto* vec_val = vec_expr->ConstantValue();
     if (!vec_val) {
diff --git a/src/tint/resolver/const_eval.h b/src/tint/resolver/const_eval.h
index dbb1c2e..6c26511 100644
--- a/src/tint/resolver/const_eval.h
+++ b/src/tint/resolver/const_eval.h
@@ -34,7 +34,7 @@
 class Value;
 }  // namespace tint::constant
 namespace tint::sem {
-class Expression;
+class ValueExpression;
 }  // namespace tint::sem
 namespace tint::type {
 class StructMember;
@@ -77,7 +77,8 @@
     /// @param ty the target type - must be an array or initializer
     /// @param args the input arguments
     /// @return the constructed value, or null if the value cannot be calculated
-    Result ArrayOrStructInit(const type::Type* ty, utils::VectorRef<const sem::Expression*> args);
+    Result ArrayOrStructInit(const type::Type* ty,
+                             utils::VectorRef<const sem::ValueExpression*> args);
 
     /// @param ty the target type
     /// @param value the value being converted
@@ -89,7 +90,7 @@
     /// @param obj the object being indexed
     /// @param idx the index expression
     /// @return the result of the index, or null if the value cannot be calculated
-    Result Index(const sem::Expression* obj, const sem::Expression* idx);
+    Result Index(const sem::ValueExpression* obj, const sem::ValueExpression* idx);
 
     /// @param ty the result type
     /// @param lit the literal AST node
@@ -99,14 +100,14 @@
     /// @param obj the object being accessed
     /// @param member the member
     /// @return the result of the member access, or null if the value cannot be calculated
-    Result MemberAccess(const sem::Expression* obj, const type::StructMember* member);
+    Result MemberAccess(const sem::ValueExpression* obj, const type::StructMember* member);
 
     /// @param ty the result type
     /// @param vector the vector being swizzled
     /// @param indices the swizzle indices
     /// @return the result of the swizzle, or null if the value cannot be calculated
     Result Swizzle(const type::Type* ty,
-                   const sem::Expression* vector,
+                   const sem::ValueExpression* vector,
                    utils::VectorRef<uint32_t> indices);
 
     /// Convert the `value` to `target_type`
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index 274244d..e34e999 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -76,7 +76,7 @@
   protected:
     using ProgramBuilder::FriendlyName;
 
-    void CheckTypesAndValues(const sem::Expression* expr,
+    void CheckTypesAndValues(const sem::ValueExpression* expr,
                              const tint::type::Type* expected_sem_ty,
                              const std::variant<AInt, AFloat>& expected_value) {
         std::visit([&](auto v) { CheckTypesAndValuesImpl(expr, expected_sem_ty, v); },
@@ -85,7 +85,7 @@
 
   private:
     template <typename T>
-    void CheckTypesAndValuesImpl(const sem::Expression* expr,
+    void CheckTypesAndValuesImpl(const sem::ValueExpression* expr,
                                  const tint::type::Type* expected_sem_ty,
                                  T expected_value) {
         EXPECT_TYPE(expr->Type(), expected_sem_ty);
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index de616b9..3bccfe8 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -453,7 +453,7 @@
         }
     }
 
-    const sem::Expression* rhs = nullptr;
+    const sem::ValueExpression* rhs = nullptr;
 
     // Does the variable have an initializer?
     if (v->initializer) {
@@ -545,7 +545,7 @@
         return nullptr;
     }
 
-    const sem::Expression* rhs = nullptr;
+    const sem::ValueExpression* rhs = nullptr;
     {
         ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "const initializer"};
         TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
@@ -603,7 +603,7 @@
         }
     }
 
-    const sem::Expression* rhs = nullptr;
+    const sem::ValueExpression* rhs = nullptr;
 
     // Does the variable have a initializer?
     if (var->initializer) {
@@ -1164,7 +1164,7 @@
     }
 
     auto values = attr->Values();
-    utils::Vector<const sem::Expression*, 3> args;
+    utils::Vector<const sem::ValueExpression*, 3> args;
     utils::Vector<const type::Type*, 3> arg_tys;
 
     constexpr const char* kErrBadExpr =
@@ -1515,7 +1515,7 @@
     });
 }
 
-sem::Expression* Resolver::Expression(const ast::Expression* root) {
+sem::ValueExpression* Resolver::Expression(const ast::Expression* root) {
     utils::Vector<const ast::Expression*, 64> sorted;
     constexpr size_t kMaxExpressionDepth = 512U;
     bool failed = false;
@@ -1550,30 +1550,34 @@
     for (auto* expr : utils::Reverse(sorted)) {
         auto* sem_expr = Switch(
             expr,
-            [&](const ast::IndexAccessorExpression* array) -> sem::Expression* {
+            [&](const ast::IndexAccessorExpression* array) -> sem::ValueExpression* {
                 return IndexAccessor(array);
             },
-            [&](const ast::BinaryExpression* bin_op) -> sem::Expression* { return Binary(bin_op); },
-            [&](const ast::BitcastExpression* bitcast) -> sem::Expression* {
+            [&](const ast::BinaryExpression* bin_op) -> sem::ValueExpression* {
+                return Binary(bin_op);
+            },
+            [&](const ast::BitcastExpression* bitcast) -> sem::ValueExpression* {
                 return Bitcast(bitcast);
             },
-            [&](const ast::CallExpression* call) -> sem::Expression* { return Call(call); },
-            [&](const ast::IdentifierExpression* ident) -> sem::Expression* {
+            [&](const ast::CallExpression* call) -> sem::ValueExpression* { return Call(call); },
+            [&](const ast::IdentifierExpression* ident) -> sem::ValueExpression* {
                 return Identifier(ident);
             },
-            [&](const ast::LiteralExpression* literal) -> sem::Expression* {
+            [&](const ast::LiteralExpression* literal) -> sem::ValueExpression* {
                 return Literal(literal);
             },
-            [&](const ast::MemberAccessorExpression* member) -> sem::Expression* {
+            [&](const ast::MemberAccessorExpression* member) -> sem::ValueExpression* {
                 return MemberAccessor(member);
             },
-            [&](const ast::UnaryOpExpression* unary) -> sem::Expression* { return UnaryOp(unary); },
-            [&](const ast::PhonyExpression*) -> sem::Expression* {
-                return builder_->create<sem::Expression>(expr, builder_->create<type::Void>(),
-                                                         sem::EvaluationStage::kRuntime,
-                                                         current_statement_,
-                                                         /* constant_value */ nullptr,
-                                                         /* has_side_effects */ false);
+            [&](const ast::UnaryOpExpression* unary) -> sem::ValueExpression* {
+                return UnaryOp(unary);
+            },
+            [&](const ast::PhonyExpression*) -> sem::ValueExpression* {
+                return builder_->create<sem::ValueExpression>(expr, builder_->create<type::Void>(),
+                                                              sem::EvaluationStage::kRuntime,
+                                                              current_statement_,
+                                                              /* constant_value */ nullptr,
+                                                              /* has_side_effects */ false);
             },
             [&](Default) {
                 TINT_ICE(Resolver, diagnostics_)
@@ -1621,7 +1625,7 @@
     return nullptr;
 }
 
-void Resolver::RegisterStore(const sem::Expression* expr) {
+void Resolver::RegisterStore(const sem::ValueExpression* expr) {
     auto& info = alias_analysis_infos_[current_function_];
     Switch(
         expr->RootIdentifier(),
@@ -1643,11 +1647,11 @@
 
     // Helper to generate an aliasing error diagnostic.
     struct Alias {
-        const sem::Expression* expr;          // the "other expression"
+        const sem::ValueExpression* expr;     // the "other expression"
         enum { Argument, ModuleScope } type;  // the type of the "other" expression
         std::string access;                   // the access performed for the "other" expression
     };
-    auto make_error = [&](const sem::Expression* arg, Alias&& var) {
+    auto make_error = [&](const sem::ValueExpression* arg, Alias&& var) {
         // TODO(crbug.com/tint/1675): Switch to error and return false after deprecation period.
         AddWarning("invalid aliased pointer argument", arg->Declaration()->source);
         switch (var.type) {
@@ -1672,8 +1676,8 @@
     auto& caller_info = alias_analysis_infos_[current_function_];
 
     // Track the set of root identifiers that are read and written by arguments passed in this call.
-    std::unordered_map<const sem::Variable*, const sem::Expression*> arg_reads;
-    std::unordered_map<const sem::Variable*, const sem::Expression*> arg_writes;
+    std::unordered_map<const sem::Variable*, const sem::ValueExpression*> arg_reads;
+    std::unordered_map<const sem::Variable*, const sem::ValueExpression*> arg_writes;
     for (size_t i = 0; i < args.Length(); i++) {
         auto* arg = args[i];
         if (!arg->Type()->Is<type::Pointer>()) {
@@ -1787,7 +1791,7 @@
         });
 }
 
-const sem::Expression* Resolver::Load(const sem::Expression* expr) {
+const sem::ValueExpression* Resolver::Load(const sem::ValueExpression* expr) {
     if (!expr) {
         // Allow for Load(Expression(blah)), where failures pass through Load()
         return nullptr;
@@ -1814,8 +1818,8 @@
     return load;
 }
 
-const sem::Expression* Resolver::Materialize(const sem::Expression* expr,
-                                             const type::Type* target_type /* = nullptr */) {
+const sem::ValueExpression* Resolver::Materialize(const sem::ValueExpression* expr,
+                                                  const type::Type* target_type /* = nullptr */) {
     if (!expr) {
         // Allow for Materialize(Expression(blah)), where failures pass through Materialize()
         return nullptr;
@@ -1865,7 +1869,7 @@
 }
 
 template <size_t N>
-bool Resolver::MaybeMaterializeAndLoadArguments(utils::Vector<const sem::Expression*, N>& args,
+bool Resolver::MaybeMaterializeAndLoadArguments(utils::Vector<const sem::ValueExpression*, N>& args,
                                                 const sem::CallTarget* target) {
     for (size_t i = 0, n = std::min(args.Length(), target->Parameters().Length()); i < n; i++) {
         const auto* param_ty = target->Parameters()[i]->Type();
@@ -1905,7 +1909,7 @@
 
 template <size_t N>
 utils::Result<utils::Vector<const constant::Value*, N>> Resolver::ConvertArguments(
-    const utils::Vector<const sem::Expression*, N>& args,
+    const utils::Vector<const sem::ValueExpression*, N>& args,
     const sem::CallTarget* target) {
     auto const_args = utils::Transform(args, [](auto* arg) { return arg->ConstantValue(); });
     for (size_t i = 0, n = std::min(args.Length(), target->Parameters().Length()); i < n; i++) {
@@ -1917,7 +1921,7 @@
     return const_args;
 }
 
-sem::Expression* Resolver::IndexAccessor(const ast::IndexAccessorExpression* expr) {
+sem::ValueExpression* Resolver::IndexAccessor(const ast::IndexAccessorExpression* expr) {
     auto* idx = Load(Materialize(sem_.Get(expr->index)));
     if (!idx) {
         return nullptr;
@@ -1980,7 +1984,7 @@
     return sem;
 }
 
-sem::Expression* Resolver::Bitcast(const ast::BitcastExpression* expr) {
+sem::ValueExpression* Resolver::Bitcast(const ast::BitcastExpression* expr) {
     auto* inner = Load(Materialize(sem_.Get(expr->expr)));
     if (!inner) {
         return nullptr;
@@ -2007,8 +2011,8 @@
         }
     }
 
-    auto* sem = builder_->create<sem::Expression>(expr, ty, stage, current_statement_,
-                                                  std::move(value), inner->HasSideEffects());
+    auto* sem = builder_->create<sem::ValueExpression>(expr, ty, stage, current_statement_,
+                                                       std::move(value), inner->HasSideEffects());
     sem->Behaviors() = inner->Behaviors();
     return sem;
 }
@@ -2021,7 +2025,7 @@
     // * A type conversion.
 
     // Resolve all of the arguments, their types and the set of behaviors.
-    utils::Vector<const sem::Expression*, 8> args;
+    utils::Vector<const sem::ValueExpression*, 8> args;
     args.Reserve(expr->args.Length());
     auto args_stage = sem::EvaluationStage::kConstant;
     sem::Behaviors arg_behaviors;
@@ -2094,8 +2098,8 @@
                 // Constant evaluation failed.
                 // Can happen for expressions that will fail validation (later).
                 // Use the kRuntime EvaluationStage, as kConstant will trigger an assertion in
-                // the sem::Expression initializer, which checks that kConstant is paired with a
-                // constant value.
+                // the sem::ValueExpression initializer, which checks that kConstant is paired with
+                // a constant value.
                 stage = sem::EvaluationStage::kRuntime;
             }
         }
@@ -2344,7 +2348,7 @@
 template <size_t N>
 sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
                                  sem::BuiltinType builtin_type,
-                                 utils::Vector<const sem::Expression*, N>& args) {
+                                 utils::Vector<const sem::ValueExpression*, N>& args) {
     auto arg_stage = sem::EvaluationStage::kConstant;
     for (auto* arg : args) {
         arg_stage = sem::EarliestStage(arg_stage, arg->Stage());
@@ -2515,8 +2519,9 @@
     return nullptr;
 }
 
-void Resolver::CollectTextureSamplerPairs(const sem::Builtin* builtin,
-                                          utils::VectorRef<const sem::Expression*> args) const {
+void Resolver::CollectTextureSamplerPairs(
+    const sem::Builtin* builtin,
+    utils::VectorRef<const sem::ValueExpression*> args) const {
     // Collect a texture/sampler pair for this builtin.
     const auto& signature = builtin->Signature();
     int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
@@ -2542,7 +2547,7 @@
 template <size_t N>
 sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr,
                                   sem::Function* target,
-                                  utils::Vector<const sem::Expression*, N>& args,
+                                  utils::Vector<const sem::ValueExpression*, N>& args,
                                   sem::Behaviors arg_behaviors) {
     auto sym = expr->target.name->symbol;
     auto name = builder_->Symbols().NameFor(sym);
@@ -2592,8 +2597,9 @@
     return call;
 }
 
-void Resolver::CollectTextureSamplerPairs(sem::Function* func,
-                                          utils::VectorRef<const sem::Expression*> args) const {
+void Resolver::CollectTextureSamplerPairs(
+    sem::Function* func,
+    utils::VectorRef<const sem::ValueExpression*> args) const {
     // Map all texture/sampler pairs from the target function to the
     // current function. These can only be global or parameter
     // variables. Resolve any parameter variables to the corresponding
@@ -2615,7 +2621,7 @@
     }
 }
 
-sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) {
+sem::ValueExpression* Resolver::Literal(const ast::LiteralExpression* literal) {
     auto* ty = Switch(
         literal,
         [&](const ast::IntLiteralExpression* i) -> type::Type* {
@@ -2669,11 +2675,12 @@
             return nullptr;
         }
     }
-    return builder_->create<sem::Expression>(literal, ty, stage, current_statement_, std::move(val),
-                                             /* has_side_effects */ false);
+    return builder_->create<sem::ValueExpression>(literal, ty, stage, current_statement_,
+                                                  std::move(val),
+                                                  /* has_side_effects */ false);
 }
 
-sem::Expression* Resolver::Identifier(const ast::IdentifierExpression* expr) {
+sem::ValueExpression* Resolver::Identifier(const ast::IdentifierExpression* expr) {
     Mark(expr->identifier);
     auto symbol = expr->identifier->symbol;
     auto* sem_resolved = sem_.ResolvedSymbol<sem::Node>(expr);
@@ -2769,7 +2776,7 @@
     return nullptr;
 }
 
-sem::Expression* Resolver::MemberAccessor(const ast::MemberAccessorExpression* expr) {
+sem::ValueExpression* Resolver::MemberAccessor(const ast::MemberAccessorExpression* expr) {
     auto* structure = sem_.TypeOf(expr->structure);
     auto* storage_ty = structure->UnwrapRef();
     auto* object = sem_.Get(expr->structure);
@@ -2784,7 +2791,7 @@
 
     return Switch(
         storage_ty,  //
-        [&](const sem::Struct* str) -> sem::Expression* {
+        [&](const sem::Struct* str) -> sem::ValueExpression* {
             auto symbol = expr->member->symbol;
 
             const sem::StructMember* member = nullptr;
@@ -2817,7 +2824,7 @@
                                                              has_side_effects, root_ident);
         },
 
-        [&](const type::Vector* vec) -> sem::Expression* {
+        [&](const type::Vector* vec) -> sem::ValueExpression* {
             std::string s = builder_->Symbols().NameFor(expr->member->symbol);
             auto size = s.size();
             utils::Vector<uint32_t, 4> swizzle;
@@ -2868,7 +2875,7 @@
                 return nullptr;
             }
 
-            const sem::Expression* obj_expr = object;
+            const sem::ValueExpression* obj_expr = object;
             if (size == 1) {
                 // A single element swizzle is just the type of the vector.
                 ty = vec->type();
@@ -2900,7 +2907,7 @@
         });
 }
 
-sem::Expression* Resolver::Binary(const ast::BinaryExpression* expr) {
+sem::ValueExpression* Resolver::Binary(const ast::BinaryExpression* expr) {
     const auto* lhs = sem_.Get(expr->lhs);
     const auto* rhs = sem_.Get(expr->rhs);
     auto* lhs_ty = lhs->Type()->UnwrapRef();
@@ -2970,14 +2977,14 @@
     }
 
     bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
-    auto* sem = builder_->create<sem::Expression>(expr, op.result, stage, current_statement_, value,
-                                                  has_side_effects);
+    auto* sem = builder_->create<sem::ValueExpression>(expr, op.result, stage, current_statement_,
+                                                       value, has_side_effects);
     sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors();
 
     return sem;
 }
 
-sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
+sem::ValueExpression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
     const auto* expr = sem_.Get(unary->expr);
     auto* expr_ty = expr->Type();
     if (!expr_ty) {
@@ -3066,8 +3073,8 @@
         }
     }
 
-    auto* sem = builder_->create<sem::Expression>(unary, ty, stage, current_statement_, value,
-                                                  expr->HasSideEffects(), root_ident);
+    auto* sem = builder_->create<sem::ValueExpression>(unary, ty, stage, current_statement_, value,
+                                                       expr->HasSideEffects(), root_ident);
     sem->Behaviors() = expr->Behaviors();
     return sem;
 }
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 8c58626..b2cddf4 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -126,7 +126,7 @@
     /// list (leaf-first) of all the expression nodes. Each of the expressions are then resolved by
     /// dispatching to the appropriate expression handlers below.
     /// @returns the resolved semantic node for the expression `expr`, or nullptr on failure.
-    sem::Expression* Expression(const ast::Expression* expr);
+    sem::ValueExpression* Expression(const ast::Expression* expr);
 
     ////////////////////////////////////////////////////////////////////////////////////////////////
     // Expression resolving methods
@@ -137,28 +137,28 @@
     // not attempt to resolve their children. This design avoids recursion, which is a common cause
     // of stack-overflows.
     ////////////////////////////////////////////////////////////////////////////////////////////////
-    sem::Expression* IndexAccessor(const ast::IndexAccessorExpression*);
-    sem::Expression* Binary(const ast::BinaryExpression*);
-    sem::Expression* Bitcast(const ast::BitcastExpression*);
+    sem::ValueExpression* IndexAccessor(const ast::IndexAccessorExpression*);
+    sem::ValueExpression* Binary(const ast::BinaryExpression*);
+    sem::ValueExpression* Bitcast(const ast::BitcastExpression*);
     sem::Call* Call(const ast::CallExpression*);
     sem::Function* Function(const ast::Function*);
     template <size_t N>
     sem::Call* FunctionCall(const ast::CallExpression*,
                             sem::Function* target,
-                            utils::Vector<const sem::Expression*, N>& args,
+                            utils::Vector<const sem::ValueExpression*, N>& args,
                             sem::Behaviors arg_behaviors);
-    sem::Expression* Identifier(const ast::IdentifierExpression*);
+    sem::ValueExpression* Identifier(const ast::IdentifierExpression*);
     template <size_t N>
     sem::Call* BuiltinCall(const ast::CallExpression*,
                            sem::BuiltinType,
-                           utils::Vector<const sem::Expression*, N>& args);
-    sem::Expression* Literal(const ast::LiteralExpression*);
-    sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
-    sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
+                           utils::Vector<const sem::ValueExpression*, N>& args);
+    sem::ValueExpression* Literal(const ast::LiteralExpression*);
+    sem::ValueExpression* MemberAccessor(const ast::MemberAccessorExpression*);
+    sem::ValueExpression* UnaryOp(const ast::UnaryOpExpression*);
 
     /// Register a memory store to an expression, to track accesses to root identifiers in order to
     /// perform alias analysis.
-    void RegisterStore(const sem::Expression* expr);
+    void RegisterStore(const sem::ValueExpression* expr);
 
     /// Perform pointer alias analysis for `call`.
     /// @returns true is the call arguments are free from aliasing issues, false otherwise.
@@ -166,7 +166,7 @@
 
     /// If `expr` is of a reference type, then Load will create and return a sem::Load node wrapping
     /// `expr`. If `expr` is not of a reference type, then Load will just return `expr`.
-    const sem::Expression* Load(const sem::Expression* expr);
+    const sem::ValueExpression* Load(const sem::ValueExpression* expr);
 
     /// If `expr` is not of an abstract-numeric type, then Materialize() will just return `expr`.
     /// * Materialize will create and return a sem::Materialize node wrapping `expr`.
@@ -181,8 +181,8 @@
     ///   materialized type.
     /// If `expr` is not of an abstract-numeric type, then Materialize() will just return `expr`.
     /// If `expr` is nullptr, then Materialize() will also return nullptr.
-    const sem::Expression* Materialize(const sem::Expression* expr,
-                                       const type::Type* target_type = nullptr);
+    const sem::ValueExpression* Materialize(const sem::ValueExpression* expr,
+                                            const type::Type* target_type = nullptr);
 
     /// For each argument in `args`:
     /// * Calls Materialize() passing the argument and the corresponding parameter type.
@@ -190,7 +190,7 @@
     ///   reference type.
     /// @returns true on success, false on failure.
     template <size_t N>
-    bool MaybeMaterializeAndLoadArguments(utils::Vector<const sem::Expression*, N>& args,
+    bool MaybeMaterializeAndLoadArguments(utils::Vector<const sem::ValueExpression*, N>& args,
                                           const sem::CallTarget* target);
 
     /// @returns true if an argument of an abstract numeric type, passed to a parameter of type
@@ -206,7 +206,7 @@
     /// @returns the vector of constants, `utils::Failure` on failure.
     template <size_t N>
     utils::Result<utils::Vector<const constant::Value*, N>> ConvertArguments(
-        const utils::Vector<const sem::Expression*, N>& args,
+        const utils::Vector<const sem::ValueExpression*, N>& args,
         const sem::CallTarget* target);
 
     /// @param ty the type that may hold abstract numeric types
@@ -247,9 +247,9 @@
     // CollectTextureSamplerPairs() collects all the texture/sampler pairs from the target function
     // / builtin, and records these on the current function by calling AddTextureSamplerPair().
     void CollectTextureSamplerPairs(sem::Function* func,
-                                    utils::VectorRef<const sem::Expression*> args) const;
+                                    utils::VectorRef<const sem::ValueExpression*> args) const;
     void CollectTextureSamplerPairs(const sem::Builtin* builtin,
-                                    utils::VectorRef<const sem::Expression*> args) const;
+                                    utils::VectorRef<const sem::ValueExpression*> args) const;
 
     /// Resolves the WorkgroupSize for the given function, assigning it to
     /// current_function_
@@ -459,9 +459,9 @@
     /// of determining if any two arguments alias at any callsite.
     struct AliasAnalysisInfo {
         /// The set of module-scope variables that are written to, and where that write occurs.
-        std::unordered_map<const sem::Variable*, const sem::Expression*> module_scope_writes;
+        std::unordered_map<const sem::Variable*, const sem::ValueExpression*> module_scope_writes;
         /// The set of module-scope variables that are read from, and where that read occurs.
-        std::unordered_map<const sem::Variable*, const sem::Expression*> module_scope_reads;
+        std::unordered_map<const sem::Variable*, const sem::ValueExpression*> module_scope_reads;
         /// The set of function parameters that are written to.
         std::unordered_set<const sem::Variable*> parameter_writes;
         /// The set of function parameters that are read from.
diff --git a/src/tint/resolver/resolver_behavior_test.cc b/src/tint/resolver/resolver_behavior_test.cc
index 6f405a3..960144e 100644
--- a/src/tint/resolver/resolver_behavior_test.cc
+++ b/src/tint/resolver/resolver_behavior_test.cc
@@ -16,10 +16,10 @@
 
 #include "gtest/gtest.h"
 #include "src/tint/resolver/resolver_test_helper.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/for_loop_statement.h"
 #include "src/tint/sem/if_statement.h"
 #include "src/tint/sem/switch_statement.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/sem/while_statement.h"
 
 using namespace tint::number_suffixes;  // NOLINT
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
index d5a6698..f04b697 100644
--- a/src/tint/resolver/resolver_test_helper.h
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -26,8 +26,8 @@
 #include "gtest/gtest.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/resolver/resolver.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/sem/variable.h"
 #include "src/tint/traits.h"
 #include "src/tint/type/abstract_float.h"
diff --git a/src/tint/resolver/sem_helper.cc b/src/tint/resolver/sem_helper.cc
index db253a7..327b1cf 100644
--- a/src/tint/resolver/sem_helper.cc
+++ b/src/tint/resolver/sem_helper.cc
@@ -14,7 +14,7 @@
 
 #include "src/tint/resolver/sem_helper.h"
 
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 
 namespace tint::resolver {
 
diff --git a/src/tint/resolver/side_effects_test.cc b/src/tint/resolver/side_effects_test.cc
index 081d97c..b0796d5 100644
--- a/src/tint/resolver/side_effects_test.cc
+++ b/src/tint/resolver/side_effects_test.cc
@@ -16,9 +16,9 @@
 
 #include "gtest/gtest.h"
 #include "src/tint/resolver/resolver_test_helper.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/index_accessor_expression.h"
 #include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/type/texture_dimension.h"
 #include "src/tint/utils/vector.h"
 
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 041c528..d6a2950 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -384,7 +384,7 @@
 bool Validator::VariableInitializer(const ast::Variable* v,
                                     type::AddressSpace address_space,
                                     const type::Type* storage_ty,
-                                    const sem::Expression* initializer) const {
+                                    const sem::ValueExpression* initializer) const {
     auto* initializer_ty = initializer->Type();
     auto* value_type = initializer_ty->UnwrapRef();  // Implicit load of RHS
 
@@ -1343,7 +1343,7 @@
     return true;
 }
 
-bool Validator::EvaluationStage(const sem::Expression* expr,
+bool Validator::EvaluationStage(const sem::ValueExpression* expr,
                                 sem::EvaluationStage latest_stage,
                                 std::string_view constraint) const {
     if (expr->Stage() == sem::EvaluationStage::kNotEvaluated) {
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 6ff3473..55625d3 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -245,7 +245,7 @@
     /// @param latest_stage the latest evaluation stage that the expression can be evaluated
     /// @param constraint the 'thing' that is imposing the contraint. e.g. "var declaration"
     /// @returns true if @p expr is evaluated in or before @p latest_stage, false otherwise
-    bool EvaluationStage(const sem::Expression* expr,
+    bool EvaluationStage(const sem::ValueExpression* expr,
                          sem::EvaluationStage latest_stage,
                          std::string_view constraint) const;
 
@@ -436,7 +436,7 @@
     bool VariableInitializer(const ast::Variable* v,
                              type::AddressSpace address_space,
                              const type::Type* storage_type,
-                             const sem::Expression* initializer) const;
+                             const sem::ValueExpression* initializer) const;
 
     /// Validates a vector
     /// @param ty the vector to validate
diff --git a/src/tint/sem/array_count.cc b/src/tint/sem/array_count.cc
index 4fee970..4fa6c08 100644
--- a/src/tint/sem/array_count.cc
+++ b/src/tint/sem/array_count.cc
@@ -40,7 +40,7 @@
     return nullptr;
 }
 
-UnnamedOverrideArrayCount::UnnamedOverrideArrayCount(const Expression* e)
+UnnamedOverrideArrayCount::UnnamedOverrideArrayCount(const ValueExpression* e)
     : Base(static_cast<size_t>(TypeInfo::Of<UnnamedOverrideArrayCount>().full_hashcode)), expr(e) {}
 UnnamedOverrideArrayCount::~UnnamedOverrideArrayCount() = default;
 
diff --git a/src/tint/sem/array_count.h b/src/tint/sem/array_count.h
index 2d3c27f..9147160 100644
--- a/src/tint/sem/array_count.h
+++ b/src/tint/sem/array_count.h
@@ -17,7 +17,7 @@
 
 #include <string>
 
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/sem/variable.h"
 #include "src/tint/type/array_count.h"
 
@@ -63,7 +63,7 @@
   public:
     /// Constructor
     /// @param e the override expression
-    explicit UnnamedOverrideArrayCount(const Expression* e);
+    explicit UnnamedOverrideArrayCount(const ValueExpression* e);
     ~UnnamedOverrideArrayCount() override;
 
     /// @param other the other node
@@ -90,7 +90,7 @@
     /// ```
     // The array count for `a` and `b` have equivalent AST expressions, but the types for `a` and
     // `b` must not compare equal.
-    const Expression* expr;
+    const ValueExpression* expr;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/break_if_statement.h b/src/tint/sem/break_if_statement.h
index bb1e3e5..c5b62de 100644
--- a/src/tint/sem/break_if_statement.h
+++ b/src/tint/sem/break_if_statement.h
@@ -22,7 +22,7 @@
 class BreakIfStatement;
 }  // namespace tint::ast
 namespace tint::sem {
-class Expression;
+class ValueExpression;
 }  // namespace tint::sem
 
 namespace tint::sem {
@@ -45,14 +45,14 @@
     const ast::BreakIfStatement* Declaration() const;
 
     /// @returns the break-if-statement condition expression
-    const Expression* Condition() const { return condition_; }
+    const ValueExpression* Condition() const { return condition_; }
 
     /// Sets the break-if-statement condition expression
     /// @param condition the break-if condition expression
-    void SetCondition(const Expression* condition) { condition_ = condition; }
+    void SetCondition(const ValueExpression* condition) { condition_ = condition; }
 
   private:
-    const Expression* condition_ = nullptr;
+    const ValueExpression* condition_ = nullptr;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/call.cc b/src/tint/sem/call.cc
index 7a28e9f..69485f5 100644
--- a/src/tint/sem/call.cc
+++ b/src/tint/sem/call.cc
@@ -24,7 +24,7 @@
 Call::Call(const ast::CallExpression* declaration,
            const CallTarget* target,
            EvaluationStage stage,
-           utils::VectorRef<const sem::Expression*> arguments,
+           utils::VectorRef<const sem::ValueExpression*> arguments,
            const Statement* statement,
            const constant::Value* constant,
            bool has_side_effects)
diff --git a/src/tint/sem/call.h b/src/tint/sem/call.h
index 0873759..3079eb1 100644
--- a/src/tint/sem/call.h
+++ b/src/tint/sem/call.h
@@ -19,14 +19,14 @@
 
 #include "src/tint/ast/call_expression.h"
 #include "src/tint/sem/builtin.h"
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/utils/vector.h"
 
 namespace tint::sem {
 
 /// Call is the base class for semantic nodes that hold semantic information for
 /// ast::CallExpression nodes.
-class Call final : public Castable<Call, Expression> {
+class Call final : public Castable<Call, ValueExpression> {
   public:
     /// Constructor
     /// @param declaration the AST node
@@ -39,7 +39,7 @@
     Call(const ast::CallExpression* declaration,
          const CallTarget* target,
          EvaluationStage stage,
-         utils::VectorRef<const sem::Expression*> arguments,
+         utils::VectorRef<const sem::ValueExpression*> arguments,
          const Statement* statement,
          const constant::Value* constant,
          bool has_side_effects);
@@ -60,7 +60,7 @@
 
   private:
     CallTarget const* const target_;
-    utils::Vector<const sem::Expression*, 8> arguments_;
+    utils::Vector<const sem::ValueExpression*, 8> arguments_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/for_loop_statement.h b/src/tint/sem/for_loop_statement.h
index 9f7d62a..7e22912 100644
--- a/src/tint/sem/for_loop_statement.h
+++ b/src/tint/sem/for_loop_statement.h
@@ -22,7 +22,7 @@
 class ForLoopStatement;
 }  // namespace tint::ast
 namespace tint::sem {
-class Expression;
+class ValueExpression;
 }  // namespace tint::sem
 
 namespace tint::sem {
@@ -45,14 +45,14 @@
     const ast::ForLoopStatement* Declaration() const;
 
     /// @returns the for-loop condition expression
-    const Expression* Condition() const { return condition_; }
+    const ValueExpression* Condition() const { return condition_; }
 
     /// Sets the for-loop condition expression
     /// @param condition the for-loop condition expression
-    void SetCondition(const Expression* condition) { condition_ = condition; }
+    void SetCondition(const ValueExpression* condition) { condition_ = condition; }
 
   private:
-    const Expression* condition_ = nullptr;
+    const ValueExpression* condition_ = nullptr;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/if_statement.h b/src/tint/sem/if_statement.h
index e9ecda0..5b27fba 100644
--- a/src/tint/sem/if_statement.h
+++ b/src/tint/sem/if_statement.h
@@ -22,7 +22,7 @@
 class IfStatement;
 }  // namespace tint::ast
 namespace tint::sem {
-class Expression;
+class ValueExpression;
 }  // namespace tint::sem
 
 namespace tint::sem {
@@ -45,14 +45,14 @@
     const ast::IfStatement* Declaration() const;
 
     /// @returns the if-statement condition expression
-    const Expression* Condition() const { return condition_; }
+    const ValueExpression* Condition() const { return condition_; }
 
     /// Sets the if-statement condition expression
     /// @param condition the if condition expression
-    void SetCondition(const Expression* condition) { condition_ = condition; }
+    void SetCondition(const ValueExpression* condition) { condition_ = condition; }
 
   private:
-    const Expression* condition_ = nullptr;
+    const ValueExpression* condition_ = nullptr;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/index_accessor_expression.cc b/src/tint/sem/index_accessor_expression.cc
index ed5a468..f407606 100644
--- a/src/tint/sem/index_accessor_expression.cc
+++ b/src/tint/sem/index_accessor_expression.cc
@@ -25,8 +25,8 @@
 IndexAccessorExpression::IndexAccessorExpression(const ast::IndexAccessorExpression* declaration,
                                                  const type::Type* type,
                                                  EvaluationStage stage,
-                                                 const Expression* object,
-                                                 const Expression* index,
+                                                 const ValueExpression* object,
+                                                 const ValueExpression* index,
                                                  const Statement* statement,
                                                  const constant::Value* constant,
                                                  bool has_side_effects,
diff --git a/src/tint/sem/index_accessor_expression.h b/src/tint/sem/index_accessor_expression.h
index 8327b79..2375a9d 100644
--- a/src/tint/sem/index_accessor_expression.h
+++ b/src/tint/sem/index_accessor_expression.h
@@ -17,7 +17,7 @@
 
 #include <vector>
 
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 
 // Forward declarations
 namespace tint::ast {
@@ -27,7 +27,7 @@
 namespace tint::sem {
 
 /// IndexAccessorExpression holds the semantic information for a ast::IndexAccessorExpression node.
-class IndexAccessorExpression final : public Castable<IndexAccessorExpression, Expression> {
+class IndexAccessorExpression final : public Castable<IndexAccessorExpression, ValueExpression> {
   public:
     /// Constructor
     /// @param declaration the AST node
@@ -42,8 +42,8 @@
     IndexAccessorExpression(const ast::IndexAccessorExpression* declaration,
                             const type::Type* type,
                             EvaluationStage stage,
-                            const Expression* object,
-                            const Expression* index,
+                            const ValueExpression* object,
+                            const ValueExpression* index,
                             const Statement* statement,
                             const constant::Value* constant,
                             bool has_side_effects,
@@ -53,14 +53,14 @@
     ~IndexAccessorExpression() override;
 
     /// @returns the object expression that is being indexed
-    Expression const* Object() const { return object_; }
+    ValueExpression const* Object() const { return object_; }
 
     /// @returns the index expression
-    Expression const* Index() const { return index_; }
+    ValueExpression const* Index() const { return index_; }
 
   private:
-    Expression const* const object_;
-    Expression const* const index_;
+    ValueExpression const* const object_;
+    ValueExpression const* const index_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/info.cc b/src/tint/sem/info.cc
index a3f5b48..a6a563e 100644
--- a/src/tint/sem/info.cc
+++ b/src/tint/sem/info.cc
@@ -14,10 +14,10 @@
 
 #include "src/tint/sem/info.h"
 
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/function.h"
 #include "src/tint/sem/module.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/sem/value_expression.h"
 
 namespace tint::sem {
 
@@ -75,7 +75,7 @@
     TINT_ASSERT(Resolver, sem != nullptr);
     auto severity = Switch(
         sem,  //
-        [&](const sem::Expression* expr) { return check_stmt(expr->Stmt()); },
+        [&](const sem::ValueExpression* expr) { return check_stmt(expr->Stmt()); },
         [&](const sem::Statement* stmt) { return check_stmt(stmt); },
         [&](const sem::Function* func) { return check_func(func); },
         [&](Default) {
diff --git a/src/tint/sem/load.cc b/src/tint/sem/load.cc
index 58f7068..acabdc7 100644
--- a/src/tint/sem/load.cc
+++ b/src/tint/sem/load.cc
@@ -20,7 +20,7 @@
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Load);
 
 namespace tint::sem {
-Load::Load(const Expression* ref, const Statement* statement)
+Load::Load(const ValueExpression* ref, const Statement* statement)
     : Base(/* declaration */ ref->Declaration(),
            /* type */ ref->Type()->UnwrapRef(),
            /* stage */ EvaluationStage::kRuntime,  // Loads can only be runtime
diff --git a/src/tint/sem/load.h b/src/tint/sem/load.h
index 1a63266..e02e050 100644
--- a/src/tint/sem/load.h
+++ b/src/tint/sem/load.h
@@ -15,7 +15,7 @@
 #ifndef SRC_TINT_SEM_LOAD_H_
 #define SRC_TINT_SEM_LOAD_H_
 
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/type/reference.h"
 
 namespace tint::sem {
@@ -23,18 +23,18 @@
 /// Load is a semantic expression which represents the load of a reference to a non-reference value.
 /// Loads from reference types are implicit in WGSL, so the Load semantic node shares the same AST
 /// node as the inner semantic node.
-class Load final : public Castable<Load, Expression> {
+class Load final : public Castable<Load, ValueExpression> {
   public:
     /// Constructor
     /// @param reference the reference expression being loaded
     /// @param statement the statement that owns this expression
-    Load(const Expression* reference, const Statement* statement);
+    Load(const ValueExpression* reference, const Statement* statement);
 
     /// Destructor
     ~Load() override;
 
     /// @return the reference being loaded
-    const Expression* Reference() const { return reference_; }
+    const ValueExpression* Reference() const { return reference_; }
 
     /// @returns the type of the loaded reference.
     const type::Reference* ReferenceType() const {
@@ -42,7 +42,7 @@
     }
 
   private:
-    Expression const* const reference_;
+    ValueExpression const* const reference_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/materialize.cc b/src/tint/sem/materialize.cc
index 300deb2..1c27dba 100644
--- a/src/tint/sem/materialize.cc
+++ b/src/tint/sem/materialize.cc
@@ -17,7 +17,7 @@
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Materialize);
 
 namespace tint::sem {
-Materialize::Materialize(const Expression* expr,
+Materialize::Materialize(const ValueExpression* expr,
                          const Statement* statement,
                          const type::Type* type,
                          const constant::Value* constant)
diff --git a/src/tint/sem/materialize.h b/src/tint/sem/materialize.h
index 532b3b2..4ce1190 100644
--- a/src/tint/sem/materialize.h
+++ b/src/tint/sem/materialize.h
@@ -15,7 +15,7 @@
 #ifndef SRC_TINT_SEM_MATERIALIZE_H_
 #define SRC_TINT_SEM_MATERIALIZE_H_
 
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 
 namespace tint::sem {
 
@@ -25,14 +25,14 @@
 /// the same AST node as the inner semantic node.
 /// Abstract numerics types may only be used by compile-time expressions, so a Materialize semantic
 /// node must have a valid Constant value.
-class Materialize final : public Castable<Materialize, Expression> {
+class Materialize final : public Castable<Materialize, ValueExpression> {
   public:
     /// Constructor
     /// @param expr the inner expression, being materialized
     /// @param statement the statement that owns this expression
     /// @param type concrete type to materialize to
     /// @param constant the constant value of this expression or nullptr
-    Materialize(const Expression* expr,
+    Materialize(const ValueExpression* expr,
                 const Statement* statement,
                 const type::Type* type,
                 const constant::Value* constant);
@@ -41,10 +41,10 @@
     ~Materialize() override;
 
     /// @return the expression being materialized
-    const Expression* Expr() const { return expr_; }
+    const ValueExpression* Expr() const { return expr_; }
 
   private:
-    Expression const* const expr_;
+    ValueExpression const* const expr_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/member_accessor_expression.cc b/src/tint/sem/member_accessor_expression.cc
index 06cfb76..9ad15f0 100644
--- a/src/tint/sem/member_accessor_expression.cc
+++ b/src/tint/sem/member_accessor_expression.cc
@@ -28,7 +28,7 @@
                                                    EvaluationStage stage,
                                                    const Statement* statement,
                                                    const constant::Value* constant,
-                                                   const Expression* object,
+                                                   const ValueExpression* object,
                                                    bool has_side_effects,
                                                    const Variable* root_ident /* = nullptr */)
     : Base(declaration, type, stage, statement, constant, has_side_effects, root_ident),
@@ -40,7 +40,7 @@
                                        const type::Type* type,
                                        const Statement* statement,
                                        const constant::Value* constant,
-                                       const Expression* object,
+                                       const ValueExpression* object,
                                        const StructMember* member,
                                        bool has_side_effects,
                                        const Variable* root_ident /* = nullptr */)
@@ -60,7 +60,7 @@
                  const type::Type* type,
                  const Statement* statement,
                  const constant::Value* constant,
-                 const Expression* object,
+                 const ValueExpression* object,
                  utils::VectorRef<uint32_t> indices,
                  bool has_side_effects,
                  const Variable* root_ident /* = nullptr */)
diff --git a/src/tint/sem/member_accessor_expression.h b/src/tint/sem/member_accessor_expression.h
index 1951afc..7ed0a41 100644
--- a/src/tint/sem/member_accessor_expression.h
+++ b/src/tint/sem/member_accessor_expression.h
@@ -15,7 +15,7 @@
 #ifndef SRC_TINT_SEM_MEMBER_ACCESSOR_EXPRESSION_H_
 #define SRC_TINT_SEM_MEMBER_ACCESSOR_EXPRESSION_H_
 
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/utils/vector.h"
 
 // Forward declarations
@@ -30,13 +30,13 @@
 
 /// MemberAccessorExpression holds the semantic information for a
 /// ast::MemberAccessorExpression node.
-class MemberAccessorExpression : public Castable<MemberAccessorExpression, Expression> {
+class MemberAccessorExpression : public Castable<MemberAccessorExpression, ValueExpression> {
   public:
     /// Destructor
     ~MemberAccessorExpression() override;
 
     /// @returns the object that holds the member being accessed
-    const Expression* Object() const { return object_; }
+    const ValueExpression* Object() const { return object_; }
 
   protected:
     /// Constructor
@@ -53,12 +53,12 @@
                              EvaluationStage stage,
                              const Statement* statement,
                              const constant::Value* constant,
-                             const Expression* object,
+                             const ValueExpression* object,
                              bool has_side_effects,
                              const Variable* root_ident = nullptr);
 
   private:
-    Expression const* const object_;
+    ValueExpression const* const object_;
 };
 
 /// StructMemberAccess holds the semantic information for a
@@ -79,7 +79,7 @@
                        const type::Type* type,
                        const Statement* statement,
                        const constant::Value* constant,
-                       const Expression* object,
+                       const ValueExpression* object,
                        const StructMember* member,
                        bool has_side_effects,
                        const Variable* root_ident = nullptr);
@@ -111,7 +111,7 @@
             const type::Type* type,
             const Statement* statement,
             const constant::Value* constant,
-            const Expression* object,
+            const ValueExpression* object,
             utils::VectorRef<uint32_t> indices,
             bool has_side_effects,
             const Variable* root_ident = nullptr);
diff --git a/src/tint/sem/switch_statement.h b/src/tint/sem/switch_statement.h
index 2476906..503d0a5 100644
--- a/src/tint/sem/switch_statement.h
+++ b/src/tint/sem/switch_statement.h
@@ -31,7 +31,7 @@
 namespace tint::sem {
 class CaseStatement;
 class CaseSelector;
-class Expression;
+class ValueExpression;
 }  // namespace tint::sem
 
 namespace tint::sem {
diff --git a/src/tint/sem/type_mappings.h b/src/tint/sem/type_mappings.h
index f8aca4d..40e287c 100644
--- a/src/tint/sem/type_mappings.h
+++ b/src/tint/sem/type_mappings.h
@@ -39,16 +39,16 @@
 class WhileStatement;
 }  // namespace tint::ast
 namespace tint::sem {
-class Expression;
 class ForLoopStatement;
 class Function;
+class GlobalVariable;
 class IfStatement;
 class Node;
-class GlobalVariable;
 class Statement;
 class Struct;
 class StructMember;
 class SwitchStatement;
+class ValueExpression;
 class Variable;
 class WhileStatement;
 }  // namespace tint::sem
@@ -66,7 +66,6 @@
 struct TypeMappings {
     //! @cond Doxygen_Suppress
     type::Array* operator()(ast::Array*);
-    Expression* operator()(ast::Expression*);
     ForLoopStatement* operator()(ast::ForLoopStatement*);
     Function* operator()(ast::Function*);
     IfStatement* operator()(ast::IfStatement*);
@@ -78,6 +77,7 @@
     SwitchStatement* operator()(ast::SwitchStatement*);
     type::Type* operator()(ast::Type*);
     type::Type* operator()(ast::TypeDecl*);
+    ValueExpression* operator()(ast::Expression*);
     Variable* operator()(ast::Variable*);
     WhileStatement* operator()(ast::WhileStatement*);
     //! @endcond
diff --git a/src/tint/sem/expression.cc b/src/tint/sem/value_expression.cc
similarity index 68%
rename from src/tint/sem/expression.cc
rename to src/tint/sem/value_expression.cc
index ebea34c..de3c4cf 100644
--- a/src/tint/sem/expression.cc
+++ b/src/tint/sem/value_expression.cc
@@ -12,24 +12,24 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 
 #include <utility>
 
 #include "src/tint/sem/load.h"
 #include "src/tint/sem/materialize.h"
 
-TINT_INSTANTIATE_TYPEINFO(tint::sem::Expression);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::ValueExpression);
 
 namespace tint::sem {
 
-Expression::Expression(const ast::Expression* declaration,
-                       const type::Type* type,
-                       EvaluationStage stage,
-                       const Statement* statement,
-                       const constant::Value* constant,
-                       bool has_side_effects,
-                       const Variable* root_ident /* = nullptr */)
+ValueExpression::ValueExpression(const ast::Expression* declaration,
+                                 const type::Type* type,
+                                 EvaluationStage stage,
+                                 const Statement* statement,
+                                 const constant::Value* constant,
+                                 bool has_side_effects,
+                                 const Variable* root_ident /* = nullptr */)
     : declaration_(declaration),
       root_identifier_(root_ident),
       type_(type),
@@ -44,23 +44,23 @@
     }
 }
 
-Expression::~Expression() = default;
+ValueExpression::~ValueExpression() = default;
 
-const Expression* Expression::UnwrapMaterialize() const {
+const ValueExpression* ValueExpression::UnwrapMaterialize() const {
     if (auto* m = As<Materialize>()) {
         return m->Expr();
     }
     return this;
 }
 
-const Expression* Expression::UnwrapLoad() const {
+const ValueExpression* ValueExpression::UnwrapLoad() const {
     if (auto* l = As<Load>()) {
         return l->Reference();
     }
     return this;
 }
 
-const Expression* Expression::Unwrap() const {
+const ValueExpression* ValueExpression::Unwrap() const {
     return Switch(
         this,  // note: An expression can only be wrapped by a Load or Materialize, not both.
         [&](const Load* load) { return load->Reference(); },
diff --git a/src/tint/sem/expression.h b/src/tint/sem/value_expression.h
similarity index 81%
rename from src/tint/sem/expression.h
rename to src/tint/sem/value_expression.h
index 575f3f5..9840c11 100644
--- a/src/tint/sem/expression.h
+++ b/src/tint/sem/value_expression.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SRC_TINT_SEM_EXPRESSION_H_
-#define SRC_TINT_SEM_EXPRESSION_H_
+#ifndef SRC_TINT_SEM_VALUE_EXPRESSION_H_
+#define SRC_TINT_SEM_VALUE_EXPRESSION_H_
 
 #include "src/tint/ast/expression.h"
 #include "src/tint/constant/value.h"
@@ -29,8 +29,8 @@
 
 namespace tint::sem {
 
-/// Expression holds the semantic information for expression nodes.
-class Expression : public Castable<Expression, Node> {
+/// ValueExpression holds the semantic information for expression nodes.
+class ValueExpression : public Castable<ValueExpression, Node> {
   public:
     /// Constructor
     /// @param declaration the AST node
@@ -40,16 +40,16 @@
     /// @param constant the constant value of the expression. May be null
     /// @param has_side_effects true if this expression may have side-effects
     /// @param root_ident the (optional) root identifier for this expression
-    Expression(const ast::Expression* declaration,
-               const type::Type* type,
-               EvaluationStage stage,
-               const Statement* statement,
-               const constant::Value* constant,
-               bool has_side_effects,
-               const Variable* root_ident = nullptr);
+    ValueExpression(const ast::Expression* declaration,
+                    const type::Type* type,
+                    EvaluationStage stage,
+                    const Statement* statement,
+                    const constant::Value* constant,
+                    bool has_side_effects,
+                    const Variable* root_ident = nullptr);
 
     /// Destructor
-    ~Expression() override;
+    ~ValueExpression() override;
 
     /// @returns the AST node
     const ast::Expression* Declaration() const { return declaration_; }
@@ -83,13 +83,13 @@
     bool HasSideEffects() const { return has_side_effects_; }
 
     /// @return the inner expression node if this is a Materialize, otherwise this.
-    const Expression* UnwrapMaterialize() const;
+    const ValueExpression* UnwrapMaterialize() const;
 
     /// @return the inner reference expression if this is a Load, otherwise this.
-    const Expression* UnwrapLoad() const;
+    const ValueExpression* UnwrapLoad() const;
 
     /// @return the inner expression node if this is a Materialize or Load, otherwise this.
-    const Expression* Unwrap() const;
+    const ValueExpression* Unwrap() const;
 
   protected:
     /// The AST expression node for this semantic expression
@@ -108,4 +108,4 @@
 
 }  // namespace tint::sem
 
-#endif  // SRC_TINT_SEM_EXPRESSION_H_
+#endif  // SRC_TINT_SEM_VALUE_EXPRESSION_H_
diff --git a/src/tint/sem/expression_test.cc b/src/tint/sem/value_expression_test.cc
similarity index 78%
rename from src/tint/sem/expression_test.cc
rename to src/tint/sem/value_expression_test.cc
index 6c49210..1758894 100644
--- a/src/tint/sem/expression_test.cc
+++ b/src/tint/sem/value_expression_test.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 
 #include "src/tint/sem/test_helper.h"
 
@@ -42,14 +42,14 @@
     const type::Type* type;
 };
 
-using ExpressionTest = TestHelper;
+using ValueExpressionTest = TestHelper;
 
-TEST_F(ExpressionTest, UnwrapMaterialize) {
+TEST_F(ValueExpressionTest, UnwrapMaterialize) {
     MockConstant c(create<type::I32>());
-    auto* a = create<Expression>(/* declaration */ nullptr, create<type::I32>(),
-                                 sem::EvaluationStage::kRuntime, /* statement */ nullptr,
-                                 /* constant_value */ nullptr,
-                                 /* has_side_effects */ false, /* root_ident */ nullptr);
+    auto* a = create<ValueExpression>(/* declaration */ nullptr, create<type::I32>(),
+                                      sem::EvaluationStage::kRuntime, /* statement */ nullptr,
+                                      /* constant_value */ nullptr,
+                                      /* has_side_effects */ false, /* root_ident */ nullptr);
     auto* b = create<Materialize>(a, /* statement */ nullptr, c.Type(), &c);
 
     EXPECT_EQ(a, a->UnwrapMaterialize());
diff --git a/src/tint/sem/variable.h b/src/tint/sem/variable.h
index cecad1e..afaa0d1 100644
--- a/src/tint/sem/variable.h
+++ b/src/tint/sem/variable.h
@@ -23,8 +23,8 @@
 
 #include "src/tint/ast/parameter.h"
 #include "src/tint/sem/binding_point.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/parameter_usage.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/type/access.h"
 #include "src/tint/type/address_space.h"
 #include "src/tint/type/type.h"
@@ -84,11 +84,11 @@
 
     /// @returns the variable initializer expression, or nullptr if the variable
     /// does not have one.
-    const Expression* Initializer() const { return initializer_; }
+    const ValueExpression* Initializer() const { return initializer_; }
 
     /// Sets the variable initializer expression.
     /// @param initializer the initializer expression to assign to this variable.
-    void SetInitializer(const Expression* initializer) { initializer_ = initializer; }
+    void SetInitializer(const ValueExpression* initializer) { initializer_ = initializer; }
 
     /// @returns the expressions that use the variable
     const std::vector<const VariableUser*>& Users() const { return users_; }
@@ -103,7 +103,7 @@
     const type::AddressSpace address_space_;
     const type::Access access_;
     const constant::Value* constant_value_;
-    const Expression* initializer_ = nullptr;
+    const ValueExpression* initializer_ = nullptr;
     std::vector<const VariableUser*> users_;
 };
 
@@ -255,7 +255,7 @@
 
 /// VariableUser holds the semantic information for an identifier expression
 /// node that resolves to a variable.
-class VariableUser final : public Castable<VariableUser, Expression> {
+class VariableUser final : public Castable<VariableUser, ValueExpression> {
   public:
     /// Constructor
     /// @param declaration the AST identifier node
diff --git a/src/tint/sem/while_statement.h b/src/tint/sem/while_statement.h
index 50f1831..fa136bd 100644
--- a/src/tint/sem/while_statement.h
+++ b/src/tint/sem/while_statement.h
@@ -22,7 +22,7 @@
 class WhileStatement;
 }  // namespace tint::ast
 namespace tint::sem {
-class Expression;
+class ValueExpression;
 }  // namespace tint::sem
 
 namespace tint::sem {
@@ -45,14 +45,14 @@
     const ast::WhileStatement* Declaration() const;
 
     /// @returns the whilecondition expression
-    const Expression* Condition() const { return condition_; }
+    const ValueExpression* Condition() const { return condition_; }
 
     /// Sets the while condition expression
     /// @param condition the while condition expression
-    void SetCondition(const Expression* condition) { condition_ = condition; }
+    void SetCondition(const ValueExpression* condition) { condition_ = condition; }
 
   private:
-    const Expression* condition_ = nullptr;
+    const ValueExpression* condition_ = nullptr;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/tint.natvis b/src/tint/tint.natvis
index 71fbd1d..f194135 100644
--- a/src/tint/tint.natvis
+++ b/src/tint/tint.natvis
@@ -260,7 +260,7 @@
 		<DisplayString>Type={*Type()} Value={Value()}</DisplayString>
 	</Type>
 
-	<Type Name="tint::sem::Expression">
+	<Type Name="tint::sem::ValueExpression">
 		<DisplayString>Decl={*declaration_}</DisplayString>
 	</Type>
 
diff --git a/src/tint/transform/combine_samplers.cc b/src/tint/transform/combine_samplers.cc
index 65bf161..7a39d6e 100644
--- a/src/tint/transform/combine_samplers.cc
+++ b/src/tint/transform/combine_samplers.cc
@@ -240,14 +240,14 @@
                     if (texture_index == -1) {
                         return nullptr;
                     }
-                    const sem::Expression* texture =
+                    const sem::ValueExpression* texture =
                         call->Arguments()[static_cast<size_t>(texture_index)];
                     // We don't want to combine storage textures with anything, since
                     // they never have associated samplers in GLSL.
                     if (texture->Type()->UnwrapRef()->Is<type::StorageTexture>()) {
                         return nullptr;
                     }
-                    const sem::Expression* sampler =
+                    const sem::ValueExpression* sampler =
                         sampler_index != -1 ? call->Arguments()[static_cast<size_t>(sampler_index)]
                                             : nullptr;
                     auto* texture_var = texture->UnwrapLoad()->As<sem::VariableUser>()->Variable();
@@ -296,13 +296,14 @@
                         const sem::Variable* texture_var = pair.first;
                         const sem::Variable* sampler_var = pair.second;
                         if (auto* param = texture_var->As<sem::Parameter>()) {
-                            const sem::Expression* texture = call->Arguments()[param->Index()];
+                            const sem::ValueExpression* texture = call->Arguments()[param->Index()];
                             texture_var =
                                 texture->UnwrapLoad()->As<sem::VariableUser>()->Variable();
                         }
                         if (sampler_var) {
                             if (auto* param = sampler_var->As<sem::Parameter>()) {
-                                const sem::Expression* sampler = call->Arguments()[param->Index()];
+                                const sem::ValueExpression* sampler =
+                                    call->Arguments()[param->Index()];
                                 sampler_var =
                                     sampler->UnwrapLoad()->As<sem::VariableUser>()->Variable();
                             }
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index abb78b2..0d5f709 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -305,10 +305,10 @@
 
 /// BufferAccess describes a single storage or uniform buffer access
 struct BufferAccess {
-    sem::Expression const* var = nullptr;  // Storage buffer variable
-    Offset const* offset = nullptr;        // The byte offset on var
-    type::Type const* type = nullptr;      // The type of the access
-    operator bool() const { return var; }  // Returns true if valid
+    sem::ValueExpression const* var = nullptr;  // Storage buffer variable
+    Offset const* offset = nullptr;             // The byte offset on var
+    type::Type const* type = nullptr;           // The type of the access
+    operator bool() const { return var; }       // Returns true if valid
 };
 
 /// Store describes a single storage or uniform buffer write
diff --git a/src/tint/transform/decompose_strided_array.cc b/src/tint/transform/decompose_strided_array.cc
index 129e417..c441df8 100644
--- a/src/tint/transform/decompose_strided_array.cc
+++ b/src/tint/transform/decompose_strided_array.cc
@@ -20,9 +20,9 @@
 
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/call.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/member_accessor_expression.h"
 #include "src/tint/sem/type_initializer.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/transform/simplify_pointers.h"
 #include "src/tint/utils/hash.h"
 #include "src/tint/utils/map.h"
diff --git a/src/tint/transform/decompose_strided_matrix.cc b/src/tint/transform/decompose_strided_matrix.cc
index 15cdf62..773cfdf 100644
--- a/src/tint/transform/decompose_strided_matrix.cc
+++ b/src/tint/transform/decompose_strided_matrix.cc
@@ -19,8 +19,8 @@
 #include <vector>
 
 #include "src/tint/program_builder.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/transform/simplify_pointers.h"
 #include "src/tint/utils/hash.h"
 #include "src/tint/utils/map.h"
diff --git a/src/tint/transform/direct_variable_access.cc b/src/tint/transform/direct_variable_access.cc
index c2a4234..2d1b514 100644
--- a/src/tint/transform/direct_variable_access.cc
+++ b/src/tint/transform/direct_variable_access.cc
@@ -151,7 +151,7 @@
 struct AccessChain : AccessShape {
     /// The array accessor index expressions. This vector is indexed by the `DynamicIndex`s in
     /// #indices.
-    tint::utils::Vector<const tint::sem::Expression*, 8> dynamic_indices;
+    tint::utils::Vector<const tint::sem::ValueExpression*, 8> dynamic_indices;
     /// If true, then this access chain is used as an argument to call a variant.
     bool used_in_call = false;
 };
@@ -216,7 +216,7 @@
         // are grown and moved up the expression tree. After this stage, we are left with all the
         // expression access chains to variables that we may need to transform.
         for (auto* node : ctx.src->ASTNodes().Objects()) {
-            if (auto* expr = sem.Get<sem::Expression>(node)) {
+            if (auto* expr = sem.Get<sem::ValueExpression>(node)) {
                 AppendAccessChain(expr);
             }
         }
@@ -361,7 +361,7 @@
         /// A map of variant signature to the variant data.
         utils::Hashmap<FnVariant::Signature, FnVariant, 8> variants;
         /// A map of expressions that have been hoisted to a 'let' declaration in the function.
-        utils::Hashmap<const sem::Expression*, Symbol, 8> hoisted_exprs;
+        utils::Hashmap<const sem::ValueExpression*, Symbol, 8> hoisted_exprs;
 
         /// @returns the variants of the function in a deterministically ordered vector.
         utils::Vector<std::pair<const FnVariant::Signature*, FnVariant*>, 8> SortedVariants() {
@@ -392,7 +392,7 @@
     /// pointer parameter.
     utils::Hashmap<AccessShape, Symbol, 8> dynamic_index_array_aliases;
     /// Map of semantic expression to AccessChain
-    utils::Hashmap<const sem::Expression*, AccessChain*, 32> access_chains;
+    utils::Hashmap<const sem::ValueExpression*, AccessChain*, 32> access_chains;
     /// Allocator for FnInfo
     utils::BlockAllocator<FnInfo> fn_info_allocator;
     /// Allocator for AccessChain
@@ -418,10 +418,10 @@
 
     /// AppendAccessChain creates or extends an existing AccessChain for the given expression,
     /// modifying the #access_chains map.
-    void AppendAccessChain(const sem::Expression* expr) {
+    void AppendAccessChain(const sem::ValueExpression* expr) {
         // take_chain moves the AccessChain from the expression `from` to the expression `expr`.
         // Returns nullptr if `from` did not hold an access chain.
-        auto take_chain = [&](const sem::Expression* from) -> AccessChain* {
+        auto take_chain = [&](const sem::ValueExpression* from) -> AccessChain* {
             if (auto* chain = AccessChainFor(from)) {
                 access_chains.Remove(from);
                 access_chains.Add(expr, chain);
@@ -492,7 +492,7 @@
                     chain->dynamic_indices.Push(a->Index());
                 }
             },
-            [&](const sem::Expression* e) {
+            [&](const sem::ValueExpression* e) {
                 if (auto* unary = e->Declaration()->As<ast::UnaryOpExpression>()) {
                     // Unary op.
                     // If this is a '&' or '*', simply move the chain to the unary op expression.
@@ -556,7 +556,7 @@
     /// * Casts the resulting expression to a u32 if @p cast_to_u32 is true, and the expression type
     ///   isn't implicitly usable as a u32. This is to help feed the expression into a
     ///   `array<u32, N>` argument passed to a callee variant function.
-    const ast::Expression* BuildDynamicIndex(const sem::Expression* idx, bool cast_to_u32) {
+    const ast::Expression* BuildDynamicIndex(const sem::ValueExpression* idx, bool cast_to_u32) {
         if (auto* val = idx->ConstantValue()) {
             // Expression evaluated to a constant value. Just emit that constant.
             return b.Expr(val->ValueAs<AInt>());
@@ -766,7 +766,7 @@
 
     /// @returns the AccessChain for the expression @p expr, or nullptr if the expression does
     /// not hold an access chain.
-    AccessChain* AccessChainFor(const sem::Expression* expr) const {
+    AccessChain* AccessChainFor(const sem::ValueExpression* expr) const {
         if (auto chain = access_chains.Find(expr)) {
             return *chain;
         }
@@ -990,7 +990,7 @@
                 return nullptr;  // Just clone the expression.
             }
 
-            auto* expr = sem.Get<sem::Expression>(ast_expr);
+            auto* expr = sem.Get<sem::ValueExpression>(ast_expr);
             if (!expr) {
                 // No semantic node for the expression.
                 return nullptr;  // Just clone the expression.
diff --git a/src/tint/transform/expand_compound_assignment.cc b/src/tint/transform/expand_compound_assignment.cc
index 3d62f23..c208601 100644
--- a/src/tint/transform/expand_compound_assignment.cc
+++ b/src/tint/transform/expand_compound_assignment.cc
@@ -20,9 +20,9 @@
 #include "src/tint/ast/increment_decrement_statement.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/block_statement.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/for_loop_statement.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/transform/utils/hoist_to_decl_before.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::ExpandCompoundAssignment);
diff --git a/src/tint/transform/localize_struct_array_assignment.cc b/src/tint/transform/localize_struct_array_assignment.cc
index 6ed16b3..0d40ccf 100644
--- a/src/tint/transform/localize_struct_array_assignment.cc
+++ b/src/tint/transform/localize_struct_array_assignment.cc
@@ -20,9 +20,9 @@
 #include "src/tint/ast/assignment_statement.h"
 #include "src/tint/ast/traverse_expressions.h"
 #include "src/tint/program_builder.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/member_accessor_expression.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/sem/variable.h"
 #include "src/tint/transform/simplify_pointers.h"
 #include "src/tint/type/reference.h"
diff --git a/src/tint/transform/packed_vec3.cc b/src/tint/transform/packed_vec3.cc
index af95161..26cad63 100644
--- a/src/tint/transform/packed_vec3.cc
+++ b/src/tint/transform/packed_vec3.cc
@@ -72,11 +72,11 @@
 
         // Walk the nodes, starting with the most deeply nested, finding all the AST expressions
         // that load a whole packed vector (not a scalar / swizzle of the vector).
-        utils::Hashset<const sem::Expression*, 16> refs;
+        utils::Hashset<const sem::ValueExpression*, 16> refs;
         for (auto* node : ctx.src->ASTNodes().Objects()) {
             auto* sem_node = sem.Get(node);
             if (sem_node) {
-                if (auto* expr = sem_node->As<sem::Expression>()) {
+                if (auto* expr = sem_node->As<sem::ValueExpression>()) {
                     sem_node = expr->UnwrapLoad();
                 }
             }
@@ -104,7 +104,7 @@
                         refs.Add(user);  // then propagate tracking to pointer usage
                     }
                 },
-                [&](const sem::Expression* expr) {
+                [&](const sem::ValueExpression* expr) {
                     if (auto* unary = expr->Declaration()->As<ast::UnaryOpExpression>()) {
                         if (unary->op == ast::UnaryOp::kAddressOf ||
                             unary->op == ast::UnaryOp::kIndirection) {
diff --git a/src/tint/transform/promote_initializers_to_let.cc b/src/tint/transform/promote_initializers_to_let.cc
index 391a7f2..0ad37de 100644
--- a/src/tint/transform/promote_initializers_to_let.cc
+++ b/src/tint/transform/promote_initializers_to_let.cc
@@ -41,7 +41,7 @@
 
     // Returns true if the expression should be hoisted to a new let statement before the
     // expression's statement.
-    auto should_hoist = [&](const sem::Expression* expr) {
+    auto should_hoist = [&](const sem::ValueExpression* expr) {
         if (!expr->Type()->IsAnyOf<type::Array, type::Struct>()) {
             // We only care about array and struct initializers
             return false;
@@ -77,13 +77,13 @@
     };
 
     // A list of expressions that should be hoisted.
-    utils::Vector<const sem::Expression*, 32> to_hoist;
+    utils::Vector<const sem::ValueExpression*, 32> to_hoist;
     // A set of expressions that are constant, which _may_ need to be hoisted.
     utils::Hashset<const ast::Expression*, 32> const_chains;
 
     // Walk the AST nodes. This order guarantees that leaf-expressions are visited first.
     for (auto* node : src->ASTNodes().Objects()) {
-        if (auto* sem = src->Sem().Get<sem::Expression>(node)) {
+        if (auto* sem = src->Sem().Get<sem::ValueExpression>(node)) {
             auto* stmt = sem->Stmt();
             if (!stmt) {
                 // Expression is outside of a statement. This usually means the expression is part
diff --git a/src/tint/transform/promote_side_effects_to_decl.cc b/src/tint/transform/promote_side_effects_to_decl.cc
index 2d4b059..632d57b 100644
--- a/src/tint/transform/promote_side_effects_to_decl.cc
+++ b/src/tint/transform/promote_side_effects_to_decl.cc
@@ -107,7 +107,7 @@
     std::unordered_set<const ast::Expression*> no_side_effects;
 
     // Returns true if `expr` has side-effects. Unlike invoking
-    // sem::Expression::HasSideEffects(), this function takes into account whether
+    // sem::ValueExpression::HasSideEffects(), this function takes into account whether
     // `expr` has been hoisted, returning false in that case. Furthermore, it
     // returns the correct result on parent expression nodes by traversing the
     // expression tree, memoizing the results to ensure O(1) amortized lookup.
diff --git a/src/tint/transform/robustness.cc b/src/tint/transform/robustness.cc
index 1d624df..4680939 100644
--- a/src/tint/transform/robustness.cc
+++ b/src/tint/transform/robustness.cc
@@ -21,9 +21,9 @@
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/block_statement.h"
 #include "src/tint/sem/call.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/index_accessor_expression.h"
 #include "src/tint/sem/statement.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/type/reference.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::Robustness);
diff --git a/src/tint/transform/spirv_atomic.cc b/src/tint/transform/spirv_atomic.cc
index 9ad5b3e..07879e8 100644
--- a/src/tint/transform/spirv_atomic.cc
+++ b/src/tint/transform/spirv_atomic.cc
@@ -54,7 +54,7 @@
     CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
     std::unordered_map<const ast::Struct*, ForkedStruct> forked_structs;
     std::unordered_set<const sem::Variable*> atomic_variables;
-    utils::UniqueVector<const sem::Expression*, 8> atomic_expressions;
+    utils::UniqueVector<const sem::ValueExpression*, 8> atomic_expressions;
 
   public:
     /// Constructor
@@ -184,7 +184,7 @@
                 [&](const sem::IndexAccessorExpression* index) {
                     atomic_expressions.Add(index->Object());
                 },
-                [&](const sem::Expression* e) {
+                [&](const sem::ValueExpression* e) {
                     if (auto* unary = e->Declaration()->As<ast::UnaryOpExpression>()) {
                         atomic_expressions.Add(ctx.src->Sem().Get(unary->expr));
                     }
@@ -226,7 +226,7 @@
 
     void ReplaceLoadsAndStores() {
         // Returns true if 'e' is a reference to an atomic variable or struct member
-        auto is_ref_to_atomic_var = [&](const sem::Expression* e) {
+        auto is_ref_to_atomic_var = [&](const sem::ValueExpression* e) {
             if (tint::Is<type::Reference>(e->Type()) && e->RootIdentifier() &&
                 (atomic_variables.count(e->RootIdentifier()) != 0)) {
                 // If it's a struct member, make sure it's one we marked as atomic
diff --git a/src/tint/transform/std140.cc b/src/tint/transform/std140.cc
index c66e396..cde4a3f 100644
--- a/src/tint/transform/std140.cc
+++ b/src/tint/transform/std140.cc
@@ -251,7 +251,7 @@
         /// The chain of access indices, starting with the first access on #var.
         AccessIndices indices;
         /// The runtime-evaluated expressions. This vector is indexed by the DynamicIndex::slot
-        utils::Vector<const sem::Expression*, 8> dynamic_indices;
+        utils::Vector<const sem::ValueExpression*, 8> dynamic_indices;
         /// The type of the std140-decomposed matrix being accessed.
         /// May be nullptr if the chain does not pass through a std140-decomposed matrix.
         const type::Matrix* std140_mat_ty = nullptr;
@@ -573,7 +573,7 @@
                     expr = s->Object();
                     return Action::kContinue;
                 },
-                [&](const sem::Expression* e) {
+                [&](const sem::ValueExpression* e) {
                     // Walk past indirection and address-of unary ops.
                     return Switch(e->Declaration(),  //
                                   [&](const ast::UnaryOpExpression* u) {
@@ -797,7 +797,7 @@
         });
 
         // Build the arguments
-        auto args = utils::Transform(access.dynamic_indices, [&](const sem::Expression* e) {
+        auto args = utils::Transform(access.dynamic_indices, [&](const sem::ValueExpression* e) {
             return b.Construct(b.ty.u32(), ctx.Clone(e->Declaration()));
         });
 
diff --git a/src/tint/transform/utils/hoist_to_decl_before.cc b/src/tint/transform/utils/hoist_to_decl_before.cc
index 5852573..fd7402e 100644
--- a/src/tint/transform/utils/hoist_to_decl_before.cc
+++ b/src/tint/transform/utils/hoist_to_decl_before.cc
@@ -36,7 +36,7 @@
     explicit State(CloneContext& ctx_in) : ctx(ctx_in), b(*ctx_in.dst) {}
 
     /// @copydoc HoistToDeclBefore::Add()
-    bool Add(const sem::Expression* before_expr,
+    bool Add(const sem::ValueExpression* before_expr,
              const ast::Expression* expr,
              VariableKind kind,
              const char* decl_name) {
@@ -94,7 +94,7 @@
     }
 
     /// @copydoc HoistToDeclBefore::Prepare()
-    bool Prepare(const sem::Expression* before_expr) {
+    bool Prepare(const sem::ValueExpression* before_expr) {
         return InsertBefore(before_expr->Stmt(), nullptr);
     }
 
@@ -376,7 +376,7 @@
 
 HoistToDeclBefore::~HoistToDeclBefore() {}
 
-bool HoistToDeclBefore::Add(const sem::Expression* before_expr,
+bool HoistToDeclBefore::Add(const sem::ValueExpression* before_expr,
                             const ast::Expression* expr,
                             VariableKind kind,
                             const char* decl_name) {
@@ -393,7 +393,7 @@
     return state_->InsertBefore(before_stmt, builder);
 }
 
-bool HoistToDeclBefore::Prepare(const sem::Expression* before_expr) {
+bool HoistToDeclBefore::Prepare(const sem::ValueExpression* before_expr) {
     return state_->Prepare(before_expr);
 }
 
diff --git a/src/tint/transform/utils/hoist_to_decl_before.h b/src/tint/transform/utils/hoist_to_decl_before.h
index 4d6a808..81c255f 100644
--- a/src/tint/transform/utils/hoist_to_decl_before.h
+++ b/src/tint/transform/utils/hoist_to_decl_before.h
@@ -18,7 +18,7 @@
 #include <functional>
 #include <memory>
 
-#include "src/tint/sem/expression.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/transform/transform.h"
 
 namespace tint::transform {
@@ -52,7 +52,7 @@
     /// @param kind variable kind to hoist to
     /// @param decl_name optional name to use for the variable/constant name
     /// @return true on success
-    bool Add(const sem::Expression* before_expr,
+    bool Add(const sem::ValueExpression* before_expr,
              const ast::Expression* expr,
              VariableKind kind,
              const char* decl_name = "");
@@ -81,7 +81,7 @@
     /// needed.
     /// @param before_expr expression we would hoist a decl before
     /// @return true on success
-    bool Prepare(const sem::Expression* before_expr);
+    bool Prepare(const sem::ValueExpression* before_expr);
 
   private:
     struct State;
diff --git a/src/tint/transform/vectorize_matrix_conversions.cc b/src/tint/transform/vectorize_matrix_conversions.cc
index 26f27c3..f22e32d 100644
--- a/src/tint/transform/vectorize_matrix_conversions.cc
+++ b/src/tint/transform/vectorize_matrix_conversions.cc
@@ -20,8 +20,8 @@
 
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/call.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/type_conversion.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/type/abstract_numeric.h"
 #include "src/tint/utils/hash.h"
 #include "src/tint/utils/map.h"
@@ -34,7 +34,7 @@
 
 bool ShouldRun(const Program* program) {
     for (auto* node : program->ASTNodes().Objects()) {
-        if (auto* sem = program->Sem().Get<sem::Expression>(node)) {
+        if (auto* sem = program->Sem().Get<sem::ValueExpression>(node)) {
             if (auto* call = sem->UnwrapMaterialize()->As<sem::Call>()) {
                 if (call->Target()->Is<sem::TypeConversion>() && call->Type()->Is<type::Matrix>()) {
                     auto& args = call->Arguments();
diff --git a/src/tint/transform/vectorize_scalar_matrix_initializers.cc b/src/tint/transform/vectorize_scalar_matrix_initializers.cc
index 9e989bc..60d4d13 100644
--- a/src/tint/transform/vectorize_scalar_matrix_initializers.cc
+++ b/src/tint/transform/vectorize_scalar_matrix_initializers.cc
@@ -19,8 +19,8 @@
 
 #include "src/tint/program_builder.h"
 #include "src/tint/sem/call.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/type_initializer.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/type/abstract_numeric.h"
 #include "src/tint/utils/map.h"
 
diff --git a/src/tint/writer/append_vector.cc b/src/tint/writer/append_vector.cc
index ab0ab70..c39c619 100644
--- a/src/tint/writer/append_vector.cc
+++ b/src/tint/writer/append_vector.cc
@@ -18,9 +18,9 @@
 #include <vector>
 
 #include "src/tint/sem/call.h"
-#include "src/tint/sem/expression.h"
 #include "src/tint/sem/type_conversion.h"
 #include "src/tint/sem/type_initializer.h"
+#include "src/tint/sem/value_expression.h"
 #include "src/tint/utils/transform.h"
 
 using namespace tint::number_suffixes;  // NOLINT
@@ -33,7 +33,7 @@
     const sem::TypeInitializer* ctor = nullptr;
     operator bool() const { return call != nullptr; }
 };
-VectorInitializerInfo AsVectorInitializer(const sem::Expression* expr) {
+VectorInitializerInfo AsVectorInitializer(const sem::ValueExpression* expr) {
     if (auto* call = expr->As<sem::Call>()) {
         if (auto* ctor = call->Target()->As<sem::TypeInitializer>()) {
             if (ctor->ReturnType()->Is<type::Vector>()) {
@@ -44,7 +44,9 @@
     return {};
 }
 
-const sem::Expression* Zero(ProgramBuilder& b, const type::Type* ty, const sem::Statement* stmt) {
+const sem::ValueExpression* Zero(ProgramBuilder& b,
+                                 const type::Type* ty,
+                                 const sem::Statement* stmt) {
     const ast::Expression* expr = nullptr;
     if (ty->Is<type::I32>()) {
         expr = b.Expr(0_i);
@@ -59,9 +61,9 @@
             << "unsupported vector element type: " << ty->TypeInfo().name;
         return nullptr;
     }
-    auto* sem = b.create<sem::Expression>(expr, ty, sem::EvaluationStage::kRuntime, stmt,
-                                          /* constant_value */ nullptr,
-                                          /* has_side_effects */ false);
+    auto* sem = b.create<sem::ValueExpression>(expr, ty, sem::EvaluationStage::kRuntime, stmt,
+                                               /* constant_value */ nullptr,
+                                               /* has_side_effects */ false);
     b.Sem().Add(expr, sem);
     return sem;
 }
@@ -112,7 +114,7 @@
     // to convert a vector of a different type, e.g. vec2<i32>(vec2<u32>()).
     // In that case, preserve the original argument, or you'll get a type error.
 
-    utils::Vector<const sem::Expression*, 4> packed;
+    utils::Vector<const sem::ValueExpression*, 4> packed;
     if (auto vc = AsVectorInitializer(vector_sem)) {
         const auto num_supplied = vc.call->Arguments().Length();
         if (num_supplied == 0) {
@@ -141,7 +143,7 @@
             sem::EvaluationStage::kRuntime);
         auto* scalar_cast_sem = b->create<sem::Call>(
             scalar_cast_ast, scalar_cast_target, sem::EvaluationStage::kRuntime,
-            utils::Vector<const sem::Expression*, 1>{scalar_sem}, statement,
+            utils::Vector<const sem::ValueExpression*, 1>{scalar_sem}, statement,
             /* constant_value */ nullptr, /* has_side_effects */ false);
         b->Sem().Add(scalar_cast_ast, scalar_cast_sem);
         packed.Push(scalar_cast_sem);
@@ -149,17 +151,19 @@
         packed.Push(scalar_sem);
     }
 
-    auto* initializer_ast = b->Construct(
-        packed_ast_ty,
-        utils::Transform(packed, [&](const sem::Expression* expr) { return expr->Declaration(); }));
+    auto* initializer_ast =
+        b->Construct(packed_ast_ty, utils::Transform(packed, [&](const sem::ValueExpression* expr) {
+                         return expr->Declaration();
+                     }));
     auto* initializer_target = b->create<sem::TypeInitializer>(
         packed_sem_ty,
-        utils::Transform(packed,
-                         [&](const tint::sem::Expression* arg, size_t i) -> const sem::Parameter* {
-                             return b->create<sem::Parameter>(
-                                 nullptr, static_cast<uint32_t>(i), arg->Type()->UnwrapRef(),
-                                 type::AddressSpace::kNone, type::Access::kUndefined);
-                         }),
+        utils::Transform(
+            packed,
+            [&](const tint::sem::ValueExpression* arg, size_t i) -> const sem::Parameter* {
+                return b->create<sem::Parameter>(
+                    nullptr, static_cast<uint32_t>(i), arg->Type()->UnwrapRef(),
+                    type::AddressSpace::kNone, type::Access::kUndefined);
+            }),
         sem::EvaluationStage::kRuntime);
     auto* initializer_sem =
         b->create<sem::Call>(initializer_ast, initializer_target, sem::EvaluationStage::kRuntime,
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 8461e51..3009355 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -1313,9 +1313,9 @@
 const ast::Expression* GeneratorImpl::CreateF32Zero(const sem::Statement* stmt) {
     auto* zero = builder_.Expr(0_f);
     auto* f32 = builder_.create<type::F32>();
-    auto* sem_zero = builder_.create<sem::Expression>(zero, f32, sem::EvaluationStage::kRuntime,
-                                                      stmt, /* constant_value */ nullptr,
-                                                      /* has_side_effects */ false);
+    auto* sem_zero = builder_.create<sem::ValueExpression>(
+        zero, f32, sem::EvaluationStage::kRuntime, stmt, /* constant_value */ nullptr,
+        /* has_side_effects */ false);
     builder_.Sem().Add(zero, sem_zero);
     return zero;
 }
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index f6524d3..014cb88 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -2569,10 +2569,10 @@
         auto* i32 = builder_.create<type::I32>();
         auto* zero = builder_.Expr(0_i);
         auto* stmt = builder_.Sem().Get(vector)->Stmt();
-        builder_.Sem().Add(
-            zero, builder_.create<sem::Expression>(zero, i32, sem::EvaluationStage::kRuntime, stmt,
-                                                   /* constant_value */ nullptr,
-                                                   /* has_side_effects */ false));
+        builder_.Sem().Add(zero, builder_.create<sem::ValueExpression>(
+                                     zero, i32, sem::EvaluationStage::kRuntime, stmt,
+                                     /* constant_value */ nullptr,
+                                     /* has_side_effects */ false));
         auto* packed = AppendVector(&builder_, vector, zero);
         return EmitExpression(out, packed->Declaration());
     };
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index fab7744..b36d89f 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -558,7 +558,7 @@
     return true;
 }
 
-uint32_t Builder::GenerateExpression(const sem::Expression* expr) {
+uint32_t Builder::GenerateExpression(const sem::ValueExpression* expr) {
     if (auto* constant = expr->ConstantValue()) {
         return GenerateConstantIfNeeded(constant);
     }
@@ -2625,7 +2625,7 @@
     auto& arguments = call->Arguments();
 
     // Generates the given expression, returning the operand ID
-    auto gen = [&](const sem::Expression* expr) { return Operand(GenerateExpression(expr)); };
+    auto gen = [&](const sem::ValueExpression* expr) { return Operand(GenerateExpression(expr)); };
 
     // Returns the argument with the given usage
     auto arg = [&](Usage usage) {
diff --git a/src/tint/writer/spirv/builder.h b/src/tint/writer/spirv/builder.h
index 12e16c1..0695a9c 100644
--- a/src/tint/writer/spirv/builder.h
+++ b/src/tint/writer/spirv/builder.h
@@ -275,7 +275,7 @@
     /// Generates an expression
     /// @param expr the expression to generate
     /// @returns the resulting ID of the expression or 0 on error
-    uint32_t GenerateExpression(const sem::Expression* expr);
+    uint32_t GenerateExpression(const sem::ValueExpression* expr);
     /// Generates an expression
     /// @param expr the expression to generate
     /// @returns the resulting ID of the expression or 0 on error