tint: Merge [S|U]intLiteralExpression

Merge SintLiteralExpression and UintLiteralExpression with
IntLiteralExpression. IntLiteralExpression has a new Suffix field which
indicates whether the literal is either a:
• 'i' suffixed integer literal
• 'u' suffixed integer literal
• no-suffix integer literal

Have the SPIR-V reader produce no-suffixed literals for i32 types, to
keep this change small. In future changes the SPIR-V reader will
produce 'i' suffixed types for these.

Have all consumers of IntLiteralExpression treat unsuffixed integers the
same as 'i'-suffixed literals. Unsuffixed will be treated as abstract in
future changes.

Removed SemHelper::TypeOf(const ast::LiteralExpression* lit).

Fixed: tint:1510
Bug: tint:1504
Change-Id: I443f41984e637ddd948182ee756af1010c5f8226
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/88842
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 1b30b66..287c40f 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -288,8 +288,6 @@
     "ast/sampled_texture.h",
     "ast/sampler.cc",
     "ast/sampler.h",
-    "ast/sint_literal_expression.cc",
-    "ast/sint_literal_expression.h",
     "ast/stage_attribute.cc",
     "ast/stage_attribute.h",
     "ast/statement.cc",
@@ -322,8 +320,6 @@
     "ast/type_name.h",
     "ast/u32.cc",
     "ast/u32.h",
-    "ast/uint_literal_expression.cc",
-    "ast/uint_literal_expression.h",
     "ast/unary_op.cc",
     "ast/unary_op.h",
     "ast/unary_op_expression.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index ec533ea..1b86aff 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -174,8 +174,6 @@
   ast/sampled_texture.h
   ast/sampler.cc
   ast/sampler.h
-  ast/sint_literal_expression.cc
-  ast/sint_literal_expression.h
   ast/stage_attribute.cc
   ast/stage_attribute.h
   ast/statement.cc
@@ -211,8 +209,6 @@
   ast/type_name.h
   ast/u32.cc
   ast/u32.h
-  ast/uint_literal_expression.cc
-  ast/uint_literal_expression.h
   ast/unary_op_expression.cc
   ast/unary_op_expression.h
   ast/unary_op.cc
@@ -711,7 +707,6 @@
     ast/return_statement_test.cc
     ast/sampled_texture_test.cc
     ast/sampler_test.cc
-    ast/sint_literal_expression_test.cc
     ast/stage_attribute_test.cc
     ast/storage_texture_test.cc
     ast/stride_attribute_test.cc
@@ -725,7 +720,6 @@
     ast/texture_test.cc
     ast/traverse_expressions_test.cc
     ast/u32_test.cc
-    ast/uint_literal_expression_test.cc
     ast/unary_op_expression_test.cc
     ast/variable_decl_statement_test.cc
     ast/variable_test.cc
diff --git a/src/tint/ast/array.cc b/src/tint/ast/array.cc
index 6083e69..0389ed0 100644
--- a/src/tint/ast/array.cc
+++ b/src/tint/ast/array.cc
@@ -29,7 +29,7 @@
         return symbols.NameFor(ident->symbol);
     }
     if (auto* literal = size->As<IntLiteralExpression>()) {
-        return std::to_string(literal->ValueAsU32());
+        return std::to_string(literal->value);
     }
     // This will never be exposed to the user as the Resolver will reject this
     // expression for array size.
diff --git a/src/tint/ast/case_statement_test.cc b/src/tint/ast/case_statement_test.cc
index 1c30de8..314a642 100644
--- a/src/tint/ast/case_statement_test.cc
+++ b/src/tint/ast/case_statement_test.cc
@@ -26,7 +26,7 @@
 
 TEST_F(CaseStatementTest, Creation_i32) {
     CaseSelectorList b;
-    auto* selector = create<SintLiteralExpression>(2);
+    auto* selector = Expr(2);
     b.push_back(selector);
 
     auto* discard = create<DiscardStatement>();
@@ -41,7 +41,7 @@
 
 TEST_F(CaseStatementTest, Creation_u32) {
     CaseSelectorList b;
-    auto* selector = create<UintLiteralExpression>(2u);
+    auto* selector = Expr(2u);
     b.push_back(selector);
 
     auto* discard = create<DiscardStatement>();
@@ -56,7 +56,7 @@
 
 TEST_F(CaseStatementTest, Creation_WithSource) {
     CaseSelectorList b;
-    b.push_back(create<SintLiteralExpression>(2));
+    b.push_back(Expr(2));
 
     auto* body = create<BlockStatement>(StatementList{
         create<DiscardStatement>(),
@@ -77,7 +77,7 @@
 
 TEST_F(CaseStatementTest, IsDefault_WithSelectors) {
     CaseSelectorList b;
-    b.push_back(create<SintLiteralExpression>(2));
+    b.push_back(Expr(2));
 
     auto* c = create<CaseStatement>(b, create<BlockStatement>(StatementList{}));
     EXPECT_FALSE(c->IsDefault());
@@ -123,7 +123,7 @@
         {
             ProgramBuilder b1;
             ProgramBuilder b2;
-            b1.create<CaseStatement>(CaseSelectorList{b2.create<SintLiteralExpression>(2)},
+            b1.create<CaseStatement>(CaseSelectorList{b2.Expr(2)},
                                      b1.create<BlockStatement>(StatementList{}));
         },
         "internal compiler error");
diff --git a/src/tint/ast/int_literal_expression.cc b/src/tint/ast/int_literal_expression.cc
index f1fd241..7e11f7e 100644
--- a/src/tint/ast/int_literal_expression.cc
+++ b/src/tint/ast/int_literal_expression.cc
@@ -14,12 +14,35 @@
 
 #include "src/tint/ast/int_literal_expression.h"
 
+#include "src/tint/program_builder.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::ast::IntLiteralExpression);
 
 namespace tint::ast {
 
-IntLiteralExpression::IntLiteralExpression(ProgramID pid, const Source& src) : Base(pid, src) {}
+IntLiteralExpression::IntLiteralExpression(ProgramID pid,
+                                           const Source& src,
+                                           int64_t val,
+                                           Suffix suf)
+    : Base(pid, src), value(val), suffix(suf) {}
 
 IntLiteralExpression::~IntLiteralExpression() = default;
 
+const IntLiteralExpression* IntLiteralExpression::Clone(CloneContext* ctx) const {
+    // Clone arguments outside of create() call to have deterministic ordering
+    auto src = ctx->Clone(source);
+    return ctx->dst->create<IntLiteralExpression>(src, value, suffix);
+}
+
+std::ostream& operator<<(std::ostream& out, IntLiteralExpression::Suffix suffix) {
+    switch (suffix) {
+        default:
+            return out;
+        case IntLiteralExpression::Suffix::kI:
+            return out << "i";
+        case IntLiteralExpression::Suffix::kU:
+            return out << "u";
+    }
+}
+
 }  // namespace tint::ast
diff --git a/src/tint/ast/int_literal_expression.h b/src/tint/ast/int_literal_expression.h
index 1900aa7..8ff58ea 100644
--- a/src/tint/ast/int_literal_expression.h
+++ b/src/tint/ast/int_literal_expression.h
@@ -19,23 +19,46 @@
 
 namespace tint::ast {
 
-/// An integer literal. This could be either signed or unsigned.
+/// An integer literal. The literal may have an 'i', 'u' or no suffix.
 class IntLiteralExpression : public Castable<IntLiteralExpression, LiteralExpression> {
   public:
-    ~IntLiteralExpression() override;
+    /// Literal suffix
+    enum class Suffix {
+        /// No suffix
+        kNone,
+        /// 'i' suffix (i32)
+        kI,
+        /// 'u' suffix (u32)
+        kU,
+    };
 
-    /// @returns the literal value as a u32
-    virtual uint32_t ValueAsU32() const = 0;
-
-    /// @returns the literal value as an i32
-    int32_t ValueAsI32() const { return static_cast<int32_t>(ValueAsU32()); }
-
-  protected:
     /// Constructor
     /// @param pid the identifier of the program that owns this node
     /// @param src the source of this node
-    IntLiteralExpression(ProgramID pid, const Source& src);
-};  // namespace ast
+    /// @param val the literal value
+    /// @param suf the literal suffix
+    IntLiteralExpression(ProgramID pid, const Source& src, int64_t val, Suffix suf);
+
+    ~IntLiteralExpression() override;
+
+    /// Clones this node and all transitive child nodes using the `CloneContext`
+    /// `ctx`.
+    /// @param ctx the clone context
+    /// @return the newly cloned node
+    const IntLiteralExpression* Clone(CloneContext* ctx) const override;
+
+    /// The literal value
+    const int64_t value;
+
+    /// The literal suffix
+    const Suffix suffix;
+};
+
+/// Writes the integer literal suffix to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param suffix the suffix to write
+/// @returns out so calls can be chained
+std::ostream& operator<<(std::ostream& out, IntLiteralExpression::Suffix suffix);
 
 }  // namespace tint::ast
 
diff --git a/src/tint/ast/int_literal_expression_test.cc b/src/tint/ast/int_literal_expression_test.cc
index 7f66bcc..8bc3d2f 100644
--- a/src/tint/ast/int_literal_expression_test.cc
+++ b/src/tint/ast/int_literal_expression_test.cc
@@ -19,14 +19,25 @@
 
 using IntLiteralExpressionTest = TestHelper;
 
-TEST_F(IntLiteralExpressionTest, Sint_IsInt) {
-    auto* i = create<SintLiteralExpression>(47);
+TEST_F(IntLiteralExpressionTest, SuffixNone) {
+    auto* i = create<IntLiteralExpression>(42, IntLiteralExpression::Suffix::kNone);
     ASSERT_TRUE(i->Is<IntLiteralExpression>());
+    EXPECT_EQ(i->value, 42);
+    EXPECT_EQ(i->suffix, IntLiteralExpression::Suffix::kNone);
 }
 
-TEST_F(IntLiteralExpressionTest, Uint_IsInt) {
-    auto* i = create<UintLiteralExpression>(42);
-    EXPECT_TRUE(i->Is<IntLiteralExpression>());
+TEST_F(IntLiteralExpressionTest, SuffixI) {
+    auto* i = create<IntLiteralExpression>(42, IntLiteralExpression::Suffix::kI);
+    ASSERT_TRUE(i->Is<IntLiteralExpression>());
+    EXPECT_EQ(i->value, 42);
+    EXPECT_EQ(i->suffix, IntLiteralExpression::Suffix::kI);
+}
+
+TEST_F(IntLiteralExpressionTest, SuffixU) {
+    auto* i = create<IntLiteralExpression>(42, IntLiteralExpression::Suffix::kU);
+    ASSERT_TRUE(i->Is<IntLiteralExpression>());
+    EXPECT_EQ(i->value, 42);
+    EXPECT_EQ(i->suffix, IntLiteralExpression::Suffix::kU);
 }
 
 }  // namespace
diff --git a/src/tint/ast/sint_literal_expression.cc b/src/tint/ast/sint_literal_expression.cc
deleted file mode 100644
index daab883..0000000
--- a/src/tint/ast/sint_literal_expression.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/sint_literal_expression.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::SintLiteralExpression);
-
-namespace tint::ast {
-
-SintLiteralExpression::SintLiteralExpression(ProgramID pid, const Source& src, int32_t val)
-    : Base(pid, src), value(val) {}
-
-SintLiteralExpression::~SintLiteralExpression() = default;
-
-uint32_t SintLiteralExpression::ValueAsU32() const {
-    return static_cast<uint32_t>(value);
-}
-
-const SintLiteralExpression* SintLiteralExpression::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    return ctx->dst->create<SintLiteralExpression>(src, value);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/sint_literal_expression.h b/src/tint/ast/sint_literal_expression.h
deleted file mode 100644
index 931302f..0000000
--- a/src/tint/ast/sint_literal_expression.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_SINT_LITERAL_EXPRESSION_H_
-#define SRC_TINT_AST_SINT_LITERAL_EXPRESSION_H_
-
-#include <string>
-
-#include "src/tint/ast/int_literal_expression.h"
-
-namespace tint::ast {
-
-/// A signed int literal
-class SintLiteralExpression final : public Castable<SintLiteralExpression, IntLiteralExpression> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param src the source of this node
-    /// @param value the signed int literals value
-    SintLiteralExpression(ProgramID pid, const Source& src, int32_t value);
-    ~SintLiteralExpression() override;
-
-    /// @returns the literal value as a u32
-    uint32_t ValueAsU32() const override;
-
-    /// Clones this node and all transitive child nodes using the `CloneContext`
-    /// `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned node
-    const SintLiteralExpression* Clone(CloneContext* ctx) const override;
-
-    /// The int literal value
-    const int32_t value;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_SINT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/sint_literal_expression_test.cc b/src/tint/ast/sint_literal_expression_test.cc
deleted file mode 100644
index 13e17c1..0000000
--- a/src/tint/ast/sint_literal_expression_test.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/test_helper.h"
-
-namespace tint::ast {
-namespace {
-
-using SintLiteralExpressionTest = TestHelper;
-
-TEST_F(SintLiteralExpressionTest, Value) {
-    auto* i = create<SintLiteralExpression>(47);
-    ASSERT_TRUE(i->Is<SintLiteralExpression>());
-    EXPECT_EQ(i->value, 47);
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/switch_statement_test.cc b/src/tint/ast/switch_statement_test.cc
index dfa73fb..9d2d5d2 100644
--- a/src/tint/ast/switch_statement_test.cc
+++ b/src/tint/ast/switch_statement_test.cc
@@ -24,7 +24,7 @@
 
 TEST_F(SwitchStatementTest, Creation) {
     CaseSelectorList lit;
-    lit.push_back(create<SintLiteralExpression>(1));
+    lit.push_back(Expr(1));
 
     auto* ident = Expr("ident");
     CaseStatementList body;
@@ -49,7 +49,7 @@
 
 TEST_F(SwitchStatementTest, IsSwitch) {
     CaseSelectorList lit;
-    lit.push_back(create<SintLiteralExpression>(2));
+    lit.push_back(Expr(2));
 
     auto* ident = Expr("ident");
     CaseStatementList body;
diff --git a/src/tint/ast/uint_literal_expression.cc b/src/tint/ast/uint_literal_expression.cc
deleted file mode 100644
index 53dd199..0000000
--- a/src/tint/ast/uint_literal_expression.cc
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/uint_literal_expression.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::UintLiteralExpression);
-
-namespace tint::ast {
-
-UintLiteralExpression::UintLiteralExpression(ProgramID pid, const Source& src, uint32_t val)
-    : Base(pid, src), value(val) {}
-
-UintLiteralExpression::~UintLiteralExpression() = default;
-
-uint32_t UintLiteralExpression::ValueAsU32() const {
-    return value;
-}
-
-const UintLiteralExpression* UintLiteralExpression::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    return ctx->dst->create<UintLiteralExpression>(src, value);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/uint_literal_expression.h b/src/tint/ast/uint_literal_expression.h
deleted file mode 100644
index bbb3906..0000000
--- a/src/tint/ast/uint_literal_expression.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SRC_TINT_AST_UINT_LITERAL_EXPRESSION_H_
-#define SRC_TINT_AST_UINT_LITERAL_EXPRESSION_H_
-
-#include <string>
-
-#include "src/tint/ast/int_literal_expression.h"
-
-namespace tint::ast {
-
-/// A uint literal
-class UintLiteralExpression final : public Castable<UintLiteralExpression, IntLiteralExpression> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param src the source of this node
-    /// @param value the uint literals value
-    UintLiteralExpression(ProgramID pid, const Source& src, uint32_t value);
-    ~UintLiteralExpression() override;
-
-    /// @returns the literal value as a u32
-    uint32_t ValueAsU32() const override;
-
-    /// Clones this node and all transitive child nodes using the `CloneContext`
-    /// `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned node
-    const UintLiteralExpression* Clone(CloneContext* ctx) const override;
-
-    /// The int literal value
-    const uint32_t value;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_UINT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/uint_literal_expression_test.cc b/src/tint/ast/uint_literal_expression_test.cc
deleted file mode 100644
index 068439a..0000000
--- a/src/tint/ast/uint_literal_expression_test.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "src/tint/ast/test_helper.h"
-
-namespace tint::ast {
-namespace {
-
-using UintLiteralExpressionTest = TestHelper;
-
-TEST_F(UintLiteralExpressionTest, Value) {
-    auto* u = create<UintLiteralExpression>(47);
-    ASSERT_TRUE(u->Is<UintLiteralExpression>());
-    EXPECT_EQ(u->value, 47u);
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/workgroup_attribute_test.cc b/src/tint/ast/workgroup_attribute_test.cc
index 3ae2a98..cc9765a 100644
--- a/src/tint/ast/workgroup_attribute_test.cc
+++ b/src/tint/ast/workgroup_attribute_test.cc
@@ -27,7 +27,7 @@
     auto values = d->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 2);
 
     EXPECT_EQ(values[1], nullptr);
     EXPECT_EQ(values[2], nullptr);
@@ -37,10 +37,10 @@
     auto values = d->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 2);
 
     ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->value, 4);
 
     EXPECT_EQ(values[2], nullptr);
 }
@@ -50,13 +50,13 @@
     auto values = d->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 2);
 
     ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->value, 4);
 
     ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 6u);
+    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->value, 6);
 }
 
 TEST_F(WorkgroupAttributeTest, Creation_WithIdentifier) {
@@ -64,10 +64,10 @@
     auto values = d->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 2);
 
     ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->value, 4);
 
     auto* z_ident = As<ast::IdentifierExpression>(values[2]);
     ASSERT_TRUE(z_ident);
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index ebae93f..bdc14b0 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -24,8 +24,6 @@
 #include "src/tint/ast/interpolate_attribute.h"
 #include "src/tint/ast/location_attribute.h"
 #include "src/tint/ast/module.h"
-#include "src/tint/ast/sint_literal_expression.h"
-#include "src/tint/ast/uint_literal_expression.h"
 #include "src/tint/sem/array.h"
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/depth_multisampled_texture.h"
@@ -248,14 +246,16 @@
             continue;
         }
 
-        if (auto* l = literal->As<ast::UintLiteralExpression>()) {
-            result[constant_id] = Scalar(l->value);
-            continue;
-        }
-
-        if (auto* l = literal->As<ast::SintLiteralExpression>()) {
-            result[constant_id] = Scalar(l->value);
-            continue;
+        if (auto* l = literal->As<ast::IntLiteralExpression>()) {
+            switch (l->suffix) {
+                case ast::IntLiteralExpression::Suffix::kNone:
+                case ast::IntLiteralExpression::Suffix::kI:
+                    result[constant_id] = Scalar(static_cast<int32_t>(l->value));
+                    continue;
+                case ast::IntLiteralExpression::Suffix::kU:
+                    result[constant_id] = Scalar(static_cast<uint32_t>(l->value));
+                    continue;
+            }
         }
 
         if (auto* l = literal->As<ast::FloatLiteralExpression>()) {
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 945d632..c1499b8 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -61,7 +61,6 @@
 #include "src/tint/ast/return_statement.h"
 #include "src/tint/ast/sampled_texture.h"
 #include "src/tint/ast/sampler.h"
-#include "src/tint/ast/sint_literal_expression.h"
 #include "src/tint/ast/stage_attribute.h"
 #include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/stride_attribute.h"
@@ -71,7 +70,6 @@
 #include "src/tint/ast/switch_statement.h"
 #include "src/tint/ast/type_name.h"
 #include "src/tint/ast/u32.h"
-#include "src/tint/ast/uint_literal_expression.h"
 #include "src/tint/ast/unary_op_expression.h"
 #include "src/tint/ast/variable_decl_statement.h"
 #include "src/tint/ast/vector.h"
@@ -1018,27 +1016,29 @@
     /// @param source the source information
     /// @param value the integer value
     /// @return a Scalar constructor for the given value
-    const ast::SintLiteralExpression* Expr(const Source& source, i32 value) {
-        return create<ast::SintLiteralExpression>(source, value);
+    const ast::IntLiteralExpression* Expr(const Source& source, i32 value) {
+        return create<ast::IntLiteralExpression>(source, value,
+                                                 ast::IntLiteralExpression::Suffix::kNone);
     }
 
     /// @param value the integer value
     /// @return a Scalar constructor for the given value
-    const ast::SintLiteralExpression* Expr(i32 value) {
-        return create<ast::SintLiteralExpression>(value);
+    const ast::IntLiteralExpression* Expr(i32 value) {
+        return create<ast::IntLiteralExpression>(value, ast::IntLiteralExpression::Suffix::kNone);
     }
 
     /// @param source the source information
     /// @param value the unsigned int value
     /// @return a Scalar constructor for the given value
-    const ast::UintLiteralExpression* Expr(const Source& source, u32 value) {
-        return create<ast::UintLiteralExpression>(source, value);
+    const ast::IntLiteralExpression* Expr(const Source& source, u32 value) {
+        return create<ast::IntLiteralExpression>(source, value,
+                                                 ast::IntLiteralExpression::Suffix::kU);
     }
 
     /// @param value the unsigned int value
     /// @return a Scalar constructor for the given value
-    const ast::UintLiteralExpression* Expr(u32 value) {
-        return create<ast::UintLiteralExpression>(value);
+    const ast::IntLiteralExpression* Expr(u32 value) {
+        return create<ast::IntLiteralExpression>(value, ast::IntLiteralExpression::Suffix::kU);
     }
 
     /// Converts `arg` to an `ast::Expression` using `Expr()`, then appends it to
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index 4597340..ef3d7f0 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -2986,9 +2986,12 @@
                 // The Tint AST handles 32-bit values.
                 const uint32_t value32 = uint32_t(value & 0xFFFFFFFF);
                 if (selector.type->IsUnsignedScalarOrVector()) {
-                    selectors.emplace_back(create<ast::UintLiteralExpression>(Source{}, value32));
+                    selectors.emplace_back(create<ast::IntLiteralExpression>(
+                        Source{}, value32, ast::IntLiteralExpression::Suffix::kU));
                 } else {
-                    selectors.emplace_back(create<ast::SintLiteralExpression>(Source{}, value32));
+                    selectors.emplace_back(create<ast::IntLiteralExpression>(
+                        Source{}, static_cast<int32_t>(value32),
+                        ast::IntLiteralExpression::Suffix::kNone));
                 }
             }
         }
@@ -4298,7 +4301,8 @@
     auto current_type_id = composite_type_id;
 
     auto make_index = [this](uint32_t literal) {
-        return create<ast::UintLiteralExpression>(Source{}, literal);
+        return create<ast::IntLiteralExpression>(Source{}, literal,
+                                                 ast::IntLiteralExpression::Suffix::kU);
     };
 
     // Build up a nested expression for the decomposition by walking down the type
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index aaa2b4c..ffcdd5b 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -1327,18 +1327,25 @@
             case SpvOpSpecConstant: {
                 ast_type = ConvertType(inst.type_id());
                 const uint32_t literal_value = inst.GetSingleWordInOperand(0);
-                if (ast_type->Is<I32>()) {
-                    ast_expr = create<ast::SintLiteralExpression>(
-                        Source{}, static_cast<int32_t>(literal_value));
-                } else if (ast_type->Is<U32>()) {
-                    ast_expr = create<ast::UintLiteralExpression>(
-                        Source{}, static_cast<uint32_t>(literal_value));
-                } else if (ast_type->Is<F32>()) {
-                    float float_value;
-                    // Copy the bits so we can read them as a float.
-                    std::memcpy(&float_value, &literal_value, sizeof(float_value));
-                    ast_expr = create<ast::FloatLiteralExpression>(Source{}, float_value);
-                } else {
+                ast_expr = Switch(
+                    ast_type,  //
+                    [&](const I32*) {
+                        return create<ast::IntLiteralExpression>(
+                            Source{}, static_cast<int64_t>(literal_value),
+                            ast::IntLiteralExpression::Suffix::kNone);
+                    },
+                    [&](const U32*) {
+                        return create<ast::IntLiteralExpression>(
+                            Source{}, static_cast<uint64_t>(literal_value),
+                            ast::IntLiteralExpression::Suffix::kU);
+                    },
+                    [&](const F32*) {
+                        float float_value;
+                        // Copy the bits so we can read them as a float.
+                        std::memcpy(&float_value, &literal_value, sizeof(float_value));
+                        return create<ast::FloatLiteralExpression>(Source{}, float_value);
+                    });
+                if (ast_expr == nullptr) {
                     return Fail() << " invalid result type for OpSpecConstant "
                                   << inst.PrettyPrint();
                 }
@@ -1895,22 +1902,31 @@
     // So canonicalization should map that way too.
     // Currently "null<type>" is missing from the WGSL parser.
     // See https://bugs.chromium.org/p/tint/issues/detail?id=34
-    if (ast_type->Is<U32>()) {
-        return {ty_.U32(), create<ast::UintLiteralExpression>(source, spirv_const->GetU32())};
-    }
-    if (ast_type->Is<I32>()) {
-        return {ty_.I32(), create<ast::SintLiteralExpression>(source, spirv_const->GetS32())};
-    }
-    if (ast_type->Is<F32>()) {
-        return {ty_.F32(), create<ast::FloatLiteralExpression>(source, spirv_const->GetFloat())};
-    }
-    if (ast_type->Is<Bool>()) {
-        const bool value =
-            spirv_const->AsNullConstant() ? false : spirv_const->AsBoolConstant()->value();
-        return {ty_.Bool(), create<ast::BoolLiteralExpression>(source, value)};
-    }
-    Fail() << "expected scalar constant";
-    return {};
+    return Switch(
+        ast_type,
+        [&](const I32*) {
+            return TypedExpression{ty_.I32(), create<ast::IntLiteralExpression>(
+                                                  source, spirv_const->GetS32(),
+                                                  ast::IntLiteralExpression::Suffix::kNone)};
+        },
+        [&](const U32*) {
+            return TypedExpression{ty_.U32(), create<ast::IntLiteralExpression>(
+                                                  source, spirv_const->GetU32(),
+                                                  ast::IntLiteralExpression::Suffix::kU)};
+        },
+        [&](const F32*) {
+            return TypedExpression{
+                ty_.F32(), create<ast::FloatLiteralExpression>(source, spirv_const->GetFloat())};
+        },
+        [&](const Bool*) {
+            const bool value =
+                spirv_const->AsNullConstant() ? false : spirv_const->AsBoolConstant()->value();
+            return TypedExpression{ty_.Bool(), create<ast::BoolLiteralExpression>(source, value)};
+        },
+        [&](Default) {
+            Fail() << "expected scalar constant";
+            return TypedExpression{};
+        });
 }
 
 const ast::Expression* ParserImpl::MakeNullValue(const Type* type) {
@@ -1927,31 +1943,33 @@
     auto* original_type = type;
     type = type->UnwrapAlias();
 
-    if (type->Is<Bool>()) {
-        return create<ast::BoolLiteralExpression>(Source{}, false);
-    }
-    if (type->Is<U32>()) {
-        return create<ast::UintLiteralExpression>(Source{}, 0u);
-    }
-    if (type->Is<I32>()) {
-        return create<ast::SintLiteralExpression>(Source{}, 0);
-    }
-    if (type->Is<F32>()) {
-        return create<ast::FloatLiteralExpression>(Source{}, 0.0f);
-    }
-    if (type->IsAnyOf<Vector, Matrix, Array>()) {
-        return builder_.Construct(Source{}, type->Build(builder_));
-    }
-    if (auto* struct_ty = type->As<Struct>()) {
-        ast::ExpressionList ast_components;
-        for (auto* member : struct_ty->members) {
-            ast_components.emplace_back(MakeNullValue(member));
-        }
-        return builder_.Construct(Source{}, original_type->Build(builder_),
-                                  std::move(ast_components));
-    }
-    Fail() << "can't make null value for type: " << type->TypeInfo().name;
-    return nullptr;
+    return Switch(
+        type,  //
+        [&](const I32*) {
+            return create<ast::IntLiteralExpression>(Source{}, 0,
+                                                     ast::IntLiteralExpression::Suffix::kNone);
+        },
+        [&](const U32*) {
+            return create<ast::IntLiteralExpression>(Source{}, 0,
+                                                     ast::IntLiteralExpression::Suffix::kU);
+        },
+        [&](const F32*) { return create<ast::FloatLiteralExpression>(Source{}, 0.0f); },
+        [&](const Vector*) { return builder_.Construct(Source{}, type->Build(builder_)); },
+        [&](const Matrix*) { return builder_.Construct(Source{}, type->Build(builder_)); },
+        [&](const Array*) { return builder_.Construct(Source{}, type->Build(builder_)); },
+        [&](const Bool*) { return create<ast::BoolLiteralExpression>(Source{}, false); },
+        [&](const Struct* struct_ty) {
+            ast::ExpressionList ast_components;
+            for (auto* member : struct_ty->members) {
+                ast_components.emplace_back(MakeNullValue(member));
+            }
+            return builder_.Construct(Source{}, original_type->Build(builder_),
+                                      std::move(ast_components));
+        },
+        [&](Default) {
+            Fail() << "can't make null value for type: " << type->TypeInfo().name;
+            return nullptr;
+        });
 }
 
 TypedExpression ParserImpl::MakeNullExpression(const Type* type) {
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index 9f2539b..8661a00 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -2772,20 +2772,22 @@
 
 // const_literal
 //   : INT_LITERAL
-//   | UINT_LITERAL
 //   | FLOAT_LITERAL
 //   | TRUE
 //   | FALSE
 Maybe<const ast::LiteralExpression*> ParserImpl::const_literal() {
     auto t = peek();
     if (match(Token::Type::kIntLiteral)) {
-        return create<ast::SintLiteralExpression>(t.source(), static_cast<int32_t>(t.to_i64()));
+        return create<ast::IntLiteralExpression>(t.source(), t.to_i64(),
+                                                 ast::IntLiteralExpression::Suffix::kNone);
     }
     if (match(Token::Type::kIntILiteral)) {
-        return create<ast::SintLiteralExpression>(t.source(), static_cast<int32_t>(t.to_i64()));
+        return create<ast::IntLiteralExpression>(t.source(), t.to_i64(),
+                                                 ast::IntLiteralExpression::Suffix::kI);
     }
     if (match(Token::Type::kIntULiteral)) {
-        return create<ast::UintLiteralExpression>(t.source(), static_cast<uint32_t>(t.to_i64()));
+        return create<ast::IntLiteralExpression>(t.source(), t.to_i64(),
+                                                 ast::IntLiteralExpression::Suffix::kU);
     }
     if (match(Token::Type::kFloatLiteral)) {
         return create<ast::FloatLiteralExpression>(t.source(), t.to_f32());
diff --git a/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc
index e6aad59..4fd23ce 100644
--- a/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_assignment_stmt_test.cc
@@ -34,8 +34,10 @@
     auto* ident = a->lhs->As<ast::IdentifierExpression>();
     EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
 
-    ASSERT_TRUE(a->rhs->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(a->rhs->As<ast::SintLiteralExpression>()->value, 123);
+    ASSERT_TRUE(a->rhs->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(a->rhs->As<ast::IntLiteralExpression>()->value, 123);
+    EXPECT_EQ(a->rhs->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, AssignmentStmt_Parses_ToMember) {
@@ -51,8 +53,10 @@
     ASSERT_NE(a->lhs, nullptr);
     ASSERT_NE(a->rhs, nullptr);
 
-    ASSERT_TRUE(a->rhs->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(a->rhs->As<ast::SintLiteralExpression>()->value, 123);
+    ASSERT_TRUE(a->rhs->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(a->rhs->As<ast::IntLiteralExpression>()->value, 123);
+    EXPECT_EQ(a->rhs->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(a->lhs->Is<ast::MemberAccessorExpression>());
     auto* mem = a->lhs->As<ast::MemberAccessorExpression>();
@@ -65,8 +69,8 @@
     auto* idx = mem->structure->As<ast::IndexAccessorExpression>();
 
     ASSERT_NE(idx->index, nullptr);
-    ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 2);
+    ASSERT_TRUE(idx->index->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(idx->index->As<ast::IntLiteralExpression>()->value, 2);
 
     ASSERT_TRUE(idx->object->Is<ast::MemberAccessorExpression>());
     mem = idx->object->As<ast::MemberAccessorExpression>();
@@ -87,7 +91,7 @@
 }
 
 TEST_F(ParserImplTest, AssignmentStmt_Parses_ToPhony) {
-    auto p = parser("_ = 123");
+    auto p = parser("_ = 123i");
     auto e = p->assignment_stmt();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
@@ -99,14 +103,16 @@
     ASSERT_NE(a->lhs, nullptr);
     ASSERT_NE(a->rhs, nullptr);
 
-    ASSERT_TRUE(a->rhs->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(a->rhs->As<ast::SintLiteralExpression>()->value, 123);
+    ASSERT_TRUE(a->rhs->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(a->rhs->As<ast::IntLiteralExpression>()->value, 123);
+    EXPECT_EQ(a->rhs->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kI);
 
     ASSERT_TRUE(a->lhs->Is<ast::PhonyExpression>());
 }
 
 TEST_F(ParserImplTest, AssignmentStmt_Parses_CompoundOp) {
-    auto p = parser("a += 123");
+    auto p = parser("a += 123u");
     auto e = p->assignment_stmt();
     EXPECT_TRUE(e.matched);
     EXPECT_FALSE(e.errored);
@@ -123,8 +129,10 @@
     auto* ident = a->lhs->As<ast::IdentifierExpression>();
     EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
 
-    ASSERT_TRUE(a->rhs->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(a->rhs->As<ast::SintLiteralExpression>()->value, 123);
+    ASSERT_TRUE(a->rhs->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(a->rhs->As<ast::IntLiteralExpression>()->value, 123);
+    EXPECT_EQ(a->rhs->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kU);
 }
 
 TEST_F(ParserImplTest, AssignmentStmt_MissingEqual) {
diff --git a/src/tint/reader/wgsl/parser_impl_const_literal_test.cc b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
index 03e9919..3e37092 100644
--- a/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
@@ -49,8 +49,10 @@
         EXPECT_FALSE(c.errored);
         EXPECT_FALSE(p->has_error()) << p->error();
         ASSERT_NE(c.value, nullptr);
-        ASSERT_TRUE(c->Is<ast::SintLiteralExpression>());
-        EXPECT_EQ(c->As<ast::SintLiteralExpression>()->value, 234);
+        ASSERT_TRUE(c->Is<ast::IntLiteralExpression>());
+        EXPECT_EQ(c->As<ast::IntLiteralExpression>()->value, 234);
+        EXPECT_EQ(c->As<ast::IntLiteralExpression>()->suffix,
+                  ast::IntLiteralExpression::Suffix::kNone);
         EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 4u}}));
     }
     {
@@ -60,8 +62,10 @@
         EXPECT_FALSE(c.errored);
         EXPECT_FALSE(p->has_error()) << p->error();
         ASSERT_NE(c.value, nullptr);
-        ASSERT_TRUE(c->Is<ast::SintLiteralExpression>());
-        EXPECT_EQ(c->As<ast::SintLiteralExpression>()->value, 234);
+        ASSERT_TRUE(c->Is<ast::IntLiteralExpression>());
+        EXPECT_EQ(c->As<ast::IntLiteralExpression>()->value, 234);
+        EXPECT_EQ(c->As<ast::IntLiteralExpression>()->suffix,
+                  ast::IntLiteralExpression::Suffix::kI);
         EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
     }
     {
@@ -71,8 +75,10 @@
         EXPECT_FALSE(c.errored);
         EXPECT_FALSE(p->has_error()) << p->error();
         ASSERT_NE(c.value, nullptr);
-        ASSERT_TRUE(c->Is<ast::SintLiteralExpression>());
-        EXPECT_EQ(c->As<ast::SintLiteralExpression>()->value, -234);
+        ASSERT_TRUE(c->Is<ast::IntLiteralExpression>());
+        EXPECT_EQ(c->As<ast::IntLiteralExpression>()->value, -234);
+        EXPECT_EQ(c->As<ast::IntLiteralExpression>()->suffix,
+                  ast::IntLiteralExpression::Suffix::kNone);
         EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
     }
     {
@@ -82,8 +88,10 @@
         EXPECT_FALSE(c.errored);
         EXPECT_FALSE(p->has_error()) << p->error();
         ASSERT_NE(c.value, nullptr);
-        ASSERT_TRUE(c->Is<ast::SintLiteralExpression>());
-        EXPECT_EQ(c->As<ast::SintLiteralExpression>()->value, -234);
+        ASSERT_TRUE(c->Is<ast::IntLiteralExpression>());
+        EXPECT_EQ(c->As<ast::IntLiteralExpression>()->value, -234);
+        EXPECT_EQ(c->As<ast::IntLiteralExpression>()->suffix,
+                  ast::IntLiteralExpression::Suffix::kI);
         EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 6u}}));
     }
 }
@@ -95,8 +103,9 @@
     EXPECT_FALSE(c.errored);
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(c.value, nullptr);
-    ASSERT_TRUE(c->Is<ast::UintLiteralExpression>());
-    EXPECT_EQ(c->As<ast::UintLiteralExpression>()->value, 234u);
+    ASSERT_TRUE(c->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(c->As<ast::IntLiteralExpression>()->value, 234);
+    EXPECT_EQ(c->As<ast::IntLiteralExpression>()->suffix, ast::IntLiteralExpression::Suffix::kU);
     EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 5u}}));
 }
 
diff --git a/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc b/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc
index a55a542..356241b 100644
--- a/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_attribute_list_test.cc
@@ -37,8 +37,9 @@
     auto* x_literal = x->As<ast::LiteralExpression>();
     ASSERT_NE(x_literal, nullptr);
     ASSERT_TRUE(x_literal->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(x_literal->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
-
+    EXPECT_EQ(x_literal->As<ast::IntLiteralExpression>()->value, 2);
+    EXPECT_EQ(x_literal->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
     ASSERT_TRUE(attr_1->Is<ast::StageAttribute>());
     EXPECT_EQ(attr_1->As<ast::StageAttribute>()->stage, ast::PipelineStage::kCompute);
 }
diff --git a/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
index 444f4de..d2585b3 100644
--- a/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
@@ -33,7 +33,9 @@
     auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 4);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     EXPECT_EQ(values[1], nullptr);
     EXPECT_EQ(values[2], nullptr);
@@ -53,10 +55,14 @@
     auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 4);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 5u);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->value, 5);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     EXPECT_EQ(values[2], nullptr);
 }
@@ -75,13 +81,19 @@
     auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 4);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 5u);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->value, 5);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 6u);
+    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->value, 6);
+    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, Attribute_Workgroup_WithIdent) {
@@ -98,7 +110,9 @@
     auto values = func_attr->As<ast::WorkgroupAttribute>()->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 4);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_NE(values[1], nullptr);
     auto* y_ident = values[1]->As<ast::IdentifierExpression>();
diff --git a/src/tint/reader/wgsl/parser_impl_function_decl_test.cc b/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
index a7b3460..181501a 100644
--- a/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_decl_test.cc
@@ -116,13 +116,19 @@
     auto values = attributes[0]->As<ast::WorkgroupAttribute>()->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 2);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 3u);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->value, 3);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->value, 4);
+    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     auto* body = f->body;
     ASSERT_EQ(body->statements.size(), 1u);
@@ -155,13 +161,19 @@
     auto values = attributes[0]->As<ast::WorkgroupAttribute>()->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 2);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 3u);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->value, 3);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->value, 4);
+    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(attributes[1]->Is<ast::StageAttribute>());
     EXPECT_EQ(attributes[1]->As<ast::StageAttribute>()->stage, ast::PipelineStage::kCompute);
@@ -198,13 +210,19 @@
     auto values = attrs[0]->As<ast::WorkgroupAttribute>()->Values();
 
     ASSERT_TRUE(values[0]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->ValueAsU32(), 2u);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->value, 2);
+    EXPECT_EQ(values[0]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(values[1]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->ValueAsU32(), 3u);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->value, 3);
+    EXPECT_EQ(values[1]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(values[2]->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->ValueAsU32(), 4u);
+    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->value, 4);
+    EXPECT_EQ(values[2]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
     ASSERT_TRUE(attrs[1]->Is<ast::StageAttribute>());
     EXPECT_EQ(attrs[1]->As<ast::StageAttribute>()->stage, ast::PipelineStage::kCompute);
diff --git a/src/tint/reader/wgsl/parser_impl_increment_decrement_stmt_test.cc b/src/tint/reader/wgsl/parser_impl_increment_decrement_stmt_test.cc
index 6ae0deb..8041735 100644
--- a/src/tint/reader/wgsl/parser_impl_increment_decrement_stmt_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_increment_decrement_stmt_test.cc
@@ -98,8 +98,8 @@
     auto* idx = mem->structure->As<ast::IndexAccessorExpression>();
 
     ASSERT_NE(idx->index, nullptr);
-    ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 2);
+    ASSERT_TRUE(idx->index->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(idx->index->As<ast::IntLiteralExpression>()->value, 2);
 
     ASSERT_TRUE(idx->object->Is<ast::MemberAccessorExpression>());
     mem = idx->object->As<ast::MemberAccessorExpression>();
diff --git a/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc b/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
index 1c2b0bd..493ce87 100644
--- a/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_primary_expression_test.cc
@@ -44,17 +44,25 @@
 
     ASSERT_EQ(call->args.size(), 4u);
     const auto& val = call->args;
-    ASSERT_TRUE(val[0]->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(val[0]->As<ast::SintLiteralExpression>()->value, 1);
+    ASSERT_TRUE(val[0]->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(val[0]->As<ast::IntLiteralExpression>()->value, 1);
+    EXPECT_EQ(val[0]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
-    ASSERT_TRUE(val[1]->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(val[1]->As<ast::SintLiteralExpression>()->value, 2);
+    ASSERT_TRUE(val[1]->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(val[1]->As<ast::IntLiteralExpression>()->value, 2);
+    EXPECT_EQ(val[1]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
-    ASSERT_TRUE(val[2]->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(val[2]->As<ast::SintLiteralExpression>()->value, 3);
+    ASSERT_TRUE(val[2]->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(val[2]->As<ast::IntLiteralExpression>()->value, 3);
+    EXPECT_EQ(val[2]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 
-    ASSERT_TRUE(val[3]->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(val[3]->As<ast::SintLiteralExpression>()->value, 4);
+    ASSERT_TRUE(val[3]->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(val[3]->As<ast::IntLiteralExpression>()->value, 4);
+    EXPECT_EQ(val[3]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_ZeroConstructor) {
@@ -158,8 +166,10 @@
 
     ASSERT_EQ(call->args.size(), 2u);
 
-    ASSERT_TRUE(call->args[0]->Is<ast::UintLiteralExpression>());
-    EXPECT_EQ(call->args[0]->As<ast::UintLiteralExpression>()->value, 1u);
+    ASSERT_TRUE(call->args[0]->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(call->args[0]->As<ast::IntLiteralExpression>()->value, 1u);
+    EXPECT_EQ(call->args[0]->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kU);
 
     ASSERT_TRUE(call->args[1]->Is<ast::FloatLiteralExpression>());
     EXPECT_EQ(call->args[1]->As<ast::FloatLiteralExpression>()->value, 2.f);
diff --git a/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc b/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
index 77bd4fe..4ab185b 100644
--- a/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_singular_expression_test.cc
@@ -32,8 +32,10 @@
     auto* ident = idx->object->As<ast::IdentifierExpression>();
     EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
 
-    ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 1);
+    ASSERT_TRUE(idx->index->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(idx->index->As<ast::IntLiteralExpression>()->value, 1);
+    EXPECT_EQ(idx->index->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, SingularExpression_Array_ExpressionIndex) {
diff --git a/src/tint/reader/wgsl/parser_impl_switch_body_test.cc b/src/tint/reader/wgsl/parser_impl_switch_body_test.cc
index b126586..0f7384d 100644
--- a/src/tint/reader/wgsl/parser_impl_switch_body_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_switch_body_test.cc
@@ -28,7 +28,8 @@
     EXPECT_FALSE(e->IsDefault());
     auto* stmt = e->As<ast::CaseStatement>();
     ASSERT_EQ(stmt->selectors.size(), 1u);
-    EXPECT_EQ(stmt->selectors[0]->ValueAsU32(), 1u);
+    EXPECT_EQ(stmt->selectors[0]->value, 1);
+    EXPECT_EQ(stmt->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
     ASSERT_EQ(e->body->statements.size(), 1u);
     EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
 }
@@ -44,7 +45,8 @@
     EXPECT_FALSE(e->IsDefault());
     auto* stmt = e->As<ast::CaseStatement>();
     ASSERT_EQ(stmt->selectors.size(), 1u);
-    EXPECT_EQ(stmt->selectors[0]->ValueAsU32(), 1u);
+    EXPECT_EQ(stmt->selectors[0]->value, 1);
+    EXPECT_EQ(stmt->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
     ASSERT_EQ(e->body->statements.size(), 1u);
     EXPECT_TRUE(e->body->statements[0]->Is<ast::AssignmentStatement>());
 }
@@ -60,8 +62,9 @@
     EXPECT_FALSE(e->IsDefault());
     auto* stmt = e->As<ast::CaseStatement>();
     ASSERT_EQ(stmt->selectors.size(), 2u);
-    EXPECT_EQ(stmt->selectors[0]->ValueAsU32(), 1u);
-    EXPECT_EQ(stmt->selectors[1]->ValueAsU32(), 2u);
+    EXPECT_EQ(stmt->selectors[0]->value, 1);
+    EXPECT_EQ(stmt->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
+    EXPECT_EQ(stmt->selectors[1]->value, 2);
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_TrailingComma_WithColon) {
@@ -75,8 +78,9 @@
     EXPECT_FALSE(e->IsDefault());
     auto* stmt = e->As<ast::CaseStatement>();
     ASSERT_EQ(stmt->selectors.size(), 2u);
-    EXPECT_EQ(stmt->selectors[0]->ValueAsU32(), 1u);
-    EXPECT_EQ(stmt->selectors[1]->ValueAsU32(), 2u);
+    EXPECT_EQ(stmt->selectors[0]->value, 1);
+    EXPECT_EQ(stmt->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
+    EXPECT_EQ(stmt->selectors[1]->value, 2);
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_InvalidConstLiteral) {
@@ -160,8 +164,10 @@
     EXPECT_FALSE(e->IsDefault());
     ASSERT_EQ(e->body->statements.size(), 0u);
     ASSERT_EQ(e->selectors.size(), 2u);
-    ASSERT_EQ(e->selectors[0]->ValueAsI32(), 1);
-    ASSERT_EQ(e->selectors[1]->ValueAsI32(), 2);
+    ASSERT_EQ(e->selectors[0]->value, 1);
+    EXPECT_EQ(e->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
+    ASSERT_EQ(e->selectors[1]->value, 2);
+    EXPECT_EQ(e->selectors[1]->suffix, ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectors_WithColon) {
@@ -175,8 +181,10 @@
     EXPECT_FALSE(e->IsDefault());
     ASSERT_EQ(e->body->statements.size(), 0u);
     ASSERT_EQ(e->selectors.size(), 2u);
-    ASSERT_EQ(e->selectors[0]->ValueAsI32(), 1);
-    ASSERT_EQ(e->selectors[1]->ValueAsI32(), 2);
+    ASSERT_EQ(e->selectors[0]->value, 1);
+    EXPECT_EQ(e->selectors[0]->suffix, ast::IntLiteralExpression::Suffix::kNone);
+    ASSERT_EQ(e->selectors[1]->value, 2);
+    EXPECT_EQ(e->selectors[1]->suffix, ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_MultipleSelectorsMissingComma) {
diff --git a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
index 7809299..a721d77 100644
--- a/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_type_decl_test.cc
@@ -376,7 +376,7 @@
     ASSERT_EQ(p->error(), "1:8: invalid type for atomic declaration");
 }
 
-TEST_F(ParserImplTest, TypeDecl_Array_SintLiteralSize) {
+TEST_F(ParserImplTest, TypeDecl_Array_AbstractIntLiteralSize) {
     auto p = parser("array<f32, 5>");
     auto t = p->type_decl();
     EXPECT_TRUE(t.matched);
@@ -391,9 +391,31 @@
     EXPECT_EQ(a->attributes.size(), 0u);
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 14u}}));
 
-    auto* size = a->count->As<ast::SintLiteralExpression>();
+    auto* size = a->count->As<ast::IntLiteralExpression>();
     ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->ValueAsI32(), 5);
+    EXPECT_EQ(size->value, 5);
+    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kNone);
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_SintLiteralSize) {
+    auto p = parser("array<f32, 5i>");
+    auto t = p->type_decl();
+    EXPECT_TRUE(t.matched);
+    EXPECT_FALSE(t.errored);
+    ASSERT_NE(t.value, nullptr) << p->error();
+    ASSERT_FALSE(p->has_error());
+    ASSERT_TRUE(t.value->Is<ast::Array>());
+
+    auto* a = t.value->As<ast::Array>();
+    ASSERT_FALSE(a->IsRuntimeArray());
+    ASSERT_TRUE(a->type->Is<ast::F32>());
+    EXPECT_EQ(a->attributes.size(), 0u);
+    EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
+
+    auto* size = a->count->As<ast::IntLiteralExpression>();
+    ASSERT_NE(size, nullptr);
+    EXPECT_EQ(size->value, 5);
+    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kI);
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_UintLiteralSize) {
@@ -411,9 +433,9 @@
     EXPECT_EQ(a->attributes.size(), 0u);
     EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 15u}}));
 
-    auto* size = a->count->As<ast::UintLiteralExpression>();
+    auto* size = a->count->As<ast::IntLiteralExpression>();
     ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->ValueAsU32(), 5u);
+    EXPECT_EQ(size->suffix, ast::IntLiteralExpression::Suffix::kU);
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_ConstantSize) {
diff --git a/src/tint/reader/wgsl/parser_impl_unary_expression_test.cc b/src/tint/reader/wgsl/parser_impl_unary_expression_test.cc
index 4c6f37a..184de66 100644
--- a/src/tint/reader/wgsl/parser_impl_unary_expression_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_unary_expression_test.cc
@@ -32,8 +32,10 @@
     auto* ident = idx->object->As<ast::IdentifierExpression>();
     EXPECT_EQ(ident->symbol, p->builder().Symbols().Get("a"));
 
-    ASSERT_TRUE(idx->index->Is<ast::SintLiteralExpression>());
-    ASSERT_EQ(idx->index->As<ast::SintLiteralExpression>()->value, 2);
+    ASSERT_TRUE(idx->index->Is<ast::IntLiteralExpression>());
+    ASSERT_EQ(idx->index->As<ast::IntLiteralExpression>()->value, 2);
+    ASSERT_EQ(idx->index->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, UnaryExpression_Minus) {
@@ -48,8 +50,10 @@
     auto* u = e->As<ast::UnaryOpExpression>();
     ASSERT_EQ(u->op, ast::UnaryOp::kNegation);
 
-    ASSERT_TRUE(u->expr->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(u->expr->As<ast::SintLiteralExpression>()->value, 1);
+    ASSERT_TRUE(u->expr->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(u->expr->As<ast::IntLiteralExpression>()->value, 1);
+    ASSERT_EQ(u->expr->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, UnaryExpression_AddressOf) {
@@ -130,8 +134,10 @@
     auto* u = e->As<ast::UnaryOpExpression>();
     ASSERT_EQ(u->op, ast::UnaryOp::kNot);
 
-    ASSERT_TRUE(u->expr->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(u->expr->As<ast::SintLiteralExpression>()->value, 1);
+    ASSERT_TRUE(u->expr->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(u->expr->As<ast::IntLiteralExpression>()->value, 1);
+    ASSERT_EQ(u->expr->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, UnaryExpression_Bang_InvalidRHS) {
@@ -156,8 +162,10 @@
     auto* u = e->As<ast::UnaryOpExpression>();
     ASSERT_EQ(u->op, ast::UnaryOp::kComplement);
 
-    ASSERT_TRUE(u->expr->Is<ast::SintLiteralExpression>());
-    EXPECT_EQ(u->expr->As<ast::SintLiteralExpression>()->value, 1);
+    ASSERT_TRUE(u->expr->Is<ast::IntLiteralExpression>());
+    EXPECT_EQ(u->expr->As<ast::IntLiteralExpression>()->value, 1);
+    ASSERT_EQ(u->expr->As<ast::IntLiteralExpression>()->suffix,
+              ast::IntLiteralExpression::Suffix::kNone);
 }
 
 TEST_F(ParserImplTest, UnaryExpression_PrefixPlusPlus) {
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index 3a11a8d..a0a6edd 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -1147,9 +1147,9 @@
     helper.Add(inner_kind, symbol, Source{{56, 78}});
     auto* inner_var = helper.nested_statements.size()
                           ? helper.nested_statements[0]->As<ast::VariableDeclStatement>()->variable
-                          : helper.statements.size()
-                                ? helper.statements[0]->As<ast::VariableDeclStatement>()->variable
-                                : helper.parameters[0];
+                      : helper.statements.size()
+                          ? helper.statements[0]->As<ast::VariableDeclStatement>()->variable
+                          : helper.parameters[0];
     helper.Build();
 
     EXPECT_EQ(Build().shadows[inner_var], outer);
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index fec6d70..dc1007b 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -859,7 +859,9 @@
         builder_->create<sem::CaseStatement>(stmt, current_compound_statement_, current_function_);
     return StatementScope(stmt, sem, [&] {
         for (auto* sel : stmt->selectors) {
-            Mark(sel);
+            if (!Expression(sel)) {
+                return false;
+            }
         }
         Mark(stmt->body);
         auto* body = BlockStatement(stmt->body);
@@ -1525,8 +1527,27 @@
 }
 
 sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) {
-    auto* ty = sem_.TypeOf(literal);
-    if (!ty) {
+    auto* ty = Switch(
+        literal,
+        [&](const ast::IntLiteralExpression* i) -> sem::Type* {
+            switch (i->suffix) {
+                case ast::IntLiteralExpression::Suffix::kNone:
+                // TODO(crbug.com/tint/1504): This will need to become abstract-int.
+                // For now, treat as 'i32'.
+                case ast::IntLiteralExpression::Suffix::kI:
+                    return builder_->create<sem::I32>();
+                case ast::IntLiteralExpression::Suffix::kU:
+                    return builder_->create<sem::U32>();
+            }
+            return nullptr;
+        },
+        [&](const ast::FloatLiteralExpression*) { return builder_->create<sem::F32>(); },
+        [&](const ast::BoolLiteralExpression*) { return builder_->create<sem::Bool>(); },
+        [&](Default) { return nullptr; });
+
+    if (ty == nullptr) {
+        TINT_UNREACHABLE(Resolver, builder_->Diagnostics())
+            << "Unhandled literal type: " << literal->TypeInfo().name;
         return nullptr;
     }
 
diff --git a/src/tint/resolver/resolver_constants.cc b/src/tint/resolver/resolver_constants.cc
index a0f2711..69ee798 100644
--- a/src/tint/resolver/resolver_constants.cc
+++ b/src/tint/resolver/resolver_constants.cc
@@ -39,20 +39,20 @@
 
 sem::Constant Resolver::EvaluateConstantValue(const ast::LiteralExpression* literal,
                                               const sem::Type* type) {
-    if (auto* lit = literal->As<ast::SintLiteralExpression>()) {
-        return {type, {lit->ValueAsI32()}};
-    }
-    if (auto* lit = literal->As<ast::UintLiteralExpression>()) {
-        return {type, {lit->ValueAsU32()}};
-    }
-    if (auto* lit = literal->As<ast::FloatLiteralExpression>()) {
-        return {type, {lit->value}};
-    }
-    if (auto* lit = literal->As<ast::BoolLiteralExpression>()) {
-        return {type, {lit->value}};
-    }
-    TINT_UNREACHABLE(Resolver, builder_->Diagnostics());
-    return {};
+    return Switch(
+        literal,
+        [&](const ast::IntLiteralExpression* lit) {
+            if (lit->suffix == ast::IntLiteralExpression::Suffix::kU) {
+                return sem::Constant{type, {static_cast<uint32_t>(lit->value)}};
+            }
+            return sem::Constant{type, {static_cast<int32_t>(lit->value)}};
+        },
+        [&](const ast::FloatLiteralExpression* lit) {
+            return sem::Constant{type, {lit->value}};
+        },
+        [&](const ast::BoolLiteralExpression* lit) {
+            return sem::Constant{type, {lit->value}};
+        });
 }
 
 sem::Constant Resolver::EvaluateConstantValue(const ast::CallExpression* call,
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index 7c06fab..45b5694 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -113,7 +113,7 @@
     auto* assign = Assign(lhs, rhs);
     auto* block = Block(assign);
     ast::CaseSelectorList lit;
-    lit.push_back(create<ast::SintLiteralExpression>(3));
+    lit.push_back(Expr(3));
     auto* cse = create<ast::CaseStatement>(lit, block);
     auto* cond_var = Var("c", ty.i32());
     auto* sw = Switch(cond_var, cse, DefaultCase());
@@ -1922,8 +1922,8 @@
             b.Expr("expr");
             Resolver(&b).Resolve();
         },
-        "internal compiler error: AST node 'tint::ast::IdentifierExpression' was "
-        "not reached by the resolver");
+        "internal compiler error: AST node 'tint::ast::IdentifierExpression' was not reached by "
+        "the resolver");
 }
 
 TEST_F(ResolverTest, ASTNodeReachedTwice) {
@@ -1935,8 +1935,8 @@
             b.Global("b", b.ty.i32(), ast::StorageClass::kPrivate, expr);
             Resolver(&b).Resolve();
         },
-        "internal compiler error: AST node 'tint::ast::SintLiteralExpression' "
-        "was encountered twice in the same AST of a Program");
+        "internal compiler error: AST node 'tint::ast::IntLiteralExpression' was encountered twice "
+        "in the same AST of a Program");
 }
 
 TEST_F(ResolverTest, UnaryOp_Not) {
diff --git a/src/tint/resolver/sem_helper.cc b/src/tint/resolver/sem_helper.cc
index 53c2817..01a5480 100644
--- a/src/tint/resolver/sem_helper.cc
+++ b/src/tint/resolver/sem_helper.cc
@@ -36,17 +36,4 @@
     return sem ? const_cast<sem::Type*>(sem->Type()) : nullptr;
 }
 
-sem::Type* SemHelper::TypeOf(const ast::LiteralExpression* lit) {
-    return Switch(
-        lit, [&](const ast::SintLiteralExpression*) { return builder_->create<sem::I32>(); },
-        [&](const ast::UintLiteralExpression*) { return builder_->create<sem::U32>(); },
-        [&](const ast::FloatLiteralExpression*) { return builder_->create<sem::F32>(); },
-        [&](const ast::BoolLiteralExpression*) { return builder_->create<sem::Bool>(); },
-        [&](Default) {
-            TINT_UNREACHABLE(Resolver, builder_->Diagnostics())
-                << "Unhandled literal type: " << lit->TypeInfo().name;
-            return nullptr;
-        });
-}
-
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/sem_helper.h b/src/tint/resolver/sem_helper.h
index 5ab14eb..9b0967b 100644
--- a/src/tint/resolver/sem_helper.h
+++ b/src/tint/resolver/sem_helper.h
@@ -62,10 +62,6 @@
     /// @param expr the expression
     sem::Type* TypeOf(const ast::Expression* expr) const;
 
-    /// @returns the semantic type of the AST literal `lit`
-    /// @param lit the literal
-    sem::Type* TypeOf(const ast::LiteralExpression* lit);
-
     /// @returns the type name of the given semantic type, unwrapping
     /// references.
     /// @param ty the type to look up
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 033c785..a97c2bd 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -2178,7 +2178,7 @@
     }
 
     bool has_default = false;
-    std::unordered_map<uint32_t, Source> selectors;
+    std::unordered_map<int64_t, Source> selectors;
 
     for (auto* case_stmt : s->body) {
         if (case_stmt->IsDefault()) {
@@ -2200,17 +2200,14 @@
                 return false;
             }
 
-            auto v = selector->ValueAsU32();
-            auto it = selectors.find(v);
+            auto it = selectors.find(selector->value);
             if (it != selectors.end()) {
-                auto val = selector->Is<ast::IntLiteralExpression>()
-                               ? std::to_string(selector->ValueAsI32())
-                               : std::to_string(selector->ValueAsU32());
+                auto val = std::to_string(selector->value);
                 AddError("duplicate switch case '" + val + "'", selector->source);
                 AddNote("previous case declared here", it->second);
                 return false;
             }
-            selectors.emplace(v, selector->source);
+            selectors.emplace(selector->value, selector->source);
         }
     }
 
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 4b21e73..6387d68 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -320,11 +320,9 @@
     /// @param expr the expression to convert to an Offset
     /// @returns an Offset for the given ast::Expression
     const Offset* ToOffset(const ast::Expression* expr) {
-        if (auto* u32 = expr->As<ast::UintLiteralExpression>()) {
-            return offsets_.Create<OffsetLiteral>(u32->value);
-        } else if (auto* i32 = expr->As<ast::SintLiteralExpression>()) {
-            if (i32->value > 0) {
-                return offsets_.Create<OffsetLiteral>(i32->value);
+        if (auto* lit = expr->As<ast::IntLiteralExpression>()) {
+            if (lit->value > 0) {
+                return offsets_.Create<OffsetLiteral>(static_cast<uint32_t>(lit->value));
             }
         }
         return offsets_.Create<OffsetExpr>(expr);
diff --git a/src/tint/transform/transform_test.cc b/src/tint/transform/transform_test.cc
index cefe18f..3e342c5 100644
--- a/src/tint/transform/transform_test.cc
+++ b/src/tint/transform/transform_test.cc
@@ -73,7 +73,7 @@
 
     auto* size = arr->As<ast::Array>()->count->As<ast::IntLiteralExpression>();
     ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->ValueAsI32(), 2);
+    EXPECT_EQ(size->value, 2);
 }
 
 TEST_F(CreateASTTypeForTest, ArrayNonImplicitStride) {
@@ -88,7 +88,7 @@
 
     auto* size = arr->As<ast::Array>()->count->As<ast::IntLiteralExpression>();
     ASSERT_NE(size, nullptr);
-    EXPECT_EQ(size->ValueAsI32(), 2);
+    EXPECT_EQ(size->value, 2);
 }
 
 TEST_F(CreateASTTypeForTest, Struct) {
diff --git a/src/tint/writer/append_vector_test.cc b/src/tint/writer/append_vector_test.cc
index c2f7f29..7cb1243 100644
--- a/src/tint/writer/append_vector_test.cc
+++ b/src/tint/writer/append_vector_test.cc
@@ -453,7 +453,7 @@
     ASSERT_NE(vec_0004, nullptr);
     ASSERT_EQ(vec_0004->args.size(), 4u);
     for (size_t i = 0; i < 3; i++) {
-        auto* literal = As<ast::SintLiteralExpression>(vec_0004->args[i]);
+        auto* literal = As<ast::IntLiteralExpression>(vec_0004->args[i]);
         ASSERT_NE(literal, nullptr);
         EXPECT_EQ(literal->value, 0);
     }
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 1d2aaee..29451e7 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -2164,26 +2164,34 @@
 }
 
 bool GeneratorImpl::EmitLiteral(std::ostream& out, const ast::LiteralExpression* lit) {
-    if (auto* l = lit->As<ast::BoolLiteralExpression>()) {
-        out << (l->value ? "true" : "false");
-    } else if (auto* fl = lit->As<ast::FloatLiteralExpression>()) {
-        if (std::isinf(fl->value)) {
-            out << (fl->value >= 0 ? "uintBitsToFloat(0x7f800000u)"
-                                   : "uintBitsToFloat(0xff800000u)");
-        } else if (std::isnan(fl->value)) {
-            out << "uintBitsToFloat(0x7fc00000u)";
-        } else {
-            out << FloatToString(fl->value) << "f";
-        }
-    } else if (auto* sl = lit->As<ast::SintLiteralExpression>()) {
-        out << sl->value;
-    } else if (auto* ul = lit->As<ast::UintLiteralExpression>()) {
-        out << ul->value << "u";
-    } else {
-        diagnostics_.add_error(diag::System::Writer, "unknown literal type");
-        return false;
-    }
-    return true;
+    return Switch(
+        lit,
+        [&](const ast::BoolLiteralExpression* l) {
+            out << (l->value ? "true" : "false");
+            return true;
+        },
+        [&](const ast::FloatLiteralExpression* l) {
+            if (std::isinf(l->value)) {
+                out << (l->value >= 0 ? "uintBitsToFloat(0x7f800000u)"
+                                      : "uintBitsToFloat(0xff800000u)");
+            } else if (std::isnan(l->value)) {
+                out << "uintBitsToFloat(0x7fc00000u)";
+            } else {
+                out << FloatToString(l->value) << "f";
+            }
+            return true;
+        },
+        [&](const ast::IntLiteralExpression* l) {
+            out << l->value;
+            if (l->suffix == ast::IntLiteralExpression::Suffix::kU) {
+                out << "u";
+            }
+            return true;
+        },
+        [&](Default) {
+            diagnostics_.add_error(diag::System::Writer, "unknown literal type");
+            return false;
+        });
 }
 
 bool GeneratorImpl::EmitZeroValue(std::ostream& out, const sem::Type* type) {
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 1dd85f6..d66b6bc 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -3042,13 +3042,18 @@
             }
             return true;
         },
-        [&](const ast::SintLiteralExpression* sl) {
-            out << sl->value;
-            return true;
-        },
-        [&](const ast::UintLiteralExpression* ul) {
-            out << ul->value << "u";
-            return true;
+        [&](const ast::IntLiteralExpression* i) {
+            out << i->value;
+            switch (i->suffix) {
+                case ast::IntLiteralExpression::Suffix::kNone:
+                case ast::IntLiteralExpression::Suffix::kI:
+                    return true;
+                case ast::IntLiteralExpression::Suffix::kU:
+                    out << "u";
+                    return true;
+            }
+            diagnostics_.add_error(diag::System::Writer, "unknown integer literal suffix type");
+            return false;
         },
         [&](Default) {
             diagnostics_.add_error(diag::System::Writer, "unknown literal type");
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 77e4e33..8665cdb 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -30,8 +30,6 @@
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/interpolate_attribute.h"
 #include "src/tint/ast/module.h"
-#include "src/tint/ast/sint_literal_expression.h"
-#include "src/tint/ast/uint_literal_expression.h"
 #include "src/tint/ast/variable_decl_statement.h"
 #include "src/tint/ast/void.h"
 #include "src/tint/sem/array.h"
@@ -1523,24 +1521,31 @@
             }
             return true;
         },
-        [&](const ast::SintLiteralExpression* l) {
-            // MSL (and C++) parse `-2147483648` as a `long` because it parses
-            // unary minus and `2147483648` as separate tokens, and the latter
-            // doesn't fit into an (32-bit) `int`. WGSL, OTOH, parses this as an
-            // `i32`. To avoid issues with `long` to `int` casts, emit
-            // `(2147483647 - 1)` instead, which ensures the expression type is
-            // `int`.
-            const auto int_min = std::numeric_limits<int32_t>::min();
-            if (l->ValueAsI32() == int_min) {
-                out << "(" << int_min + 1 << " - 1)";
-            } else {
-                out << l->value;
+        [&](const ast::IntLiteralExpression* i) {
+            switch (i->suffix) {
+                case ast::IntLiteralExpression::Suffix::kNone:
+                case ast::IntLiteralExpression::Suffix::kI: {
+                    // MSL (and C++) parse `-2147483648` as a `long` because it parses
+                    // unary minus and `2147483648` as separate tokens, and the latter
+                    // doesn't fit into an (32-bit) `int`. WGSL, OTOH, parses this as an
+                    // `i32`. To avoid issues with `long` to `int` casts, emit
+                    // `(2147483647 - 1)` instead, which ensures the expression type is
+                    // `int`.
+                    const auto int_min = std::numeric_limits<int32_t>::min();
+                    if (i->value == int_min) {
+                        out << "(" << int_min + 1 << " - 1)";
+                    } else {
+                        out << i->value;
+                    }
+                    return true;
+                }
+                case ast::IntLiteralExpression::Suffix::kU: {
+                    out << i->value << "u";
+                    return true;
+                }
             }
-            return true;
-        },
-        [&](const ast::UintLiteralExpression* l) {
-            out << l->value << "u";
-            return true;
+            diagnostics_.add_error(diag::System::Writer, "unknown integer literal suffix type");
+            return false;
         },
         [&](Default) {
             diagnostics_.add_error(diag::System::Writer, "unknown literal type");
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index efb8929..72a0962 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -743,22 +743,30 @@
             }
 
             // SPIR-V requires specialization constants to have initializers.
-            if (type->Is<sem::F32>()) {
-                ast::FloatLiteralExpression l(ProgramID(), Source{}, 0.0f);
-                init_id = GenerateLiteralIfNeeded(var, &l);
-            } else if (type->Is<sem::U32>()) {
-                ast::UintLiteralExpression l(ProgramID(), Source{}, 0);
-                init_id = GenerateLiteralIfNeeded(var, &l);
-            } else if (type->Is<sem::I32>()) {
-                ast::SintLiteralExpression l(ProgramID(), Source{}, 0);
-                init_id = GenerateLiteralIfNeeded(var, &l);
-            } else if (type->Is<sem::Bool>()) {
-                ast::BoolLiteralExpression l(ProgramID(), Source{}, false);
-                init_id = GenerateLiteralIfNeeded(var, &l);
-            } else {
-                error_ = "invalid type for pipeline constant ID, must be scalar";
-                return false;
-            }
+            init_id = Switch(
+                type,  //
+                [&](const sem::F32*) {
+                    ast::FloatLiteralExpression l(ProgramID{}, Source{}, 0.0f);
+                    return GenerateLiteralIfNeeded(var, &l);
+                },
+                [&](const sem::U32*) {
+                    ast::IntLiteralExpression l(ProgramID{}, Source{}, 0,
+                                                ast::IntLiteralExpression::Suffix::kU);
+                    return GenerateLiteralIfNeeded(var, &l);
+                },
+                [&](const sem::I32*) {
+                    ast::IntLiteralExpression l(ProgramID{}, Source{}, 0,
+                                                ast::IntLiteralExpression::Suffix::kI);
+                    return GenerateLiteralIfNeeded(var, &l);
+                },
+                [&](const sem::Bool*) {
+                    ast::BoolLiteralExpression l(ProgramID{}, Source{}, false);
+                    return GenerateLiteralIfNeeded(var, &l);
+                },
+                [&](Default) {
+                    error_ = "invalid type for pipeline constant ID, must be scalar";
+                    return 0;
+                });
             if (init_id == 0) {
                 return 0;
             }
@@ -1537,28 +1545,22 @@
         uint32_t one_id;
         uint32_t zero_id;
         if (to_elem_type->Is<sem::F32>()) {
-            ast::FloatLiteralExpression one(ProgramID(), Source{}, 1.0f);
-            ast::FloatLiteralExpression zero(ProgramID(), Source{}, 0.0f);
-            one_id = GenerateLiteralIfNeeded(nullptr, &one);
-            zero_id = GenerateLiteralIfNeeded(nullptr, &zero);
+            zero_id = GenerateConstantIfNeeded(ScalarConstant::F32(0));
+            one_id = GenerateConstantIfNeeded(ScalarConstant::F32(1));
         } else if (to_elem_type->Is<sem::U32>()) {
-            ast::UintLiteralExpression one(ProgramID(), Source{}, 1);
-            ast::UintLiteralExpression zero(ProgramID(), Source{}, 0);
-            one_id = GenerateLiteralIfNeeded(nullptr, &one);
-            zero_id = GenerateLiteralIfNeeded(nullptr, &zero);
+            zero_id = GenerateConstantIfNeeded(ScalarConstant::U32(0));
+            one_id = GenerateConstantIfNeeded(ScalarConstant::U32(1));
         } else if (to_elem_type->Is<sem::I32>()) {
-            ast::SintLiteralExpression one(ProgramID(), Source{}, 1);
-            ast::SintLiteralExpression zero(ProgramID(), Source{}, 0);
-            one_id = GenerateLiteralIfNeeded(nullptr, &one);
-            zero_id = GenerateLiteralIfNeeded(nullptr, &zero);
+            zero_id = GenerateConstantIfNeeded(ScalarConstant::I32(0));
+            one_id = GenerateConstantIfNeeded(ScalarConstant::I32(1));
         } else {
             error_ = "invalid destination type for bool conversion";
             return false;
         }
         if (auto* to_vec = to_type->As<sem::Vector>()) {
             // Splat the scalars into vectors.
-            one_id = GenerateConstantVectorSplatIfNeeded(to_vec, one_id);
             zero_id = GenerateConstantVectorSplatIfNeeded(to_vec, zero_id);
+            one_id = GenerateConstantVectorSplatIfNeeded(to_vec, one_id);
         }
         if (!one_id || !zero_id) {
             return false;
@@ -1605,17 +1607,22 @@
             constant.kind = ScalarConstant::Kind::kBool;
             constant.value.b = l->value;
         },
-        [&](const ast::SintLiteralExpression* sl) {
-            constant.kind = ScalarConstant::Kind::kI32;
-            constant.value.i32 = sl->value;
+        [&](const ast::IntLiteralExpression* i) {
+            switch (i->suffix) {
+                case ast::IntLiteralExpression::Suffix::kNone:
+                case ast::IntLiteralExpression::Suffix::kI:
+                    constant.kind = ScalarConstant::Kind::kI32;
+                    constant.value.i32 = static_cast<int32_t>(i->value);
+                    return;
+                case ast::IntLiteralExpression::Suffix::kU:
+                    constant.kind = ScalarConstant::Kind::kU32;
+                    constant.value.u32 = static_cast<uint32_t>(i->value);
+                    return;
+            }
         },
-        [&](const ast::UintLiteralExpression* ul) {
-            constant.kind = ScalarConstant::Kind::kU32;
-            constant.value.u32 = ul->value;
-        },
-        [&](const ast::FloatLiteralExpression* fl) {
+        [&](const ast::FloatLiteralExpression* f) {
             constant.kind = ScalarConstant::Kind::kF32;
-            constant.value.f32 = fl->value;
+            constant.value.f32 = f->value;
         },
         [&](Default) { error_ = "unknown literal type"; });
 
@@ -2699,9 +2706,9 @@
                 op = spv::Op::OpImageQuerySizeLod;
                 spirv_params.emplace_back(gen(level));
             } else {
-                ast::SintLiteralExpression i32_0(ProgramID(), Source{}, 0);
                 op = spv::Op::OpImageQuerySizeLod;
-                spirv_params.emplace_back(Operand(GenerateLiteralIfNeeded(nullptr, &i32_0)));
+                spirv_params.emplace_back(
+                    Operand(GenerateConstantIfNeeded(ScalarConstant::I32(0))));
             }
             break;
         }
@@ -2729,9 +2736,9 @@
                 texture_type->Is<sem::StorageTexture>()) {
                 op = spv::Op::OpImageQuerySize;
             } else {
-                ast::SintLiteralExpression i32_0(ProgramID(), Source{}, 0);
                 op = spv::Op::OpImageQuerySizeLod;
-                spirv_params.emplace_back(Operand(GenerateLiteralIfNeeded(nullptr, &i32_0)));
+                spirv_params.emplace_back(
+                    Operand(GenerateConstantIfNeeded(ScalarConstant::I32(0))));
             }
             break;
         }
@@ -3401,7 +3408,7 @@
                 return false;
             }
 
-            params.push_back(Operand(int_literal->ValueAsU32()));
+            params.push_back(Operand(static_cast<uint32_t>(int_literal->value)));
             params.push_back(Operand(block_id));
         }
     }
diff --git a/src/tint/writer/spirv/builder_literal_test.cc b/src/tint/writer/spirv/builder_literal_test.cc
index 3544dc5..a23713c 100644
--- a/src/tint/writer/spirv/builder_literal_test.cc
+++ b/src/tint/writer/spirv/builder_literal_test.cc
@@ -70,7 +70,7 @@
 }
 
 TEST_F(BuilderTest, Literal_I32) {
-    auto* i = create<ast::SintLiteralExpression>(-23);
+    auto* i = Expr(-23);
     WrapInFunction(i);
     spirv::Builder& b = Build();
 
@@ -84,8 +84,8 @@
 }
 
 TEST_F(BuilderTest, Literal_I32_Dedup) {
-    auto* i1 = create<ast::SintLiteralExpression>(-23);
-    auto* i2 = create<ast::SintLiteralExpression>(-23);
+    auto* i1 = Expr(-23);
+    auto* i2 = Expr(-23);
     WrapInFunction(i1, i2);
 
     spirv::Builder& b = Build();
@@ -100,7 +100,7 @@
 }
 
 TEST_F(BuilderTest, Literal_U32) {
-    auto* i = create<ast::UintLiteralExpression>(23);
+    auto* i = Expr(23u);
     WrapInFunction(i);
 
     spirv::Builder& b = Build();
@@ -115,8 +115,8 @@
 }
 
 TEST_F(BuilderTest, Literal_U32_Dedup) {
-    auto* i1 = create<ast::UintLiteralExpression>(23);
-    auto* i2 = create<ast::UintLiteralExpression>(23);
+    auto* i1 = Expr(23u);
+    auto* i2 = Expr(23u);
     WrapInFunction(i1, i2);
 
     spirv::Builder& b = Build();
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index d1194e6..588a6e9 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -37,7 +37,6 @@
 #include "src/tint/ast/multisampled_texture.h"
 #include "src/tint/ast/pointer.h"
 #include "src/tint/ast/sampled_texture.h"
-#include "src/tint/ast/sint_literal_expression.h"
 #include "src/tint/ast/stage_attribute.h"
 #include "src/tint/ast/storage_texture.h"
 #include "src/tint/ast/stride_attribute.h"
@@ -46,7 +45,6 @@
 #include "src/tint/ast/struct_member_size_attribute.h"
 #include "src/tint/ast/type_name.h"
 #include "src/tint/ast/u32.h"
-#include "src/tint/ast/uint_literal_expression.h"
 #include "src/tint/ast/variable_decl_statement.h"
 #include "src/tint/ast/vector.h"
 #include "src/tint/ast/void.h"
@@ -259,20 +257,16 @@
 bool GeneratorImpl::EmitLiteral(std::ostream& out, const ast::LiteralExpression* lit) {
     return Switch(
         lit,
-        [&](const ast::BoolLiteralExpression* bl) {  //
-            out << (bl->value ? "true" : "false");
+        [&](const ast::BoolLiteralExpression* l) {  //
+            out << (l->value ? "true" : "false");
             return true;
         },
-        [&](const ast::FloatLiteralExpression* fl) {  //
-            out << FloatToBitPreservingString(fl->value);
+        [&](const ast::FloatLiteralExpression* l) {  //
+            out << FloatToBitPreservingString(l->value);
             return true;
         },
-        [&](const ast::SintLiteralExpression* sl) {  //
-            out << sl->value;
-            return true;
-        },
-        [&](const ast::UintLiteralExpression* ul) {  //
-            out << ul->value << "u";
+        [&](const ast::IntLiteralExpression* l) {  //
+            out << l->value << l->suffix;
             return true;
         },
         [&](Default) {  //
diff --git a/test/tint/BUILD.gn b/test/tint/BUILD.gn
index 817bca5..3b9379f 100644
--- a/test/tint/BUILD.gn
+++ b/test/tint/BUILD.gn
@@ -192,7 +192,6 @@
     "../../src/tint/ast/return_statement_test.cc",
     "../../src/tint/ast/sampled_texture_test.cc",
     "../../src/tint/ast/sampler_test.cc",
-    "../../src/tint/ast/sint_literal_expression_test.cc",
     "../../src/tint/ast/stage_attribute_test.cc",
     "../../src/tint/ast/storage_texture_test.cc",
     "../../src/tint/ast/stride_attribute_test.cc",
@@ -206,7 +205,6 @@
     "../../src/tint/ast/texture_test.cc",
     "../../src/tint/ast/traverse_expressions_test.cc",
     "../../src/tint/ast/u32_test.cc",
-    "../../src/tint/ast/uint_literal_expression_test.cc",
     "../../src/tint/ast/unary_op_expression_test.cc",
     "../../src/tint/ast/variable_decl_statement_test.cc",
     "../../src/tint/ast/variable_test.cc",