[tint][wgsl] Add ColorAttribute

Not yet parsed, not yet resolved.

Bug: tint:2085
Change-Id: If94b6c5929265117dc87f81a34a0280aa5b14def
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/159602
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/core/attribute.cc b/src/tint/lang/core/attribute.cc
index 7b18915..2568294 100644
--- a/src/tint/lang/core/attribute.cc
+++ b/src/tint/lang/core/attribute.cc
@@ -51,6 +51,9 @@
     if (str == "builtin") {
         return Attribute::kBuiltin;
     }
+    if (str == "color") {
+        return Attribute::kColor;
+    }
     if (str == "compute") {
         return Attribute::kCompute;
     }
@@ -103,6 +106,8 @@
             return "binding";
         case Attribute::kBuiltin:
             return "builtin";
+        case Attribute::kColor:
+            return "color";
         case Attribute::kCompute:
             return "compute";
         case Attribute::kDiagnostic:
diff --git a/src/tint/lang/core/attribute.h b/src/tint/lang/core/attribute.h
index 3e7b087..46d7619 100644
--- a/src/tint/lang/core/attribute.h
+++ b/src/tint/lang/core/attribute.h
@@ -52,6 +52,7 @@
     kAlign,
     kBinding,
     kBuiltin,
+    kColor,
     kCompute,
     kDiagnostic,
     kFragment,
@@ -85,9 +86,9 @@
 Attribute ParseAttribute(std::string_view str);
 
 constexpr std::string_view kAttributeStrings[] = {
-    "align",    "binding", "builtin", "compute",        "diagnostic", "fragment",
-    "group",    "id",      "index",   "interpolate",    "invariant",  "location",
-    "must_use", "size",    "vertex",  "workgroup_size",
+    "align",    "binding",  "builtin", "color",  "compute",        "diagnostic",
+    "fragment", "group",    "id",      "index",  "interpolate",    "invariant",
+    "location", "must_use", "size",    "vertex", "workgroup_size",
 };
 
 }  // namespace tint::core
diff --git a/src/tint/lang/core/attribute_bench.cc b/src/tint/lang/core/attribute_bench.cc
index 1621a94..16a660c 100644
--- a/src/tint/lang/core/attribute_bench.cc
+++ b/src/tint/lang/core/attribute_bench.cc
@@ -66,97 +66,104 @@
         "Euiltin",
         "bPTTltin",
         "builtdxx",
-        "c44mpute",
-        "coSSpuVVe",
-        "RomR22e",
+        "c44lor",
+        "coVVSSr",
+        "22RRr",
+        "color",
+        "cFor",
+        "colr",
+        "ROOHVr",
+        "copuye",
+        "llnorrp77te",
+        "comp4t00",
         "compute",
-        "cFpu9e",
-        "comute",
-        "VOORRHte",
-        "dyagnstic",
-        "d77agnnnsllrrc",
-        "dia400ostic",
+        "opooe",
+        "zzpute",
+        "ciimppu1",
+        "XXiagnostic",
+        "IIia99nonnt55c",
+        "dYagSSrrstHHac",
         "diagnostic",
-        "danstooc",
-        "dignszzic",
-        "d11ansppiic",
-        "XXragment",
-        "fIIa9955nnnt",
-        "aarHHgmenYSS",
+        "dakkoHtc",
+        "jiagnsgRR",
+        "diagbost",
+        "fragjent",
+        "fragmnt",
+        "frqent",
         "fragment",
-        "fkkaet",
-        "gjamRRn",
-        "fabmnt",
-        "gjoup",
-        "goup",
-        "goq",
+        "fragenNN",
+        "ravvent",
+        "frgmQQnt",
+        "grof",
+        "grojp",
+        "NNrw2u",
         "group",
-        "Nroup",
-        "govv",
-        "gruQQ",
-        "r",
-        "jd",
-        "NNw",
+        "grup",
+        "grroup",
+        "Group",
+        "iFF",
+        "NN",
+        "iAA",
         "id",
-        "i",
-        "rrd",
-        "iG",
-        "inFFex",
-        "iE",
-        "inrrx",
+        "d",
+        "L",
+        "yy",
+        "nek",
+        "indx",
+        "Jndx",
         "index",
-        "inx",
-        "inJJD",
-        "ie",
-        "inerpklae",
-        "intrpolate",
-        "inJerpolae",
+        "incex",
+        "iOdex",
+        "__nttKKvv",
+        "int8rpoxx5e",
+        "inteqq__lte",
+        "interpqlate",
         "interpolate",
-        "interpocate",
-        "interpolaOe",
-        "__nttevvpoKKate",
-        "xnvari5n8",
-        "inFq__ant",
-        "iqqariant",
+        "33ntOpolat66",
+        "intoott6QQlate",
+        "66terpolate",
+        "zzxvO6rint",
+        "invayyiant",
+        "HHnariZt",
         "invariant",
-        "invar6a33O",
-        "i96arQttanoo",
-        "inari66nt",
-        "lOxati6zz",
-        "locyytion",
-        "lHHtion",
+        "iWW44rianq",
+        "iOOvaiant",
+        "ivariYnt",
+        "ltion",
+        "loaFion",
+        "wocatio",
         "location",
-        "qWW4caton",
-        "locOOton",
-        "ocatiYn",
-        "m_use",
-        "mutFuse",
-        "wust_us",
+        "Kcatoff",
+        "qocKKtion",
+        "lFcmmt3on",
+        "mustuse",
+        "must_se",
+        "ubbt_ube",
         "must_use",
-        "Kst_sff",
-        "qusKK_use",
-        "mFsmm_3se",
-        "ize",
-        "sze",
-        "sbbb",
+        "mstiius",
+        "muqt_uOe",
+        "muTTt_usvv",
+        "FFize",
+        "QP00",
+        "siPe",
         "size",
-        "iie",
-        "siqe",
-        "svvTTe",
-        "vertFFx",
-        "vrQ00P",
-        "vePtex",
+        "sis77",
+        "CiRbbe",
+        "sizXX",
+        "CCrtOOOO",
+        "vrsuL",
+        "verteX",
         "vertex",
-        "vsste77",
-        "veCtRRbb",
-        "verteXX",
-        "workgqou_siCCOOO",
-        "worsgroupsuzL",
-        "wXrkgroup_size",
+        "verte",
+        "qqrx",
+        "verte22",
+        "workgou0yzzizXX",
+        "workgrop_VPize",
+        "wokgroupnnsCze",
         "workgroup_size",
-        "workgroup_sze",
-        "wqqrOgoupize",
-        "workg22oup_size",
+        "workgrouq_sizHA",
+        "workgrup_size",
+        "forroupKKsize",
     };
     for (auto _ : state) {
         for (auto* str : kStrings) {
diff --git a/src/tint/lang/core/attribute_test.cc b/src/tint/lang/core/attribute_test.cc
index 58e03ab..ac6b338 100644
--- a/src/tint/lang/core/attribute_test.cc
+++ b/src/tint/lang/core/attribute_test.cc
@@ -57,14 +57,23 @@
 }
 
 static constexpr Case kValidCases[] = {
-    {"align", Attribute::kAlign},           {"binding", Attribute::kBinding},
-    {"builtin", Attribute::kBuiltin},       {"compute", Attribute::kCompute},
-    {"diagnostic", Attribute::kDiagnostic}, {"fragment", Attribute::kFragment},
-    {"group", Attribute::kGroup},           {"id", Attribute::kId},
-    {"index", Attribute::kIndex},           {"interpolate", Attribute::kInterpolate},
-    {"invariant", Attribute::kInvariant},   {"location", Attribute::kLocation},
-    {"must_use", Attribute::kMustUse},      {"size", Attribute::kSize},
-    {"vertex", Attribute::kVertex},         {"workgroup_size", Attribute::kWorkgroupSize},
+    {"align", Attribute::kAlign},
+    {"binding", Attribute::kBinding},
+    {"builtin", Attribute::kBuiltin},
+    {"color", Attribute::kColor},
+    {"compute", Attribute::kCompute},
+    {"diagnostic", Attribute::kDiagnostic},
+    {"fragment", Attribute::kFragment},
+    {"group", Attribute::kGroup},
+    {"id", Attribute::kId},
+    {"index", Attribute::kIndex},
+    {"interpolate", Attribute::kInterpolate},
+    {"invariant", Attribute::kInvariant},
+    {"location", Attribute::kLocation},
+    {"must_use", Attribute::kMustUse},
+    {"size", Attribute::kSize},
+    {"vertex", Attribute::kVertex},
+    {"workgroup_size", Attribute::kWorkgroupSize},
 };
 
 static constexpr Case kInvalidCases[] = {
@@ -77,45 +86,48 @@
     {"ppqqiliHH", Attribute::kUndefined},
     {"bucv", Attribute::kUndefined},
     {"biltGn", Attribute::kUndefined},
-    {"compiive", Attribute::kUndefined},
-    {"8WWmpute", Attribute::kUndefined},
-    {"cxxpute", Attribute::kUndefined},
-    {"dXagnosigg", Attribute::kUndefined},
-    {"dagnXuVc", Attribute::kUndefined},
-    {"diagnosti3", Attribute::kUndefined},
-    {"fraEment", Attribute::kUndefined},
-    {"PPagTTent", Attribute::kUndefined},
-    {"xxragddnt", Attribute::kUndefined},
-    {"g44oup", Attribute::kUndefined},
-    {"grVVSSp", Attribute::kUndefined},
-    {"22RRp", Attribute::kUndefined},
-    {"d", Attribute::kUndefined},
-    {"i", Attribute::kUndefined},
-    {"OVd", Attribute::kUndefined},
-    {"ndyx", Attribute::kUndefined},
-    {"n77rrldGx", Attribute::kUndefined},
-    {"inde40", Attribute::kUndefined},
-    {"itooolate", Attribute::kUndefined},
-    {"intezplate", Attribute::kUndefined},
-    {"ppnerii1olat", Attribute::kUndefined},
-    {"invarianXX", Attribute::kUndefined},
-    {"inv55ria99nII", Attribute::kUndefined},
-    {"irrvariaSSaHH", Attribute::kUndefined},
-    {"lkkcin", Attribute::kUndefined},
-    {"gjctRRo", Attribute::kUndefined},
-    {"lcbton", Attribute::kUndefined},
-    {"mustjuse", Attribute::kUndefined},
-    {"must_se", Attribute::kUndefined},
-    {"muquse", Attribute::kUndefined},
-    {"szNN", Attribute::kUndefined},
-    {"zvv", Attribute::kUndefined},
-    {"QQze", Attribute::kUndefined},
-    {"eterf", Attribute::kUndefined},
-    {"vertjx", Attribute::kUndefined},
-    {"v82wNNx", Attribute::kUndefined},
-    {"worgroup_size", Attribute::kUndefined},
-    {"workgrourr_size", Attribute::kUndefined},
-    {"workgroGp_size", Attribute::kUndefined},
+    {"covior", Attribute::kUndefined},
+    {"co8WWr", Attribute::kUndefined},
+    {"Mxxlo", Attribute::kUndefined},
+    {"cXputgg", Attribute::kUndefined},
+    {"opuXe", Attribute::kUndefined},
+    {"comp3te", Attribute::kUndefined},
+    {"diagnostiE", Attribute::kUndefined},
+    {"TTiagnosPPi", Attribute::kUndefined},
+    {"diagdoxxtic", Attribute::kUndefined},
+    {"44ragment", Attribute::kUndefined},
+    {"fSSagmenVV", Attribute::kUndefined},
+    {"Rag2Rent", Attribute::kUndefined},
+    {"gFup", Attribute::kUndefined},
+    {"grop", Attribute::kUndefined},
+    {"ROOHVp", Attribute::kUndefined},
+    {"y", Attribute::kUndefined},
+    {"Gn77rl", Attribute::kUndefined},
+    {"04d", Attribute::kUndefined},
+    {"oox", Attribute::kUndefined},
+    {"inzz", Attribute::kUndefined},
+    {"1ippex", Attribute::kUndefined},
+    {"interpoXXate", Attribute::kUndefined},
+    {"intII99r55olate", Attribute::kUndefined},
+    {"intaarpoSSrHHYe", Attribute::kUndefined},
+    {"kkvHant", Attribute::kUndefined},
+    {"jgaianRR", Attribute::kUndefined},
+    {"inaianb", Attribute::kUndefined},
+    {"locajion", Attribute::kUndefined},
+    {"locaton", Attribute::kUndefined},
+    {"loqion", Attribute::kUndefined},
+    {"mustusNN", Attribute::kUndefined},
+    {"usvvuse", Attribute::kUndefined},
+    {"mut_QQse", Attribute::kUndefined},
+    {"srf", Attribute::kUndefined},
+    {"sije", Attribute::kUndefined},
+    {"NNz2w", Attribute::kUndefined},
+    {"vrtex", Attribute::kUndefined},
+    {"rrertex", Attribute::kUndefined},
+    {"vGrtex", Attribute::kUndefined},
+    {"workgroup_sizFF", Attribute::kUndefined},
+    {"wErkrp_size", Attribute::kUndefined},
+    {"worgrroup_size", Attribute::kUndefined},
 };
 
 using AttributeParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/lang/core/core.def b/src/tint/lang/core/core.def
index 4e8ced4..2337b79 100644
--- a/src/tint/lang/core/core.def
+++ b/src/tint/lang/core/core.def
@@ -212,6 +212,9 @@
   size
   vertex
   workgroup_size
+
+  // framebuffer-fetch input
+  color
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/lang/wgsl/ast/BUILD.bazel b/src/tint/lang/wgsl/ast/BUILD.bazel
index 31601b6..f7514c9 100644
--- a/src/tint/lang/wgsl/ast/BUILD.bazel
+++ b/src/tint/lang/wgsl/ast/BUILD.bazel
@@ -57,6 +57,7 @@
     "case_selector.cc",
     "case_statement.cc",
     "clone_context.cc",
+    "color_attribute.cc",
     "compound_assignment_statement.cc",
     "const.cc",
     "const_assert.cc",
@@ -137,6 +138,7 @@
     "case_selector.h",
     "case_statement.h",
     "clone_context.h",
+    "color_attribute.h",
     "compound_assignment_statement.h",
     "const.h",
     "const_assert.h",
@@ -244,6 +246,7 @@
     "case_selector_test.cc",
     "case_statement_test.cc",
     "clone_context_test.cc",
+    "color_attribute_test.cc",
     "compound_assignment_statement_test.cc",
     "const_assert_test.cc",
     "continue_statement_test.cc",
diff --git a/src/tint/lang/wgsl/ast/BUILD.cmake b/src/tint/lang/wgsl/ast/BUILD.cmake
index ebad7e9..fe4eef9 100644
--- a/src/tint/lang/wgsl/ast/BUILD.cmake
+++ b/src/tint/lang/wgsl/ast/BUILD.cmake
@@ -77,6 +77,8 @@
   lang/wgsl/ast/case_statement.h
   lang/wgsl/ast/clone_context.cc
   lang/wgsl/ast/clone_context.h
+  lang/wgsl/ast/color_attribute.cc
+  lang/wgsl/ast/color_attribute.h
   lang/wgsl/ast/compound_assignment_statement.cc
   lang/wgsl/ast/compound_assignment_statement.h
   lang/wgsl/ast/const.cc
@@ -244,6 +246,7 @@
   lang/wgsl/ast/case_selector_test.cc
   lang/wgsl/ast/case_statement_test.cc
   lang/wgsl/ast/clone_context_test.cc
+  lang/wgsl/ast/color_attribute_test.cc
   lang/wgsl/ast/compound_assignment_statement_test.cc
   lang/wgsl/ast/const_assert_test.cc
   lang/wgsl/ast/continue_statement_test.cc
diff --git a/src/tint/lang/wgsl/ast/BUILD.gn b/src/tint/lang/wgsl/ast/BUILD.gn
index 4580e46..6cc263d 100644
--- a/src/tint/lang/wgsl/ast/BUILD.gn
+++ b/src/tint/lang/wgsl/ast/BUILD.gn
@@ -80,6 +80,8 @@
     "case_statement.h",
     "clone_context.cc",
     "clone_context.h",
+    "color_attribute.cc",
+    "color_attribute.h",
     "compound_assignment_statement.cc",
     "compound_assignment_statement.h",
     "const.cc",
@@ -244,6 +246,7 @@
       "case_selector_test.cc",
       "case_statement_test.cc",
       "clone_context_test.cc",
+      "color_attribute_test.cc",
       "compound_assignment_statement_test.cc",
       "const_assert_test.cc",
       "continue_statement_test.cc",
diff --git a/src/tint/lang/wgsl/ast/builder.h b/src/tint/lang/wgsl/ast/builder.h
index 1cbdfba..1b80ec9 100644
--- a/src/tint/lang/wgsl/ast/builder.h
+++ b/src/tint/lang/wgsl/ast/builder.h
@@ -67,6 +67,7 @@
 #include "src/tint/lang/wgsl/ast/call_expression.h"
 #include "src/tint/lang/wgsl/ast/call_statement.h"
 #include "src/tint/lang/wgsl/ast/case_statement.h"
+#include "src/tint/lang/wgsl/ast/color_attribute.h"
 #include "src/tint/lang/wgsl/ast/compound_assignment_statement.h"
 #include "src/tint/lang/wgsl/ast/const.h"
 #include "src/tint/lang/wgsl/ast/const_assert.h"
@@ -3167,6 +3168,23 @@
         return create<ast::LocationAttribute>(source, Expr(std::forward<EXPR>(location)));
     }
 
+    /// Creates an ast::ColorAttribute
+    /// @param index the index value expression
+    /// @returns the index attribute pointer
+    template <typename EXPR>
+    const ast::ColorAttribute* Color(EXPR&& index) {
+        return create<ast::ColorAttribute>(source_, Expr(std::forward<EXPR>(index)));
+    }
+
+    /// Creates an ast::ColorAttribute
+    /// @param source the source information
+    /// @param index the index value expression
+    /// @returns the index attribute pointer
+    template <typename EXPR>
+    const ast::ColorAttribute* Color(const Source& source, EXPR&& index) {
+        return create<ast::ColorAttribute>(source, Expr(std::forward<EXPR>(index)));
+    }
+
     /// Creates an ast::LocationAttribute
     /// @param location the location value expression
     /// @returns the location attribute pointer
diff --git a/src/tint/lang/wgsl/ast/color_attribute.cc b/src/tint/lang/wgsl/ast/color_attribute.cc
new file mode 100644
index 0000000..daa1597
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/color_attribute.cc
@@ -0,0 +1,60 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/ast/color_attribute.h"
+
+#include <string>
+
+#include "src/tint/lang/wgsl/ast/builder.h"
+#include "src/tint/lang/wgsl/ast/clone_context.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::ColorAttribute);
+
+namespace tint::ast {
+
+ColorAttribute::ColorAttribute(GenerationID pid,
+                               NodeID nid,
+                               const Source& src,
+                               const Expression* exp)
+    : Base(pid, nid, src), expr(exp) {
+    TINT_ASSERT_GENERATION_IDS_EQUAL(exp, generation_id);
+}
+
+ColorAttribute::~ColorAttribute() = default;
+
+std::string ColorAttribute::Name() const {
+    return "color";
+}
+
+const ColorAttribute* ColorAttribute::Clone(CloneContext& ctx) const {
+    // Clone arguments outside of create() call to have deterministic ordering
+    auto src = ctx.Clone(source);
+    auto e = ctx.Clone(expr);
+    return ctx.dst->create<ColorAttribute>(src, e);
+}
+
+}  // namespace tint::ast
diff --git a/src/tint/lang/wgsl/ast/color_attribute.h b/src/tint/lang/wgsl/ast/color_attribute.h
new file mode 100644
index 0000000..d0a21e4
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/color_attribute.h
@@ -0,0 +1,68 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_WGSL_AST_COLOR_ATTRIBUTE_H_
+#define SRC_TINT_LANG_WGSL_AST_COLOR_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/lang/wgsl/ast/attribute.h"
+
+// Forward declarations
+namespace tint::ast {
+class Expression;
+}
+
+namespace tint::ast {
+
+/// A color attribute (enabled with the frame-buffer fetch extension)
+class ColorAttribute final : public Castable<ColorAttribute, Attribute> {
+  public:
+    /// constructor
+    /// @param pid the identifier of the program that owns this node
+    /// @param nid the unique node identifier
+    /// @param src the source of this node
+    /// @param expr the frame-buffer index value
+    ColorAttribute(GenerationID pid, NodeID nid, const Source& src, const Expression* expr);
+    ~ColorAttribute() override;
+
+    /// @returns the WGSL name for the attribute
+    std::string Name() 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 ColorAttribute* Clone(CloneContext& ctx) const override;
+
+    /// The index value expression
+    const Expression* const expr;
+};
+
+}  // namespace tint::ast
+
+#endif  // SRC_TINT_LANG_WGSL_AST_COLOR_ATTRIBUTE_H_
diff --git a/src/tint/lang/wgsl/ast/color_attribute_test.cc b/src/tint/lang/wgsl/ast/color_attribute_test.cc
new file mode 100644
index 0000000..9afba5f
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/color_attribute_test.cc
@@ -0,0 +1,66 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "gtest/gtest-spi.h"
+
+#include "src/tint/lang/wgsl/ast/color_attribute.h"
+#include "src/tint/lang/wgsl/ast/helper_test.h"
+
+using namespace tint::core::number_suffixes;  // NOLINT
+
+namespace tint::ast {
+namespace {
+
+using ColorAttributeTest = TestHelper;
+
+TEST_F(ColorAttributeTest, Creation) {
+    auto* expr = Expr(1_u);
+    auto* c = Color(expr);
+    EXPECT_EQ(c->expr, expr);
+}
+
+TEST_F(ColorAttributeTest, Assert_Null_Builtin) {
+    EXPECT_FATAL_FAILURE(
+        {
+            ProgramBuilder b;
+            b.Color(nullptr);
+        },
+        "internal compiler error");
+}
+
+TEST_F(ColorAttributeTest, Assert_DifferentGenerationID_Color) {
+    EXPECT_FATAL_FAILURE(
+        {
+            ProgramBuilder b1;
+            ProgramBuilder b2;
+            b1.Color(b2.Expr(1_u));
+        },
+        "internal compiler error");
+}
+
+}  // namespace
+}  // namespace tint::ast
diff --git a/src/tint/lang/wgsl/reader/parser/error_resync_test.cc b/src/tint/lang/wgsl/reader/parser/error_resync_test.cc
index 0a368a9..a38fd87 100644
--- a/src/tint/lang/wgsl/reader/parser/error_resync_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/error_resync_test.cc
@@ -67,7 +67,7 @@
      ^
 
 test.wgsl:4:2 error: expected attribute
-Possible values: 'align', 'binding', 'builtin', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size'
+Possible values: 'align', 'binding', 'builtin', 'color', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size'
 @_ fn -> {}
  ^
 )");
@@ -135,7 +135,7 @@
          ^^^^
 
 test.wgsl:7:6 error: expected attribute
-Possible values: 'align', 'binding', 'builtin', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size'
+Possible values: 'align', 'binding', 'builtin', 'color', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size'
     @- x : i32,
      ^
 )");
diff --git a/src/tint/lang/wgsl/reader/parser/function_attribute_list_test.cc b/src/tint/lang/wgsl/reader/parser/function_attribute_list_test.cc
index 259f1a1..4fae501 100644
--- a/src/tint/lang/wgsl/reader/parser/function_attribute_list_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/function_attribute_list_test.cc
@@ -66,7 +66,7 @@
     EXPECT_TRUE(attrs.value.IsEmpty());
     EXPECT_EQ(p->error(), R"(1:2: expected attribute
 Did you mean 'invariant'?
-Possible values: 'align', 'binding', 'builtin', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size')");
+Possible values: 'align', 'binding', 'builtin', 'color', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size')");
 }
 
 }  // namespace
diff --git a/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc b/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc
index f10f725..417ab1f 100644
--- a/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc
@@ -64,7 +64,7 @@
     EXPECT_TRUE(attrs.value.IsEmpty());
     EXPECT_EQ(p->error(), R"(1:2: expected attribute
 Did you mean 'invariant'?
-Possible values: 'align', 'binding', 'builtin', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size')");
+Possible values: 'align', 'binding', 'builtin', 'color', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size')");
 }
 
 }  // namespace