tint: Add sem::Expression

A new base class for sem::ValueExpression, which other types of
expression can derive from.

Example: sem::TypeExpression - an expression that resolves to a type.

Bug: tint:1810
Change-Id: I90dfb66b265b67d9fdf0c04eb3dce2442c7e18ea
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/118404
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 054e7ec..e49c128 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -368,6 +368,7 @@
     "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",
@@ -763,6 +764,8 @@
     "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",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 8ffc488..a8de1d6 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -330,6 +330,8 @@
   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
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 9f8a14b..7b3c416 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
@@ -51,7 +51,7 @@
             continue;
         }
 
-        const auto* expr_sem_node = program.Sem().Get(expr_ast_node);
+        const auto* expr_sem_node = program.Sem().GetVal(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/change_binary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc
index 78cc131..29cfbcf 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.cc
@@ -297,8 +297,8 @@
     }
 
     // Get the types of the operators.
-    const auto* lhs_type = program.Sem().Get(binary_expr.lhs)->Type();
-    const auto* rhs_type = program.Sem().Get(binary_expr.rhs)->Type();
+    const auto* lhs_type = program.Sem().GetVal(binary_expr.lhs)->Type();
+    const auto* rhs_type = program.Sem().GetVal(binary_expr.rhs)->Type();
 
     // If these are reference types, unwrap them to get the pointee type.
     const type::Type* lhs_basic_type =
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 df5c13c..25ac065 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,7 +50,7 @@
         return false;
     }
 
-    const auto* expression_sem_node = program.Sem().Get(expression_ast_node);
+    const auto* expression_sem_node = program.Sem().GetVal(expression_ast_node);
 
     if (!expression_sem_node) {
         // Semantic information for the expression ast node is not present
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index e57dce3..b07536d 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -900,7 +900,7 @@
     utils::UniqueVector<const ast::CallExpression*, 8> callsites;
 
     for (size_t i = 0; i < N; i++) {
-        const sem::Variable* root_ident = sem.Get(exprs[i])->RootIdentifier();
+        const sem::Variable* root_ident = sem.GetVal(exprs[i])->RootIdentifier();
         if (auto* global = root_ident->As<sem::GlobalVariable>()) {
             globals[i] = global;
         } else if (auto* param = root_ident->As<sem::Parameter>()) {
diff --git a/src/tint/program.cc b/src/tint/program.cc
index f2a766a..a5d8c45 100644
--- a/src/tint/program.cc
+++ b/src/tint/program.cc
@@ -118,7 +118,7 @@
 }
 
 const type::Type* Program::TypeOf(const ast::Expression* expr) const {
-    auto* sem = Sem().Get(expr);
+    auto* sem = Sem().GetVal(expr);
     return sem ? sem->Type() : nullptr;
 }
 
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
index 1a0faf6..0ce8f0b 100644
--- a/src/tint/program_builder.cc
+++ b/src/tint/program_builder.cc
@@ -97,7 +97,7 @@
 }
 
 const type::Type* ProgramBuilder::TypeOf(const ast::Expression* expr) const {
-    auto* sem = Sem().Get(expr);
+    auto* sem = Sem().GetVal(expr);
     return sem ? sem->Type() : nullptr;
 }
 
diff --git a/src/tint/resolver/builtin_validation_test.cc b/src/tint/resolver/builtin_validation_test.cc
index cfd9877..c9037ba 100644
--- a/src/tint/resolver/builtin_validation_test.cc
+++ b/src/tint/resolver/builtin_validation_test.cc
@@ -175,7 +175,7 @@
     WrapInFunction(Decl(Var("v", use)));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
-    auto* sem = Sem().Get(use)->UnwrapLoad()->As<sem::VariableUser>();
+    auto* sem = Sem().GetVal(use)->UnwrapLoad()->As<sem::VariableUser>();
     ASSERT_NE(sem, nullptr);
     EXPECT_EQ(sem->Variable(), Sem().Get(mix));
 }
diff --git a/src/tint/resolver/const_eval_binary_op_test.cc b/src/tint/resolver/const_eval_binary_op_test.cc
index 30c802e..10c047c 100644
--- a/src/tint/resolver/const_eval_binary_op_test.cc
+++ b/src/tint/resolver/const_eval_binary_op_test.cc
@@ -896,7 +896,7 @@
     ASSERT_NE(value, nullptr);
     EXPECT_TYPE(value->Type(), sem->Type());
 
-    auto* expected_sem = Sem().Get(expected_expr);
+    auto* expected_sem = Sem().GetVal(expected_expr);
     const constant::Value* expected_value = expected_sem->ConstantValue();
     ASSERT_NE(expected_value, nullptr);
     EXPECT_TYPE(expected_value->Type(), expected_sem->Type());
@@ -1374,12 +1374,12 @@
     auto* lhs = binary->lhs;
     auto* rhs = binary->rhs;
 
-    auto* lhs_sem = sem.Get(lhs);
+    auto* lhs_sem = sem.GetVal(lhs);
     ASSERT_TRUE(lhs_sem->ConstantValue());
     EXPECT_EQ(lhs_sem->ConstantValue()->ValueAs<bool>(), false);
     EXPECT_EQ(lhs_sem->Stage(), sem::EvaluationStage::kConstant);
 
-    auto* rhs_sem = sem.Get(rhs);
+    auto* rhs_sem = sem.GetVal(rhs);
     EXPECT_EQ(rhs_sem->ConstantValue(), nullptr);
     EXPECT_EQ(rhs_sem->Stage(), sem::EvaluationStage::kNotEvaluated);
 
@@ -1394,12 +1394,12 @@
     auto* lhs = binary->lhs;
     auto* rhs = binary->rhs;
 
-    auto* lhs_sem = sem.Get(lhs);
+    auto* lhs_sem = sem.GetVal(lhs);
     ASSERT_TRUE(lhs_sem->ConstantValue());
     EXPECT_EQ(lhs_sem->ConstantValue()->ValueAs<bool>(), true);
     EXPECT_EQ(lhs_sem->Stage(), sem::EvaluationStage::kConstant);
 
-    auto* rhs_sem = sem.Get(rhs);
+    auto* rhs_sem = sem.GetVal(rhs);
     EXPECT_EQ(rhs_sem->ConstantValue(), nullptr);
     EXPECT_EQ(rhs_sem->Stage(), sem::EvaluationStage::kNotEvaluated);
 
diff --git a/src/tint/resolver/const_eval_bitcast_test.cc b/src/tint/resolver/const_eval_bitcast_test.cc
index 50d4693..32c1382 100644
--- a/src/tint/resolver/const_eval_bitcast_test.cc
+++ b/src/tint/resolver/const_eval_bitcast_test.cc
@@ -76,7 +76,7 @@
     if (expected) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
 
-        auto* sem = Sem().Get(expr);
+        auto* sem = Sem().GetVal(expr);
         ASSERT_NE(sem, nullptr);
         EXPECT_TYPE(sem->Type(), target_sem_ty);
         ASSERT_NE(sem->ConstantValue(), nullptr);
diff --git a/src/tint/resolver/const_eval_construction_test.cc b/src/tint/resolver/const_eval_construction_test.cc
index d8b25b9..96e37ca 100644
--- a/src/tint/resolver/const_eval_construction_test.cc
+++ b/src/tint/resolver/const_eval_construction_test.cc
@@ -1712,7 +1712,7 @@
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
-    auto* sem = Sem().Get(expr);
+    auto* sem = Sem().GetVal(expr);
     ASSERT_NE(sem, nullptr);
     auto* arr = sem->Type()->As<type::Array>();
     ASSERT_NE(arr, nullptr);
diff --git a/src/tint/resolver/const_eval_member_access_test.cc b/src/tint/resolver/const_eval_member_access_test.cc
index f228509..04ed3f8 100644
--- a/src/tint/resolver/const_eval_member_access_test.cc
+++ b/src/tint/resolver/const_eval_member_access_test.cc
@@ -287,7 +287,7 @@
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
-    auto* sem = Sem().Get(expr);
+    auto* sem = Sem().GetVal(expr);
     ASSERT_NE(sem, nullptr);
     auto* arr = sem->Type()->As<type::Array>();
     ASSERT_NE(arr, nullptr);
@@ -362,7 +362,7 @@
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
-    auto* sem = Sem().Get(expr);
+    auto* sem = Sem().GetVal(expr);
     ASSERT_NE(sem, nullptr);
     auto* vec = sem->Type()->As<type::Vector>();
     ASSERT_NE(vec, nullptr);
diff --git a/src/tint/resolver/load_test.cc b/src/tint/resolver/load_test.cc
index 3e9d50c..d44b34f 100644
--- a/src/tint/resolver/load_test.cc
+++ b/src/tint/resolver/load_test.cc
@@ -361,7 +361,7 @@
                    Let("l", AddressOf(ident)));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
-    auto* no_load = Sem().Get(ident);
+    auto* no_load = Sem().GetVal(ident);
     ASSERT_NE(no_load, nullptr);
     EXPECT_TRUE(no_load->Type()->Is<type::Reference>());  // No load
 }
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index e34e999..4543f85 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -404,7 +404,7 @@
         }
         case Expectation::kNoMaterialize: {
             ASSERT_TRUE(r()->Resolve()) << r()->error();
-            auto* sem = Sem().Get(abstract_expr);
+            auto* sem = Sem().GetVal(abstract_expr);
             ASSERT_NE(sem, nullptr);
             EXPECT_FALSE(sem->Is<sem::Materialize>());
             CheckTypesAndValues(sem, data.target_sem_ty(*this), data.materialized_value);
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 7082057..7dbd6ae 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -1318,7 +1318,7 @@
             if (!sel->IsDefault()) {
                 // The sem statement was created in the switch when attempting to determine the
                 // common type.
-                auto* materialized = Materialize(sem_.Get(sel->expr), ty);
+                auto* materialized = Materialize(sem_.GetVal(sel->expr), ty);
                 if (!materialized) {
                     return false;
                 }
@@ -1923,11 +1923,11 @@
 }
 
 sem::ValueExpression* Resolver::IndexAccessor(const ast::IndexAccessorExpression* expr) {
-    auto* idx = Load(Materialize(sem_.Get(expr->index)));
+    auto* idx = Load(Materialize(sem_.GetVal(expr->index)));
     if (!idx) {
         return nullptr;
     }
-    const auto* obj = sem_.Get(expr->object);
+    const auto* obj = sem_.GetVal(expr->object);
     if (idx->Stage() != sem::EvaluationStage::kConstant) {
         // If the index is non-constant, then the resulting expression is non-constant, so we'll
         // have to materialize the object. For example, consider:
@@ -1986,7 +1986,7 @@
 }
 
 sem::ValueExpression* Resolver::Bitcast(const ast::BitcastExpression* expr) {
-    auto* inner = Load(Materialize(sem_.Get(expr->expr)));
+    auto* inner = Load(Materialize(sem_.GetVal(expr->expr)));
     if (!inner) {
         return nullptr;
     }
@@ -2031,7 +2031,7 @@
     auto args_stage = sem::EvaluationStage::kConstant;
     sem::Behaviors arg_behaviors;
     for (size_t i = 0; i < expr->args.Length(); i++) {
-        auto* arg = sem_.Get(expr->args[i]);
+        auto* arg = sem_.GetVal(expr->args[i]);
         if (!arg) {
             return nullptr;
         }
@@ -2780,13 +2780,17 @@
 sem::ValueExpression* Resolver::MemberAccessor(const ast::MemberAccessorExpression* expr) {
     auto* structure = sem_.TypeOf(expr->object);
     auto* storage_ty = structure->UnwrapRef();
-    auto* object = sem_.Get(expr->object);
+    auto* object = sem_.GetVal(expr->object);
+    if (!object) {
+        return nullptr;
+    }
+
     auto* root_ident = object->RootIdentifier();
 
     const type::Type* ty = nullptr;
 
     // Object may be a side-effecting expression (e.g. function call).
-    bool has_side_effects = object && object->HasSideEffects();
+    bool has_side_effects = object->HasSideEffects();
 
     Mark(expr->member);
 
@@ -2909,8 +2913,8 @@
 }
 
 sem::ValueExpression* Resolver::Binary(const ast::BinaryExpression* expr) {
-    const auto* lhs = sem_.Get(expr->lhs);
-    const auto* rhs = sem_.Get(expr->rhs);
+    const auto* lhs = sem_.GetVal(expr->lhs);
+    const auto* rhs = sem_.GetVal(expr->rhs);
     auto* lhs_ty = lhs->Type()->UnwrapRef();
     auto* rhs_ty = rhs->Type()->UnwrapRef();
 
@@ -2986,7 +2990,7 @@
 }
 
 sem::ValueExpression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
-    const auto* expr = sem_.Get(unary->expr);
+    const auto* expr = sem_.GetVal(unary->expr);
     auto* expr_ty = expr->Type();
     if (!expr_ty) {
         return nullptr;
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
index f04b697..2c175b9 100644
--- a/src/tint/resolver/resolver_test_helper.h
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -83,9 +83,12 @@
     /// @return the resolved sem::Variable of the identifier, or nullptr if
     /// the expression did not resolve to a variable.
     const sem::Variable* VarOf(const ast::Expression* expr) {
-        auto* sem_ident = Sem().Get(expr)->UnwrapLoad();
-        auto* var_user = sem_ident ? sem_ident->As<sem::VariableUser>() : nullptr;
-        return var_user ? var_user->Variable() : nullptr;
+        if (auto* sem = Sem().GetVal(expr)) {
+            if (auto* var_user = As<sem::VariableUser>(sem->UnwrapLoad())) {
+                return var_user->Variable();
+            }
+        }
+        return nullptr;
     }
 
     /// Checks that all the users of the given variable are as expected
diff --git a/src/tint/resolver/root_identifier_test.cc b/src/tint/resolver/root_identifier_test.cc
index 9580695..656b0a8 100644
--- a/src/tint/resolver/root_identifier_test.cc
+++ b/src/tint/resolver/root_identifier_test.cc
@@ -34,7 +34,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, GlobalWorkgroupVar) {
@@ -45,7 +45,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, GlobalStorageVar) {
@@ -56,7 +56,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, GlobalUniformVar) {
@@ -67,7 +67,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, GlobalTextureVar) {
@@ -79,7 +79,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, GlobalOverride) {
@@ -90,7 +90,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, GlobalConst) {
@@ -101,7 +101,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, FunctionVar) {
@@ -112,7 +112,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, FunctionLet) {
@@ -123,7 +123,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, Parameter) {
@@ -134,7 +134,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_a = Sem().Get(a);
-    EXPECT_EQ(Sem().Get(expr)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr)->RootIdentifier(), sem_a);
 }
 
 TEST_F(ResolverRootIdentifierTest, PointerParameter) {
@@ -152,8 +152,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 
     auto* sem_param = Sem().Get(param);
-    EXPECT_EQ(Sem().Get(expr_param)->RootIdentifier(), sem_param);
-    EXPECT_EQ(Sem().Get(expr_let)->RootIdentifier(), sem_param);
+    EXPECT_EQ(Sem().GetVal(expr_param)->RootIdentifier(), sem_param);
+    EXPECT_EQ(Sem().GetVal(expr_let)->RootIdentifier(), sem_param);
 }
 
 TEST_F(ResolverRootIdentifierTest, VarCopyVar) {
@@ -171,8 +171,8 @@
 
     auto* sem_a = Sem().Get(a);
     auto* sem_b = Sem().Get(b);
-    EXPECT_EQ(Sem().Get(expr_a)->RootIdentifier(), sem_a);
-    EXPECT_EQ(Sem().Get(expr_b)->RootIdentifier(), sem_b);
+    EXPECT_EQ(Sem().GetVal(expr_a)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr_b)->RootIdentifier(), sem_b);
 }
 
 TEST_F(ResolverRootIdentifierTest, LetCopyVar) {
@@ -190,8 +190,8 @@
 
     auto* sem_a = Sem().Get(a);
     auto* sem_b = Sem().Get(b);
-    EXPECT_EQ(Sem().Get(expr_a)->RootIdentifier(), sem_a);
-    EXPECT_EQ(Sem().Get(expr_b)->RootIdentifier(), sem_b);
+    EXPECT_EQ(Sem().GetVal(expr_a)->RootIdentifier(), sem_a);
+    EXPECT_EQ(Sem().GetVal(expr_b)->RootIdentifier(), sem_b);
 }
 
 TEST_F(ResolverRootIdentifierTest, ThroughIndexAccessor) {
diff --git a/src/tint/resolver/sem_helper.cc b/src/tint/resolver/sem_helper.cc
index 327b1cf..fbabb5e 100644
--- a/src/tint/resolver/sem_helper.cc
+++ b/src/tint/resolver/sem_helper.cc
@@ -32,7 +32,7 @@
 }
 
 type::Type* SemHelper::TypeOf(const ast::Expression* expr) const {
-    auto* sem = Get(expr);
+    auto* sem = GetVal(expr);
     return sem ? const_cast<type::Type*>(sem->Type()) : nullptr;
 }
 
diff --git a/src/tint/resolver/sem_helper.h b/src/tint/resolver/sem_helper.h
index ded9e59..c8382db 100644
--- a/src/tint/resolver/sem_helper.h
+++ b/src/tint/resolver/sem_helper.h
@@ -34,11 +34,13 @@
     ~SemHelper();
 
     /// Get is a helper for obtaining the semantic node for the given AST node.
+    /// Raises an ICE and returns `nullptr` if there is no semantic node associated with the AST
+    /// node.
     /// @param ast the ast node to get the sem for
-    /// @returns the sem node for the provided |ast|
-    template <typename SEM = sem::Info::InferFromAST, typename AST_OR_TYPE = CastableBase>
-    auto* Get(const AST_OR_TYPE* ast) const {
-        using T = sem::Info::GetResultType<SEM, AST_OR_TYPE>;
+    /// @returns the sem node for @p ast
+    template <typename SEM = sem::Info::InferFromAST, typename AST = ast::Node>
+    auto* Get(const AST* ast) const {
+        using T = sem::Info::GetResultType<SEM, AST>;
         auto* sem = builder_->Sem().Get(ast);
         if (TINT_UNLIKELY(!sem)) {
             TINT_ICE(Resolver, builder_->Diagnostics())
@@ -49,6 +51,31 @@
         return const_cast<T*>(As<T>(sem));
     }
 
+    /// GetVal is a helper for obtaining the semantic sem::ValueExpression for the given AST node.
+    /// Raises an error diagnostic and returns `nullptr` if the semantic node is not a
+    /// sem::ValueExpression.
+    /// @param ast the ast node to get the sem for
+    /// @returns the sem node for @p ast
+    template <typename AST = ast::Node>
+    auto* GetVal(const AST* ast) const {
+        if constexpr (traits::IsTypeOrDerived<sem::SemanticNodeTypeFor<AST>,
+                                              sem::ValueExpression>) {
+            return Get(ast);
+        } else {
+            if (auto* sem = Get(ast); TINT_LIKELY(sem)) {
+                auto* val = sem->template As<sem::ValueExpression>();
+                if (TINT_LIKELY(val)) {
+                    return val;
+                }
+                // TODO(crbug.com/tint/1810): Improve error
+                builder_->Diagnostics().add_error(diag::System::Resolver,
+                                                  "required value expression, got something else",
+                                                  ast->source);
+            }
+            return static_cast<sem::ValueExpression*>(nullptr);
+        }
+    }
+
     /// @returns the resolved symbol (function, type or variable) for the given ast::Identifier or
     /// ast::TypeName cast to the given semantic type.
     /// @param node the node to retrieve
diff --git a/src/tint/resolver/side_effects_test.cc b/src/tint/resolver/side_effects_test.cc
index b0796d5..cca7741 100644
--- a/src/tint/resolver/side_effects_test.cc
+++ b/src/tint/resolver/side_effects_test.cc
@@ -82,7 +82,7 @@
     WrapInFunction(var, expr);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
-    auto* sem = Sem().Get(expr);
+    auto* sem = Sem().GetVal(expr);
     ASSERT_NE(sem, nullptr);
     EXPECT_TRUE(sem->UnwrapLoad()->Is<sem::VariableUser>());
     EXPECT_FALSE(sem->HasSideEffects());
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index 69754da..8fa6d64 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -1141,9 +1141,11 @@
             return true;
         };
 
-        auto* var_user = sem_.Get(ident)->Unwrap()->As<sem::VariableUser>();
-        auto* sem = var_user->Variable();
         auto* node = CreateNode({NameFor(ident->identifier), "_ident_expr"}, ident);
+        auto* sem_ident = sem_.GetVal(ident);
+        TINT_ASSERT(Resolver, sem_ident);
+        auto* var_user = sem_ident->Unwrap()->As<sem::VariableUser>();
+        auto* sem = var_user->Variable();
         return Switch(
             sem,
 
@@ -1357,7 +1359,7 @@
             expr,
 
             [&](const ast::IdentifierExpression* i) {
-                auto* sem = sem_.Get(i)->UnwrapLoad()->As<sem::VariableUser>();
+                auto* sem = sem_.GetVal(i)->UnwrapLoad()->As<sem::VariableUser>();
                 if (sem->Variable()->Is<sem::GlobalVariable>()) {
                     return std::make_pair(cf, current_function_->may_be_non_uniform);
                 } else if (auto* local = sem->Variable()->As<sem::LocalVariable>()) {
@@ -1452,7 +1454,7 @@
 
             // For pointer arguments, create an additional node to represent the contents of that
             // pointer prior to the function call.
-            auto* sem_arg = sem_.Get(call->args[i]);
+            auto* sem_arg = sem_.GetVal(call->args[i]);
             if (sem_arg->Type()->Is<type::Pointer>()) {
                 auto* arg_contents =
                     CreateNode({name, "_ptrarg_", std::to_string(i), "_contents"}, call);
@@ -1581,7 +1583,7 @@
 
                 // Capture the effects of other call parameters on the contents of this parameter
                 // after the call returns.
-                auto* sem_arg = sem_.Get(call->args[i]);
+                auto* sem_arg = sem_.GetVal(call->args[i]);
                 if (sem_arg->Type()->Is<type::Pointer>()) {
                     auto* ptr_result =
                         CreateNode({name, "_ptrarg_", std::to_string(i), "_result"}, call);
@@ -1762,7 +1764,7 @@
         Switch(
             non_uniform_source->ast,
             [&](const ast::IdentifierExpression* ident) {
-                auto* var = sem_.Get(ident)->UnwrapLoad()->As<sem::VariableUser>()->Variable();
+                auto* var = sem_.GetVal(ident)->UnwrapLoad()->As<sem::VariableUser>()->Variable();
                 std::ostringstream ss;
                 if (auto* param = var->As<sem::Parameter>()) {
                     auto* func = param->Owner()->As<sem::Function>();
@@ -1792,7 +1794,7 @@
                     }
                     case Node::kFunctionCallArgumentContents: {
                         auto* arg = c->args[non_uniform_source->arg_index];
-                        auto* var = sem_.Get(arg)->RootIdentifier();
+                        auto* var = sem_.GetVal(arg)->RootIdentifier();
                         std::ostringstream ss;
                         ss << "reading from " << var_type(var) << "'" << NameFor(var->Declaration())
                            << "' may result in a non-uniform value";
diff --git a/src/tint/resolver/variable_test.cc b/src/tint/resolver/variable_test.cc
index 54629e3..8dcff0f 100644
--- a/src/tint/resolver/variable_test.cc
+++ b/src/tint/resolver/variable_test.cc
@@ -250,7 +250,7 @@
     EXPECT_EQ(local->Shadows(), global);
 
     auto* user_v =
-        Sem().Get(local->Declaration()->initializer)->UnwrapLoad()->As<sem::VariableUser>();
+        Sem().GetVal(local->Declaration()->initializer)->UnwrapLoad()->As<sem::VariableUser>();
     ASSERT_NE(user_v, nullptr);
     EXPECT_EQ(user_v->Variable(), global);
 }
@@ -300,7 +300,7 @@
     EXPECT_EQ(local_y->Shadows(), local_x);
 
     auto* user_y =
-        Sem().Get(local_y->Declaration()->initializer)->UnwrapLoad()->As<sem::VariableUser>();
+        Sem().GetVal(local_y->Declaration()->initializer)->UnwrapLoad()->As<sem::VariableUser>();
     ASSERT_NE(user_y, nullptr);
     EXPECT_EQ(user_y->Variable(), local_x);
 }
@@ -370,7 +370,7 @@
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 
-    auto* param = Sem().Get<sem::Parameter>(p);
+    auto* param = Sem().Get(p);
     auto* local = Sem().Get<sem::LocalVariable>(v);
 
     ASSERT_NE(param, nullptr);
@@ -566,7 +566,7 @@
     EXPECT_EQ(local->Shadows(), global);
 
     auto* user =
-        Sem().Get(local->Declaration()->initializer)->UnwrapLoad()->As<sem::VariableUser>();
+        Sem().GetVal(local->Declaration()->initializer)->UnwrapLoad()->As<sem::VariableUser>();
     ASSERT_NE(user, nullptr);
     EXPECT_EQ(user->Variable(), global);
 }
@@ -616,7 +616,7 @@
     EXPECT_EQ(local_l->Shadows(), local_v);
 
     auto* user =
-        Sem().Get(local_l->Declaration()->initializer)->UnwrapLoad()->As<sem::VariableUser>();
+        Sem().GetVal(local_l->Declaration()->initializer)->UnwrapLoad()->As<sem::VariableUser>();
     ASSERT_NE(user, nullptr);
     EXPECT_EQ(user->Variable(), local_v);
 }
diff --git a/src/tint/sem/expression.cc b/src/tint/sem/expression.cc
new file mode 100644
index 0000000..f491ab4
--- /dev/null
+++ b/src/tint/sem/expression.cc
@@ -0,0 +1,26 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/sem/expression.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::Expression);
+
+namespace tint::sem {
+
+Expression::Expression(const ast::Expression* declaration, const Statement* statement)
+    : declaration_(declaration), statement_(statement) {}
+
+Expression::~Expression() = default;
+
+}  // namespace tint::sem
diff --git a/src/tint/sem/expression.h b/src/tint/sem/expression.h
new file mode 100644
index 0000000..884ff4d
--- /dev/null
+++ b/src/tint/sem/expression.h
@@ -0,0 +1,55 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// 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_
+
+#include "src/tint/ast/expression.h"
+#include "src/tint/sem/node.h"
+
+// Forward declarations
+namespace tint::sem {
+class Statement;
+}  // namespace tint::sem
+
+namespace tint::sem {
+
+/// Expression holds the semantic information for expression nodes.
+class Expression : public Castable<Expression, Node> {
+  public:
+    /// Constructor
+    /// @param declaration the AST node
+    /// @param statement the statement that owns this expression
+    Expression(const ast::Expression* declaration, const Statement* statement);
+
+    /// Destructor
+    ~Expression() override;
+
+    /// @returns the AST node
+    const ast::Expression* Declaration() const { return declaration_; }
+
+    /// @return the statement that owns this expression
+    const Statement* Stmt() const { return statement_; }
+
+  protected:
+    /// The AST expression node for this semantic expression
+    const ast::Expression* const declaration_;
+
+  private:
+    const Statement* const statement_;
+};
+
+}  // namespace tint::sem
+
+#endif  // SRC_TINT_SEM_EXPRESSION_H_
diff --git a/src/tint/sem/info.h b/src/tint/sem/info.h
index 43a158e..2ce46f5 100644
--- a/src/tint/sem/info.h
+++ b/src/tint/sem/info.h
@@ -30,6 +30,7 @@
 // Forward declarations
 namespace tint::sem {
 class Module;
+class ValueExpression;
 }  // namespace tint::sem
 namespace tint::type {
 class Node;
@@ -88,6 +89,14 @@
         return nullptr;
     }
 
+    /// Convenience function that's an alias for Get<ValueExpression>()
+    /// @param ast_node the AST node
+    /// @returns a pointer to the semantic node if found, otherwise nullptr
+    template <typename AST>
+    const sem::ValueExpression* GetVal(const AST* ast_node) const {
+        return Get<ValueExpression>(ast_node);
+    }
+
     /// Add registers the semantic node `sem_node` for the AST node `ast_node`.
     /// @param ast_node the AST node
     /// @param sem_node the semantic node
diff --git a/src/tint/sem/type_mappings.h b/src/tint/sem/type_mappings.h
index 40e287c..419a9c2 100644
--- a/src/tint/sem/type_mappings.h
+++ b/src/tint/sem/type_mappings.h
@@ -22,13 +22,19 @@
 class CastableBase;
 }  // namespace tint
 namespace tint::ast {
+class AccessorExpression;
 class Array;
+class BinaryExpression;
+class BitcastExpression;
+class CallExpression;
 class Expression;
 class ForLoopStatement;
 class Function;
 class IfStatement;
+class LiteralExpression;
 class Node;
 class Override;
+class PhonyExpression;
 class Statement;
 class Struct;
 class StructMember;
@@ -37,8 +43,10 @@
 class TypeDecl;
 class Variable;
 class WhileStatement;
+class UnaryOpExpression;
 }  // namespace tint::ast
 namespace tint::sem {
+class Expression;
 class ForLoopStatement;
 class Function;
 class GlobalVariable;
@@ -77,17 +85,24 @@
     SwitchStatement* operator()(ast::SwitchStatement*);
     type::Type* operator()(ast::Type*);
     type::Type* operator()(ast::TypeDecl*);
-    ValueExpression* operator()(ast::Expression*);
+    Expression* operator()(ast::Expression*);
+    ValueExpression* operator()(ast::AccessorExpression*);
+    ValueExpression* operator()(ast::CallExpression*);
+    ValueExpression* operator()(ast::BinaryExpression*);
+    ValueExpression* operator()(ast::BitcastExpression*);
+    ValueExpression* operator()(ast::LiteralExpression*);
+    ValueExpression* operator()(ast::PhonyExpression*);
+    ValueExpression* operator()(ast::UnaryOpExpression*);
     Variable* operator()(ast::Variable*);
     WhileStatement* operator()(ast::WhileStatement*);
     //! @endcond
 };
 
 /// SemanticNodeTypeFor resolves to the appropriate sem::Node type for the
-/// AST or type node `AST_OR_TYPE`.
-template <typename AST_OR_TYPE>
+/// AST node `AST`.
+template <typename AST>
 using SemanticNodeTypeFor =
-    typename std::remove_pointer<decltype(TypeMappings()(std::declval<AST_OR_TYPE*>()))>::type;
+    typename std::remove_pointer<decltype(TypeMappings()(std::declval<AST*>()))>::type;
 
 }  // namespace tint::sem
 
diff --git a/src/tint/sem/value_expression.cc b/src/tint/sem/value_expression.cc
index de3c4cf..9fe4615 100644
--- a/src/tint/sem/value_expression.cc
+++ b/src/tint/sem/value_expression.cc
@@ -30,11 +30,10 @@
                                  const constant::Value* constant,
                                  bool has_side_effects,
                                  const Variable* root_ident /* = nullptr */)
-    : declaration_(declaration),
+    : Base(declaration, statement),
       root_identifier_(root_ident),
       type_(type),
       stage_(stage),
-      statement_(statement),
       constant_(std::move(constant)),
       has_side_effects_(has_side_effects) {
     TINT_ASSERT(Semantic, type_);
diff --git a/src/tint/sem/value_expression.h b/src/tint/sem/value_expression.h
index 9840c11..a6fb624 100644
--- a/src/tint/sem/value_expression.h
+++ b/src/tint/sem/value_expression.h
@@ -15,11 +15,10 @@
 #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"
 #include "src/tint/sem/behavior.h"
 #include "src/tint/sem/evaluation_stage.h"
-#include "src/tint/sem/node.h"
+#include "src/tint/sem/expression.h"
 
 // Forward declarations
 namespace tint::sem {
@@ -30,7 +29,7 @@
 namespace tint::sem {
 
 /// ValueExpression holds the semantic information for expression nodes.
-class ValueExpression : public Castable<ValueExpression, Node> {
+class ValueExpression : public Castable<ValueExpression, Expression> {
   public:
     /// Constructor
     /// @param declaration the AST node
@@ -51,18 +50,12 @@
     /// Destructor
     ~ValueExpression() override;
 
-    /// @returns the AST node
-    const ast::Expression* Declaration() const { return declaration_; }
-
     /// @return the resolved type of the expression
     const type::Type* Type() const { return type_; }
 
     /// @return the earliest evaluation stage for the expression
     EvaluationStage Stage() const { return stage_; }
 
-    /// @return the statement that owns this expression
-    const Statement* Stmt() const { return statement_; }
-
     /// @return the constant value of this expression
     const constant::Value* ConstantValue() const { return constant_; }
 
@@ -92,15 +85,12 @@
     const ValueExpression* Unwrap() const;
 
   protected:
-    /// The AST expression node for this semantic expression
-    const ast::Expression* const declaration_;
     /// The root identifier for this semantic expression, or nullptr
     const Variable* root_identifier_;
 
   private:
     const type::Type* const type_;
     const EvaluationStage stage_;
-    const Statement* const statement_;
     const constant::Value* const constant_;
     sem::Behaviors behaviors_{sem::Behavior::kNext};
     const bool has_side_effects_;
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 7d7dc3c..c3c8245 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -74,7 +74,7 @@
     explicit OffsetExpr(const ast::Expression* e) : expr(e) {}
 
     const ast::Expression* Build(CloneContext& ctx) const override {
-        auto* type = ctx.src->Sem().Get(expr)->Type()->UnwrapRef();
+        auto* type = ctx.src->Sem().GetVal(expr)->Type()->UnwrapRef();
         auto* res = ctx.Clone(expr);
         if (!type->Is<type::U32>()) {
             res = ctx.dst->Construct<u32>(res);
@@ -881,7 +881,7 @@
     for (auto* node : src->ASTNodes().Objects()) {
         if (auto* ident = node->As<ast::IdentifierExpression>()) {
             // X
-            if (auto* sem_ident = sem.Get(ident)) {
+            if (auto* sem_ident = sem.GetVal(ident)) {
                 if (auto* var = sem_ident->UnwrapLoad()->As<sem::VariableUser>()) {
                     if (var->Variable()->AddressSpace() == type::AddressSpace::kStorage ||
                         var->Variable()->AddressSpace() == type::AddressSpace::kUniform) {
diff --git a/src/tint/transform/demote_to_helper.cc b/src/tint/transform/demote_to_helper.cc
index 3763f7c..128894d 100644
--- a/src/tint/transform/demote_to_helper.cc
+++ b/src/tint/transform/demote_to_helper.cc
@@ -123,7 +123,7 @@
                 }
 
                 // Skip writes to invocation-private address spaces.
-                auto* ref = sem.Get(assign->lhs)->Type()->As<type::Reference>();
+                auto* ref = sem.GetVal(assign->lhs)->Type()->As<type::Reference>();
                 switch (ref->AddressSpace()) {
                     case type::AddressSpace::kStorage:
                         // Need to mask these.
diff --git a/src/tint/transform/direct_variable_access.cc b/src/tint/transform/direct_variable_access.cc
index 9d3d37b..92af018 100644
--- a/src/tint/transform/direct_variable_access.cc
+++ b/src/tint/transform/direct_variable_access.cc
@@ -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::ValueExpression>(node)) {
+            if (auto* expr = sem.GetVal(node)) {
                 AppendAccessChain(expr);
             }
         }
@@ -464,7 +464,7 @@
                     [&](const ast::Let*) {
                         if (variable->Type()->Is<type::Pointer>()) {
                             // variable is a pointer-let.
-                            auto* init = sem.Get(variable->Declaration()->initializer);
+                            auto* init = sem.GetVal(variable->Declaration()->initializer);
                             // Note: We do not use take_chain() here, as we need to preserve the
                             // AccessChain on the let's initializer, as the let needs its
                             // initializer updated, and the let may be used multiple times. Instead
@@ -498,7 +498,7 @@
                     // If this is a '&' or '*', simply move the chain to the unary op expression.
                     if (unary->op == ast::UnaryOp::kAddressOf ||
                         unary->op == ast::UnaryOp::kIndirection) {
-                        take_chain(sem.Get(unary->expr));
+                        take_chain(sem.GetVal(unary->expr));
                     }
                 }
             });
@@ -990,7 +990,7 @@
                 return nullptr;  // Just clone the expression.
             }
 
-            auto* expr = sem.Get(ast_expr);
+            auto* expr = sem.GetVal(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 253eed4..f8b4111 100644
--- a/src/tint/transform/expand_compound_assignment.cc
+++ b/src/tint/transform/expand_compound_assignment.cc
@@ -86,7 +86,10 @@
 
         // Helper function that returns `true` if the type of `expr` is a vector.
         auto is_vec = [&](const ast::Expression* expr) {
-            return ctx.src->Sem().Get(expr)->Type()->UnwrapRef()->Is<type::Vector>();
+            if (auto* val_expr = ctx.src->Sem().GetVal(expr)) {
+                return val_expr->Type()->UnwrapRef()->Is<type::Vector>();
+            }
+            return false;
         };
 
         // Hoist the LHS expression subtree into local constants to produce a new
diff --git a/src/tint/transform/first_index_offset.cc b/src/tint/transform/first_index_offset.cc
index 2359ae9..afc946d 100644
--- a/src/tint/transform/first_index_offset.cc
+++ b/src/tint/transform/first_index_offset.cc
@@ -138,7 +138,7 @@
 
         // Fix up all references to the builtins with the offsets
         ctx.ReplaceAll([=, &ctx](const ast::Expression* expr) -> const ast::Expression* {
-            if (auto* sem = ctx.src->Sem().Get(expr)) {
+            if (auto* sem = ctx.src->Sem().GetVal(expr)) {
                 if (auto* user = sem->UnwrapLoad()->As<sem::VariableUser>()) {
                     auto it = builtin_vars.find(user->Variable());
                     if (it != builtin_vars.end()) {
diff --git a/src/tint/transform/localize_struct_array_assignment.cc b/src/tint/transform/localize_struct_array_assignment.cc
index 0d40ccf..5f70731 100644
--- a/src/tint/transform/localize_struct_array_assignment.cc
+++ b/src/tint/transform/localize_struct_array_assignment.cc
@@ -164,7 +164,7 @@
         ast::TraverseExpressions(
             expr, b.Diagnostics(), [&](const ast::IndexAccessorExpression* ia) {
                 // Indexing using a runtime value?
-                auto* idx_sem = src->Sem().Get(ia->index);
+                auto* idx_sem = src->Sem().GetVal(ia->index);
                 if (!idx_sem->ConstantValue()) {
                     // Indexing a member access expr?
                     if (auto* ma = ia->object->As<ast::MemberAccessorExpression>()) {
@@ -186,7 +186,7 @@
     // See https://www.w3.org/TR/WGSL/#originating-variable-section
     std::pair<const type::Type*, type::AddressSpace> GetOriginatingTypeAndAddressSpace(
         const ast::AssignmentStatement* assign_stmt) {
-        auto* root_ident = src->Sem().Get(assign_stmt->lhs)->RootIdentifier();
+        auto* root_ident = src->Sem().GetVal(assign_stmt->lhs)->RootIdentifier();
         if (TINT_UNLIKELY(!root_ident)) {
             TINT_ICE(Transform, b.Diagnostics())
                 << "Unable to determine originating variable for lhs of assignment "
diff --git a/src/tint/transform/multiplanar_external_texture.cc b/src/tint/transform/multiplanar_external_texture.cc
index 652023e..6e68428 100644
--- a/src/tint/transform/multiplanar_external_texture.cc
+++ b/src/tint/transform/multiplanar_external_texture.cc
@@ -198,7 +198,7 @@
                 builtin->Parameters()[0]->Type()->Is<type::ExternalTexture>() &&
                 builtin->Type() != sem::BuiltinType::kTextureDimensions) {
                 if (auto* var_user =
-                        sem.Get(expr->args[0])->UnwrapLoad()->As<sem::VariableUser>()) {
+                        sem.GetVal(expr->args[0])->UnwrapLoad()->As<sem::VariableUser>()) {
                     auto it = new_binding_symbols.find(var_user->Variable());
                     if (it == new_binding_symbols.end()) {
                         // If valid new binding locations were not provided earlier, we would have
@@ -223,7 +223,7 @@
                 // texture_external parameter. These need to be expanded out to multiple plane
                 // textures and the texture parameters structure.
                 for (auto* arg : expr->args) {
-                    if (auto* var_user = sem.Get(arg)->UnwrapLoad()->As<sem::VariableUser>()) {
+                    if (auto* var_user = sem.GetVal(arg)->UnwrapLoad()->As<sem::VariableUser>()) {
                         // Check if a parameter is a texture_external by trying to find
                         // it in the transform state.
                         auto it = new_binding_symbols.find(var_user->Variable());
diff --git a/src/tint/transform/packed_vec3.cc b/src/tint/transform/packed_vec3.cc
index 26cad63..c553a32 100644
--- a/src/tint/transform/packed_vec3.cc
+++ b/src/tint/transform/packed_vec3.cc
@@ -109,7 +109,7 @@
                         if (unary->op == ast::UnaryOp::kAddressOf ||
                             unary->op == ast::UnaryOp::kIndirection) {
                             // Memory access on the packed vector. Track these.
-                            auto* inner = sem.Get(unary->expr);
+                            auto* inner = sem.GetVal(unary->expr);
                             if (refs.Remove(inner)) {
                                 refs.Add(expr);
                             }
@@ -121,7 +121,7 @@
                 [&](const sem::Statement* e) {
                     if (auto* assign = e->Declaration()->As<ast::AssignmentStatement>()) {
                         // We don't want to cast packed_vectors if they're being assigned to.
-                        refs.Remove(sem.Get(assign->lhs));
+                        refs.Remove(sem.GetVal(assign->lhs));
                     }
                 });
         }
diff --git a/src/tint/transform/preserve_padding.cc b/src/tint/transform/preserve_padding.cc
index c5c0d1b..1946ca5 100644
--- a/src/tint/transform/preserve_padding.cc
+++ b/src/tint/transform/preserve_padding.cc
@@ -48,7 +48,7 @@
             Switch(
                 node,  //
                 [&](const ast::AssignmentStatement* assign) {
-                    auto* ty = sem.Get(assign->lhs)->Type();
+                    auto* ty = sem.GetVal(assign->lhs)->Type();
                     if (assign->lhs->Is<ast::PhonyExpression>()) {
                         // Ignore phony assignment.
                         return;
@@ -80,7 +80,7 @@
             if (!assignments_to_transform.count(assign)) {
                 return nullptr;
             }
-            auto* ty = sem.Get(assign->lhs)->Type()->UnwrapRef();
+            auto* ty = sem.GetVal(assign->lhs)->Type()->UnwrapRef();
             return MakeAssignment(ty, ctx.Clone(assign->lhs), ctx.Clone(assign->rhs));
         });
 
diff --git a/src/tint/transform/promote_initializers_to_let.cc b/src/tint/transform/promote_initializers_to_let.cc
index 0ad37de..20f7d32 100644
--- a/src/tint/transform/promote_initializers_to_let.cc
+++ b/src/tint/transform/promote_initializers_to_let.cc
@@ -83,7 +83,7 @@
 
     // 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::ValueExpression>(node)) {
+        if (auto* sem = src->Sem().GetVal(node)) {
             auto* stmt = sem->Stmt();
             if (!stmt) {
                 // Expression is outside of a statement. This usually means the expression is part
@@ -118,7 +118,7 @@
     // After walking the full AST, const_chains only contains the outer-most constant expressions.
     // Check if any of these need hoisting, and append those to to_hoist.
     for (auto* expr : const_chains) {
-        if (auto* sem = src->Sem().Get(expr); should_hoist(sem)) {
+        if (auto* sem = src->Sem().GetVal(expr); should_hoist(sem)) {
             to_hoist.Push(sem);
         }
     }
diff --git a/src/tint/transform/promote_side_effects_to_decl.cc b/src/tint/transform/promote_side_effects_to_decl.cc
index d4ad5d7..15f4674 100644
--- a/src/tint/transform/promote_side_effects_to_decl.cc
+++ b/src/tint/transform/promote_side_effects_to_decl.cc
@@ -66,9 +66,8 @@
 
     HoistToDeclBefore hoist_to_decl_before(ctx);
     for (auto* node : ctx.src->ASTNodes().Objects()) {
-        if (auto* expr = node->As<ast::Expression>()) {
-            auto* sem_expr = src->Sem().Get(expr);
-            if (!sem_expr || !sem_expr->HasSideEffects()) {
+        if (auto* sem_expr = src->Sem().GetVal(node)) {
+            if (!sem_expr->HasSideEffects()) {
                 continue;
             }
 
@@ -278,7 +277,7 @@
                 return true;
             },
             [&](const ast::IdentifierExpression* e) {
-                if (auto* sem_e = sem.Get(e)) {
+                if (auto* sem_e = sem.GetVal(e)) {
                     if (auto* var_user = sem_e->UnwrapLoad()->As<sem::VariableUser>()) {
                         // Don't hoist constants.
                         if (var_user->ConstantValue()) {
@@ -417,8 +416,8 @@
 
     // Returns true if `binary_expr` should be decomposed for short-circuit eval.
     bool IsLogicalWithSideEffects(const ast::BinaryExpression* binary_expr) {
-        return binary_expr->IsLogical() && (sem.Get(binary_expr->lhs)->HasSideEffects() ||
-                                            sem.Get(binary_expr->rhs)->HasSideEffects());
+        return binary_expr->IsLogical() && (sem.GetVal(binary_expr->lhs)->HasSideEffects() ||
+                                            sem.GetVal(binary_expr->rhs)->HasSideEffects());
     }
 
     // Recursive function used to decompose an expression for short-circuit eval.
@@ -560,7 +559,8 @@
         return Switch(
             stmt,
             [&](const ast::AssignmentStatement* s) -> const ast::Statement* {
-                if (!sem.Get(s->lhs)->HasSideEffects() && !sem.Get(s->rhs)->HasSideEffects()) {
+                if (!sem.GetVal(s->lhs)->HasSideEffects() &&
+                    !sem.GetVal(s->rhs)->HasSideEffects()) {
                     return nullptr;
                 }
                 // rhs before lhs
@@ -580,7 +580,7 @@
                 return ctx.CloneWithoutTransform(s);
             },
             [&](const ast::ForLoopStatement* s) -> const ast::Statement* {
-                if (!s->condition || !sem.Get(s->condition)->HasSideEffects()) {
+                if (!s->condition || !sem.GetVal(s->condition)->HasSideEffects()) {
                     return nullptr;
                 }
                 tint::utils::Vector<const ast::Statement*, 8> stmts;
@@ -589,7 +589,7 @@
                 return ctx.CloneWithoutTransform(s);
             },
             [&](const ast::WhileStatement* s) -> const ast::Statement* {
-                if (!sem.Get(s->condition)->HasSideEffects()) {
+                if (!sem.GetVal(s->condition)->HasSideEffects()) {
                     return nullptr;
                 }
                 tint::utils::Vector<const ast::Statement*, 8> stmts;
@@ -598,7 +598,7 @@
                 return ctx.CloneWithoutTransform(s);
             },
             [&](const ast::IfStatement* s) -> const ast::Statement* {
-                if (!sem.Get(s->condition)->HasSideEffects()) {
+                if (!sem.GetVal(s->condition)->HasSideEffects()) {
                     return nullptr;
                 }
                 tint::utils::Vector<const ast::Statement*, 8> stmts;
@@ -607,7 +607,7 @@
                 return ctx.CloneWithoutTransform(s);
             },
             [&](const ast::ReturnStatement* s) -> const ast::Statement* {
-                if (!s->value || !sem.Get(s->value)->HasSideEffects()) {
+                if (!s->value || !sem.GetVal(s->value)->HasSideEffects()) {
                     return nullptr;
                 }
                 tint::utils::Vector<const ast::Statement*, 8> stmts;
@@ -626,7 +626,7 @@
             },
             [&](const ast::VariableDeclStatement* s) -> const ast::Statement* {
                 auto* var = s->variable;
-                if (!var->initializer || !sem.Get(var->initializer)->HasSideEffects()) {
+                if (!var->initializer || !sem.GetVal(var->initializer)->HasSideEffects()) {
                     return nullptr;
                 }
                 tint::utils::Vector<const ast::Statement*, 8> stmts;
diff --git a/src/tint/transform/remove_phonies.cc b/src/tint/transform/remove_phonies.cc
index cb2d914..3b0dabd 100644
--- a/src/tint/transform/remove_phonies.cc
+++ b/src/tint/transform/remove_phonies.cc
@@ -102,7 +102,7 @@
                     ctx.Replace(stmt, [&, side_effects] {
                         SinkSignature sig;
                         for (auto* arg : side_effects) {
-                            sig.push_back(sem.Get(arg)->Type()->UnwrapRef());
+                            sig.push_back(sem.GetVal(arg)->Type()->UnwrapRef());
                         }
                         auto sink = sinks.GetOrCreate(sig, [&] {
                             auto name = b.Symbols().New("phony_sink");
diff --git a/src/tint/transform/renamer.cc b/src/tint/transform/renamer.cc
index 4f35392..6396238 100644
--- a/src/tint/transform/renamer.cc
+++ b/src/tint/transform/renamer.cc
@@ -1286,7 +1286,7 @@
                 auto* sem = src->Sem().Get(accessor)->UnwrapLoad();
                 if (sem->Is<sem::Swizzle>()) {
                     preserved_identifiers.Add(accessor->member);
-                } else if (auto* str_expr = src->Sem().Get(accessor->object)) {
+                } else if (auto* str_expr = src->Sem().GetVal(accessor->object)) {
                     if (auto* ty = str_expr->Type()->UnwrapRef()->As<sem::Struct>()) {
                         if (ty->Declaration() == nullptr) {  // Builtin structure
                             preserved_identifiers.Add(accessor->member);
diff --git a/src/tint/transform/spirv_atomic.cc b/src/tint/transform/spirv_atomic.cc
index 07879e8..3a53e71 100644
--- a/src/tint/transform/spirv_atomic.cc
+++ b/src/tint/transform/spirv_atomic.cc
@@ -101,7 +101,7 @@
 
                     // Keep track of this expression. We'll need to modify the root identifier /
                     // structure to be atomic.
-                    atomic_expressions.Add(ctx.src->Sem().Get(args[0]));
+                    atomic_expressions.Add(ctx.src->Sem().GetVal(args[0]));
                 }
 
                 // Remove the stub from the output program
@@ -186,7 +186,7 @@
                 },
                 [&](const sem::ValueExpression* e) {
                     if (auto* unary = e->Declaration()->As<ast::UnaryOpExpression>()) {
-                        atomic_expressions.Add(ctx.src->Sem().Get(unary->expr));
+                        atomic_expressions.Add(ctx.src->Sem().GetVal(unary->expr));
                     }
                 });
         }
@@ -249,7 +249,7 @@
                 Switch(
                     vu->Stmt()->Declaration(),
                     [&](const ast::AssignmentStatement* assign) {
-                        auto* sem_lhs = ctx.src->Sem().Get(assign->lhs);
+                        auto* sem_lhs = ctx.src->Sem().GetVal(assign->lhs);
                         if (is_ref_to_atomic_var(sem_lhs)) {
                             ctx.Replace(assign, [=] {
                                 auto* lhs = ctx.CloneWithoutTransform(assign->lhs);
@@ -261,7 +261,7 @@
                             return;
                         }
 
-                        auto sem_rhs = ctx.src->Sem().Get(assign->rhs);
+                        auto sem_rhs = ctx.src->Sem().GetVal(assign->rhs);
                         if (is_ref_to_atomic_var(sem_rhs->UnwrapLoad())) {
                             ctx.Replace(assign->rhs, [=] {
                                 auto* rhs = ctx.CloneWithoutTransform(assign->rhs);
@@ -273,7 +273,7 @@
                     },
                     [&](const ast::VariableDeclStatement* decl) {
                         auto* var = decl->variable;
-                        if (auto* sem_init = ctx.src->Sem().Get(var->initializer)) {
+                        if (auto* sem_init = ctx.src->Sem().GetVal(var->initializer)) {
                             if (is_ref_to_atomic_var(sem_init->UnwrapLoad())) {
                                 ctx.Replace(var->initializer, [=] {
                                     auto* rhs = ctx.CloneWithoutTransform(var->initializer);
diff --git a/src/tint/transform/std140.cc b/src/tint/transform/std140.cc
index cde4a3f..46f71bc 100644
--- a/src/tint/transform/std140.cc
+++ b/src/tint/transform/std140.cc
@@ -494,7 +494,7 @@
     /// @returns an AccessChain if the expression is an access to a std140-forked uniform buffer,
     ///          otherwise returns a std::nullopt.
     std::optional<AccessChain> AccessChainFor(const ast::Expression* ast_expr) {
-        auto* expr = sem.Get(ast_expr);
+        auto* expr = sem.GetVal(ast_expr);
         if (!expr) {
             return std::nullopt;
         }
@@ -580,7 +580,7 @@
                                       switch (u->op) {
                                           case ast::UnaryOp::kAddressOf:
                                           case ast::UnaryOp::kIndirection:
-                                              expr = sem.Get(u->expr);
+                                              expr = sem.GetVal(u->expr);
                                               return Action::kContinue;
                                           default:
                                               TINT_ICE(Transform, b.Diagnostics())
diff --git a/src/tint/transform/unshadow.cc b/src/tint/transform/unshadow.cc
index 7c9f75c..bbb3a27 100644
--- a/src/tint/transform/unshadow.cc
+++ b/src/tint/transform/unshadow.cc
@@ -106,7 +106,7 @@
 
         ctx.ReplaceAll(
             [&](const ast::IdentifierExpression* ident) -> const tint::ast::IdentifierExpression* {
-                if (auto* sem_ident = sem.Get(ident)) {
+                if (auto* sem_ident = sem.GetVal(ident)) {
                     if (auto* user = sem_ident->UnwrapLoad()->As<sem::VariableUser>()) {
                         if (auto renamed = renamed_to.Find(user->Variable())) {
                             return b.Expr(*renamed);
diff --git a/src/tint/transform/utils/hoist_to_decl_before_test.cc b/src/tint/transform/utils/hoist_to_decl_before_test.cc
index c5dca5d..8f16675 100644
--- a/src/tint/transform/utils/hoist_to_decl_before_test.cc
+++ b/src/tint/transform/utils/hoist_to_decl_before_test.cc
@@ -108,7 +108,7 @@
     CloneContext ctx(&cloned_b, &original);
 
     HoistToDeclBefore hoistToDeclBefore(ctx);
-    auto* sem_expr = ctx.src->Sem().Get(expr);
+    auto* sem_expr = ctx.src->Sem().GetVal(expr);
     hoistToDeclBefore.Add(sem_expr, expr, HoistToDeclBefore::VariableKind::kConst);
 
     ctx.Clone();
@@ -189,7 +189,7 @@
     CloneContext ctx(&cloned_b, &original);
 
     HoistToDeclBefore hoistToDeclBefore(ctx);
-    auto* sem_expr = ctx.src->Sem().Get(expr);
+    auto* sem_expr = ctx.src->Sem().GetVal(expr);
     hoistToDeclBefore.Add(sem_expr, expr, HoistToDeclBefore::VariableKind::kVar);
 
     ctx.Clone();
@@ -233,7 +233,7 @@
     CloneContext ctx(&cloned_b, &original);
 
     HoistToDeclBefore hoistToDeclBefore(ctx);
-    auto* sem_expr = ctx.src->Sem().Get(expr);
+    auto* sem_expr = ctx.src->Sem().GetVal(expr);
     hoistToDeclBefore.Add(sem_expr, expr, HoistToDeclBefore::VariableKind::kConst);
 
     ctx.Clone();
@@ -339,7 +339,7 @@
     CloneContext ctx(&cloned_b, &original);
 
     HoistToDeclBefore hoistToDeclBefore(ctx);
-    auto* sem_expr = ctx.src->Sem().Get(expr);
+    auto* sem_expr = ctx.src->Sem().GetVal(expr);
     hoistToDeclBefore.Prepare(sem_expr);
 
     ctx.Clone();
@@ -422,7 +422,7 @@
     CloneContext ctx(&cloned_b, &original);
 
     HoistToDeclBefore hoistToDeclBefore(ctx);
-    auto* sem_expr = ctx.src->Sem().Get(expr);
+    auto* sem_expr = ctx.src->Sem().GetVal(expr);
     hoistToDeclBefore.Prepare(sem_expr);
 
     ctx.Clone();
diff --git a/src/tint/transform/var_for_dynamic_index.cc b/src/tint/transform/var_for_dynamic_index.cc
index de69d53..18f71bb 100644
--- a/src/tint/transform/var_for_dynamic_index.cc
+++ b/src/tint/transform/var_for_dynamic_index.cc
@@ -40,13 +40,13 @@
         auto* object_expr = access_expr->object;
         auto& sem = src->Sem();
 
-        if (sem.Get(index_expr)->ConstantValue()) {
+        if (sem.GetVal(index_expr)->ConstantValue()) {
             // Index expression resolves to a compile time value.
             // As this isn't a dynamic index, we can ignore this.
             return true;
         }
 
-        auto* indexed = sem.Get(object_expr);
+        auto* indexed = sem.GetVal(object_expr);
         if (!indexed->Type()->IsAnyOf<type::Array, type::Matrix>()) {
             // We only care about array and matrices.
             return true;
diff --git a/src/tint/transform/vectorize_matrix_conversions.cc b/src/tint/transform/vectorize_matrix_conversions.cc
index f22e32d..8aa3690 100644
--- a/src/tint/transform/vectorize_matrix_conversions.cc
+++ b/src/tint/transform/vectorize_matrix_conversions.cc
@@ -34,7 +34,7 @@
 
 bool ShouldRun(const Program* program) {
     for (auto* node : program->ASTNodes().Objects()) {
-        if (auto* sem = program->Sem().Get<sem::ValueExpression>(node)) {
+        if (auto* sem = program->Sem().GetVal(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/zero_init_workgroup_memory.cc b/src/tint/transform/zero_init_workgroup_memory.cc
index 61d7a4b..c7e1b8a 100644
--- a/src/tint/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/transform/zero_init_workgroup_memory.cc
@@ -403,7 +403,7 @@
             if (!expr) {
                 continue;
             }
-            auto* sem = ctx.src->Sem().Get(expr);
+            auto* sem = ctx.src->Sem().GetVal(expr);
             if (auto* c = sem->ConstantValue()) {
                 workgroup_size_const *= c->ValueAs<AInt>();
                 continue;
diff --git a/src/tint/writer/append_vector.cc b/src/tint/writer/append_vector.cc
index c39c619..40222c0 100644
--- a/src/tint/writer/append_vector.cc
+++ b/src/tint/writer/append_vector.cc
@@ -75,8 +75,8 @@
                               const ast::Expression* scalar_ast) {
     uint32_t packed_size;
     const type::Type* packed_el_sem_ty;
-    auto* vector_sem = b->Sem().Get(vector_ast);
-    auto* scalar_sem = b->Sem().Get(scalar_ast);
+    auto* vector_sem = b->Sem().GetVal(vector_ast);
+    auto* scalar_sem = b->Sem().GetVal(scalar_ast);
     auto* vector_ty = vector_sem->Type()->UnwrapRef();
     if (auto* vec = vector_ty->As<type::Vector>()) {
         packed_size = vec->Width() + 1;
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 8669550..1e2279a 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -1814,7 +1814,7 @@
 }
 
 bool GeneratorImpl::EmitExpression(std::ostream& out, const ast::Expression* expr) {
-    if (auto* sem = builder_.Sem().Get(expr)) {
+    if (auto* sem = builder_.Sem().GetVal(expr)) {
         if (auto* constant = sem->ConstantValue()) {
             return EmitConstant(out, constant);
         }
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 5b0b012..df0f75b 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -658,8 +658,8 @@
         // with at least one dynamic index
         if (auto* lhs_sub_access = lhs_access->object->As<ast::IndexAccessorExpression>()) {
             if (auto* mat = TypeOf(lhs_sub_access->object)->UnwrapRef()->As<type::Matrix>()) {
-                auto* rhs_row_idx_sem = builder_.Sem().Get(lhs_access->index);
-                auto* rhs_col_idx_sem = builder_.Sem().Get(lhs_sub_access->index);
+                auto* rhs_row_idx_sem = builder_.Sem().GetVal(lhs_access->index);
+                auto* rhs_col_idx_sem = builder_.Sem().GetVal(lhs_sub_access->index);
                 if (!rhs_row_idx_sem->ConstantValue() || !rhs_col_idx_sem->ConstantValue()) {
                     return EmitDynamicMatrixScalarAssignment(stmt, mat);
                 }
@@ -669,7 +669,7 @@
         // with dynamic indices
         const auto* lhs_access_type = TypeOf(lhs_access->object)->UnwrapRef();
         if (auto* mat = lhs_access_type->As<type::Matrix>()) {
-            auto* lhs_index_sem = builder_.Sem().Get(lhs_access->index);
+            auto* lhs_index_sem = builder_.Sem().GetVal(lhs_access->index);
             if (!lhs_index_sem->ConstantValue()) {
                 return EmitDynamicMatrixVectorAssignment(stmt, mat);
             }
@@ -677,7 +677,7 @@
         // BUG(crbug.com/tint/534): work around assignment to vectors with dynamic
         // indices
         if (auto* vec = lhs_access_type->As<type::Vector>()) {
-            auto* rhs_sem = builder_.Sem().Get(lhs_access->index);
+            auto* rhs_sem = builder_.Sem().GetVal(lhs_access->index);
             if (!rhs_sem->ConstantValue()) {
                 return EmitDynamicVectorAssignment(stmt, vec);
             }
@@ -1109,7 +1109,7 @@
     const ast::CallExpression* expr,
     const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
     const auto& args = expr->args;
-    auto* offset_arg = builder_.Sem().Get(args[1]);
+    auto* offset_arg = builder_.Sem().GetVal(args[1]);
 
     // offset in bytes
     uint32_t scalar_offset_bytes = 0;
@@ -2790,7 +2790,7 @@
 }
 
 bool GeneratorImpl::EmitExpression(std::ostream& out, const ast::Expression* expr) {
-    if (auto* sem = builder_.Sem().Get(expr)) {
+    if (auto* sem = builder_.Sem().GetVal(expr)) {
         if (auto* constant = sem->ConstantValue()) {
             bool is_variable_initializer = false;
             if (auto* stmt = sem->Stmt()) {
@@ -3864,7 +3864,7 @@
 
     // Emit the switch condition as-is if it has side-effects (e.g.
     // function call). Note that we can ignore the result of the expression (if any).
-    if (auto* sem_cond = builder_.Sem().Get(stmt->condition); sem_cond->HasSideEffects()) {
+    if (auto* sem_cond = builder_.Sem().GetVal(stmt->condition); sem_cond->HasSideEffects()) {
         auto out = line();
         if (!EmitExpression(out, stmt->condition)) {
             return false;
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index a1ce613..a57b423 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -733,7 +733,7 @@
         }
 
         case sem::BuiltinType::kLength: {
-            auto* sem = builder_.Sem().Get(expr->args[0]);
+            auto* sem = builder_.Sem().GetVal(expr->args[0]);
             if (sem->Type()->UnwrapRef()->is_scalar()) {
                 // Emulate scalar overload using fabs(x).
                 name = "fabs";
@@ -742,7 +742,7 @@
         }
 
         case sem::BuiltinType::kDistance: {
-            auto* sem = builder_.Sem().Get(expr->args[0]);
+            auto* sem = builder_.Sem().GetVal(expr->args[0]);
             if (sem->Type()->UnwrapRef()->is_scalar()) {
                 // Emulate scalar overload using fabs(x - y);
                 out << "fabs";
@@ -1827,7 +1827,7 @@
 }
 
 bool GeneratorImpl::EmitExpression(std::ostream& out, const ast::Expression* expr) {
-    if (auto* sem = builder_.Sem().Get(expr)) {
+    if (auto* sem = builder_.Sem().GetVal(expr)) {
         if (auto* constant = sem->ConstantValue()) {
             return EmitConstant(out, constant);
         }
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index e938396..c8f32bd 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -424,7 +424,7 @@
 
 bool Builder::GenerateAssignStatement(const ast::AssignmentStatement* assign) {
     if (assign->lhs->Is<ast::PhonyExpression>()) {
-        if (builder_.Sem().Get(assign->rhs)->ConstantValue()) {
+        if (builder_.Sem().GetVal(assign->rhs)->ConstantValue()) {
             // RHS of phony assignment is constant.
             // Constants can't have side-effects, so just drop this.
             return true;
@@ -558,9 +558,11 @@
     return true;
 }
 
-uint32_t Builder::GenerateExpression(const sem::ValueExpression* expr) {
-    if (auto* constant = expr->ConstantValue()) {
-        return GenerateConstantIfNeeded(constant);
+uint32_t Builder::GenerateExpression(const sem::Expression* expr) {
+    if (auto* val_expr = expr->As<sem::ValueExpression>()) {
+        if (auto* constant = val_expr->ConstantValue()) {
+            return GenerateConstantIfNeeded(constant);
+        }
     }
     if (auto* load = expr->As<sem::Load>()) {
         auto ref_id = GenerateExpression(load->Reference());
@@ -909,7 +911,7 @@
     auto extract_id = std::get<uint32_t>(extract);
 
     // If the index is compile-time constant, we use OpCompositeExtract.
-    auto* idx = builder_.Sem().Get(expr->index);
+    auto* idx = builder_.Sem().GetVal(expr->index);
     if (auto idx_constval = idx->ConstantValue()) {
         if (!push_function_inst(spv::Op::OpCompositeExtract,
                                 {
@@ -1140,8 +1142,7 @@
 }
 
 uint32_t Builder::GenerateIdentifierExpression(const ast::IdentifierExpression* expr) {
-    auto* sem = builder_.Sem().Get(expr);
-    if (sem) {
+    if (auto* sem = builder_.Sem().GetVal(expr); sem) {
         if (auto* user = sem->UnwrapLoad()->As<sem::VariableUser>()) {
             return LookupVariableID(user->Variable());
         }
@@ -1231,7 +1232,7 @@
 
 uint32_t Builder::GenerateInitializerExpression(const ast::Variable* var,
                                                 const ast::Expression* expr) {
-    if (auto* sem = builder_.Sem().Get(expr)) {
+    if (auto* sem = builder_.Sem().GetVal(expr)) {
         if (auto constant = sem->ConstantValue()) {
             return GenerateConstantIfNeeded(constant);
         }
diff --git a/src/tint/writer/spirv/builder.h b/src/tint/writer/spirv/builder.h
index e468a31..6f9bf5d 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::ValueExpression* expr);
+    uint32_t GenerateExpression(const sem::Expression* expr);
     /// Generates an expression
     /// @param expr the expression to generate
     /// @returns the resulting ID of the expression or 0 on error