Replace Expression::(Is|As)* with Castable

Change-Id: I6ab98ed8b198f1b3b42ce1f09a6c4f992d65fe95
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/34316
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/ast/array_accessor_expression.cc b/src/ast/array_accessor_expression.cc
index 1891f4f..ded7716 100644
--- a/src/ast/array_accessor_expression.cc
+++ b/src/ast/array_accessor_expression.cc
@@ -33,10 +33,6 @@
 
 ArrayAccessorExpression::~ArrayAccessorExpression() = default;
 
-bool ArrayAccessorExpression::IsArrayAccessor() const {
-  return true;
-}
-
 bool ArrayAccessorExpression::IsValid() const {
   if (array_ == nullptr || !array_->IsValid())
     return false;
diff --git a/src/ast/array_accessor_expression.h b/src/ast/array_accessor_expression.h
index f7e5192..0f18d5e 100644
--- a/src/ast/array_accessor_expression.h
+++ b/src/ast/array_accessor_expression.h
@@ -57,9 +57,6 @@
   /// @returns the index expression
   Expression* idx_expr() const { return idx_expr_; }
 
-  /// @returns true if this is an array accessor expression
-  bool IsArrayAccessor() const override;
-
   /// @returns true if the node is valid
   bool IsValid() const override;
 
diff --git a/src/ast/array_accessor_expression_test.cc b/src/ast/array_accessor_expression_test.cc
index 92b38fa..e49ba8e 100644
--- a/src/ast/array_accessor_expression_test.cc
+++ b/src/ast/array_accessor_expression_test.cc
@@ -44,7 +44,7 @@
 
 TEST_F(ArrayAccessorExpressionTest, IsArrayAccessor) {
   ArrayAccessorExpression exp;
-  EXPECT_TRUE(exp.IsArrayAccessor());
+  EXPECT_TRUE(exp.Is<ast::ArrayAccessorExpression>());
 }
 
 TEST_F(ArrayAccessorExpressionTest, IsValid) {
diff --git a/src/ast/binary_expression.cc b/src/ast/binary_expression.cc
index d43caeb..c14f538 100644
--- a/src/ast/binary_expression.cc
+++ b/src/ast/binary_expression.cc
@@ -34,10 +34,6 @@
 
 BinaryExpression::~BinaryExpression() = default;
 
-bool BinaryExpression::IsBinary() const {
-  return true;
-}
-
 bool BinaryExpression::IsValid() const {
   if (lhs_ == nullptr || !lhs_->IsValid()) {
     return false;
diff --git a/src/ast/binary_expression.h b/src/ast/binary_expression.h
index e96d8da..1431f18 100644
--- a/src/ast/binary_expression.h
+++ b/src/ast/binary_expression.h
@@ -125,9 +125,6 @@
   /// @returns the right side expression
   Expression* rhs() const { return rhs_; }
 
-  /// @returns true if this is a op expression
-  bool IsBinary() const override;
-
   /// @returns true if the node is valid
   bool IsValid() const override;
 
diff --git a/src/ast/binary_expression_test.cc b/src/ast/binary_expression_test.cc
index 7727cbd..d056ebc 100644
--- a/src/ast/binary_expression_test.cc
+++ b/src/ast/binary_expression_test.cc
@@ -48,7 +48,7 @@
 
 TEST_F(BinaryExpressionTest, IsBinaryal) {
   BinaryExpression r;
-  EXPECT_TRUE(r.IsBinary());
+  EXPECT_TRUE(r.Is<ast::BinaryExpression>());
 }
 
 TEST_F(BinaryExpressionTest, IsValid) {
diff --git a/src/ast/bitcast_expression.cc b/src/ast/bitcast_expression.cc
index 6d52a2b..4ebfc53 100644
--- a/src/ast/bitcast_expression.cc
+++ b/src/ast/bitcast_expression.cc
@@ -30,10 +30,6 @@
 BitcastExpression::BitcastExpression(BitcastExpression&&) = default;
 BitcastExpression::~BitcastExpression() = default;
 
-bool BitcastExpression::IsBitcast() const {
-  return true;
-}
-
 bool BitcastExpression::IsValid() const {
   if (expr_ == nullptr || !expr_->IsValid())
     return false;
diff --git a/src/ast/bitcast_expression.h b/src/ast/bitcast_expression.h
index 7a30c9e..a898036 100644
--- a/src/ast/bitcast_expression.h
+++ b/src/ast/bitcast_expression.h
@@ -55,9 +55,6 @@
   /// @returns the expression
   Expression* expr() const { return expr_; }
 
-  /// @returns true if this is a bitcast expression
-  bool IsBitcast() const override;
-
   /// @returns true if the node is valid
   bool IsValid() const override;
 
diff --git a/src/ast/bitcast_expression_test.cc b/src/ast/bitcast_expression_test.cc
index 3f02451..9810a2f 100644
--- a/src/ast/bitcast_expression_test.cc
+++ b/src/ast/bitcast_expression_test.cc
@@ -45,7 +45,7 @@
 
 TEST_F(BitcastExpressionTest, IsBitcast) {
   BitcastExpression exp;
-  EXPECT_TRUE(exp.IsBitcast());
+  EXPECT_TRUE(exp.Is<ast::BitcastExpression>());
 }
 
 TEST_F(BitcastExpressionTest, IsValid) {
diff --git a/src/ast/call_expression.cc b/src/ast/call_expression.cc
index 13c5efc..c17a146 100644
--- a/src/ast/call_expression.cc
+++ b/src/ast/call_expression.cc
@@ -31,10 +31,6 @@
 
 CallExpression::~CallExpression() = default;
 
-bool CallExpression::IsCall() const {
-  return true;
-}
-
 bool CallExpression::IsValid() const {
   if (func_ == nullptr || !func_->IsValid())
     return false;
diff --git a/src/ast/call_expression.h b/src/ast/call_expression.h
index c79dffd..0e27008 100644
--- a/src/ast/call_expression.h
+++ b/src/ast/call_expression.h
@@ -54,9 +54,6 @@
   /// @returns the parameters
   const ExpressionList& params() const { return params_; }
 
-  /// @returns true if this is a call expression
-  bool IsCall() const override;
-
   /// @returns true if the node is valid
   bool IsValid() const override;
 
diff --git a/src/ast/call_expression_test.cc b/src/ast/call_expression_test.cc
index e736cb7..0da64ec 100644
--- a/src/ast/call_expression_test.cc
+++ b/src/ast/call_expression_test.cc
@@ -49,7 +49,7 @@
 TEST_F(CallExpressionTest, IsCall) {
   auto* func = create<IdentifierExpression>("func");
   CallExpression stmt(func, {});
-  EXPECT_TRUE(stmt.IsCall());
+  EXPECT_TRUE(stmt.Is<ast::CallExpression>());
 }
 
 TEST_F(CallExpressionTest, IsValid) {
diff --git a/src/ast/constructor_expression.cc b/src/ast/constructor_expression.cc
index 6aae250..1f19ed8 100644
--- a/src/ast/constructor_expression.cc
+++ b/src/ast/constructor_expression.cc
@@ -31,10 +31,6 @@
 ConstructorExpression::ConstructorExpression(const Source& source)
     : Base(source) {}
 
-bool ConstructorExpression::IsConstructor() const {
-  return true;
-}
-
 bool ConstructorExpression::IsScalarConstructor() const {
   return false;
 }
diff --git a/src/ast/constructor_expression.h b/src/ast/constructor_expression.h
index 9613318..3c9e375 100644
--- a/src/ast/constructor_expression.h
+++ b/src/ast/constructor_expression.h
@@ -29,9 +29,6 @@
  public:
   ~ConstructorExpression() override;
 
-  /// @returns true if this is an constructor expression
-  bool IsConstructor() const override;
-
   /// @returns true if this is a scalar constructor
   virtual bool IsScalarConstructor() const;
   /// @returns true if this is a type constructor
diff --git a/src/ast/expression.cc b/src/ast/expression.cc
index 0f77f2e..b21a903 100644
--- a/src/ast/expression.cc
+++ b/src/ast/expression.cc
@@ -14,18 +14,6 @@
 
 #include "src/ast/expression.h"
 
-#include <assert.h>
-
-#include "src/ast/array_accessor_expression.h"
-#include "src/ast/binary_expression.h"
-#include "src/ast/bitcast_expression.h"
-#include "src/ast/call_expression.h"
-#include "src/ast/constructor_expression.h"
-#include "src/ast/identifier_expression.h"
-#include "src/ast/member_accessor_expression.h"
-#include "src/ast/type/alias_type.h"
-#include "src/ast/unary_op_expression.h"
-
 namespace tint {
 namespace ast {
 
@@ -42,116 +30,5 @@
   result_type_ = type->UnwrapIfNeeded();
 }
 
-bool Expression::IsArrayAccessor() const {
-  return false;
-}
-
-bool Expression::IsBitcast() const {
-  return false;
-}
-
-bool Expression::IsCall() const {
-  return false;
-}
-
-bool Expression::IsIdentifier() const {
-  return false;
-}
-
-bool Expression::IsConstructor() const {
-  return false;
-}
-
-bool Expression::IsMemberAccessor() const {
-  return false;
-}
-
-bool Expression::IsBinary() const {
-  return false;
-}
-
-bool Expression::IsUnaryOp() const {
-  return false;
-}
-const ArrayAccessorExpression* Expression::AsArrayAccessor() const {
-  assert(IsArrayAccessor());
-  return static_cast<const ArrayAccessorExpression*>(this);
-}
-
-const BitcastExpression* Expression::AsBitcast() const {
-  assert(IsBitcast());
-  return static_cast<const BitcastExpression*>(this);
-}
-
-const BinaryExpression* Expression::AsBinary() const {
-  assert(IsBinary());
-  return static_cast<const BinaryExpression*>(this);
-}
-
-const CallExpression* Expression::AsCall() const {
-  assert(IsCall());
-  return static_cast<const CallExpression*>(this);
-}
-
-const ConstructorExpression* Expression::AsConstructor() const {
-  assert(IsConstructor());
-  return static_cast<const ConstructorExpression*>(this);
-}
-
-const IdentifierExpression* Expression::AsIdentifier() const {
-  assert(IsIdentifier());
-  return static_cast<const IdentifierExpression*>(this);
-}
-
-const MemberAccessorExpression* Expression::AsMemberAccessor() const {
-  assert(IsMemberAccessor());
-  return static_cast<const MemberAccessorExpression*>(this);
-}
-
-const UnaryOpExpression* Expression::AsUnaryOp() const {
-  assert(IsUnaryOp());
-  return static_cast<const UnaryOpExpression*>(this);
-}
-
-ArrayAccessorExpression* Expression::AsArrayAccessor() {
-  assert(IsArrayAccessor());
-  return static_cast<ArrayAccessorExpression*>(this);
-}
-
-BitcastExpression* Expression::AsBitcast() {
-  assert(IsBitcast());
-  return static_cast<BitcastExpression*>(this);
-}
-
-BinaryExpression* Expression::AsBinary() {
-  assert(IsBinary());
-  return static_cast<BinaryExpression*>(this);
-}
-
-CallExpression* Expression::AsCall() {
-  assert(IsCall());
-  return static_cast<CallExpression*>(this);
-}
-
-ConstructorExpression* Expression::AsConstructor() {
-  assert(IsConstructor());
-  return static_cast<ConstructorExpression*>(this);
-}
-
-IdentifierExpression* Expression::AsIdentifier() {
-  assert(IsIdentifier());
-  return static_cast<IdentifierExpression*>(this);
-}
-
-MemberAccessorExpression* Expression::AsMemberAccessor() {
-  assert(IsMemberAccessor());
-  return static_cast<MemberAccessorExpression*>(this);
-}
-
-UnaryOpExpression* Expression::AsUnaryOp() {
-  assert(IsUnaryOp());
-  return static_cast<UnaryOpExpression*>(this);
-}
-
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/expression.h b/src/ast/expression.h
index c4d85d4..1d0807b 100644
--- a/src/ast/expression.h
+++ b/src/ast/expression.h
@@ -25,15 +25,6 @@
 namespace tint {
 namespace ast {
 
-class ArrayAccessorExpression;
-class BinaryExpression;
-class BitcastExpression;
-class CallExpression;
-class IdentifierExpression;
-class ConstructorExpression;
-class MemberAccessorExpression;
-class UnaryOpExpression;
-
 /// Base expression class
 class Expression : public Castable<Expression, Node> {
  public:
@@ -51,57 +42,6 @@
     return result_type_ ? result_type_->type_name() : "not set";
   }
 
-  /// @returns true if this is an array accessor expression
-  virtual bool IsArrayAccessor() const;
-  /// @returns true if this is a bitcast expression
-  virtual bool IsBitcast() const;
-  /// @returns true if this is a call expression
-  virtual bool IsCall() const;
-  /// @returns true if this is an identifier expression
-  virtual bool IsIdentifier() const;
-  /// @returns true if this is an constructor expression
-  virtual bool IsConstructor() const;
-  /// @returns true if this is a member accessor expression
-  virtual bool IsMemberAccessor() const;
-  /// @returns true if this is a binary expression
-  virtual bool IsBinary() const;
-  /// @returns true if this is a unary op expression
-  virtual bool IsUnaryOp() const;
-
-  /// @returns the expression as an array accessor
-  const ArrayAccessorExpression* AsArrayAccessor() const;
-  /// @returns the expression as a bitcast
-  const BitcastExpression* AsBitcast() const;
-  /// @returns the expression as a call
-  const CallExpression* AsCall() const;
-  /// @returns the expression as an identifier
-  const IdentifierExpression* AsIdentifier() const;
-  /// @returns the expression as an constructor
-  const ConstructorExpression* AsConstructor() const;
-  /// @returns the expression as a member accessor
-  const MemberAccessorExpression* AsMemberAccessor() const;
-  /// @returns the expression as a binary expression
-  const BinaryExpression* AsBinary() const;
-  /// @returns the expression as a unary op expression
-  const UnaryOpExpression* AsUnaryOp() const;
-
-  /// @returns the expression as an array accessor
-  ArrayAccessorExpression* AsArrayAccessor();
-  /// @returns the expression as a bitcast
-  BitcastExpression* AsBitcast();
-  /// @returns the expression as a call
-  CallExpression* AsCall();
-  /// @returns the expression as an identifier
-  IdentifierExpression* AsIdentifier();
-  /// @returns the expression as an constructor
-  ConstructorExpression* AsConstructor();
-  /// @returns the expression as a member accessor
-  MemberAccessorExpression* AsMemberAccessor();
-  /// @returns the expression as a binary expression
-  BinaryExpression* AsBinary();
-  /// @returns the expression as a unary op expression
-  UnaryOpExpression* AsUnaryOp();
-
  protected:
   /// Constructor
   Expression();
diff --git a/src/ast/identifier_expression.cc b/src/ast/identifier_expression.cc
index ecad6ad..cf30ddc 100644
--- a/src/ast/identifier_expression.cc
+++ b/src/ast/identifier_expression.cc
@@ -28,10 +28,6 @@
 
 IdentifierExpression::~IdentifierExpression() = default;
 
-bool IdentifierExpression::IsIdentifier() const {
-  return true;
-}
-
 bool IdentifierExpression::IsValid() const {
   return !name_.empty();
 }
diff --git a/src/ast/identifier_expression.h b/src/ast/identifier_expression.h
index b13fb0e..708648d 100644
--- a/src/ast/identifier_expression.h
+++ b/src/ast/identifier_expression.h
@@ -61,9 +61,6 @@
   /// @returns true if this identifier is for an intrinsic
   bool IsIntrinsic() const { return intrinsic_ != Intrinsic::kNone; }
 
-  /// @returns true if this is an identifier expression
-  bool IsIdentifier() const override;
-
   /// @returns true if the node is valid
   bool IsValid() const override;
 
diff --git a/src/ast/identifier_expression_test.cc b/src/ast/identifier_expression_test.cc
index c4c6539..1bef59d 100644
--- a/src/ast/identifier_expression_test.cc
+++ b/src/ast/identifier_expression_test.cc
@@ -38,7 +38,7 @@
 
 TEST_F(IdentifierExpressionTest, IsIdentifier) {
   IdentifierExpression i("ident");
-  EXPECT_TRUE(i.IsIdentifier());
+  EXPECT_TRUE(i.Is<ast::IdentifierExpression>());
 }
 
 TEST_F(IdentifierExpressionTest, IsValid) {
diff --git a/src/ast/member_accessor_expression.cc b/src/ast/member_accessor_expression.cc
index 029c272..64d8aad 100644
--- a/src/ast/member_accessor_expression.cc
+++ b/src/ast/member_accessor_expression.cc
@@ -33,10 +33,6 @@
 
 MemberAccessorExpression::~MemberAccessorExpression() = default;
 
-bool MemberAccessorExpression::IsMemberAccessor() const {
-  return true;
-}
-
 bool MemberAccessorExpression::IsValid() const {
   if (struct_ == nullptr || !struct_->IsValid()) {
     return false;
diff --git a/src/ast/member_accessor_expression.h b/src/ast/member_accessor_expression.h
index 006ef88..2af5839 100644
--- a/src/ast/member_accessor_expression.h
+++ b/src/ast/member_accessor_expression.h
@@ -59,9 +59,6 @@
   /// @returns the member expression
   IdentifierExpression* member() const { return member_; }
 
-  /// @returns true if this is a member accessor expression
-  bool IsMemberAccessor() const override;
-
   /// @returns true if the node is valid
   bool IsValid() const override;
 
diff --git a/src/ast/member_accessor_expression_test.cc b/src/ast/member_accessor_expression_test.cc
index 756f65f..ddf5477 100644
--- a/src/ast/member_accessor_expression_test.cc
+++ b/src/ast/member_accessor_expression_test.cc
@@ -46,7 +46,7 @@
 
 TEST_F(MemberAccessorExpressionTest, IsMemberAccessor) {
   MemberAccessorExpression stmt;
-  EXPECT_TRUE(stmt.IsMemberAccessor());
+  EXPECT_TRUE(stmt.Is<ast::MemberAccessorExpression>());
 }
 
 TEST_F(MemberAccessorExpressionTest, IsValid) {
diff --git a/src/ast/unary_op_expression.cc b/src/ast/unary_op_expression.cc
index ed60a62..46d53c6 100644
--- a/src/ast/unary_op_expression.cc
+++ b/src/ast/unary_op_expression.cc
@@ -31,10 +31,6 @@
 
 UnaryOpExpression::~UnaryOpExpression() = default;
 
-bool UnaryOpExpression::IsUnaryOp() const {
-  return true;
-}
-
 bool UnaryOpExpression::IsValid() const {
   return expr_ != nullptr && expr_->IsValid();
 }
diff --git a/src/ast/unary_op_expression.h b/src/ast/unary_op_expression.h
index 67d9e01..7d96fc4 100644
--- a/src/ast/unary_op_expression.h
+++ b/src/ast/unary_op_expression.h
@@ -55,9 +55,6 @@
   /// @returns the expression
   Expression* expr() const { return expr_; }
 
-  /// @returns true if this is an as expression
-  bool IsUnaryOp() const override;
-
   /// @returns true if the node is valid
   bool IsValid() const override;
 
diff --git a/src/ast/unary_op_expression_test.cc b/src/ast/unary_op_expression_test.cc
index f474ea9..3717a27 100644
--- a/src/ast/unary_op_expression_test.cc
+++ b/src/ast/unary_op_expression_test.cc
@@ -43,7 +43,7 @@
 
 TEST_F(UnaryOpExpressionTest, IsUnaryOp) {
   UnaryOpExpression u;
-  EXPECT_TRUE(u.IsUnaryOp());
+  EXPECT_TRUE(u.Is<ast::UnaryOpExpression>());
 }
 
 TEST_F(UnaryOpExpressionTest, IsValid) {
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
index 7a62f3f..655ed3a 100644
--- a/src/inspector/inspector.cc
+++ b/src/inspector/inspector.cc
@@ -124,13 +124,13 @@
     }
 
     auto* expression = var->constructor();
-    if (!expression->IsConstructor()) {
+    if (!expression->Is<ast::ConstructorExpression>()) {
       // This is invalid WGSL, but handling gracefully.
       result[constant_id] = Scalar();
       continue;
     }
 
-    auto* constructor = expression->AsConstructor();
+    auto* constructor = expression->As<ast::ConstructorExpression>();
     if (!constructor->IsScalarConstructor()) {
       // This is invalid WGSL, but handling gracefully.
       result[constant_id] = Scalar();
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index 19fe372..d7a71e7 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -32,6 +32,7 @@
 #include "source/opt/type_manager.h"
 #include "src/ast/case_statement.h"
 #include "src/ast/expression.h"
+#include "src/ast/identifier_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/statement.h"
 #include "src/ast/storage_class.h"
diff --git a/src/reader/wgsl/parser_impl_additive_expression_test.cc b/src/reader/wgsl/parser_impl_additive_expression_test.cc
index 09948cd..7cdfbda 100644
--- a/src/reader/wgsl/parser_impl_additive_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_additive_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kAdd, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -56,17 +56,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -97,7 +97,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_and_expression_test.cc b/src/reader/wgsl/parser_impl_and_expression_test.cc
index 2131620..5cc8d98 100644
--- a/src/reader/wgsl/parser_impl_and_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_and_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kAnd, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -74,7 +74,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_argument_expression_list_test.cc b/src/reader/wgsl/parser_impl_argument_expression_list_test.cc
index d1ea588..457a322 100644
--- a/src/reader/wgsl/parser_impl_argument_expression_list_test.cc
+++ b/src/reader/wgsl/parser_impl_argument_expression_list_test.cc
@@ -14,6 +14,7 @@
 
 #include "gtest/gtest.h"
 #include "src/ast/array_accessor_expression.h"
+#include "src/ast/binary_expression.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/sint_literal.h"
@@ -33,7 +34,7 @@
   ASSERT_FALSE(e.errored);
 
   ASSERT_EQ(e.value.size(), 1u);
-  ASSERT_TRUE(e.value[0]->IsIdentifier());
+  ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
 }
 
 TEST_F(ParserImplTest, ArgumentExpressionList_ParsesMultiple) {
@@ -43,9 +44,9 @@
   ASSERT_FALSE(e.errored);
 
   ASSERT_EQ(e.value.size(), 3u);
-  ASSERT_TRUE(e.value[0]->IsIdentifier());
-  ASSERT_TRUE(e.value[1]->IsConstructor());
-  ASSERT_TRUE(e.value[2]->IsBinary());
+  ASSERT_TRUE(e.value[0]->Is<ast::IdentifierExpression>());
+  ASSERT_TRUE(e.value[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e.value[2]->Is<ast::BinaryExpression>());
 }
 
 TEST_F(ParserImplTest, ArgumentExpressionList_HandlesMissingExpression) {
diff --git a/src/reader/wgsl/parser_impl_assignment_stmt_test.cc b/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
index dfcadfe..541932d 100644
--- a/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
@@ -40,14 +40,14 @@
   ASSERT_NE(e->lhs(), nullptr);
   ASSERT_NE(e->rhs(), nullptr);
 
-  ASSERT_TRUE(e->lhs()->IsIdentifier());
-  auto* ident = e->lhs()->AsIdentifier();
+  ASSERT_TRUE(e->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = e->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(e->rhs()->IsConstructor());
-  ASSERT_TRUE(e->rhs()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(e->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e->rhs()->Is<ast::ScalarConstructorExpression>());
 
-  auto* init = e->rhs()->AsConstructor()->AsScalarConstructor();
+  auto* init = e->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_NE(init->literal(), nullptr);
   ASSERT_TRUE(init->literal()->IsSint());
   EXPECT_EQ(init->literal()->AsSint()->value(), 123);
@@ -65,45 +65,45 @@
   ASSERT_NE(e->lhs(), nullptr);
   ASSERT_NE(e->rhs(), nullptr);
 
-  ASSERT_TRUE(e->rhs()->IsConstructor());
-  ASSERT_TRUE(e->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = e->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(e->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = e->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_NE(init->literal(), nullptr);
   ASSERT_TRUE(init->literal()->IsSint());
   EXPECT_EQ(init->literal()->AsSint()->value(), 123);
 
-  ASSERT_TRUE(e->lhs()->IsMemberAccessor());
-  auto* mem = e->lhs()->AsMemberAccessor();
+  ASSERT_TRUE(e->lhs()->Is<ast::MemberAccessorExpression>());
+  auto* mem = e->lhs()->As<ast::MemberAccessorExpression>();
 
-  ASSERT_TRUE(mem->member()->IsIdentifier());
-  auto* ident = mem->member()->AsIdentifier();
+  ASSERT_TRUE(mem->member()->Is<ast::IdentifierExpression>());
+  auto* ident = mem->member()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "d");
 
-  ASSERT_TRUE(mem->structure()->IsArrayAccessor());
-  auto* ary = mem->structure()->AsArrayAccessor();
+  ASSERT_TRUE(mem->structure()->Is<ast::ArrayAccessorExpression>());
+  auto* ary = mem->structure()->As<ast::ArrayAccessorExpression>();
 
-  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
-  init = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
+  init = ary->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_NE(init->literal(), nullptr);
   ASSERT_TRUE(init->literal()->IsSint());
   EXPECT_EQ(init->literal()->AsSint()->value(), 2);
 
-  ASSERT_TRUE(ary->array()->IsMemberAccessor());
-  mem = ary->array()->AsMemberAccessor();
-  ASSERT_TRUE(mem->member()->IsIdentifier());
-  ident = mem->member()->AsIdentifier();
+  ASSERT_TRUE(ary->array()->Is<ast::MemberAccessorExpression>());
+  mem = ary->array()->As<ast::MemberAccessorExpression>();
+  ASSERT_TRUE(mem->member()->Is<ast::IdentifierExpression>());
+  ident = mem->member()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "c");
 
-  ASSERT_TRUE(mem->structure()->IsMemberAccessor());
-  mem = mem->structure()->AsMemberAccessor();
+  ASSERT_TRUE(mem->structure()->Is<ast::MemberAccessorExpression>());
+  mem = mem->structure()->As<ast::MemberAccessorExpression>();
 
-  ASSERT_TRUE(mem->structure()->IsIdentifier());
-  ident = mem->structure()->AsIdentifier();
+  ASSERT_TRUE(mem->structure()->Is<ast::IdentifierExpression>());
+  ident = mem->structure()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(mem->member()->IsIdentifier());
-  ident = mem->member()->AsIdentifier();
+  ASSERT_TRUE(mem->member()->Is<ast::IdentifierExpression>());
+  ident = mem->member()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "b");
 }
 
diff --git a/src/reader/wgsl/parser_impl_call_stmt_test.cc b/src/reader/wgsl/parser_impl_call_stmt_test.cc
index a0ce2ed..158f7fc 100644
--- a/src/reader/wgsl/parser_impl_call_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_call_stmt_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "gtest/gtest.h"
+#include "src/ast/binary_expression.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/identifier_expression.h"
@@ -35,8 +36,8 @@
   ASSERT_TRUE(e->Is<ast::CallStatement>());
   auto* c = e->As<ast::CallStatement>()->expr();
 
-  ASSERT_TRUE(c->func()->IsIdentifier());
-  auto* func = c->func()->AsIdentifier();
+  ASSERT_TRUE(c->func()->Is<ast::IdentifierExpression>());
+  auto* func = c->func()->As<ast::IdentifierExpression>();
   EXPECT_EQ(func->name(), "a");
 
   EXPECT_EQ(c->params().size(), 0u);
@@ -53,14 +54,14 @@
   ASSERT_TRUE(e->Is<ast::CallStatement>());
   auto* c = e->As<ast::CallStatement>()->expr();
 
-  ASSERT_TRUE(c->func()->IsIdentifier());
-  auto* func = c->func()->AsIdentifier();
+  ASSERT_TRUE(c->func()->Is<ast::IdentifierExpression>());
+  auto* func = c->func()->As<ast::IdentifierExpression>();
   EXPECT_EQ(func->name(), "a");
 
   EXPECT_EQ(c->params().size(), 3u);
-  EXPECT_TRUE(c->params()[0]->IsConstructor());
-  EXPECT_TRUE(c->params()[1]->IsIdentifier());
-  EXPECT_TRUE(c->params()[2]->IsBinary());
+  EXPECT_TRUE(c->params()[0]->Is<ast::ConstructorExpression>());
+  EXPECT_TRUE(c->params()[1]->Is<ast::IdentifierExpression>());
+  EXPECT_TRUE(c->params()[2]->Is<ast::BinaryExpression>());
 }
 
 TEST_F(ParserImplTest, Statement_Call_Missing_RightParen) {
diff --git a/src/reader/wgsl/parser_impl_const_expr_test.cc b/src/reader/wgsl/parser_impl_const_expr_test.cc
index 4135515..4d13cf7 100644
--- a/src/reader/wgsl/parser_impl_const_expr_test.cc
+++ b/src/reader/wgsl/parser_impl_const_expr_test.cc
@@ -31,25 +31,25 @@
   auto e = p->expect_const_expr();
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsTypeConstructor());
+  ASSERT_TRUE(e->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
 
-  auto* t = e->AsConstructor()->AsTypeConstructor();
+  auto* t = e->As<ast::TypeConstructorExpression>();
   ASSERT_TRUE(t->type()->Is<ast::type::VectorType>());
   EXPECT_EQ(t->type()->As<ast::type::VectorType>()->size(), 2u);
 
   ASSERT_EQ(t->values().size(), 2u);
   auto& v = t->values();
 
-  ASSERT_TRUE(v[0]->IsConstructor());
-  ASSERT_TRUE(v[0]->AsConstructor()->IsScalarConstructor());
-  auto* c = v[0]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(v[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(v[0]->Is<ast::ScalarConstructorExpression>());
+  auto* c = v[0]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(c->literal()->IsFloat());
   EXPECT_FLOAT_EQ(c->literal()->AsFloat()->value(), 1.);
 
-  ASSERT_TRUE(v[1]->IsConstructor());
-  ASSERT_TRUE(v[1]->AsConstructor()->IsScalarConstructor());
-  c = v[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(v[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(v[1]->Is<ast::ScalarConstructorExpression>());
+  c = v[1]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(c->literal()->IsFloat());
   EXPECT_FLOAT_EQ(c->literal()->AsFloat()->value(), 2.);
 }
@@ -114,9 +114,9 @@
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsScalarConstructor());
-  auto* c = e->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(e->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e->Is<ast::ScalarConstructorExpression>());
+  auto* c = e->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(c->literal()->IsBool());
   EXPECT_TRUE(c->literal()->AsBool()->IsTrue());
 }
diff --git a/src/reader/wgsl/parser_impl_elseif_stmt_test.cc b/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
index 1c27a9b..150c7ea 100644
--- a/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
@@ -13,7 +13,9 @@
 // limitations under the License.
 
 #include "gtest/gtest.h"
+#include "src/ast/binary_expression.h"
 #include "src/ast/else_statement.h"
+#include "src/ast/identifier_expression.h"
 #include "src/reader/wgsl/parser_impl.h"
 #include "src/reader/wgsl/parser_impl_test_helper.h"
 
@@ -32,7 +34,7 @@
 
   ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
   ASSERT_NE(e.value[0]->condition(), nullptr);
-  ASSERT_TRUE(e.value[0]->condition()->IsBinary());
+  ASSERT_TRUE(e.value[0]->condition()->Is<ast::BinaryExpression>());
   EXPECT_EQ(e.value[0]->body()->size(), 2u);
 }
 
@@ -46,12 +48,12 @@
 
   ASSERT_TRUE(e.value[0]->Is<ast::ElseStatement>());
   ASSERT_NE(e.value[0]->condition(), nullptr);
-  ASSERT_TRUE(e.value[0]->condition()->IsBinary());
+  ASSERT_TRUE(e.value[0]->condition()->Is<ast::BinaryExpression>());
   EXPECT_EQ(e.value[0]->body()->size(), 2u);
 
   ASSERT_TRUE(e.value[1]->Is<ast::ElseStatement>());
   ASSERT_NE(e.value[1]->condition(), nullptr);
-  ASSERT_TRUE(e.value[1]->condition()->IsIdentifier());
+  ASSERT_TRUE(e.value[1]->condition()->Is<ast::IdentifierExpression>());
   EXPECT_EQ(e.value[1]->body()->size(), 1u);
 }
 
diff --git a/src/reader/wgsl/parser_impl_equality_expression_test.cc b/src/reader/wgsl/parser_impl_equality_expression_test.cc
index 36cbcef..d73d1a5 100644
--- a/src/reader/wgsl/parser_impl_equality_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_equality_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kEqual, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -56,17 +56,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kNotEqual, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -97,7 +97,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc b/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
index 1338dfa..63f7dd5 100644
--- a/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kXor, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -74,7 +74,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
index ed6bbf6..fe26ad1 100644
--- a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
@@ -43,7 +43,7 @@
   EXPECT_EQ(e->source().range.end.column, 8u);
 
   ASSERT_NE(e->constructor(), nullptr);
-  EXPECT_TRUE(e->constructor()->IsConstructor());
+  EXPECT_TRUE(e->constructor()->Is<ast::ConstructorExpression>());
 }
 
 TEST_F(ParserImplTest, GlobalConstantDecl_MissingEqual) {
diff --git a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
index 80a5d30..b03cd79 100644
--- a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -14,6 +14,7 @@
 
 #include "gtest/gtest.h"
 #include "src/ast/decorated_variable.h"
+#include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/type/f32_type.h"
 #include "src/ast/variable_decoration.h"
 #include "src/reader/wgsl/parser_impl.h"
@@ -69,8 +70,8 @@
   EXPECT_EQ(e->source().range.end.column, 11u);
 
   ASSERT_NE(e->constructor(), nullptr);
-  ASSERT_TRUE(e->constructor()->IsConstructor());
-  ASSERT_TRUE(e->constructor()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(e->constructor()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e->constructor()->Is<ast::ScalarConstructorExpression>());
 
   ASSERT_FALSE(e->IsDecorated());
 }
diff --git a/src/reader/wgsl/parser_impl_if_stmt_test.cc b/src/reader/wgsl/parser_impl_if_stmt_test.cc
index fc1ac37..efd4248 100644
--- a/src/reader/wgsl/parser_impl_if_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_if_stmt_test.cc
@@ -13,7 +13,9 @@
 // limitations under the License.
 
 #include "gtest/gtest.h"
+#include "src/ast/binary_expression.h"
 #include "src/ast/else_statement.h"
+#include "src/ast/identifier_expression.h"
 #include "src/ast/if_statement.h"
 #include "src/reader/wgsl/parser_impl.h"
 #include "src/reader/wgsl/parser_impl_test_helper.h"
@@ -33,7 +35,7 @@
 
   ASSERT_TRUE(e->Is<ast::IfStatement>());
   ASSERT_NE(e->condition(), nullptr);
-  ASSERT_TRUE(e->condition()->IsBinary());
+  ASSERT_TRUE(e->condition()->Is<ast::BinaryExpression>());
   EXPECT_EQ(e->body()->size(), 2u);
   EXPECT_EQ(e->else_statements().size(), 0u);
 }
@@ -48,12 +50,13 @@
 
   ASSERT_TRUE(e->Is<ast::IfStatement>());
   ASSERT_NE(e->condition(), nullptr);
-  ASSERT_TRUE(e->condition()->IsBinary());
+  ASSERT_TRUE(e->condition()->Is<ast::BinaryExpression>());
   EXPECT_EQ(e->body()->size(), 2u);
 
   ASSERT_EQ(e->else_statements().size(), 2u);
   ASSERT_NE(e->else_statements()[0]->condition(), nullptr);
-  ASSERT_TRUE(e->else_statements()[0]->condition()->IsIdentifier());
+  ASSERT_TRUE(
+      e->else_statements()[0]->condition()->Is<ast::IdentifierExpression>());
   EXPECT_EQ(e->else_statements()[0]->body()->size(), 1u);
 
   ASSERT_EQ(e->else_statements()[1]->condition(), nullptr);
diff --git a/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc b/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
index e1bf011..207fcd9 100644
--- a/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kOr, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -74,7 +74,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_logical_and_expression_test.cc b/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
index e291ce9..7dd5ce3 100644
--- a/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kLogicalAnd, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -74,7 +74,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_logical_or_expression_test.cc b/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
index 006f06f..c5bfaee 100644
--- a/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kLogicalOr, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -74,7 +74,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc b/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
index 38f4dac..41a3e8c 100644
--- a/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -56,17 +56,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kDivide, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -79,17 +79,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kModulo, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -120,7 +120,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc b/src/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
index 85b7d3c..b56de58 100644
--- a/src/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_paren_rhs_stmt_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "gtest/gtest.h"
+#include "src/ast/binary_expression.h"
 #include "src/reader/wgsl/parser_impl.h"
 #include "src/reader/wgsl/parser_impl_test_helper.h"
 
@@ -27,7 +28,7 @@
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_FALSE(e.errored);
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsBinary());
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
 }
 
 TEST_F(ParserImplTest, ParenRhsStmt_MissingLeftParen) {
diff --git a/src/reader/wgsl/parser_impl_postfix_expression_test.cc b/src/reader/wgsl/parser_impl_postfix_expression_test.cc
index 10730da..7eb7ceb 100644
--- a/src/reader/wgsl/parser_impl_postfix_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_postfix_expression_test.cc
@@ -14,6 +14,7 @@
 
 #include "gtest/gtest.h"
 #include "src/ast/array_accessor_expression.h"
+#include "src/ast/binary_expression.h"
 #include "src/ast/call_expression.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/member_accessor_expression.h"
@@ -36,16 +37,16 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsArrayAccessor());
-  auto* ary = e->AsArrayAccessor();
+  ASSERT_TRUE(e->Is<ast::ArrayAccessorExpression>());
+  auto* ary = e->As<ast::ArrayAccessorExpression>();
 
-  ASSERT_TRUE(ary->array()->IsIdentifier());
-  auto* ident = ary->array()->AsIdentifier();
+  ASSERT_TRUE(ary->array()->Is<ast::IdentifierExpression>());
+  auto* ident = ary->array()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
-  auto* c = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
+  auto* c = ary->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(c->literal()->IsSint());
   EXPECT_EQ(c->literal()->AsSint()->value(), 1);
 }
@@ -58,14 +59,14 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsArrayAccessor());
-  auto* ary = e->AsArrayAccessor();
+  ASSERT_TRUE(e->Is<ast::ArrayAccessorExpression>());
+  auto* ary = e->As<ast::ArrayAccessorExpression>();
 
-  ASSERT_TRUE(ary->array()->IsIdentifier());
-  auto* ident = ary->array()->AsIdentifier();
+  ASSERT_TRUE(ary->array()->Is<ast::IdentifierExpression>());
+  auto* ident = ary->array()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(ary->idx_expr()->IsBinary());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::BinaryExpression>());
 }
 
 TEST_F(ParserImplTest, PostfixExpression_Array_MissingIndex) {
@@ -106,11 +107,11 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsCall());
-  auto* c = e->AsCall();
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* c = e->As<ast::CallExpression>();
 
-  ASSERT_TRUE(c->func()->IsIdentifier());
-  auto* func = c->func()->AsIdentifier();
+  ASSERT_TRUE(c->func()->Is<ast::IdentifierExpression>());
+  auto* func = c->func()->As<ast::IdentifierExpression>();
   EXPECT_EQ(func->name(), "a");
 
   EXPECT_EQ(c->params().size(), 0u);
@@ -124,17 +125,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsCall());
-  auto* c = e->AsCall();
+  ASSERT_TRUE(e->Is<ast::CallExpression>());
+  auto* c = e->As<ast::CallExpression>();
 
-  ASSERT_TRUE(c->func()->IsIdentifier());
-  auto* func = c->func()->AsIdentifier();
+  ASSERT_TRUE(c->func()->Is<ast::IdentifierExpression>());
+  auto* func = c->func()->As<ast::IdentifierExpression>();
   EXPECT_EQ(func->name(), "test");
 
   EXPECT_EQ(c->params().size(), 3u);
-  EXPECT_TRUE(c->params()[0]->IsConstructor());
-  EXPECT_TRUE(c->params()[1]->IsIdentifier());
-  EXPECT_TRUE(c->params()[2]->IsBinary());
+  EXPECT_TRUE(c->params()[0]->Is<ast::ConstructorExpression>());
+  EXPECT_TRUE(c->params()[1]->Is<ast::IdentifierExpression>());
+  EXPECT_TRUE(c->params()[2]->Is<ast::BinaryExpression>());
 }
 
 TEST_F(ParserImplTest, PostfixExpression_Call_InvalidArg) {
@@ -174,14 +175,14 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsMemberAccessor());
+  ASSERT_TRUE(e->Is<ast::MemberAccessorExpression>());
 
-  auto* m = e->AsMemberAccessor();
-  ASSERT_TRUE(m->structure()->IsIdentifier());
-  EXPECT_EQ(m->structure()->AsIdentifier()->name(), "a");
+  auto* m = e->As<ast::MemberAccessorExpression>();
+  ASSERT_TRUE(m->structure()->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(m->structure()->As<ast::IdentifierExpression>()->name(), "a");
 
-  ASSERT_TRUE(m->member()->IsIdentifier());
-  EXPECT_EQ(m->member()->AsIdentifier()->name(), "b");
+  ASSERT_TRUE(m->member()->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(m->member()->As<ast::IdentifierExpression>()->name(), "b");
 }
 
 TEST_F(ParserImplTest, PostfixExpression_MemberAccesssor_InvalidIdent) {
@@ -211,7 +212,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_primary_expression_test.cc b/src/reader/wgsl/parser_impl_primary_expression_test.cc
index 24a9c59..10ebcc9 100644
--- a/src/reader/wgsl/parser_impl_primary_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_primary_expression_test.cc
@@ -14,6 +14,7 @@
 
 #include "gtest/gtest.h"
 #include "src/ast/array_accessor_expression.h"
+#include "src/ast/binary_expression.h"
 #include "src/ast/bitcast_expression.h"
 #include "src/ast/bool_literal.h"
 #include "src/ast/identifier_expression.h"
@@ -38,8 +39,8 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
-  auto* ident = e->AsIdentifier();
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
+  auto* ident = e->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 }
 
@@ -50,33 +51,33 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsTypeConstructor());
-  auto* ty = e->AsConstructor()->AsTypeConstructor();
+  ASSERT_TRUE(e->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
+  auto* ty = e->As<ast::TypeConstructorExpression>();
 
   ASSERT_EQ(ty->values().size(), 4u);
   const auto& val = ty->values();
-  ASSERT_TRUE(val[0]->IsConstructor());
-  ASSERT_TRUE(val[0]->AsConstructor()->IsScalarConstructor());
-  auto* ident = val[0]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(val[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(val[0]->Is<ast::ScalarConstructorExpression>());
+  auto* ident = val[0]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(ident->literal()->IsSint());
   EXPECT_EQ(ident->literal()->AsSint()->value(), 1);
 
-  ASSERT_TRUE(val[1]->IsConstructor());
-  ASSERT_TRUE(val[1]->AsConstructor()->IsScalarConstructor());
-  ident = val[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(val[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(val[1]->Is<ast::ScalarConstructorExpression>());
+  ident = val[1]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(ident->literal()->IsSint());
   EXPECT_EQ(ident->literal()->AsSint()->value(), 2);
 
-  ASSERT_TRUE(val[2]->IsConstructor());
-  ASSERT_TRUE(val[2]->AsConstructor()->IsScalarConstructor());
-  ident = val[2]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(val[2]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(val[2]->Is<ast::ScalarConstructorExpression>());
+  ident = val[2]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(ident->literal()->IsSint());
   EXPECT_EQ(ident->literal()->AsSint()->value(), 3);
 
-  ASSERT_TRUE(val[3]->IsConstructor());
-  ASSERT_TRUE(val[3]->AsConstructor()->IsScalarConstructor());
-  ident = val[3]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(val[3]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(val[3]->Is<ast::ScalarConstructorExpression>());
+  ident = val[3]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(ident->literal()->IsSint());
   EXPECT_EQ(ident->literal()->AsSint()->value(), 4);
 }
@@ -88,9 +89,9 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsTypeConstructor());
-  auto* ty = e->AsConstructor()->AsTypeConstructor();
+  ASSERT_TRUE(e->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
+  auto* ty = e->As<ast::TypeConstructorExpression>();
 
   ASSERT_EQ(ty->values().size(), 0u);
 }
@@ -142,9 +143,9 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsScalarConstructor());
-  auto* init = e->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(e->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e->Is<ast::ScalarConstructorExpression>());
+  auto* init = e->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   EXPECT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -156,7 +157,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsBinary());
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_MissingRightParen) {
@@ -200,15 +201,15 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsTypeConstructor());
+  ASSERT_TRUE(e->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(e->Is<ast::TypeConstructorExpression>());
 
-  auto* c = e->AsConstructor()->AsTypeConstructor();
+  auto* c = e->As<ast::TypeConstructorExpression>();
   ASSERT_EQ(c->type(), f32);
   ASSERT_EQ(c->values().size(), 1u);
 
-  ASSERT_TRUE(c->values()[0]->IsConstructor());
-  ASSERT_TRUE(c->values()[0]->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(c->values()[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(c->values()[0]->Is<ast::ScalarConstructorExpression>());
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast) {
@@ -222,13 +223,13 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsBitcast());
+  ASSERT_TRUE(e->Is<ast::BitcastExpression>());
 
-  auto* c = e->AsBitcast();
+  auto* c = e->As<ast::BitcastExpression>();
   ASSERT_EQ(c->type(), f32);
 
-  ASSERT_TRUE(c->expr()->IsConstructor());
-  ASSERT_TRUE(c->expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(c->expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(c->expr()->Is<ast::ScalarConstructorExpression>());
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingGreaterThan) {
diff --git a/src/reader/wgsl/parser_impl_relational_expression_test.cc b/src/reader/wgsl/parser_impl_relational_expression_test.cc
index 70f9a70..ffa6840 100644
--- a/src/reader/wgsl/parser_impl_relational_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_relational_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kLessThan, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -56,17 +56,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kGreaterThan, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -79,17 +79,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kLessThanEqual, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -102,17 +102,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kGreaterThanEqual, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -141,7 +141,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_shift_expression_test.cc b/src/reader/wgsl/parser_impl_shift_expression_test.cc
index 4bda7f4..17e7bad 100644
--- a/src/reader/wgsl/parser_impl_shift_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_shift_expression_test.cc
@@ -33,17 +33,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kShiftLeft, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -56,17 +56,17 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e->Is<ast::BinaryExpression>());
+  auto* rel = e->As<ast::BinaryExpression>();
   EXPECT_EQ(ast::BinaryOp::kShiftRight, rel->op());
 
-  ASSERT_TRUE(rel->lhs()->IsIdentifier());
-  auto* ident = rel->lhs()->AsIdentifier();
+  ASSERT_TRUE(rel->lhs()->Is<ast::IdentifierExpression>());
+  auto* ident = rel->lhs()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(rel->rhs()->IsConstructor());
-  ASSERT_TRUE(rel->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = rel->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(rel->rhs()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(rel->rhs()->Is<ast::ScalarConstructorExpression>());
+  auto* init = rel->rhs()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsBool());
   ASSERT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -97,7 +97,7 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  ASSERT_TRUE(e->Is<ast::IdentifierExpression>());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_statement_test.cc b/src/reader/wgsl/parser_impl_statement_test.cc
index ec96357..e2bfd20 100644
--- a/src/reader/wgsl/parser_impl_statement_test.cc
+++ b/src/reader/wgsl/parser_impl_statement_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "gtest/gtest.h"
+#include "src/ast/binary_expression.h"
 #include "src/ast/discard_statement.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/statement.h"
@@ -60,7 +61,7 @@
   ASSERT_TRUE(e->Is<ast::ReturnStatement>());
   auto* ret = e->As<ast::ReturnStatement>();
   ASSERT_NE(ret->value(), nullptr);
-  EXPECT_TRUE(ret->value()->IsBinary());
+  EXPECT_TRUE(ret->value()->Is<ast::BinaryExpression>());
 }
 
 TEST_F(ParserImplTest, Statement_Return_MissingSemi) {
diff --git a/src/reader/wgsl/parser_impl_unary_expression_test.cc b/src/reader/wgsl/parser_impl_unary_expression_test.cc
index 6817ed7..c846667 100644
--- a/src/reader/wgsl/parser_impl_unary_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_unary_expression_test.cc
@@ -34,15 +34,15 @@
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsArrayAccessor());
-  auto* ary = e->AsArrayAccessor();
-  ASSERT_TRUE(ary->array()->IsIdentifier());
-  auto* ident = ary->array()->AsIdentifier();
+  ASSERT_TRUE(e->Is<ast::ArrayAccessorExpression>());
+  auto* ary = e->As<ast::ArrayAccessorExpression>();
+  ASSERT_TRUE(ary->array()->Is<ast::IdentifierExpression>());
+  auto* ident = ary->array()->As<ast::IdentifierExpression>();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
-  auto* init = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
+  auto* init = ary->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsSint());
   ASSERT_EQ(init->literal()->AsSint()->value(), 2);
 }
@@ -54,15 +54,15 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsUnaryOp());
+  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
 
-  auto* u = e->AsUnaryOp();
+  auto* u = e->As<ast::UnaryOpExpression>();
   ASSERT_EQ(u->op(), ast::UnaryOp::kNegation);
 
-  ASSERT_TRUE(u->expr()->IsConstructor());
-  ASSERT_TRUE(u->expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(u->expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(u->expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* init = u->expr()->AsConstructor()->AsScalarConstructor();
+  auto* init = u->expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsSint());
   EXPECT_EQ(init->literal()->AsSint()->value(), 1);
 }
@@ -84,15 +84,15 @@
   EXPECT_FALSE(e.errored);
   EXPECT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(e.value, nullptr);
-  ASSERT_TRUE(e->IsUnaryOp());
+  ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
 
-  auto* u = e->AsUnaryOp();
+  auto* u = e->As<ast::UnaryOpExpression>();
   ASSERT_EQ(u->op(), ast::UnaryOp::kNot);
 
-  ASSERT_TRUE(u->expr()->IsConstructor());
-  ASSERT_TRUE(u->expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(u->expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(u->expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* init = u->expr()->AsConstructor()->AsScalarConstructor();
+  auto* init = u->expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(init->literal()->IsSint());
   EXPECT_EQ(init->literal()->AsSint()->value(), 1);
 }
diff --git a/src/reader/wgsl/parser_impl_variable_stmt_test.cc b/src/reader/wgsl/parser_impl_variable_stmt_test.cc
index 8f83c31..d541af3 100644
--- a/src/reader/wgsl/parser_impl_variable_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_stmt_test.cc
@@ -59,7 +59,7 @@
   ASSERT_EQ(e->source().range.end.column, 6u);
 
   ASSERT_NE(e->variable()->constructor(), nullptr);
-  EXPECT_TRUE(e->variable()->constructor()->IsConstructor());
+  EXPECT_TRUE(e->variable()->constructor()->Is<ast::ConstructorExpression>());
 }
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_Invalid) {
diff --git a/src/transform/bound_array_accessors_transform.cc b/src/transform/bound_array_accessors_transform.cc
index 1715c8c..135518e 100644
--- a/src/transform/bound_array_accessors_transform.cc
+++ b/src/transform/bound_array_accessors_transform.cc
@@ -135,24 +135,22 @@
 }
 
 bool BoundArrayAccessorsTransform::ProcessExpression(ast::Expression* expr) {
-  if (expr->IsArrayAccessor()) {
-    return ProcessArrayAccessor(expr->AsArrayAccessor());
-  } else if (expr->IsBitcast()) {
-    return ProcessExpression(expr->AsBitcast()->expr());
-  } else if (expr->IsCall()) {
-    auto* c = expr->AsCall();
-    if (!ProcessExpression(c->func())) {
+  if (auto* array = expr->As<ast::ArrayAccessorExpression>()) {
+    return ProcessArrayAccessor(array);
+  } else if (auto* bitcast = expr->As<ast::BitcastExpression>()) {
+    return ProcessExpression(bitcast->expr());
+  } else if (auto* call = expr->As<ast::CallExpression>()) {
+    if (!ProcessExpression(call->func())) {
       return false;
     }
-    for (auto* e : c->params()) {
+    for (auto* e : call->params()) {
       if (!ProcessExpression(e)) {
         return false;
       }
     }
-  } else if (expr->IsIdentifier()) {
+  } else if (expr->Is<ast::IdentifierExpression>()) {
     /* nop */
-  } else if (expr->IsConstructor()) {
-    auto* c = expr->AsConstructor();
+  } else if (auto* c = expr->As<ast::ConstructorExpression>()) {
     if (c->IsTypeConstructor()) {
       for (auto* e : c->AsTypeConstructor()->values()) {
         if (!ProcessExpression(e)) {
@@ -160,14 +158,12 @@
         }
       }
     }
-  } else if (expr->IsMemberAccessor()) {
-    auto* m = expr->AsMemberAccessor();
+  } else if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
     return ProcessExpression(m->structure()) && ProcessExpression(m->member());
-  } else if (expr->IsBinary()) {
-    auto* b = expr->AsBinary();
+  } else if (auto* b = expr->As<ast::BinaryExpression>()) {
     return ProcessExpression(b->lhs()) && ProcessExpression(b->rhs());
-  } else if (expr->IsUnaryOp()) {
-    return ProcessExpression(expr->AsUnaryOp()->expr());
+  } else if (auto* u = expr->As<ast::UnaryOpExpression>()) {
+    return ProcessExpression(u->expr());
   } else {
     error_ = "unknown statement in bound array accessors transform";
     return false;
@@ -217,10 +213,8 @@
     ast::ArrayAccessorExpression* expr,
     uint32_t size) {
   // Scalar constructor we can re-write the value to be within bounds.
-  if (expr->idx_expr()->IsConstructor() &&
-      expr->idx_expr()->AsConstructor()->IsScalarConstructor()) {
-    auto* lit =
-        expr->idx_expr()->AsConstructor()->AsScalarConstructor()->literal();
+  if (auto* c = expr->idx_expr()->As<ast::ScalarConstructorExpression>()) {
+    auto* lit = c->literal();
     if (lit->IsSint()) {
       int32_t val = lit->AsSint()->value();
       if (val < 0) {
diff --git a/src/transform/bound_array_accessors_transform_test.cc b/src/transform/bound_array_accessors_transform_test.cc
index d9aa522..b0e6543 100644
--- a/src/transform/bound_array_accessors_transform_test.cc
+++ b/src/transform/bound_array_accessors_transform_test.cc
@@ -129,25 +129,25 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsCall());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::CallExpression>());
 
-  auto* idx = ptr->idx_expr()->AsCall();
-  ASSERT_TRUE(idx->func()->IsIdentifier());
-  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "min");
+  auto* idx = ptr->idx_expr()->As<ast::CallExpression>();
+  ASSERT_TRUE(idx->func()->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(idx->func()->As<ast::IdentifierExpression>()->name(), "min");
 
   ASSERT_EQ(idx->params().size(), 2u);
 
-  ASSERT_TRUE(idx->params()[0]->IsConstructor());
-  ASSERT_TRUE(idx->params()[0]->AsConstructor()->IsTypeConstructor());
-  auto* tc = idx->params()[0]->AsConstructor()->AsTypeConstructor();
+  ASSERT_TRUE(idx->params()[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[0]->Is<ast::TypeConstructorExpression>());
+  auto* tc = idx->params()[0]->As<ast::TypeConstructorExpression>();
   EXPECT_TRUE(tc->type()->Is<ast::type::U32Type>());
   ASSERT_EQ(tc->values().size(), 1u);
   ASSERT_EQ(tc->values()[0], access_idx);
 
-  ASSERT_TRUE(idx->params()[1]->IsConstructor());
-  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
-  auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ScalarConstructorExpression>());
+  auto* scalar = idx->params()[1]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
@@ -192,45 +192,49 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsCall());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::CallExpression>());
 
-  auto* idx = ptr->idx_expr()->AsCall();
-  ASSERT_TRUE(idx->func()->IsIdentifier());
-  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "min");
+  auto* idx = ptr->idx_expr()->As<ast::CallExpression>();
+  ASSERT_TRUE(idx->func()->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(idx->func()->As<ast::IdentifierExpression>()->name(), "min");
 
   ASSERT_EQ(idx->params().size(), 2u);
 
-  ASSERT_TRUE(idx->params()[0]->IsConstructor());
-  ASSERT_TRUE(idx->params()[0]->AsConstructor()->IsTypeConstructor());
-  auto* tc = idx->params()[0]->AsConstructor()->AsTypeConstructor();
+  ASSERT_TRUE(idx->params()[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[0]->Is<ast::TypeConstructorExpression>());
+  auto* tc = idx->params()[0]->As<ast::TypeConstructorExpression>();
   EXPECT_TRUE(tc->type()->Is<ast::type::U32Type>());
   ASSERT_EQ(tc->values().size(), 1u);
 
   auto* sub = tc->values()[0];
-  ASSERT_TRUE(sub->IsArrayAccessor());
-  ASSERT_TRUE(sub->AsArrayAccessor()->idx_expr()->IsCall());
+  ASSERT_TRUE(sub->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(sub->As<ast::ArrayAccessorExpression>()
+                  ->idx_expr()
+                  ->Is<ast::CallExpression>());
 
-  auto* sub_idx = sub->AsArrayAccessor()->idx_expr()->AsCall();
-  ASSERT_TRUE(sub_idx->func()->IsIdentifier());
-  EXPECT_EQ(sub_idx->func()->AsIdentifier()->name(), "min");
+  auto* sub_idx = sub->As<ast::ArrayAccessorExpression>()
+                      ->idx_expr()
+                      ->As<ast::CallExpression>();
+  ASSERT_TRUE(sub_idx->func()->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(sub_idx->func()->As<ast::IdentifierExpression>()->name(), "min");
 
-  ASSERT_TRUE(sub_idx->params()[0]->IsConstructor());
-  ASSERT_TRUE(sub_idx->params()[0]->AsConstructor()->IsTypeConstructor());
-  tc = sub_idx->params()[0]->AsConstructor()->AsTypeConstructor();
+  ASSERT_TRUE(sub_idx->params()[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(sub_idx->params()[0]->Is<ast::TypeConstructorExpression>());
+  tc = sub_idx->params()[0]->As<ast::TypeConstructorExpression>();
   EXPECT_TRUE(tc->type()->Is<ast::type::U32Type>());
   ASSERT_EQ(tc->values().size(), 1u);
   ASSERT_EQ(tc->values()[0], b_access_idx);
 
-  ASSERT_TRUE(sub_idx->params()[1]->IsConstructor());
-  ASSERT_TRUE(sub_idx->params()[1]->AsConstructor()->IsScalarConstructor());
-  auto* scalar = sub_idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(sub_idx->params()[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(sub_idx->params()[1]->Is<ast::ScalarConstructorExpression>());
+  auto* scalar = sub_idx->params()[1]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 4u);
 
-  ASSERT_TRUE(idx->params()[1]->IsConstructor());
-  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
-  scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ScalarConstructorExpression>());
+  scalar = idx->params()[1]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
@@ -265,11 +269,11 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
 
@@ -313,25 +317,25 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsCall());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::CallExpression>());
 
-  auto* idx = ptr->idx_expr()->AsCall();
-  ASSERT_TRUE(idx->func()->IsIdentifier());
-  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "min");
+  auto* idx = ptr->idx_expr()->As<ast::CallExpression>();
+  ASSERT_TRUE(idx->func()->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(idx->func()->As<ast::IdentifierExpression>()->name(), "min");
 
   ASSERT_EQ(idx->params().size(), 2u);
 
-  ASSERT_TRUE(idx->params()[0]->IsConstructor());
-  ASSERT_TRUE(idx->params()[0]->AsConstructor()->IsTypeConstructor());
-  auto* tc = idx->params()[0]->AsConstructor()->AsTypeConstructor();
+  ASSERT_TRUE(idx->params()[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[0]->Is<ast::TypeConstructorExpression>());
+  auto* tc = idx->params()[0]->As<ast::TypeConstructorExpression>();
   EXPECT_TRUE(tc->type()->Is<ast::type::U32Type>());
   ASSERT_EQ(tc->values().size(), 1u);
   ASSERT_EQ(tc->values()[0], access_idx);
 
-  ASSERT_TRUE(idx->params()[1]->IsConstructor());
-  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
-  auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ScalarConstructorExpression>());
+  auto* scalar = idx->params()[1]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
@@ -366,11 +370,11 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsSint());
   EXPECT_EQ(scalar->literal()->AsSint()->value(), 0);
 
@@ -405,11 +409,11 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
@@ -444,11 +448,11 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
 
@@ -492,24 +496,24 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsCall());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::CallExpression>());
 
-  auto* idx = ptr->idx_expr()->AsCall();
-  ASSERT_TRUE(idx->func()->IsIdentifier());
-  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "min");
+  auto* idx = ptr->idx_expr()->As<ast::CallExpression>();
+  ASSERT_TRUE(idx->func()->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(idx->func()->As<ast::IdentifierExpression>()->name(), "min");
 
   ASSERT_EQ(idx->params().size(), 2u);
-  ASSERT_TRUE(idx->params()[0]->IsConstructor());
-  ASSERT_TRUE(idx->params()[0]->AsConstructor()->IsTypeConstructor());
-  auto* tc = idx->params()[0]->AsConstructor()->AsTypeConstructor();
+  ASSERT_TRUE(idx->params()[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[0]->Is<ast::TypeConstructorExpression>());
+  auto* tc = idx->params()[0]->As<ast::TypeConstructorExpression>();
   EXPECT_TRUE(tc->type()->Is<ast::type::U32Type>());
   ASSERT_EQ(tc->values().size(), 1u);
   ASSERT_EQ(tc->values()[0], access_idx);
 
-  ASSERT_TRUE(idx->params()[1]->IsConstructor());
-  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
-  auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ScalarConstructorExpression>());
+  auto* scalar = idx->params()[1]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
@@ -544,11 +548,11 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsSint());
   EXPECT_EQ(scalar->literal()->AsSint()->value(), 0);
 
@@ -583,11 +587,11 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
@@ -625,24 +629,24 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
 
-  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
-  auto* ary = ptr->array()->AsArrayAccessor();
-  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->array()->Is<ast::ArrayAccessorExpression>());
+  auto* ary = ptr->array()->As<ast::ArrayAccessorExpression>();
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ary->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
   ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
   ASSERT_TRUE(ary->idx_expr()->result_type()->Is<ast::type::U32Type>());
 
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
 
@@ -689,38 +693,38 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
 
-  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
-  auto* ary = ptr->array()->AsArrayAccessor();
+  ASSERT_TRUE(ptr->array()->Is<ast::ArrayAccessorExpression>());
+  auto* ary = ptr->array()->As<ast::ArrayAccessorExpression>();
 
-  ASSERT_TRUE(ary->idx_expr()->IsCall());
-  auto* idx = ary->idx_expr()->AsCall();
-  ASSERT_TRUE(idx->func()->IsIdentifier());
-  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "min");
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::CallExpression>());
+  auto* idx = ary->idx_expr()->As<ast::CallExpression>();
+  ASSERT_TRUE(idx->func()->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(idx->func()->As<ast::IdentifierExpression>()->name(), "min");
 
   ASSERT_EQ(idx->params().size(), 2u);
 
-  ASSERT_TRUE(idx->params()[0]->IsConstructor());
-  ASSERT_TRUE(idx->params()[0]->AsConstructor()->IsTypeConstructor());
-  auto* tc = idx->params()[0]->AsConstructor()->AsTypeConstructor();
+  ASSERT_TRUE(idx->params()[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[0]->Is<ast::TypeConstructorExpression>());
+  auto* tc = idx->params()[0]->As<ast::TypeConstructorExpression>();
   EXPECT_TRUE(tc->type()->Is<ast::type::U32Type>());
   ASSERT_EQ(tc->values().size(), 1u);
   ASSERT_EQ(tc->values()[0], access_idx);
 
-  ASSERT_TRUE(idx->params()[1]->IsConstructor());
-  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
-  auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ScalarConstructorExpression>());
+  auto* scalar = idx->params()[1]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
   ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
   ASSERT_TRUE(ary->idx_expr()->result_type()->Is<ast::type::U32Type>());
 
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
 
@@ -768,35 +772,35 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
 
-  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
-  auto* ary = ptr->array()->AsArrayAccessor();
+  ASSERT_TRUE(ptr->array()->Is<ast::ArrayAccessorExpression>());
+  auto* ary = ptr->array()->As<ast::ArrayAccessorExpression>();
 
-  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ary->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
 
-  ASSERT_TRUE(ptr->idx_expr()->IsCall());
-  auto* idx = ptr->idx_expr()->AsCall();
-  ASSERT_TRUE(idx->func()->IsIdentifier());
-  EXPECT_EQ(idx->func()->AsIdentifier()->name(), "min");
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::CallExpression>());
+  auto* idx = ptr->idx_expr()->As<ast::CallExpression>();
+  ASSERT_TRUE(idx->func()->Is<ast::IdentifierExpression>());
+  EXPECT_EQ(idx->func()->As<ast::IdentifierExpression>()->name(), "min");
 
   ASSERT_EQ(idx->params().size(), 2u);
 
-  ASSERT_TRUE(idx->params()[0]->IsConstructor());
-  ASSERT_TRUE(idx->params()[0]->AsConstructor()->IsTypeConstructor());
-  auto* tc = idx->params()[0]->AsConstructor()->AsTypeConstructor();
+  ASSERT_TRUE(idx->params()[0]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[0]->Is<ast::TypeConstructorExpression>());
+  auto* tc = idx->params()[0]->As<ast::TypeConstructorExpression>();
   EXPECT_TRUE(tc->type()->Is<ast::type::U32Type>());
   ASSERT_EQ(tc->values().size(), 1u);
   ASSERT_EQ(tc->values()[0], access_idx);
 
-  ASSERT_TRUE(idx->params()[1]->IsConstructor());
-  ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor());
-  scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(idx->params()[1]->Is<ast::ScalarConstructorExpression>());
+  scalar = idx->params()[1]->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
 
@@ -836,24 +840,24 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
 
-  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
-  auto* ary = ptr->array()->AsArrayAccessor();
-  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->array()->Is<ast::ArrayAccessorExpression>());
+  auto* ary = ptr->array()->As<ast::ArrayAccessorExpression>();
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ary->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsSint());
   EXPECT_EQ(scalar->literal()->AsSint()->value(), 0);
 
   ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
   ASSERT_TRUE(ary->idx_expr()->result_type()->Is<ast::type::I32Type>());
 
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsSint());
   EXPECT_EQ(scalar->literal()->AsSint()->value(), 1);
 
@@ -890,24 +894,24 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
 
-  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
-  auto* ary = ptr->array()->AsArrayAccessor();
-  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->array()->Is<ast::ArrayAccessorExpression>());
+  auto* ary = ptr->array()->As<ast::ArrayAccessorExpression>();
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ary->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsSint());
   EXPECT_EQ(scalar->literal()->AsSint()->value(), 2);
 
   ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
   ASSERT_TRUE(ary->idx_expr()->result_type()->Is<ast::type::I32Type>());
 
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsSint());
   EXPECT_EQ(scalar->literal()->AsSint()->value(), 0);
 
@@ -945,24 +949,24 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
 
-  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
-  auto* ary = ptr->array()->AsArrayAccessor();
-  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->array()->Is<ast::ArrayAccessorExpression>());
+  auto* ary = ptr->array()->As<ast::ArrayAccessorExpression>();
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ary->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
   ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
   ASSERT_TRUE(ary->idx_expr()->result_type()->Is<ast::type::U32Type>());
 
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
 
@@ -1000,24 +1004,24 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   ASSERT_TRUE(manager()->Run());
-  ASSERT_TRUE(ptr->IsArrayAccessor());
+  ASSERT_TRUE(ptr->Is<ast::ArrayAccessorExpression>());
 
-  ASSERT_TRUE(ptr->array()->IsArrayAccessor());
-  auto* ary = ptr->array()->AsArrayAccessor();
-  ASSERT_TRUE(ary->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->array()->Is<ast::ArrayAccessorExpression>());
+  auto* ary = ptr->array()->As<ast::ArrayAccessorExpression>();
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ary->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor();
+  auto* scalar = ary->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u);
 
   ASSERT_NE(ary->idx_expr()->result_type(), nullptr);
   ASSERT_TRUE(ary->idx_expr()->result_type()->Is<ast::type::U32Type>());
 
-  ASSERT_TRUE(ptr->idx_expr()->IsConstructor());
-  ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ConstructorExpression>());
+  ASSERT_TRUE(ptr->idx_expr()->Is<ast::ScalarConstructorExpression>());
 
-  scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor();
+  scalar = ptr->idx_expr()->As<ast::ScalarConstructorExpression>();
   ASSERT_TRUE(scalar->literal()->IsUint());
   EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u);
 
diff --git a/src/type_determiner.cc b/src/type_determiner.cc
index 6a83e74..6d3a38e 100644
--- a/src/type_determiner.cc
+++ b/src/type_determiner.cc
@@ -289,29 +289,29 @@
     return true;
   }
 
-  if (expr->IsArrayAccessor()) {
-    return DetermineArrayAccessor(expr->AsArrayAccessor());
+  if (auto* a = expr->As<ast::ArrayAccessorExpression>()) {
+    return DetermineArrayAccessor(a);
   }
-  if (expr->IsBinary()) {
-    return DetermineBinary(expr->AsBinary());
+  if (auto* b = expr->As<ast::BinaryExpression>()) {
+    return DetermineBinary(b);
   }
-  if (expr->IsBitcast()) {
-    return DetermineBitcast(expr->AsBitcast());
+  if (auto* b = expr->As<ast::BitcastExpression>()) {
+    return DetermineBitcast(b);
   }
-  if (expr->IsCall()) {
-    return DetermineCall(expr->AsCall());
+  if (auto* c = expr->As<ast::CallExpression>()) {
+    return DetermineCall(c);
   }
-  if (expr->IsConstructor()) {
-    return DetermineConstructor(expr->AsConstructor());
+  if (auto* c = expr->As<ast::ConstructorExpression>()) {
+    return DetermineConstructor(c);
   }
-  if (expr->IsIdentifier()) {
-    return DetermineIdentifier(expr->AsIdentifier());
+  if (auto* i = expr->As<ast::IdentifierExpression>()) {
+    return DetermineIdentifier(i);
   }
-  if (expr->IsMemberAccessor()) {
-    return DetermineMemberAccessor(expr->AsMemberAccessor());
+  if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
+    return DetermineMemberAccessor(m);
   }
-  if (expr->IsUnaryOp()) {
-    return DetermineUnaryOp(expr->AsUnaryOp());
+  if (auto* u = expr->As<ast::UnaryOpExpression>()) {
+    return DetermineUnaryOp(u);
   }
 
   set_error(expr->source(), "unknown expression for type determination");
@@ -380,9 +380,7 @@
   // The expression has to be an identifier as you can't store function pointers
   // but, if it isn't we'll just use the normal result determination to be on
   // the safe side.
-  if (expr->func()->IsIdentifier()) {
-    auto* ident = expr->func()->AsIdentifier();
-
+  if (auto* ident = expr->func()->As<ast::IdentifierExpression>()) {
     if (ident->IsIntrinsic()) {
       if (!DetermineIntrinsic(ident, expr)) {
         return false;
@@ -417,7 +415,7 @@
   }
 
   if (!expr->func()->result_type()) {
-    auto func_name = expr->func()->AsIdentifier()->name();
+    auto func_name = expr->func()->As<ast::IdentifierExpression>()->name();
     set_error(
         expr->source(),
         "v-0005: function must be declared before use: '" + func_name + "'");
diff --git a/src/validator/validator_impl.cc b/src/validator/validator_impl.cc
index eea936e..c00c998 100644
--- a/src/validator/validator_impl.cc
+++ b/src/validator/validator_impl.cc
@@ -394,8 +394,7 @@
     return false;
   }
 
-  if (expr->func()->IsIdentifier()) {
-    auto* ident = expr->func()->AsIdentifier();
+  if (auto* ident = expr->func()->As<ast::IdentifierExpression>()) {
     auto func_name = ident->name();
     if (ident->IsIntrinsic()) {
       // TODO(sarahM0): validate intrinsics - tied with type-determiner
@@ -441,9 +440,8 @@
     return false;
   }
 
-  if (assign->lhs()->IsIdentifier()) {
+  if (auto* ident = assign->lhs()->As<ast::IdentifierExpression>()) {
     ast::Variable* var;
-    auto* ident = assign->lhs()->AsIdentifier();
     if (variable_stack_.get(ident->name(), &var)) {
       if (var->is_const()) {
         add_error(assign->source(), "v-0021",
@@ -477,12 +475,12 @@
   if (!expr) {
     return false;
   }
-  if (expr->IsIdentifier()) {
-    return ValidateIdentifier(expr->AsIdentifier());
+  if (auto* i = expr->As<ast::IdentifierExpression>()) {
+    return ValidateIdentifier(i);
   }
 
-  if (expr->IsCall()) {
-    return ValidateCallExpr(expr->AsCall());
+  if (auto* c = expr->As<ast::CallExpression>()) {
+    return ValidateCallExpr(c);
   }
   return true;
 }
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 0ffbc06..62b46e5 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -82,12 +82,12 @@
 
 std::string get_buffer_name(ast::Expression* expr) {
   for (;;) {
-    if (expr->IsIdentifier()) {
-      return expr->AsIdentifier()->name();
-    } else if (expr->IsMemberAccessor()) {
-      expr = expr->AsMemberAccessor()->structure();
-    } else if (expr->IsArrayAccessor()) {
-      expr = expr->AsArrayAccessor()->array();
+    if (auto* ident = expr->As<ast::IdentifierExpression>()) {
+      return ident->name();
+    } else if (auto* member = expr->As<ast::MemberAccessorExpression>()) {
+      expr = member->structure();
+    } else if (auto* array = expr->As<ast::ArrayAccessorExpression>()) {
+      expr = array->array();
     } else {
       break;
     }
@@ -299,8 +299,7 @@
 
   // If the LHS is an accessor into a storage buffer then we have to
   // emit a Store operation instead of an ='s.
-  if (stmt->lhs()->IsMemberAccessor()) {
-    auto* mem = stmt->lhs()->AsMemberAccessor();
+  if (auto* mem = stmt->lhs()->As<ast::MemberAccessorExpression>()) {
     if (is_storage_buffer_access(mem)) {
       std::ostringstream accessor_out;
       if (!EmitStorageBufferAccessor(pre, accessor_out, mem, stmt->rhs())) {
@@ -310,8 +309,7 @@
       out << accessor_out.str() << ";" << std::endl;
       return true;
     }
-  } else if (stmt->lhs()->IsArrayAccessor()) {
-    auto* ary = stmt->lhs()->AsArrayAccessor();
+  } else if (auto* ary = stmt->lhs()->As<ast::ArrayAccessorExpression>()) {
     if (is_storage_buffer_access(ary)) {
       std::ostringstream accessor_out;
       if (!EmitStorageBufferAccessor(pre, accessor_out, ary, stmt->rhs())) {
@@ -577,12 +575,12 @@
 bool GeneratorImpl::EmitCall(std::ostream& pre,
                              std::ostream& out,
                              ast::CallExpression* expr) {
-  if (!expr->func()->IsIdentifier()) {
+  auto* ident = expr->func()->As<ast::IdentifierExpression>();
+  if (ident == nullptr) {
     error_ = "invalid function name";
     return 0;
   }
 
-  auto* ident = expr->func()->AsIdentifier();
   if (ident->IsIntrinsic()) {
     const auto& params = expr->params();
     if (ident->intrinsic() == ast::Intrinsic::kSelect) {
@@ -731,7 +729,7 @@
                                     ast::CallExpression* expr) {
   make_indent(out);
 
-  auto* ident = expr->func()->AsIdentifier();
+  auto* ident = expr->func()->As<ast::IdentifierExpression>();
 
   auto params = expr->params();
   auto* signature = static_cast<const ast::intrinsic::TextureSignature*>(
@@ -801,7 +799,7 @@
 
 std::string GeneratorImpl::generate_builtin_name(ast::CallExpression* expr) {
   std::string out;
-  auto* ident = expr->func()->AsIdentifier();
+  auto* ident = expr->func()->As<ast::IdentifierExpression>();
   switch (ident->intrinsic()) {
     case ast::Intrinsic::kAcos:
     case ast::Intrinsic::kAsin:
@@ -976,29 +974,29 @@
 bool GeneratorImpl::EmitExpression(std::ostream& pre,
                                    std::ostream& out,
                                    ast::Expression* expr) {
-  if (expr->IsArrayAccessor()) {
-    return EmitArrayAccessor(pre, out, expr->AsArrayAccessor());
+  if (auto* a = expr->As<ast::ArrayAccessorExpression>()) {
+    return EmitArrayAccessor(pre, out, a);
   }
-  if (expr->IsBinary()) {
-    return EmitBinary(pre, out, expr->AsBinary());
+  if (auto* b = expr->As<ast::BinaryExpression>()) {
+    return EmitBinary(pre, out, b);
   }
-  if (expr->IsBitcast()) {
-    return EmitBitcast(pre, out, expr->AsBitcast());
+  if (auto* b = expr->As<ast::BitcastExpression>()) {
+    return EmitBitcast(pre, out, b);
   }
-  if (expr->IsCall()) {
-    return EmitCall(pre, out, expr->AsCall());
+  if (auto* c = expr->As<ast::CallExpression>()) {
+    return EmitCall(pre, out, c);
   }
-  if (expr->IsConstructor()) {
-    return EmitConstructor(pre, out, expr->AsConstructor());
+  if (auto* c = expr->As<ast::ConstructorExpression>()) {
+    return EmitConstructor(pre, out, c);
   }
-  if (expr->IsIdentifier()) {
-    return EmitIdentifier(pre, out, expr->AsIdentifier());
+  if (auto* i = expr->As<ast::IdentifierExpression>()) {
+    return EmitIdentifier(pre, out, i);
   }
-  if (expr->IsMemberAccessor()) {
-    return EmitMemberAccessor(pre, out, expr->AsMemberAccessor());
+  if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
+    return EmitMemberAccessor(pre, out, m);
   }
-  if (expr->IsUnaryOp()) {
-    return EmitUnaryOp(pre, out, expr->AsUnaryOp());
+  if (auto* u = expr->As<ast::UnaryOpExpression>()) {
+    return EmitUnaryOp(pre, out, u);
   }
 
   error_ = "unknown expression type: " + expr->str();
@@ -1016,7 +1014,7 @@
 bool GeneratorImpl::EmitIdentifier(std::ostream&,
                                    std::ostream& out,
                                    ast::IdentifierExpression* expr) {
-  auto* ident = expr->AsIdentifier();
+  auto* ident = expr->As<ast::IdentifierExpression>();
   ast::Variable* var = nullptr;
   if (global_variables_.get(ident->name(), &var)) {
     if (global_is_in_struct(var)) {
@@ -1683,7 +1681,7 @@
   std::ostringstream out;
   bool first = true;
   for (;;) {
-    if (expr->IsIdentifier()) {
+    if (expr->Is<ast::IdentifierExpression>()) {
       break;
     }
 
@@ -1691,8 +1689,7 @@
       out << " + ";
     }
     first = false;
-    if (expr->IsMemberAccessor()) {
-      auto* mem = expr->AsMemberAccessor();
+    if (auto* mem = expr->As<ast::MemberAccessorExpression>()) {
       auto* res_type = mem->structure()->result_type()->UnwrapAll();
       if (res_type->Is<ast::type::StructType>()) {
         auto* str_type = res_type->As<ast::type::StructType>()->impl();
@@ -1726,8 +1723,7 @@
       }
 
       expr = mem->structure();
-    } else if (expr->IsArrayAccessor()) {
-      auto* ary = expr->AsArrayAccessor();
+    } else if (auto* ary = expr->As<ast::ArrayAccessorExpression>()) {
       auto* ary_type = ary->array()->result_type()->UnwrapAll();
 
       out << "(";
@@ -1885,10 +1881,10 @@
   // If it isn't an array or a member accessor we can stop looking as it won't
   // be a storage buffer.
   auto* ary = expr->array();
-  if (ary->IsMemberAccessor()) {
-    return is_storage_buffer_access(ary->AsMemberAccessor());
-  } else if (ary->IsArrayAccessor()) {
-    return is_storage_buffer_access(ary->AsArrayAccessor());
+  if (auto* member = ary->As<ast::MemberAccessorExpression>()) {
+    return is_storage_buffer_access(member);
+  } else if (auto* array = ary->As<ast::ArrayAccessorExpression>()) {
+    return is_storage_buffer_access(array);
   }
   return false;
 }
@@ -1905,17 +1901,16 @@
   }
 
   // Check if this is a storage buffer variable
-  if (structure->IsIdentifier()) {
-    auto* ident = expr->structure()->AsIdentifier();
+  if (auto* ident = expr->structure()->As<ast::IdentifierExpression>()) {
     ast::Variable* var = nullptr;
     if (!global_variables_.get(ident->name(), &var)) {
       return false;
     }
     return var->storage_class() == ast::StorageClass::kStorageBuffer;
-  } else if (structure->IsMemberAccessor()) {
-    return is_storage_buffer_access(structure->AsMemberAccessor());
-  } else if (structure->IsArrayAccessor()) {
-    return is_storage_buffer_access(structure->AsArrayAccessor());
+  } else if (auto* member = structure->As<ast::MemberAccessorExpression>()) {
+    return is_storage_buffer_access(member);
+  } else if (auto* array = structure->As<ast::ArrayAccessorExpression>()) {
+    return is_storage_buffer_access(array);
   }
 
   // Technically I don't think this is possible, but if we don't have a struct
diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
index be2f31b..cbfc7cd 100644
--- a/src/writer/hlsl/generator_impl.h
+++ b/src/writer/hlsl/generator_impl.h
@@ -19,21 +19,28 @@
 #include <unordered_map>
 #include <unordered_set>
 
+#include "src/ast/array_accessor_expression.h"
 #include "src/ast/assignment_statement.h"
+#include "src/ast/binary_expression.h"
+#include "src/ast/bitcast_expression.h"
 #include "src/ast/break_statement.h"
+#include "src/ast/call_expression.h"
 #include "src/ast/case_statement.h"
 #include "src/ast/continue_statement.h"
 #include "src/ast/discard_statement.h"
+#include "src/ast/identifier_expression.h"
 #include "src/ast/if_statement.h"
 #include "src/ast/intrinsic.h"
 #include "src/ast/literal.h"
 #include "src/ast/loop_statement.h"
+#include "src/ast/member_accessor_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/switch_statement.h"
 #include "src/ast/type/struct_type.h"
 #include "src/ast/type_constructor_expression.h"
+#include "src/ast/unary_op_expression.h"
 #include "src/context.h"
 #include "src/scope_stack.h"
 #include "src/writer/hlsl/namer.h"
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 61aec3d..92e4433 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -484,12 +484,13 @@
 }
 
 bool GeneratorImpl::EmitCall(ast::CallExpression* expr) {
-  if (!expr->func()->IsIdentifier()) {
+  auto* ident = expr->func()->As<ast::IdentifierExpression>();
+
+  if (ident == nullptr) {
     error_ = "invalid function name";
     return 0;
   }
 
-  auto* ident = expr->func()->AsIdentifier();
   if (ident->IsIntrinsic()) {
     const auto& params = expr->params();
     if (ident->intrinsic() == ast::Intrinsic::kOuterProduct) {
@@ -658,7 +659,7 @@
 }
 
 bool GeneratorImpl::EmitTextureCall(ast::CallExpression* expr) {
-  auto* ident = expr->func()->AsIdentifier();
+  auto* ident = expr->func()->As<ast::IdentifierExpression>();
 
   auto params = expr->params();
   auto* signature = static_cast<const ast::intrinsic::TextureSignature*>(
@@ -1094,29 +1095,29 @@
 }
 
 bool GeneratorImpl::EmitExpression(ast::Expression* expr) {
-  if (expr->IsArrayAccessor()) {
-    return EmitArrayAccessor(expr->AsArrayAccessor());
+  if (auto* a = expr->As<ast::ArrayAccessorExpression>()) {
+    return EmitArrayAccessor(a);
   }
-  if (expr->IsBinary()) {
-    return EmitBinary(expr->AsBinary());
+  if (auto* b = expr->As<ast::BinaryExpression>()) {
+    return EmitBinary(b);
   }
-  if (expr->IsBitcast()) {
-    return EmitBitcast(expr->AsBitcast());
+  if (auto* b = expr->As<ast::BitcastExpression>()) {
+    return EmitBitcast(b);
   }
-  if (expr->IsCall()) {
-    return EmitCall(expr->AsCall());
+  if (auto* c = expr->As<ast::CallExpression>()) {
+    return EmitCall(c);
   }
-  if (expr->IsConstructor()) {
-    return EmitConstructor(expr->AsConstructor());
+  if (auto* c = expr->As<ast::ConstructorExpression>()) {
+    return EmitConstructor(c);
   }
-  if (expr->IsIdentifier()) {
-    return EmitIdentifier(expr->AsIdentifier());
+  if (auto* i = expr->As<ast::IdentifierExpression>()) {
+    return EmitIdentifier(i);
   }
-  if (expr->IsMemberAccessor()) {
-    return EmitMemberAccessor(expr->AsMemberAccessor());
+  if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
+    return EmitMemberAccessor(m);
   }
-  if (expr->IsUnaryOp()) {
-    return EmitUnaryOp(expr->AsUnaryOp());
+  if (auto* u = expr->As<ast::UnaryOpExpression>()) {
+    return EmitUnaryOp(u);
   }
 
   error_ = "unknown expression type: " + expr->str();
@@ -1501,7 +1502,7 @@
 }
 
 bool GeneratorImpl::EmitIdentifier(ast::IdentifierExpression* expr) {
-  auto* ident = expr->AsIdentifier();
+  auto* ident = expr->As<ast::IdentifierExpression>();
   ast::Variable* var = nullptr;
   if (global_variables_.get(ident->name(), &var)) {
     if (global_is_in_struct(var)) {
diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h
index ca95151..2b8a687 100644
--- a/src/writer/msl/generator_impl.h
+++ b/src/writer/msl/generator_impl.h
@@ -19,22 +19,29 @@
 #include <string>
 #include <unordered_map>
 
+#include "src/ast/array_accessor_expression.h"
 #include "src/ast/assignment_statement.h"
+#include "src/ast/binary_expression.h"
+#include "src/ast/bitcast_expression.h"
 #include "src/ast/break_statement.h"
+#include "src/ast/call_expression.h"
 #include "src/ast/case_statement.h"
 #include "src/ast/continue_statement.h"
 #include "src/ast/discard_statement.h"
 #include "src/ast/else_statement.h"
+#include "src/ast/identifier_expression.h"
 #include "src/ast/if_statement.h"
 #include "src/ast/intrinsic.h"
 #include "src/ast/literal.h"
 #include "src/ast/loop_statement.h"
+#include "src/ast/member_accessor_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/switch_statement.h"
 #include "src/ast/type/struct_type.h"
 #include "src/ast/type_constructor_expression.h"
+#include "src/ast/unary_op_expression.h"
 #include "src/scope_stack.h"
 #include "src/writer/msl/namer.h"
 #include "src/writer/text_generator.h"
diff --git a/src/writer/pack_coord_arrayidx.cc b/src/writer/pack_coord_arrayidx.cc
index 8004c0a..6fb9b6d 100644
--- a/src/writer/pack_coord_arrayidx.cc
+++ b/src/writer/pack_coord_arrayidx.cc
@@ -26,13 +26,10 @@
 namespace {
 
 ast::TypeConstructorExpression* AsVectorConstructor(ast::Expression* expr) {
-  if (!expr->IsConstructor())
-    return nullptr;
-  auto* constructor = expr->AsConstructor();
-  if (!constructor->IsTypeConstructor()) {
+  auto* type_constructor = expr->As<ast::TypeConstructorExpression>();
+  if (type_constructor == nullptr) {
     return nullptr;
   }
-  auto* type_constructor = constructor->AsTypeConstructor();
   if (!type_constructor->type()->Is<ast::type::VectorType>()) {
     return nullptr;
   }
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 0f577c1..ef9dc6f 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -484,29 +484,29 @@
 }
 
 uint32_t Builder::GenerateExpression(ast::Expression* expr) {
-  if (expr->IsArrayAccessor()) {
-    return GenerateAccessorExpression(expr->AsArrayAccessor());
+  if (auto* a = expr->As<ast::ArrayAccessorExpression>()) {
+    return GenerateAccessorExpression(a);
   }
-  if (expr->IsBinary()) {
-    return GenerateBinaryExpression(expr->AsBinary());
+  if (auto* b = expr->As<ast::BinaryExpression>()) {
+    return GenerateBinaryExpression(b);
   }
-  if (expr->IsBitcast()) {
-    return GenerateBitcastExpression(expr->AsBitcast());
+  if (auto* b = expr->As<ast::BitcastExpression>()) {
+    return GenerateBitcastExpression(b);
   }
-  if (expr->IsCall()) {
-    return GenerateCallExpression(expr->AsCall());
+  if (auto* c = expr->As<ast::CallExpression>()) {
+    return GenerateCallExpression(c);
   }
-  if (expr->IsConstructor()) {
-    return GenerateConstructorExpression(nullptr, expr->AsConstructor(), false);
+  if (auto* c = expr->As<ast::ConstructorExpression>()) {
+    return GenerateConstructorExpression(nullptr, c, false);
   }
-  if (expr->IsIdentifier()) {
-    return GenerateIdentifierExpression(expr->AsIdentifier());
+  if (auto* i = expr->As<ast::IdentifierExpression>()) {
+    return GenerateIdentifierExpression(i);
   }
-  if (expr->IsMemberAccessor()) {
-    return GenerateAccessorExpression(expr->AsMemberAccessor());
+  if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
+    return GenerateAccessorExpression(m);
   }
-  if (expr->IsUnaryOp()) {
-    return GenerateUnaryOpExpression(expr->AsUnaryOp());
+  if (auto* u = expr->As<ast::UnaryOpExpression>()) {
+    return GenerateUnaryOpExpression(u);
   }
 
   error_ = "unknown expression type: " + expr->str();
@@ -672,13 +672,13 @@
 bool Builder::GenerateGlobalVariable(ast::Variable* var) {
   uint32_t init_id = 0;
   if (var->has_constructor()) {
-    if (!var->constructor()->IsConstructor()) {
+    if (!var->constructor()->Is<ast::ConstructorExpression>()) {
       error_ = "scalar constructor expected";
       return false;
     }
 
     init_id = GenerateConstructorExpression(
-        var, var->constructor()->AsConstructor(), true);
+        var, var->constructor()->As<ast::ConstructorExpression>(), true);
     if (init_id == 0) {
       return false;
     }
@@ -981,7 +981,8 @@
 }
 
 uint32_t Builder::GenerateAccessorExpression(ast::Expression* expr) {
-  assert(expr->IsArrayAccessor() || expr->IsMemberAccessor());
+  assert(expr->Is<ast::ArrayAccessorExpression>() ||
+         expr->Is<ast::MemberAccessorExpression>());
 
   // Gather a list of all the member and array accessors that are in this chain.
   // The list is built in reverse order as that's the order we need to access
@@ -989,12 +990,12 @@
   std::vector<ast::Expression*> accessors;
   ast::Expression* source = expr;
   while (true) {
-    if (source->IsArrayAccessor()) {
+    if (auto* array = source->As<ast::ArrayAccessorExpression>()) {
       accessors.insert(accessors.begin(), source);
-      source = source->AsArrayAccessor()->array();
-    } else if (source->IsMemberAccessor()) {
+      source = array->array();
+    } else if (auto* member = source->As<ast::MemberAccessorExpression>()) {
       accessors.insert(accessors.begin(), source);
-      source = source->AsMemberAccessor()->structure();
+      source = member->structure();
     } else {
       break;
     }
@@ -1010,9 +1011,8 @@
   // If our initial access is into an array of non-scalar types, and that array
   // is not a pointer, then we need to load that array into a variable in order
   // to access chain into the array.
-  if (accessors[0]->IsArrayAccessor()) {
-    auto* ary_res_type =
-        accessors[0]->AsArrayAccessor()->array()->result_type();
+  if (auto* array = accessors[0]->As<ast::ArrayAccessorExpression>()) {
+    auto* ary_res_type = array->array()->result_type();
 
     if (!ary_res_type->Is<ast::type::PointerType>() &&
         (ary_res_type->Is<ast::type::ArrayType>() &&
@@ -1043,12 +1043,12 @@
 
   std::vector<uint32_t> access_chain_indices;
   for (auto* accessor : accessors) {
-    if (accessor->IsArrayAccessor()) {
-      if (!GenerateArrayAccessor(accessor->AsArrayAccessor(), &info)) {
+    if (auto* array = accessor->As<ast::ArrayAccessorExpression>()) {
+      if (!GenerateArrayAccessor(array, &info)) {
         return 0;
       }
-    } else if (accessor->IsMemberAccessor()) {
-      if (!GenerateMemberAccessor(accessor->AsMemberAccessor(), &info)) {
+    } else if (auto* member = accessor->As<ast::MemberAccessorExpression>()) {
+      if (!GenerateMemberAccessor(member, &info)) {
         return 0;
       }
 
@@ -1170,19 +1170,20 @@
 }
 
 bool Builder::is_constructor_const(ast::Expression* expr, bool is_global_init) {
-  if (!expr->IsConstructor()) {
+  auto* constructor = expr->As<ast::ConstructorExpression>();
+  if (constructor == nullptr) {
     return false;
   }
-  if (expr->AsConstructor()->IsScalarConstructor()) {
+  if (constructor->Is<ast::ScalarConstructorExpression>()) {
     return true;
   }
 
-  auto* tc = expr->AsConstructor()->AsTypeConstructor();
+  auto* tc = constructor->AsTypeConstructor();
   auto* result_type = tc->type()->UnwrapAll();
   for (size_t i = 0; i < tc->values().size(); ++i) {
     auto* e = tc->values()[i];
 
-    if (!e->IsConstructor()) {
+    if (!e->Is<ast::ConstructorExpression>()) {
       if (is_global_init) {
         error_ = "constructor must be a constant expression";
         return false;
@@ -1197,16 +1198,16 @@
     }
 
     if (result_type->Is<ast::type::VectorType>() &&
-        !e->AsConstructor()->IsScalarConstructor()) {
+        !e->Is<ast::ScalarConstructorExpression>()) {
       return false;
     }
 
     // This should all be handled by |is_constructor_const| call above
-    if (!e->AsConstructor()->IsScalarConstructor()) {
+    if (!e->Is<ast::ScalarConstructorExpression>()) {
       continue;
     }
 
-    auto* sc = e->AsConstructor()->AsScalarConstructor();
+    auto* sc = e->As<ast::ScalarConstructorExpression>();
     ast::type::Type* subtype = result_type->UnwrapAll();
     if (subtype->Is<ast::type::VectorType>()) {
       subtype = subtype->As<ast::type::VectorType>()->type()->UnwrapAll();
@@ -1279,8 +1280,8 @@
   for (auto* e : values) {
     uint32_t id = 0;
     if (constructor_is_const) {
-      id = GenerateConstructorExpression(nullptr, e->AsConstructor(),
-                                         is_global_init);
+      id = GenerateConstructorExpression(
+          nullptr, e->As<ast::ConstructorExpression>(), is_global_init);
     } else {
       id = GenerateExpression(e);
       id = GenerateLoadIfNeeded(e->result_type(), id);
@@ -1751,13 +1752,13 @@
 }
 
 uint32_t Builder::GenerateCallExpression(ast::CallExpression* expr) {
-  if (!expr->func()->IsIdentifier()) {
+  auto* ident = expr->func()->As<ast::IdentifierExpression>();
+
+  if (ident == nullptr) {
     error_ = "invalid function name";
     return 0;
   }
 
-  auto* ident = expr->func()->AsIdentifier();
-
   if (ident->IsIntrinsic()) {
     return GenerateIntrinsic(ident, expr);
   }
@@ -1826,7 +1827,7 @@
     if (call->params().empty()) {
       error_ = "missing param for runtime array length";
       return 0;
-    } else if (!call->params()[0]->IsMemberAccessor()) {
+    } else if (!call->params()[0]->Is<ast::MemberAccessorExpression>()) {
       if (call->params()[0]->result_type()->Is<ast::type::PointerType>()) {
         error_ = "pointer accessors not supported yet";
       } else {
@@ -1834,7 +1835,7 @@
       }
       return 0;
     }
-    auto* accessor = call->params()[0]->AsMemberAccessor();
+    auto* accessor = call->params()[0]->As<ast::MemberAccessorExpression>();
     auto struct_id = GenerateExpression(accessor->structure());
     if (struct_id == 0) {
       return 0;
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index a8ed01c..d78a9a2 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -22,15 +22,21 @@
 #include <vector>
 
 #include "spirv/unified1/spirv.h"
+#include "src/ast/array_accessor_expression.h"
 #include "src/ast/assignment_statement.h"
+#include "src/ast/binary_expression.h"
+#include "src/ast/bitcast_expression.h"
 #include "src/ast/break_statement.h"
 #include "src/ast/builtin.h"
+#include "src/ast/call_expression.h"
 #include "src/ast/continue_statement.h"
 #include "src/ast/discard_statement.h"
 #include "src/ast/else_statement.h"
+#include "src/ast/identifier_expression.h"
 #include "src/ast/if_statement.h"
 #include "src/ast/literal.h"
 #include "src/ast/loop_statement.h"
+#include "src/ast/member_accessor_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/struct_member.h"
@@ -43,6 +49,7 @@
 #include "src/ast/type/struct_type.h"
 #include "src/ast/type/vector_type.h"
 #include "src/ast/type_constructor_expression.h"
+#include "src/ast/unary_op_expression.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/context.h"
 #include "src/scope_stack.h"
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index 5fa37cb..6942a34 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -193,29 +193,29 @@
 }
 
 bool GeneratorImpl::EmitExpression(ast::Expression* expr) {
-  if (expr->IsArrayAccessor()) {
-    return EmitArrayAccessor(expr->AsArrayAccessor());
+  if (auto* a = expr->As<ast::ArrayAccessorExpression>()) {
+    return EmitArrayAccessor(a);
   }
-  if (expr->IsBinary()) {
-    return EmitBinary(expr->AsBinary());
+  if (auto* b = expr->As<ast::BinaryExpression>()) {
+    return EmitBinary(b);
   }
-  if (expr->IsBitcast()) {
-    return EmitBitcast(expr->AsBitcast());
+  if (auto* b = expr->As<ast::BitcastExpression>()) {
+    return EmitBitcast(b);
   }
-  if (expr->IsCall()) {
-    return EmitCall(expr->AsCall());
+  if (auto* c = expr->As<ast::CallExpression>()) {
+    return EmitCall(c);
   }
-  if (expr->IsIdentifier()) {
-    return EmitIdentifier(expr->AsIdentifier());
+  if (auto* i = expr->As<ast::IdentifierExpression>()) {
+    return EmitIdentifier(i);
   }
-  if (expr->IsConstructor()) {
-    return EmitConstructor(expr->AsConstructor());
+  if (auto* c = expr->As<ast::ConstructorExpression>()) {
+    return EmitConstructor(c);
   }
-  if (expr->IsMemberAccessor()) {
-    return EmitMemberAccessor(expr->AsMemberAccessor());
+  if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
+    return EmitMemberAccessor(m);
   }
-  if (expr->IsUnaryOp()) {
-    return EmitUnaryOp(expr->AsUnaryOp());
+  if (auto* u = expr->As<ast::UnaryOpExpression>()) {
+    return EmitUnaryOp(u);
   }
 
   error_ = "unknown expression type";
@@ -337,7 +337,7 @@
 }
 
 bool GeneratorImpl::EmitIdentifier(ast::IdentifierExpression* expr) {
-  auto* ident = expr->AsIdentifier();
+  auto* ident = expr->As<ast::IdentifierExpression>();
   out_ << ident->name();
   return true;
 }
diff --git a/src/writer/wgsl/generator_impl.h b/src/writer/wgsl/generator_impl.h
index 77fd16d..1de0b9d 100644
--- a/src/writer/wgsl/generator_impl.h
+++ b/src/writer/wgsl/generator_impl.h
@@ -20,7 +20,10 @@
 
 #include "src/ast/array_accessor_expression.h"
 #include "src/ast/assignment_statement.h"
+#include "src/ast/binary_expression.h"
+#include "src/ast/bitcast_expression.h"
 #include "src/ast/break_statement.h"
+#include "src/ast/call_expression.h"
 #include "src/ast/case_statement.h"
 #include "src/ast/constructor_expression.h"
 #include "src/ast/continue_statement.h"
@@ -29,6 +32,7 @@
 #include "src/ast/identifier_expression.h"
 #include "src/ast/if_statement.h"
 #include "src/ast/loop_statement.h"
+#include "src/ast/member_accessor_expression.h"
 #include "src/ast/module.h"
 #include "src/ast/return_statement.h"
 #include "src/ast/scalar_constructor_expression.h"
@@ -37,6 +41,7 @@
 #include "src/ast/type/struct_type.h"
 #include "src/ast/type/type.h"
 #include "src/ast/type_constructor_expression.h"
+#include "src/ast/unary_op_expression.h"
 #include "src/ast/variable.h"
 #include "src/writer/text_generator.h"