tint: Resolve @interpolate() args as expressions

This CL makes the builtin argument resolve as a shadowable enumerator
expression.

Bug: tint:1841
Bug: tint:1845
Change-Id: I5000ea91771fabb460c80c164bc7708fbbb0288c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/120722
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ast/interpolate_attribute.cc b/src/tint/ast/interpolate_attribute.cc
index 339880d..eda6405 100644
--- a/src/tint/ast/interpolate_attribute.cc
+++ b/src/tint/ast/interpolate_attribute.cc
@@ -25,8 +25,8 @@
 InterpolateAttribute::InterpolateAttribute(ProgramID pid,
                                            NodeID nid,
                                            const Source& src,
-                                           builtin::InterpolationType ty,
-                                           builtin::InterpolationSampling smpl)
+                                           const Expression* ty,
+                                           const Expression* smpl)
     : Base(pid, nid, src), type(ty), sampling(smpl) {}
 
 InterpolateAttribute::~InterpolateAttribute() = default;
@@ -38,7 +38,9 @@
 const InterpolateAttribute* InterpolateAttribute::Clone(CloneContext* ctx) const {
     // Clone arguments outside of create() call to have deterministic ordering
     auto src = ctx->Clone(source);
-    return ctx->dst->create<InterpolateAttribute>(src, type, sampling);
+    auto* ty = ctx->Clone(type);
+    auto* smpl = ctx->Clone(sampling);
+    return ctx->dst->create<InterpolateAttribute>(src, ty, smpl);
 }
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/interpolate_attribute.h b/src/tint/ast/interpolate_attribute.h
index 2d3ce6f..e50d8dd 100644
--- a/src/tint/ast/interpolate_attribute.h
+++ b/src/tint/ast/interpolate_attribute.h
@@ -19,8 +19,11 @@
 #include <string>
 
 #include "src/tint/ast/attribute.h"
-#include "src/tint/builtin/interpolation_sampling.h"
-#include "src/tint/builtin/interpolation_type.h"
+
+// Forward declarations
+namespace tint::ast {
+class Expression;
+}
 
 namespace tint::ast {
 
@@ -36,8 +39,8 @@
     InterpolateAttribute(ProgramID pid,
                          NodeID nid,
                          const Source& src,
-                         builtin::InterpolationType type,
-                         builtin::InterpolationSampling sampling);
+                         const Expression* type,
+                         const Expression* sampling);
     ~InterpolateAttribute() override;
 
     /// @returns the WGSL name for the attribute
@@ -50,10 +53,10 @@
     const InterpolateAttribute* Clone(CloneContext* ctx) const override;
 
     /// The interpolation type
-    const builtin::InterpolationType type;
+    const Expression* const type;
 
     /// The interpolation sampling
-    const builtin::InterpolationSampling sampling;
+    const Expression* const sampling;
 };
 
 }  // namespace tint::ast
diff --git a/src/tint/ast/interpolate_attribute_test.cc b/src/tint/ast/interpolate_attribute_test.cc
index 472994d..ecb2fac 100644
--- a/src/tint/ast/interpolate_attribute_test.cc
+++ b/src/tint/ast/interpolate_attribute_test.cc
@@ -25,10 +25,10 @@
 using InterpolateAttributeTest = TestHelper;
 
 TEST_F(InterpolateAttributeTest, Creation) {
-    auto* d = create<InterpolateAttribute>(builtin::InterpolationType::kLinear,
-                                           builtin::InterpolationSampling::kCenter);
-    EXPECT_EQ(builtin::InterpolationType::kLinear, d->type);
-    EXPECT_EQ(builtin::InterpolationSampling::kCenter, d->sampling);
+    auto* d =
+        Interpolate(builtin::InterpolationType::kLinear, builtin::InterpolationSampling::kCenter);
+    CheckIdentifier(Symbols(), d->type, "linear");
+    CheckIdentifier(Symbols(), d->sampling, "center");
 }
 
 }  // namespace
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index b7455c7..b12d8bf 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -30,6 +30,8 @@
 #include "src/tint/ast/var.h"
 #include "src/tint/builtin/builtin_value.h"
 #include "src/tint/builtin/extension.h"
+#include "src/tint/builtin/interpolation_sampling.h"
+#include "src/tint/builtin/interpolation_type.h"
 #include "src/tint/sem/builtin_enum_expression.h"
 #include "src/tint/sem/call.h"
 #include "src/tint/sem/function.h"
@@ -117,59 +119,6 @@
     return {componentType, compositionType};
 }
 
-std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData(
-    const type::Type* type,
-    utils::VectorRef<const ast::Attribute*> attributes) {
-    auto* interpolation_attribute = ast::GetAttribute<ast::InterpolateAttribute>(attributes);
-    if (type->is_integer_scalar_or_vector()) {
-        return {InterpolationType::kFlat, InterpolationSampling::kNone};
-    }
-
-    if (!interpolation_attribute) {
-        return {InterpolationType::kPerspective, InterpolationSampling::kCenter};
-    }
-
-    auto ast_interpolation_type = interpolation_attribute->type;
-    auto ast_sampling_type = interpolation_attribute->sampling;
-    if (ast_interpolation_type != builtin::InterpolationType::kFlat &&
-        ast_sampling_type == builtin::InterpolationSampling::kUndefined) {
-        ast_sampling_type = builtin::InterpolationSampling::kCenter;
-    }
-
-    auto interpolation_type = InterpolationType::kUnknown;
-    switch (ast_interpolation_type) {
-        case builtin::InterpolationType::kPerspective:
-            interpolation_type = InterpolationType::kPerspective;
-            break;
-        case builtin::InterpolationType::kLinear:
-            interpolation_type = InterpolationType::kLinear;
-            break;
-        case builtin::InterpolationType::kFlat:
-            interpolation_type = InterpolationType::kFlat;
-            break;
-        case builtin::InterpolationType::kUndefined:
-            break;
-    }
-
-    auto sampling_type = InterpolationSampling::kUnknown;
-    switch (ast_sampling_type) {
-        case builtin::InterpolationSampling::kUndefined:
-            sampling_type = InterpolationSampling::kNone;
-            break;
-        case builtin::InterpolationSampling::kCenter:
-            sampling_type = InterpolationSampling::kCenter;
-            break;
-        case builtin::InterpolationSampling::kCentroid:
-            sampling_type = InterpolationSampling::kCentroid;
-            break;
-        case builtin::InterpolationSampling::kSample:
-            sampling_type = InterpolationSampling::kSample;
-            break;
-    }
-
-    return {interpolation_type, sampling_type};
-}
-
 }  // namespace
 
 Inspector::Inspector(const Program* program) : program_(program) {}
@@ -887,6 +836,70 @@
     }
 }
 
+std::tuple<InterpolationType, InterpolationSampling> Inspector::CalculateInterpolationData(
+    const type::Type* type,
+    utils::VectorRef<const ast::Attribute*> attributes) const {
+    auto* interpolation_attribute = ast::GetAttribute<ast::InterpolateAttribute>(attributes);
+    if (type->is_integer_scalar_or_vector()) {
+        return {InterpolationType::kFlat, InterpolationSampling::kNone};
+    }
+
+    if (!interpolation_attribute) {
+        return {InterpolationType::kPerspective, InterpolationSampling::kCenter};
+    }
+
+    auto& sem = program_->Sem();
+
+    auto ast_interpolation_type = sem.Get<sem::BuiltinEnumExpression<builtin::InterpolationType>>(
+                                         interpolation_attribute->type)
+                                      ->Value();
+
+    auto ast_sampling_type = builtin::InterpolationSampling::kUndefined;
+    if (interpolation_attribute->sampling) {
+        ast_sampling_type = sem.Get<sem::BuiltinEnumExpression<builtin::InterpolationSampling>>(
+                                   interpolation_attribute->sampling)
+                                ->Value();
+    }
+
+    if (ast_interpolation_type != builtin::InterpolationType::kFlat &&
+        ast_sampling_type == builtin::InterpolationSampling::kUndefined) {
+        ast_sampling_type = builtin::InterpolationSampling::kCenter;
+    }
+
+    auto interpolation_type = InterpolationType::kUnknown;
+    switch (ast_interpolation_type) {
+        case builtin::InterpolationType::kPerspective:
+            interpolation_type = InterpolationType::kPerspective;
+            break;
+        case builtin::InterpolationType::kLinear:
+            interpolation_type = InterpolationType::kLinear;
+            break;
+        case builtin::InterpolationType::kFlat:
+            interpolation_type = InterpolationType::kFlat;
+            break;
+        case builtin::InterpolationType::kUndefined:
+            break;
+    }
+
+    auto sampling_type = InterpolationSampling::kUnknown;
+    switch (ast_sampling_type) {
+        case builtin::InterpolationSampling::kUndefined:
+            sampling_type = InterpolationSampling::kNone;
+            break;
+        case builtin::InterpolationSampling::kCenter:
+            sampling_type = InterpolationSampling::kCenter;
+            break;
+        case builtin::InterpolationSampling::kCentroid:
+            sampling_type = InterpolationSampling::kCentroid;
+            break;
+        case builtin::InterpolationSampling::kSample:
+            sampling_type = InterpolationSampling::kSample;
+            break;
+    }
+
+    return {interpolation_type, sampling_type};
+}
+
 template <size_t N, typename F>
 void Inspector::GetOriginatingResources(std::array<const ast::Expression*, N> exprs, F&& callback) {
     if (TINT_UNLIKELY(!program_->IsValid())) {
diff --git a/src/tint/inspector/inspector.h b/src/tint/inspector/inspector.h
index baaf8aa..c76df68 100644
--- a/src/tint/inspector/inspector.h
+++ b/src/tint/inspector/inspector.h
@@ -224,6 +224,13 @@
     /// Constructs |sampler_targets_| if it hasn't already been instantiated.
     void GenerateSamplerTargets();
 
+    /// @param type the type of the parameter or structure member
+    /// @param attributes attributes associated with the parameter or structure member
+    /// @returns the interpolation type and sampling modes for the value
+    std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData(
+        const type::Type* type,
+        utils::VectorRef<const ast::Attribute*> attributes) const;
+
     /// For a N-uple of expressions, resolve to the appropriate global resources
     /// and call 'cb'.
     /// 'cb' may be called multiple times.
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 44c2330..d52acfc 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -75,6 +75,8 @@
 #include "src/tint/ast/while_statement.h"
 #include "src/tint/ast/workgroup_attribute.h"
 #include "src/tint/builtin/extension.h"
+#include "src/tint/builtin/interpolation_sampling.h"
+#include "src/tint/builtin/interpolation_type.h"
 #include "src/tint/constant/composite.h"
 #include "src/tint/constant/splat.h"
 #include "src/tint/constant/value.h"
@@ -3460,25 +3462,48 @@
     }
 
     /// Creates an ast::InterpolateAttribute
+    /// @param type the interpolation type
+    /// @returns the interpolate attribute pointer
+    template <typename TYPE, typename = DisableIfSource<TYPE>>
+    const ast::InterpolateAttribute* Interpolate(TYPE&& type) {
+        return Interpolate(source_, std::forward<TYPE>(type));
+    }
+
+    /// Creates an ast::InterpolateAttribute
     /// @param source the source information
     /// @param type the interpolation type
-    /// @param sampling the interpolation sampling
     /// @returns the interpolate attribute pointer
-    const ast::InterpolateAttribute* Interpolate(
-        const Source& source,
-        builtin::InterpolationType type,
-        builtin::InterpolationSampling sampling = builtin::InterpolationSampling::kUndefined) {
-        return create<ast::InterpolateAttribute>(source, type, sampling);
+    template <typename TYPE>
+    const ast::InterpolateAttribute* Interpolate(const Source& source, TYPE&& type) {
+        return create<ast::InterpolateAttribute>(source, Expr(std::forward<TYPE>(type)), nullptr);
     }
 
     /// Creates an ast::InterpolateAttribute
     /// @param type the interpolation type
     /// @param sampling the interpolation sampling
     /// @returns the interpolate attribute pointer
-    const ast::InterpolateAttribute* Interpolate(
-        builtin::InterpolationType type,
-        builtin::InterpolationSampling sampling = builtin::InterpolationSampling::kUndefined) {
-        return create<ast::InterpolateAttribute>(source_, type, sampling);
+    template <typename TYPE, typename SAMPLING, typename = DisableIfSource<TYPE>>
+    const ast::InterpolateAttribute* Interpolate(TYPE&& type, SAMPLING&& sampling) {
+        return Interpolate(source_, std::forward<TYPE>(type), std::forward<SAMPLING>(sampling));
+    }
+
+    /// Creates an ast::InterpolateAttribute
+    /// @param source the source information
+    /// @param type the interpolation type
+    /// @param sampling the interpolation sampling
+    /// @returns the interpolate attribute pointer
+    template <typename TYPE, typename SAMPLING>
+    const ast::InterpolateAttribute* Interpolate(const Source& source,
+                                                 TYPE&& type,
+                                                 SAMPLING&& sampling) {
+        if constexpr (std::is_same_v<std::decay_t<SAMPLING>, builtin::InterpolationSampling>) {
+            if (sampling == builtin::InterpolationSampling::kUndefined) {
+                return create<ast::InterpolateAttribute>(source, Expr(std::forward<TYPE>(type)),
+                                                         nullptr);
+            }
+        }
+        return create<ast::InterpolateAttribute>(source, Expr(std::forward<TYPE>(type)),
+                                                 Expr(std::forward<SAMPLING>(sampling)));
     }
 
     /// Creates an ast::InterpolateAttribute using flat interpolation
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index e615e14..a1c9042 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -1821,7 +1821,7 @@
         sampling == builtin::InterpolationSampling::kUndefined) {
         // This is the default. Don't add a decoration.
     } else {
-        attributes.Add(create<ast::InterpolateAttribute>(type, sampling));
+        attributes.Add(builder_.Interpolate(type, sampling));
     }
 
     return success();
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index d80a039..9314cae 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -1103,24 +1103,6 @@
                           std::move(attrs.value));  // attributes
 }
 
-// interpolation_sample_name
-//   : 'center'
-//   | 'centroid'
-//   | 'sample'
-Expect<builtin::InterpolationSampling> ParserImpl::expect_interpolation_sample_name() {
-    return expect_enum("interpolation sampling", builtin::ParseInterpolationSampling,
-                       builtin::kInterpolationSamplingStrings);
-}
-
-// interpolation_type_name
-//   : 'perspective'
-//   | 'linear'
-//   | 'flat'
-Expect<builtin::InterpolationType> ParserImpl::expect_interpolation_type_name() {
-    return expect_enum("interpolation type", builtin::ParseInterpolationType,
-                       builtin::kInterpolationTypeStrings);
-}
-
 // compound_statement
 //   : attribute* BRACE_LEFT statement* BRACE_RIGHT
 Expect<ast::BlockStatement*> ParserImpl::expect_compound_statement(std::string_view use) {
@@ -3061,15 +3043,15 @@
 
     if (t == "interpolate") {
         return expect_paren_block("interpolate attribute", [&]() -> Result {
-            auto type = expect_interpolation_type_name();
+            auto type = expect_expression("interpolation type");
             if (type.errored) {
                 return Failure::kErrored;
             }
 
-            builtin::InterpolationSampling sampling = builtin::InterpolationSampling::kUndefined;
+            const ast::Expression* sampling = nullptr;
             if (match(Token::Type::kComma)) {
                 if (!peek_is(Token::Type::kParenRight)) {
-                    auto sample = expect_interpolation_sample_name();
+                    auto sample = expect_expression("interpolation sampling");
                     if (sample.errored) {
                         return Failure::kErrored;
                     }
diff --git a/src/tint/reader/wgsl/parser_impl.h b/src/tint/reader/wgsl/parser_impl.h
index 19502c4..8cd0cdb 100644
--- a/src/tint/reader/wgsl/parser_impl.h
+++ b/src/tint/reader/wgsl/parser_impl.h
@@ -454,14 +454,6 @@
     /// not match a stage name.
     /// @returns the pipeline stage.
     Expect<ast::PipelineStage> expect_pipeline_stage();
-    /// Parses an interpolation sample name identifier, erroring if the next token does not match a
-    /// valid sample name.
-    /// @returns the parsed sample name.
-    Expect<builtin::InterpolationSampling> expect_interpolation_sample_name();
-    /// Parses an interpolation type name identifier, erroring if the next token does not match a
-    /// value type name.
-    /// @returns the parsed type name
-    Expect<builtin::InterpolationType> expect_interpolation_type_name();
     /// Parses a `compound_statement` grammar element, erroring on parse failure.
     /// @param use a description of what was being parsed if an error was raised
     /// @returns the parsed statements
diff --git a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
index 67df3b5..9f06aec 100644
--- a/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_variable_attribute_test.cc
@@ -308,8 +308,8 @@
     ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
 
     auto* interp = var_attr->As<ast::InterpolateAttribute>();
-    EXPECT_EQ(interp->type, builtin::InterpolationType::kFlat);
-    EXPECT_EQ(interp->sampling, builtin::InterpolationSampling::kUndefined);
+    ast::CheckIdentifier(p->builder().Symbols(), interp->type, "flat");
+    EXPECT_EQ(interp->sampling, nullptr);
 }
 
 TEST_F(ParserImplTest, Attribute_Interpolate_Single_TrailingComma) {
@@ -324,8 +324,8 @@
     ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
 
     auto* interp = var_attr->As<ast::InterpolateAttribute>();
-    EXPECT_EQ(interp->type, builtin::InterpolationType::kFlat);
-    EXPECT_EQ(interp->sampling, builtin::InterpolationSampling::kUndefined);
+    ast::CheckIdentifier(p->builder().Symbols(), interp->type, "flat");
+    EXPECT_EQ(interp->sampling, nullptr);
 }
 
 TEST_F(ParserImplTest, Attribute_Interpolate_Single_DoubleTrailingComma) {
@@ -335,8 +335,7 @@
     EXPECT_TRUE(attr.errored);
     EXPECT_EQ(attr.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), R"(1:18: expected interpolation sampling
-Possible values: 'center', 'centroid', 'sample')");
+    EXPECT_EQ(p->error(), R"(1:18: expected expression for interpolation sampling)");
 }
 
 TEST_F(ParserImplTest, Attribute_Interpolate_Perspective_Center) {
@@ -351,8 +350,8 @@
     ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
 
     auto* interp = var_attr->As<ast::InterpolateAttribute>();
-    EXPECT_EQ(interp->type, builtin::InterpolationType::kPerspective);
-    EXPECT_EQ(interp->sampling, builtin::InterpolationSampling::kCenter);
+    ast::CheckIdentifier(p->builder().Symbols(), interp->type, "perspective");
+    ast::CheckIdentifier(p->builder().Symbols(), interp->sampling, "center");
 }
 
 TEST_F(ParserImplTest, Attribute_Interpolate_Double_TrailingComma) {
@@ -367,8 +366,8 @@
     ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
 
     auto* interp = var_attr->As<ast::InterpolateAttribute>();
-    EXPECT_EQ(interp->type, builtin::InterpolationType::kPerspective);
-    EXPECT_EQ(interp->sampling, builtin::InterpolationSampling::kCenter);
+    ast::CheckIdentifier(p->builder().Symbols(), interp->type, "perspective");
+    ast::CheckIdentifier(p->builder().Symbols(), interp->sampling, "center");
 }
 
 TEST_F(ParserImplTest, Attribute_Interpolate_Perspective_Centroid) {
@@ -383,8 +382,8 @@
     ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
 
     auto* interp = var_attr->As<ast::InterpolateAttribute>();
-    EXPECT_EQ(interp->type, builtin::InterpolationType::kPerspective);
-    EXPECT_EQ(interp->sampling, builtin::InterpolationSampling::kCentroid);
+    ast::CheckIdentifier(p->builder().Symbols(), interp->type, "perspective");
+    ast::CheckIdentifier(p->builder().Symbols(), interp->sampling, "centroid");
 }
 
 TEST_F(ParserImplTest, Attribute_Interpolate_Linear_Sample) {
@@ -399,8 +398,8 @@
     ASSERT_TRUE(var_attr->Is<ast::InterpolateAttribute>());
 
     auto* interp = var_attr->As<ast::InterpolateAttribute>();
-    EXPECT_EQ(interp->type, builtin::InterpolationType::kLinear);
-    EXPECT_EQ(interp->sampling, builtin::InterpolationSampling::kSample);
+    ast::CheckIdentifier(p->builder().Symbols(), interp->type, "linear");
+    ast::CheckIdentifier(p->builder().Symbols(), interp->sampling, "sample");
 }
 
 TEST_F(ParserImplTest, Attribute_Interpolate_MissingLeftParen) {
@@ -430,31 +429,7 @@
     EXPECT_TRUE(attr.errored);
     EXPECT_EQ(attr.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), R"(1:13: expected interpolation type
-Possible values: 'flat', 'linear', 'perspective')");
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_InvalidFirstValue) {
-    auto p = parser("interpolate(other_thingy)");
-    auto attr = p->attribute();
-    EXPECT_FALSE(attr.matched);
-    EXPECT_TRUE(attr.errored);
-    EXPECT_EQ(attr.value, nullptr);
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), R"(1:13: expected interpolation type
-Possible values: 'flat', 'linear', 'perspective')");
-}
-
-TEST_F(ParserImplTest, Attribute_Interpolate_InvalidSecondValue) {
-    auto p = parser("interpolate(perspective, nope)");
-    auto attr = p->attribute();
-    EXPECT_FALSE(attr.matched);
-    EXPECT_TRUE(attr.errored);
-    EXPECT_EQ(attr.value, nullptr);
-    EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), R"(1:26: expected interpolation sampling
-Did you mean 'sample'?
-Possible values: 'center', 'centroid', 'sample')");
+    EXPECT_EQ(p->error(), R"(1:13: expected expression for interpolation type)");
 }
 
 TEST_F(ParserImplTest, Attribute_Binding) {
diff --git a/src/tint/resolver/attribute_validation_test.cc b/src/tint/resolver/attribute_validation_test.cc
index 100c239..e0c47d0 100644
--- a/src/tint/resolver/attribute_validation_test.cc
+++ b/src/tint/resolver/attribute_validation_test.cc
@@ -1649,8 +1649,7 @@
              Param("a", ty.vec4<f32>(),
                    utils::Vector{
                        Builtin(builtin::BuiltinValue::kPosition),
-                       Interpolate(Source{{12, 34}}, builtin::InterpolationType::kFlat,
-                                   builtin::InterpolationSampling::kUndefined),
+                       Interpolate(Source{{12, 34}}, builtin::InterpolationType::kFlat),
                    }),
          },
          ty.void_(), utils::Empty,
@@ -1673,8 +1672,7 @@
          },
          utils::Vector{
              Builtin(builtin::BuiltinValue::kPosition),
-             Interpolate(Source{{12, 34}}, builtin::InterpolationType::kFlat,
-                         builtin::InterpolationSampling::kUndefined),
+             Interpolate(Source{{12, 34}}, builtin::InterpolationType::kFlat),
          });
 
     EXPECT_FALSE(r()->Resolve());
@@ -1687,8 +1685,7 @@
         "S",
         utils::Vector{
             Member("a", ty.f32(),
-                   utils::Vector{Interpolate(Source{{12, 34}}, builtin::InterpolationType::kFlat,
-                                             builtin::InterpolationSampling::kUndefined)}),
+                   utils::Vector{Interpolate(Source{{12, 34}}, builtin::InterpolationType::kFlat)}),
         });
 
     EXPECT_FALSE(r()->Resolve());
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index a764596..e913789 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -438,6 +438,11 @@
                 TraverseValueExpression(id->expr);
                 return true;
             },
+            [&](const ast::InterpolateAttribute* interpolate) {
+                TraverseExpression(interpolate->type, "interpolation type", "references");
+                TraverseExpression(interpolate->sampling, "interpolation sampling", "references");
+                return true;
+            },
             [&](const ast::LocationAttribute* loc) {
                 TraverseValueExpression(loc->expr);
                 return true;
@@ -505,6 +510,16 @@
                 graph_.resolved_identifiers.Add(from, ResolvedIdentifier(access));
                 return;
             }
+            if (auto i_type = builtin::ParseInterpolationType(s);
+                i_type != builtin::InterpolationType::kUndefined) {
+                graph_.resolved_identifiers.Add(from, ResolvedIdentifier(i_type));
+                return;
+            }
+            if (auto i_smpl = builtin::ParseInterpolationSampling(s);
+                i_smpl != builtin::InterpolationSampling::kUndefined) {
+                graph_.resolved_identifiers.Add(from, ResolvedIdentifier(i_smpl));
+                return;
+            }
 
             UnknownSymbol(to, from->source, use);
             return;
@@ -884,6 +899,12 @@
     if (auto addr = AddressSpace(); addr != builtin::AddressSpace::kUndefined) {
         return "address space '" + utils::ToString(addr) + "'";
     }
+    if (auto type = InterpolationType(); type != builtin::InterpolationType::kUndefined) {
+        return "interpolation type '" + utils::ToString(type) + "'";
+    }
+    if (auto smpl = InterpolationSampling(); smpl != builtin::InterpolationSampling::kUndefined) {
+        return "interpolation sampling '" + utils::ToString(smpl) + "'";
+    }
     if (auto fmt = TexelFormat(); fmt != builtin::TexelFormat::kUndefined) {
         return "texel format '" + utils::ToString(fmt) + "'";
     }
diff --git a/src/tint/resolver/dependency_graph.h b/src/tint/resolver/dependency_graph.h
index 382514f..da25383 100644
--- a/src/tint/resolver/dependency_graph.h
+++ b/src/tint/resolver/dependency_graph.h
@@ -22,6 +22,8 @@
 #include "src/tint/builtin/access.h"
 #include "src/tint/builtin/builtin.h"
 #include "src/tint/builtin/builtin_value.h"
+#include "src/tint/builtin/interpolation_sampling.h"
+#include "src/tint/builtin/interpolation_type.h"
 #include "src/tint/builtin/texel_format.h"
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/sem/builtin_type.h"
@@ -40,6 +42,8 @@
 /// - builtin::AddressSpace
 /// - builtin::Builtin
 /// - builtin::BuiltinValue
+/// - builtin::InterpolationSampling
+/// - builtin::InterpolationType
 /// - builtin::TexelFormat
 class ResolvedIdentifier {
   public:
@@ -106,6 +110,24 @@
         return builtin::BuiltinValue::kUndefined;
     }
 
+    /// @return the texel format if the ResolvedIdentifier holds type::InterpolationSampling,
+    /// otherwise type::InterpolationSampling::kUndefined
+    builtin::InterpolationSampling InterpolationSampling() const {
+        if (auto n = std::get_if<builtin::InterpolationSampling>(&value_)) {
+            return *n;
+        }
+        return builtin::InterpolationSampling::kUndefined;
+    }
+
+    /// @return the texel format if the ResolvedIdentifier holds type::InterpolationType,
+    /// otherwise type::InterpolationType::kUndefined
+    builtin::InterpolationType InterpolationType() const {
+        if (auto n = std::get_if<builtin::InterpolationType>(&value_)) {
+            return *n;
+        }
+        return builtin::InterpolationType::kUndefined;
+    }
+
     /// @return the texel format if the ResolvedIdentifier holds type::TexelFormat, otherwise
     /// type::TexelFormat::kUndefined
     builtin::TexelFormat TexelFormat() const {
@@ -145,6 +167,8 @@
                  builtin::AddressSpace,
                  builtin::Builtin,
                  builtin::BuiltinValue,
+                 builtin::InterpolationSampling,
+                 builtin::InterpolationType,
                  builtin::TexelFormat>
         value_;
 };
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index d8ec08b..8c1f0bd 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -21,6 +21,7 @@
 #include "src/tint/resolver/dependency_graph.h"
 #include "src/tint/resolver/resolver_test_helper.h"
 #include "src/tint/type/texture_dimension.h"
+#include "src/tint/utils/transform.h"
 
 using namespace tint::number_suffixes;  // NOLINT
 
@@ -139,6 +140,40 @@
     WorkgroupSizeValue,
 };
 
+static constexpr SymbolUseKind kAllUseKinds[] = {
+    SymbolUseKind::GlobalVarType,
+    SymbolUseKind::GlobalVarArrayElemType,
+    SymbolUseKind::GlobalVarArraySizeValue,
+    SymbolUseKind::GlobalVarVectorElemType,
+    SymbolUseKind::GlobalVarMatrixElemType,
+    SymbolUseKind::GlobalVarSampledTexElemType,
+    SymbolUseKind::GlobalVarMultisampledTexElemType,
+    SymbolUseKind::GlobalVarValue,
+    SymbolUseKind::GlobalConstType,
+    SymbolUseKind::GlobalConstArrayElemType,
+    SymbolUseKind::GlobalConstArraySizeValue,
+    SymbolUseKind::GlobalConstVectorElemType,
+    SymbolUseKind::GlobalConstMatrixElemType,
+    SymbolUseKind::GlobalConstValue,
+    SymbolUseKind::AliasType,
+    SymbolUseKind::StructMemberType,
+    SymbolUseKind::CallFunction,
+    SymbolUseKind::ParameterType,
+    SymbolUseKind::LocalVarType,
+    SymbolUseKind::LocalVarArrayElemType,
+    SymbolUseKind::LocalVarArraySizeValue,
+    SymbolUseKind::LocalVarVectorElemType,
+    SymbolUseKind::LocalVarMatrixElemType,
+    SymbolUseKind::LocalVarValue,
+    SymbolUseKind::LocalLetType,
+    SymbolUseKind::LocalLetValue,
+    SymbolUseKind::NestedLocalVarType,
+    SymbolUseKind::NestedLocalVarValue,
+    SymbolUseKind::NestedLocalLetType,
+    SymbolUseKind::NestedLocalLetValue,
+    SymbolUseKind::WorkgroupSizeValue,
+};
+
 static constexpr SymbolUseKind kTypeUseKinds[] = {
     SymbolUseKind::GlobalVarType,
     SymbolUseKind::GlobalVarArrayElemType,
@@ -1178,51 +1213,6 @@
     EXPECT_EQ(resolved->BuiltinFunction(), builtin) << resolved->String(Symbols(), Diagnostics());
 }
 
-TEST_P(ResolverDependencyGraphResolveToBuiltinFunc, ShadowedByGlobalVar) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::GlobalVar, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToBuiltinFunc, ShadowedByStruct) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Struct, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToBuiltinFunc, ShadowedByFunc) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Function, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
 INSTANTIATE_TEST_SUITE_P(Types,
                          ResolverDependencyGraphResolveToBuiltinFunc,
                          testing::Combine(testing::ValuesIn(kTypeUseKinds),
@@ -1263,56 +1253,6 @@
         << resolved->String(Symbols(), Diagnostics());
 }
 
-TEST_P(ResolverDependencyGraphResolveToBuiltinType, ShadowedByGlobalVar) {
-    const auto use = std::get<0>(GetParam());
-    const std::string name = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(name);
-
-    auto* decl =
-        GlobalVar(symbol, name == "i32" ? ty.u32() : ty.i32(), builtin::AddressSpace::kPrivate);
-
-    SymbolTestHelper helper(this);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToBuiltinType, ShadowedByStruct) {
-    const auto use = std::get<0>(GetParam());
-    const std::string name = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(name);
-
-    auto* decl = Structure(symbol, utils::Vector{
-                                       Member("m", name == "i32" ? ty.u32() : ty.i32()),
-                                   });
-
-    SymbolTestHelper helper(this);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToBuiltinType, ShadowedByFunc) {
-    const auto use = std::get<0>(GetParam());
-    const auto name = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(name);
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Function, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
 INSTANTIATE_TEST_SUITE_P(Types,
                          ResolverDependencyGraphResolveToBuiltinType,
                          testing::Combine(testing::ValuesIn(kTypeUseKinds),
@@ -1353,51 +1293,6 @@
         << resolved->String(Symbols(), Diagnostics());
 }
 
-TEST_P(ResolverDependencyGraphResolveToAccess, ShadowedByGlobalVar) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::GlobalVar, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToAccess, ShadowedByStruct) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Struct, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToAccess, ShadowedByFunc) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Function, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
 INSTANTIATE_TEST_SUITE_P(Types,
                          ResolverDependencyGraphResolveToAccess,
                          testing::Combine(testing::ValuesIn(kTypeUseKinds),
@@ -1438,51 +1333,6 @@
         << resolved->String(Symbols(), Diagnostics());
 }
 
-TEST_P(ResolverDependencyGraphResolveToAddressSpace, ShadowedByGlobalConst) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::GlobalConst, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToAddressSpace, ShadowedByStruct) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Struct, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToAddressSpace, ShadowedByFunc) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Function, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
 INSTANTIATE_TEST_SUITE_P(Types,
                          ResolverDependencyGraphResolveToAddressSpace,
                          testing::Combine(testing::ValuesIn(kTypeUseKinds),
@@ -1523,51 +1373,6 @@
         << resolved->String(Symbols(), Diagnostics());
 }
 
-TEST_P(ResolverDependencyGraphResolveToBuiltinValue, ShadowedByGlobalConst) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::GlobalConst, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToBuiltinValue, ShadowedByStruct) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Struct, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToBuiltinValue, ShadowedByFunc) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Function, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
 INSTANTIATE_TEST_SUITE_P(Types,
                          ResolverDependencyGraphResolveToBuiltinValue,
                          testing::Combine(testing::ValuesIn(kTypeUseKinds),
@@ -1586,6 +1391,89 @@
 }  // namespace resolve_to_builtin_value
 
 ////////////////////////////////////////////////////////////////////////////////
+// Resolve to builtin::InterpolationSampling tests
+////////////////////////////////////////////////////////////////////////////////
+namespace resolve_to_interpolation_sampling {
+
+using ResolverDependencyGraphResolveToInterpolationSampling =
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+
+TEST_P(ResolverDependencyGraphResolveToInterpolationSampling, Resolve) {
+    const auto use = std::get<0>(GetParam());
+    const auto name = std::get<1>(GetParam());
+    const auto symbol = Symbols().New(name);
+
+    SymbolTestHelper helper(this);
+    auto* ident = helper.Add(use, symbol);
+    helper.Build();
+
+    auto resolved = Build().resolved_identifiers.Get(ident);
+    ASSERT_TRUE(resolved);
+    EXPECT_EQ(resolved->InterpolationSampling(), builtin::ParseInterpolationSampling(name))
+        << resolved->String(Symbols(), Diagnostics());
+}
+
+INSTANTIATE_TEST_SUITE_P(Types,
+                         ResolverDependencyGraphResolveToInterpolationSampling,
+                         testing::Combine(testing::ValuesIn(kTypeUseKinds),
+                                          testing::ValuesIn(builtin::kInterpolationTypeStrings)));
+
+INSTANTIATE_TEST_SUITE_P(Values,
+                         ResolverDependencyGraphResolveToInterpolationSampling,
+                         testing::Combine(testing::ValuesIn(kValueUseKinds),
+                                          testing::ValuesIn(builtin::kInterpolationTypeStrings)));
+
+INSTANTIATE_TEST_SUITE_P(Functions,
+                         ResolverDependencyGraphResolveToInterpolationSampling,
+                         testing::Combine(testing::ValuesIn(kFuncUseKinds),
+                                          testing::ValuesIn(builtin::kInterpolationTypeStrings)));
+
+}  // namespace resolve_to_interpolation_sampling
+
+////////////////////////////////////////////////////////////////////////////////
+// Resolve to builtin::InterpolationType tests
+////////////////////////////////////////////////////////////////////////////////
+namespace resolve_to_interpolation_sampling {
+
+using ResolverDependencyGraphResolveToInterpolationType =
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+
+TEST_P(ResolverDependencyGraphResolveToInterpolationType, Resolve) {
+    const auto use = std::get<0>(GetParam());
+    const auto name = std::get<1>(GetParam());
+    const auto symbol = Symbols().New(name);
+
+    SymbolTestHelper helper(this);
+    auto* ident = helper.Add(use, symbol);
+    helper.Build();
+
+    auto resolved = Build().resolved_identifiers.Get(ident);
+    ASSERT_TRUE(resolved);
+    EXPECT_EQ(resolved->InterpolationType(), builtin::ParseInterpolationType(name))
+        << resolved->String(Symbols(), Diagnostics());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Types,
+    ResolverDependencyGraphResolveToInterpolationType,
+    testing::Combine(testing::ValuesIn(kTypeUseKinds),
+                     testing::ValuesIn(builtin::kInterpolationSamplingStrings)));
+
+INSTANTIATE_TEST_SUITE_P(
+    Values,
+    ResolverDependencyGraphResolveToInterpolationType,
+    testing::Combine(testing::ValuesIn(kValueUseKinds),
+                     testing::ValuesIn(builtin::kInterpolationSamplingStrings)));
+
+INSTANTIATE_TEST_SUITE_P(
+    Functions,
+    ResolverDependencyGraphResolveToInterpolationType,
+    testing::Combine(testing::ValuesIn(kFuncUseKinds),
+                     testing::ValuesIn(builtin::kInterpolationSamplingStrings)));
+
+}  // namespace resolve_to_interpolation_sampling
+
+////////////////////////////////////////////////////////////////////////////////
 // Resolve to builtin::TexelFormat tests
 ////////////////////////////////////////////////////////////////////////////////
 namespace resolve_to_texel_format {
@@ -1608,51 +1496,6 @@
         << resolved->String(Symbols(), Diagnostics());
 }
 
-TEST_P(ResolverDependencyGraphResolveToTexelFormat, ShadowedByGlobalVar) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::GlobalVar, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToTexelFormat, ShadowedByStruct) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Struct, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
-TEST_P(ResolverDependencyGraphResolveToTexelFormat, ShadowedByFunc) {
-    const auto use = std::get<0>(GetParam());
-    const auto builtin = std::get<1>(GetParam());
-    const auto symbol = Symbols().New(utils::ToString(builtin));
-
-    SymbolTestHelper helper(this);
-    auto* decl = helper.Add(SymbolDeclKind::Function, symbol);
-    auto* ident = helper.Add(use, symbol);
-    helper.Build();
-
-    auto resolved = Build().resolved_identifiers.Get(ident);
-    ASSERT_TRUE(resolved);
-    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
-}
-
 INSTANTIATE_TEST_SUITE_P(Types,
                          ResolverDependencyGraphResolveToTexelFormat,
                          testing::Combine(testing::ValuesIn(kTypeUseKinds),
@@ -1675,10 +1518,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 namespace shadowing {
 
-using ResolverDependencyGraphShadowTest =
+using ResolverDependencyGraphShadowScopeTest =
     ResolverDependencyGraphTestWithParam<std::tuple<SymbolDeclKind, SymbolDeclKind>>;
 
-TEST_P(ResolverDependencyGraphShadowTest, Test) {
+TEST_P(ResolverDependencyGraphShadowScopeTest, Test) {
     const Symbol symbol = Sym("SYMBOL");
     const auto outer_kind = std::get<0>(GetParam());
     const auto inner_kind = std::get<1>(GetParam());
@@ -1701,18 +1544,107 @@
 }
 
 INSTANTIATE_TEST_SUITE_P(LocalShadowGlobal,
-                         ResolverDependencyGraphShadowTest,
+                         ResolverDependencyGraphShadowScopeTest,
                          testing::Combine(testing::ValuesIn(kGlobalDeclKinds),
                                           testing::ValuesIn(kLocalDeclKinds)));
 
 INSTANTIATE_TEST_SUITE_P(NestedLocalShadowLocal,
-                         ResolverDependencyGraphShadowTest,
+                         ResolverDependencyGraphShadowScopeTest,
                          testing::Combine(testing::Values(SymbolDeclKind::Parameter,
                                                           SymbolDeclKind::LocalVar,
                                                           SymbolDeclKind::LocalLet),
                                           testing::Values(SymbolDeclKind::NestedLocalVar,
                                                           SymbolDeclKind::NestedLocalLet)));
 
+using ResolverDependencyGraphShadowKindTest =
+    ResolverDependencyGraphTestWithParam<std::tuple<SymbolUseKind, const char*>>;
+
+TEST_P(ResolverDependencyGraphShadowKindTest, ShadowedByGlobalVar) {
+    const auto use = std::get<0>(GetParam());
+    const std::string_view name = std::get<1>(GetParam());
+    const auto symbol = Symbols().New(utils::ToString(name));
+
+    SymbolTestHelper helper(this);
+    auto* decl = GlobalVar(
+        symbol,  //
+        name == "i32" ? ty.u32() : ty.i32(),
+        name == "private" ? builtin::AddressSpace::kWorkgroup : builtin::AddressSpace::kPrivate);
+    auto* ident = helper.Add(use, symbol);
+    helper.Build();
+
+    auto resolved = Build().resolved_identifiers.Get(ident);
+    ASSERT_TRUE(resolved);
+    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
+}
+
+TEST_P(ResolverDependencyGraphShadowKindTest, ShadowedByStruct) {
+    const auto use = std::get<0>(GetParam());
+    const std::string_view name = std::get<1>(GetParam());
+    const auto symbol = Symbols().New(utils::ToString(name));
+
+    SymbolTestHelper helper(this);
+    auto* decl = Structure(symbol, utils::Vector{
+                                       Member("m", name == "i32" ? ty.u32() : ty.i32()),
+                                   });
+    auto* ident = helper.Add(use, symbol);
+    helper.Build();
+
+    auto resolved = Build().resolved_identifiers.Get(ident);
+    ASSERT_TRUE(resolved);
+    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
+}
+
+TEST_P(ResolverDependencyGraphShadowKindTest, ShadowedByFunc) {
+    const auto use = std::get<0>(GetParam());
+    const auto name = std::get<1>(GetParam());
+    const auto symbol = Symbols().New(utils::ToString(name));
+
+    SymbolTestHelper helper(this);
+    auto* decl = helper.Add(SymbolDeclKind::Function, symbol);
+    auto* ident = helper.Add(use, symbol);
+    helper.Build();
+
+    auto resolved = Build().resolved_identifiers.Get(ident);
+    ASSERT_TRUE(resolved);
+    EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics());
+}
+
+INSTANTIATE_TEST_SUITE_P(Access,
+                         ResolverDependencyGraphShadowKindTest,
+                         testing::Combine(testing::ValuesIn(kAllUseKinds),
+                                          testing::ValuesIn(builtin::kAccessStrings)));
+
+INSTANTIATE_TEST_SUITE_P(AddressSpace,
+                         ResolverDependencyGraphShadowKindTest,
+                         testing::Combine(testing::ValuesIn(kAllUseKinds),
+                                          testing::ValuesIn(builtin::kAddressSpaceStrings)));
+
+INSTANTIATE_TEST_SUITE_P(BuiltinType,
+                         ResolverDependencyGraphShadowKindTest,
+                         testing::Combine(testing::ValuesIn(kAllUseKinds),
+                                          testing::ValuesIn(builtin::kBuiltinStrings)));
+
+INSTANTIATE_TEST_SUITE_P(BuiltinFunction,
+                         ResolverDependencyGraphShadowKindTest,
+                         testing::Combine(testing::ValuesIn(kAllUseKinds),
+                                          testing::ValuesIn(builtin::kBuiltinStrings)));
+
+INSTANTIATE_TEST_SUITE_P(
+    InterpolationSampling,
+    ResolverDependencyGraphShadowKindTest,
+    testing::Combine(testing::ValuesIn(kAllUseKinds),
+                     testing::ValuesIn(builtin::kInterpolationSamplingStrings)));
+
+INSTANTIATE_TEST_SUITE_P(InterpolationType,
+                         ResolverDependencyGraphShadowKindTest,
+                         testing::Combine(testing::ValuesIn(kAllUseKinds),
+                                          testing::ValuesIn(builtin::kInterpolationTypeStrings)));
+
+INSTANTIATE_TEST_SUITE_P(TexelFormat,
+                         ResolverDependencyGraphShadowKindTest,
+                         testing::Combine(testing::ValuesIn(kAllUseKinds),
+                                          testing::ValuesIn(builtin::kTexelFormatStrings)));
+
 }  // namespace shadowing
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1753,6 +1685,7 @@
                       std::string(__FILE__) + ":" + std::to_string(line) + ": " + kind});
         return use;
     };
+
 #define V add_use(value_decl, Expr(value_sym), __LINE__, "V()")
 #define T add_use(type_decl, ty(type_sym), __LINE__, "T()")
 #define F add_use(func_decl, Ident(func_sym), __LINE__, "F()")
@@ -1772,6 +1705,8 @@
                    utils::Vector{
                        Location(V),  // Parameter attributes
                        Builtin(V),
+                       Interpolate(V),
+                       Interpolate(V, V),
                    }),
          },
          T,  // Return type
diff --git a/src/tint/resolver/expression_kind_test.cc b/src/tint/resolver/expression_kind_test.cc
index 8761055..01248b3 100644
--- a/src/tint/resolver/expression_kind_test.cc
+++ b/src/tint/resolver/expression_kind_test.cc
@@ -29,6 +29,8 @@
     kBuiltinType,
     kBuiltinValue,
     kFunction,
+    kInterpolationSampling,
+    kInterpolationType,
     kStruct,
     kTexelFormat,
     kTypeAlias,
@@ -49,6 +51,10 @@
             return out << "Def::kBuiltinValue";
         case Def::kFunction:
             return out << "Def::kFunction";
+        case Def::kInterpolationSampling:
+            return out << "Def::kInterpolationSampling";
+        case Def::kInterpolationType:
+            return out << "Def::kInterpolationType";
         case Def::kStruct:
             return out << "Def::kStruct";
         case Def::kTexelFormat:
@@ -69,6 +75,8 @@
     kCallExpr,
     kCallStmt,
     kFunctionReturnType,
+    kInterpolationSampling,
+    kInterpolationType,
     kMemberType,
     kTexelFormat,
     kValueExpression,
@@ -92,6 +100,10 @@
             return out << "Use::kCallStmt";
         case Use::kFunctionReturnType:
             return out << "Use::kFunctionReturnType";
+        case Use::kInterpolationSampling:
+            return out << "Use::kInterpolationSampling";
+        case Use::kInterpolationType:
+            return out << "Use::kInterpolationType";
         case Use::kMemberType:
             return out << "Use::kMemberType";
         case Use::kTexelFormat:
@@ -183,6 +195,28 @@
             };
             break;
         }
+        case Def::kInterpolationSampling: {
+            sym = Sym("center");
+            check_expr = [](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* enum_expr =
+                    expr->As<sem::BuiltinEnumExpression<builtin::InterpolationSampling>>();
+                ASSERT_NE(enum_expr, nullptr);
+                EXPECT_EQ(enum_expr->Value(), builtin::InterpolationSampling::kCenter);
+            };
+            break;
+        }
+        case Def::kInterpolationType: {
+            sym = Sym("linear");
+            check_expr = [](const sem::Expression* expr) {
+                ASSERT_NE(expr, nullptr);
+                auto* enum_expr =
+                    expr->As<sem::BuiltinEnumExpression<builtin::InterpolationType>>();
+                ASSERT_NE(enum_expr, nullptr);
+                EXPECT_EQ(enum_expr->Value(), builtin::InterpolationType::kLinear);
+            };
+            break;
+        }
         case Def::kStruct: {
             auto* s = Structure(kDefSource, "STRUCT", utils::Vector{Member("m", ty.i32())});
             sym = Sym("STRUCT");
@@ -256,6 +290,26 @@
         case Use::kFunctionReturnType:
             Func("f", utils::Empty, ty(expr), Return(Call(sym)));
             break;
+        case Use::kInterpolationSampling: {
+            Func("f",
+                 utils::Vector{Param("p", ty.vec4<f32>(),
+                                     utils::Vector{
+                                         Location(0_a),
+                                         Interpolate(builtin::InterpolationType::kLinear, expr),
+                                     })},
+                 ty.void_(), utils::Empty, utils::Vector{Stage(ast::PipelineStage::kFragment)});
+            break;
+        }
+        case Use::kInterpolationType: {
+            Func("f",
+                 utils::Vector{Param("p", ty.vec4<f32>(),
+                                     utils::Vector{
+                                         Location(0_a),
+                                         Interpolate(expr, builtin::InterpolationSampling::kCenter),
+                                     })},
+                 ty.void_(), utils::Empty, utils::Vector{Stage(ast::PipelineStage::kFragment)});
+            break;
+        }
         case Use::kMemberType:
             Structure("s", utils::Vector{Member("m", ty(expr))});
             break;
@@ -296,6 +350,10 @@
         {Def::kAccess, Use::kCallExpr, R"(5:6 error: cannot use access 'write' as call target)"},
         {Def::kAccess, Use::kCallStmt, R"(5:6 error: cannot use access 'write' as call target)"},
         {Def::kAccess, Use::kFunctionReturnType, R"(5:6 error: cannot use access 'write' as type)"},
+        {Def::kAccess, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use access 'write' as interpolation sampling)"},
+        {Def::kAccess, Use::kInterpolationType,
+         R"(5:6 error: cannot use access 'write' as interpolation type)"},
         {Def::kAccess, Use::kMemberType, R"(5:6 error: cannot use access 'write' as type)"},
         {Def::kAccess, Use::kTexelFormat,
          R"(5:6 error: cannot use access 'write' as texel format)"},
@@ -316,6 +374,10 @@
          R"(5:6 error: cannot use address space 'workgroup' as call target)"},
         {Def::kAddressSpace, Use::kFunctionReturnType,
          R"(5:6 error: cannot use address space 'workgroup' as type)"},
+        {Def::kAddressSpace, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use address space 'workgroup' as interpolation sampling)"},
+        {Def::kAddressSpace, Use::kInterpolationType,
+         R"(5:6 error: cannot use address space 'workgroup' as interpolation type)"},
         {Def::kAddressSpace, Use::kMemberType,
          R"(5:6 error: cannot use address space 'workgroup' as type)"},
         {Def::kAddressSpace, Use::kTexelFormat,
@@ -338,6 +400,10 @@
         {Def::kBuiltinFunction, Use::kCallStmt, kPass},
         {Def::kBuiltinFunction, Use::kFunctionReturnType,
          R"(7:8 error: missing '(' for builtin function call)"},
+        {Def::kBuiltinFunction, Use::kInterpolationSampling,
+         R"(7:8 error: missing '(' for builtin function call)"},
+        {Def::kBuiltinFunction, Use::kInterpolationType,
+         R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kMemberType,
          R"(7:8 error: missing '(' for builtin function call)"},
         {Def::kBuiltinFunction, Use::kTexelFormat,
@@ -359,6 +425,10 @@
          R"(5:6 error: cannot use type 'vec4<f32>' as builtin value)"},
         {Def::kBuiltinType, Use::kCallExpr, kPass},
         {Def::kBuiltinType, Use::kFunctionReturnType, kPass},
+        {Def::kBuiltinType, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use type 'vec4<f32>' as interpolation sampling)"},
+        {Def::kBuiltinType, Use::kInterpolationType,
+         R"(5:6 error: cannot use type 'vec4<f32>' as interpolation type)"},
         {Def::kBuiltinType, Use::kMemberType, kPass},
         {Def::kBuiltinType, Use::kTexelFormat,
          R"(5:6 error: cannot use type 'vec4<f32>' as texel format)"},
@@ -383,6 +453,10 @@
          R"(5:6 error: cannot use builtin value 'position' as call target)"},
         {Def::kBuiltinValue, Use::kFunctionReturnType,
          R"(5:6 error: cannot use builtin value 'position' as type)"},
+        {Def::kBuiltinValue, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use builtin value 'position' as interpolation sampling)"},
+        {Def::kBuiltinValue, Use::kInterpolationType,
+         R"(5:6 error: cannot use builtin value 'position' as interpolation type)"},
         {Def::kBuiltinValue, Use::kMemberType,
          R"(5:6 error: cannot use builtin value 'position' as type)"},
         {Def::kBuiltinValue, Use::kTexelFormat,
@@ -409,6 +483,12 @@
         {Def::kFunction, Use::kFunctionReturnType,
          R"(5:6 error: cannot use function 'FUNCTION' as type
 1:2 note: function 'FUNCTION' declared here)"},
+        {Def::kFunction, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use function 'FUNCTION' as interpolation sampling
+1:2 note: function 'FUNCTION' declared here)"},
+        {Def::kFunction, Use::kInterpolationType,
+         R"(5:6 error: cannot use function 'FUNCTION' as interpolation type
+1:2 note: function 'FUNCTION' declared here)"},
         {Def::kFunction, Use::kMemberType,
          R"(5:6 error: cannot use function 'FUNCTION' as type
 1:2 note: function 'FUNCTION' declared here)"},
@@ -424,6 +504,62 @@
         {Def::kFunction, Use::kUnaryOp, R"(5:6 error: cannot use function 'FUNCTION' as value
 1:2 note: function 'FUNCTION' declared here)"},
 
+        {Def::kInterpolationSampling, Use::kAccess,
+         R"(5:6 error: cannot use interpolation sampling 'center' as access)"},
+        {Def::kInterpolationSampling, Use::kAddressSpace,
+         R"(5:6 error: cannot use interpolation sampling 'center' as address space)"},
+        {Def::kInterpolationSampling, Use::kBinaryOp,
+         R"(5:6 error: cannot use interpolation sampling 'center' as value)"},
+        {Def::kInterpolationSampling, Use::kBuiltinValue,
+         R"(5:6 error: cannot use interpolation sampling 'center' as builtin value)"},
+        {Def::kInterpolationSampling, Use::kCallStmt,
+         R"(5:6 error: cannot use interpolation sampling 'center' as call target)"},
+        {Def::kInterpolationSampling, Use::kCallExpr,
+         R"(5:6 error: cannot use interpolation sampling 'center' as call target)"},
+        {Def::kInterpolationSampling, Use::kFunctionReturnType,
+         R"(5:6 error: cannot use interpolation sampling 'center' as type)"},
+        {Def::kInterpolationSampling, Use::kInterpolationSampling, kPass},
+        {Def::kInterpolationSampling, Use::kInterpolationType,
+         R"(5:6 error: cannot use interpolation sampling 'center' as interpolation type)"},
+        {Def::kInterpolationSampling, Use::kMemberType,
+         R"(5:6 error: cannot use interpolation sampling 'center' as type)"},
+        {Def::kInterpolationSampling, Use::kTexelFormat,
+         R"(5:6 error: cannot use interpolation sampling 'center' as texel format)"},
+        {Def::kInterpolationSampling, Use::kValueExpression,
+         R"(5:6 error: cannot use interpolation sampling 'center' as value)"},
+        {Def::kInterpolationSampling, Use::kVariableType,
+         R"(5:6 error: cannot use interpolation sampling 'center' as type)"},
+        {Def::kInterpolationSampling, Use::kUnaryOp,
+         R"(5:6 error: cannot use interpolation sampling 'center' as value)"},
+
+        {Def::kInterpolationType, Use::kAccess,
+         R"(5:6 error: cannot use interpolation type 'linear' as access)"},
+        {Def::kInterpolationType, Use::kAddressSpace,
+         R"(5:6 error: cannot use interpolation type 'linear' as address space)"},
+        {Def::kInterpolationType, Use::kBinaryOp,
+         R"(5:6 error: cannot use interpolation type 'linear' as value)"},
+        {Def::kInterpolationType, Use::kBuiltinValue,
+         R"(5:6 error: cannot use interpolation type 'linear' as builtin value)"},
+        {Def::kInterpolationType, Use::kCallStmt,
+         R"(5:6 error: cannot use interpolation type 'linear' as call target)"},
+        {Def::kInterpolationType, Use::kCallExpr,
+         R"(5:6 error: cannot use interpolation type 'linear' as call target)"},
+        {Def::kInterpolationType, Use::kFunctionReturnType,
+         R"(5:6 error: cannot use interpolation type 'linear' as type)"},
+        {Def::kInterpolationType, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use interpolation type 'linear' as interpolation sampling)"},
+        {Def::kInterpolationType, Use::kInterpolationType, kPass},
+        {Def::kInterpolationType, Use::kMemberType,
+         R"(5:6 error: cannot use interpolation type 'linear' as type)"},
+        {Def::kInterpolationType, Use::kTexelFormat,
+         R"(5:6 error: cannot use interpolation type 'linear' as texel format)"},
+        {Def::kInterpolationType, Use::kValueExpression,
+         R"(5:6 error: cannot use interpolation type 'linear' as value)"},
+        {Def::kInterpolationType, Use::kVariableType,
+         R"(5:6 error: cannot use interpolation type 'linear' as type)"},
+        {Def::kInterpolationType, Use::kUnaryOp,
+         R"(5:6 error: cannot use interpolation type 'linear' as value)"},
+
         {Def::kStruct, Use::kAccess, R"(5:6 error: cannot use type 'STRUCT' as access)"},
         {Def::kStruct, Use::kAddressSpace,
          R"(5:6 error: cannot use type 'STRUCT' as address space)"},
@@ -433,6 +569,10 @@
         {Def::kStruct, Use::kBuiltinValue,
          R"(5:6 error: cannot use type 'STRUCT' as builtin value)"},
         {Def::kStruct, Use::kFunctionReturnType, kPass},
+        {Def::kStruct, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use type 'STRUCT' as interpolation sampling)"},
+        {Def::kStruct, Use::kInterpolationType,
+         R"(5:6 error: cannot use type 'STRUCT' as interpolation type)"},
         {Def::kStruct, Use::kMemberType, kPass},
         {Def::kStruct, Use::kTexelFormat, R"(5:6 error: cannot use type 'STRUCT' as texel format)"},
         {Def::kStruct, Use::kValueExpression,
@@ -459,6 +599,10 @@
          R"(5:6 error: cannot use texel format 'rgba8unorm' as call target)"},
         {Def::kTexelFormat, Use::kFunctionReturnType,
          R"(5:6 error: cannot use texel format 'rgba8unorm' as type)"},
+        {Def::kTexelFormat, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use texel format 'rgba8unorm' as interpolation sampling)"},
+        {Def::kTexelFormat, Use::kInterpolationType,
+         R"(5:6 error: cannot use texel format 'rgba8unorm' as interpolation type)"},
         {Def::kTexelFormat, Use::kMemberType,
          R"(5:6 error: cannot use texel format 'rgba8unorm' as type)"},
         {Def::kTexelFormat, Use::kTexelFormat, kPass},
@@ -479,6 +623,10 @@
          R"(5:6 error: cannot use type 'i32' as builtin value)"},
         {Def::kTypeAlias, Use::kCallExpr, kPass},
         {Def::kTypeAlias, Use::kFunctionReturnType, kPass},
+        {Def::kTypeAlias, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use type 'i32' as interpolation sampling)"},
+        {Def::kTypeAlias, Use::kInterpolationType,
+         R"(5:6 error: cannot use type 'i32' as interpolation type)"},
         {Def::kTypeAlias, Use::kMemberType, kPass},
         {Def::kTypeAlias, Use::kTexelFormat, R"(5:6 error: cannot use type 'i32' as texel format)"},
         {Def::kTypeAlias, Use::kValueExpression,
@@ -507,6 +655,12 @@
         {Def::kVariable, Use::kFunctionReturnType,
          R"(5:6 error: cannot use const 'VARIABLE' as type
 1:2 note: const 'VARIABLE' declared here)"},
+        {Def::kVariable, Use::kInterpolationSampling,
+         R"(5:6 error: cannot use const 'VARIABLE' as interpolation sampling
+1:2 note: const 'VARIABLE' declared here)"},
+        {Def::kVariable, Use::kInterpolationType,
+         R"(5:6 error: cannot use const 'VARIABLE' as interpolation type
+1:2 note: const 'VARIABLE' declared here)"},
         {Def::kVariable, Use::kMemberType,
          R"(5:6 error: cannot use const 'VARIABLE' as type
 1:2 note: const 'VARIABLE' declared here)"},
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 3d0b539..ca5c46f 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -90,6 +90,8 @@
 TINT_INSTANTIATE_TYPEINFO(tint::sem::BuiltinEnumExpression<tint::builtin::Access>);
 TINT_INSTANTIATE_TYPEINFO(tint::sem::BuiltinEnumExpression<tint::builtin::AddressSpace>);
 TINT_INSTANTIATE_TYPEINFO(tint::sem::BuiltinEnumExpression<tint::builtin::BuiltinValue>);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::BuiltinEnumExpression<tint::builtin::InterpolationSampling>);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::BuiltinEnumExpression<tint::builtin::InterpolationType>);
 TINT_INSTANTIATE_TYPEINFO(tint::sem::BuiltinEnumExpression<tint::builtin::TexelFormat>);
 
 namespace tint::resolver {
@@ -1521,6 +1523,16 @@
     return sem_.AsAccess(Expression(expr));
 }
 
+sem::BuiltinEnumExpression<builtin::InterpolationSampling>* Resolver::InterpolationSampling(
+    const ast::Expression* expr) {
+    return sem_.AsInterpolationSampling(Expression(expr));
+}
+
+sem::BuiltinEnumExpression<builtin::InterpolationType>* Resolver::InterpolationType(
+    const ast::Expression* expr) {
+    return sem_.AsInterpolationType(Expression(expr));
+}
+
 void Resolver::RegisterStore(const sem::ValueExpression* expr) {
     auto& info = alias_analysis_infos_[current_function_];
     Switch(
@@ -3001,6 +3013,18 @@
             expr, current_statement_, builtin);
     }
 
+    if (auto i_smpl = resolved->InterpolationSampling();
+        i_smpl != builtin::InterpolationSampling::kUndefined) {
+        return builder_->create<sem::BuiltinEnumExpression<builtin::InterpolationSampling>>(
+            expr, current_statement_, i_smpl);
+    }
+
+    if (auto i_type = resolved->InterpolationType();
+        i_type != builtin::InterpolationType::kUndefined) {
+        return builder_->create<sem::BuiltinEnumExpression<builtin::InterpolationType>>(
+            expr, current_statement_, i_type);
+    }
+
     if (auto fmt = resolved->TexelFormat(); fmt != builtin::TexelFormat::kUndefined) {
         return builder_->create<sem::BuiltinEnumExpression<builtin::TexelFormat>>(
             expr, current_statement_, fmt);
@@ -3326,7 +3350,8 @@
     return Switch(
         attr,  //
         [&](const ast::BuiltinAttribute* b) { return BuiltinAttribute(b); },
-        [&](const ast::DiagnosticAttribute* dc) { return DiagnosticControl(dc->control); },
+        [&](const ast::DiagnosticAttribute* d) { return DiagnosticControl(d->control); },
+        [&](const ast::InterpolateAttribute* i) { return InterpolateAttribute(i); },
         [&](Default) { return true; });
 }
 
@@ -3341,6 +3366,16 @@
     return true;
 }
 
+bool Resolver::InterpolateAttribute(const ast::InterpolateAttribute* attr) {
+    if (!InterpolationType(attr->type)) {
+        return false;
+    }
+    if (attr->sampling && !InterpolationSampling(attr->sampling)) {
+        return false;
+    }
+    return true;
+}
+
 bool Resolver::DiagnosticControl(const ast::DiagnosticControl& control) {
     Mark(control.rule_name);
 
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 2a04a2a..bbde9e9 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -163,6 +163,20 @@
     /// diagnostic is raised and nullptr is returned.
     sem::BuiltinEnumExpression<builtin::Access>* AccessExpression(const ast::Expression* expr);
 
+    /// @returns the call of Expression() cast to a
+    /// sem::BuiltinEnumExpression<builtin::InterpolationSampling>*. If the sem::Expression is not a
+    /// sem::BuiltinEnumExpression<builtin::InterpolationSampling>*, then an error diagnostic is
+    /// raised and nullptr is returned.
+    sem::BuiltinEnumExpression<builtin::InterpolationSampling>* InterpolationSampling(
+        const ast::Expression* expr);
+
+    /// @returns the call of Expression() cast to a
+    /// sem::BuiltinEnumExpression<builtin::InterpolationType>*. If the sem::Expression is not a
+    /// sem::BuiltinEnumExpression<builtin::InterpolationType>*, then an error diagnostic is raised
+    /// and nullptr is returned.
+    sem::BuiltinEnumExpression<builtin::InterpolationType>* InterpolationType(
+        const ast::Expression* expr);
+
     /// Expression traverses the graph of expressions starting at `expr`, building a post-ordered
     /// list (leaf-first) of all the expression nodes. Each of the expressions are then resolved by
     /// dispatching to the appropriate expression handlers below.
@@ -304,6 +318,10 @@
     /// @returns true on success, false on failure
     bool BuiltinAttribute(const ast::BuiltinAttribute* attr);
 
+    /// Resolves the `@interpolate` attribute @p attr
+    /// @returns true on success, false on failure
+    bool InterpolateAttribute(const ast::InterpolateAttribute* attr);
+
     /// @param control the diagnostic control
     /// @returns true on success, false on failure
     bool DiagnosticControl(const ast::DiagnosticControl& control);
diff --git a/src/tint/resolver/sem_helper.cc b/src/tint/resolver/sem_helper.cc
index b8c328c..0a9bc1b 100644
--- a/src/tint/resolver/sem_helper.cc
+++ b/src/tint/resolver/sem_helper.cc
@@ -89,6 +89,16 @@
                          std::string(wanted),
                      builtin->Declaration()->source);
         },
+        [&](const sem::BuiltinEnumExpression<builtin::InterpolationSampling>* fmt) {
+            AddError("cannot use interpolation sampling '" + utils::ToString(fmt->Value()) +
+                         "' as " + std::string(wanted),
+                     fmt->Declaration()->source);
+        },
+        [&](const sem::BuiltinEnumExpression<builtin::InterpolationType>* fmt) {
+            AddError("cannot use interpolation type '" + utils::ToString(fmt->Value()) + "' as " +
+                         std::string(wanted),
+                     fmt->Declaration()->source);
+        },
         [&](const sem::BuiltinEnumExpression<builtin::TexelFormat>* fmt) {
             AddError("cannot use texel format '" + utils::ToString(fmt->Value()) + "' as " +
                          std::string(wanted),
diff --git a/src/tint/resolver/sem_helper.h b/src/tint/resolver/sem_helper.h
index a7c37b1..2ba8502 100644
--- a/src/tint/resolver/sem_helper.h
+++ b/src/tint/resolver/sem_helper.h
@@ -18,6 +18,8 @@
 #include <string>
 
 #include "src/tint/builtin/builtin_value.h"
+#include "src/tint/builtin/interpolation_sampling.h"
+#include "src/tint/builtin/interpolation_type.h"
 #include "src/tint/diagnostic/diagnostic.h"
 #include "src/tint/program_builder.h"
 #include "src/tint/resolver/dependency_graph.h"
@@ -164,6 +166,39 @@
         return nullptr;
     }
 
+    /// @param expr the semantic node
+    /// @returns nullptr if @p expr is nullptr, or @p expr cast to
+    /// sem::BuiltinEnumExpression<builtin::InterpolationSampling> if the cast is successful,
+    /// otherwise an error diagnostic is raised.
+    sem::BuiltinEnumExpression<builtin::InterpolationSampling>* AsInterpolationSampling(
+        sem::Expression* expr) const {
+        if (TINT_LIKELY(expr)) {
+            auto* enum_expr =
+                expr->As<sem::BuiltinEnumExpression<builtin::InterpolationSampling>>();
+            if (TINT_LIKELY(enum_expr)) {
+                return enum_expr;
+            }
+            ErrorUnexpectedExprKind(expr, "interpolation sampling");
+        }
+        return nullptr;
+    }
+
+    /// @param expr the semantic node
+    /// @returns nullptr if @p expr is nullptr, or @p expr cast to
+    /// sem::BuiltinEnumExpression<builtin::InterpolationType> if the cast is successful, otherwise
+    /// an error diagnostic is raised.
+    sem::BuiltinEnumExpression<builtin::InterpolationType>* AsInterpolationType(
+        sem::Expression* expr) const {
+        if (TINT_LIKELY(expr)) {
+            auto* enum_expr = expr->As<sem::BuiltinEnumExpression<builtin::InterpolationType>>();
+            if (TINT_LIKELY(enum_expr)) {
+                return enum_expr;
+            }
+            ErrorUnexpectedExprKind(expr, "interpolation type");
+        }
+        return nullptr;
+    }
+
     /// @returns the resolved type of the ast::Expression @p expr
     /// @param expr the expression
     type::Type* TypeOf(const ast::Expression* expr) const;
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 5e64d98..4db6c1a 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -985,14 +985,19 @@
                                      const type::Type* storage_ty) const {
     auto* type = storage_ty->UnwrapRef();
 
-    if (type->is_integer_scalar_or_vector() && attr->type != builtin::InterpolationType::kFlat) {
+    auto i_type = sem_.AsInterpolationType(sem_.Get(attr->type));
+    if (TINT_UNLIKELY(!i_type)) {
+        return false;
+    }
+
+    if (type->is_integer_scalar_or_vector() &&
+        i_type->Value() != builtin::InterpolationType::kFlat) {
         AddError("interpolation type must be 'flat' for integral user-defined IO types",
                  attr->source);
         return false;
     }
 
-    if (attr->type == builtin::InterpolationType::kFlat &&
-        attr->sampling != builtin::InterpolationSampling::kUndefined) {
+    if (attr->sampling && i_type->Value() == builtin::InterpolationType::kFlat) {
         AddError("flat interpolation attribute must not have a sampling parameter", attr->source);
         return false;
     }
diff --git a/src/tint/sem/builtin_type.h b/src/tint/sem/builtin_type.h
index 1328d99..114afb6 100644
--- a/src/tint/sem/builtin_type.h
+++ b/src/tint/sem/builtin_type.h
@@ -161,7 +161,7 @@
 /// matches the name in the WGSL spec.
 std::ostream& operator<<(std::ostream& out, BuiltinType i);
 
-/// All builtin types
+/// All builtin function
 constexpr BuiltinType kBuiltinTypes[] = {
     BuiltinType::kAbs,
     BuiltinType::kAcos,
@@ -279,6 +279,124 @@
     BuiltinType::kTintMaterialize,
 };
 
+/// All builtin function names
+constexpr const char* kBuiltinStrings[] = {
+    "abs",
+    "acos",
+    "acosh",
+    "all",
+    "any",
+    "arrayLength",
+    "asin",
+    "asinh",
+    "atan",
+    "atan2",
+    "atanh",
+    "ceil",
+    "clamp",
+    "cos",
+    "cosh",
+    "countLeadingZeros",
+    "countOneBits",
+    "countTrailingZeros",
+    "cross",
+    "degrees",
+    "determinant",
+    "distance",
+    "dot",
+    "dot4I8Packed",
+    "dot4U8Packed",
+    "dpdx",
+    "dpdxCoarse",
+    "dpdxFine",
+    "dpdy",
+    "dpdyCoarse",
+    "dpdyFine",
+    "exp",
+    "exp2",
+    "extractBits",
+    "faceForward",
+    "firstLeadingBit",
+    "firstTrailingBit",
+    "floor",
+    "fma",
+    "fract",
+    "frexp",
+    "fwidth",
+    "fwidthCoarse",
+    "fwidthFine",
+    "insertBits",
+    "inverseSqrt",
+    "ldexp",
+    "length",
+    "log",
+    "log2",
+    "max",
+    "min",
+    "mix",
+    "modf",
+    "normalize",
+    "pack2x16float",
+    "pack2x16snorm",
+    "pack2x16unorm",
+    "pack4x8snorm",
+    "pack4x8unorm",
+    "pow",
+    "quantizeToF16",
+    "radians",
+    "reflect",
+    "refract",
+    "reverseBits",
+    "round",
+    "saturate",
+    "select",
+    "sign",
+    "sin",
+    "sinh",
+    "smoothstep",
+    "sqrt",
+    "step",
+    "storageBarrier",
+    "tan",
+    "tanh",
+    "transpose",
+    "trunc",
+    "unpack2x16float",
+    "unpack2x16snorm",
+    "unpack2x16unorm",
+    "unpack4x8snorm",
+    "unpack4x8unorm",
+    "workgroupBarrier",
+    "workgroupUniformLoad",
+    "textureDimensions",
+    "textureGather",
+    "textureGatherCompare",
+    "textureNumLayers",
+    "textureNumLevels",
+    "textureNumSamples",
+    "textureSample",
+    "textureSampleBias",
+    "textureSampleCompare",
+    "textureSampleCompareLevel",
+    "textureSampleGrad",
+    "textureSampleLevel",
+    "textureSampleBaseClampToEdge",
+    "textureStore",
+    "textureLoad",
+    "atomicLoad",
+    "atomicStore",
+    "atomicAdd",
+    "atomicSub",
+    "atomicMax",
+    "atomicMin",
+    "atomicAnd",
+    "atomicOr",
+    "atomicXor",
+    "atomicExchange",
+    "atomicCompareExchangeWeak",
+    "_tint_materialize",
+};
+
 }  // namespace tint::sem
 
 #endif  // SRC_TINT_SEM_BUILTIN_TYPE_H_
diff --git a/src/tint/sem/builtin_type.h.tmpl b/src/tint/sem/builtin_type.h.tmpl
index 7cd715b..7e574f5 100644
--- a/src/tint/sem/builtin_type.h.tmpl
+++ b/src/tint/sem/builtin_type.h.tmpl
@@ -41,13 +41,20 @@
 /// matches the name in the WGSL spec.
 std::ostream& operator<<(std::ostream& out, BuiltinType i);
 
-/// All builtin types
+/// All builtin function
 constexpr BuiltinType kBuiltinTypes[] = {
 {{- range Sem.Builtins }}
     BuiltinType::k{{PascalCase .Name}},
 {{- end }}
 };
 
+/// All builtin function names
+constexpr const char* kBuiltinStrings[] = {
+{{- range Sem.Builtins }}
+    "{{.Name}}",
+{{- end }}
+};
+
 }  // namespace tint::sem
 
 #endif  // SRC_TINT_SEM_BUILTIN_TYPE_H_
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 773bcaf..147cc57 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -2191,7 +2191,11 @@
     utils::VectorRef<const ast::Attribute*> attributes) {
     for (auto* attr : attributes) {
         if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
-            switch (interpolate->type) {
+            auto& sem = program_->Sem();
+            auto i_type =
+                sem.Get<sem::BuiltinEnumExpression<builtin::InterpolationType>>(interpolate->type)
+                    ->Value();
+            switch (i_type) {
                 case builtin::InterpolationType::kPerspective:
                 case builtin::InterpolationType::kLinear:
                 case builtin::InterpolationType::kUndefined:
@@ -2200,14 +2204,20 @@
                     out << "flat ";
                     break;
             }
-            switch (interpolate->sampling) {
-                case builtin::InterpolationSampling::kCentroid:
-                    out << "centroid ";
-                    break;
-                case builtin::InterpolationSampling::kSample:
-                case builtin::InterpolationSampling::kCenter:
-                case builtin::InterpolationSampling::kUndefined:
-                    break;
+
+            if (interpolate->sampling) {
+                auto i_smpl = sem.Get<sem::BuiltinEnumExpression<builtin::InterpolationSampling>>(
+                                     interpolate->sampling)
+                                  ->Value();
+                switch (i_smpl) {
+                    case builtin::InterpolationSampling::kCentroid:
+                        out << "centroid ";
+                        break;
+                    case builtin::InterpolationSampling::kSample:
+                    case builtin::InterpolationSampling::kCenter:
+                    case builtin::InterpolationSampling::kUndefined:
+                        break;
+                }
             }
         }
     }
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 4c7c9ee..dbcd4b6 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -4219,8 +4219,21 @@
                         }
                         post += " : " + name;
                     } else if (auto* interpolate = attr->As<ast::InterpolateAttribute>()) {
-                        auto mod =
-                            interpolation_to_modifiers(interpolate->type, interpolate->sampling);
+                        auto& sem = program_->Sem();
+                        auto i_type =
+                            sem.Get<sem::BuiltinEnumExpression<builtin::InterpolationType>>(
+                                   interpolate->type)
+                                ->Value();
+
+                        auto i_smpl = builtin::InterpolationSampling::kUndefined;
+                        if (interpolate->sampling) {
+                            i_smpl =
+                                sem.Get<sem::BuiltinEnumExpression<builtin::InterpolationSampling>>(
+                                       interpolate->sampling)
+                                    ->Value();
+                        }
+
+                        auto mod = interpolation_to_modifiers(i_type, i_smpl);
                         if (mod.empty()) {
                             diagnostics_.add_error(diag::System::Writer,
                                                    "unsupported interpolation");
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 8723222..d401cd2 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -2891,8 +2891,21 @@
                         return true;
                     },
                     [&](const ast::InterpolateAttribute* interpolate) {
-                        auto name =
-                            interpolation_to_attribute(interpolate->type, interpolate->sampling);
+                        auto& sem = program_->Sem();
+                        auto i_type =
+                            sem.Get<sem::BuiltinEnumExpression<builtin::InterpolationType>>(
+                                   interpolate->type)
+                                ->Value();
+
+                        auto i_smpl = builtin::InterpolationSampling::kUndefined;
+                        if (interpolate->sampling) {
+                            i_smpl =
+                                sem.Get<sem::BuiltinEnumExpression<builtin::InterpolationSampling>>(
+                                       interpolate->sampling)
+                                    ->Value();
+                        }
+
+                        auto name = interpolation_to_attribute(i_type, i_smpl);
                         if (name.empty()) {
                             diagnostics_.add_error(diag::System::Writer,
                                                    "unknown interpolation attribute");
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index eeaa1e1..9c85bc1 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -851,7 +851,19 @@
                 return true;
             },
             [&](const ast::InterpolateAttribute* interpolate) {
-                AddInterpolationDecorations(var_id, interpolate->type, interpolate->sampling);
+                auto& s = builder_.Sem();
+                auto i_type =
+                    s.Get<sem::BuiltinEnumExpression<builtin::InterpolationType>>(interpolate->type)
+                        ->Value();
+
+                auto i_smpl = builtin::InterpolationSampling::kUndefined;
+                if (interpolate->sampling) {
+                    i_smpl = s.Get<sem::BuiltinEnumExpression<builtin::InterpolationSampling>>(
+                                  interpolate->sampling)
+                                 ->Value();
+                }
+
+                AddInterpolationDecorations(var_id, i_type, i_smpl);
                 return true;
             },
             [&](const ast::InvariantAttribute*) {
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 1fa03ff..a52bff0 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -575,9 +575,15 @@
                 return EmitDiagnosticControl(out, diagnostic->control);
             },
             [&](const ast::InterpolateAttribute* interpolate) {
-                out << "interpolate(" << interpolate->type;
-                if (interpolate->sampling != builtin::InterpolationSampling::kUndefined) {
-                    out << ", " << interpolate->sampling;
+                out << "interpolate(";
+                if (!EmitExpression(out, interpolate->type)) {
+                    return false;
+                }
+                if (interpolate->sampling) {
+                    out << ", ";
+                    if (!EmitExpression(out, interpolate->sampling)) {
+                        return false;
+                    }
                 }
                 out << ")";
                 return true;