[shuffle] Move utils to new structure.
The name `core` was decided fit better with the base language for the
IR. This CL moves the `core/` folder to `utils/` and moves the files
into the correct subdirectories. The build targets and namespaces are
not updated as part of the move and will be fixed up in later CLs.
Bug: tint:1988
Change-Id: I1fc4414c86b28e1669af2d2d07340ecfdd9ba681
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/142361
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/utils/cli/cli.cc b/src/tint/utils/cli/cli.cc
new file mode 100644
index 0000000..72ff0d6
--- /dev/null
+++ b/src/tint/utils/cli/cli.cc
@@ -0,0 +1,182 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/cli/cli.h"
+
+#include <algorithm>
+#include <sstream>
+#include <utility>
+
+#include "src/tint/utils/containers/hashset.h"
+#include "src/tint/utils/containers/transform.h"
+#include "src/tint/utils/text/string.h"
+
+namespace tint::utils::cli {
+
+Option::~Option() = default;
+
+void OptionSet::ShowHelp(std::ostream& s_out) {
+ utils::Vector<const Option*, 32> sorted_options;
+ for (auto* opt : options.Objects()) {
+ sorted_options.Push(opt);
+ }
+ sorted_options.Sort([](const Option* a, const Option* b) { return a->Name() < b->Name(); });
+
+ struct CmdInfo {
+ std::string left;
+ std::string right;
+ };
+ utils::Vector<CmdInfo, 64> cmd_infos;
+
+ for (auto* opt : sorted_options) {
+ {
+ std::stringstream left, right;
+ left << "--" << opt->Name();
+ if (auto param = opt->Parameter(); !param.empty()) {
+ left << " <" << param << ">";
+ }
+ right << opt->Description();
+ if (auto def = opt->DefaultValue(); !def.empty()) {
+ right << "\ndefault: " << def;
+ }
+ cmd_infos.Push({left.str(), right.str()});
+ }
+ if (auto alias = opt->Alias(); !alias.empty()) {
+ std::stringstream left, right;
+ left << "--" << alias;
+ right << "alias for --" << opt->Name();
+ cmd_infos.Push({left.str(), right.str()});
+ }
+ if (auto sn = opt->ShortName(); !sn.empty()) {
+ std::stringstream left, right;
+ left << " -" << sn;
+ right << "short name for --" << opt->Name();
+ cmd_infos.Push({left.str(), right.str()});
+ }
+ }
+
+ const size_t kMaxRightOffset = 30;
+
+ // Measure
+ size_t left_width = 0;
+ for (auto& cmd_info : cmd_infos) {
+ for (auto line : utils::Split(cmd_info.left, "\n")) {
+ if (line.length() < kMaxRightOffset) {
+ left_width = std::max(left_width, line.length());
+ }
+ }
+ }
+
+ // Print
+ left_width = std::min(left_width, kMaxRightOffset);
+
+ auto pad = [&](size_t n) {
+ while (n--) {
+ s_out << " ";
+ }
+ };
+
+ for (auto& cmd_info : cmd_infos) {
+ auto left_lines = utils::Split(cmd_info.left, "\n");
+ auto right_lines = utils::Split(cmd_info.right, "\n");
+
+ size_t num_lines = std::max(left_lines.Length(), right_lines.Length());
+ for (size_t i = 0; i < num_lines; i++) {
+ bool has_left = (i < left_lines.Length()) && !left_lines[i].empty();
+ bool has_right = (i < right_lines.Length()) && !right_lines[i].empty();
+ if (has_left) {
+ s_out << left_lines[i];
+ if (has_right) {
+ if (left_lines[i].length() > left_width) {
+ // Left exceeds column width.
+ // Insert a new line and indent to the right
+ s_out << std::endl;
+ pad(left_width);
+ } else {
+ pad(left_width - left_lines[i].length());
+ }
+ }
+ } else if (has_right) {
+ pad(left_width);
+ }
+ if (has_right) {
+ s_out << " " << right_lines[i];
+ }
+ s_out << std::endl;
+ }
+ }
+}
+
+Result<OptionSet::Unconsumed> OptionSet::Parse(std::ostream& s_err,
+ utils::VectorRef<std::string_view> arguments_raw) {
+ // Build a map of name to option, and set defaults
+ utils::Hashmap<std::string, Option*, 32> options_by_name;
+ for (auto* opt : options.Objects()) {
+ opt->SetDefault();
+ for (auto name : {opt->Name(), opt->Alias(), opt->ShortName()}) {
+ if (!name.empty() && !options_by_name.Add(name, opt)) {
+ s_err << "multiple options with name '" << name << "'" << std::endl;
+ return Failure;
+ }
+ }
+ }
+
+ // Canonicalize arguments by splitting '--foo=x' into '--foo' 'x'.
+ std::deque<std::string_view> arguments;
+ for (auto arg : arguments_raw) {
+ if (HasPrefix(arg, "-")) {
+ if (auto eq_idx = arg.find("="); eq_idx != std::string_view::npos) {
+ arguments.push_back(arg.substr(0, eq_idx));
+ arguments.push_back(arg.substr(eq_idx + 1));
+ continue;
+ }
+ }
+ arguments.push_back(arg);
+ }
+
+ utils::Hashset<Option*, 8> options_parsed;
+
+ Unconsumed unconsumed;
+ while (!arguments.empty()) {
+ auto arg = std::move(arguments.front());
+ arguments.pop_front();
+ auto name = TrimLeft(arg, [](char c) { return c == '-'; });
+ if (arg == name || name.length() == 0) {
+ unconsumed.Push(arg);
+ continue;
+ }
+ if (auto opt = options_by_name.Find(name)) {
+ if (auto err = (*opt)->Parse(arguments); !err.empty()) {
+ s_err << err << std::endl;
+ return Failure;
+ }
+ } else {
+ s_err << "unknown flag: " << arg << std::endl;
+ auto names = options_by_name.Keys();
+ auto alternatives =
+ Transform(names, [&](const std::string& s) { return std::string_view(s); });
+ utils::StringStream ss;
+ utils::SuggestAlternativeOptions opts;
+ opts.prefix = "--";
+ opts.list_possible_values = false;
+ SuggestAlternatives(arg, alternatives.Slice(), ss, opts);
+ s_err << ss.str();
+ return Failure;
+ }
+ }
+
+ return unconsumed;
+}
+
+} // namespace tint::utils::cli
diff --git a/src/tint/utils/cli/cli.h b/src/tint/utils/cli/cli.h
new file mode 100644
index 0000000..1f14fb2
--- /dev/null
+++ b/src/tint/utils/cli/cli.h
@@ -0,0 +1,410 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CLI_CLI_H_
+#define SRC_TINT_UTILS_CLI_CLI_H_
+
+#include <deque>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/macros/compiler.h"
+#include "src/tint/utils/memory/block_allocator.h"
+#include "src/tint/utils/result/result.h"
+#include "src/tint/utils/text/parse_num.h"
+#include "src/tint/utils/text/string.h"
+
+namespace tint::utils::cli {
+
+/// Alias is a fluent-constructor helper for Options
+struct Alias {
+ /// The alias to apply to an Option
+ std::string value;
+
+ /// @param option the option to apply the alias to
+ template <typename T>
+ void Apply(T& option) {
+ option.alias = value;
+ }
+};
+
+/// ShortName is a fluent-constructor helper for Options
+struct ShortName {
+ /// The short-name to apply to an Option
+ std::string value;
+
+ /// @param option the option to apply the short name to
+ template <typename T>
+ void Apply(T& option) {
+ option.short_name = value;
+ }
+};
+
+/// Parameter is a fluent-constructor helper for Options
+struct Parameter {
+ /// The parameter name to apply to an Option
+ std::string value;
+
+ /// @param option the option to apply the parameter name to
+ template <typename T>
+ void Apply(T& option) {
+ option.parameter = value;
+ }
+};
+
+/// Default is a fluent-constructor helper for Options
+template <typename T>
+struct Default {
+ /// The default value to apply to an Option
+ T value;
+
+ /// @param option the option to apply the default value to
+ template <typename O>
+ void Apply(O& option) {
+ option.default_value = value;
+ }
+};
+
+/// Deduction guide for Default
+template <typename T>
+Default(T) -> Default<T>;
+
+/// Option is the base class for all command line options
+class Option {
+ public:
+ /// An alias to std::string, used to hold error messages.
+ using Error = std::string;
+
+ /// Destructor
+ virtual ~Option();
+
+ /// @return the name of the option, without any leading hyphens.
+ /// Example: 'help'
+ virtual std::string Name() const = 0;
+
+ /// @return the alias name of the option, without any leading hyphens. (optional)
+ /// Example: 'flag'
+ virtual std::string Alias() const = 0;
+
+ /// @return the shorter name of the option, without any leading hyphens. (optional)
+ /// Example: 'h'
+ virtual std::string ShortName() const = 0;
+
+ /// @return a string describing the parameter that the option expects.
+ /// Empty represents no expected parameter.
+ virtual std::string Parameter() const = 0;
+
+ /// @return a description of the option.
+ /// Example: 'shows this message'
+ virtual std::string Description() const = 0;
+
+ /// @return the default value of the option, or an empty string if there is no default value.
+ virtual std::string DefaultValue() const = 0;
+
+ /// Sets the option value to the default (called before arguments are parsed)
+ virtual void SetDefault() = 0;
+
+ /// Parses the option's arguments from the list of command line arguments, removing the consumed
+ /// arguments before returning. @p arguments will have already had the option's name consumed
+ /// before calling.
+ /// @param arguments the queue of unprocessed arguments. Parse() may take from the front of @p
+ /// arguments.
+ /// @return empty Error if successfully parsed, otherwise an error string.
+ virtual Error Parse(std::deque<std::string_view>& arguments) = 0;
+
+ protected:
+ /// An empty string, used to represent no-error.
+ static constexpr const char* Success = "";
+
+ /// @param expected the expected value(s) for the option
+ /// @return an Error message for a missing argument
+ Error ErrMissingArgument(std::string expected) const {
+ Error err = "missing value for option '--" + Name() + "'";
+ if (!expected.empty()) {
+ err += "Expected: " + expected;
+ }
+ return err;
+ }
+
+ /// @param got the argument value provided
+ /// @param reason the reason the argument is invalid (optional)
+ /// @return an Error message for an invalid argument
+ Error ErrInvalidArgument(std::string_view got, std::string reason) const {
+ Error err = "invalid value '" + std::string(got) + "' for option '--" + Name() + "'";
+ if (!reason.empty()) {
+ err += "\n" + reason;
+ }
+ return err;
+ }
+};
+
+/// OptionSet is a set of Options, which can parse the command line arguments.
+class OptionSet {
+ public:
+ /// Unconsumed is a list of unconsumed command line arguments
+ using Unconsumed = utils::Vector<std::string_view, 8>;
+
+ /// Constructs and returns a new Option to be owned by the OptionSet
+ /// @tparam T the Option type
+ /// @tparam ARGS the constructor argument types
+ /// @param args the constructor arguments
+ /// @return the constructed Option
+ template <typename T, typename... ARGS>
+ T& Add(ARGS&&... args) {
+ return *options.Create<T>(std::forward<ARGS>(args)...);
+ }
+
+ /// Prints to @p out the description of all the command line options.
+ /// @param out the output stream
+ void ShowHelp(std::ostream& out);
+
+ /// Parses all the options in @p options.
+ /// @param err the error stream
+ /// @param arguments the command line arguments, excluding the initial executable name
+ /// @return a Result holding a list of arguments that were not consumed as options
+ Result<Unconsumed> Parse(std::ostream& err, utils::VectorRef<std::string_view> arguments);
+
+ private:
+ /// The list of options to parse
+ utils::BlockAllocator<Option, 1024> options;
+};
+
+/// ValueOption is an option that accepts a single value
+template <typename T>
+class ValueOption : public Option {
+ static constexpr bool is_bool = std::is_same_v<T, bool>;
+ static constexpr bool is_number =
+ !is_bool && (std::is_integral_v<T> || std::is_floating_point_v<T>);
+ static constexpr bool is_string = std::is_same_v<T, std::string>;
+ static_assert(is_bool || is_number || is_string, "unsupported data type");
+
+ public:
+ /// The name of the option, without any leading hyphens.
+ std::string name;
+ /// The alias name of the option, without any leading hyphens.
+ std::string alias;
+ /// The shorter name of the option, without any leading hyphens.
+ std::string short_name;
+ /// A description of the option.
+ std::string description;
+ /// The default value.
+ std::optional<T> default_value;
+ /// The option value. Populated with Parse().
+ std::optional<T> value;
+ /// A string describing the name of the option's value.
+ std::string parameter = "value";
+
+ /// Constructor
+ ValueOption() = default;
+
+ /// Constructor
+ /// @param option_name the option name
+ /// @param option_description the option description
+ /// @param settings a number of fluent-constructor values that configure the option
+ /// @see ShortName, Parameter, Default
+ template <typename... SETTINGS>
+ ValueOption(std::string option_name, std::string option_description, SETTINGS&&... settings)
+ : name(std::move(option_name)), description(std::move(option_description)) {
+ (settings.Apply(*this), ...);
+ }
+
+ std::string Name() const override { return name; }
+
+ std::string Alias() const override { return alias; }
+
+ std::string ShortName() const override { return short_name; }
+
+ std::string Parameter() const override { return parameter; }
+
+ std::string Description() const override { return description; }
+
+ std::string DefaultValue() const override {
+ return default_value.has_value() ? ToString(*default_value) : "";
+ }
+
+ void SetDefault() override { value = default_value; }
+
+ Error Parse(std::deque<std::string_view>& arguments) override {
+ TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
+
+ if (arguments.empty()) {
+ if constexpr (is_bool) {
+ // Treat as flag (--blah)
+ value = true;
+ return Success;
+ } else {
+ return ErrMissingArgument(parameter);
+ }
+ }
+
+ auto arg = arguments.front();
+
+ if constexpr (is_number) {
+ auto result = ParseNumber<T>(arg);
+ if (result) {
+ value = result.Get();
+ arguments.pop_front();
+ return Success;
+ }
+ if (result.Failure() == ParseNumberError::kResultOutOfRange) {
+ return ErrInvalidArgument(arg, "value out of range");
+ }
+ return ErrInvalidArgument(arg, "failed to parse value");
+ } else if constexpr (is_string) {
+ value = arg;
+ arguments.pop_front();
+ return Success;
+ } else if constexpr (is_bool) {
+ if (arg == "true") {
+ value = true;
+ arguments.pop_front();
+ return Success;
+ }
+ if (arg == "false") {
+ value = false;
+ arguments.pop_front();
+ return Success;
+ }
+ // Next argument is assumed to be another option, or unconsumed argument.
+ // Treat as flag (--blah)
+ value = true;
+ return Success;
+ }
+
+ TINT_END_DISABLE_WARNING(UNREACHABLE_CODE);
+ }
+};
+
+/// BoolOption is an alias to ValueOption<bool>
+using BoolOption = ValueOption<bool>;
+
+/// StringOption is an alias to ValueOption<std::string>
+using StringOption = ValueOption<std::string>;
+
+/// EnumName is a pair of enum value and name.
+/// @tparam ENUM the enum type
+template <typename ENUM>
+struct EnumName {
+ /// Constructor
+ EnumName() = default;
+
+ /// Constructor
+ /// @param v the enum value
+ /// @param n the name of the enum value
+ EnumName(ENUM v, std::string n) : value(v), name(std::move(n)) {}
+
+ /// the enum value
+ ENUM value;
+ /// the name of the enum value
+ std::string name;
+};
+
+/// Deduction guide for EnumName
+template <typename ENUM>
+EnumName(ENUM, std::string) -> EnumName<ENUM>;
+
+/// EnumOption is an option that accepts an enumerator of values
+template <typename ENUM>
+class EnumOption : public Option {
+ public:
+ /// The name of the option, without any leading hyphens.
+ std::string name;
+ /// The alias name of the option, without any leading hyphens.
+ std::string alias;
+ /// The shorter name of the option, without any leading hyphens.
+ std::string short_name;
+ /// A description of the option.
+ std::string description;
+ /// The enum options as a pair of enum value to name
+ utils::Vector<EnumName<ENUM>, 8> enum_names;
+ /// The default value.
+ std::optional<ENUM> default_value;
+ /// The option value. Populated with Parse().
+ std::optional<ENUM> value;
+
+ /// Constructor
+ EnumOption() = default;
+
+ /// Constructor
+ /// @param option_name the option name
+ /// @param option_description the option description
+ /// @param names The enum options as a pair of enum value to name
+ /// @param settings a number of fluent-constructor values that configure the option
+ /// @see ShortName, Parameter, Default
+ template <typename... SETTINGS>
+ EnumOption(std::string option_name,
+ std::string option_description,
+ utils::VectorRef<EnumName<ENUM>> names,
+ SETTINGS&&... settings)
+ : name(std::move(option_name)),
+ description(std::move(option_description)),
+ enum_names(std::move(names)) {
+ (settings.Apply(*this), ...);
+ }
+
+ std::string Name() const override { return name; }
+
+ std::string ShortName() const override { return short_name; }
+
+ std::string Alias() const override { return alias; }
+
+ std::string Parameter() const override { return PossibleValues("|"); }
+
+ std::string Description() const override { return description; }
+
+ std::string DefaultValue() const override {
+ for (auto& enum_name : enum_names) {
+ if (enum_name.value == default_value) {
+ return enum_name.name;
+ }
+ }
+ return "";
+ }
+
+ void SetDefault() override { value = default_value; }
+
+ Error Parse(std::deque<std::string_view>& arguments) override {
+ if (arguments.empty()) {
+ return ErrMissingArgument("one of: " + PossibleValues(", "));
+ }
+ auto& arg = arguments.front();
+ for (auto& enum_name : enum_names) {
+ if (enum_name.name == arg) {
+ value = enum_name.value;
+ arguments.pop_front();
+ return Success;
+ }
+ }
+ return ErrInvalidArgument(arg, "Must be one of: " + PossibleValues(", "));
+ }
+
+ /// @param delimiter the delimiter between each enum option
+ /// @returns the accepted enum names delimited with @p delimiter
+ std::string PossibleValues(std::string delimiter) const {
+ std::string out;
+ for (auto& enum_name : enum_names) {
+ if (!out.empty()) {
+ out += delimiter;
+ }
+ out += enum_name.name;
+ }
+ return out;
+ }
+};
+
+} // namespace tint::utils::cli
+
+#endif // SRC_TINT_UTILS_CLI_CLI_H_
diff --git a/src/tint/utils/cli/cli_test.cc b/src/tint/utils/cli/cli_test.cc
new file mode 100644
index 0000000..6b8fe4e
--- /dev/null
+++ b/src/tint/utils/cli/cli_test.cc
@@ -0,0 +1,299 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/cli/cli.h"
+
+#include <sstream>
+
+#include "gmock/gmock.h"
+#include "src/tint/utils/text/string.h"
+
+#include "src/tint/utils/containers/transform.h" // Used by ToStringList()
+
+namespace tint::utils::cli {
+namespace {
+
+// Workaround for https://github.com/google/googletest/issues/3081
+// Remove when using C++20
+template <size_t N>
+utils::Vector<std::string, N> ToStringList(const utils::Vector<std::string_view, N>& views) {
+ return Transform(views, [](std::string_view view) { return std::string(view); });
+}
+
+using CLITest = testing::Test;
+
+TEST_F(CLITest, ShowHelp_ValueWithParameter) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("my_option", "sets the awesome value");
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--my_option <value> sets the awesome value
+)");
+}
+
+TEST_F(CLITest, ShowHelp_ValueWithAlias) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("my_option", "sets the awesome value", Alias{"alias"});
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--my_option <value> sets the awesome value
+--alias alias for --my_option
+)");
+}
+TEST_F(CLITest, ShowHelp_ValueWithShortName) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("my_option", "sets the awesome value", ShortName{"a"});
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--my_option <value> sets the awesome value
+ -a short name for --my_option
+)");
+}
+
+TEST_F(CLITest, ShowHelp_MultilineDesc) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("an-option", R"(this is a
+multi-line description
+for an option
+)");
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--an-option <value> this is a
+ multi-line description
+ for an option
+
+)");
+}
+
+TEST_F(CLITest, ShowHelp_LongName) {
+ OptionSet opts;
+ opts.Add<ValueOption<int>>("an-option-with-a-really-really-long-name",
+ "this is an option that has a silly long name", ShortName{"a"});
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--an-option-with-a-really-really-long-name <value>
+ this is an option that has a silly long name
+ -a short name for --an-option-with-a-really-really-long-name
+)");
+}
+
+TEST_F(CLITest, ShowHelp_EnumValue) {
+ enum class E { X, Y, Z };
+
+ OptionSet opts;
+ opts.Add<EnumOption<E>>("my_enum_option", "sets the awesome value",
+ utils::Vector{
+ EnumName(E::X, "X"),
+ EnumName(E::Y, "Y"),
+ EnumName(E::Z, "Z"),
+ });
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--my_enum_option <X|Y|Z> sets the awesome value
+)");
+}
+
+TEST_F(CLITest, ShowHelp_MixedValues) {
+ enum class E { X, Y, Z };
+
+ OptionSet opts;
+
+ opts.Add<ValueOption<int>>("option-a", "an integer");
+ opts.Add<BoolOption>("option-b", "a boolean");
+ opts.Add<EnumOption<E>>("option-c", "sets the awesome value",
+ utils::Vector{
+ EnumName(E::X, "X"),
+ EnumName(E::Y, "Y"),
+ EnumName(E::Z, "Z"),
+ });
+
+ std::stringstream out;
+ out << std::endl;
+ opts.ShowHelp(out);
+ EXPECT_EQ(out.str(), R"(
+--option-a <value> an integer
+--option-b <value> a boolean
+--option-c <X|Y|Z> sets the awesome value
+)");
+}
+
+TEST_F(CLITest, ParseBool_Flag) {
+ OptionSet opts;
+ auto& opt = opts.Add<BoolOption>("my_option", "a boolean value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option unconsumed", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre("unconsumed"));
+ EXPECT_EQ(opt.value, true);
+}
+
+TEST_F(CLITest, ParseBool_ExplicitTrue) {
+ OptionSet opts;
+ auto& opt = opts.Add<BoolOption>("my_option", "a boolean value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option true", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, true);
+}
+
+TEST_F(CLITest, ParseBool_ExplicitFalse) {
+ OptionSet opts;
+ auto& opt = opts.Add<BoolOption>("my_option", "a boolean value", Default{true});
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option false", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, false);
+}
+
+TEST_F(CLITest, ParseInt) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<int>>("my_option", "an integer value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option 42", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 42);
+}
+
+TEST_F(CLITest, ParseUint64) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<uint64_t>>("my_option", "a uint64_t value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option 1000000", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 1000000);
+}
+
+TEST_F(CLITest, ParseFloat) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<float>>("my_option", "a float value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option 1.25", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 1.25f);
+}
+
+TEST_F(CLITest, ParseString) {
+ OptionSet opts;
+ auto& opt = opts.Add<StringOption>("my_option", "a string value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option blah", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, "blah");
+}
+
+TEST_F(CLITest, ParseEnum) {
+ enum class E { X, Y, Z };
+
+ OptionSet opts;
+ auto& opt = opts.Add<EnumOption<E>>("my_option", "sets the awesome value",
+ utils::Vector{
+ EnumName(E::X, "X"),
+ EnumName(E::Y, "Y"),
+ EnumName(E::Z, "Z"),
+ });
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option Y", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, E::Y);
+}
+
+TEST_F(CLITest, ParseShortName) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<int>>("my_option", "an integer value", ShortName{"o"});
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("-o 42", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 42);
+}
+
+TEST_F(CLITest, ParseUnconsumed) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<int32_t>>("my_option", "a int32_t value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("abc --my_option -123 def", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre("abc", "def"));
+ EXPECT_EQ(opt.value, -123);
+}
+
+TEST_F(CLITest, ParseUsingEquals) {
+ OptionSet opts;
+ auto& opt = opts.Add<ValueOption<int>>("my_option", "an int value");
+
+ std::stringstream err;
+ auto res = opts.Parse(err, Split("--my_option=123", " "));
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
+ EXPECT_EQ(opt.value, 123);
+}
+
+TEST_F(CLITest, SetValueToDefault) {
+ OptionSet opts;
+ auto& opt = opts.Add<BoolOption>("my_option", "a boolean value", Default{true});
+
+ std::stringstream err;
+ auto res = opts.Parse(err, utils::Empty);
+ ASSERT_TRUE(res) << err.str();
+ EXPECT_TRUE(err.str().empty());
+ EXPECT_EQ(opt.value, true);
+}
+
+} // namespace
+} // namespace tint::utils::cli
diff --git a/src/tint/utils/command/command.h b/src/tint/utils/command/command.h
new file mode 100644
index 0000000..3406ffc
--- /dev/null
+++ b/src/tint/utils/command/command.h
@@ -0,0 +1,82 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_COMMAND_COMMAND_H_
+#define SRC_TINT_UTILS_COMMAND_COMMAND_H_
+
+#include <string>
+#include <utility>
+
+namespace tint::utils {
+
+/// Command is a helper used by tests for executing a process with a number of
+/// arguments and an optional stdin string, and then collecting and returning
+/// the process's stdout and stderr output as strings.
+class Command {
+ public:
+ /// Output holds the output of the process
+ struct Output {
+ /// stdout from the process
+ std::string out;
+ /// stderr from the process
+ std::string err;
+ /// process error code
+ int error_code = 0;
+ };
+
+ /// Constructor
+ /// @param path path to the executable
+ explicit Command(const std::string& path);
+
+ /// Looks for an executable with the given name in the current working
+ /// directory, and if not found there, in each of the directories in the
+ /// `PATH` environment variable.
+ /// @param executable the executable name
+ /// @returns a Command which will return true for Found() if the executable
+ /// was found.
+ static Command LookPath(const std::string& executable);
+
+ /// @return true if the executable exists at the path provided to the
+ /// constructor
+ bool Found() const;
+
+ /// @returns the path of the command
+ const std::string& Path() const { return path_; }
+
+ /// Invokes the command with the given argument strings, blocking until the
+ /// process has returned.
+ /// @param args the string arguments to pass to the process
+ /// @returns the process output
+ template <typename... ARGS>
+ Output operator()(ARGS... args) const {
+ return Exec({std::forward<ARGS>(args)...});
+ }
+
+ /// Exec invokes the command with the given argument strings, blocking until
+ /// the process has returned.
+ /// @param args the string arguments to pass to the process
+ /// @returns the process output
+ Output Exec(std::initializer_list<std::string> args) const;
+
+ /// @param input the input data to pipe to the process's stdin
+ void SetInput(const std::string& input) { input_ = input; }
+
+ private:
+ std::string const path_;
+ std::string input_;
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_COMMAND_COMMAND_H_
diff --git a/src/tint/utils/command/command_other.cc b/src/tint/utils/command/command_other.cc
new file mode 100644
index 0000000..f500482
--- /dev/null
+++ b/src/tint/utils/command/command_other.cc
@@ -0,0 +1,35 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/command/command.h"
+
+namespace tint::utils {
+
+Command::Command(const std::string&) {}
+
+Command Command::LookPath(const std::string&) {
+ return Command("");
+}
+
+bool Command::Found() const {
+ return false;
+}
+
+Command::Output Command::Exec(std::initializer_list<std::string>) const {
+ Output out;
+ out.err = "Command not supported by this target";
+ return out;
+}
+
+} // namespace tint::utils
diff --git a/src/tint/utils/command/command_posix.cc b/src/tint/utils/command/command_posix.cc
new file mode 100644
index 0000000..a615cc6
--- /dev/null
+++ b/src/tint/utils/command/command_posix.cc
@@ -0,0 +1,264 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/command/command.h"
+
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sstream>
+#include <vector>
+
+namespace tint::utils {
+
+namespace {
+
+/// File is a simple wrapper around a POSIX file descriptor
+class File {
+ constexpr static const int kClosed = -1;
+
+ public:
+ /// Constructor
+ File() : handle_(kClosed) {}
+
+ /// Constructor
+ explicit File(int handle) : handle_(handle) {}
+
+ /// Destructor
+ ~File() { Close(); }
+
+ /// Move assignment operator
+ File& operator=(File&& rhs) {
+ Close();
+ handle_ = rhs.handle_;
+ rhs.handle_ = kClosed;
+ return *this;
+ }
+
+ /// Closes the file (if it wasn't already closed)
+ void Close() {
+ if (handle_ != kClosed) {
+ close(handle_);
+ }
+ handle_ = kClosed;
+ }
+
+ /// @returns the file handle
+ operator int() { return handle_; }
+
+ /// @returns true if the file is not closed
+ operator bool() { return handle_ != kClosed; }
+
+ private:
+ File(const File&) = delete;
+ File& operator=(const File&) = delete;
+
+ int handle_ = kClosed;
+};
+
+/// Pipe is a simple wrapper around a POSIX pipe() function
+class Pipe {
+ public:
+ /// Constructs the pipe
+ Pipe() {
+ int pipes[2] = {};
+ if (pipe(pipes) == 0) {
+ read = File(pipes[0]);
+ write = File(pipes[1]);
+ }
+ }
+
+ /// Closes both the read and write files (if they're not already closed)
+ void Close() {
+ read.Close();
+ write.Close();
+ }
+
+ /// @returns true if the pipe has an open read or write file
+ operator bool() { return read || write; }
+
+ /// The reader end of the pipe
+ File read;
+
+ /// The writer end of the pipe
+ File write;
+};
+
+bool ExecutableExists(const std::string& path) {
+ struct stat s {};
+ if (stat(path.c_str(), &s) != 0) {
+ return false;
+ }
+ return s.st_mode & S_IXUSR;
+}
+
+std::string FindExecutable(const std::string& name) {
+ if (ExecutableExists(name)) {
+ return name;
+ }
+ if (name.find("/") == std::string::npos) {
+ auto* path_env = getenv("PATH");
+ if (!path_env) {
+ return "";
+ }
+ std::istringstream path{path_env};
+ std::string dir;
+ while (getline(path, dir, ':')) {
+ auto test = dir + "/" + name;
+ if (ExecutableExists(test)) {
+ return test;
+ }
+ }
+ }
+ return "";
+}
+
+} // namespace
+
+Command::Command(const std::string& path) : path_(path) {}
+
+Command Command::LookPath(const std::string& executable) {
+ return Command(FindExecutable(executable));
+}
+
+bool Command::Found() const {
+ return ExecutableExists(path_);
+}
+
+Command::Output Command::Exec(std::initializer_list<std::string> arguments) const {
+ if (!Found()) {
+ Output out;
+ out.err = "Executable not found";
+ return out;
+ }
+
+ // Pipes used for piping std[in,out,err] to / from the target process.
+ Pipe stdin_pipe;
+ Pipe stdout_pipe;
+ Pipe stderr_pipe;
+
+ if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
+ Output output;
+ output.err = "Command::Exec(): Failed to create pipes";
+ return output;
+ }
+
+ // execv() and friends replace the current process image with the target
+ // process image. To keep process that called this function going, we need to
+ // fork() this process into a child and parent process.
+ //
+ // The child process is responsible for hooking up the pipes to
+ // std[in,out,err]_pipes to STD[IN,OUT,ERR]_FILENO and then calling execv() to
+ // run the target command.
+ //
+ // The parent process is responsible for feeding any input to the stdin_pipe
+ // and collecting output from the std[out,err]_pipes.
+
+ int child_id = fork();
+ if (child_id < 0) {
+ Output output;
+ output.err = "Command::Exec(): fork() failed";
+ return output;
+ }
+
+ if (child_id > 0) {
+ // fork() - parent
+
+ // Close the stdout and stderr writer pipes.
+ // This is required for getting poll() POLLHUP events.
+ stdout_pipe.write.Close();
+ stderr_pipe.write.Close();
+
+ // Write the input to the child process
+ if (!input_.empty()) {
+ ssize_t n = write(stdin_pipe.write, input_.data(), input_.size());
+ if (n != static_cast<ssize_t>(input_.size())) {
+ Output output;
+ output.err = "Command::Exec(): write() for stdin failed";
+ return output;
+ }
+ }
+ stdin_pipe.write.Close();
+
+ // Accumulate the stdout and stderr output from the child process
+ pollfd poll_fds[2];
+ poll_fds[0].fd = stdout_pipe.read;
+ poll_fds[0].events = POLLIN;
+ poll_fds[1].fd = stderr_pipe.read;
+ poll_fds[1].events = POLLIN;
+
+ Output output;
+ bool stdout_open = true;
+ bool stderr_open = true;
+ while (stdout_open || stderr_open) {
+ if (poll(poll_fds, 2, -1) < 0) {
+ break;
+ }
+ char buf[256];
+ if (poll_fds[0].revents & POLLIN) {
+ auto n = read(stdout_pipe.read, buf, sizeof(buf));
+ if (n > 0) {
+ output.out += std::string(buf, buf + n);
+ }
+ }
+ if (poll_fds[0].revents & POLLHUP) {
+ stdout_open = false;
+ }
+ if (poll_fds[1].revents & POLLIN) {
+ auto n = read(stderr_pipe.read, buf, sizeof(buf));
+ if (n > 0) {
+ output.err += std::string(buf, buf + n);
+ }
+ }
+ if (poll_fds[1].revents & POLLHUP) {
+ stderr_open = false;
+ }
+ }
+
+ // Get the resulting error code
+ waitpid(child_id, &output.error_code, 0);
+
+ return output;
+ } else {
+ // fork() - child
+
+ // Redirect the stdin, stdout, stderr pipes for the execv process
+ if ((dup2(stdin_pipe.read, STDIN_FILENO) == -1) ||
+ (dup2(stdout_pipe.write, STDOUT_FILENO) == -1) ||
+ (dup2(stderr_pipe.write, STDERR_FILENO) == -1)) {
+ fprintf(stderr, "Command::Exec(): Failed to redirect pipes");
+ exit(errno);
+ }
+
+ // Close the pipes, once redirected above, we're now done with them.
+ stdin_pipe.Close();
+ stdout_pipe.Close();
+ stderr_pipe.Close();
+
+ // Run target executable
+ std::vector<const char*> args;
+ args.emplace_back(path_.c_str());
+ for (auto& arg : arguments) {
+ if (!arg.empty()) {
+ args.emplace_back(arg.c_str());
+ }
+ }
+ args.emplace_back(nullptr);
+ auto res = execv(path_.c_str(), const_cast<char* const*>(args.data()));
+ exit(res);
+ }
+}
+
+} // namespace tint::utils
diff --git a/src/tint/utils/command/command_test.cc b/src/tint/utils/command/command_test.cc
new file mode 100644
index 0000000..a362243
--- /dev/null
+++ b/src/tint/utils/command/command_test.cc
@@ -0,0 +1,90 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/command/command.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+#ifdef _WIN32
+
+TEST(CommandTest, Echo) {
+ auto cmd = Command::LookPath("cmd");
+ if (!cmd.Found()) {
+ GTEST_SKIP() << "cmd not found on PATH";
+ }
+
+ auto res = cmd("/C", "echo", "hello world");
+ EXPECT_EQ(res.error_code, 0);
+ EXPECT_EQ(res.out, "hello world\r\n");
+ EXPECT_EQ(res.err, "");
+}
+
+#else
+
+TEST(CommandTest, Echo) {
+ auto cmd = Command::LookPath("echo");
+ if (!cmd.Found()) {
+ GTEST_SKIP() << "echo not found on PATH";
+ }
+
+ auto res = cmd("hello world");
+ EXPECT_EQ(res.error_code, 0);
+ EXPECT_EQ(res.out, "hello world\n");
+ EXPECT_EQ(res.err, "");
+}
+
+TEST(CommandTest, Cat) {
+ auto cmd = Command::LookPath("cat");
+ if (!cmd.Found()) {
+ GTEST_SKIP() << "cat not found on PATH";
+ }
+
+ cmd.SetInput("hello world");
+ auto res = cmd();
+ EXPECT_EQ(res.error_code, 0);
+ EXPECT_EQ(res.out, "hello world");
+ EXPECT_EQ(res.err, "");
+}
+
+TEST(CommandTest, True) {
+ auto cmd = Command::LookPath("true");
+ if (!cmd.Found()) {
+ GTEST_SKIP() << "true not found on PATH";
+ }
+
+ auto res = cmd();
+ EXPECT_EQ(res.error_code, 0);
+ EXPECT_EQ(res.out, "");
+ EXPECT_EQ(res.err, "");
+}
+
+TEST(CommandTest, False) {
+ auto cmd = Command::LookPath("false");
+ if (!cmd.Found()) {
+ GTEST_SKIP() << "false not found on PATH";
+ }
+
+ auto res = cmd();
+ EXPECT_NE(res.error_code, 0);
+ EXPECT_EQ(res.out, "");
+ EXPECT_EQ(res.err, "");
+}
+
+#endif
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/command/command_windows.cc b/src/tint/utils/command/command_windows.cc
new file mode 100644
index 0000000..cbd749f
--- /dev/null
+++ b/src/tint/utils/command/command_windows.cc
@@ -0,0 +1,272 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/command/command.h"
+
+#define WIN32_LEAN_AND_MEAN 1
+#include <Windows.h>
+#include <dbghelp.h>
+#include <string>
+
+#include "src/tint/utils/macros/defer.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::utils {
+
+namespace {
+
+/// Handle is a simple wrapper around the Win32 HANDLE
+class Handle {
+ public:
+ /// Constructor
+ Handle() : handle_(nullptr) {}
+
+ /// Constructor
+ explicit Handle(HANDLE handle) : handle_(handle) {}
+
+ /// Destructor
+ ~Handle() { Close(); }
+
+ /// Move assignment operator
+ Handle& operator=(Handle&& rhs) {
+ Close();
+ handle_ = rhs.handle_;
+ rhs.handle_ = nullptr;
+ return *this;
+ }
+
+ /// Closes the handle (if it wasn't already closed)
+ void Close() {
+ if (handle_) {
+ CloseHandle(handle_);
+ }
+ handle_ = nullptr;
+ }
+
+ /// @returns the handle
+ operator HANDLE() { return handle_; }
+
+ /// @returns true if the handle is not invalid
+ operator bool() { return handle_ != nullptr; }
+
+ private:
+ Handle(const Handle&) = delete;
+ Handle& operator=(const Handle&) = delete;
+
+ HANDLE handle_ = nullptr;
+};
+
+/// Pipe is a simple wrapper around a Win32 CreatePipe() function
+class Pipe {
+ public:
+ /// Constructs the pipe
+ explicit Pipe(bool for_read) {
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa.bInheritHandle = TRUE;
+ sa.lpSecurityDescriptor = nullptr;
+
+ HANDLE hread;
+ HANDLE hwrite;
+ if (CreatePipe(&hread, &hwrite, &sa, 0)) {
+ read = Handle(hread);
+ write = Handle(hwrite);
+ // Ensure the read handle to the pipe is not inherited
+ if (!SetHandleInformation(for_read ? read : write, HANDLE_FLAG_INHERIT, 0)) {
+ read.Close();
+ write.Close();
+ }
+ }
+ }
+
+ /// @returns true if the pipe has an open read or write file
+ operator bool() { return read || write; }
+
+ /// The reader end of the pipe
+ Handle read;
+
+ /// The writer end of the pipe
+ Handle write;
+};
+
+/// Queries whether the file at the given path is an executable or DLL.
+bool ExecutableExists(const std::string& path) {
+ auto file = Handle(CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr));
+ if (!file) {
+ return false;
+ }
+
+ auto map = Handle(CreateFileMappingA(file, nullptr, PAGE_READONLY, 0, 0, nullptr));
+ if (map == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ void* addr_header = MapViewOfFileEx(map, FILE_MAP_READ, 0, 0, 0, nullptr);
+
+ // Dynamically obtain the address of, and call ImageNtHeader. This is done to avoid tint.exe
+ // needing to statically link Dbghelp.lib.
+ static auto* dbg_help = LoadLibraryA("Dbghelp.dll"); // Leaks, but who cares?
+ if (dbg_help) {
+ if (FARPROC proc = GetProcAddress(dbg_help, "ImageNtHeader")) {
+ using ImageNtHeaderPtr = decltype(&ImageNtHeader);
+ auto* image_nt_header = reinterpret_cast<ImageNtHeaderPtr>(proc)(addr_header);
+ return image_nt_header != nullptr;
+ }
+ }
+
+ // Couldn't call ImageNtHeader, assume it is executable
+ return false;
+}
+
+std::string FindExecutable(const std::string& name) {
+ if (ExecutableExists(name)) {
+ return name;
+ }
+ if (ExecutableExists(name + ".exe")) {
+ return name + ".exe";
+ }
+ if (name.find("/") == std::string::npos && name.find("\\") == std::string::npos) {
+ char* path_env = nullptr;
+ size_t path_env_len = 0;
+ if (_dupenv_s(&path_env, &path_env_len, "PATH")) {
+ return "";
+ }
+ std::istringstream path{path_env};
+ free(path_env);
+ std::string dir;
+ while (getline(path, dir, ';')) {
+ auto test = dir + "\\" + name;
+ if (ExecutableExists(test)) {
+ return test;
+ }
+ if (ExecutableExists(test + ".exe")) {
+ return test + ".exe";
+ }
+ }
+ }
+ return "";
+}
+
+} // namespace
+
+Command::Command(const std::string& path) : path_(path) {}
+
+Command Command::LookPath(const std::string& executable) {
+ return Command(FindExecutable(executable));
+}
+
+bool Command::Found() const {
+ return ExecutableExists(path_);
+}
+
+Command::Output Command::Exec(std::initializer_list<std::string> arguments) const {
+ Pipe stdout_pipe(true);
+ Pipe stderr_pipe(true);
+ Pipe stdin_pipe(false);
+ if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
+ Output output;
+ output.err = "Command::Exec(): Failed to create pipes";
+ return output;
+ }
+
+ if (!input_.empty()) {
+ if (!WriteFile(stdin_pipe.write, input_.data(), input_.size(), nullptr, nullptr)) {
+ Output output;
+ output.err = "Command::Exec() Failed to write stdin";
+ return output;
+ }
+ }
+ stdin_pipe.write.Close();
+
+ STARTUPINFOA si{};
+ si.cb = sizeof(si);
+ si.dwFlags |= STARTF_USESTDHANDLES;
+ si.hStdOutput = stdout_pipe.write;
+ si.hStdError = stderr_pipe.write;
+ si.hStdInput = stdin_pipe.read;
+
+ utils::StringStream args;
+ args << path_;
+ for (auto& arg : arguments) {
+ if (!arg.empty()) {
+ args << " " << arg;
+ }
+ }
+
+ PROCESS_INFORMATION pi{};
+ if (!CreateProcessA(nullptr, // No module name (use command line)
+ const_cast<LPSTR>(args.str().c_str()), // Command line
+ nullptr, // Process handle not inheritable
+ nullptr, // Thread handle not inheritable
+ TRUE, // Handles are inherited
+ 0, // No creation flags
+ nullptr, // Use parent's environment block
+ nullptr, // Use parent's starting directory
+ &si, // Pointer to STARTUPINFO structure
+ &pi)) { // Pointer to PROCESS_INFORMATION structure
+ Output out;
+ out.err = "Command::Exec() CreateProcess('" + args.str() + "') failed";
+ out.error_code = 1;
+ return out;
+ }
+
+ stdin_pipe.read.Close();
+ stdout_pipe.write.Close();
+ stderr_pipe.write.Close();
+
+ struct StreamReadThreadArgs {
+ HANDLE stream;
+ std::string output;
+ };
+
+ auto stream_read_thread = [](LPVOID user) -> DWORD {
+ auto* thread_args = reinterpret_cast<StreamReadThreadArgs*>(user);
+ DWORD n = 0;
+ char buf[256];
+ while (ReadFile(thread_args->stream, buf, sizeof(buf), &n, NULL)) {
+ auto s = std::string(buf, buf + n);
+ thread_args->output += std::string(buf, buf + n);
+ }
+ return 0;
+ };
+
+ StreamReadThreadArgs stdout_read_args{stdout_pipe.read, {}};
+ auto* stdout_read_thread =
+ ::CreateThread(nullptr, 0, stream_read_thread, &stdout_read_args, 0, nullptr);
+
+ StreamReadThreadArgs stderr_read_args{stderr_pipe.read, {}};
+ auto* stderr_read_thread =
+ ::CreateThread(nullptr, 0, stream_read_thread, &stderr_read_args, 0, nullptr);
+
+ HANDLE handles[] = {pi.hProcess, stdout_read_thread, stderr_read_thread};
+ constexpr DWORD num_handles = sizeof(handles) / sizeof(handles[0]);
+
+ Output output;
+
+ auto res = WaitForMultipleObjects(num_handles, handles, /* wait_all = */ TRUE, INFINITE);
+ if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + num_handles) {
+ output.out = stdout_read_args.output;
+ output.err = stderr_read_args.output;
+ DWORD exit_code = 0;
+ GetExitCodeProcess(pi.hProcess, &exit_code);
+ output.error_code = static_cast<int>(exit_code);
+ } else {
+ output.err = "Command::Exec() WaitForMultipleObjects() returned " + std::to_string(res);
+ }
+
+ return output;
+}
+
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/bitset.h b/src/tint/utils/containers/bitset.h
new file mode 100644
index 0000000..de8eb58
--- /dev/null
+++ b/src/tint/utils/containers/bitset.h
@@ -0,0 +1,121 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_BITSET_H_
+#define SRC_TINT_UTILS_CONTAINERS_BITSET_H_
+
+#include <stdint.h>
+
+#include "src/tint/utils/containers/vector.h"
+
+namespace tint::utils {
+
+/// Bitset is a dynamically sized, vector of bits, packed into integer words.
+/// Bits can be individually read and written using the index operator.
+///
+/// Bitset will fit at least `N` bits internally before spilling to heap allocations.
+template <size_t N = 0>
+class Bitset {
+ /// The integer word type used to hold the bits
+ using Word = size_t;
+ /// Number of bits per word
+ static constexpr size_t kWordBits = sizeof(Word) * 8;
+
+ /// Number of words required to hold the number of bits
+ static constexpr size_t NumWords(size_t num_bits) {
+ return ((num_bits + kWordBits - 1) / kWordBits);
+ }
+
+ public:
+ /// Constructor
+ Bitset() = default;
+
+ /// Destructor
+ ~Bitset() = default;
+
+ /// Accessor for a single bit
+ struct Bit {
+ /// The word that contains the bit
+ Word& word;
+ /// A word with a single bit set, which masks the targetted bit
+ Word const mask;
+
+ /// Assignment operator
+ /// @param value the new value for the bit
+ /// @returns this Bit so calls can be chained
+ const Bit& operator=(bool value) const {
+ if (value) {
+ word = word | mask;
+ } else {
+ word = word & ~mask;
+ }
+ return *this;
+ }
+
+ /// Conversion operator
+ /// @returns the bit value
+ operator bool() const { return (word & mask) != 0; }
+ };
+
+ /// @param new_len the new size of the bitmap, in bits.
+ void Resize(size_t new_len) {
+ vec_.Resize(NumWords(new_len));
+
+ // Clear any potentially set bits that are in the top part of the word
+ if (size_t high_bit = new_len % kWordBits; high_bit > 0) {
+ vec_.Back() &= (static_cast<Word>(1) << high_bit) - 1;
+ }
+
+ len_ = new_len;
+ }
+
+ /// @return the number of bits in the bitset.
+ size_t Length() const { return len_; }
+
+ /// Index operator
+ /// @param index the index of the bit to access
+ /// @return the accessor for the indexed bit
+ Bit operator[](size_t index) {
+ auto& word = vec_[index / kWordBits];
+ auto mask = static_cast<Word>(1) << (index % kWordBits);
+ return Bit{word, mask};
+ }
+
+ /// Const index operator
+ /// @param index the index of the bit to access
+ /// @return bool value of the indexed bit
+ bool operator[](size_t index) const {
+ const auto& word = vec_[index / kWordBits];
+ auto mask = static_cast<Word>(1) << (index % kWordBits);
+ return word & mask;
+ }
+
+ /// @returns true iff the all bits are unset (0)
+ bool AllBitsZero() const {
+ for (auto word : vec_) {
+ if (word) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ Vector<size_t, NumWords(N)> vec_;
+ size_t len_ = 0;
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_BITSET_H_
diff --git a/src/tint/utils/containers/bitset_test.cc b/src/tint/utils/containers/bitset_test.cc
new file mode 100644
index 0000000..70de877
--- /dev/null
+++ b/src/tint/utils/containers/bitset_test.cc
@@ -0,0 +1,145 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/bitset.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(Bitset, Length) {
+ Bitset<8> bits;
+ EXPECT_EQ(bits.Length(), 0u);
+ bits.Resize(100u);
+ EXPECT_EQ(bits.Length(), 100u);
+}
+
+TEST(Bitset, AllBitsZero) {
+ Bitset<8> bits;
+ EXPECT_TRUE(bits.AllBitsZero());
+
+ bits.Resize(4u);
+ EXPECT_TRUE(bits.AllBitsZero());
+
+ bits.Resize(100u);
+ EXPECT_TRUE(bits.AllBitsZero());
+
+ bits[63] = true;
+ EXPECT_FALSE(bits.AllBitsZero());
+
+ bits.Resize(60);
+ EXPECT_TRUE(bits.AllBitsZero());
+
+ bits.Resize(64);
+ EXPECT_TRUE(bits.AllBitsZero());
+
+ bits[4] = true;
+ EXPECT_FALSE(bits.AllBitsZero());
+
+ bits.Resize(8);
+ EXPECT_FALSE(bits.AllBitsZero());
+}
+
+TEST(Bitset, InitCleared_NoSpill) {
+ Bitset<256> bits;
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ EXPECT_FALSE(bits[i]);
+ }
+}
+
+TEST(Bitset, InitCleared_Spill) {
+ Bitset<64> bits;
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ EXPECT_FALSE(bits[i]);
+ }
+}
+
+TEST(Bitset, ReadWrite_NoSpill) {
+ Bitset<256> bits;
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ bits[i] = (i & 0x2) == 0;
+ }
+ for (size_t i = 0; i < 256; i++) {
+ EXPECT_EQ(bits[i], (i & 0x2) == 0);
+ }
+}
+
+TEST(Bitset, ReadWrite_Spill) {
+ Bitset<64> bits;
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ bits[i] = (i & 0x2) == 0;
+ }
+ for (size_t i = 0; i < 256; i++) {
+ EXPECT_EQ(bits[i], (i & 0x2) == 0);
+ }
+}
+
+TEST(Bitset, ShinkGrowAlignedClears_NoSpill) {
+ Bitset<256> bits;
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ bits[i] = true;
+ }
+ bits.Resize(64);
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ EXPECT_EQ(bits[i], i < 64u);
+ }
+}
+
+TEST(Bitset, ShinkGrowAlignedClears_Spill) {
+ Bitset<64> bits;
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ bits[i] = true;
+ }
+ bits.Resize(64);
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ EXPECT_EQ(bits[i], i < 64u);
+ }
+}
+TEST(Bitset, ShinkGrowMisalignedClears_NoSpill) {
+ Bitset<256> bits;
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ bits[i] = true;
+ }
+ bits.Resize(42);
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ EXPECT_EQ(bits[i], i < 42u);
+ }
+}
+
+TEST(Bitset, ShinkGrowMisalignedClears_Spill) {
+ Bitset<64> bits;
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ bits[i] = true;
+ }
+ bits.Resize(42);
+ bits.Resize(256);
+ for (size_t i = 0; i < 256; i++) {
+ EXPECT_EQ(bits[i], i < 42u);
+ }
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/enum_set.h b/src/tint/utils/containers/enum_set.h
new file mode 100644
index 0000000..e8729629
--- /dev/null
+++ b/src/tint/utils/containers/enum_set.h
@@ -0,0 +1,261 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_ENUM_SET_H_
+#define SRC_TINT_UTILS_CONTAINERS_ENUM_SET_H_
+
+#include <cstdint>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::utils {
+
+/// EnumSet is a set of enum values.
+/// @note As the EnumSet is backed by a single uint64_t value, it can only hold
+/// enum values in the range [0 .. 63].
+template <typename ENUM>
+struct EnumSet {
+ public:
+ /// Enum is the enum type this EnumSet wraps
+ using Enum = ENUM;
+
+ /// Constructor. Initializes the EnumSet with zero.
+ constexpr EnumSet() = default;
+
+ /// Copy constructor.
+ /// @param s the EnumSet to copy
+ constexpr EnumSet(const EnumSet& s) = default;
+
+ /// Constructor. Initializes the EnumSet with the given values.
+ /// @param values the enumerator values to construct the EnumSet with
+ template <typename... VALUES>
+ explicit constexpr EnumSet(VALUES... values) : set(Union(values...)) {}
+
+ /// Copy assignment operator.
+ /// @param set the EnumSet to assign to this set
+ /// @returns this EnumSet so calls can be chained
+ inline EnumSet& operator=(const EnumSet& set) = default;
+
+ /// Copy assignment operator.
+ /// @param e the enum value
+ /// @returns this EnumSet so calls can be chained
+ inline EnumSet& operator=(Enum e) { return *this = EnumSet{e}; }
+
+ /// Adds all the given values to this set
+ /// @param values the values to add
+ /// @returns this EnumSet so calls can be chained
+ template <typename... VALUES>
+ inline EnumSet& Add(VALUES... values) {
+ return Add(EnumSet(std::forward<VALUES>(values)...));
+ }
+
+ /// Removes all the given values from this set
+ /// @param values the values to remove
+ /// @returns this EnumSet so calls can be chained
+ template <typename... VALUES>
+ inline EnumSet& Remove(VALUES... values) {
+ return Remove(EnumSet(std::forward<VALUES>(values)...));
+ }
+
+ /// Adds all of @p s to this set
+ /// @param s the enum value
+ /// @returns this EnumSet so calls can be chained
+ inline EnumSet& Add(EnumSet s) { return (*this = *this + s); }
+
+ /// Removes all of @p s from this set
+ /// @param s the enum value
+ /// @returns this EnumSet so calls can be chained
+ inline EnumSet& Remove(EnumSet s) { return (*this = *this - s); }
+
+ /// Adds or removes @p e to the set
+ /// @param e the enum value
+ /// @param add if true the enum value is added, otherwise removed
+ /// @returns this EnumSet so calls can be chained
+ inline EnumSet& Set(Enum e, bool add = true) { return add ? Add(e) : Remove(e); }
+
+ /// @param e the enum value
+ /// @returns a copy of this EnumSet with @p e added
+ inline EnumSet operator+(Enum e) const {
+ EnumSet out;
+ out.set = set | Bit(e);
+ return out;
+ }
+
+ /// @param e the enum value
+ /// @returns a copy of this EnumSet with @p e removed
+ inline EnumSet operator-(Enum e) const {
+ EnumSet out;
+ out.set = set & ~Bit(e);
+ return out;
+ }
+
+ /// @param s the other set
+ /// @returns the union of this EnumSet with @p s (`this` ∪ @p s)
+ inline EnumSet operator+(EnumSet s) const {
+ EnumSet out;
+ out.set = set | s.set;
+ return out;
+ }
+
+ /// @param s the other set
+ /// @returns the set of entries found in this but not in s (`this` \ @p s)
+ inline EnumSet operator-(EnumSet s) const {
+ EnumSet out;
+ out.set = set & ~s.set;
+ return out;
+ }
+
+ /// @param s the other set
+ /// @returns the intersection of this EnumSet with s (`this` ∩ @p s)
+ inline EnumSet operator&(EnumSet s) const {
+ EnumSet out;
+ out.set = set & s.set;
+ return out;
+ }
+
+ /// @param e the enum value
+ /// @return true if the set contains @p e
+ inline bool Contains(Enum e) const { return (set & Bit(e)) != 0; }
+
+ /// @return true if the set is empty
+ inline bool Empty() const { return set == 0; }
+
+ /// Equality operator
+ /// @param rhs the other EnumSet to compare this to
+ /// @return true if this EnumSet is equal to @p rhs
+ inline bool operator==(EnumSet rhs) const { return set == rhs.set; }
+
+ /// Inequality operator
+ /// @param rhs the other EnumSet to compare this to
+ /// @return true if this EnumSet is not equal to @p rhs
+ inline bool operator!=(EnumSet rhs) const { return set != rhs.set; }
+
+ /// Equality operator
+ /// @param rhs the enum to compare this to
+ /// @return true if this EnumSet only contains @p rhs
+ inline bool operator==(Enum rhs) const { return set == Bit(rhs); }
+
+ /// Inequality operator
+ /// @param rhs the enum to compare this to
+ /// @return false if this EnumSet only contains @p rhs
+ inline bool operator!=(Enum rhs) const { return set != Bit(rhs); }
+
+ /// @return the underlying value for the EnumSet
+ inline uint64_t Value() const { return set; }
+
+ /// Iterator provides read-only, unidirectional iterator over the enums of an
+ /// EnumSet.
+ class Iterator {
+ static constexpr int8_t kEnd = 63;
+
+ Iterator(uint64_t s, int8_t b) : set(s), pos(b) {}
+
+ /// Make the constructor accessible to the EnumSet.
+ friend struct EnumSet;
+
+ public:
+ /// @return the Enum value at this point in the iterator
+ Enum operator*() const { return static_cast<Enum>(pos); }
+
+ /// Increments the iterator
+ /// @returns this iterator
+ Iterator& operator++() {
+ while (pos < kEnd) {
+ pos++;
+ if (set & (static_cast<uint64_t>(1) << static_cast<uint64_t>(pos))) {
+ break;
+ }
+ }
+ return *this;
+ }
+
+ /// Equality operator
+ /// @param rhs the Iterator to compare this to
+ /// @return true if the two iterators are equal
+ bool operator==(const Iterator& rhs) const { return set == rhs.set && pos == rhs.pos; }
+
+ /// Inequality operator
+ /// @param rhs the Iterator to compare this to
+ /// @return true if the two iterators are different
+ bool operator!=(const Iterator& rhs) const { return !(*this == rhs); }
+
+ private:
+ const uint64_t set;
+ int8_t pos;
+ };
+
+ /// @returns an read-only iterator to the beginning of the set
+ Iterator begin() const {
+ auto it = Iterator{set, -1};
+ ++it; // Move to first set bit
+ return it;
+ }
+
+ /// @returns an iterator to the beginning of the set
+ Iterator end() const { return Iterator{set, Iterator::kEnd}; }
+
+ private:
+ static constexpr uint64_t Bit(Enum value) {
+ return static_cast<uint64_t>(1) << static_cast<uint64_t>(value);
+ }
+
+ static constexpr uint64_t Union() { return 0; }
+
+ template <typename FIRST, typename... VALUES>
+ static constexpr uint64_t Union(FIRST first, VALUES... values) {
+ return Bit(first) | Union(values...);
+ }
+
+ uint64_t set = 0;
+};
+
+/// Writes the EnumSet to the stream.
+/// @param out the stream to write to
+/// @param set the EnumSet to write
+/// @returns out so calls can be chained
+template <typename ENUM>
+inline utils::StringStream& operator<<(utils::StringStream& out, EnumSet<ENUM> set) {
+ out << "{";
+ bool first = true;
+ for (auto e : set) {
+ if (!first) {
+ out << ", ";
+ }
+ first = false;
+ out << e;
+ }
+ return out << "}";
+}
+
+} // namespace tint::utils
+
+namespace std {
+
+/// Custom std::hash specialization for tint::utils::EnumSet<T>
+template <typename T>
+class hash<tint::utils::EnumSet<T>> {
+ public:
+ /// @param e the EnumSet to create a hash for
+ /// @return the hash value
+ inline std::size_t operator()(const tint::utils::EnumSet<T>& e) const {
+ return std::hash<uint64_t>()(e.Value());
+ }
+};
+
+} // namespace std
+
+#endif // SRC_TINT_UTILS_CONTAINERS_ENUM_SET_H_
diff --git a/src/tint/utils/containers/enum_set_test.cc b/src/tint/utils/containers/enum_set_test.cc
new file mode 100644
index 0000000..32303c8
--- /dev/null
+++ b/src/tint/utils/containers/enum_set_test.cc
@@ -0,0 +1,254 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/enum_set.h"
+
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::utils {
+namespace {
+
+using ::testing::ElementsAre;
+
+enum class E { A = 0, B = 3, C = 7 };
+
+utils::StringStream& operator<<(utils::StringStream& out, E e) {
+ switch (e) {
+ case E::A:
+ return out << "A";
+ case E::B:
+ return out << "B";
+ case E::C:
+ return out << "C";
+ }
+ return out << "E(" << static_cast<uint32_t>(e) << ")";
+}
+
+TEST(EnumSetTest, ConstructEmpty) {
+ EnumSet<E> set;
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_FALSE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+ EXPECT_TRUE(set.Empty());
+}
+
+TEST(EnumSetTest, ConstructWithSingle) {
+ EnumSet<E> set(E::B);
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_TRUE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+ EXPECT_FALSE(set.Empty());
+}
+
+TEST(EnumSetTest, ConstructWithMultiple) {
+ EnumSet<E> set(E::A, E::C);
+ EXPECT_TRUE(set.Contains(E::A));
+ EXPECT_FALSE(set.Contains(E::B));
+ EXPECT_TRUE(set.Contains(E::C));
+ EXPECT_FALSE(set.Empty());
+}
+
+TEST(EnumSetTest, AssignSet) {
+ EnumSet<E> set;
+ set = EnumSet<E>(E::A, E::C);
+ EXPECT_TRUE(set.Contains(E::A));
+ EXPECT_FALSE(set.Contains(E::B));
+ EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AssignEnum) {
+ EnumSet<E> set(E::A);
+ set = E::B;
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_TRUE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AddEnum) {
+ EnumSet<E> set;
+ set.Add(E::B);
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_TRUE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, RemoveEnum) {
+ EnumSet<E> set(E::A, E::B);
+ set.Remove(E::B);
+ EXPECT_TRUE(set.Contains(E::A));
+ EXPECT_FALSE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AddEnums) {
+ EnumSet<E> set;
+ set.Add(E::B, E::C);
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_TRUE(set.Contains(E::B));
+ EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, RemoveEnums) {
+ EnumSet<E> set(E::A, E::B);
+ set.Remove(E::C, E::B);
+ EXPECT_TRUE(set.Contains(E::A));
+ EXPECT_FALSE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AddEnumSet) {
+ EnumSet<E> set;
+ set.Add(EnumSet<E>{E::B, E::C});
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_TRUE(set.Contains(E::B));
+ EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, RemoveEnumSet) {
+ EnumSet<E> set(E::A, E::B);
+ set.Remove(EnumSet<E>{E::B, E::C});
+ EXPECT_TRUE(set.Contains(E::A));
+ EXPECT_FALSE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, Set) {
+ EnumSet<E> set;
+ set.Set(E::B);
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_TRUE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+
+ set.Set(E::B, false);
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_FALSE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorPlusEnum) {
+ EnumSet<E> set = EnumSet<E>{E::B} + E::C;
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_TRUE(set.Contains(E::B));
+ EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorMinusEnum) {
+ EnumSet<E> set = EnumSet<E>{E::A, E::B} - E::B;
+ EXPECT_TRUE(set.Contains(E::A));
+ EXPECT_FALSE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorPlusSet) {
+ EnumSet<E> set = EnumSet<E>{E::B} + EnumSet<E>{E::B, E::C};
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_TRUE(set.Contains(E::B));
+ EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorMinusSet) {
+ EnumSet<E> set = EnumSet<E>{E::A, E::B} - EnumSet<E>{E::B, E::C};
+ EXPECT_TRUE(set.Contains(E::A));
+ EXPECT_FALSE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorAnd) {
+ EnumSet<E> set = EnumSet<E>{E::A, E::B} & EnumSet<E>{E::B, E::C};
+ EXPECT_FALSE(set.Contains(E::A));
+ EXPECT_TRUE(set.Contains(E::B));
+ EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, EqualitySet) {
+ EXPECT_TRUE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::B));
+ EXPECT_FALSE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::C));
+}
+
+TEST(EnumSetTest, InequalitySet) {
+ EXPECT_FALSE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::B));
+ EXPECT_TRUE(EnumSet<E>(E::A, E::B) != EnumSet<E>(E::A, E::C));
+}
+
+TEST(EnumSetTest, EqualityEnum) {
+ EXPECT_TRUE(EnumSet<E>(E::A) == E::A);
+ EXPECT_FALSE(EnumSet<E>(E::B) == E::A);
+ EXPECT_FALSE(EnumSet<E>(E::B) == E::C);
+ EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::A);
+ EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::B);
+ EXPECT_FALSE(EnumSet<E>(E::A, E::B) == E::C);
+}
+
+TEST(EnumSetTest, InequalityEnum) {
+ EXPECT_FALSE(EnumSet<E>(E::A) != E::A);
+ EXPECT_TRUE(EnumSet<E>(E::B) != E::A);
+ EXPECT_TRUE(EnumSet<E>(E::B) != E::C);
+ EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::A);
+ EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::B);
+ EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::C);
+}
+
+TEST(EnumSetTest, Hash) {
+ auto hash = [&](EnumSet<E> s) { return std::hash<EnumSet<E>>()(s); };
+ EXPECT_EQ(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::B)));
+}
+
+TEST(EnumSetTest, Value) {
+ EXPECT_EQ(EnumSet<E>().Value(), 0u);
+ EXPECT_EQ(EnumSet<E>(E::A).Value(), 1u);
+ EXPECT_EQ(EnumSet<E>(E::B).Value(), 8u);
+ EXPECT_EQ(EnumSet<E>(E::C).Value(), 128u);
+ EXPECT_EQ(EnumSet<E>(E::A, E::C).Value(), 129u);
+}
+
+TEST(EnumSetTest, Iterator) {
+ auto set = EnumSet<E>(E::C, E::A);
+
+ auto it = set.begin();
+ EXPECT_EQ(*it, E::A);
+ EXPECT_NE(it, set.end());
+ ++it;
+ EXPECT_EQ(*it, E::C);
+ EXPECT_NE(it, set.end());
+ ++it;
+ EXPECT_EQ(it, set.end());
+}
+
+TEST(EnumSetTest, IteratorEmpty) {
+ auto set = EnumSet<E>();
+ EXPECT_EQ(set.begin(), set.end());
+}
+
+TEST(EnumSetTest, Loop) {
+ auto set = EnumSet<E>(E::C, E::A);
+
+ std::vector<E> seen;
+ for (auto e : set) {
+ seen.emplace_back(e);
+ }
+
+ EXPECT_THAT(seen, ElementsAre(E::A, E::C));
+}
+
+TEST(EnumSetTest, Ostream) {
+ utils::StringStream ss;
+ ss << EnumSet<E>(E::A, E::C);
+ EXPECT_EQ(ss.str(), "{A, C}");
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/hashmap.h b/src/tint/utils/containers/hashmap.h
new file mode 100644
index 0000000..daa006e
--- /dev/null
+++ b/src/tint/utils/containers/hashmap.h
@@ -0,0 +1,290 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_HASHMAP_H_
+#define SRC_TINT_UTILS_CONTAINERS_HASHMAP_H_
+
+#include <functional>
+#include <optional>
+#include <utility>
+
+#include "src/tint/utils/containers/hashmap_base.h"
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/debug/debug.h"
+#include "src/tint/utils/math/hash.h"
+
+namespace tint::utils {
+
+/// An unordered map that uses a robin-hood hashing algorithm.
+template <typename KEY,
+ typename VALUE,
+ size_t N,
+ typename HASH = Hasher<KEY>,
+ typename EQUAL = EqualTo<KEY>>
+class Hashmap : public HashmapBase<KEY, VALUE, N, HASH, EQUAL> {
+ using Base = HashmapBase<KEY, VALUE, N, HASH, EQUAL>;
+ using PutMode = typename Base::PutMode;
+
+ template <typename T>
+ using ReferenceKeyType = traits::CharArrayToCharPtr<std::remove_reference_t<T>>;
+
+ public:
+ /// The key type
+ using Key = KEY;
+ /// The value type
+ using Value = VALUE;
+ /// The key-value type for a map entry
+ using Entry = KeyValue<Key, Value>;
+
+ /// Result of Add()
+ using AddResult = typename Base::PutResult;
+
+ /// Reference is returned by Hashmap::Find(), and performs dynamic Hashmap lookups.
+ /// The value returned by the Reference reflects the current state of the Hashmap, and so the
+ /// referenced value may change, or transition between valid or invalid based on the current
+ /// state of the Hashmap.
+ template <bool IS_CONST, typename K>
+ class ReferenceT {
+ /// `const Value` if IS_CONST, or `Value` if !IS_CONST
+ using T = std::conditional_t<IS_CONST, const Value, Value>;
+
+ /// `const Hashmap` if IS_CONST, or `Hashmap` if !IS_CONST
+ using Map = std::conditional_t<IS_CONST, const Hashmap, Hashmap>;
+
+ public:
+ /// @returns true if the reference is valid.
+ operator bool() const { return Get() != nullptr; }
+
+ /// @returns the pointer to the Value, or nullptr if the reference is invalid.
+ operator T*() const { return Get(); }
+
+ /// @returns the pointer to the Value
+ /// @warning if the Hashmap does not contain a value for the reference, then this will
+ /// trigger a TINT_ASSERT, or invalid pointer dereference.
+ T* operator->() const {
+ auto* hashmap_reference_lookup = Get();
+ TINT_ASSERT(Utils, hashmap_reference_lookup != nullptr);
+ return hashmap_reference_lookup;
+ }
+
+ /// @returns the pointer to the Value, or nullptr if the reference is invalid.
+ T* Get() const {
+ auto generation = map_.Generation();
+ if (generation_ != generation) {
+ cached_ = map_.Lookup(key_);
+ generation_ = generation;
+ }
+ return cached_;
+ }
+
+ private:
+ friend Hashmap;
+
+ /// Constructor
+ template <typename K_ARG>
+ ReferenceT(Map& map, K_ARG&& key)
+ : map_(map),
+ key_(std::forward<K_ARG>(key)),
+ cached_(nullptr),
+ generation_(map.Generation() - 1) {}
+
+ /// Constructor
+ template <typename K_ARG>
+ ReferenceT(Map& map, K_ARG&& key, T* value)
+ : map_(map),
+ key_(std::forward<K_ARG>(key)),
+ cached_(value),
+ generation_(map.Generation()) {}
+
+ Map& map_;
+ const K key_;
+ mutable T* cached_ = nullptr;
+ mutable size_t generation_ = 0;
+ };
+
+ /// A mutable reference returned by Find()
+ template <typename K>
+ using Reference = ReferenceT</*IS_CONST*/ false, K>;
+
+ /// An immutable reference returned by Find()
+ template <typename K>
+ using ConstReference = ReferenceT</*IS_CONST*/ true, K>;
+
+ /// Adds a value to the map, if the map does not already contain an entry with the key @p key.
+ /// @param key the entry key.
+ /// @param value the value of the entry to add to the map.
+ /// @returns A AddResult describing the result of the add
+ template <typename K, typename V>
+ AddResult Add(K&& key, V&& value) {
+ return this->template Put<PutMode::kAdd>(std::forward<K>(key), std::forward<V>(value));
+ }
+
+ /// Adds a new entry to the map, replacing any entry that has a key equal to @p key.
+ /// @param key the entry key.
+ /// @param value the value of the entry to add to the map.
+ /// @returns A AddResult describing the result of the replace
+ template <typename K, typename V>
+ AddResult Replace(K&& key, V&& value) {
+ return this->template Put<PutMode::kReplace>(std::forward<K>(key), std::forward<V>(value));
+ }
+
+ /// @param key the key to search for.
+ /// @returns the value of the entry that is equal to `value`, or no value if the entry was not
+ /// found.
+ template <typename K>
+ std::optional<Value> Get(K&& key) const {
+ if (auto [found, index] = this->IndexOf(key); found) {
+ return this->slots_[index].entry->value;
+ }
+ return std::nullopt;
+ }
+
+ /// Searches for an entry with the given key, adding and returning the result of calling
+ /// @p create if the entry was not found.
+ /// @note: Before calling `create`, the map will insert a zero-initialized value for the given
+ /// key, which will be replaced with the value returned by @p create. If @p create adds an entry
+ /// with @p key to this map, it will be replaced.
+ /// @param key the entry's key value to search for.
+ /// @param create the create function to call if the map does not contain the key.
+ /// @returns the value of the entry.
+ template <typename K, typename CREATE>
+ Value& GetOrCreate(K&& key, CREATE&& create) {
+ auto res = Add(std::forward<K>(key), Value{});
+ if (res.action == MapAction::kAdded) {
+ // Store the map generation before calling create()
+ auto generation = this->Generation();
+ // Call create(), which might modify this map.
+ auto value = create();
+ // Was this map mutated?
+ if (this->Generation() == generation) {
+ // Calling create() did not touch the map. No need to lookup again.
+ *res.value = std::move(value);
+ } else {
+ // Calling create() modified the map. Need to insert again.
+ res = Replace(key, std::move(value));
+ }
+ }
+ return *res.value;
+ }
+
+ /// Searches for an entry with the given key value, adding and returning a newly created
+ /// zero-initialized value if the entry was not found.
+ /// @param key the entry's key value to search for.
+ /// @returns the value of the entry.
+ template <typename K>
+ auto GetOrZero(K&& key) {
+ auto res = Add(std::forward<K>(key), Value{});
+ return Reference<ReferenceKeyType<K>>(*this, key, res.value);
+ }
+
+ /// @param key the key to search for.
+ /// @returns a reference to the entry that is equal to the given value.
+ template <typename K>
+ auto Find(K&& key) {
+ return Reference<ReferenceKeyType<K>>(*this, std::forward<K>(key));
+ }
+
+ /// @param key the key to search for.
+ /// @returns a reference to the entry that is equal to the given value.
+ template <typename K>
+ auto Find(K&& key) const {
+ return ConstReference<ReferenceKeyType<K>>(*this, std::forward<K>(key));
+ }
+
+ /// @returns the keys of the map as a vector.
+ /// @note the order of the returned vector is non-deterministic between compilers.
+ template <size_t N2 = N>
+ Vector<Key, N2> Keys() const {
+ Vector<Key, N2> out;
+ out.Reserve(this->Count());
+ for (auto it : *this) {
+ out.Push(it.key);
+ }
+ return out;
+ }
+
+ /// @returns the values of the map as a vector
+ /// @note the order of the returned vector is non-deterministic between compilers.
+ template <size_t N2 = N>
+ Vector<Value, N2> Values() const {
+ Vector<Value, N2> out;
+ out.Reserve(this->Count());
+ for (auto it : *this) {
+ out.Push(it.value);
+ }
+ return out;
+ }
+
+ /// Equality operator
+ /// @param other the other Hashmap to compare this Hashmap to
+ /// @returns true if this Hashmap has the same key and value pairs as @p other
+ template <typename K, typename V, size_t N2>
+ bool operator==(const Hashmap<K, V, N2>& other) const {
+ if (this->Count() != other.Count()) {
+ return false;
+ }
+ for (auto it : *this) {
+ auto other_val = other.Find(it.key);
+ if (!other_val || it.value != *other_val) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /// Inequality operator
+ /// @param other the other Hashmap to compare this Hashmap to
+ /// @returns false if this Hashmap has the same key and value pairs as @p other
+ template <typename K, typename V, size_t N2>
+ bool operator!=(const Hashmap<K, V, N2>& other) const {
+ return !(*this == other);
+ }
+
+ private:
+ template <typename K>
+ Value* Lookup(K&& key) {
+ if (auto [found, index] = this->IndexOf(key); found) {
+ return &this->slots_[index].entry->value;
+ }
+ return nullptr;
+ }
+
+ template <typename K>
+ const Value* Lookup(K&& key) const {
+ if (auto [found, index] = this->IndexOf(key); found) {
+ return &this->slots_[index].entry->value;
+ }
+ return nullptr;
+ }
+};
+
+/// Hasher specialization for Hashmap
+template <typename K, typename V, size_t N, typename HASH, typename EQUAL>
+struct Hasher<Hashmap<K, V, N, HASH, EQUAL>> {
+ /// @param map the Hashmap to hash
+ /// @returns a hash of the map
+ size_t operator()(const Hashmap<K, V, N, HASH, EQUAL>& map) const {
+ auto hash = Hash(map.Count());
+ for (auto it : map) {
+ // Use an XOR to ensure that the non-deterministic ordering of the map still produces
+ // the same hash value for the same entries.
+ hash ^= Hash(it.key) * 31 + Hash(it.value);
+ }
+ return hash;
+ }
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_HASHMAP_H_
diff --git a/src/tint/utils/containers/hashmap_base.h b/src/tint/utils/containers/hashmap_base.h
new file mode 100644
index 0000000..9d20197
--- /dev/null
+++ b/src/tint/utils/containers/hashmap_base.h
@@ -0,0 +1,636 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_HASHMAP_BASE_H_
+#define SRC_TINT_UTILS_CONTAINERS_HASHMAP_BASE_H_
+
+#include <algorithm>
+#include <functional>
+#include <optional>
+#include <tuple>
+#include <utility>
+
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/debug/debug.h"
+#include "src/tint/utils/math/hash.h"
+
+#define TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+
+namespace tint::utils {
+
+/// Action taken by a map mutation
+enum class MapAction {
+ /// A new entry was added to the map
+ kAdded,
+ /// A existing entry in the map was replaced
+ kReplaced,
+ /// No action was taken as the map already contained an entry with the given key
+ kKeptExisting,
+};
+
+/// KeyValue is a key-value pair.
+template <typename KEY, typename VALUE>
+struct KeyValue {
+ /// The key type
+ using Key = KEY;
+ /// The value type
+ using Value = VALUE;
+
+ /// The key
+ Key key;
+
+ /// The value
+ Value value;
+
+ /// Equality operator
+ /// @param other the RHS of the operator
+ /// @returns true if both the key and value of this KeyValue are equal to the key and value
+ /// of @p other
+ template <typename K, typename V>
+ bool operator==(const KeyValue<K, V>& other) const {
+ return key == other.key && value == other.value;
+ }
+
+ /// Inequality operator
+ /// @param other the RHS of the operator
+ /// @returns true if either the key and value of this KeyValue are not equal to the key and
+ /// value of @p other
+ template <typename K, typename V>
+ bool operator!=(const KeyValue<K, V>& other) const {
+ return *this != other;
+ }
+};
+
+/// KeyValueRef is a pair of references to a key and value.
+/// #key is always a const reference.
+/// #value is always a const reference if @tparam VALUE_IS_CONST is true, otherwise a non-const
+/// reference.
+template <typename KEY, typename VALUE, bool VALUE_IS_CONST>
+struct KeyValueRef {
+ /// The reference to key type
+ using KeyRef = const KEY&;
+ /// The reference to value type
+ using ValueRef = std::conditional_t<VALUE_IS_CONST, const VALUE&, VALUE&>;
+
+ /// The reference to the key
+ KeyRef key;
+
+ /// The reference to the value
+ ValueRef value;
+
+ /// @returns a KeyValue<KEY, VALUE> with the referenced key and value
+ operator KeyValue<KEY, VALUE>() const { return {key, value}; }
+};
+
+/// Writes the KeyValue to the stream.
+/// @param out the stream to write to
+/// @param key_value the KeyValue to write
+/// @returns out so calls can be chained
+template <typename KEY, typename VALUE>
+utils::StringStream& operator<<(utils::StringStream& out, const KeyValue<KEY, VALUE>& key_value) {
+ return out << "[" << key_value.key << ": " << key_value.value << "]";
+}
+
+/// A base class for Hashmap and Hashset that uses a robin-hood hashing algorithm.
+/// @see the fantastic tutorial: https://programming.guide/robin-hood-hashing.html
+template <typename KEY,
+ typename VALUE,
+ size_t N,
+ typename HASH = Hasher<KEY>,
+ typename EQUAL = EqualTo<KEY>>
+class HashmapBase {
+ static constexpr bool ValueIsVoid = std::is_same_v<VALUE, void>;
+
+ public:
+ /// The key type
+ using Key = KEY;
+ /// The value type
+ using Value = VALUE;
+ /// The entry type for the map.
+ /// This is:
+ /// - Key when Value is void (used by Hashset)
+ /// - KeyValue<Key, Value> when Value is not void (used by Hashmap)
+ using Entry = std::conditional_t<ValueIsVoid, Key, KeyValue<Key, Value>>;
+
+ /// A reference to an entry in the map.
+ /// This is:
+ /// - const Key& when Value is void (used by Hashset)
+ /// - KeyValueRef<Key, Value> when Value is not void (used by Hashmap)
+ template <bool IS_CONST>
+ using EntryRef = std::conditional_t<
+ ValueIsVoid,
+ const Key&,
+ KeyValueRef<Key, std::conditional_t<ValueIsVoid, bool, Value>, IS_CONST>>;
+
+ /// STL-friendly alias to Entry. Used by gmock.
+ using value_type = Entry;
+
+ private:
+ /// @returns the key from an entry
+ static const Key& KeyOf(const Entry& entry) {
+ if constexpr (ValueIsVoid) {
+ return entry;
+ } else {
+ return entry.key;
+ }
+ }
+
+ /// @returns a pointer to the value from an entry.
+ static Value* ValueOf(Entry& entry) {
+ if constexpr (ValueIsVoid) {
+ return nullptr; // Hashset only has keys
+ } else {
+ return &entry.value;
+ }
+ }
+
+ /// A slot is a single entry in the underlying vector.
+ /// A slot can either be empty or filled with a value. If the slot is empty, #hash and #distance
+ /// will be zero.
+ struct Slot {
+ template <typename K>
+ bool Equals(size_t key_hash, K&& key) const {
+ return key_hash == hash && EQUAL()(std::forward<K>(key), KeyOf(*entry));
+ }
+
+ /// The slot value. If this does not contain a value, then the slot is vacant.
+ std::optional<Entry> entry;
+ /// The precomputed hash of value.
+ size_t hash = 0;
+ size_t distance = 0;
+ };
+
+ /// The target length of the underlying vector length in relation to the number of entries in
+ /// the map, expressed as a percentage. For example a value of `150` would mean there would be
+ /// at least 50% more slots than the number of map entries.
+ static constexpr size_t kRehashFactor = 150;
+
+ /// @returns the target slot vector size to hold `n` map entries.
+ static constexpr size_t NumSlots(size_t count) { return (count * kRehashFactor) / 100; }
+
+ /// The fixed-size slot vector length, based on N and kRehashFactor.
+ static constexpr size_t kNumFixedSlots = NumSlots(N);
+
+ /// The minimum number of slots for the map.
+ static constexpr size_t kMinSlots = std::max<size_t>(kNumFixedSlots, 4);
+
+ public:
+ /// Iterator for entries in the map.
+ /// Iterators are invalidated if the map is modified.
+ template <bool IS_CONST>
+ class IteratorT {
+ public:
+ /// @returns the value pointed to by this iterator
+ EntryRef<IS_CONST> operator->() const {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+ TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+ "iterator invalidated by container modification");
+#endif
+ return *this;
+ }
+
+ /// @returns a reference to the value at the iterator
+ EntryRef<IS_CONST> operator*() const {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+ TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+ "iterator invalidated by container modification");
+#endif
+ auto& ref = current->entry.value();
+ if constexpr (ValueIsVoid) {
+ return ref;
+ } else {
+ return {ref.key, ref.value};
+ }
+ }
+
+ /// Increments the iterator
+ /// @returns this iterator
+ IteratorT& operator++() {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+ TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+ "iterator invalidated by container modification");
+#endif
+ if (current == end) {
+ return *this;
+ }
+ current++;
+ SkipToNextValue();
+ return *this;
+ }
+
+ /// Equality operator
+ /// @param other the other iterator to compare this iterator to
+ /// @returns true if this iterator is equal to other
+ bool operator==(const IteratorT& other) const {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+ TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+ "iterator invalidated by container modification");
+#endif
+ return current == other.current;
+ }
+
+ /// Inequality operator
+ /// @param other the other iterator to compare this iterator to
+ /// @returns true if this iterator is not equal to other
+ bool operator!=(const IteratorT& other) const {
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+ TINT_ASSERT(Utils, map.Generation() == initial_generation &&
+ "iterator invalidated by container modification");
+#endif
+ return current != other.current;
+ }
+
+ private:
+ /// Friend class
+ friend class HashmapBase;
+
+ using SLOT = std::conditional_t<IS_CONST, const Slot, Slot>;
+
+ IteratorT(SLOT* c, SLOT* e, [[maybe_unused]] const HashmapBase& m)
+ : current(c),
+ end(e)
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+ ,
+ map(m),
+ initial_generation(m.Generation())
+#endif
+ {
+ SkipToNextValue();
+ }
+
+ /// Moves the iterator forward, stopping at the next slot that is not empty.
+ void SkipToNextValue() {
+ while (current != end && !current->entry.has_value()) {
+ current++;
+ }
+ }
+
+ SLOT* current; /// The slot the iterator is pointing to
+ SLOT* end; /// One past the last slot in the map
+
+#ifdef TINT_ASSERT_ITERATORS_NOT_INVALIDATED
+ const HashmapBase& map; /// The hashmap that is being iterated over.
+ size_t initial_generation; /// The generation ID when the iterator was created.
+#endif
+ };
+
+ /// An immutable key and mutable value iterator
+ using Iterator = IteratorT</*IS_CONST*/ false>;
+
+ /// An immutable key and value iterator
+ using ConstIterator = IteratorT</*IS_CONST*/ true>;
+
+ /// Constructor
+ HashmapBase() { slots_.Resize(kMinSlots); }
+
+ /// Copy constructor
+ /// @param other the other HashmapBase to copy
+ HashmapBase(const HashmapBase& other) = default;
+
+ /// Move constructor
+ /// @param other the other HashmapBase to move
+ HashmapBase(HashmapBase&& other) = default;
+
+ /// Destructor
+ ~HashmapBase() { Clear(); }
+
+ /// Copy-assignment operator
+ /// @param other the other HashmapBase to copy
+ /// @returns this so calls can be chained
+ HashmapBase& operator=(const HashmapBase& other) = default;
+
+ /// Move-assignment operator
+ /// @param other the other HashmapBase to move
+ /// @returns this so calls can be chained
+ HashmapBase& operator=(HashmapBase&& other) = default;
+
+ /// Removes all entries from the map.
+ void Clear() {
+ slots_.Clear(); // Destructs all entries
+ slots_.Resize(kMinSlots);
+ count_ = 0;
+ generation_++;
+ }
+
+ /// Removes an entry from the map.
+ /// @param key the entry key.
+ /// @returns true if an entry was removed.
+ bool Remove(const Key& key) {
+ const auto [found, start] = IndexOf(key);
+ if (!found) {
+ return false;
+ }
+
+ // Shuffle the entries backwards until we either find a free slot, or a slot that has zero
+ // distance.
+ Slot* prev = nullptr;
+
+ const auto count = slots_.Length();
+ for (size_t distance = 0, index = start; distance < count; distance++) {
+ auto& slot = slots_[index];
+ if (prev) {
+ // note: `distance == 0` also includes empty slots.
+ if (slot.distance == 0) {
+ // Clear the previous slot, and stop shuffling.
+ *prev = {};
+ break;
+ }
+ // Shuffle the slot backwards.
+ prev->entry = std::move(slot.entry);
+ prev->hash = slot.hash;
+ prev->distance = slot.distance - 1;
+ }
+ prev = &slot;
+
+ index = (index == count - 1) ? 0 : index + 1;
+ }
+
+ // Entry was removed.
+ count_--;
+ generation_++;
+
+ return true;
+ }
+
+ /// Checks whether an entry exists in the map
+ /// @param key the key to search for.
+ /// @returns true if the map contains an entry with the given value.
+ bool Contains(const Key& key) const {
+ const auto [found, _] = IndexOf(key);
+ return found;
+ }
+
+ /// Pre-allocates memory so that the map can hold at least `capacity` entries.
+ /// @param capacity the new capacity of the map.
+ void Reserve(size_t capacity) {
+ // Calculate the number of slots required to hold `capacity` entries.
+ const size_t num_slots = std::max(NumSlots(capacity), kMinSlots);
+ if (slots_.Length() >= num_slots) {
+ // Already have enough slots.
+ return;
+ }
+
+ // Move all the values out of the map and into a vector.
+ Vector<Entry, N> entries;
+ entries.Reserve(count_);
+ for (auto& slot : slots_) {
+ if (slot.entry.has_value()) {
+ entries.Push(std::move(slot.entry.value()));
+ }
+ }
+
+ // Clear the map, grow the number of slots.
+ Clear();
+ slots_.Resize(num_slots);
+
+ // As the number of slots has grown, the slot indices will have changed from before, so
+ // re-add all the entries back into the map.
+ for (auto& entry : entries) {
+ if constexpr (ValueIsVoid) {
+ struct NoValue {};
+ Put<PutMode::kAdd>(std::move(entry), NoValue{});
+ } else {
+ Put<PutMode::kAdd>(std::move(entry.key), std::move(entry.value));
+ }
+ }
+ }
+
+ /// @returns the number of entries in the map.
+ size_t Count() const { return count_; }
+
+ /// @returns true if the map contains no entries.
+ bool IsEmpty() const { return count_ == 0; }
+
+ /// @returns a monotonic counter which is incremented whenever the map is mutated.
+ size_t Generation() const { return generation_; }
+
+ /// @returns an immutable iterator to the start of the map.
+ ConstIterator begin() const { return ConstIterator{slots_.begin(), slots_.end(), *this}; }
+
+ /// @returns an immutable iterator to the end of the map.
+ ConstIterator end() const { return ConstIterator{slots_.end(), slots_.end(), *this}; }
+
+ /// @returns an iterator to the start of the map.
+ Iterator begin() { return Iterator{slots_.begin(), slots_.end(), *this}; }
+
+ /// @returns an iterator to the end of the map.
+ Iterator end() { return Iterator{slots_.end(), slots_.end(), *this}; }
+
+ /// A debug function for checking that the map is in good health.
+ /// Asserts if the map is corrupted.
+ void ValidateIntegrity() const {
+ size_t num_alive = 0;
+ for (size_t slot_idx = 0; slot_idx < slots_.Length(); slot_idx++) {
+ const auto& slot = slots_[slot_idx];
+ if (slot.entry.has_value()) {
+ num_alive++;
+ auto const [index, hash] = Hash(KeyOf(*slot.entry));
+ TINT_ASSERT(Utils, hash == slot.hash);
+ TINT_ASSERT(Utils, slot_idx == Wrap(index + slot.distance));
+ }
+ }
+ TINT_ASSERT(Utils, num_alive == count_);
+ }
+
+ protected:
+ /// The behaviour of Put() when an entry already exists with the given key.
+ enum class PutMode {
+ /// Do not replace existing entries with the new value.
+ kAdd,
+ /// Replace existing entries with the new value.
+ kReplace,
+ };
+
+ /// Result of Put()
+ struct PutResult {
+ /// Whether the insert replaced or added a new entry to the map.
+ MapAction action = MapAction::kAdded;
+ /// A pointer to the inserted entry value.
+ Value* value = nullptr;
+
+ /// @returns true if the entry was added to the map, or an existing entry was replaced.
+ operator bool() const { return action != MapAction::kKeptExisting; }
+ };
+
+ /// The common implementation for Add() and Replace()
+ /// @param key the key of the entry to add to the map.
+ /// @param value the value of the entry to add to the map.
+ /// @returns A PutResult describing the result of the insertion
+ template <PutMode MODE, typename K, typename V>
+ PutResult Put(K&& key, V&& value) {
+ // Ensure the map can fit a new entry
+ if (ShouldRehash(count_ + 1)) {
+ Reserve((count_ + 1) * 2);
+ }
+
+ const auto hash = Hash(key);
+
+ auto make_entry = [&] {
+ if constexpr (ValueIsVoid) {
+ return std::forward<K>(key);
+ } else {
+ return Entry{std::forward<K>(key), std::forward<V>(value)};
+ }
+ };
+
+ const auto count = slots_.Length();
+ for (size_t distance = 0, index = hash.scan_start; distance < count; distance++) {
+ auto& slot = slots_[index];
+ if (!slot.entry.has_value()) {
+ // Found an empty slot.
+ // Place value directly into the slot, and we're done.
+ slot.entry.emplace(make_entry());
+ slot.hash = hash.code;
+ slot.distance = distance;
+ count_++;
+ generation_++;
+ return PutResult{MapAction::kAdded, ValueOf(*slot.entry)};
+ }
+
+ // Slot has an entry
+
+ if (slot.Equals(hash.code, key)) {
+ // Slot is equal to value. Replace or preserve?
+ if constexpr (MODE == PutMode::kReplace) {
+ slot.entry = make_entry();
+ generation_++;
+ return PutResult{MapAction::kReplaced, ValueOf(*slot.entry)};
+ } else {
+ return PutResult{MapAction::kKeptExisting, ValueOf(*slot.entry)};
+ }
+ }
+
+ if (slot.distance < distance) {
+ // Existing slot has a closer distance than the value we're attempting to insert.
+ // Steal from the rich!
+ // Move the current slot to a temporary (evicted), and put the value into the slot.
+ Slot evicted{make_entry(), hash.code, distance};
+ std::swap(evicted, slot);
+
+ // Find a new home for the evicted slot.
+ evicted.distance++; // We've already swapped at index.
+ InsertShuffle(Wrap(index + 1), std::move(evicted));
+
+ count_++;
+ generation_++;
+ return PutResult{MapAction::kAdded, ValueOf(*slot.entry)};
+ }
+
+ index = (index == count - 1) ? 0 : index + 1;
+ }
+
+ tint::diag::List diags;
+ TINT_ICE(Utils, diags) << "HashmapBase::Put() looped entire map without finding a slot";
+ return PutResult{};
+ }
+
+ /// HashResult is the return value of Hash()
+ struct HashResult {
+ /// The target (zero-distance) slot index for the key.
+ size_t scan_start;
+ /// The calculated hash code of the key.
+ size_t code;
+ };
+
+ /// @param key the key to hash
+ /// @returns a tuple holding the target slot index for the given value, and the hash of the
+ /// value, respectively.
+ template <typename K>
+ HashResult Hash(K&& key) const {
+ size_t hash = HASH()(std::forward<K>(key));
+ size_t index = Wrap(hash);
+ return {index, hash};
+ }
+
+ /// Looks for the key in the map.
+ /// @param key the key to search for.
+ /// @returns a tuple holding a boolean representing whether the key was found in the map, and
+ /// if found, the index of the slot that holds the key.
+ template <typename K>
+ std::tuple<bool, size_t> IndexOf(K&& key) const {
+ const auto hash = Hash(key);
+ const auto count = slots_.Length();
+ for (size_t distance = 0, index = hash.scan_start; distance < count; distance++) {
+ auto& slot = slots_[index];
+ if (!slot.entry.has_value()) {
+ return {/* found */ false, /* index */ 0};
+ }
+ if (slot.Equals(hash.code, key)) {
+ return {/* found */ true, index};
+ }
+ if (slot.distance < distance) {
+ // If the slot distance is less than the current probe distance, then the slot
+ // must be for entry that has an index that comes after key. In this situation,
+ // we know that the map does not contain the key, as it would have been found
+ // before this slot. The "Lookup" section of
+ // https://programming.guide/robin-hood-hashing.html suggests that the condition
+ // should inverted, but this is wrong.
+ return {/* found */ false, /* index */ 0};
+ }
+ index = (index == count - 1) ? 0 : index + 1;
+ }
+
+ tint::diag::List diags;
+ TINT_ICE(Utils, diags) << "HashmapBase::IndexOf() looped entire map without finding a slot";
+ return {/* found */ false, /* index */ 0};
+ }
+
+ /// Shuffles slots for an insertion that has been placed one slot before `start`.
+ /// @param start the index of the first slot to start shuffling.
+ /// @param evicted the slot content that was evicted for the insertion.
+ void InsertShuffle(size_t start, Slot&& evicted) {
+ const auto count = slots_.Length();
+ for (size_t distance = 0, index = start; distance < count; distance++) {
+ auto& slot = slots_[index];
+
+ if (!slot.entry.has_value()) {
+ // Empty slot found for evicted.
+ slot = std::move(evicted);
+ return; // We're done.
+ }
+
+ if (slot.distance < evicted.distance) {
+ // Occupied slot has shorter distance to evicted.
+ // Swap slot and evicted.
+ std::swap(slot, evicted);
+ }
+
+ // evicted moves further from the target slot...
+ evicted.distance++;
+
+ index = (index == count - 1) ? 0 : index + 1;
+ }
+ }
+
+ /// @param count the number of new entries in the map
+ /// @returns true if the map should grow the slot vector, and rehash the items.
+ bool ShouldRehash(size_t count) const { return NumSlots(count) > slots_.Length(); }
+
+ /// @param index an input value
+ /// @returns the input value modulo the number of slots.
+ size_t Wrap(size_t index) const { return index % slots_.Length(); }
+
+ /// The vector of slots. The vector length is equal to its capacity.
+ Vector<Slot, kNumFixedSlots> slots_;
+
+ /// The number of entries in the map.
+ size_t count_ = 0;
+
+ /// Counter that's incremented with each modification to the map.
+ size_t generation_ = 0;
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_HASHMAP_BASE_H_
diff --git a/src/tint/utils/containers/hashmap_test.cc b/src/tint/utils/containers/hashmap_test.cc
new file mode 100644
index 0000000..52f2970
--- /dev/null
+++ b/src/tint/utils/containers/hashmap_test.cc
@@ -0,0 +1,537 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/hashmap.h"
+
+#include <array>
+#include <random>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+
+#include "gmock/gmock.h"
+
+namespace tint::utils {
+namespace {
+
+constexpr std::array kPrimes{
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
+ 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131,
+ 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223,
+ 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311,
+ 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
+};
+
+TEST(Hashmap, Empty) {
+ Hashmap<std::string, int, 8> map;
+ EXPECT_EQ(map.Count(), 0u);
+}
+
+TEST(Hashmap, AddRemove) {
+ Hashmap<std::string, std::string, 8> map;
+ EXPECT_TRUE(map.Add("hello", "world"));
+ EXPECT_EQ(map.Get("hello"), "world");
+ EXPECT_EQ(map.Count(), 1u);
+ EXPECT_TRUE(map.Contains("hello"));
+ EXPECT_FALSE(map.Contains("world"));
+ EXPECT_FALSE(map.Add("hello", "cat"));
+ EXPECT_EQ(map.Count(), 1u);
+ EXPECT_TRUE(map.Remove("hello"));
+ EXPECT_EQ(map.Count(), 0u);
+ EXPECT_FALSE(map.Contains("hello"));
+ EXPECT_FALSE(map.Contains("world"));
+}
+
+TEST(Hashmap, ReplaceRemove) {
+ Hashmap<std::string, std::string, 8> map;
+ map.Replace("hello", "world");
+ EXPECT_EQ(map.Get("hello"), "world");
+ EXPECT_EQ(map.Count(), 1u);
+ EXPECT_TRUE(map.Contains("hello"));
+ EXPECT_FALSE(map.Contains("world"));
+ map.Replace("hello", "cat");
+ EXPECT_EQ(map.Get("hello"), "cat");
+ EXPECT_EQ(map.Count(), 1u);
+ EXPECT_TRUE(map.Remove("hello"));
+ EXPECT_EQ(map.Count(), 0u);
+ EXPECT_FALSE(map.Contains("hello"));
+ EXPECT_FALSE(map.Contains("world"));
+}
+
+TEST(Hashmap, Generation) {
+ Hashmap<int, std::string, 8> map;
+ EXPECT_EQ(map.Generation(), 0u);
+ map.Add(1, "one");
+ EXPECT_EQ(map.Generation(), 1u);
+ map.Add(1, "uno");
+ EXPECT_EQ(map.Generation(), 1u);
+ map.Replace(1, "une");
+ EXPECT_EQ(map.Generation(), 2u);
+ map.Add(2, "dos");
+ EXPECT_EQ(map.Generation(), 3u);
+ map.Remove(1);
+ EXPECT_EQ(map.Generation(), 4u);
+ map.Clear();
+ EXPECT_EQ(map.Generation(), 5u);
+ map.Find(2);
+ EXPECT_EQ(map.Generation(), 5u);
+ map.Get(2);
+ EXPECT_EQ(map.Generation(), 5u);
+}
+
+TEST(Hashmap, Index) {
+ Hashmap<int, std::string, 4> map;
+ auto zero = map.Find(0);
+ EXPECT_FALSE(zero);
+
+ map.Add(3, "three");
+ auto three = map.Find(3);
+ map.Add(2, "two");
+ auto two = map.Find(2);
+ map.Add(4, "four");
+ auto four = map.Find(4);
+ map.Add(8, "eight");
+ auto eight = map.Find(8);
+
+ EXPECT_FALSE(zero);
+ ASSERT_TRUE(three);
+ ASSERT_TRUE(two);
+ ASSERT_TRUE(four);
+ ASSERT_TRUE(eight);
+
+ EXPECT_EQ(*three, "three");
+ EXPECT_EQ(*two, "two");
+ EXPECT_EQ(*four, "four");
+ EXPECT_EQ(*eight, "eight");
+
+ map.Add(0, "zero"); // Note: Find called before Add() is okay!
+
+ map.Add(5, "five");
+ auto five = map.Find(5);
+ map.Add(6, "six");
+ auto six = map.Find(6);
+ map.Add(1, "one");
+ auto one = map.Find(1);
+ map.Add(7, "seven");
+ auto seven = map.Find(7);
+
+ ASSERT_TRUE(zero);
+ ASSERT_TRUE(three);
+ ASSERT_TRUE(two);
+ ASSERT_TRUE(four);
+ ASSERT_TRUE(eight);
+ ASSERT_TRUE(five);
+ ASSERT_TRUE(six);
+ ASSERT_TRUE(one);
+ ASSERT_TRUE(seven);
+
+ EXPECT_EQ(*zero, "zero");
+ EXPECT_EQ(*three, "three");
+ EXPECT_EQ(*two, "two");
+ EXPECT_EQ(*four, "four");
+ EXPECT_EQ(*eight, "eight");
+ EXPECT_EQ(*five, "five");
+ EXPECT_EQ(*six, "six");
+ EXPECT_EQ(*one, "one");
+ EXPECT_EQ(*seven, "seven");
+
+ map.Remove(2);
+ map.Remove(8);
+ map.Remove(1);
+
+ EXPECT_FALSE(two);
+ EXPECT_FALSE(eight);
+ EXPECT_FALSE(one);
+}
+
+TEST(Hashmap, StringKeys) {
+ Hashmap<std::string, int, 4> map;
+ EXPECT_FALSE(map.Find("zero"));
+ EXPECT_FALSE(map.Find(std::string("zero")));
+ EXPECT_FALSE(map.Find(std::string_view("zero")));
+
+ map.Add("three", 3);
+ auto three_cstr = map.Find("three");
+ auto three_str = map.Find(std::string("three"));
+ auto three_sv = map.Find(std::string_view("three"));
+ map.Add(std::string("two"), 2);
+ auto two_cstr = map.Find("two");
+ auto two_str = map.Find(std::string("two"));
+ auto two_sv = map.Find(std::string_view("two"));
+ map.Add("four", 4);
+ auto four_cstr = map.Find("four");
+ auto four_str = map.Find(std::string("four"));
+ auto four_sv = map.Find(std::string_view("four"));
+ map.Add(std::string("eight"), 8);
+ auto eight_cstr = map.Find("eight");
+ auto eight_str = map.Find(std::string("eight"));
+ auto eight_sv = map.Find(std::string_view("eight"));
+
+ ASSERT_TRUE(three_cstr);
+ ASSERT_TRUE(three_str);
+ ASSERT_TRUE(three_sv);
+ ASSERT_TRUE(two_cstr);
+ ASSERT_TRUE(two_str);
+ ASSERT_TRUE(two_sv);
+ ASSERT_TRUE(four_cstr);
+ ASSERT_TRUE(four_str);
+ ASSERT_TRUE(four_sv);
+ ASSERT_TRUE(eight_cstr);
+ ASSERT_TRUE(eight_str);
+ ASSERT_TRUE(eight_sv);
+
+ EXPECT_EQ(*three_cstr, 3);
+ EXPECT_EQ(*three_str, 3);
+ EXPECT_EQ(*three_sv, 3);
+ EXPECT_EQ(*two_cstr, 2);
+ EXPECT_EQ(*two_str, 2);
+ EXPECT_EQ(*two_sv, 2);
+ EXPECT_EQ(*four_cstr, 4);
+ EXPECT_EQ(*four_str, 4);
+ EXPECT_EQ(*four_sv, 4);
+ EXPECT_EQ(*eight_cstr, 8);
+ EXPECT_EQ(*eight_str, 8);
+ EXPECT_EQ(*eight_sv, 8);
+
+ map.Add("zero", 0); // Note: Find called before Add() is okay!
+ auto zero_cstr = map.Find("zero");
+ auto zero_str = map.Find(std::string("zero"));
+ auto zero_sv = map.Find(std::string_view("zero"));
+
+ map.Add(std::string("five"), 5);
+ auto five_cstr = map.Find("five");
+ auto five_str = map.Find(std::string("five"));
+ auto five_sv = map.Find(std::string_view("five"));
+ map.Add("six", 6);
+ auto six_cstr = map.Find("six");
+ auto six_str = map.Find(std::string("six"));
+ auto six_sv = map.Find(std::string_view("six"));
+ map.Add("one", 1);
+ auto one_cstr = map.Find("one");
+ auto one_str = map.Find(std::string("one"));
+ auto one_sv = map.Find(std::string_view("one"));
+ map.Add(std::string("seven"), 7);
+ auto seven_cstr = map.Find("seven");
+ auto seven_str = map.Find(std::string("seven"));
+ auto seven_sv = map.Find(std::string_view("seven"));
+
+ ASSERT_TRUE(zero_cstr);
+ ASSERT_TRUE(zero_str);
+ ASSERT_TRUE(zero_sv);
+ ASSERT_TRUE(three_cstr);
+ ASSERT_TRUE(three_str);
+ ASSERT_TRUE(three_sv);
+ ASSERT_TRUE(two_cstr);
+ ASSERT_TRUE(two_str);
+ ASSERT_TRUE(two_sv);
+ ASSERT_TRUE(four_cstr);
+ ASSERT_TRUE(four_str);
+ ASSERT_TRUE(four_sv);
+ ASSERT_TRUE(eight_cstr);
+ ASSERT_TRUE(eight_str);
+ ASSERT_TRUE(eight_sv);
+ ASSERT_TRUE(five_cstr);
+ ASSERT_TRUE(five_str);
+ ASSERT_TRUE(five_sv);
+ ASSERT_TRUE(six_cstr);
+ ASSERT_TRUE(six_str);
+ ASSERT_TRUE(six_sv);
+ ASSERT_TRUE(one_cstr);
+ ASSERT_TRUE(one_str);
+ ASSERT_TRUE(one_sv);
+ ASSERT_TRUE(seven_cstr);
+ ASSERT_TRUE(seven_str);
+ ASSERT_TRUE(seven_sv);
+
+ EXPECT_EQ(*zero_cstr, 0);
+ EXPECT_EQ(*zero_str, 0);
+ EXPECT_EQ(*zero_sv, 0);
+ EXPECT_EQ(*three_cstr, 3);
+ EXPECT_EQ(*three_str, 3);
+ EXPECT_EQ(*three_sv, 3);
+ EXPECT_EQ(*two_cstr, 2);
+ EXPECT_EQ(*two_str, 2);
+ EXPECT_EQ(*two_sv, 2);
+ EXPECT_EQ(*four_cstr, 4);
+ EXPECT_EQ(*four_str, 4);
+ EXPECT_EQ(*four_sv, 4);
+ EXPECT_EQ(*eight_cstr, 8);
+ EXPECT_EQ(*eight_str, 8);
+ EXPECT_EQ(*eight_sv, 8);
+ EXPECT_EQ(*five_cstr, 5);
+ EXPECT_EQ(*five_str, 5);
+ EXPECT_EQ(*five_sv, 5);
+ EXPECT_EQ(*six_cstr, 6);
+ EXPECT_EQ(*six_str, 6);
+ EXPECT_EQ(*six_sv, 6);
+ EXPECT_EQ(*one_cstr, 1);
+ EXPECT_EQ(*one_str, 1);
+ EXPECT_EQ(*one_sv, 1);
+ EXPECT_EQ(*seven_cstr, 7);
+ EXPECT_EQ(*seven_str, 7);
+ EXPECT_EQ(*seven_sv, 7);
+}
+
+TEST(Hashmap, Iterator) {
+ using Map = Hashmap<int, std::string, 8>;
+ using Entry = typename Map::Entry;
+ Map map;
+ map.Add(1, "one");
+ map.Add(4, "four");
+ map.Add(3, "three");
+ map.Add(2, "two");
+ EXPECT_THAT(map, testing::UnorderedElementsAre(Entry{1, "one"}, Entry{2, "two"},
+ Entry{3, "three"}, Entry{4, "four"}));
+}
+
+TEST(Hashmap, MutableIterator) {
+ using Map = Hashmap<int, std::string, 8>;
+ using Entry = typename Map::Entry;
+ Map map;
+ map.Add(1, "one");
+ map.Add(4, "four");
+ map.Add(3, "three");
+ map.Add(2, "two");
+ for (auto pair : map) {
+ pair.value += "!";
+ }
+ EXPECT_THAT(map, testing::UnorderedElementsAre(Entry{1, "one!"}, Entry{2, "two!"},
+ Entry{3, "three!"}, Entry{4, "four!"}));
+}
+
+TEST(Hashmap, KeysValues) {
+ using Map = Hashmap<int, std::string, 8>;
+ Map map;
+ map.Add(1, "one");
+ map.Add(4, "four");
+ map.Add(3, "three");
+ map.Add(2, "two");
+ EXPECT_THAT(map.Keys(), testing::UnorderedElementsAre(1, 2, 3, 4));
+ EXPECT_THAT(map.Values(), testing::UnorderedElementsAre("one", "two", "three", "four"));
+}
+
+TEST(Hashmap, AddMany) {
+ Hashmap<int, std::string, 8> map;
+ for (size_t i = 0; i < kPrimes.size(); i++) {
+ int prime = kPrimes[i];
+ ASSERT_TRUE(map.Add(prime, std::to_string(prime))) << "i: " << i;
+ ASSERT_FALSE(map.Add(prime, std::to_string(prime))) << "i: " << i;
+ ASSERT_EQ(map.Count(), i + 1);
+ }
+ ASSERT_EQ(map.Count(), kPrimes.size());
+ for (int prime : kPrimes) {
+ ASSERT_TRUE(map.Contains(prime)) << prime;
+ ASSERT_EQ(map.Get(prime), std::to_string(prime)) << prime;
+ }
+}
+
+TEST(Hashmap, GetOrCreate) {
+ Hashmap<int, std::string, 8> map;
+ std::optional<std::string> value_of_key_0_at_create;
+ EXPECT_EQ(map.GetOrCreate(0,
+ [&] {
+ value_of_key_0_at_create = map.Get(0);
+ return "zero";
+ }),
+ "zero");
+ EXPECT_EQ(map.Count(), 1u);
+ EXPECT_EQ(map.Get(0), "zero");
+ EXPECT_EQ(value_of_key_0_at_create, "");
+
+ bool create_called = false;
+ EXPECT_EQ(map.GetOrCreate(0,
+ [&] {
+ create_called = true;
+ return "oh noes";
+ }),
+ "zero");
+ EXPECT_FALSE(create_called);
+ EXPECT_EQ(map.Count(), 1u);
+ EXPECT_EQ(map.Get(0), "zero");
+
+ EXPECT_EQ(map.GetOrCreate(1, [&] { return "one"; }), "one");
+ EXPECT_EQ(map.Count(), 2u);
+ EXPECT_EQ(map.Get(1), "one");
+}
+
+TEST(Hashmap, GetOrCreate_CreateModifiesMap) {
+ Hashmap<int, std::string, 8> map;
+ EXPECT_EQ(map.GetOrCreate(0,
+ [&] {
+ map.Add(3, "three");
+ map.Add(1, "one");
+ map.Add(2, "two");
+ return "zero";
+ }),
+ "zero");
+ EXPECT_EQ(map.Count(), 4u);
+ EXPECT_EQ(map.Get(0), "zero");
+ EXPECT_EQ(map.Get(1), "one");
+ EXPECT_EQ(map.Get(2), "two");
+ EXPECT_EQ(map.Get(3), "three");
+
+ bool create_called = false;
+ EXPECT_EQ(map.GetOrCreate(0,
+ [&] {
+ create_called = true;
+ return "oh noes";
+ }),
+ "zero");
+ EXPECT_FALSE(create_called);
+ EXPECT_EQ(map.Count(), 4u);
+ EXPECT_EQ(map.Get(0), "zero");
+ EXPECT_EQ(map.Get(1), "one");
+ EXPECT_EQ(map.Get(2), "two");
+ EXPECT_EQ(map.Get(3), "three");
+
+ EXPECT_EQ(map.GetOrCreate(4,
+ [&] {
+ map.Add(6, "six");
+ map.Add(5, "five");
+ map.Add(7, "seven");
+ return "four";
+ }),
+ "four");
+ EXPECT_EQ(map.Count(), 8u);
+ EXPECT_EQ(map.Get(0), "zero");
+ EXPECT_EQ(map.Get(1), "one");
+ EXPECT_EQ(map.Get(2), "two");
+ EXPECT_EQ(map.Get(3), "three");
+ EXPECT_EQ(map.Get(4), "four");
+ EXPECT_EQ(map.Get(5), "five");
+ EXPECT_EQ(map.Get(6), "six");
+ EXPECT_EQ(map.Get(7), "seven");
+}
+
+TEST(Hashmap, GetOrCreate_CreateAddsSameKeyedValue) {
+ Hashmap<int, std::string, 8> map;
+ EXPECT_EQ(map.GetOrCreate(42,
+ [&] {
+ map.Add(42, "should-be-replaced");
+ return "expected-value";
+ }),
+ "expected-value");
+ EXPECT_EQ(map.Count(), 1u);
+ EXPECT_EQ(map.Get(42), "expected-value");
+}
+
+TEST(Hashmap, Soak) {
+ std::mt19937 rnd;
+ std::unordered_map<std::string, std::string> reference;
+ Hashmap<std::string, std::string, 8> map;
+ for (size_t i = 0; i < 1000000; i++) {
+ std::string key = std::to_string(rnd() & 64);
+ std::string value = "V" + key;
+ switch (rnd() % 7) {
+ case 0: { // Add
+ auto expected = reference.emplace(key, value).second;
+ EXPECT_EQ(map.Add(key, value), expected) << "i:" << i;
+ EXPECT_EQ(map.Get(key), value) << "i:" << i;
+ EXPECT_TRUE(map.Contains(key)) << "i:" << i;
+ break;
+ }
+ case 1: { // Replace
+ reference[key] = value;
+ map.Replace(key, value);
+ EXPECT_EQ(map.Get(key), value) << "i:" << i;
+ EXPECT_TRUE(map.Contains(key)) << "i:" << i;
+ break;
+ }
+ case 2: { // Remove
+ auto expected = reference.erase(key) != 0;
+ EXPECT_EQ(map.Remove(key), expected) << "i:" << i;
+ EXPECT_FALSE(map.Get(key).has_value()) << "i:" << i;
+ EXPECT_FALSE(map.Contains(key)) << "i:" << i;
+ break;
+ }
+ case 3: { // Contains
+ auto expected = reference.count(key) != 0;
+ EXPECT_EQ(map.Contains(key), expected) << "i:" << i;
+ break;
+ }
+ case 4: { // Get
+ if (reference.count(key) != 0) {
+ auto expected = reference[key];
+ EXPECT_EQ(map.Get(key), expected) << "i:" << i;
+ } else {
+ EXPECT_FALSE(map.Get(key).has_value()) << "i:" << i;
+ }
+ break;
+ }
+ case 5: { // Copy / Move
+ Hashmap<std::string, std::string, 8> tmp(map);
+ map = std::move(tmp);
+ break;
+ }
+ case 6: { // Clear
+ reference.clear();
+ map.Clear();
+ break;
+ }
+ }
+ }
+}
+
+TEST(Hashmap, EqualitySameSize) {
+ Hashmap<int, std::string, 8> a;
+ Hashmap<int, std::string, 8> b;
+ EXPECT_EQ(a, b);
+ a.Add(1, "one");
+ EXPECT_NE(a, b);
+ b.Add(2, "two");
+ EXPECT_NE(a, b);
+ a.Add(2, "two");
+ EXPECT_NE(a, b);
+ b.Add(1, "one");
+ EXPECT_EQ(a, b);
+}
+
+TEST(Hashmap, EqualityDifferentSize) {
+ Hashmap<int, std::string, 8> a;
+ Hashmap<int, std::string, 4> b;
+ EXPECT_EQ(a, b);
+ a.Add(1, "one");
+ EXPECT_NE(a, b);
+ b.Add(2, "two");
+ EXPECT_NE(a, b);
+ a.Add(2, "two");
+ EXPECT_NE(a, b);
+ b.Add(1, "one");
+ EXPECT_EQ(a, b);
+}
+
+TEST(Hashmap, HashSameSize) {
+ Hashmap<int, std::string, 8> a;
+ Hashmap<int, std::string, 8> b;
+ EXPECT_EQ(Hash(a), Hash(b));
+ a.Add(1, "one");
+ b.Add(2, "two");
+ a.Add(2, "two");
+ b.Add(1, "one");
+ EXPECT_EQ(Hash(a), Hash(b));
+}
+
+TEST(Hashmap, HashDifferentSize) {
+ Hashmap<int, std::string, 8> a;
+ Hashmap<int, std::string, 4> b;
+ EXPECT_EQ(Hash(a), Hash(b));
+ a.Add(1, "one");
+ b.Add(2, "two");
+ a.Add(2, "two");
+ b.Add(1, "one");
+ EXPECT_EQ(Hash(a), Hash(b));
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/hashset.h b/src/tint/utils/containers/hashset.h
new file mode 100644
index 0000000..8ab452f
--- /dev/null
+++ b/src/tint/utils/containers/hashset.h
@@ -0,0 +1,97 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_HASHSET_H_
+#define SRC_TINT_UTILS_CONTAINERS_HASHSET_H_
+
+#include <stddef.h>
+#include <algorithm>
+#include <functional>
+#include <optional>
+#include <tuple>
+#include <utility>
+
+#include "src/tint/utils/containers/hashmap.h"
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/debug/debug.h"
+
+namespace tint::utils {
+
+/// An unordered set that uses a robin-hood hashing algorithm.
+template <typename KEY, size_t N, typename HASH = Hasher<KEY>, typename EQUAL = std::equal_to<KEY>>
+class Hashset : public HashmapBase<KEY, void, N, HASH, EQUAL> {
+ using Base = HashmapBase<KEY, void, N, HASH, EQUAL>;
+ using PutMode = typename Base::PutMode;
+
+ public:
+ using Base::Base;
+
+ /// Constructor with initializer list of items
+ /// @param items the items to place into the set
+ Hashset(std::initializer_list<KEY> items) {
+ this->Reserve(items.size());
+ for (auto item : items) {
+ this->Add(item);
+ }
+ }
+
+ /// Adds a value to the set, if the set does not already contain an entry equal to `value`.
+ /// @param value the value to add to the set.
+ /// @returns true if the value was added, false if there was an existing value in the set.
+ template <typename V>
+ bool Add(V&& value) {
+ struct NoValue {};
+ return this->template Put<PutMode::kAdd>(std::forward<V>(value), NoValue{});
+ }
+
+ /// @returns the set entries of the map as a vector
+ /// @note the order of the returned vector is non-deterministic between compilers.
+ template <size_t N2 = N>
+ utils::Vector<KEY, N2> Vector() const {
+ utils::Vector<KEY, N2> out;
+ out.Reserve(this->Count());
+ for (auto& value : *this) {
+ out.Push(value);
+ }
+ return out;
+ }
+
+ /// @returns true if the predicate function returns true for any of the elements of the set
+ /// @param pred a function-like with the signature `bool(T)`
+ template <typename PREDICATE>
+ bool Any(PREDICATE&& pred) const {
+ for (const auto& it : *this) {
+ if (pred(it)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// @returns false if the predicate function returns false for any of the elements of the set
+ /// @param pred a function-like with the signature `bool(T)`
+ template <typename PREDICATE>
+ bool All(PREDICATE&& pred) const {
+ for (const auto& it : *this) {
+ if (!pred(it)) {
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_HASHSET_H_
diff --git a/src/tint/utils/containers/hashset_test.cc b/src/tint/utils/containers/hashset_test.cc
new file mode 100644
index 0000000..c9e147e
--- /dev/null
+++ b/src/tint/utils/containers/hashset_test.cc
@@ -0,0 +1,181 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/hashset.h"
+
+#include <array>
+#include <random>
+#include <string>
+#include <tuple>
+#include <unordered_set>
+
+#include "gmock/gmock.h"
+#include "src/tint/utils/containers/predicates.h"
+
+namespace tint::utils {
+namespace {
+
+constexpr std::array kPrimes{
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
+ 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131,
+ 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223,
+ 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311,
+ 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
+};
+
+TEST(Hashset, Empty) {
+ Hashset<std::string, 8> set;
+ EXPECT_EQ(set.Count(), 0u);
+}
+
+TEST(Hashset, InitializerConstructor) {
+ Hashset<int, 8> set{1, 5, 7};
+ EXPECT_EQ(set.Count(), 3u);
+ EXPECT_TRUE(set.Contains(1u));
+ EXPECT_FALSE(set.Contains(3u));
+ EXPECT_TRUE(set.Contains(5u));
+ EXPECT_TRUE(set.Contains(7u));
+ EXPECT_FALSE(set.Contains(9u));
+}
+
+TEST(Hashset, AddRemove) {
+ Hashset<std::string, 8> set;
+ EXPECT_TRUE(set.Add("hello"));
+ EXPECT_EQ(set.Count(), 1u);
+ EXPECT_TRUE(set.Contains("hello"));
+ EXPECT_FALSE(set.Contains("world"));
+ EXPECT_FALSE(set.Add("hello"));
+ EXPECT_EQ(set.Count(), 1u);
+ EXPECT_TRUE(set.Remove("hello"));
+ EXPECT_EQ(set.Count(), 0u);
+ EXPECT_FALSE(set.Contains("hello"));
+ EXPECT_FALSE(set.Contains("world"));
+}
+
+TEST(Hashset, AddMany) {
+ Hashset<int, 8> set;
+ for (size_t i = 0; i < kPrimes.size(); i++) {
+ int prime = kPrimes[i];
+ ASSERT_TRUE(set.Add(prime)) << "i: " << i;
+ ASSERT_FALSE(set.Add(prime)) << "i: " << i;
+ ASSERT_EQ(set.Count(), i + 1);
+ set.ValidateIntegrity();
+ }
+ ASSERT_EQ(set.Count(), kPrimes.size());
+ for (int prime : kPrimes) {
+ ASSERT_TRUE(set.Contains(prime)) << prime;
+ }
+}
+
+TEST(Hashset, Generation) {
+ Hashset<int, 8> set;
+ EXPECT_EQ(set.Generation(), 0u);
+ set.Add(1);
+ EXPECT_EQ(set.Generation(), 1u);
+ set.Add(1);
+ EXPECT_EQ(set.Generation(), 1u);
+ set.Add(2);
+ EXPECT_EQ(set.Generation(), 2u);
+ set.Remove(1);
+ EXPECT_EQ(set.Generation(), 3u);
+ set.Clear();
+ EXPECT_EQ(set.Generation(), 4u);
+}
+
+TEST(Hashset, Iterator) {
+ Hashset<std::string, 8> set;
+ set.Add("one");
+ set.Add("four");
+ set.Add("three");
+ set.Add("two");
+ EXPECT_THAT(set, testing::UnorderedElementsAre("one", "two", "three", "four"));
+}
+
+TEST(Hashset, Vector) {
+ Hashset<std::string, 8> set;
+ set.Add("one");
+ set.Add("four");
+ set.Add("three");
+ set.Add("two");
+ auto vec = set.Vector();
+ EXPECT_THAT(vec, testing::UnorderedElementsAre("one", "two", "three", "four"));
+}
+
+TEST(Hashset, Soak) {
+ std::mt19937 rnd;
+ std::unordered_set<std::string> reference;
+ Hashset<std::string, 8> set;
+ for (size_t i = 0; i < 1000000; i++) {
+ std::string value = std::to_string(rnd() & 0x100);
+ switch (rnd() % 5) {
+ case 0: { // Add
+ auto expected = reference.emplace(value).second;
+ ASSERT_EQ(set.Add(value), expected) << "i: " << i;
+ ASSERT_TRUE(set.Contains(value)) << "i: " << i;
+ break;
+ }
+ case 1: { // Remove
+ auto expected = reference.erase(value) != 0;
+ ASSERT_EQ(set.Remove(value), expected) << "i: " << i;
+ ASSERT_FALSE(set.Contains(value)) << "i: " << i;
+ break;
+ }
+ case 2: { // Contains
+ auto expected = reference.count(value) != 0;
+ ASSERT_EQ(set.Contains(value), expected) << "i: " << i;
+ break;
+ }
+ case 3: { // Copy / Move
+ Hashset<std::string, 8> tmp(set);
+ set = std::move(tmp);
+ break;
+ }
+ case 4: { // Clear
+ reference.clear();
+ set.Clear();
+ break;
+ }
+ }
+ set.ValidateIntegrity();
+ }
+}
+
+TEST(HashsetTest, Any) {
+ Hashset<int, 8> set{1, 7, 5, 9};
+ EXPECT_TRUE(set.Any(Eq(1)));
+ EXPECT_FALSE(set.Any(Eq(2)));
+ EXPECT_FALSE(set.Any(Eq(3)));
+ EXPECT_FALSE(set.Any(Eq(4)));
+ EXPECT_TRUE(set.Any(Eq(5)));
+ EXPECT_FALSE(set.Any(Eq(6)));
+ EXPECT_TRUE(set.Any(Eq(7)));
+ EXPECT_FALSE(set.Any(Eq(8)));
+ EXPECT_TRUE(set.Any(Eq(9)));
+}
+
+TEST(HashsetTest, All) {
+ Hashset<int, 8> set{1, 7, 5, 9};
+ EXPECT_FALSE(set.All(Ne(1)));
+ EXPECT_TRUE(set.All(Ne(2)));
+ EXPECT_TRUE(set.All(Ne(3)));
+ EXPECT_TRUE(set.All(Ne(4)));
+ EXPECT_FALSE(set.All(Ne(5)));
+ EXPECT_TRUE(set.All(Ne(6)));
+ EXPECT_FALSE(set.All(Ne(7)));
+ EXPECT_TRUE(set.All(Ne(8)));
+ EXPECT_FALSE(set.All(Ne(9)));
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/map.h b/src/tint/utils/containers/map.h
new file mode 100644
index 0000000..74a0ea8
--- /dev/null
+++ b/src/tint/utils/containers/map.h
@@ -0,0 +1,56 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_MAP_H_
+#define SRC_TINT_UTILS_CONTAINERS_MAP_H_
+
+#include <unordered_map>
+
+namespace tint::utils {
+
+/// Lookup is a utility function for fetching a value from an unordered map if
+/// it exists, otherwise returning the `if_missing` argument.
+/// @param map the unordered_map
+/// @param key the map key of the item to query
+/// @param if_missing the value to return if the map does not contain the given
+/// key. Defaults to the zero-initializer for the value type.
+/// @return the map item value, or `if_missing` if the map does not contain the
+/// given key
+template <typename K, typename V, typename H, typename C, typename KV = K>
+V Lookup(const std::unordered_map<K, V, H, C>& map, const KV& key, const V& if_missing = {}) {
+ auto it = map.find(key);
+ return it != map.end() ? it->second : if_missing;
+}
+
+/// GetOrCreate is a utility function for lazily adding to an unordered map.
+/// If the map already contains the key `key` then this is returned, otherwise
+/// `create()` is called and the result is added to the map and is returned.
+/// @param map the unordered_map
+/// @param key the map key of the item to query or add
+/// @param create a callable function-like object with the signature `V()`
+/// @return the value of the item with the given key, or the newly created item
+template <typename K, typename V, typename H, typename C, typename CREATE>
+V GetOrCreate(std::unordered_map<K, V, H, C>& map, const K& key, CREATE&& create) {
+ auto it = map.find(key);
+ if (it != map.end()) {
+ return it->second;
+ }
+ V value = create();
+ map.emplace(key, value);
+ return value;
+}
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_MAP_H_
diff --git a/src/tint/utils/containers/map_test.cc b/src/tint/utils/containers/map_test.cc
new file mode 100644
index 0000000..843421a
--- /dev/null
+++ b/src/tint/utils/containers/map_test.cc
@@ -0,0 +1,56 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/map.h"
+
+#include <unordered_map>
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(Lookup, Test) {
+ std::unordered_map<int, int> map;
+ map.emplace(10, 1);
+ EXPECT_EQ(Lookup(map, 10, 0), 1); // exists, with if_missing
+ EXPECT_EQ(Lookup(map, 10), 1); // exists, without if_missing
+ EXPECT_EQ(Lookup(map, 20, 50), 50); // missing, with if_missing
+ EXPECT_EQ(Lookup(map, 20), 0); // missing, without if_missing
+}
+
+TEST(GetOrCreateTest, NewKey) {
+ std::unordered_map<int, int> map;
+ EXPECT_EQ(GetOrCreate(map, 1, [&] { return 2; }), 2);
+ EXPECT_EQ(map.size(), 1u);
+ EXPECT_EQ(map[1], 2);
+}
+
+TEST(GetOrCreateTest, ExistingKey) {
+ std::unordered_map<int, int> map;
+ map[1] = 2;
+ bool called = false;
+ EXPECT_EQ(GetOrCreate(map, 1,
+ [&] {
+ called = true;
+ return -2;
+ }),
+ 2);
+ EXPECT_EQ(called, false);
+ EXPECT_EQ(map.size(), 1u);
+ EXPECT_EQ(map[1], 2);
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/predicates.h b/src/tint/utils/containers/predicates.h
new file mode 100644
index 0000000..a919741
--- /dev/null
+++ b/src/tint/utils/containers/predicates.h
@@ -0,0 +1,78 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_PREDICATES_H_
+#define SRC_TINT_UTILS_CONTAINERS_PREDICATES_H_
+
+namespace tint::utils {
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// equal to
+/// @p value
+template <typename T>
+auto Eq(const T& value) {
+ return [value](const T& v) { return v == value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is not
+/// equal to @p value
+template <typename T>
+auto Ne(const T& value) {
+ return [value](const T& v) { return v != value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// greater than @p value
+template <typename T>
+auto Gt(const T& value) {
+ return [value](const T& v) { return v > value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// less than
+/// @p value
+template <typename T>
+auto Lt(const T& value) {
+ return [value](const T& v) { return v < value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// greater or equal to @p value
+template <typename T>
+auto Ge(const T& value) {
+ return [value](const T& v) { return v >= value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// less than or equal to @p value
+template <typename T>
+auto Le(const T& value) {
+ return [value](const T& v) { return v <= value; };
+}
+
+/// @param ptr the pointer
+/// @return true if the pointer argument is null.
+static inline bool IsNull(const void* ptr) {
+ return ptr == nullptr;
+}
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_PREDICATES_H_
diff --git a/src/tint/utils/containers/predicates_test.cc b/src/tint/utils/containers/predicates_test.cc
new file mode 100644
index 0000000..cf685b2
--- /dev/null
+++ b/src/tint/utils/containers/predicates_test.cc
@@ -0,0 +1,83 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/predicates.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(PredicatesTest, Eq) {
+ auto pred = Eq(3);
+ EXPECT_FALSE(pred(1));
+ EXPECT_FALSE(pred(2));
+ EXPECT_TRUE(pred(3));
+ EXPECT_FALSE(pred(4));
+ EXPECT_FALSE(pred(5));
+}
+
+TEST(PredicatesTest, Ne) {
+ auto pred = Ne(3);
+ EXPECT_TRUE(pred(1));
+ EXPECT_TRUE(pred(2));
+ EXPECT_FALSE(pred(3));
+ EXPECT_TRUE(pred(4));
+ EXPECT_TRUE(pred(5));
+}
+
+TEST(PredicatesTest, Gt) {
+ auto pred = Gt(3);
+ EXPECT_FALSE(pred(1));
+ EXPECT_FALSE(pred(2));
+ EXPECT_FALSE(pred(3));
+ EXPECT_TRUE(pred(4));
+ EXPECT_TRUE(pred(5));
+}
+
+TEST(PredicatesTest, Lt) {
+ auto pred = Lt(3);
+ EXPECT_TRUE(pred(1));
+ EXPECT_TRUE(pred(2));
+ EXPECT_FALSE(pred(3));
+ EXPECT_FALSE(pred(4));
+ EXPECT_FALSE(pred(5));
+}
+
+TEST(PredicatesTest, Ge) {
+ auto pred = Ge(3);
+ EXPECT_FALSE(pred(1));
+ EXPECT_FALSE(pred(2));
+ EXPECT_TRUE(pred(3));
+ EXPECT_TRUE(pred(4));
+ EXPECT_TRUE(pred(5));
+}
+
+TEST(PredicatesTest, Le) {
+ auto pred = Le(3);
+ EXPECT_TRUE(pred(1));
+ EXPECT_TRUE(pred(2));
+ EXPECT_TRUE(pred(3));
+ EXPECT_FALSE(pred(4));
+ EXPECT_FALSE(pred(5));
+}
+
+TEST(PredicatesTest, IsNull) {
+ int i = 1;
+ EXPECT_TRUE(IsNull(nullptr));
+ EXPECT_FALSE(IsNull(&i));
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/reverse.h b/src/tint/utils/containers/reverse.h
new file mode 100644
index 0000000..bb85a9e
--- /dev/null
+++ b/src/tint/utils/containers/reverse.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_REVERSE_H_
+#define SRC_TINT_UTILS_CONTAINERS_REVERSE_H_
+
+#include <iterator>
+
+namespace tint::utils {
+
+namespace detail {
+/// Used by utils::Reverse to hold the underlying iterable.
+/// begin(ReverseIterable<T>) and end(ReverseIterable<T>) are automatically
+/// called for range-for loops, via argument-dependent lookup.
+/// See https://en.cppreference.com/w/cpp/language/range-for
+template <typename T>
+struct ReverseIterable {
+ /// The wrapped iterable object.
+ T& iterable;
+};
+
+template <typename T>
+auto begin(ReverseIterable<T> r_it) {
+ return std::rbegin(r_it.iterable);
+}
+
+template <typename T>
+auto end(ReverseIterable<T> r_it) {
+ return std::rend(r_it.iterable);
+}
+} // namespace detail
+
+/// Reverse returns an iterable wrapper that when used in range-for loops,
+/// performs a reverse iteration over the object `iterable`.
+/// Example:
+/// ```
+/// /* Equivalent to:
+/// * for (auto it = vec.rbegin(); i != vec.rend(); ++it) {
+/// * auto v = *it;
+/// */
+/// for (auto v : utils::Reverse(vec)) {
+/// }
+/// ```
+template <typename T>
+detail::ReverseIterable<T> Reverse(T&& iterable) {
+ return {iterable};
+}
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_REVERSE_H_
diff --git a/src/tint/utils/containers/reverse_test.cc b/src/tint/utils/containers/reverse_test.cc
new file mode 100644
index 0000000..4ca0369
--- /dev/null
+++ b/src/tint/utils/containers/reverse_test.cc
@@ -0,0 +1,34 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/reverse.h"
+
+#include <vector>
+
+#include "gmock/gmock.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(ReverseTest, Vector) {
+ std::vector<int> vec{1, 3, 5, 7, 9};
+ std::vector<int> rev;
+ for (auto v : Reverse(vec)) {
+ rev.emplace_back(v);
+ }
+ ASSERT_THAT(rev, testing::ElementsAre(9, 7, 5, 3, 1));
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/scope_stack.h b/src/tint/utils/containers/scope_stack.h
new file mode 100644
index 0000000..d98f2cd
--- /dev/null
+++ b/src/tint/utils/containers/scope_stack.h
@@ -0,0 +1,84 @@
+// Copyright 2020 The Tint Authors. //
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_SCOPE_STACK_H_
+#define SRC_TINT_UTILS_CONTAINERS_SCOPE_STACK_H_
+
+#include <utility>
+
+#include "src/tint/utils/containers/hashmap.h"
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/text/symbol.h"
+
+namespace tint {
+
+/// Used to store a stack of scope information.
+/// The stack starts with a global scope which can not be popped.
+template <class K, class V>
+class ScopeStack {
+ public:
+ /// Push a new scope on to the stack
+ void Push() { stack_.Push({}); }
+
+ /// Pop the scope off the top of the stack
+ void Pop() {
+ if (stack_.Length() > 1) {
+ stack_.Pop();
+ }
+ }
+
+ /// Assigns the value into the top most scope of the stack.
+ /// @param key the key of the value
+ /// @param val the value
+ /// @returns the old value if there was an existing key at the top of the
+ /// stack, otherwise the zero initializer for type T.
+ V Set(const K& key, V val) {
+ auto& back = stack_.Back();
+ if (auto el = back.Find(key)) {
+ std::swap(val, *el);
+ return val;
+ }
+ back.Add(key, val);
+ return {};
+ }
+
+ /// Retrieves a value from the stack
+ /// @param key the key to look for
+ /// @returns the value, or the zero initializer if the value was not found
+ V Get(const K& key) const {
+ for (auto iter = stack_.rbegin(); iter != stack_.rend(); ++iter) {
+ if (auto val = iter->Find(key)) {
+ return *val;
+ }
+ }
+
+ return V{};
+ }
+
+ /// Return the top scope of the stack.
+ /// @returns the top scope of the stack
+ const utils::Hashmap<K, V, 4>& Top() const { return stack_.Back(); }
+
+ /// Clear the scope stack.
+ void Clear() {
+ stack_.Clear();
+ stack_.Push({});
+ }
+
+ private:
+ utils::Vector<utils::Hashmap<K, V, 4>, 8> stack_ = {{}};
+};
+
+} // namespace tint
+
+#endif // SRC_TINT_UTILS_CONTAINERS_SCOPE_STACK_H_
diff --git a/src/tint/utils/containers/scope_stack_test.cc b/src/tint/utils/containers/scope_stack_test.cc
new file mode 100644
index 0000000..c24b7d3
--- /dev/null
+++ b/src/tint/utils/containers/scope_stack_test.cc
@@ -0,0 +1,91 @@
+// Copyright 2020 The Tint Authors. //
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/scope_stack.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/lang/wgsl/program/program_builder.h"
+
+namespace tint {
+namespace {
+
+class ScopeStackTest : public ProgramBuilder, public testing::Test {};
+
+TEST_F(ScopeStackTest, Get) {
+ ScopeStack<Symbol, uint32_t> s;
+ Symbol a(1, ID(), "1");
+ Symbol b(3, ID(), "3");
+ s.Push();
+ s.Set(a, 5u);
+ s.Set(b, 10u);
+
+ EXPECT_EQ(s.Get(a), 5u);
+ EXPECT_EQ(s.Get(b), 10u);
+
+ s.Push();
+
+ s.Set(a, 15u);
+ EXPECT_EQ(s.Get(a), 15u);
+ EXPECT_EQ(s.Get(b), 10u);
+
+ s.Pop();
+ EXPECT_EQ(s.Get(a), 5u);
+ EXPECT_EQ(s.Get(b), 10u);
+}
+
+TEST_F(ScopeStackTest, Get_MissingSymbol) {
+ ScopeStack<Symbol, uint32_t> s;
+ Symbol sym(1, ID(), "1");
+ EXPECT_EQ(s.Get(sym), 0u);
+}
+
+TEST_F(ScopeStackTest, Set) {
+ ScopeStack<Symbol, uint32_t> s;
+ Symbol a(1, ID(), "1");
+ Symbol b(2, ID(), "2");
+
+ EXPECT_EQ(s.Set(a, 5u), 0u);
+ EXPECT_EQ(s.Get(a), 5u);
+
+ EXPECT_EQ(s.Set(b, 10u), 0u);
+ EXPECT_EQ(s.Get(b), 10u);
+
+ EXPECT_EQ(s.Set(a, 20u), 5u);
+ EXPECT_EQ(s.Get(a), 20u);
+
+ EXPECT_EQ(s.Set(b, 25u), 10u);
+ EXPECT_EQ(s.Get(b), 25u);
+}
+
+TEST_F(ScopeStackTest, Clear) {
+ ScopeStack<Symbol, uint32_t> s;
+ Symbol a(1, ID(), "1");
+ Symbol b(2, ID(), "2");
+
+ EXPECT_EQ(s.Set(a, 5u), 0u);
+ EXPECT_EQ(s.Get(a), 5u);
+
+ s.Push();
+
+ EXPECT_EQ(s.Set(b, 10u), 0u);
+ EXPECT_EQ(s.Get(b), 10u);
+
+ s.Push();
+
+ s.Clear();
+ EXPECT_EQ(s.Get(a), 0u);
+ EXPECT_EQ(s.Get(b), 0u);
+}
+
+} // namespace
+} // namespace tint
diff --git a/src/tint/utils/containers/slice.h b/src/tint/utils/containers/slice.h
new file mode 100644
index 0000000..972d4bb
--- /dev/null
+++ b/src/tint/utils/containers/slice.h
@@ -0,0 +1,258 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_SLICE_H_
+#define SRC_TINT_UTILS_CONTAINERS_SLICE_H_
+
+#include <cstdint>
+#include <iterator>
+
+#include "src/tint/utils/debug/debug.h"
+#include "src/tint/utils/memory/bitcast.h"
+#include "src/tint/utils/rtti/castable.h"
+#include "src/tint/utils/traits/traits.h"
+
+namespace tint::utils {
+
+/// A type used to indicate an empty array.
+struct EmptyType {};
+
+/// An instance of the EmptyType.
+static constexpr EmptyType Empty;
+
+/// Mode enumerator for ReinterpretSlice
+enum class ReinterpretMode {
+ /// Only upcasts of pointers are permitted
+ kSafe,
+ /// Potentially unsafe downcasts of pointers are also permitted
+ kUnsafe,
+};
+
+namespace detail {
+
+template <typename TO, typename FROM>
+static constexpr bool ConstRemoved = std::is_const_v<FROM> && !std::is_const_v<TO>;
+
+/// Private implementation of tint::utils::CanReinterpretSlice.
+/// Specialized for the case of TO equal to FROM, which is the common case, and avoids inspection of
+/// the base classes, which can be troublesome if the slice is of an incomplete type.
+template <ReinterpretMode MODE, typename TO, typename FROM>
+struct CanReinterpretSlice {
+ private:
+ using TO_EL = std::remove_pointer_t<std::decay_t<TO>>;
+ using FROM_EL = std::remove_pointer_t<std::decay_t<FROM>>;
+
+ public:
+ /// @see utils::CanReinterpretSlice
+ static constexpr bool value =
+ // const can only be applied, not removed
+ !ConstRemoved<TO, FROM> &&
+
+ // Both TO and FROM are the same type (ignoring const)
+ (std::is_same_v<std::remove_const_t<TO>, std::remove_const_t<FROM>> ||
+
+ // Both TO and FROM are pointers...
+ ((std::is_pointer_v<TO> && std::is_pointer_v<FROM>)&&
+
+ // const can only be applied to element type, not removed
+ !ConstRemoved<TO_EL, FROM_EL> &&
+
+ // Either:
+ // * Both the pointer elements are of the same type (ignoring const)
+ // * Both the pointer elements are both Castable, and MODE is kUnsafe, or FROM is of,
+ // or
+ // derives from TO
+ (std::is_same_v<std::remove_const_t<FROM_EL>, std::remove_const_t<TO_EL>> ||
+ (IsCastable<FROM_EL, TO_EL> && (MODE == ReinterpretMode::kUnsafe ||
+ utils::traits::IsTypeOrDerived<FROM_EL, TO_EL>)))));
+};
+
+/// Specialization of 'CanReinterpretSlice' for when TO and FROM are equal types.
+template <typename T, ReinterpretMode MODE>
+struct CanReinterpretSlice<MODE, T, T> {
+ /// Always `true` as TO and FROM are the same type.
+ static constexpr bool value = true;
+};
+
+} // namespace detail
+
+/// Evaluates whether a `Slice<FROM>` and be reinterpreted as a `Slice<TO>`.
+/// Slices can be reinterpreted if:
+/// * TO has the same or more 'constness' than FROM.
+/// * And either:
+/// * `FROM` and `TO` are pointers to the same type
+/// * `FROM` and `TO` are pointers to CastableBase (or derived), and the pointee type of `TO` is of
+/// the same type as, or is an ancestor of the pointee type of `FROM`.
+template <ReinterpretMode MODE, typename TO, typename FROM>
+static constexpr bool CanReinterpretSlice =
+ utils::detail::CanReinterpretSlice<MODE, TO, FROM>::value;
+
+/// A slice represents a contigious array of elements of type T.
+template <typename T>
+struct Slice {
+ /// Type of `T`.
+ using value_type = T;
+
+ /// The pointer to the first element in the slice
+ T* data = nullptr;
+
+ /// The total number of elements in the slice
+ size_t len = 0;
+
+ /// The total capacity of the backing store for the slice
+ size_t cap = 0;
+
+ /// Constructor
+ Slice() = default;
+
+ /// Constructor
+ Slice(EmptyType) {} // NOLINT
+
+ /// Copy constructor with covariance / const conversion
+ /// @param other the vector to copy
+ /// @see CanReinterpretSlice for rules about conversion
+ template <typename U,
+ typename = std::enable_if_t<CanReinterpretSlice<ReinterpretMode::kSafe, T, U>>>
+ Slice(const Slice<U>& other) { // NOLINT(runtime/explicit)
+ *this = other.template Reinterpret<T, ReinterpretMode::kSafe>();
+ }
+
+ /// Constructor
+ /// @param d pointer to the first element in the slice
+ /// @param l total number of elements in the slice
+ /// @param c total capacity of the backing store for the slice
+ Slice(T* d, size_t l, size_t c) : data(d), len(l), cap(c) {}
+
+ /// Constructor
+ /// @param elements c-array of elements
+ template <size_t N>
+ Slice(T (&elements)[N]) // NOLINT
+ : data(elements), len(N), cap(N) {}
+
+ /// Reinterprets this slice as `const Slice<TO>&`
+ /// @returns the reinterpreted slice
+ /// @see CanReinterpretSlice
+ template <typename TO, ReinterpretMode MODE = ReinterpretMode::kSafe>
+ const Slice<TO>& Reinterpret() const {
+ static_assert(CanReinterpretSlice<MODE, TO, T>);
+ return *Bitcast<const Slice<TO>*>(this);
+ }
+
+ /// Reinterprets this slice as `Slice<TO>&`
+ /// @returns the reinterpreted slice
+ /// @see CanReinterpretSlice
+ template <typename TO, ReinterpretMode MODE = ReinterpretMode::kSafe>
+ Slice<TO>& Reinterpret() {
+ static_assert(CanReinterpretSlice<MODE, TO, T>);
+ return *Bitcast<Slice<TO>*>(this);
+ }
+
+ /// @return true if the slice length is zero
+ bool IsEmpty() const { return len == 0; }
+
+ /// @return the length of the slice
+ size_t Length() const { return len; }
+
+ /// Create a new slice that represents an offset into this slice
+ /// @param offset the number of elements to offset
+ /// @return the new slice
+ Slice<T> Offset(size_t offset) const {
+ if (offset > len) {
+ offset = len;
+ }
+ return Slice(data + offset, len - offset, cap - offset);
+ }
+
+ /// Create a new slice that represents a truncated version of this slice
+ /// @param length the new length
+ /// @return a new slice that is truncated to `length` elements
+ Slice<T> Truncate(size_t length) const {
+ if (length > len) {
+ length = len;
+ }
+ return Slice(data, length, length);
+ }
+
+ /// Index operator
+ /// @param i the element index. Must be less than `len`.
+ /// @returns a reference to the i'th element.
+ T& operator[](size_t i) {
+ TINT_ASSERT(Utils, i < Length());
+ return data[i];
+ }
+
+ /// Index operator
+ /// @param i the element index. Must be less than `len`.
+ /// @returns a reference to the i'th element.
+ const T& operator[](size_t i) const {
+ TINT_ASSERT(Utils, i < Length());
+ return data[i];
+ }
+
+ /// @returns a reference to the first element in the vector
+ T& Front() {
+ TINT_ASSERT(Utils, !IsEmpty());
+ return data[0];
+ }
+
+ /// @returns a reference to the first element in the vector
+ const T& Front() const {
+ TINT_ASSERT(Utils, !IsEmpty());
+ return data[0];
+ }
+
+ /// @returns a reference to the last element in the vector
+ T& Back() {
+ TINT_ASSERT(Utils, !IsEmpty());
+ return data[len - 1];
+ }
+
+ /// @returns a reference to the last element in the vector
+ const T& Back() const {
+ TINT_ASSERT(Utils, !IsEmpty());
+ return data[len - 1];
+ }
+
+ /// @returns a pointer to the first element in the vector
+ T* begin() { return data; }
+
+ /// @returns a pointer to the first element in the vector
+ const T* begin() const { return data; }
+
+ /// @returns a pointer to one past the last element in the vector
+ T* end() { return data + len; }
+
+ /// @returns a pointer to one past the last element in the vector
+ const T* end() const { return data + len; }
+
+ /// @returns a reverse iterator starting with the last element in the vector
+ auto rbegin() { return std::reverse_iterator<T*>(end()); }
+
+ /// @returns a reverse iterator starting with the last element in the vector
+ auto rbegin() const { return std::reverse_iterator<const T*>(end()); }
+
+ /// @returns the end for a reverse iterator
+ auto rend() { return std::reverse_iterator<T*>(begin()); }
+
+ /// @returns the end for a reverse iterator
+ auto rend() const { return std::reverse_iterator<const T*>(begin()); }
+};
+
+/// Deduction guide for Slice from c-array
+template <typename T, size_t N>
+Slice(T (&elements)[N]) -> Slice<T>;
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_SLICE_H_
diff --git a/src/tint/utils/containers/slice_test.cc b/src/tint/utils/containers/slice_test.cc
new file mode 100644
index 0000000..139dcc5
--- /dev/null
+++ b/src/tint/utils/containers/slice_test.cc
@@ -0,0 +1,185 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/slice.h"
+
+#include "gmock/gmock.h"
+
+namespace tint::utils {
+namespace {
+
+class C0 : public Castable<C0> {};
+class C1 : public Castable<C1, C0> {};
+class C2a : public Castable<C2a, C1> {};
+class C2b : public Castable<C2b, C1> {};
+
+////////////////////////////////////////////////////////////////////////////////
+// Static asserts
+////////////////////////////////////////////////////////////////////////////////
+// Non-pointer
+static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, int, int>, "same type");
+static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, int const, int>, "apply const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, int, int const>, "remove const");
+
+// Non-castable pointers
+static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, int* const, int*>, "apply ptr const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, int*, int* const>, "remove ptr const");
+static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, int const*, int*>, "apply el const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, int*, int const*>, "remove el const");
+
+// Castable
+static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, const C0*, C0*>, "apply const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C0*, const C0*>, "remove const");
+static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, C0*, C1*>, "up cast");
+static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, const C0*, const C1*>, "up cast");
+static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, const C0*, C1*>, "up cast, apply const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C0*, const C1*>,
+ "up cast, remove const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C1*, C0*>, "down cast");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C1*, const C0*>, "down cast");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C1*, C0*>,
+ "down cast, apply const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C1*, const C0*>,
+ "down cast, remove const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C1*, C0*>,
+ "down cast, apply const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C1*, const C0*>,
+ "down cast, remove const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C2a*, C2b*>, "sideways cast");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C2a*, const C2b*>,
+ "sideways cast");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C2a*, C2b*>,
+ "sideways cast, apply const");
+static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C2a*, const C2b*>,
+ "sideways cast, remove const");
+
+TEST(TintSliceTest, Ctor) {
+ Slice<int> slice;
+ EXPECT_EQ(slice.data, nullptr);
+ EXPECT_EQ(slice.len, 0u);
+ EXPECT_EQ(slice.cap, 0u);
+ EXPECT_TRUE(slice.IsEmpty());
+}
+
+TEST(TintSliceTest, CtorCast) {
+ C1* elements[3];
+
+ Slice<C1*> slice_a;
+ slice_a.data = &elements[0];
+ slice_a.len = 3;
+ slice_a.cap = 3;
+
+ Slice<const C0*> slice_b(slice_a);
+ EXPECT_EQ(slice_b.data, Bitcast<const C0**>(&elements[0]));
+ EXPECT_EQ(slice_b.len, 3u);
+ EXPECT_EQ(slice_b.cap, 3u);
+ EXPECT_FALSE(slice_b.IsEmpty());
+}
+
+TEST(TintSliceTest, CtorEmpty) {
+ Slice<int> slice{Empty};
+ EXPECT_EQ(slice.data, nullptr);
+ EXPECT_EQ(slice.len, 0u);
+ EXPECT_EQ(slice.cap, 0u);
+ EXPECT_TRUE(slice.IsEmpty());
+}
+
+TEST(TintSliceTest, CtorCArray) {
+ int elements[] = {1, 2, 3};
+
+ auto slice = Slice{elements};
+ EXPECT_EQ(slice.data, elements);
+ EXPECT_EQ(slice.len, 3u);
+ EXPECT_EQ(slice.cap, 3u);
+ EXPECT_FALSE(slice.IsEmpty());
+}
+
+TEST(TintSliceTest, Index) {
+ int elements[] = {1, 2, 3};
+
+ auto slice = Slice{elements};
+ EXPECT_EQ(slice[0], 1);
+ EXPECT_EQ(slice[1], 2);
+ EXPECT_EQ(slice[2], 3);
+}
+
+TEST(TintSliceTest, Front) {
+ int elements[] = {1, 2, 3};
+ auto slice = Slice{elements};
+ EXPECT_EQ(slice.Front(), 1);
+}
+
+TEST(TintSliceTest, Back) {
+ int elements[] = {1, 2, 3};
+ auto slice = Slice{elements};
+ EXPECT_EQ(slice.Back(), 3);
+}
+
+TEST(TintSliceTest, BeginEnd) {
+ int elements[] = {1, 2, 3};
+ auto slice = Slice{elements};
+ EXPECT_THAT(slice, testing::ElementsAre(1, 2, 3));
+}
+
+TEST(TintSliceTest, ReverseBeginEnd) {
+ int elements[] = {1, 2, 3};
+ auto slice = Slice{elements};
+ size_t i = 0;
+ for (auto it = slice.rbegin(); it != slice.rend(); it++) {
+ EXPECT_EQ(*it, elements[2 - i]);
+ i++;
+ }
+}
+
+TEST(TintSliceTest, Offset) {
+ int elements[] = {1, 2, 3};
+
+ auto slice = Slice{elements};
+ auto offset = slice.Offset(1);
+ EXPECT_EQ(offset.Length(), 2u);
+ EXPECT_EQ(offset[0], 2);
+ EXPECT_EQ(offset[1], 3);
+}
+
+TEST(TintSliceTest, Offset_PastEnd) {
+ int elements[] = {1, 2, 3};
+
+ auto slice = Slice{elements};
+ auto offset = slice.Offset(4);
+ EXPECT_EQ(offset.Length(), 0u);
+}
+
+TEST(TintSliceTest, Truncate) {
+ int elements[] = {1, 2, 3};
+
+ auto slice = Slice{elements};
+ auto truncated = slice.Truncate(2);
+ EXPECT_EQ(truncated.Length(), 2u);
+ EXPECT_EQ(truncated[0], 1);
+ EXPECT_EQ(truncated[1], 2);
+}
+
+TEST(TintSliceTest, Truncate_PastEnd) {
+ int elements[] = {1, 2, 3};
+
+ auto slice = Slice{elements};
+ auto truncated = slice.Truncate(4);
+ EXPECT_EQ(truncated.Length(), 3u);
+ EXPECT_EQ(truncated[0], 1);
+ EXPECT_EQ(truncated[1], 2);
+ EXPECT_EQ(truncated[2], 3);
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/transform.h b/src/tint/utils/containers/transform.h
new file mode 100644
index 0000000..4e3e686
--- /dev/null
+++ b/src/tint/utils/containers/transform.h
@@ -0,0 +1,175 @@
+// Copyright 2021 The Tint Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_TRANSFORM_H_
+#define SRC_TINT_UTILS_CONTAINERS_TRANSFORM_H_
+
+#include <algorithm>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/traits/traits.h"
+
+namespace tint::utils {
+
+/// Transform performs an element-wise transformation of a vector.
+/// @param in the input vector.
+/// @param transform the transformation function with signature: `OUT(IN)`
+/// @returns a new vector with each element of the source vector transformed by `transform`.
+template <typename IN, typename TRANSFORMER>
+auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
+ -> std::vector<decltype(transform(in[0]))> {
+ std::vector<decltype(transform(in[0]))> result(in.size());
+ for (size_t i = 0; i < result.size(); ++i) {
+ result[i] = transform(in[i]);
+ }
+ return result;
+}
+
+/// Transform performs an element-wise transformation of a vector.
+/// @param in the input vector.
+/// @param transform the transformation function with signature: `OUT(IN, size_t)`
+/// @returns a new vector with each element of the source vector transformed by `transform`.
+template <typename IN, typename TRANSFORMER>
+auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
+ -> std::vector<decltype(transform(in[0], 1u))> {
+ std::vector<decltype(transform(in[0], 1u))> result(in.size());
+ for (size_t i = 0; i < result.size(); ++i) {
+ result[i] = transform(in[i], i);
+ }
+ return result;
+}
+
+/// Transform performs an element-wise transformation of a vector.
+/// @param in the input vector.
+/// @param transform the transformation function with signature: `OUT(IN)`
+/// @returns a new vector with each element of the source vector transformed by `transform`.
+template <typename IN, size_t N, typename TRANSFORMER>
+auto Transform(const Vector<IN, N>& in, TRANSFORMER&& transform)
+ -> Vector<decltype(transform(in[0])), N> {
+ const auto count = in.Length();
+ Vector<decltype(transform(in[0])), N> result;
+ result.Reserve(count);
+ for (size_t i = 0; i < count; ++i) {
+ result.Push(transform(in[i]));
+ }
+ return result;
+}
+
+/// Transform performs an element-wise transformation of a vector.
+/// @param in the input vector.
+/// @param transform the transformation function with signature: `OUT(IN, size_t)`
+/// @returns a new vector with each element of the source vector transformed by `transform`.
+template <typename IN, size_t N, typename TRANSFORMER>
+auto Transform(const Vector<IN, N>& in, TRANSFORMER&& transform)
+ -> Vector<decltype(transform(in[0], 1u)), N> {
+ const auto count = in.Length();
+ Vector<decltype(transform(in[0], 1u)), N> result;
+ result.Reserve(count);
+ for (size_t i = 0; i < count; ++i) {
+ result.Push(transform(in[i], i));
+ }
+ return result;
+}
+
+/// Transform performs an element-wise transformation of a slice.
+/// @param in the input slice.
+/// @param transform the transformation function with signature: `OUT(IN)`
+/// @tparam N the small-array size of the returned Vector
+/// @returns a new vector with each element of the source vector transformed by `transform`.
+template <size_t N, typename IN, typename TRANSFORMER>
+auto Transform(Slice<IN> in, TRANSFORMER&& transform) -> Vector<decltype(transform(in[0])), N> {
+ Vector<decltype(transform(in[0])), N> result;
+ result.Reserve(in.len);
+ for (size_t i = 0; i < in.len; ++i) {
+ result.Push(transform(in[i]));
+ }
+ return result;
+}
+
+/// Transform performs an element-wise transformation of a slice.
+/// @param in the input slice.
+/// @param transform the transformation function with signature: `OUT(IN, size_t)`
+/// @tparam N the small-array size of the returned Vector
+/// @returns a new vector with each element of the source vector transformed by `transform`.
+template <size_t N, typename IN, typename TRANSFORMER>
+auto Transform(Slice<IN> in, TRANSFORMER&& transform) -> Vector<decltype(transform(in[0], 1u)), N> {
+ Vector<decltype(transform(in[0], 1u)), N> result;
+ result.Reserve(in.len);
+ for (size_t i = 0; i < in.len; ++i) {
+ result.Push(transform(in[i], i));
+ }
+ return result;
+}
+
+/// Transform performs an element-wise transformation of a vector reference.
+/// @param in the input vector.
+/// @param transform the transformation function with signature: `OUT(IN)`
+/// @tparam N the small-array size of the returned Vector
+/// @returns a new vector with each element of the source vector transformed by `transform`.
+template <size_t N, typename IN, typename TRANSFORMER>
+auto Transform(VectorRef<IN> in, TRANSFORMER&& transform) -> Vector<decltype(transform(in[0])), N> {
+ return Transform<N>(in.Slice(), std::forward<TRANSFORMER>(transform));
+}
+
+/// Transform performs an element-wise transformation of a vector reference.
+/// @param in the input vector.
+/// @param transform the transformation function with signature: `OUT(IN, size_t)`
+/// @tparam N the small-array size of the returned Vector
+/// @returns a new vector with each element of the source vector transformed by `transform`.
+template <size_t N, typename IN, typename TRANSFORMER>
+auto Transform(VectorRef<IN> in, TRANSFORMER&& transform)
+ -> Vector<decltype(transform(in[0], 1u)), N> {
+ return Transform<N>(in.Slice(), std::forward<TRANSFORMER>(transform));
+}
+
+/// TransformN performs an element-wise transformation of a vector, transforming and returning at
+/// most `n` elements.
+/// @param in the input vector.
+/// @param n the maximum number of elements to transform.
+/// @param transform the transformation function with signature: `OUT(IN)`
+/// @returns a new vector with at most n-elements of the source vector transformed by `transform`.
+template <typename IN, typename TRANSFORMER>
+auto TransformN(const std::vector<IN>& in, size_t n, TRANSFORMER&& transform)
+ -> std::vector<decltype(transform(in[0]))> {
+ const auto count = std::min(n, in.size());
+ std::vector<decltype(transform(in[0]))> result(count);
+ for (size_t i = 0; i < count; ++i) {
+ result[i] = transform(in[i]);
+ }
+ return result;
+}
+
+/// TransformN performs an element-wise transformation of a vector, transforming and returning at
+/// most `n` elements.
+/// @param in the input vector.
+/// @param n the maximum number of elements to transform.
+/// @param transform the transformation function with signature: `OUT(IN, size_t)`
+/// @returns a new vector with at most n-elements of the source vector transformed by `transform`.
+template <typename IN, typename TRANSFORMER>
+auto TransformN(const std::vector<IN>& in, size_t n, TRANSFORMER&& transform)
+ -> std::vector<decltype(transform(in[0], 1u))> {
+ const auto count = std::min(n, in.size());
+ std::vector<decltype(transform(in[0], 1u))> result(count);
+ for (size_t i = 0; i < count; ++i) {
+ result[i] = transform(in[i], i);
+ }
+ return result;
+}
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_TRANSFORM_H_
diff --git a/src/tint/utils/containers/transform_test.cc b/src/tint/utils/containers/transform_test.cc
new file mode 100644
index 0000000..aec9d78
--- /dev/null
+++ b/src/tint/utils/containers/transform_test.cc
@@ -0,0 +1,349 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/transform.h"
+
+#include <string>
+#include <type_traits>
+
+#include "gmock/gmock.h"
+
+#define CHECK_ELEMENT_TYPE(vector, expected) \
+ static_assert(std::is_same<decltype(vector)::value_type, expected>::value, \
+ "unexpected result vector element type")
+
+namespace tint::utils {
+namespace {
+
+TEST(TransformTest, StdVectorEmpty) {
+ const std::vector<int> empty{};
+ {
+ auto transformed = Transform(empty, [](int) -> int {
+ [] { FAIL() << "Callback should not be called for empty vector"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_EQ(transformed.size(), 0u);
+ }
+ {
+ auto transformed = Transform(empty, [](int, size_t) -> int {
+ [] { FAIL() << "Callback should not be called for empty vector"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_EQ(transformed.size(), 0u);
+ }
+}
+
+TEST(TransformTest, StdVectorIdentity) {
+ const std::vector<int> input{1, 2, 3, 4};
+ auto transformed = Transform(input, [](int i) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+}
+
+TEST(TransformTest, StdVectorIdentityWithIndex) {
+ const std::vector<int> input{1, 2, 3, 4};
+ auto transformed = Transform(input, [](int i, size_t) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+}
+
+TEST(TransformTest, StdVectorIndex) {
+ const std::vector<int> input{10, 20, 30, 40};
+ {
+ auto transformed = Transform(input, [](int, size_t idx) { return idx; });
+ CHECK_ELEMENT_TYPE(transformed, size_t);
+ EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
+ }
+}
+
+TEST(TransformTest, TransformStdVectorSameType) {
+ const std::vector<int> input{1, 2, 3, 4};
+ {
+ auto transformed = Transform(input, [](int i) { return i * 10; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
+ }
+}
+
+TEST(TransformTest, TransformStdVectorDifferentType) {
+ const std::vector<int> input{1, 2, 3, 4};
+ {
+ auto transformed = Transform(input, [](int i) { return std::to_string(i); });
+ CHECK_ELEMENT_TYPE(transformed, std::string);
+ EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
+ }
+}
+
+TEST(TransformNTest, StdVectorEmpty) {
+ const std::vector<int> empty{};
+ {
+ auto transformed = TransformN(empty, 4u, [](int) -> int {
+ [] { FAIL() << "Callback should not be called for empty vector"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_EQ(transformed.size(), 0u);
+ }
+ {
+ auto transformed = TransformN(empty, 4u, [](int, size_t) -> int {
+ [] { FAIL() << "Callback should not be called for empty vector"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_EQ(transformed.size(), 0u);
+ }
+}
+
+TEST(TransformNTest, StdVectorIdentity) {
+ const std::vector<int> input{1, 2, 3, 4};
+ {
+ auto transformed = TransformN(input, 0u, [](int) {
+ [] { FAIL() << "Callback should not call the transform when n == 0"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_TRUE(transformed.empty());
+ }
+ {
+ auto transformed = TransformN(input, 2u, [](int i) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2));
+ }
+ {
+ auto transformed = TransformN(input, 6u, [](int i) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+ }
+}
+
+TEST(TransformNTest, StdVectorIdentityWithIndex) {
+ const std::vector<int> input{1, 2, 3, 4};
+ {
+ auto transformed = TransformN(input, 0u, [](int, size_t) {
+ [] { FAIL() << "Callback should not call the transform when n == 0"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_TRUE(transformed.empty());
+ }
+ {
+ auto transformed = TransformN(input, 3u, [](int i, size_t) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3));
+ }
+ {
+ auto transformed = TransformN(input, 9u, [](int i, size_t) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+ }
+}
+
+TEST(TransformNTest, StdVectorIndex) {
+ const std::vector<int> input{10, 20, 30, 40};
+ {
+ auto transformed = TransformN(input, 0u, [](int, size_t) {
+ [] { FAIL() << "Callback should not call the transform when n == 0"; }();
+ return static_cast<size_t>(0);
+ });
+ CHECK_ELEMENT_TYPE(transformed, size_t);
+ EXPECT_TRUE(transformed.empty());
+ }
+ {
+ auto transformed = TransformN(input, 2u, [](int, size_t idx) { return idx; });
+ CHECK_ELEMENT_TYPE(transformed, size_t);
+ EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u));
+ }
+ {
+ auto transformed = TransformN(input, 9u, [](int, size_t idx) { return idx; });
+ CHECK_ELEMENT_TYPE(transformed, size_t);
+ EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
+ }
+}
+
+TEST(TransformNTest, StdVectorTransformSameType) {
+ const std::vector<int> input{1, 2, 3, 4};
+ {
+ auto transformed = TransformN(input, 0u, [](int, size_t) {
+ [] { FAIL() << "Callback should not call the transform when n == 0"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_TRUE(transformed.empty());
+ }
+ {
+ auto transformed = TransformN(input, 2u, [](int i) { return i * 10; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(10, 20));
+ }
+ {
+ auto transformed = TransformN(input, 9u, [](int i) { return i * 10; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
+ }
+}
+
+TEST(TransformNTest, StdVectorTransformDifferentType) {
+ const std::vector<int> input{1, 2, 3, 4};
+ {
+ auto transformed = TransformN(input, 0u, [](int) {
+ [] { FAIL() << "Callback should not call the transform when n == 0"; }();
+ return std::string();
+ });
+ CHECK_ELEMENT_TYPE(transformed, std::string);
+ EXPECT_TRUE(transformed.empty());
+ }
+ {
+ auto transformed = TransformN(input, 2u, [](int i) { return std::to_string(i); });
+ CHECK_ELEMENT_TYPE(transformed, std::string);
+ EXPECT_THAT(transformed, testing::ElementsAre("1", "2"));
+ }
+ {
+ auto transformed = TransformN(input, 9u, [](int i) { return std::to_string(i); });
+ CHECK_ELEMENT_TYPE(transformed, std::string);
+ EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
+ }
+}
+
+TEST(TransformTest, TintVectorEmpty) {
+ const Vector<int, 4> empty{};
+ {
+ auto transformed = Transform(empty, [](int) -> int {
+ [] { FAIL() << "Callback should not be called for empty vector"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_EQ(transformed.Length(), 0u);
+ }
+ {
+ auto transformed = Transform(empty, [](int, size_t) -> int {
+ [] { FAIL() << "Callback should not be called for empty vector"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_EQ(transformed.Length(), 0u);
+ }
+}
+
+TEST(TransformTest, TintVectorIdentity) {
+ const Vector<int, 4> input{1, 2, 3, 4};
+ auto transformed = Transform(input, [](int i) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+}
+
+TEST(TransformTest, TintVectorIdentityWithIndex) {
+ const Vector<int, 4> input{1, 2, 3, 4};
+ auto transformed = Transform(input, [](int i, size_t) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+}
+
+TEST(TransformTest, TintVectorIndex) {
+ const Vector<int, 4> input{10, 20, 30, 40};
+ {
+ auto transformed = Transform(input, [](int, size_t idx) { return idx; });
+ CHECK_ELEMENT_TYPE(transformed, size_t);
+ EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
+ }
+}
+
+TEST(TransformTest, TransformTintVectorSameType) {
+ const Vector<int, 4> input{1, 2, 3, 4};
+ {
+ auto transformed = Transform(input, [](int i) { return i * 10; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
+ }
+}
+
+TEST(TransformTest, TransformTintVectorDifferentType) {
+ const Vector<int, 4> input{1, 2, 3, 4};
+ {
+ auto transformed = Transform(input, [](int i) { return std::to_string(i); });
+ CHECK_ELEMENT_TYPE(transformed, std::string);
+ EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
+ }
+}
+
+TEST(TransformTest, VectorRefEmpty) {
+ Vector<int, 4> empty{};
+ VectorRef<int> ref(empty);
+ {
+ auto transformed = Transform<4>(ref, [](int) -> int {
+ [] { FAIL() << "Callback should not be called for empty vector"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_EQ(transformed.Length(), 0u);
+ }
+ {
+ auto transformed = Transform<4>(ref, [](int, size_t) -> int {
+ [] { FAIL() << "Callback should not be called for empty vector"; }();
+ return 0;
+ });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_EQ(transformed.Length(), 0u);
+ }
+}
+
+TEST(TransformTest, VectorRefIdentity) {
+ Vector<int, 4> input{1, 2, 3, 4};
+ VectorRef<int> ref(input);
+ auto transformed = Transform<8>(ref, [](int i) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+}
+
+TEST(TransformTest, VectorRefIdentityWithIndex) {
+ Vector<int, 4> input{1, 2, 3, 4};
+ VectorRef<int> ref(input);
+ auto transformed = Transform<2>(ref, [](int i, size_t) { return i; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4));
+}
+
+TEST(TransformTest, VectorRefIndex) {
+ Vector<int, 4> input{10, 20, 30, 40};
+ VectorRef<int> ref(input);
+ {
+ auto transformed = Transform<4>(ref, [](int, size_t idx) { return idx; });
+ CHECK_ELEMENT_TYPE(transformed, size_t);
+ EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u));
+ }
+}
+
+TEST(TransformTest, TransformVectorRefSameType) {
+ Vector<int, 4> input{1, 2, 3, 4};
+ VectorRef<int> ref(input);
+ {
+ auto transformed = Transform<4>(ref, [](int i) { return i * 10; });
+ CHECK_ELEMENT_TYPE(transformed, int);
+ EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40));
+ }
+}
+
+TEST(TransformTest, TransformVectorRefDifferentType) {
+ Vector<int, 4> input{1, 2, 3, 4};
+ VectorRef<int> ref(input);
+ {
+ auto transformed = Transform<4>(ref, [](int i) { return std::to_string(i); });
+ CHECK_ELEMENT_TYPE(transformed, std::string);
+ EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4"));
+ }
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/unique_allocator.h b/src/tint/utils/containers/unique_allocator.h
new file mode 100644
index 0000000..d5e7e24
--- /dev/null
+++ b/src/tint/utils/containers/unique_allocator.h
@@ -0,0 +1,119 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_UNIQUE_ALLOCATOR_H_
+#define SRC_TINT_UTILS_CONTAINERS_UNIQUE_ALLOCATOR_H_
+
+#include <functional>
+#include <unordered_set>
+#include <utility>
+
+#include "src/tint/utils/memory/block_allocator.h"
+
+namespace tint::utils {
+
+/// UniqueAllocator is used to allocate unique instances of the template type
+/// `T`.
+template <typename T, typename HASH = std::hash<T>, typename EQUAL = std::equal_to<T>>
+class UniqueAllocator {
+ public:
+ /// Iterator is the type returned by begin() and end()
+ using Iterator = typename BlockAllocator<T>::ConstIterator;
+
+ /// @param args the arguments used to construct the object.
+ /// @return a pointer to an instance of `T` with the provided arguments.
+ /// If an existing instance of `T` has been constructed, then the same
+ /// pointer is returned.
+ template <typename TYPE = T, typename... ARGS>
+ TYPE* Get(ARGS&&... args) {
+ // Create a temporary T instance on the stack so that we can hash it, and
+ // use it for equality lookup for the std::unordered_set. If the item is not
+ // found in the set, then we create the persisted instance with the
+ // allocator.
+ TYPE key{args...};
+ auto hash = Hasher{}(key);
+ auto it = items.find(Entry{hash, &key});
+ if (it != items.end()) {
+ return static_cast<TYPE*>(it->ptr);
+ }
+ auto* ptr = allocator.template Create<TYPE>(std::forward<ARGS>(args)...);
+ items.emplace_hint(it, Entry{hash, ptr});
+ return ptr;
+ }
+
+ /// @param args the arguments used to create the temporary used for the search.
+ /// @return a pointer to an instance of `T` with the provided arguments, or nullptr if the item
+ /// was not found.
+ template <typename TYPE = T, typename... ARGS>
+ TYPE* Find(ARGS&&... args) const {
+ // Create a temporary T instance on the stack so that we can hash it, and
+ // use it for equality lookup for the std::unordered_set.
+ TYPE key{args...};
+ auto hash = Hasher{}(key);
+ auto it = items.find(Entry{hash, &key});
+ if (it != items.end()) {
+ return static_cast<TYPE*>(it->ptr);
+ }
+ return nullptr;
+ }
+
+ /// Wrap sets this allocator to the objects created with the content of `inner`.
+ /// The allocator after Wrap is intended to temporarily extend the objects
+ /// of an existing immutable UniqueAllocator.
+ /// As the copied objects are owned by `inner`, `inner` must not be destructed
+ /// or assigned while using this allocator.
+ /// @param o the immutable UniqueAlllocator to extend
+ void Wrap(const UniqueAllocator<T, HASH, EQUAL>& o) { items = o.items; }
+
+ /// @returns an iterator to the beginning of the types
+ Iterator begin() const { return allocator.Objects().begin(); }
+ /// @returns an iterator to the end of the types
+ Iterator end() const { return allocator.Objects().end(); }
+
+ private:
+ /// The hash function
+ using Hasher = HASH;
+ /// The equality function
+ using Equality = EQUAL;
+
+ /// Entry is used as the entry to the unordered_set
+ struct Entry {
+ /// The pre-calculated hash of the entry
+ size_t hash;
+ /// The pointer to the unique object
+ T* ptr;
+ };
+ /// Comparator is the hashing and equality function used by the unordered_set
+ struct Comparator {
+ /// Hashing function
+ /// @param e the entry
+ /// @returns the hash of the entry
+ size_t operator()(Entry e) const { return e.hash; }
+
+ /// Equality function
+ /// @param a the first entry to compare
+ /// @param b the second entry to compare
+ /// @returns true if the two entries are equal
+ bool operator()(Entry a, Entry b) const { return EQUAL{}(*a.ptr, *b.ptr); }
+ };
+
+ /// The block allocator used to allocate the unique objects
+ BlockAllocator<T> allocator;
+ /// The unordered_set of unique item entries
+ std::unordered_set<Entry, Comparator, Comparator> items;
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_UNIQUE_ALLOCATOR_H_
diff --git a/src/tint/utils/containers/unique_allocator_test.cc b/src/tint/utils/containers/unique_allocator_test.cc
new file mode 100644
index 0000000..a71ce98
--- /dev/null
+++ b/src/tint/utils/containers/unique_allocator_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/unique_allocator.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(UniqueAllocator, Int) {
+ UniqueAllocator<int> a;
+ EXPECT_NE(a.Get(0), a.Get(1));
+ EXPECT_NE(a.Get(1), a.Get(2));
+ EXPECT_EQ(a.Get(0), a.Get(0));
+ EXPECT_EQ(a.Get(1), a.Get(1));
+ EXPECT_EQ(a.Get(2), a.Get(2));
+}
+
+TEST(UniqueAllocator, Float) {
+ UniqueAllocator<float> a;
+ EXPECT_NE(a.Get(0.1f), a.Get(1.1f));
+ EXPECT_NE(a.Get(1.1f), a.Get(2.1f));
+ EXPECT_EQ(a.Get(0.1f), a.Get(0.1f));
+ EXPECT_EQ(a.Get(1.1f), a.Get(1.1f));
+ EXPECT_EQ(a.Get(2.1f), a.Get(2.1f));
+}
+
+TEST(UniqueAllocator, String) {
+ UniqueAllocator<std::string> a;
+ EXPECT_NE(a.Get("x"), a.Get("y"));
+ EXPECT_NE(a.Get("z"), a.Get("w"));
+ EXPECT_EQ(a.Get("x"), a.Get("x"));
+ EXPECT_EQ(a.Get("y"), a.Get("y"));
+ EXPECT_EQ(a.Get("z"), a.Get("z"));
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/unique_vector.h b/src/tint/utils/containers/unique_vector.h
new file mode 100644
index 0000000..980a06f
--- /dev/null
+++ b/src/tint/utils/containers/unique_vector.h
@@ -0,0 +1,149 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_UNIQUE_VECTOR_H_
+#define SRC_TINT_UTILS_CONTAINERS_UNIQUE_VECTOR_H_
+
+#include <cstddef>
+#include <functional>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/utils/containers/hashset.h"
+#include "src/tint/utils/containers/vector.h"
+
+namespace tint::utils {
+
+/// UniqueVector is an ordered container that only contains unique items.
+/// Attempting to add a duplicate is a no-op.
+template <typename T, size_t N, typename HASH = std::hash<T>, typename EQUAL = std::equal_to<T>>
+struct UniqueVector {
+ /// STL-friendly alias to T. Used by gmock.
+ using value_type = T;
+
+ /// Constructor
+ UniqueVector() = default;
+
+ /// Constructor
+ /// @param v the vector to construct this UniqueVector with. Duplicate
+ /// elements will be removed.
+ explicit UniqueVector(std::vector<T>&& v) {
+ for (auto& el : v) {
+ Add(el);
+ }
+ }
+
+ /// Add appends the item to the end of the vector, if the vector does not
+ /// already contain the given item.
+ /// @param item the item to append to the end of the vector
+ /// @returns true if the item was added, otherwise false.
+ bool Add(const T& item) {
+ if (set.Add(item)) {
+ vector.Push(item);
+ return true;
+ }
+ return false;
+ }
+
+ /// Removes @p count elements from the vector
+ /// @param start the index of the first element to remove
+ /// @param count the number of elements to remove
+ void Erase(size_t start, size_t count = 1) {
+ for (size_t i = 0; i < count; i++) {
+ set.Remove(vector[start + i]);
+ }
+ vector.Erase(start, count);
+ }
+
+ /// @returns true if the vector contains `item`
+ /// @param item the item
+ bool Contains(const T& item) const { return set.Contains(item); }
+
+ /// @param i the index of the element to retrieve
+ /// @returns the element at the index `i`
+ T& operator[](size_t i) { return vector[i]; }
+
+ /// @param i the index of the element to retrieve
+ /// @returns the element at the index `i`
+ const T& operator[](size_t i) const { return vector[i]; }
+
+ /// @returns true if the vector is empty
+ bool IsEmpty() const { return vector.IsEmpty(); }
+
+ /// Removes all elements from the vector
+ void Clear() {
+ vector.Clear();
+ set.Clear();
+ }
+
+ /// @returns the number of items in the vector
+ size_t Length() const { return vector.Length(); }
+
+ /// @returns the pointer to the first element in the vector, or nullptr if the vector is empty.
+ const T* Data() const { return vector.IsEmpty() ? nullptr : &vector[0]; }
+
+ /// @returns an iterator to the beginning of the vector
+ auto begin() const { return vector.begin(); }
+
+ /// @returns an iterator to the end of the vector
+ auto end() const { return vector.end(); }
+
+ /// @returns an iterator to the beginning of the reversed vector
+ auto rbegin() const { return vector.rbegin(); }
+
+ /// @returns an iterator to the end of the reversed vector
+ auto rend() const { return vector.rend(); }
+
+ /// @returns a reference to the internal vector
+ operator VectorRef<T>() const { return vector; }
+
+ /// @returns the std::move()'d vector.
+ /// @note The UniqueVector must not be used after calling this method
+ VectorRef<T> Release() { return std::move(vector); }
+
+ /// Pre-allocates `count` elements in the vector and set
+ /// @param count the number of elements to pre-allocate
+ void Reserve(size_t count) {
+ vector.Reserve(count);
+ set.Reserve(count);
+ }
+
+ /// Removes the last element from the vector
+ /// @returns the popped element
+ T Pop() {
+ set.Remove(vector.Back());
+ return vector.Pop();
+ }
+
+ /// Removes the last element from the vector if it is equal to @p value
+ /// @param value the value to pop if it is at the back of the vector
+ /// @returns true if the value was popped, otherwise false
+ bool TryPop(T value) {
+ if (!vector.IsEmpty() && vector.Back() == value) {
+ set.Remove(vector.Back());
+ vector.Pop();
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ Vector<T, N> vector;
+ Hashset<T, N, HASH, EQUAL> set;
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_UNIQUE_VECTOR_H_
diff --git a/src/tint/utils/containers/unique_vector_test.cc b/src/tint/utils/containers/unique_vector_test.cc
new file mode 100644
index 0000000..5f50b3a
--- /dev/null
+++ b/src/tint/utils/containers/unique_vector_test.cc
@@ -0,0 +1,236 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/unique_vector.h"
+
+#include <vector>
+
+#include "src/tint/utils/containers/reverse.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(UniqueVectorTest, Empty) {
+ UniqueVector<int, 4> unique_vec;
+ ASSERT_EQ(unique_vec.Length(), 0u);
+ EXPECT_EQ(unique_vec.IsEmpty(), true);
+ EXPECT_EQ(unique_vec.begin(), unique_vec.end());
+}
+
+TEST(UniqueVectorTest, MoveConstructor) {
+ UniqueVector<int, 4> unique_vec(std::vector<int>{0, 3, 2, 1, 2});
+ ASSERT_EQ(unique_vec.Length(), 4u);
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+ EXPECT_EQ(unique_vec[0], 0);
+ EXPECT_EQ(unique_vec[1], 3);
+ EXPECT_EQ(unique_vec[2], 2);
+ EXPECT_EQ(unique_vec[3], 1);
+}
+
+TEST(UniqueVectorTest, AddUnique) {
+ UniqueVector<int, 4> unique_vec;
+ unique_vec.Add(0);
+ unique_vec.Add(1);
+ unique_vec.Add(2);
+ ASSERT_EQ(unique_vec.Length(), 3u);
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+ int i = 0;
+ for (auto n : unique_vec) {
+ EXPECT_EQ(n, i);
+ i++;
+ }
+ for (auto n : Reverse(unique_vec)) {
+ i--;
+ EXPECT_EQ(n, i);
+ }
+ EXPECT_EQ(unique_vec[0], 0);
+ EXPECT_EQ(unique_vec[1], 1);
+ EXPECT_EQ(unique_vec[2], 2);
+}
+
+TEST(UniqueVectorTest, AddDuplicates) {
+ UniqueVector<int, 4> unique_vec;
+ unique_vec.Add(0);
+ unique_vec.Add(0);
+ unique_vec.Add(0);
+ unique_vec.Add(1);
+ unique_vec.Add(1);
+ unique_vec.Add(2);
+ ASSERT_EQ(unique_vec.Length(), 3u);
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+ int i = 0;
+ for (auto n : unique_vec) {
+ EXPECT_EQ(n, i);
+ i++;
+ }
+ for (auto n : Reverse(unique_vec)) {
+ i--;
+ EXPECT_EQ(n, i);
+ }
+ EXPECT_EQ(unique_vec[0], 0);
+ EXPECT_EQ(unique_vec[1], 1);
+ EXPECT_EQ(unique_vec[2], 2);
+}
+
+TEST(UniqueVectorTest, Erase) {
+ UniqueVector<int, 4> unique_vec;
+ unique_vec.Add(0);
+ unique_vec.Add(3);
+ unique_vec.Add(2);
+ unique_vec.Add(5);
+ unique_vec.Add(1);
+ unique_vec.Add(6);
+ EXPECT_EQ(unique_vec.Length(), 6u);
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+
+ unique_vec.Erase(2, 2);
+
+ ASSERT_EQ(unique_vec.Length(), 4u);
+ EXPECT_EQ(unique_vec[0], 0);
+ EXPECT_EQ(unique_vec[1], 3);
+ EXPECT_EQ(unique_vec[2], 1);
+ EXPECT_EQ(unique_vec[3], 6);
+ EXPECT_TRUE(unique_vec.Contains(0));
+ EXPECT_TRUE(unique_vec.Contains(3));
+ EXPECT_FALSE(unique_vec.Contains(2));
+ EXPECT_FALSE(unique_vec.Contains(5));
+ EXPECT_TRUE(unique_vec.Contains(1));
+ EXPECT_TRUE(unique_vec.Contains(6));
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+
+ unique_vec.Erase(1);
+
+ ASSERT_EQ(unique_vec.Length(), 3u);
+ EXPECT_EQ(unique_vec[0], 0);
+ EXPECT_EQ(unique_vec[1], 1);
+ EXPECT_EQ(unique_vec[2], 6);
+ EXPECT_TRUE(unique_vec.Contains(0));
+ EXPECT_FALSE(unique_vec.Contains(3));
+ EXPECT_FALSE(unique_vec.Contains(2));
+ EXPECT_FALSE(unique_vec.Contains(5));
+ EXPECT_TRUE(unique_vec.Contains(1));
+ EXPECT_TRUE(unique_vec.Contains(6));
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+
+ unique_vec.Erase(2);
+
+ ASSERT_EQ(unique_vec.Length(), 2u);
+ EXPECT_EQ(unique_vec[0], 0);
+ EXPECT_EQ(unique_vec[1], 1);
+ EXPECT_TRUE(unique_vec.Contains(0));
+ EXPECT_FALSE(unique_vec.Contains(3));
+ EXPECT_FALSE(unique_vec.Contains(2));
+ EXPECT_FALSE(unique_vec.Contains(5));
+ EXPECT_TRUE(unique_vec.Contains(1));
+ EXPECT_FALSE(unique_vec.Contains(6));
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+
+ unique_vec.Erase(0, 2);
+
+ ASSERT_EQ(unique_vec.Length(), 0u);
+ EXPECT_FALSE(unique_vec.Contains(0));
+ EXPECT_FALSE(unique_vec.Contains(3));
+ EXPECT_FALSE(unique_vec.Contains(2));
+ EXPECT_FALSE(unique_vec.Contains(5));
+ EXPECT_FALSE(unique_vec.Contains(1));
+ EXPECT_FALSE(unique_vec.Contains(6));
+ EXPECT_EQ(unique_vec.IsEmpty(), true);
+
+ unique_vec.Add(6);
+ unique_vec.Add(0);
+ unique_vec.Add(2);
+
+ ASSERT_EQ(unique_vec.Length(), 3u);
+ EXPECT_EQ(unique_vec[0], 6);
+ EXPECT_EQ(unique_vec[1], 0);
+ EXPECT_EQ(unique_vec[2], 2);
+ EXPECT_TRUE(unique_vec.Contains(0));
+ EXPECT_FALSE(unique_vec.Contains(3));
+ EXPECT_TRUE(unique_vec.Contains(2));
+ EXPECT_FALSE(unique_vec.Contains(5));
+ EXPECT_FALSE(unique_vec.Contains(1));
+ EXPECT_TRUE(unique_vec.Contains(6));
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+}
+
+TEST(UniqueVectorTest, AsVector) {
+ UniqueVector<int, 4> unique_vec;
+ unique_vec.Add(0);
+ unique_vec.Add(0);
+ unique_vec.Add(0);
+ unique_vec.Add(1);
+ unique_vec.Add(1);
+ unique_vec.Add(2);
+
+ utils::VectorRef<int> ref = unique_vec;
+ EXPECT_EQ(ref.Length(), 3u);
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+ int i = 0;
+ for (auto n : ref) {
+ EXPECT_EQ(n, i);
+ i++;
+ }
+ for (auto n : Reverse(unique_vec)) {
+ i--;
+ EXPECT_EQ(n, i);
+ }
+}
+
+TEST(UniqueVectorTest, PopBack) {
+ UniqueVector<int, 4> unique_vec;
+ unique_vec.Add(0);
+ unique_vec.Add(2);
+ unique_vec.Add(1);
+
+ EXPECT_EQ(unique_vec.Pop(), 1);
+ ASSERT_EQ(unique_vec.Length(), 2u);
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+ EXPECT_EQ(unique_vec[0], 0);
+ EXPECT_EQ(unique_vec[1], 2);
+
+ EXPECT_EQ(unique_vec.Pop(), 2);
+ ASSERT_EQ(unique_vec.Length(), 1u);
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+ EXPECT_EQ(unique_vec[0], 0);
+
+ unique_vec.Add(1);
+
+ ASSERT_EQ(unique_vec.Length(), 2u);
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+ EXPECT_EQ(unique_vec[0], 0);
+ EXPECT_EQ(unique_vec[1], 1);
+
+ EXPECT_EQ(unique_vec.Pop(), 1);
+ ASSERT_EQ(unique_vec.Length(), 1u);
+ EXPECT_EQ(unique_vec.IsEmpty(), false);
+ EXPECT_EQ(unique_vec[0], 0);
+
+ EXPECT_EQ(unique_vec.Pop(), 0);
+ EXPECT_EQ(unique_vec.Length(), 0u);
+ EXPECT_EQ(unique_vec.IsEmpty(), true);
+}
+
+TEST(UniqueVectorTest, Data) {
+ UniqueVector<int, 4> unique_vec;
+ EXPECT_EQ(unique_vec.Data(), nullptr);
+
+ unique_vec.Add(42);
+ EXPECT_EQ(unique_vec.Data(), &unique_vec[0]);
+ EXPECT_EQ(*unique_vec.Data(), 42);
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/containers/vector.h b/src/tint/utils/containers/vector.h
new file mode 100644
index 0000000..b844e54
--- /dev/null
+++ b/src/tint/utils/containers/vector.h
@@ -0,0 +1,897 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_CONTAINERS_VECTOR_H_
+#define SRC_TINT_UTILS_CONTAINERS_VECTOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <algorithm>
+#include <iterator>
+#include <new>
+#include <utility>
+#include <vector>
+
+#include "src/tint/utils/containers/slice.h"
+#include "src/tint/utils/debug/debug.h"
+#include "src/tint/utils/macros/compiler.h"
+#include "src/tint/utils/memory/bitcast.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::utils {
+
+/// Forward declarations
+template <typename>
+class VectorRef;
+template <typename>
+class VectorRef;
+
+} // namespace tint::utils
+
+namespace tint::utils {
+
+/// Vector is a small-object-optimized, dynamically-sized vector of contigious elements of type T.
+///
+/// Vector will fit `N` elements internally before spilling to heap allocations. If `N` is greater
+/// than zero, the internal elements are stored in a 'small array' held internally by the Vector.
+///
+/// Vectors can be copied or moved.
+///
+/// Copying a vector will either copy to the 'small array' if the number of elements is equal to or
+/// less than N, otherwise elements will be copied into a new heap allocation.
+///
+/// Moving a vector will reassign ownership of the heap-allocation memory, if the source vector
+/// holds its elements in a heap allocation, otherwise a copy will be made as described above.
+///
+/// Vector is optimized for CPU performance over memory efficiency. For example:
+/// * Moving a vector that stores its elements in a heap allocation to another vector will simply
+/// assign the heap allocation, even if the target vector can hold the elements in its 'small
+/// array'. This reduces memory copying, but may incur additional memory usage.
+/// * Resizing, or popping elements from a vector that has spilled to a heap allocation does not
+/// revert back to using the 'small array'. Again, this is to reduce memory copying.
+template <typename T, size_t N>
+class Vector {
+ public:
+ /// Alias to `T*`.
+ using iterator = T*;
+ /// Alias to `const T*`.
+ using const_iterator = const T*;
+ /// Alias to `T`.
+ using value_type = T;
+ /// Value of `N`
+ static constexpr size_t static_length = N;
+
+ /// Constructor
+ Vector() = default;
+
+ /// Constructor
+ Vector(EmptyType) {} // NOLINT(runtime/explicit)
+
+ /// Constructor
+ /// @param elements the elements to place into the vector
+ Vector(std::initializer_list<T> elements) {
+ Reserve(elements.size());
+ for (auto& el : elements) {
+ new (&impl_.slice.data[impl_.slice.len++]) T{el};
+ }
+ }
+
+ /// Copy constructor
+ /// @param other the vector to copy
+ Vector(const Vector& other) { Copy(other.impl_.slice); }
+
+ /// Move constructor
+ /// @param other the vector to move
+ Vector(Vector&& other) { MoveOrCopy(VectorRef<T>(std::move(other))); }
+
+ /// Copy constructor (differing N length)
+ /// @param other the vector to copy
+ template <size_t N2>
+ Vector(const Vector<T, N2>& other) {
+ Copy(other.impl_.slice);
+ }
+
+ /// Move constructor (differing N length)
+ /// @param other the vector to move
+ template <size_t N2>
+ Vector(Vector<T, N2>&& other) {
+ MoveOrCopy(VectorRef<T>(std::move(other)));
+ }
+
+ /// Copy constructor with covariance / const conversion
+ /// @param other the vector to copy
+ /// @see CanReinterpretSlice for rules about conversion
+ template <typename U,
+ size_t N2,
+ ReinterpretMode MODE,
+ typename = std::enable_if_t<CanReinterpretSlice<MODE, T, U>>>
+ Vector(const Vector<U, N2>& other) { // NOLINT(runtime/explicit)
+ Copy(other.impl_.slice.template Reinterpret<T, MODE>);
+ }
+
+ /// Move constructor with covariance / const conversion
+ /// @param other the vector to move
+ /// @see CanReinterpretSlice for rules about conversion
+ template <typename U,
+ size_t N2,
+ ReinterpretMode MODE,
+ typename = std::enable_if_t<CanReinterpretSlice<MODE, T, U>>>
+ Vector(Vector<U, N2>&& other) { // NOLINT(runtime/explicit)
+ MoveOrCopy(VectorRef<T>(std::move(other)));
+ }
+
+ /// Move constructor from a mutable vector reference
+ /// @param other the vector reference to move
+ Vector(VectorRef<T>&& other) { MoveOrCopy(std::move(other)); } // NOLINT(runtime/explicit)
+
+ /// Copy constructor from an immutable vector reference
+ /// @param other the vector reference to copy
+ Vector(const VectorRef<T>& other) { Copy(other.slice_); } // NOLINT(runtime/explicit)
+
+ /// Copy constructor from an immutable slice
+ /// @param other the slice to copy
+ Vector(const Slice<T>& other) { Copy(other); } // NOLINT(runtime/explicit)
+
+ /// Destructor
+ ~Vector() { ClearAndFree(); }
+
+ /// Assignment operator
+ /// @param other the vector to copy
+ /// @returns this vector so calls can be chained
+ Vector& operator=(const Vector& other) {
+ if (&other != this) {
+ Copy(other.impl_.slice);
+ }
+ return *this;
+ }
+
+ /// Move operator
+ /// @param other the vector to move
+ /// @returns this vector so calls can be chained
+ Vector& operator=(Vector&& other) {
+ if (&other != this) {
+ MoveOrCopy(VectorRef<T>(std::move(other)));
+ }
+ return *this;
+ }
+
+ /// Assignment operator (differing N length)
+ /// @param other the vector to copy
+ /// @returns this vector so calls can be chained
+ template <size_t N2>
+ Vector& operator=(const Vector<T, N2>& other) {
+ Copy(other.impl_.slice);
+ return *this;
+ }
+
+ /// Move operator (differing N length)
+ /// @param other the vector to copy
+ /// @returns this vector so calls can be chained
+ template <size_t N2>
+ Vector& operator=(Vector<T, N2>&& other) {
+ MoveOrCopy(VectorRef<T>(std::move(other)));
+ return *this;
+ }
+
+ /// Assignment operator (differing N length)
+ /// @param other the vector reference to copy
+ /// @returns this vector so calls can be chained
+ Vector& operator=(const VectorRef<T>& other) {
+ if (&other.slice_ != &impl_.slice) {
+ Copy(other.slice_);
+ }
+ return *this;
+ }
+
+ /// Move operator (differing N length)
+ /// @param other the vector reference to copy
+ /// @returns this vector so calls can be chained
+ Vector& operator=(VectorRef<T>&& other) {
+ if (&other.slice_ != &impl_.slice) {
+ MoveOrCopy(std::move(other));
+ }
+ return *this;
+ }
+
+ /// Assignment operator for Slice
+ /// @param other the slice to copy
+ /// @returns this vector so calls can be chained
+ Vector& operator=(const Slice<T>& other) {
+ Copy(other);
+ return *this;
+ }
+
+ /// Index operator
+ /// @param i the element index. Must be less than `len`.
+ /// @returns a reference to the i'th element.
+ T& operator[](size_t i) {
+ TINT_ASSERT(Utils, i < Length());
+ return impl_.slice[i];
+ }
+
+ /// Index operator
+ /// @param i the element index. Must be less than `len`.
+ /// @returns a reference to the i'th element.
+ const T& operator[](size_t i) const {
+ TINT_ASSERT(Utils, i < Length());
+ return impl_.slice[i];
+ }
+
+ /// @return the number of elements in the vector
+ size_t Length() const { return impl_.slice.len; }
+
+ /// @return the number of elements that the vector could hold before a heap allocation needs to
+ /// be made
+ size_t Capacity() const { return impl_.slice.cap; }
+
+ /// Reserves memory to hold at least `new_cap` elements
+ /// @param new_cap the new vector capacity
+ void Reserve(size_t new_cap) {
+ if (new_cap > impl_.slice.cap) {
+ auto* old_data = impl_.slice.data;
+ impl_.Allocate(new_cap);
+ for (size_t i = 0; i < impl_.slice.len; i++) {
+ new (&impl_.slice.data[i]) T(std::move(old_data[i]));
+ old_data[i].~T();
+ }
+ impl_.Free(old_data);
+ }
+ }
+
+ /// Resizes the vector to the given length, expanding capacity if necessary.
+ /// New elements are zero-initialized
+ /// @param new_len the new vector length
+ void Resize(size_t new_len) {
+ Reserve(new_len);
+ for (size_t i = impl_.slice.len; i > new_len; i--) { // Shrink
+ impl_.slice.data[i - 1].~T();
+ }
+ for (size_t i = impl_.slice.len; i < new_len; i++) { // Grow
+ new (&impl_.slice.data[i]) T{};
+ }
+ impl_.slice.len = new_len;
+ }
+
+ /// Resizes the vector to the given length, expanding capacity if necessary.
+ /// @param new_len the new vector length
+ /// @param value the value to copy into the new elements
+ void Resize(size_t new_len, const T& value) {
+ Reserve(new_len);
+ for (size_t i = impl_.slice.len; i > new_len; i--) { // Shrink
+ impl_.slice.data[i - 1].~T();
+ }
+ for (size_t i = impl_.slice.len; i < new_len; i++) { // Grow
+ new (&impl_.slice.data[i]) T{value};
+ }
+ impl_.slice.len = new_len;
+ }
+
+ /// Copies all the elements from `other` to this vector, replacing the content of this vector.
+ /// @param other the
+ template <typename T2, size_t N2>
+ void Copy(const Vector<T2, N2>& other) {
+ Copy(other.impl_.slice);
+ }
+
+ /// Clears all elements from the vector, keeping the capacity the same.
+ void Clear() {
+ TINT_BEGIN_DISABLE_WARNING(MAYBE_UNINITIALIZED);
+ for (size_t i = 0; i < impl_.slice.len; i++) {
+ impl_.slice.data[i].~T();
+ }
+ impl_.slice.len = 0;
+ TINT_END_DISABLE_WARNING(MAYBE_UNINITIALIZED);
+ }
+
+ /// Appends a new element to the vector.
+ /// @param el the element to copy to the vector.
+ void Push(const T& el) {
+ if (impl_.slice.len >= impl_.slice.cap) {
+ Grow();
+ }
+ new (&impl_.slice.data[impl_.slice.len++]) T(el);
+ }
+
+ /// Appends a new element to the vector.
+ /// @param el the element to move to the vector.
+ void Push(T&& el) {
+ if (impl_.slice.len >= impl_.slice.cap) {
+ Grow();
+ }
+ new (&impl_.slice.data[impl_.slice.len++]) T(std::move(el));
+ }
+
+ /// Appends a new element to the vector.
+ /// @param args the arguments to pass to the element constructor.
+ template <typename... ARGS>
+ void Emplace(ARGS&&... args) {
+ if (impl_.slice.len >= impl_.slice.cap) {
+ Grow();
+ }
+ new (&impl_.slice.data[impl_.slice.len++]) T{std::forward<ARGS>(args)...};
+ }
+
+ /// Removes and returns the last element from the vector.
+ /// @returns the popped element
+ T Pop() {
+ TINT_ASSERT(Utils, !IsEmpty());
+ auto& el = impl_.slice.data[--impl_.slice.len];
+ auto val = std::move(el);
+ el.~T();
+ return val;
+ }
+
+ /// Inserts the element @p element before the element at @p before
+ /// @param before the index of the element to insert before
+ /// @param element the element to insert
+ template <typename EL>
+ void Insert(size_t before, EL&& element) {
+ TINT_ASSERT(Utils, before <= Length());
+ size_t n = Length();
+ Resize(Length() + 1);
+ // Shuffle
+ for (size_t i = n; i > before; i--) {
+ auto& src = impl_.slice.data[i - 1];
+ auto& dst = impl_.slice.data[i];
+ dst = std::move(src);
+ }
+ // Insert
+ impl_.slice.data[before] = std::forward<EL>(element);
+ }
+
+ /// Removes @p count elements from the vector
+ /// @param start the index of the first element to remove
+ /// @param count the number of elements to remove
+ void Erase(size_t start, size_t count = 1) {
+ TINT_ASSERT(Utils, start < Length());
+ TINT_ASSERT(Utils, (start + count) <= Length());
+ // Shuffle
+ for (size_t i = start + count; i < impl_.slice.len; i++) {
+ auto& src = impl_.slice.data[i];
+ auto& dst = impl_.slice.data[i - count];
+ dst = std::move(src);
+ }
+ // Pop
+ for (size_t i = 0; i < count; i++) {
+ auto& el = impl_.slice.data[--impl_.slice.len];
+ el.~T();
+ }
+ }
+
+ /// Sort sorts the vector in-place using the predicate function @p pred
+ /// @param pred a function that has the signature `bool(const T& a, const T& b)` which returns
+ /// true if `a` is ordered before `b`.
+ template <typename PREDICATE>
+ void Sort(PREDICATE&& pred) {
+ std::sort(begin(), end(), std::forward<PREDICATE>(pred));
+ }
+
+ /// Sort sorts the vector in-place using `T::operator<()`
+ void Sort() {
+ Sort([](auto& a, auto& b) { return a < b; });
+ }
+
+ /// Reverse reversed the vector in-place
+ void Reverse() {
+ size_t n = Length();
+ size_t mid = n / 2;
+ auto& self = *this;
+ for (size_t i = 0; i < mid; i++) {
+ std::swap(self[i], self[n - i - 1]);
+ }
+ }
+
+ /// @returns true if the predicate function returns true for any of the elements of the vector
+ /// @param pred a function-like with the signature `bool(T)`
+ template <typename PREDICATE>
+ bool Any(PREDICATE&& pred) const {
+ return std::any_of(begin(), end(), std::forward<PREDICATE>(pred));
+ }
+
+ /// @returns false if the predicate function returns false for any of the elements of the vector
+ /// @param pred a function-like with the signature `bool(T)`
+ template <typename PREDICATE>
+ bool All(PREDICATE&& pred) const {
+ return std::all_of(begin(), end(), std::forward<PREDICATE>(pred));
+ }
+
+ /// @returns true if the vector is empty.
+ bool IsEmpty() const { return impl_.slice.len == 0; }
+
+ /// @returns a reference to the first element in the vector
+ T& Front() { return impl_.slice.Front(); }
+
+ /// @returns a reference to the first element in the vector
+ const T& Front() const { return impl_.slice.Front(); }
+
+ /// @returns a reference to the last element in the vector
+ T& Back() { return impl_.slice.Back(); }
+
+ /// @returns a reference to the last element in the vector
+ const T& Back() const { return impl_.slice.Back(); }
+
+ /// @returns a pointer to the first element in the vector
+ T* begin() { return impl_.slice.begin(); }
+
+ /// @returns a pointer to the first element in the vector
+ const T* begin() const { return impl_.slice.begin(); }
+
+ /// @returns a pointer to one past the last element in the vector
+ T* end() { return impl_.slice.end(); }
+
+ /// @returns a pointer to one past the last element in the vector
+ const T* end() const { return impl_.slice.end(); }
+
+ /// @returns a reverse iterator starting with the last element in the vector
+ auto rbegin() { return impl_.slice.rbegin(); }
+
+ /// @returns a reverse iterator starting with the last element in the vector
+ auto rbegin() const { return impl_.slice.rbegin(); }
+
+ /// @returns the end for a reverse iterator
+ auto rend() { return impl_.slice.rend(); }
+
+ /// @returns the end for a reverse iterator
+ auto rend() const { return impl_.slice.rend(); }
+
+ /// Equality operator
+ /// @param other the other vector
+ /// @returns true if this vector is the same length as `other`, and all elements are equal.
+ template <typename T2, size_t N2>
+ bool operator==(const Vector<T2, N2>& other) const {
+ const size_t len = Length();
+ if (len != other.Length()) {
+ return false;
+ }
+ for (size_t i = 0; i < len; i++) {
+ if ((*this)[i] != other[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /// Inequality operator
+ /// @param other the other vector
+ /// @returns true if this vector is not the same length as `other`, or all elements are not
+ /// equal.
+ template <typename T2, size_t N2>
+ bool operator!=(const Vector<T2, N2>& other) const {
+ return !(*this == other);
+ }
+
+ /// @returns the internal slice of the vector
+ utils::Slice<T> Slice() { return impl_.slice; }
+
+ /// @returns the internal slice of the vector
+ utils::Slice<const T> Slice() const { return impl_.slice; }
+
+ private:
+ /// Friend class (differing specializations of this class)
+ template <typename, size_t>
+ friend class Vector;
+
+ /// Friend class
+ template <typename>
+ friend class VectorRef;
+
+ /// Friend class
+ template <typename>
+ friend class VectorRef;
+
+ template <typename... Ts>
+ void AppendVariadic(Ts&&... args) {
+ ((new (&impl_.slice.data[impl_.slice.len++]) T(std::forward<Ts>(args))), ...);
+ }
+
+ /// Expands the capacity of the vector
+ void Grow() { Reserve(std::max(impl_.slice.cap, static_cast<size_t>(1)) * 2); }
+
+ /// Moves 'other' to this vector, if possible, otherwise performs a copy.
+ void MoveOrCopy(VectorRef<T>&& other) {
+ if (other.can_move_) {
+ ClearAndFree();
+ impl_.slice = other.slice_;
+ other.slice_ = {};
+ } else {
+ Copy(other.slice_);
+ }
+ }
+
+ /// Copies all the elements from `other` to this vector, replacing the content of this vector.
+ /// @param other the
+ void Copy(const utils::Slice<T>& other) {
+ if (impl_.slice.cap < other.len) {
+ ClearAndFree();
+ impl_.Allocate(other.len);
+ } else {
+ Clear();
+ }
+
+ impl_.slice.len = other.len;
+ for (size_t i = 0; i < impl_.slice.len; i++) {
+ new (&impl_.slice.data[i]) T{other.data[i]};
+ }
+ }
+
+ /// Clears the vector, then frees the slice data.
+ void ClearAndFree() {
+ Clear();
+ impl_.Free(impl_.slice.data);
+ }
+
+ /// True if this vector uses a small array for small object optimization.
+ constexpr static bool HasSmallArray = N > 0;
+
+ /// A structure that has the same size and alignment as T.
+ /// Replacement for std::aligned_storage as this is broken on earlier versions of MSVC.
+ struct alignas(alignof(T)) TStorage {
+ /// @returns the storage reinterpreted as a T*
+ T* Get() { return Bitcast<T*>(&data[0]); }
+ /// @returns the storage reinterpreted as a T*
+ const T* Get() const { return Bitcast<const T*>(&data[0]); }
+ /// Byte array of length sizeof(T)
+ uint8_t data[sizeof(T)];
+ };
+
+ /// The internal structure for the vector with a small array.
+ struct ImplWithSmallArray {
+ TStorage small_arr[N];
+ utils::Slice<T> slice = {small_arr[0].Get(), 0, N};
+
+ /// Allocates a new vector of `T` either from #small_arr, or from the heap, then assigns the
+ /// pointer it to #slice.data, and updates #slice.cap.
+ void Allocate(size_t new_cap) {
+ if (new_cap < N) {
+ slice.data = small_arr[0].Get();
+ slice.cap = N;
+ } else {
+ slice.data = Bitcast<T*>(new TStorage[new_cap]);
+ slice.cap = new_cap;
+ }
+ }
+
+ /// Frees `data`, if not nullptr and isn't a pointer to #small_arr
+ void Free(T* data) const {
+ if (data && data != small_arr[0].Get()) {
+ delete[] Bitcast<TStorage*>(data);
+ }
+ }
+
+ /// Indicates whether the slice structure can be std::move()d.
+ /// @returns true if #slice.data does not point to #small_arr
+ bool CanMove() const { return slice.data != small_arr[0].Get(); }
+ };
+
+ /// The internal structure for the vector without a small array.
+ struct ImplWithoutSmallArray {
+ utils::Slice<T> slice = Empty;
+
+ /// Allocates a new vector of `T` and assigns it to #slice.data, and updates #slice.cap.
+ void Allocate(size_t new_cap) {
+ slice.data = Bitcast<T*>(new TStorage[new_cap]);
+ slice.cap = new_cap;
+ }
+
+ /// Frees `data`, if not nullptr.
+ void Free(T* data) const {
+ if (data) {
+ delete[] Bitcast<TStorage*>(data);
+ }
+ }
+
+ /// Indicates whether the slice structure can be std::move()d.
+ /// @returns true
+ bool CanMove() const { return true; }
+ };
+
+ /// Either a ImplWithSmallArray or ImplWithoutSmallArray based on N.
+ std::conditional_t<HasSmallArray, ImplWithSmallArray, ImplWithoutSmallArray> impl_;
+};
+
+namespace detail {
+
+/// Helper for determining the Vector element type (`T`) from the vector's constuctor arguments
+/// @tparam IS_CASTABLE true if the types of `Ts` derive from CastableBase
+/// @tparam Ts the vector constructor argument types to infer the vector element type from.
+template <bool IS_CASTABLE, typename... Ts>
+struct VectorCommonType;
+
+/// VectorCommonType specialization for non-castable types.
+template <typename... Ts>
+struct VectorCommonType</*IS_CASTABLE*/ false, Ts...> {
+ /// The common T type to use for the vector
+ using type = std::common_type_t<Ts...>;
+};
+
+/// VectorCommonType specialization for castable types.
+template <typename... Ts>
+struct VectorCommonType</*IS_CASTABLE*/ true, Ts...> {
+ /// The common Castable type (excluding pointer)
+ using common_ty = CastableCommonBase<std::remove_pointer_t<Ts>...>;
+ /// The common T type to use for the vector
+ using type = std::conditional_t<(std::is_const_v<std::remove_pointer_t<Ts>> || ...),
+ const common_ty*,
+ common_ty*>;
+};
+
+} // namespace detail
+
+/// Helper for determining the Vector element type (`T`) from the vector's constuctor arguments
+template <typename... Ts>
+using VectorCommonType =
+ typename utils::detail::VectorCommonType<IsCastable<std::remove_pointer_t<Ts>...>, Ts...>::type;
+
+/// Deduction guide for Vector
+template <typename... Ts>
+Vector(Ts...) -> Vector<VectorCommonType<Ts...>, sizeof...(Ts)>;
+
+/// VectorRef is a weak reference to a Vector, used to pass vectors as parameters, avoiding copies
+/// between the caller and the callee, or as an non-static sized accessor on a vector. VectorRef can
+/// accept a Vector of any 'N' value, decoupling the caller's vector internal size from the callee's
+/// vector size. A VectorRef tracks the usage of moves either side of the call. If at the call site,
+/// a Vector argument is moved to a VectorRef parameter, and within the callee, the VectorRef
+/// parameter is moved to a Vector, then the Vector heap allocation will be moved. For example:
+///
+/// ```
+/// void func_a() {
+/// Vector<std::string, 4> vec;
+/// // logic to populate 'vec'.
+/// func_b(std::move(vec)); // Constructs a VectorRef tracking the move here.
+/// }
+///
+/// void func_b(VectorRef<std::string> vec_ref) {
+/// // A move was made when calling func_b, so the vector can be moved instead of copied.
+/// Vector<std::string, 2> vec(std::move(vec_ref));
+/// }
+/// ```
+///
+/// Aside from this move pattern, a VectorRef provides an immutable reference to the Vector.
+template <typename T>
+class VectorRef {
+ /// @returns an empty slice.
+ static utils::Slice<T>& EmptySlice() {
+ static utils::Slice<T> empty;
+ return empty;
+ }
+
+ public:
+ /// Type of `T`.
+ using value_type = T;
+
+ /// Constructor - empty reference
+ VectorRef() : slice_(EmptySlice()) {}
+
+ /// Constructor
+ VectorRef(EmptyType) : slice_(EmptySlice()) {} // NOLINT(runtime/explicit)
+
+ /// Constructor from a Slice
+ /// @param slice the slice
+ VectorRef(utils::Slice<T>& slice) // NOLINT(runtime/explicit)
+ : slice_(slice) {}
+
+ /// Constructor from a Vector
+ /// @param vector the vector to create a reference of
+ template <size_t N>
+ VectorRef(Vector<T, N>& vector) // NOLINT(runtime/explicit)
+ : slice_(vector.impl_.slice) {}
+
+ /// Constructor from a const Vector
+ /// @param vector the vector to create a reference of
+ template <size_t N>
+ VectorRef(const Vector<T, N>& vector) // NOLINT(runtime/explicit)
+ : slice_(const_cast<utils::Slice<T>&>(vector.impl_.slice)) {}
+
+ /// Constructor from a moved Vector
+ /// @param vector the vector being moved
+ template <size_t N>
+ VectorRef(Vector<T, N>&& vector) // NOLINT(runtime/explicit)
+ : slice_(vector.impl_.slice), can_move_(vector.impl_.CanMove()) {}
+
+ /// Copy constructor
+ /// @param other the vector reference
+ VectorRef(const VectorRef& other) : slice_(other.slice_) {}
+
+ /// Move constructor
+ /// @param other the vector reference
+ VectorRef(VectorRef&& other) = default;
+
+ /// Copy constructor with covariance / const conversion
+ /// @param other the other vector reference
+ template <typename U,
+ typename = std::enable_if_t<CanReinterpretSlice<ReinterpretMode::kSafe, T, U>>>
+ VectorRef(const VectorRef<U>& other) // NOLINT(runtime/explicit)
+ : slice_(other.slice_.template Reinterpret<T>()) {}
+
+ /// Move constructor with covariance / const conversion
+ /// @param other the vector reference
+ template <typename U,
+ typename = std::enable_if_t<CanReinterpretSlice<ReinterpretMode::kSafe, T, U>>>
+ VectorRef(VectorRef<U>&& other) // NOLINT(runtime/explicit)
+ : slice_(other.slice_.template Reinterpret<T>()), can_move_(other.can_move_) {}
+
+ /// Constructor from a Vector with covariance / const conversion
+ /// @param vector the vector to create a reference of
+ /// @see CanReinterpretSlice for rules about conversion
+ template <typename U,
+ size_t N,
+ typename = std::enable_if_t<CanReinterpretSlice<ReinterpretMode::kSafe, T, U>>>
+ VectorRef(Vector<U, N>& vector) // NOLINT(runtime/explicit)
+ : slice_(vector.impl_.slice.template Reinterpret<T>()) {}
+
+ /// Constructor from a moved Vector with covariance / const conversion
+ /// @param vector the vector to create a reference of
+ /// @see CanReinterpretSlice for rules about conversion
+ template <typename U,
+ size_t N,
+ typename = std::enable_if_t<CanReinterpretSlice<ReinterpretMode::kSafe, T, U>>>
+ VectorRef(Vector<U, N>&& vector) // NOLINT(runtime/explicit)
+ : slice_(vector.impl_.slice.template Reinterpret<T>()), can_move_(vector.impl_.CanMove()) {}
+
+ /// Index operator
+ /// @param i the element index. Must be less than `len`.
+ /// @returns a reference to the i'th element.
+ const T& operator[](size_t i) const { return slice_[i]; }
+
+ /// @return the number of elements in the vector
+ size_t Length() const { return slice_.len; }
+
+ /// @return the number of elements that the vector could hold before a heap allocation needs to
+ /// be made
+ size_t Capacity() const { return slice_.cap; }
+
+ /// @return a reinterpretation of this VectorRef as elements of type U.
+ /// @note this is doing a reinterpret_cast of elements. It is up to the caller to ensure that
+ /// this is a safe operation.
+ template <typename U>
+ VectorRef<U> ReinterpretCast() const {
+ return {slice_.template Reinterpret<U, ReinterpretMode::kUnsafe>()};
+ }
+
+ /// @returns the internal slice of the vector
+ utils::Slice<T> Slice() { return slice_; }
+
+ /// @returns true if the vector is empty.
+ bool IsEmpty() const { return slice_.len == 0; }
+
+ /// @returns a reference to the first element in the vector
+ const T& Front() const { return slice_.Front(); }
+
+ /// @returns a reference to the last element in the vector
+ const T& Back() const { return slice_.Back(); }
+
+ /// @returns a pointer to the first element in the vector
+ const T* begin() const { return slice_.begin(); }
+
+ /// @returns a pointer to one past the last element in the vector
+ const T* end() const { return slice_.end(); }
+
+ /// @returns a reverse iterator starting with the last element in the vector
+ auto rbegin() const { return slice_.rbegin(); }
+
+ /// @returns the end for a reverse iterator
+ auto rend() const { return slice_.rend(); }
+
+ private:
+ /// Friend class
+ template <typename, size_t>
+ friend class Vector;
+
+ /// Friend class
+ template <typename>
+ friend class VectorRef;
+
+ /// Friend class
+ template <typename>
+ friend class VectorRef;
+
+ /// The slice of the vector being referenced.
+ utils::Slice<T>& slice_;
+ /// Whether the slice data is passed by r-value reference, and can be moved.
+ bool can_move_ = false;
+};
+
+/// Helper for converting a Vector to a std::vector.
+/// @note This helper exists to help code migration. Avoid if possible.
+template <typename T, size_t N>
+std::vector<T> ToStdVector(const Vector<T, N>& vector) {
+ std::vector<T> out;
+ out.reserve(vector.Length());
+ for (auto& el : vector) {
+ out.emplace_back(el);
+ }
+ return out;
+}
+
+/// Helper for converting a std::vector to a Vector.
+/// @note This helper exists to help code migration. Avoid if possible.
+template <typename T, size_t N = 0>
+Vector<T, N> ToVector(const std::vector<T>& vector) {
+ Vector<T, N> out;
+ out.Reserve(vector.size());
+ for (auto& el : vector) {
+ out.Push(el);
+ }
+ return out;
+}
+
+/// Prints the vector @p vec to @p o
+/// @param o the stream to write to
+/// @param vec the vector
+/// @return the stream so calls can be chained
+template <typename T, size_t N>
+inline StringStream& operator<<(StringStream& o, const Vector<T, N>& vec) {
+ o << "[";
+ bool first = true;
+ for (auto& el : vec) {
+ if (!first) {
+ o << ", ";
+ }
+ first = false;
+ o << el;
+ }
+ o << "]";
+ return o;
+}
+
+/// Prints the vector @p vec to @p o
+/// @param o the stream to write to
+/// @param vec the vector reference
+/// @return the stream so calls can be chained
+template <typename T>
+inline StringStream& operator<<(StringStream& o, VectorRef<T> vec) {
+ o << "[";
+ bool first = true;
+ for (auto& el : vec) {
+ if (!first) {
+ o << ", ";
+ }
+ first = false;
+ o << el;
+ }
+ o << "]";
+ return o;
+}
+
+namespace detail {
+
+/// IsVectorLike<T>::value is true if T is a utils::Vector or utils::VectorRef.
+template <typename T>
+struct IsVectorLike {
+ /// Non-specialized form of IsVectorLike defaults to false
+ static constexpr bool value = false;
+};
+
+/// IsVectorLike specialization for utils::Vector
+template <typename T, size_t N>
+struct IsVectorLike<utils::Vector<T, N>> {
+ /// True for the IsVectorLike specialization of utils::Vector
+ static constexpr bool value = true;
+};
+
+/// IsVectorLike specialization for utils::VectorRef
+template <typename T>
+struct IsVectorLike<utils::VectorRef<T>> {
+ /// True for the IsVectorLike specialization of utils::VectorRef
+ static constexpr bool value = true;
+};
+} // namespace detail
+
+/// True if T is a Vector<T, N> or VectorRef<T>
+template <typename T>
+static constexpr bool IsVectorLike = utils::detail::IsVectorLike<T>::value;
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_CONTAINERS_VECTOR_H_
diff --git a/src/tint/utils/containers/vector_test.cc b/src/tint/utils/containers/vector_test.cc
new file mode 100644
index 0000000..0883eab
--- /dev/null
+++ b/src/tint/utils/containers/vector_test.cc
@@ -0,0 +1,2278 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/containers/vector.h"
+
+#include <string>
+#include <tuple>
+
+#include "gmock/gmock.h"
+
+#include "src/tint/utils/containers/predicates.h"
+#include "src/tint/utils/memory/bitcast.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::utils {
+namespace {
+
+class C0 : public Castable<C0> {};
+class C1 : public Castable<C1, C0> {};
+class C2a : public Castable<C2a, C1> {};
+class C2b : public Castable<C2b, C1> {};
+
+/// @returns true if the address of el is within the memory of the vector vec.
+template <typename T, size_t N, typename E>
+bool IsInternal(Vector<T, N>& vec, E& el) {
+ auto ptr = Bitcast<uintptr_t>(&el);
+ auto base = Bitcast<uintptr_t>(&vec);
+ return ptr >= base && ptr < base + sizeof(vec);
+}
+
+/// @returns true if all elements of the vector `vec` are held within the memory of `vec`.
+template <typename T, size_t N>
+bool AllInternallyHeld(Vector<T, N>& vec) {
+ for (auto& el : vec) {
+ if (!IsInternal(vec, el)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/// @returns true if all elements of the vector `vec` are held outside the memory of `vec`.
+template <typename T, size_t N>
+bool AllExternallyHeld(Vector<T, N>& vec) {
+ for (auto& el : vec) {
+ if (IsInternal(vec, el)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Static asserts
+////////////////////////////////////////////////////////////////////////////////
+static_assert(std::is_same_v<VectorCommonType<int>, int>);
+static_assert(std::is_same_v<VectorCommonType<int, int>, int>);
+static_assert(std::is_same_v<VectorCommonType<int, float>, float>);
+
+static_assert(std::is_same_v<VectorCommonType<C0*>, C0*>);
+static_assert(std::is_same_v<VectorCommonType<const C0*>, const C0*>);
+
+static_assert(std::is_same_v<VectorCommonType<C0*, C1*>, C0*>);
+static_assert(std::is_same_v<VectorCommonType<const C0*, C1*>, const C0*>);
+static_assert(std::is_same_v<VectorCommonType<C0*, const C1*>, const C0*>);
+static_assert(std::is_same_v<VectorCommonType<const C0*, const C1*>, const C0*>);
+
+static_assert(std::is_same_v<VectorCommonType<C2a*, C2b*>, C1*>);
+static_assert(std::is_same_v<VectorCommonType<const C2a*, C2b*>, const C1*>);
+static_assert(std::is_same_v<VectorCommonType<C2a*, const C2b*>, const C1*>);
+static_assert(std::is_same_v<VectorCommonType<const C2a*, const C2b*>, const C1*>);
+
+static_assert(IsVectorLike<Vector<int, 3>>);
+static_assert(IsVectorLike<VectorRef<int>>);
+static_assert(!IsVectorLike<int>);
+
+////////////////////////////////////////////////////////////////////////////////
+// TintVectorTest
+////////////////////////////////////////////////////////////////////////////////
+TEST(TintVectorTest, SmallArray_Empty) {
+ Vector<int, 2> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+}
+
+TEST(TintVectorTest, NoSmallArray) {
+ Vector<int, 0> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 0u);
+}
+
+TEST(TintVectorTest, Empty_SmallArray_Empty) {
+ Vector<int, 2> vec(Empty);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+}
+
+TEST(TintVectorTest, Empty_NoSmallArray) {
+ Vector<int, 0> vec(Empty);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 0u);
+}
+
+TEST(TintVectorTest, InitializerList_NoSpill) {
+ Vector<std::string, 2> vec{"one", "two"};
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "one");
+ EXPECT_EQ(vec[1], "two");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InitializerList_WithSpill) {
+ Vector<std::string, 2> vec{"one", "two", "three"};
+ EXPECT_EQ(vec.Length(), 3u);
+ EXPECT_EQ(vec.Capacity(), 3u);
+ EXPECT_EQ(vec[0], "one");
+ EXPECT_EQ(vec[1], "two");
+ EXPECT_EQ(vec[2], "three");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InitializerList_NoSmallArray) {
+ Vector<std::string, 0> vec{"one", "two"};
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "one");
+ EXPECT_EQ(vec[1], "two");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, Push_NoSmallArray) {
+ Vector<std::string, 0> vec;
+ vec.Push("one");
+ vec.Push("two");
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "one");
+ EXPECT_EQ(vec[1], "two");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, Insert) {
+ Vector<std::string, 3> vec;
+ EXPECT_THAT(vec, testing::ElementsAre());
+
+ vec.Insert(0, "six");
+ EXPECT_THAT(vec, testing::ElementsAre("six"));
+
+ vec.Insert(0, "three");
+ EXPECT_THAT(vec, testing::ElementsAre("three", "six"));
+
+ vec.Insert(1, "five");
+ EXPECT_THAT(vec, testing::ElementsAre("three", "five", "six"));
+
+ vec.Insert(0, "two");
+ EXPECT_THAT(vec, testing::ElementsAre("two", "three", "five", "six"));
+
+ vec.Insert(2, "four");
+ EXPECT_THAT(vec, testing::ElementsAre("two", "three", "four", "five", "six"));
+
+ vec.Insert(0, "one");
+ EXPECT_THAT(vec, testing::ElementsAre("one", "two", "three", "four", "five", "six"));
+}
+
+TEST(TintVectorTest, Erase_Front) {
+ Vector<std::string, 3> vec;
+ vec.Push("one");
+ vec.Push("two");
+ vec.Push("three");
+ vec.Push("four");
+ EXPECT_EQ(vec.Length(), 4u);
+
+ vec.Erase(0);
+ EXPECT_EQ(vec.Length(), 3u);
+ EXPECT_EQ(vec[0], "two");
+ EXPECT_EQ(vec[1], "three");
+ EXPECT_EQ(vec[2], "four");
+
+ vec.Erase(0, 1);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec[0], "three");
+ EXPECT_EQ(vec[1], "four");
+
+ vec.Erase(0, 2);
+ EXPECT_EQ(vec.Length(), 0u);
+}
+
+TEST(TintVectorTest, Erase_Mid) {
+ Vector<std::string, 5> vec;
+ vec.Push("one");
+ vec.Push("two");
+ vec.Push("three");
+ vec.Push("four");
+ vec.Push("five");
+ EXPECT_EQ(vec.Length(), 5u);
+
+ vec.Erase(1);
+ EXPECT_EQ(vec.Length(), 4u);
+ EXPECT_EQ(vec[0], "one");
+ EXPECT_EQ(vec[1], "three");
+ EXPECT_EQ(vec[2], "four");
+ EXPECT_EQ(vec[3], "five");
+
+ vec.Erase(1, 2);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec[0], "one");
+ EXPECT_EQ(vec[1], "five");
+}
+
+TEST(TintVectorTest, Erase_Back) {
+ Vector<std::string, 3> vec;
+ vec.Push("one");
+ vec.Push("two");
+ vec.Push("three");
+ vec.Push("four");
+ EXPECT_EQ(vec.Length(), 4u);
+
+ vec.Erase(3);
+ EXPECT_EQ(vec.Length(), 3u);
+ EXPECT_EQ(vec[0], "one");
+ EXPECT_EQ(vec[1], "two");
+ EXPECT_EQ(vec[2], "three");
+
+ vec.Erase(1, 2);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec[0], "one");
+}
+
+TEST(TintVectorTest, InferTN_1CString) {
+ auto vec = Vector{"one"};
+ static_assert(std::is_same_v<decltype(vec)::value_type, const char*>);
+ static_assert(decltype(vec)::static_length == 1u);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ EXPECT_STREQ(vec[0], "one");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_2CStrings) {
+ auto vec = Vector{"one", "two"};
+ static_assert(std::is_same_v<decltype(vec)::value_type, const char*>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_STREQ(vec[0], "one");
+ EXPECT_STREQ(vec[1], "two");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_IntFloat) {
+ auto vec = Vector{1, 2.0f};
+ static_assert(std::is_same_v<decltype(vec)::value_type, float>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], 1.0f);
+ EXPECT_EQ(vec[1], 2.0f);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_IntDoubleIntDouble) {
+ auto vec = Vector{1, 2.0, 3, 4.0};
+ static_assert(std::is_same_v<decltype(vec)::value_type, double>);
+ static_assert(decltype(vec)::static_length == 4u);
+ EXPECT_EQ(vec.Length(), 4u);
+ EXPECT_EQ(vec.Capacity(), 4u);
+ EXPECT_EQ(vec[0], 1.0);
+ EXPECT_EQ(vec[1], 2.0);
+ EXPECT_EQ(vec[2], 3.0);
+ EXPECT_EQ(vec[3], 4.0);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_C0) {
+ C0 c0;
+ auto vec = Vector{&c0};
+ static_assert(std::is_same_v<decltype(vec)::value_type, C0*>);
+ static_assert(decltype(vec)::static_length == 1u);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ EXPECT_EQ(vec[0], &c0);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_ConstC0) {
+ const C0 c0;
+ auto vec = Vector{&c0};
+ static_assert(std::is_same_v<decltype(vec)::value_type, const C0*>);
+ static_assert(decltype(vec)::static_length == 1u);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ EXPECT_EQ(vec[0], &c0);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_C0C1) {
+ C0 c0;
+ C1 c1;
+ auto vec = Vector{&c0, &c1};
+ static_assert(std::is_same_v<decltype(vec)::value_type, C0*>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], &c0);
+ EXPECT_EQ(vec[1], &c1);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_ConstC0C1) {
+ const C0 c0;
+ C1 c1;
+ auto vec = Vector{&c0, &c1};
+ static_assert(std::is_same_v<decltype(vec)::value_type, const C0*>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], &c0);
+ EXPECT_EQ(vec[1], &c1);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_C0ConstC1) {
+ C0 c0;
+ const C1 c1;
+ auto vec = Vector{&c0, &c1};
+ static_assert(std::is_same_v<decltype(vec)::value_type, const C0*>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], &c0);
+ EXPECT_EQ(vec[1], &c1);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_ConstC0ConstC1) {
+ const C0 c0;
+ const C1 c1;
+ auto vec = Vector{&c0, &c1};
+ static_assert(std::is_same_v<decltype(vec)::value_type, const C0*>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], &c0);
+ EXPECT_EQ(vec[1], &c1);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_C2aC2b) {
+ C2a c2a;
+ C2b c2b;
+ auto vec = Vector{&c2a, &c2b};
+ static_assert(std::is_same_v<decltype(vec)::value_type, C1*>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], &c2a);
+ EXPECT_EQ(vec[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_ConstC2aC2b) {
+ const C2a c2a;
+ C2b c2b;
+ auto vec = Vector{&c2a, &c2b};
+ static_assert(std::is_same_v<decltype(vec)::value_type, const C1*>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], &c2a);
+ EXPECT_EQ(vec[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_C2aConstC2b) {
+ C2a c2a;
+ const C2b c2b;
+ auto vec = Vector{&c2a, &c2b};
+ static_assert(std::is_same_v<decltype(vec)::value_type, const C1*>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], &c2a);
+ EXPECT_EQ(vec[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, InferTN_ConstC2aConstC2b) {
+ const C2a c2a;
+ const C2b c2b;
+ auto vec = Vector{&c2a, &c2b};
+ static_assert(std::is_same_v<decltype(vec)::value_type, const C1*>);
+ static_assert(decltype(vec)::static_length == 2u);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], &c2a);
+ EXPECT_EQ(vec[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, CopyVector_NoSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 2> vec_b(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyVector_WithSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 2> vec_b(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyVector_NoSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 1> vec_b(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyVector_WithSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 1> vec_b(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyVector_NoSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 3> vec_b(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyVector_WithSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 3> vec_b(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyVector_NoMoveUpcast_NoSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 2> vec_a{&c2a, &c2b};
+ Vector<C0*, 2> vec_b(vec_a); // No move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorTest, CopyVector_NoMoveUpcast_WithSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ Vector<C0*, 2> vec_b(vec_a); // No move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorTest, CopyVector_NoMoveAddConst_NoSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 2> vec_a{&c2a, &c2b};
+ Vector<const C1*, 2> vec_b(vec_a); // No move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorTest, CopyVector_NoMoveAddConst_WithSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ Vector<const C1*, 2> vec_b(vec_a); // No move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorTest, CopyVector_NoMoveUpcastAndAddConst_NoSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 2> vec_a{&c2a, &c2b};
+ Vector<const C0*, 2> vec_b(vec_a); // No move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorTest, CopyVector_NoMoveUpcastAndAddConst_WithSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ Vector<const C0*, 2> vec_b(vec_a); // No move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorTest, MoveVector_NoSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 2> vec_b(std::move(vec_a));
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveVector_WithSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 2> vec_b(std::move(vec_a));
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveVector_NoSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 1> vec_b(std::move(vec_a));
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveVector_WithSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 1> vec_b(std::move(vec_a));
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveVector_NoSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 3> vec_b(std::move(vec_a));
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveVector_WithSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 3> vec_b(std::move(vec_a));
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveVector_Upcast_NoSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 2> vec_a{&c2a, &c2b};
+ Vector<C0*, 2> vec_b(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorTest, MoveVector_Upcast_WithSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ Vector<C0*, 2> vec_b(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorTest, MoveVector_AddConst_NoSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 2> vec_a{&c2a, &c2b};
+ Vector<const C1*, 2> vec_b(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorTest, MoveVector_AddConst_WithSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ Vector<const C1*, 2> vec_b(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorTest, MoveVector_UpcastAndAddConst_NoSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 2> vec_a{&c2a, &c2b};
+ Vector<const C0*, 2> vec_b(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorTest, MoveVector_UpcastAndAddConst_WithSpill) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ Vector<const C0*, 2> vec_b(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorTest, CopyAssign_NoSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 2> vec_b;
+ vec_b = vec_a;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssign_WithSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 2> vec_b;
+ vec_b = vec_a;
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssign_NoSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 1> vec_b;
+ vec_b = vec_a;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssign_WithSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 1> vec_b;
+ vec_b = vec_a;
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssign_NoSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 3> vec_b;
+ vec_b = vec_a;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssign_WithSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 3> vec_b;
+ vec_b = vec_a;
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssign_NoSpill_N2_to_N0) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 0> vec_b;
+ vec_b = vec_a;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssign_WithSpill_N2_to_N0) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 0> vec_b;
+ vec_b = vec_a;
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssign_Self_NoSpill) {
+ Vector<std::string, 2> vec{"hello", "world"};
+ auto* vec_ptr = &vec; // Used to avoid -Wself-assign-overloaded
+ vec = *vec_ptr;
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, CopyAssign_Self_WithSpill) {
+ Vector<std::string, 1> vec{"hello", "world"};
+ auto* vec_ptr = &vec; // Used to avoid -Wself-assign-overloaded
+ vec = *vec_ptr;
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, MoveAssign_NoSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 2> vec_b;
+ vec_b = std::move(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssign_WithSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 2> vec_b;
+ vec_b = std::move(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssign_NoSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 1> vec_b;
+ vec_b = std::move(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssign_SpillSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 1> vec_b;
+ vec_b = std::move(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssign_NoSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 3> vec_b;
+ vec_b = std::move(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssign_WithSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 3> vec_b;
+ vec_b = std::move(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssign_NoSpill_N2_to_N0) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 0> vec_b;
+ vec_b = std::move(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssign_WithSpill_N2_to_N0) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 0> vec_b;
+ vec_b = std::move(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssign_Self_NoSpill) {
+ Vector<std::string, 2> vec{"hello", "world"};
+ auto* vec_ptr = &vec; // Used to avoid -Wself-move
+ vec = std::move(*vec_ptr);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, MoveAssign_Self_WithSpill) {
+ Vector<std::string, 1> vec{"hello", "world"};
+ auto* vec_ptr = &vec; // Used to avoid -Wself-move
+ vec = std::move(*vec_ptr);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, RepeatMoveAssign_NoSpill) {
+ Vector<std::string, 3> vec_a{"hello", "world"};
+ Vector<std::string, 3> vec_b{"Ciao", "mondo"};
+ Vector<std::string, 3> vec_c{"Bonjour", "le", "monde"};
+ Vector<std::string, 3> vec;
+ vec = std::move(vec_a);
+ vec = std::move(vec_b);
+ vec = std::move(vec_c);
+ EXPECT_EQ(vec.Length(), 3u);
+ EXPECT_EQ(vec.Capacity(), 3u);
+ EXPECT_EQ(vec[0], "Bonjour");
+ EXPECT_EQ(vec[1], "le");
+ EXPECT_EQ(vec[2], "monde");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, RepeatMoveAssign_WithSpill) {
+ Vector<std::string, 1> vec_a{"hello", "world"};
+ Vector<std::string, 1> vec_b{"Ciao", "mondo"};
+ Vector<std::string, 1> vec_c{"bonjour", "le", "monde"};
+ Vector<std::string, 1> vec;
+ vec = std::move(vec_a);
+ vec = std::move(vec_b);
+ vec = std::move(vec_c);
+ EXPECT_EQ(vec.Length(), 3u);
+ EXPECT_EQ(vec.Capacity(), 3u);
+ EXPECT_EQ(vec[0], "bonjour");
+ EXPECT_EQ(vec[1], "le");
+ EXPECT_EQ(vec[2], "monde");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 2> vec_b;
+ vec_b = ref;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 2> vec_b;
+ vec_b = ref;
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 1> vec_b;
+ vec_b = ref;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 1> vec_b;
+ vec_b = ref;
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 3> vec_b;
+ vec_b = ref;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 3> vec_b;
+ vec_b = ref;
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_NoSpill_N2_to_N0) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 0> vec_b;
+ vec_b = ref;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_WithSpill_N2_to_N0) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 0> vec_b;
+ vec_b = ref;
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignRef_Self_NoSpill) {
+ Vector<std::string, 2> vec{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec)};
+ vec = ref;
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, CopyAssignRef_Self_WithSpill) {
+ Vector<std::string, 1> vec{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec)};
+ vec = ref;
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 2> vec_b;
+ vec_b = std::move(ref);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_WithSpill_N2_to_N2) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 2> vec_b;
+ vec_b = std::move(ref);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 1> vec_b;
+ vec_b = std::move(ref);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_SpillSpill_N2_to_N1) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 1> vec_b;
+ vec_b = std::move(ref);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 3> vec_b;
+ vec_b = std::move(ref);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_WithSpill_N2_to_N3) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 3> vec_b;
+ vec_b = std::move(ref);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_NoSpill_N2_to_N0) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 0> vec_b;
+ vec_b = std::move(ref);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_WithSpill_N2_to_N0) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ VectorRef<std::string> ref{std::move(vec_a)};
+ Vector<std::string, 0> vec_b;
+ vec_b = std::move(ref);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, MoveAssignRef_Self_NoSpill) {
+ Vector<std::string, 2> vec{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec)};
+ vec = std::move(ref);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, MoveAssignRef_Self_WithSpill) {
+ Vector<std::string, 1> vec{"hello", "world"};
+ VectorRef<std::string> ref{std::move(vec)};
+ vec = std::move(ref);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, RepeatMoveAssignRef_NoSpill) {
+ Vector<std::string, 3> vec_a{"hello", "world"};
+ Vector<std::string, 3> vec_b{"Ciao", "mondo"};
+ Vector<std::string, 3> vec_c{"Bonjour", "le", "monde"};
+ VectorRef<std::string> ref_a{std::move(vec_a)};
+ VectorRef<std::string> ref_b{std::move(vec_b)};
+ VectorRef<std::string> ref_c{std::move(vec_c)};
+ Vector<std::string, 3> vec;
+ vec = std::move(ref_a);
+ vec = std::move(ref_b);
+ vec = std::move(ref_c);
+ EXPECT_EQ(vec.Length(), 3u);
+ EXPECT_EQ(vec.Capacity(), 3u);
+ EXPECT_EQ(vec[0], "Bonjour");
+ EXPECT_EQ(vec[1], "le");
+ EXPECT_EQ(vec[2], "monde");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, RepeatMoveAssignRef_WithSpill) {
+ Vector<std::string, 1> vec_a{"hello", "world"};
+ Vector<std::string, 1> vec_b{"Ciao", "mondo"};
+ Vector<std::string, 1> vec_c{"bonjour", "le", "monde"};
+ VectorRef<std::string> ref_a{std::move(vec_a)};
+ VectorRef<std::string> ref_b{std::move(vec_b)};
+ VectorRef<std::string> ref_c{std::move(vec_c)};
+ Vector<std::string, 1> vec;
+ vec = std::move(ref_a);
+ vec = std::move(ref_b);
+ vec = std::move(ref_c);
+ EXPECT_EQ(vec.Length(), 3u);
+ EXPECT_EQ(vec.Capacity(), 3u);
+ EXPECT_EQ(vec[0], "bonjour");
+ EXPECT_EQ(vec[1], "le");
+ EXPECT_EQ(vec[2], "monde");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, CopyAssignSlice_N2_to_N2) {
+ std::string data[] = {"hello", "world"};
+ Slice<std::string> slice(data);
+ Vector<std::string, 2> vec_b;
+ vec_b = slice;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignSlice_N2_to_N1) {
+ std::string data[] = {"hello", "world"};
+ Slice<std::string> slice(data);
+ Vector<std::string, 1> vec_b;
+ vec_b = slice;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignSlice_N2_to_N3) {
+ std::string data[] = {"hello", "world"};
+ Slice<std::string> slice(data);
+ Vector<std::string, 3> vec_b;
+ vec_b = slice;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, CopyAssignSlice_N2_to_N0) {
+ std::string data[] = {"hello", "world"};
+ Slice<std::string> slice(data);
+ Vector<std::string, 0> vec_b;
+ vec_b = slice;
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Index) {
+ Vector<std::string, 2> vec{"hello", "world"};
+ static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec[0])>>);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+}
+
+TEST(TintVectorTest, ConstIndex) {
+ const Vector<std::string, 2> vec{"hello", "world"};
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(vec[0])>>);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "world");
+}
+
+TEST(TintVectorTest, Reserve_NoSpill) {
+ Vector<std::string, 2> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ vec.Reserve(1);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ vec.Reserve(2);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ vec.Push("hello");
+ vec.Push("world");
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+ vec.Reserve(1);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, Reserve_WithSpill) {
+ Vector<std::string, 1> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ vec.Reserve(1);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ vec.Reserve(2);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ vec.Push("hello");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec.Push("world");
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec.Reserve(1);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, ResizeZero_NoSpill) {
+ Vector<std::string, 2> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ vec.Resize(1);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+ vec[0] = "hello";
+ vec.Resize(2);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+ vec[1] = "world";
+ vec.Resize(1);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+ vec.Resize(2);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, ResizeZero_WithSpill) {
+ Vector<std::string, 1> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ vec.Resize(1);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ EXPECT_EQ(vec[0], "");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+ vec[0] = "hello";
+ vec.Resize(2);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec[1] = "world";
+ vec.Resize(1);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec.Resize(2);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, ResizeValue_NoSpill) {
+ Vector<std::string, 2> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ vec.Resize(1, "meow");
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "meow");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+ vec[0] = "hello";
+ vec.Resize(2, "woof");
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "woof");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+ vec[1] = "world";
+ vec.Resize(1, "quack");
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+ vec.Resize(2, "hiss");
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "hiss");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, ResizeValue_WithSpill) {
+ Vector<std::string, 1> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ vec.Resize(1, "meow");
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ EXPECT_EQ(vec[0], "meow");
+ EXPECT_TRUE(AllInternallyHeld(vec));
+ vec[0] = "hello";
+ vec.Resize(2, "woof");
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "woof");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec[1] = "world";
+ vec.Resize(1, "quack");
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec.Resize(2, "hiss");
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "hiss");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, Reserve_NoSmallArray) {
+ Vector<std::string, 0> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 0u);
+ vec.Reserve(1);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ vec.Reserve(2);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ vec.Push("hello");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec.Push("world");
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec.Reserve(1);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, Resize_NoSmallArray) {
+ Vector<std::string, 0> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 0u);
+ vec.Resize(1);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 1u);
+ EXPECT_EQ(vec[0], "");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec[0] = "hello";
+ vec.Resize(2);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec[1] = "world";
+ vec.Resize(1);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+ vec.Resize(2);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+ EXPECT_EQ(vec[0], "hello");
+ EXPECT_EQ(vec[1], "");
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, Copy_NoSpill_N2_to_N2_Empty) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 2> vec_b;
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_NoSpill_N2_to_N2_NonEmpty) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 2> vec_b{"hallo", "wereld"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_NoSpill_N2_to_N2_Spill) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 2> vec_b{"hallo", "wereld", "spill"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_WithSpill_N2_to_N2_Empty) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 2> vec_b;
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_WithSpill_N2_to_N2_NonEmpty) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 2> vec_b{"hallo", "wereld"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_WithSpill_N2_to_N2_Spill) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 2> vec_b{"hallo", "wereld", "morsen"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_NoSpill_N2_to_N1_Empty) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 1> vec_b;
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_NoSpill_N2_to_N1_NonEmpty) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 1> vec_b{"hallo"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_NoSpill_N2_to_N1_Spill) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 1> vec_b{"hallo", "morsen"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 2u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_WithSpill_N2_to_N1_Empty) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 1> vec_b;
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_WithSpill_N2_to_N1_NonEmpty) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 1> vec_b{"hallo"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_WithSpill_N2_to_N1_Spill) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 1> vec_b{"hallo", "wereld"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_NoSpill_N2_to_N3_Empty) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 3> vec_b;
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_NoSpill_N2_to_N3_NonEmpty) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 3> vec_b{"hallo", "fijne", "wereld"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_NoSpill_N2_to_N3_Spill) {
+ Vector<std::string, 2> vec_a{"hello", "world"};
+ Vector<std::string, 3> vec_b{"hallo", "fijne", "wereld", "morsen"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 2u);
+ EXPECT_EQ(vec_b.Capacity(), 4u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_WithSpill_N2_to_N3_Empty) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 3> vec_b;
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_WithSpill_N2_to_N3_NonEmpty) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 3> vec_b{"hallo", "fijne", "wereld"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 3u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllInternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Copy_WithSpill_N2_to_N3_Spill) {
+ Vector<std::string, 2> vec_a{"hello", "world", "spill"};
+ Vector<std::string, 3> vec_b{"hallo", "fijne", "wereld", "morsen"};
+ vec_b.Copy(vec_a);
+ EXPECT_EQ(vec_b.Length(), 3u);
+ EXPECT_EQ(vec_b.Capacity(), 4u);
+ EXPECT_EQ(vec_b[0], "hello");
+ EXPECT_EQ(vec_b[1], "world");
+ EXPECT_EQ(vec_b[2], "spill");
+ EXPECT_TRUE(AllExternallyHeld(vec_b));
+}
+
+TEST(TintVectorTest, Clear_Empty) {
+ Vector<std::string, 2> vec;
+ vec.Clear();
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+}
+
+TEST(TintVectorTest, Clear_NoSpill) {
+ Vector<std::string, 2> vec{"hello", "world"};
+ vec.Clear();
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 2u);
+}
+
+TEST(TintVectorTest, Clear_WithSpill) {
+ Vector<std::string, 2> vec{"hello", "world", "spill"};
+ vec.Clear();
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_EQ(vec.Capacity(), 3u);
+}
+
+TEST(TintVectorTest, PushPop_StringNoSpill) {
+ const std::string hello = "hello";
+ const std::string world = "world";
+
+ Vector<std::string, 2> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Push(hello);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Push(world);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), world);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), hello);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, PushPop_StringWithSpill) {
+ const std::string hello = "hello";
+ const std::string world = "world";
+
+ Vector<std::string, 1> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Push(hello);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Push(world);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), world);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), hello);
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, PushPop_StringMoveNoSpill) {
+ std::string hello = "hello";
+ std::string world = "world";
+
+ Vector<std::string, 2> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Push(std::move(hello));
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Push(std::move(world));
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), "world");
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), "hello");
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, PushPop_StringMoveWithSpill) {
+ std::string hello = "hello";
+ std::string world = "world";
+
+ Vector<std::string, 1> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Push(std::move(hello));
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Push(std::move(world));
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), "world");
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), "hello");
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, EmplacePop_TupleVarArgNoSpill) {
+ Vector<std::tuple<int, float, bool>, 2> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Emplace(1, 2.0, false);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Emplace(3, 4.0, true);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), std::make_tuple(3, 4.0, true));
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), std::make_tuple(1, 2.0, false));
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+}
+
+TEST(TintVectorTest, EmplacePop_TupleVarArgWithSpill) {
+ Vector<std::tuple<int, float, bool>, 1> vec;
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Emplace(1, 2.0, false);
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllInternallyHeld(vec));
+
+ vec.Emplace(3, 4.0, true);
+ EXPECT_EQ(vec.Length(), 2u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), std::make_tuple(3, 4.0, true));
+ EXPECT_EQ(vec.Length(), 1u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+
+ EXPECT_EQ(vec.Pop(), std::make_tuple(1, 2.0, false));
+ EXPECT_EQ(vec.Length(), 0u);
+ EXPECT_TRUE(AllExternallyHeld(vec));
+}
+
+TEST(TintVectorTest, IsEmpty) {
+ Vector<std::string, 1> vec;
+ EXPECT_TRUE(vec.IsEmpty());
+ vec.Push("one");
+ EXPECT_FALSE(vec.IsEmpty());
+ vec.Pop();
+ EXPECT_TRUE(vec.IsEmpty());
+}
+
+TEST(TintVectorTest, FrontBack_NoSpill) {
+ Vector<std::string, 3> vec{"front", "mid", "back"};
+ static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec.Front())>>);
+ static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec.Back())>>);
+ EXPECT_EQ(vec.Front(), "front");
+ EXPECT_EQ(vec.Back(), "back");
+}
+
+TEST(TintVectorTest, FrontBack_WithSpill) {
+ Vector<std::string, 2> vec{"front", "mid", "back"};
+ static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec.Front())>>);
+ static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec.Back())>>);
+ EXPECT_EQ(vec.Front(), "front");
+ EXPECT_EQ(vec.Back(), "back");
+}
+
+TEST(TintVectorTest, ConstFrontBack_NoSpill) {
+ const Vector<std::string, 3> vec{"front", "mid", "back"};
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(vec.Front())>>);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(vec.Back())>>);
+ EXPECT_EQ(vec.Front(), "front");
+ EXPECT_EQ(vec.Back(), "back");
+}
+
+TEST(TintVectorTest, ConstFrontBack_WithSpill) {
+ const Vector<std::string, 2> vec{"front", "mid", "back"};
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(vec.Front())>>);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(vec.Back())>>);
+ EXPECT_EQ(vec.Front(), "front");
+ EXPECT_EQ(vec.Back(), "back");
+}
+
+TEST(TintVectorTest, BeginEnd_NoSpill) {
+ Vector<std::string, 3> vec{"front", "mid", "back"};
+ static_assert(!std::is_const_v<std::remove_reference_t<decltype(*vec.begin())>>);
+ static_assert(!std::is_const_v<std::remove_reference_t<decltype(*vec.end())>>);
+ EXPECT_EQ(vec.begin(), &vec[0]);
+ EXPECT_EQ(vec.end(), &vec[0] + 3);
+}
+
+TEST(TintVectorTest, BeginEnd_WithSpill) {
+ Vector<std::string, 2> vec{"front", "mid", "back"};
+ static_assert(!std::is_const_v<std::remove_reference_t<decltype(*vec.begin())>>);
+ static_assert(!std::is_const_v<std::remove_reference_t<decltype(*vec.end())>>);
+ EXPECT_EQ(vec.begin(), &vec[0]);
+ EXPECT_EQ(vec.end(), &vec[0] + 3);
+}
+
+TEST(TintVectorTest, ConstBeginEnd_NoSpill) {
+ const Vector<std::string, 3> vec{"front", "mid", "back"};
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec.begin())>>);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec.end())>>);
+ EXPECT_EQ(vec.begin(), &vec[0]);
+ EXPECT_EQ(vec.end(), &vec[0] + 3);
+}
+
+TEST(TintVectorTest, ConstBeginEnd_WithSpill) {
+ const Vector<std::string, 2> vec{"front", "mid", "back"};
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec.begin())>>);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec.end())>>);
+ EXPECT_EQ(vec.begin(), &vec[0]);
+ EXPECT_EQ(vec.end(), &vec[0] + 3);
+}
+
+TEST(TintVectorTest, Equality) {
+ EXPECT_EQ((Vector<int, 2>{1, 2}), (Vector<int, 2>{1, 2}));
+ EXPECT_EQ((Vector<int, 1>{1, 2}), (Vector<int, 3>{1, 2}));
+ EXPECT_NE((Vector{1, 2}), (Vector{1}));
+ EXPECT_NE((Vector{1}), (Vector{1, 2}));
+ EXPECT_NE((Vector{1, 2}), (Vector{2, 1}));
+ EXPECT_NE((Vector{2, 1}), (Vector{1, 2}));
+}
+
+TEST(TintVectorTest, Sort) {
+ Vector vec{1, 5, 3, 4, 2};
+ vec.Sort();
+ EXPECT_THAT(vec, testing::ElementsAre(1, 2, 3, 4, 5));
+}
+
+TEST(TintVectorTest, ReverseOdd) {
+ Vector vec{1, 5, 3, 4, 2};
+ vec.Reverse();
+ EXPECT_THAT(vec, testing::ElementsAre(2, 4, 3, 5, 1));
+}
+
+TEST(TintVectorTest, ReverseEven) {
+ Vector vec{1, 5, 3, 4, 2, 9};
+ vec.Reverse();
+ EXPECT_THAT(vec, testing::ElementsAre(9, 2, 4, 3, 5, 1));
+}
+
+TEST(TintVectorTest, Any) {
+ Vector vec{1, 7, 5, 9};
+ EXPECT_TRUE(vec.Any(Eq(1)));
+ EXPECT_FALSE(vec.Any(Eq(2)));
+ EXPECT_FALSE(vec.Any(Eq(3)));
+ EXPECT_FALSE(vec.Any(Eq(4)));
+ EXPECT_TRUE(vec.Any(Eq(5)));
+ EXPECT_FALSE(vec.Any(Eq(6)));
+ EXPECT_TRUE(vec.Any(Eq(7)));
+ EXPECT_FALSE(vec.Any(Eq(8)));
+ EXPECT_TRUE(vec.Any(Eq(9)));
+}
+
+TEST(TintVectorTest, All) {
+ Vector vec{1, 7, 5, 9};
+ EXPECT_FALSE(vec.All(Ne(1)));
+ EXPECT_TRUE(vec.All(Ne(2)));
+ EXPECT_TRUE(vec.All(Ne(3)));
+ EXPECT_TRUE(vec.All(Ne(4)));
+ EXPECT_FALSE(vec.All(Ne(5)));
+ EXPECT_TRUE(vec.All(Ne(6)));
+ EXPECT_FALSE(vec.All(Ne(7)));
+ EXPECT_TRUE(vec.All(Ne(8)));
+ EXPECT_FALSE(vec.All(Ne(9)));
+}
+
+TEST(TintVectorTest, Slice) {
+ Vector<std::string, 3> vec{"hello", "world"};
+ auto slice = vec.Slice();
+ static_assert(std::is_same_v<decltype(slice), Slice<std::string>>);
+ EXPECT_EQ(slice.data, &vec[0]);
+ EXPECT_EQ(slice.len, 2u);
+ EXPECT_EQ(slice.cap, 3u);
+}
+
+TEST(TintVectorTest, SliceConst) {
+ const Vector<std::string, 3> vec{"hello", "world"};
+ auto slice = vec.Slice();
+ static_assert(std::is_same_v<decltype(slice), Slice<const std::string>>);
+ EXPECT_EQ(slice.data, &vec[0]);
+ EXPECT_EQ(slice.len, 2u);
+ EXPECT_EQ(slice.cap, 3u);
+}
+
+TEST(TintVectorTest, ostream) {
+ utils::StringStream ss;
+ ss << Vector{1, 2, 3};
+ EXPECT_EQ(ss.str(), "[1, 2, 3]");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TintVectorRefTest
+////////////////////////////////////////////////////////////////////////////////
+TEST(TintVectorRefTest, CopyVectorRef) {
+ Vector<std::string, 1> vec_a{"one", "two"};
+ VectorRef<std::string> vec_ref_a(std::move(vec_a));
+ VectorRef<std::string> vec_ref_b(vec_ref_a); // No move
+ Vector<std::string, 2> vec_b(std::move(vec_ref_b));
+ EXPECT_EQ(vec_b[0], "one");
+ EXPECT_EQ(vec_b[1], "two");
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorRefTest, CopyVectorRef_Upcast) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<C1*> vec_ref_a(std::move(vec_a));
+ VectorRef<C0*> vec_ref_b(vec_ref_a); // No-move. Up-cast
+ Vector<C0*, 2> vec_b(std::move(vec_ref_b));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorRefTest, CopyVectorRef_AddConst) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<C1*> vec_ref_a(std::move(vec_a));
+ VectorRef<const C1*> vec_ref_b(vec_ref_a); // No-move. Up-cast
+ Vector<const C1*, 2> vec_b(std::move(vec_ref_b));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorRefTest, CopyVectorRef_UpcastAndAddConst) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<C1*> vec_ref_a(std::move(vec_a));
+ VectorRef<const C0*> vec_ref_b(vec_ref_a); // No-move. Up-cast
+ Vector<const C0*, 2> vec_b(std::move(vec_ref_b));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorRefTest, MoveVectorRef) {
+ Vector<std::string, 1> vec_a{"one", "two"};
+ VectorRef<std::string> vec_ref_a(std::move(vec_a)); // Move
+ VectorRef<std::string> vec_ref_b(std::move(vec_ref_a));
+ Vector<std::string, 2> vec_b(std::move(vec_ref_b));
+ EXPECT_EQ(vec_b[0], "one");
+ EXPECT_EQ(vec_b[1], "two");
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorRefTest, MoveVectorRef_Upcast) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<C1*> vec_ref_a(std::move(vec_a));
+ VectorRef<C0*> vec_ref_b(std::move(vec_ref_a)); // Moved. Up-cast
+ Vector<C0*, 2> vec_b(std::move(vec_ref_b));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorRefTest, MoveVectorRef_AddConst) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<C1*> vec_ref_a(std::move(vec_a));
+ VectorRef<const C1*> vec_ref_b(std::move(vec_ref_a)); // Moved. Up-cast
+ Vector<const C1*, 2> vec_b(std::move(vec_ref_b));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorRefTest, MoveVectorRef_UpcastAndAddConst) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<C1*> vec_ref_a(std::move(vec_a));
+ VectorRef<const C0*> vec_ref_b(std::move(vec_ref_a)); // Moved. Up-cast
+ Vector<const C0*, 2> vec_b(std::move(vec_ref_b));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorRefTest, CopyVector) {
+ Vector<std::string, 1> vec_a{"one", "two"};
+ VectorRef<std::string> vec_ref(vec_a); // No move
+ Vector<std::string, 2> vec_b(std::move(vec_ref));
+ EXPECT_EQ(vec_b[0], "one");
+ EXPECT_EQ(vec_b[1], "two");
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorRefTest, CopyVector_Upcast) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<C0*> vec_ref(vec_a); // No move
+ EXPECT_EQ(vec_ref[0], &c2a);
+ EXPECT_EQ(vec_ref[1], &c2b);
+ Vector<C0*, 2> vec_b(std::move(vec_ref));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorRefTest, CopyVector_AddConst) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<const C1*> vec_ref(vec_a); // No move
+ EXPECT_EQ(vec_ref[0], &c2a);
+ EXPECT_EQ(vec_ref[1], &c2b);
+ Vector<const C1*, 2> vec_b(std::move(vec_ref));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorRefTest, CopyVector_UpcastAndAddConst) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<const C0*> vec_ref(vec_a); // No move
+ EXPECT_EQ(vec_ref[0], &c2a);
+ EXPECT_EQ(vec_ref[1], &c2b);
+ Vector<const C0*, 2> vec_b(std::move(vec_ref));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
+}
+
+TEST(TintVectorRefTest, MoveVector) {
+ Vector<std::string, 1> vec_a{"one", "two"};
+ VectorRef<std::string> vec_ref(std::move(vec_a)); // Move
+ Vector<std::string, 2> vec_b(std::move(vec_ref));
+ EXPECT_EQ(vec_b[0], "one");
+ EXPECT_EQ(vec_b[1], "two");
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorRefTest, MoveVector_Upcast) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<C0*> vec_ref(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_ref[0], &c2a);
+ EXPECT_EQ(vec_ref[1], &c2b);
+ Vector<C0*, 2> vec_b(std::move(vec_ref));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorRefTest, MoveVector_AddConst) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<const C1*> vec_ref(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_ref[0], &c2a);
+ EXPECT_EQ(vec_ref[1], &c2b);
+ Vector<const C1*, 2> vec_b(std::move(vec_ref));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorRefTest, MoveVector_UpcastAndAddConst) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C1*, 1> vec_a{&c2a, &c2b};
+ VectorRef<const C0*> vec_ref(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_ref[0], &c2a);
+ EXPECT_EQ(vec_ref[1], &c2b);
+ Vector<const C0*, 2> vec_b(std::move(vec_ref));
+ EXPECT_EQ(vec_b[0], &c2a);
+ EXPECT_EQ(vec_b[1], &c2b);
+ EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
+}
+
+TEST(TintVectorRefTest, MoveVector_ReinterpretCast) {
+ C2a c2a;
+ C2b c2b;
+ Vector<C0*, 1> vec_a{&c2a, &c2b};
+ VectorRef<const C0*> vec_ref(std::move(vec_a)); // Move
+ EXPECT_EQ(vec_ref[0], &c2a);
+ EXPECT_EQ(vec_ref[1], &c2b);
+ VectorRef<const C1*> reinterpret = vec_ref.ReinterpretCast<const C1*>();
+ EXPECT_EQ(reinterpret[0], &c2a);
+ EXPECT_EQ(reinterpret[1], &c2b);
+}
+
+TEST(TintVectorRefTest, Index) {
+ Vector<std::string, 2> vec{"one", "two"};
+ VectorRef<std::string> vec_ref(vec);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
+ EXPECT_EQ(vec_ref[0], "one");
+ EXPECT_EQ(vec_ref[1], "two");
+}
+
+TEST(TintVectorRefTest, SortPredicate) {
+ Vector vec{1, 5, 3, 4, 2};
+ vec.Sort([](int a, int b) { return b < a; });
+ EXPECT_THAT(vec, testing::ElementsAre(5, 4, 3, 2, 1));
+}
+
+TEST(TintVectorRefTest, ConstIndex) {
+ Vector<std::string, 2> vec{"one", "two"};
+ const VectorRef<std::string> vec_ref(vec);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
+ EXPECT_EQ(vec_ref[0], "one");
+ EXPECT_EQ(vec_ref[1], "two");
+}
+
+TEST(TintVectorRefTest, Length) {
+ Vector<std::string, 2> vec{"one", "two", "three"};
+ VectorRef<std::string> vec_ref(vec);
+ EXPECT_EQ(vec_ref.Length(), 3u);
+}
+
+TEST(TintVectorRefTest, Capacity) {
+ Vector<std::string, 5> vec{"one", "two", "three"};
+ VectorRef<std::string> vec_ref(vec);
+ EXPECT_EQ(vec_ref.Capacity(), 5u);
+}
+
+TEST(TintVectorRefTest, IsEmpty) {
+ Vector<std::string, 1> vec;
+ VectorRef<std::string> vec_ref(vec);
+ EXPECT_TRUE(vec_ref.IsEmpty());
+ vec.Push("one");
+ EXPECT_FALSE(vec_ref.IsEmpty());
+ vec.Pop();
+ EXPECT_TRUE(vec_ref.IsEmpty());
+}
+
+TEST(TintVectorRefTest, FrontBack) {
+ Vector<std::string, 3> vec{"front", "mid", "back"};
+ const VectorRef<std::string> vec_ref(vec);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Front())>>);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref.Back())>>);
+ EXPECT_EQ(vec_ref.Front(), "front");
+ EXPECT_EQ(vec_ref.Back(), "back");
+}
+
+TEST(TintVectorRefTest, BeginEnd) {
+ Vector<std::string, 3> vec{"front", "mid", "back"};
+ const VectorRef<std::string> vec_ref(vec);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.begin())>>);
+ static_assert(std::is_const_v<std::remove_reference_t<decltype(*vec_ref.end())>>);
+ EXPECT_EQ(vec_ref.begin(), &vec[0]);
+ EXPECT_EQ(vec_ref.end(), &vec[0] + 3);
+}
+
+TEST(TintVectorRefTest, ostream) {
+ utils::StringStream ss;
+ Vector vec{1, 2, 3};
+ const VectorRef<int> vec_ref(vec);
+ ss << vec_ref;
+ EXPECT_EQ(ss.str(), "[1, 2, 3]");
+}
+
+} // namespace
+} // namespace tint::utils
+
+TINT_INSTANTIATE_TYPEINFO(tint::utils::C0);
+TINT_INSTANTIATE_TYPEINFO(tint::utils::C1);
+TINT_INSTANTIATE_TYPEINFO(tint::utils::C2a);
+TINT_INSTANTIATE_TYPEINFO(tint::utils::C2b);
diff --git a/src/tint/utils/debug/debug.cc b/src/tint/utils/debug/debug.cc
new file mode 100644
index 0000000..c5c07f1
--- /dev/null
+++ b/src/tint/utils/debug/debug.cc
@@ -0,0 +1,50 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/debug/debug.h"
+
+#include <memory>
+
+#include "src/tint/utils/debug/debugger.h"
+
+namespace tint {
+namespace {
+
+InternalCompilerErrorReporter* ice_reporter = nullptr;
+
+} // namespace
+
+void SetInternalCompilerErrorReporter(InternalCompilerErrorReporter* reporter) {
+ ice_reporter = reporter;
+}
+
+InternalCompilerError::InternalCompilerError(const char* file,
+ size_t line,
+ diag::System system,
+ diag::List& diagnostics)
+ : file_(file), line_(line), system_(system), diagnostics_(diagnostics) {}
+
+InternalCompilerError::~InternalCompilerError() {
+ auto file = std::make_shared<Source::File>(file_, "");
+ Source source{Source::Range{{line_}}, file.get()};
+ diagnostics_.add_ice(system_, msg_.str(), source, std::move(file));
+
+ if (ice_reporter) {
+ ice_reporter(diagnostics_);
+ }
+
+ debugger::Break();
+}
+
+} // namespace tint
diff --git a/src/tint/utils/debug/debug.h b/src/tint/utils/debug/debug.h
new file mode 100644
index 0000000..4cf4257
--- /dev/null
+++ b/src/tint/utils/debug/debug.h
@@ -0,0 +1,156 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_DEBUG_DEBUG_H_
+#define SRC_TINT_UTILS_DEBUG_DEBUG_H_
+
+#include <utility>
+
+#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/diagnostic/formatter.h"
+#include "src/tint/utils/diagnostic/printer.h"
+#include "src/tint/utils/macros/compiler.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint {
+
+/// Function type used for registering an internal compiler error reporter
+using InternalCompilerErrorReporter = void(const diag::List&);
+
+/// Sets the global error reporter to be called in case of internal compiler
+/// errors.
+/// @param reporter the error reporter
+void SetInternalCompilerErrorReporter(InternalCompilerErrorReporter* reporter);
+
+/// InternalCompilerError is a helper for reporting internal compiler errors.
+/// Construct the InternalCompilerError with the source location of the ICE
+/// fault and append any error details with the `<<` operator.
+/// When the InternalCompilerError is destructed, the concatenated error message
+/// is appended to the diagnostics list with the severity of
+/// tint::diag::Severity::InternalCompilerError, and if a
+/// InternalCompilerErrorReporter is set, then it is called with the diagnostic
+/// list.
+class InternalCompilerError {
+ public:
+ /// Constructor
+ /// @param file the file containing the ICE
+ /// @param line the line containing the ICE
+ /// @param system the Tint system that has raised the ICE
+ /// @param diagnostics the list of diagnostics to append the ICE message to
+ InternalCompilerError(const char* file,
+ size_t line,
+ diag::System system,
+ diag::List& diagnostics);
+
+ /// Destructor.
+ /// Adds the internal compiler error message to the diagnostics list, and then
+ /// calls the InternalCompilerErrorReporter if one is set.
+ ~InternalCompilerError();
+
+ /// Appends `arg` to the ICE message.
+ /// @param arg the argument to append to the ICE message
+ /// @returns this object so calls can be chained
+ template <typename T>
+ InternalCompilerError& operator<<(T&& arg) {
+ msg_ << std::forward<T>(arg);
+ return *this;
+ }
+
+ private:
+ char const* const file_;
+ const size_t line_;
+ diag::System system_;
+ diag::List& diagnostics_;
+ utils::StringStream msg_;
+};
+
+} // namespace tint
+
+/// TINT_ICE() is a macro for appending an internal compiler error message
+/// to the diagnostics list `diagnostics`, and calling the
+/// InternalCompilerErrorReporter with the full diagnostic list if a reporter is
+/// set.
+/// The ICE message contains the callsite's file and line.
+/// Use the `<<` operator to append an error message to the ICE.
+#define TINT_ICE(system, diagnostics) \
+ tint::InternalCompilerError(__FILE__, __LINE__, ::tint::diag::System::system, diagnostics)
+
+/// TINT_UNREACHABLE() is a macro for appending a "TINT_UNREACHABLE"
+/// internal compiler error message to the diagnostics list `diagnostics`, and
+/// calling the InternalCompilerErrorReporter with the full diagnostic list if a
+/// reporter is set.
+/// The ICE message contains the callsite's file and line.
+/// Use the `<<` operator to append an error message to the ICE.
+#define TINT_UNREACHABLE(system, diagnostics) TINT_ICE(system, diagnostics) << "TINT_UNREACHABLE "
+
+/// TINT_UNIMPLEMENTED() is a macro for appending a "TINT_UNIMPLEMENTED"
+/// internal compiler error message to the diagnostics list `diagnostics`, and
+/// calling the InternalCompilerErrorReporter with the full diagnostic list if a
+/// reporter is set.
+/// The ICE message contains the callsite's file and line.
+/// Use the `<<` operator to append an error message to the ICE.
+#define TINT_UNIMPLEMENTED(system, diagnostics) \
+ TINT_ICE(system, diagnostics) << "TINT_UNIMPLEMENTED "
+
+/// TINT_ASSERT() is a macro for checking the expression is true, triggering a
+/// TINT_ICE if it is not.
+/// The ICE message contains the callsite's file and line.
+/// @warning: Unlike TINT_ICE() and TINT_UNREACHABLE(), TINT_ASSERT() does not
+/// append a message to an existing tint::diag::List. As such, TINT_ASSERT()
+/// may silently fail in builds where SetInternalCompilerErrorReporter() is not
+/// called. Only use in places where there's no sensible place to put proper
+/// error handling.
+#define TINT_ASSERT(system, condition) \
+ do { \
+ if (TINT_UNLIKELY(!(condition))) { \
+ tint::diag::List diagnostics; \
+ TINT_ICE(system, diagnostics) << "TINT_ASSERT(" #system ", " #condition ")"; \
+ } \
+ } while (false)
+
+/// TINT_ASSERT_OR_RETURN() is a macro for checking the expression is true, triggering a
+/// TINT_ICE if it is not and returning from the calling function.
+/// The ICE message contains the callsite's file and line.
+/// @warning: Unlike TINT_ICE() and TINT_UNREACHABLE(), TINT_ASSERT_OR_RETURN() does not
+/// append a message to an existing tint::diag::List. As such, TINT_ASSERT_OR_RETURN()
+/// may silently fail in builds where SetInternalCompilerErrorReporter() is not
+/// called. Only use in places where there's no sensible place to put proper
+/// error handling.
+#define TINT_ASSERT_OR_RETURN(system, condition) \
+ do { \
+ if (TINT_UNLIKELY(!(condition))) { \
+ tint::diag::List diagnostics; \
+ TINT_ICE(system, diagnostics) << "TINT_ASSERT(" #system ", " #condition ")"; \
+ return; \
+ } \
+ } while (false)
+
+/// TINT_ASSERT_OR_RETURN_VALUE() is a macro for checking the expression is true, triggering a
+/// TINT_ICE if it is not and returning a value from the calling function.
+/// The ICE message contains the callsite's file and line.
+/// @warning: Unlike TINT_ICE() and TINT_UNREACHABLE(), TINT_ASSERT_OR_RETURN_VALUE() does not
+/// append a message to an existing tint::diag::List. As such, TINT_ASSERT_OR_RETURN_VALUE()
+/// may silently fail in builds where SetInternalCompilerErrorReporter() is not
+/// called. Only use in places where there's no sensible place to put proper
+/// error handling.
+#define TINT_ASSERT_OR_RETURN_VALUE(system, condition, value) \
+ do { \
+ if (TINT_UNLIKELY(!(condition))) { \
+ tint::diag::List diagnostics; \
+ TINT_ICE(system, diagnostics) << "TINT_ASSERT(" #system ", " #condition ")"; \
+ return value; \
+ } \
+ } while (false)
+
+#endif // SRC_TINT_UTILS_DEBUG_DEBUG_H_
diff --git a/src/tint/utils/debug/debug_test.cc b/src/tint/utils/debug/debug_test.cc
new file mode 100644
index 0000000..3566427
--- /dev/null
+++ b/src/tint/utils/debug/debug_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/debug/debug.h"
+
+#include "gtest/gtest-spi.h"
+
+namespace tint {
+namespace {
+
+TEST(DebugTest, Unreachable) {
+ EXPECT_FATAL_FAILURE(
+ {
+ diag::List diagnostics;
+ TINT_UNREACHABLE(Test, diagnostics);
+ },
+ "internal compiler error");
+}
+
+TEST(DebugTest, AssertTrue) {
+ TINT_ASSERT(Test, true);
+}
+
+TEST(DebugTest, AssertFalse) {
+ EXPECT_FATAL_FAILURE({ TINT_ASSERT(Test, false); }, "internal compiler error");
+}
+
+} // namespace
+} // namespace tint
diff --git a/src/tint/utils/debug/debugger.cc b/src/tint/utils/debug/debugger.cc
new file mode 100644
index 0000000..0cb6b59
--- /dev/null
+++ b/src/tint/utils/debug/debugger.cc
@@ -0,0 +1,62 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/debug/debugger.h"
+
+#ifdef TINT_ENABLE_BREAK_IN_DEBUGGER
+
+#ifdef _MSC_VER
+#include <Windows.h>
+#elif defined(__linux__)
+#include <signal.h>
+#include <fstream>
+#include <string>
+#endif
+
+#ifdef _MSC_VER
+#define TINT_DEBUGGER_BREAK_DEFINED
+void tint::debugger::Break() {
+ if (::IsDebuggerPresent()) {
+ ::DebugBreak();
+ }
+}
+
+#elif defined(__linux__)
+
+#define TINT_DEBUGGER_BREAK_DEFINED
+void tint::debugger::Break() {
+ // A process is being traced (debugged) if "/proc/self/status" contains a
+ // line with "TracerPid: <non-zero-digit>...".
+ bool is_traced = false;
+ std::ifstream fin("/proc/self/status");
+ std::string line;
+ while (!is_traced && std::getline(fin, line)) {
+ const char kPrefix[] = "TracerPid:\t";
+ static constexpr int kPrefixLen = sizeof(kPrefix) - 1;
+ if (line.length() > kPrefixLen && line.compare(0, kPrefixLen, kPrefix) == 0) {
+ is_traced = line[kPrefixLen] != '0';
+ }
+ }
+
+ if (is_traced) {
+ raise(SIGTRAP);
+ }
+}
+#endif // platform
+
+#endif // TINT_ENABLE_BREAK_IN_DEBUGGER
+
+#ifndef TINT_DEBUGGER_BREAK_DEFINED
+void tint::debugger::Break() {}
+#endif
diff --git a/src/tint/utils/debug/debugger.h b/src/tint/utils/debug/debugger.h
new file mode 100644
index 0000000..2558678
--- /dev/null
+++ b/src/tint/utils/debug/debugger.h
@@ -0,0 +1,27 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_DEBUG_DEBUGGER_H_
+#define SRC_TINT_UTILS_DEBUG_DEBUGGER_H_
+
+namespace tint::debugger {
+
+/// If debugger is attached and the `TINT_ENABLE_BREAK_IN_DEBUGGER` preprocessor
+/// macro is defined for `debugger.cc`, calling `Break()` will cause the
+/// debugger to break at the call site.
+void Break();
+
+} // namespace tint::debugger
+
+#endif // SRC_TINT_UTILS_DEBUG_DEBUGGER_H_
diff --git a/src/tint/utils/diagnostic/diagnostic.cc b/src/tint/utils/diagnostic/diagnostic.cc
new file mode 100644
index 0000000..f108c44
--- /dev/null
+++ b/src/tint/utils/diagnostic/diagnostic.cc
@@ -0,0 +1,51 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/diagnostic/diagnostic.h"
+
+#include <unordered_map>
+
+#include "src/tint/utils/diagnostic/formatter.h"
+
+namespace tint::diag {
+
+Diagnostic::Diagnostic() = default;
+Diagnostic::Diagnostic(const Diagnostic&) = default;
+Diagnostic::~Diagnostic() = default;
+Diagnostic& Diagnostic::operator=(const Diagnostic&) = default;
+
+List::List() = default;
+List::List(std::initializer_list<Diagnostic> list) : entries_(list) {}
+List::List(const List& rhs) = default;
+
+List::List(List&& rhs) = default;
+
+List::~List() = default;
+
+List& List::operator=(const List& rhs) = default;
+
+List& List::operator=(List&& rhs) = default;
+
+std::string List::str() const {
+ diag::Formatter::Style style;
+ style.print_newline_at_end = false;
+ return Formatter{style}.format(*this);
+}
+
+std::ostream& operator<<(std::ostream& out, const List& list) {
+ out << list.str();
+ return out;
+}
+
+} // namespace tint::diag
diff --git a/src/tint/utils/diagnostic/diagnostic.h b/src/tint/utils/diagnostic/diagnostic.h
new file mode 100644
index 0000000..0eb7727
--- /dev/null
+++ b/src/tint/utils/diagnostic/diagnostic.h
@@ -0,0 +1,261 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_DIAGNOSTIC_DIAGNOSTIC_H_
+#define SRC_TINT_UTILS_DIAGNOSTIC_DIAGNOSTIC_H_
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "src/tint/utils/diagnostic/source.h"
+
+namespace tint::diag {
+
+/// Severity is an enumerator of diagnostic severities.
+enum class Severity { Note, Warning, Error, InternalCompilerError, Fatal };
+
+/// @return true iff `a` is more than, or of equal severity to `b`
+inline bool operator>=(Severity a, Severity b) {
+ return static_cast<int>(a) >= static_cast<int>(b);
+}
+
+/// System is an enumerator of Tint systems that can be the originator of a
+/// diagnostic message.
+enum class System {
+ AST,
+ Builtin,
+ Clone,
+ Constant,
+ Inspector,
+ IR,
+ Program,
+ ProgramBuilder,
+ Reader,
+ Resolver,
+ Semantic,
+ Symbol,
+ Test,
+ Transform,
+ Type,
+ Utils,
+ Writer,
+};
+
+/// Diagnostic holds all the information for a single compiler diagnostic
+/// message.
+class Diagnostic {
+ public:
+ /// Constructor
+ Diagnostic();
+ /// Copy constructor
+ Diagnostic(const Diagnostic&);
+ /// Destructor
+ ~Diagnostic();
+
+ /// Copy assignment operator
+ /// @return this diagnostic
+ Diagnostic& operator=(const Diagnostic&);
+
+ /// severity is the severity of the diagnostic message.
+ Severity severity = Severity::Error;
+ /// source is the location of the diagnostic.
+ Source source;
+ /// message is the text associated with the diagnostic.
+ std::string message;
+ /// system is the Tint system that raised the diagnostic.
+ System system;
+ /// code is the error code, for example a validation error might have the code
+ /// `"v-0001"`.
+ const char* code = nullptr;
+ /// A shared pointer to a Source::File. Only used if the diagnostic Source
+ /// points to a file that was created specifically for this diagnostic
+ /// (usually an ICE).
+ std::shared_ptr<Source::File> owned_file = nullptr;
+};
+
+/// List is a container of Diagnostic messages.
+class List {
+ public:
+ /// iterator is the type used for range based iteration.
+ using iterator = std::vector<Diagnostic>::const_iterator;
+
+ /// Constructs the list with no elements.
+ List();
+
+ /// Copy constructor. Copies the diagnostics from `list` into this list.
+ /// @param list the list of diagnostics to copy into this list.
+ List(std::initializer_list<Diagnostic> list);
+
+ /// Copy constructor. Copies the diagnostics from `list` into this list.
+ /// @param list the list of diagnostics to copy into this list.
+ List(const List& list);
+
+ /// Move constructor. Moves the diagnostics from `list` into this list.
+ /// @param list the list of diagnostics to move into this list.
+ List(List&& list);
+
+ /// Destructor
+ ~List();
+
+ /// Assignment operator. Copies the diagnostics from `list` into this list.
+ /// @param list the list to copy into this list.
+ /// @return this list.
+ List& operator=(const List& list);
+
+ /// Assignment move operator. Moves the diagnostics from `list` into this
+ /// list.
+ /// @param list the list to move into this list.
+ /// @return this list.
+ List& operator=(List&& list);
+
+ /// adds a diagnostic to the end of this list.
+ /// @param diag the diagnostic to append to this list.
+ void add(Diagnostic&& diag) {
+ if (diag.severity >= Severity::Error) {
+ error_count_++;
+ }
+ entries_.emplace_back(std::move(diag));
+ }
+
+ /// adds a list of diagnostics to the end of this list.
+ /// @param list the diagnostic to append to this list.
+ void add(const List& list) {
+ for (auto diag : list) {
+ add(std::move(diag));
+ }
+ }
+
+ /// adds the note message with the given Source to the end of this list.
+ /// @param system the system raising the note message
+ /// @param note_msg the note message
+ /// @param source the source of the note diagnostic
+ void add_note(System system, std::string_view note_msg, const Source& source) {
+ diag::Diagnostic note{};
+ note.severity = diag::Severity::Note;
+ note.system = system;
+ note.source = source;
+ note.message = note_msg;
+ add(std::move(note));
+ }
+
+ /// adds the warning message with the given Source to the end of this list.
+ /// @param system the system raising the warning message
+ /// @param warning_msg the warning message
+ /// @param source the source of the warning diagnostic
+ void add_warning(System system, std::string_view warning_msg, const Source& source) {
+ diag::Diagnostic warning{};
+ warning.severity = diag::Severity::Warning;
+ warning.system = system;
+ warning.source = source;
+ warning.message = warning_msg;
+ add(std::move(warning));
+ }
+
+ /// adds the error message without a source to the end of this list.
+ /// @param system the system raising the error message
+ /// @param err_msg the error message
+ void add_error(System system, std::string_view err_msg) {
+ diag::Diagnostic error{};
+ error.severity = diag::Severity::Error;
+ error.system = system;
+ error.message = err_msg;
+ add(std::move(error));
+ }
+
+ /// adds the error message with the given Source to the end of this list.
+ /// @param system the system raising the error message
+ /// @param err_msg the error message
+ /// @param source the source of the error diagnostic
+ void add_error(System system, std::string_view err_msg, const Source& source) {
+ diag::Diagnostic error{};
+ error.severity = diag::Severity::Error;
+ error.system = system;
+ error.source = source;
+ error.message = err_msg;
+ add(std::move(error));
+ }
+
+ /// adds the error message with the given code and Source to the end of this
+ /// list.
+ /// @param system the system raising the error message
+ /// @param code the error code
+ /// @param err_msg the error message
+ /// @param source the source of the error diagnostic
+ void add_error(System system,
+ const char* code,
+ std::string_view err_msg,
+ const Source& source) {
+ diag::Diagnostic error{};
+ error.code = code;
+ error.severity = diag::Severity::Error;
+ error.system = system;
+ error.source = source;
+ error.message = err_msg;
+ add(std::move(error));
+ }
+
+ /// adds an internal compiler error message to the end of this list.
+ /// @param system the system raising the error message
+ /// @param err_msg the error message
+ /// @param source the source of the internal compiler error
+ /// @param file the Source::File owned by this diagnostic
+ void add_ice(System system,
+ std::string_view err_msg,
+ const Source& source,
+ std::shared_ptr<Source::File> file) {
+ diag::Diagnostic ice{};
+ ice.severity = diag::Severity::InternalCompilerError;
+ ice.system = system;
+ ice.source = source;
+ ice.message = err_msg;
+ ice.owned_file = std::move(file);
+ add(std::move(ice));
+ }
+
+ /// @returns true iff the diagnostic list contains errors diagnostics (or of
+ /// higher severity).
+ bool contains_errors() const { return error_count_ > 0; }
+ /// @returns the number of error diagnostics (or of higher severity).
+ size_t error_count() const { return error_count_; }
+ /// @returns the number of entries in the list.
+ size_t count() const { return entries_.size(); }
+ /// @returns true if the diagnostics list is empty
+ bool empty() const { return entries_.empty(); }
+ /// @returns the number of entrise in the diagnostics list
+ size_t size() const { return entries_.size(); }
+ /// @returns the first diagnostic in the list.
+ iterator begin() const { return entries_.begin(); }
+ /// @returns the last diagnostic in the list.
+ iterator end() const { return entries_.end(); }
+
+ /// @returns a formatted string of all the diagnostics in this list.
+ std::string str() const;
+
+ private:
+ std::vector<Diagnostic> entries_;
+ size_t error_count_ = 0;
+};
+
+/// Write the diagnostic list to the given stream
+/// @param out the output stream
+/// @param list the list to emit
+/// @returns the output stream
+std::ostream& operator<<(std::ostream& out, const List& list);
+
+} // namespace tint::diag
+
+#endif // SRC_TINT_UTILS_DIAGNOSTIC_DIAGNOSTIC_H_
diff --git a/src/tint/utils/diagnostic/diagnostic_test.cc b/src/tint/utils/diagnostic/diagnostic_test.cc
new file mode 100644
index 0000000..0077649
--- /dev/null
+++ b/src/tint/utils/diagnostic/diagnostic_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/diagnostic/formatter.h"
+
+#include "gtest/gtest.h"
+#include "src/tint/utils/diagnostic/diagnostic.h"
+
+namespace tint::diag {
+namespace {
+
+TEST(DiagListTest, OwnedFilesShared) {
+ auto file = std::make_shared<Source::File>("path", "content");
+
+ diag::List list_a, list_b;
+ {
+ diag::Diagnostic diag{};
+ diag.source = Source{Source::Range{{0, 0}}, file.get()};
+ list_a.add(std::move(diag));
+ }
+
+ list_b = list_a;
+
+ ASSERT_EQ(list_b.count(), list_a.count());
+ EXPECT_EQ(list_b.begin()->source.file, file.get());
+}
+
+} // namespace
+} // namespace tint::diag
diff --git a/src/tint/utils/diagnostic/formatter.cc b/src/tint/utils/diagnostic/formatter.cc
new file mode 100644
index 0000000..416551f
--- /dev/null
+++ b/src/tint/utils/diagnostic/formatter.cc
@@ -0,0 +1,267 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/diagnostic/formatter.h"
+
+#include <algorithm>
+#include <iterator>
+#include <utility>
+#include <vector>
+
+#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/diagnostic/printer.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::diag {
+namespace {
+
+const char* to_str(Severity severity) {
+ switch (severity) {
+ case Severity::Note:
+ return "note";
+ case Severity::Warning:
+ return "warning";
+ case Severity::Error:
+ return "error";
+ case Severity::InternalCompilerError:
+ return "internal compiler error";
+ case Severity::Fatal:
+ return "fatal";
+ }
+ return "";
+}
+
+std::string to_str(const Source::Location& location) {
+ utils::StringStream ss;
+ if (location.line > 0) {
+ ss << location.line;
+ if (location.column > 0) {
+ ss << ":" << location.column;
+ }
+ }
+ return ss.str();
+}
+
+} // namespace
+
+/// State holds the internal formatter state for a format() call.
+struct Formatter::State {
+ /// Constructs a State associated with the given printer.
+ /// @param p the printer to write formatted messages to.
+ explicit State(Printer* p) : printer(p) {}
+ ~State() { flush(); }
+
+ /// set_style() sets the current style to new_style, flushing any pending
+ /// messages to the printer if the style changed.
+ /// @param new_style the new style to apply for future written messages.
+ void set_style(const diag::Style& new_style) {
+ if (style.color != new_style.color || style.bold != new_style.bold) {
+ flush();
+ style = new_style;
+ }
+ }
+
+ /// flush writes any pending messages to the printer, clearing the buffer.
+ void flush() {
+ auto str = stream.str();
+ if (str.length() > 0) {
+ printer->write(str, style);
+ utils::StringStream reset;
+ stream.swap(reset);
+ }
+ }
+
+ /// operator<< queues msg to be written to the printer.
+ /// @param msg the value or string to write to the printer
+ /// @returns this State so that calls can be chained
+ template <typename T>
+ State& operator<<(T&& msg) {
+ stream << std::forward<T>(msg);
+ return *this;
+ }
+
+ /// newline queues a newline to be written to the printer.
+ void newline() { stream << std::endl; }
+
+ /// repeat queues the character c to be written to the printer n times.
+ /// @param c the character to print `n` times
+ /// @param n the number of times to print character `c`
+ void repeat(char c, size_t n) { stream.repeat(c, n); }
+
+ private:
+ Printer* printer;
+ diag::Style style;
+ utils::StringStream stream;
+};
+
+Formatter::Formatter() {}
+Formatter::Formatter(const Style& style) : style_(style) {}
+
+void Formatter::format(const List& list, Printer* printer) const {
+ State state{printer};
+
+ bool first = true;
+ for (auto diag : list) {
+ state.set_style({});
+ if (!first) {
+ state.newline();
+ }
+ format(diag, state);
+ first = false;
+ }
+
+ if (style_.print_newline_at_end) {
+ state.newline();
+ }
+}
+
+void Formatter::format(const Diagnostic& diag, State& state) const {
+ auto const& src = diag.source;
+ auto const& rng = src.range;
+ bool has_code = diag.code != nullptr && diag.code[0] != '\0';
+
+ state.set_style({Color::kDefault, true});
+
+ struct TextAndColor {
+ std::string text;
+ Color color;
+ bool bold = false;
+ };
+ std::vector<TextAndColor> prefix;
+ prefix.reserve(6);
+
+ if (style_.print_file && src.file != nullptr) {
+ if (rng.begin.line > 0) {
+ prefix.emplace_back(
+ TextAndColor{src.file->path + ":" + to_str(rng.begin), Color::kDefault});
+ } else {
+ prefix.emplace_back(TextAndColor{src.file->path, Color::kDefault});
+ }
+ } else if (rng.begin.line > 0) {
+ prefix.emplace_back(TextAndColor{to_str(rng.begin), Color::kDefault});
+ }
+
+ Color severity_color = Color::kDefault;
+ switch (diag.severity) {
+ case Severity::Note:
+ break;
+ case Severity::Warning:
+ severity_color = Color::kYellow;
+ break;
+ case Severity::Error:
+ severity_color = Color::kRed;
+ break;
+ case Severity::Fatal:
+ case Severity::InternalCompilerError:
+ severity_color = Color::kMagenta;
+ break;
+ }
+ if (style_.print_severity) {
+ prefix.emplace_back(TextAndColor{to_str(diag.severity), severity_color, true});
+ }
+ if (has_code) {
+ prefix.emplace_back(TextAndColor{diag.code, severity_color});
+ }
+
+ for (size_t i = 0; i < prefix.size(); i++) {
+ if (i > 0) {
+ state << " ";
+ }
+ state.set_style({prefix[i].color, prefix[i].bold});
+ state << prefix[i].text;
+ }
+
+ state.set_style({Color::kDefault, true});
+ if (!prefix.empty()) {
+ state << ": ";
+ }
+ state << diag.message;
+
+ if (style_.print_line && src.file && rng.begin.line > 0) {
+ state.newline();
+ state.set_style({Color::kDefault, false});
+
+ for (size_t line_num = rng.begin.line;
+ (line_num <= rng.end.line) && (line_num <= src.file->content.lines.size());
+ line_num++) {
+ auto& line = src.file->content.lines[line_num - 1];
+ auto line_len = line.size();
+
+ bool is_ascii = true;
+ for (auto c : line) {
+ if (c == '\t') {
+ state.repeat(' ', style_.tab_width);
+ } else {
+ state << c;
+ }
+ if (c & 0x80) {
+ is_ascii = false;
+ }
+ }
+
+ state.newline();
+
+ // If the line contains non-ascii characters, then we cannot assume that
+ // a single utf8 code unit represents a single glyph, so don't attempt to
+ // draw squiggles.
+ if (!is_ascii) {
+ continue;
+ }
+
+ state.set_style({Color::kCyan, false});
+
+ // Count the number of glyphs in the line span.
+ // start and end use 1-based indexing.
+ auto num_glyphs = [&](size_t start, size_t end) {
+ size_t count = 0;
+ start = (start > 0) ? (start - 1) : 0;
+ end = (end > 0) ? (end - 1) : 0;
+ for (size_t i = start; (i < end) && (i < line_len); i++) {
+ count += (line[i] == '\t') ? style_.tab_width : 1;
+ }
+ return count;
+ };
+
+ if (line_num == rng.begin.line && line_num == rng.end.line) {
+ // Single line
+ state.repeat(' ', num_glyphs(1, rng.begin.column));
+ state.repeat('^',
+ std::max<size_t>(num_glyphs(rng.begin.column, rng.end.column), 1));
+ } else if (line_num == rng.begin.line) {
+ // Start of multi-line
+ state.repeat(' ', num_glyphs(1, rng.begin.column));
+ state.repeat('^', num_glyphs(rng.begin.column, line_len + 1));
+ } else if (line_num == rng.end.line) {
+ // End of multi-line
+ state.repeat('^', num_glyphs(1, rng.end.column));
+ } else {
+ // Middle of multi-line
+ state.repeat('^', num_glyphs(1, line_len + 1));
+ }
+ state.newline();
+ }
+
+ state.set_style({});
+ }
+}
+
+std::string Formatter::format(const List& list) const {
+ StringPrinter printer;
+ format(list, &printer);
+ return printer.str();
+}
+
+Formatter::~Formatter() = default;
+
+} // namespace tint::diag
diff --git a/src/tint/utils/diagnostic/formatter.h b/src/tint/utils/diagnostic/formatter.h
new file mode 100644
index 0000000..1b9a9b3
--- /dev/null
+++ b/src/tint/utils/diagnostic/formatter.h
@@ -0,0 +1,70 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_DIAGNOSTIC_FORMATTER_H_
+#define SRC_TINT_UTILS_DIAGNOSTIC_FORMATTER_H_
+
+#include <string>
+
+namespace tint::diag {
+
+class Diagnostic;
+class List;
+class Printer;
+
+/// Formatter are used to print a list of diagnostics messages.
+class Formatter {
+ public:
+ /// Style controls the formatter's output style.
+ struct Style {
+ /// include the file path for each diagnostic
+ bool print_file = true;
+ /// include the severity for each diagnostic
+ bool print_severity = true;
+ /// include the source line(s) for the diagnostic
+ bool print_line = true;
+ /// print a newline at the end of a diagnostic list
+ bool print_newline_at_end = true;
+ /// width of a tab character
+ size_t tab_width = 2u;
+ };
+
+ /// Constructor for the formatter using a default style.
+ Formatter();
+
+ /// Constructor for the formatter using the custom style.
+ /// @param style the style used for the formatter.
+ explicit Formatter(const Style& style);
+
+ ~Formatter();
+
+ /// @param list the list of diagnostic messages to format
+ /// @param printer the printer used to display the formatted diagnostics
+ void format(const List& list, Printer* printer) const;
+
+ /// @return the list of diagnostics `list` formatted to a string.
+ /// @param list the list of diagnostic messages to format
+ std::string format(const List& list) const;
+
+ private:
+ struct State;
+
+ void format(const Diagnostic& diag, State& state) const;
+
+ const Style style_;
+};
+
+} // namespace tint::diag
+
+#endif // SRC_TINT_UTILS_DIAGNOSTIC_FORMATTER_H_
diff --git a/src/tint/utils/diagnostic/formatter_test.cc b/src/tint/utils/diagnostic/formatter_test.cc
new file mode 100644
index 0000000..242be15
--- /dev/null
+++ b/src/tint/utils/diagnostic/formatter_test.cc
@@ -0,0 +1,294 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/diagnostic/formatter.h"
+
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "src/tint/utils/diagnostic/diagnostic.h"
+
+namespace tint::diag {
+namespace {
+
+Diagnostic Diag(Severity severity,
+ Source source,
+ std::string message,
+ System system,
+ const char* code = nullptr) {
+ Diagnostic d;
+ d.severity = severity;
+ d.source = source;
+ d.message = std::move(message);
+ d.system = system;
+ d.code = code;
+ return d;
+}
+
+constexpr const char* ascii_content = // Note: words are tab-delimited
+ R"(the cat says meow
+the dog says woof
+the snake says quack
+the snail says ???
+)";
+
+constexpr const char* utf8_content = // Note: words are tab-delimited
+ "the \xf0\x9f\x90\xb1 says meow\n" // NOLINT: tabs
+ "the \xf0\x9f\x90\x95 says woof\n" // NOLINT: tabs
+ "the \xf0\x9f\x90\x8d says quack\n" // NOLINT: tabs
+ "the \xf0\x9f\x90\x8c says ???\n"; // NOLINT: tabs
+
+class DiagFormatterTest : public testing::Test {
+ public:
+ Source::File ascii_file{"file.name", ascii_content};
+ Source::File utf8_file{"file.name", utf8_content};
+ Diagnostic ascii_diag_note = Diag(Severity::Note,
+ Source{Source::Range{Source::Location{1, 14}}, &ascii_file},
+ "purr",
+ System::Test);
+ Diagnostic ascii_diag_warn = Diag(Severity::Warning,
+ Source{Source::Range{{2, 14}, {2, 18}}, &ascii_file},
+ "grrr",
+ System::Test);
+ Diagnostic ascii_diag_err = Diag(Severity::Error,
+ Source{Source::Range{{3, 16}, {3, 21}}, &ascii_file},
+ "hiss",
+ System::Test,
+ "abc123");
+ Diagnostic ascii_diag_ice = Diag(Severity::InternalCompilerError,
+ Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file},
+ "unreachable",
+ System::Test);
+ Diagnostic ascii_diag_fatal = Diag(Severity::Fatal,
+ Source{Source::Range{{4, 16}, {4, 19}}, &ascii_file},
+ "nothing",
+ System::Test);
+
+ Diagnostic utf8_diag_note = Diag(Severity::Note,
+ Source{Source::Range{Source::Location{1, 15}}, &utf8_file},
+ "purr",
+ System::Test);
+ Diagnostic utf8_diag_warn = Diag(Severity::Warning,
+ Source{Source::Range{{2, 15}, {2, 19}}, &utf8_file},
+ "grrr",
+ System::Test);
+ Diagnostic utf8_diag_err = Diag(Severity::Error,
+ Source{Source::Range{{3, 15}, {3, 20}}, &utf8_file},
+ "hiss",
+ System::Test,
+ "abc123");
+ Diagnostic utf8_diag_ice = Diag(Severity::InternalCompilerError,
+ Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file},
+ "unreachable",
+ System::Test);
+ Diagnostic utf8_diag_fatal = Diag(Severity::Fatal,
+ Source{Source::Range{{4, 15}, {4, 18}}, &utf8_file},
+ "nothing",
+ System::Test);
+};
+
+TEST_F(DiagFormatterTest, Simple) {
+ Formatter fmt{{false, false, false, false}};
+ auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+ auto* expect = R"(1:14: purr
+2:14: grrr
+3:16 abc123: hiss)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, SimpleNewlineAtEnd) {
+ Formatter fmt{{false, false, false, true}};
+ auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+ auto* expect = R"(1:14: purr
+2:14: grrr
+3:16 abc123: hiss
+)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, SimpleNoSource) {
+ Formatter fmt{{false, false, false, false}};
+ auto diag = Diag(Severity::Note, Source{}, "no source!", System::Test);
+ auto got = fmt.format(List{diag});
+ auto* expect = "no source!";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, WithFile) {
+ Formatter fmt{{true, false, false, false}};
+ auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+ auto* expect = R"(file.name:1:14: purr
+file.name:2:14: grrr
+file.name:3:16 abc123: hiss)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, WithSeverity) {
+ Formatter fmt{{false, true, false, false}};
+ auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+ auto* expect = R"(1:14 note: purr
+2:14 warning: grrr
+3:16 error abc123: hiss)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, WithLine) {
+ Formatter fmt{{false, false, true, false}};
+ auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+ auto* expect = R"(1:14: purr
+the cat says meow
+ ^
+
+2:14: grrr
+the dog says woof
+ ^^^^
+
+3:16 abc123: hiss
+the snake says quack
+ ^^^^^
+)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, UnicodeWithLine) {
+ Formatter fmt{{false, false, true, false}};
+ auto got = fmt.format(List{utf8_diag_note, utf8_diag_warn, utf8_diag_err});
+ auto* expect =
+ "1:15: purr\n"
+ "the \xf0\x9f\x90\xb1 says meow\n"
+ "\n"
+ "2:15: grrr\n"
+ "the \xf0\x9f\x90\x95 says woof\n"
+ "\n"
+ "3:15 abc123: hiss\n"
+ "the \xf0\x9f\x90\x8d says quack\n";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, BasicWithFileSeverityLine) {
+ Formatter fmt{{true, true, true, false}};
+ auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+ auto* expect = R"(file.name:1:14 note: purr
+the cat says meow
+ ^
+
+file.name:2:14 warning: grrr
+the dog says woof
+ ^^^^
+
+file.name:3:16 error abc123: hiss
+the snake says quack
+ ^^^^^
+)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, BasicWithMultiLine) {
+ auto multiline = Diag(Severity::Warning, Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
+ "multiline", System::Test);
+ Formatter fmt{{false, false, true, false}};
+ auto got = fmt.format(List{multiline});
+ auto* expect = R"(2:9: multiline
+the dog says woof
+ ^^^^^^^^^^
+the snake says quack
+^^^^^^^^^^^^^^^^^^^^^^^
+the snail says ???
+^^^^^^^^^^^^^^^^
+)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, UnicodeWithMultiLine) {
+ auto multiline = Diag(Severity::Warning, Source{Source::Range{{2, 9}, {4, 15}}, &utf8_file},
+ "multiline", System::Test);
+ Formatter fmt{{false, false, true, false}};
+ auto got = fmt.format(List{multiline});
+ auto* expect =
+ "2:9: multiline\n"
+ "the \xf0\x9f\x90\x95 says woof\n"
+ "the \xf0\x9f\x90\x8d says quack\n"
+ "the \xf0\x9f\x90\x8c says ???\n";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, BasicWithFileSeverityLineTab4) {
+ Formatter fmt{{true, true, true, false, 4u}};
+ auto got = fmt.format(List{ascii_diag_note, ascii_diag_warn, ascii_diag_err});
+ auto* expect = R"(file.name:1:14 note: purr
+the cat says meow
+ ^
+
+file.name:2:14 warning: grrr
+the dog says woof
+ ^^^^
+
+file.name:3:16 error abc123: hiss
+the snake says quack
+ ^^^^^
+)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, BasicWithMultiLineTab4) {
+ auto multiline = Diag(Severity::Warning, Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
+ "multiline", System::Test);
+ Formatter fmt{{false, false, true, false, 4u}};
+ auto got = fmt.format(List{multiline});
+ auto* expect = R"(2:9: multiline
+the dog says woof
+ ^^^^^^^^^^^^
+the snake says quack
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+the snail says ???
+^^^^^^^^^^^^^^^^^^^^
+)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, ICE) {
+ Formatter fmt{{}};
+ auto got = fmt.format(List{ascii_diag_ice});
+ auto* expect = R"(file.name:4:16 internal compiler error: unreachable
+the snail says ???
+ ^^^
+
+)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, Fatal) {
+ Formatter fmt{{}};
+ auto got = fmt.format(List{ascii_diag_fatal});
+ auto* expect = R"(file.name:4:16 fatal: nothing
+the snail says ???
+ ^^^
+
+)";
+ ASSERT_EQ(expect, got);
+}
+
+TEST_F(DiagFormatterTest, RangeOOB) {
+ Formatter fmt{{true, true, true, true}};
+ diag::List list;
+ list.add_error(System::Test, "oob", Source{{{10, 20}, {30, 20}}, &ascii_file});
+ auto got = fmt.format(list);
+ auto* expect = R"(file.name:10:20 error: oob
+
+)";
+ ASSERT_EQ(expect, got);
+}
+
+} // namespace
+} // namespace tint::diag
diff --git a/src/tint/utils/diagnostic/printer.cc b/src/tint/utils/diagnostic/printer.cc
new file mode 100644
index 0000000..f2c3345
--- /dev/null
+++ b/src/tint/utils/diagnostic/printer.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/diagnostic/printer.h"
+
+namespace tint::diag {
+
+Printer::~Printer() = default;
+
+StringPrinter::StringPrinter() = default;
+StringPrinter::~StringPrinter() = default;
+
+std::string StringPrinter::str() const {
+ return stream.str();
+}
+
+void StringPrinter::write(const std::string& str, const Style&) {
+ stream << str;
+}
+
+} // namespace tint::diag
diff --git a/src/tint/utils/diagnostic/printer.h b/src/tint/utils/diagnostic/printer.h
new file mode 100644
index 0000000..07b2c2c
--- /dev/null
+++ b/src/tint/utils/diagnostic/printer.h
@@ -0,0 +1,82 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_DIAGNOSTIC_PRINTER_H_
+#define SRC_TINT_UTILS_DIAGNOSTIC_PRINTER_H_
+
+#include <memory>
+#include <string>
+
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::diag {
+
+class List;
+
+/// Color is an enumerator of colors used by Style.
+enum class Color {
+ kDefault,
+ kBlack,
+ kRed,
+ kGreen,
+ kYellow,
+ kBlue,
+ kMagenta,
+ kCyan,
+ kWhite,
+};
+
+/// Style describes how a diagnostic message should be printed.
+struct Style {
+ /// The foreground text color
+ Color color = Color::kDefault;
+ /// If true the text will be displayed with a strong weight
+ bool bold = false;
+};
+
+/// Printers are used to print formatted diagnostic messages to a terminal.
+class Printer {
+ public:
+ /// @returns a diagnostic Printer
+ /// @param out the file to print to.
+ /// @param use_colors if true, the printer will use colors if `out` is a
+ /// terminal and supports them.
+ static std::unique_ptr<Printer> create(FILE* out, bool use_colors);
+
+ virtual ~Printer();
+
+ /// writes the string str to the printer with the given style.
+ /// @param str the string to write to the printer
+ /// @param style the style used to print `str`
+ virtual void write(const std::string& str, const Style& style) = 0;
+};
+
+/// StringPrinter is an implementation of Printer that writes to a std::string.
+class StringPrinter : public Printer {
+ public:
+ StringPrinter();
+ ~StringPrinter() override;
+
+ /// @returns the printed string.
+ std::string str() const;
+
+ void write(const std::string& str, const Style&) override;
+
+ private:
+ utils::StringStream stream;
+};
+
+} // namespace tint::diag
+
+#endif // SRC_TINT_UTILS_DIAGNOSTIC_PRINTER_H_
diff --git a/src/tint/utils/diagnostic/printer_other.cc b/src/tint/utils/diagnostic/printer_other.cc
new file mode 100644
index 0000000..2fe7c2d
--- /dev/null
+++ b/src/tint/utils/diagnostic/printer_other.cc
@@ -0,0 +1,40 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstring>
+
+#include "src/tint/utils/diagnostic/printer.h"
+
+namespace tint::diag {
+namespace {
+
+class PrinterOther : public Printer {
+ public:
+ explicit PrinterOther(FILE* f) : file(f) {}
+
+ void write(const std::string& str, const Style&) override {
+ fwrite(str.data(), 1, str.size(), file);
+ }
+
+ private:
+ FILE* file;
+};
+
+} // namespace
+
+std::unique_ptr<Printer> Printer::create(FILE* out, bool) {
+ return std::make_unique<PrinterOther>(out);
+}
+
+} // namespace tint::diag
diff --git a/src/tint/utils/diagnostic/printer_posix.cc b/src/tint/utils/diagnostic/printer_posix.cc
new file mode 100644
index 0000000..0d5e092
--- /dev/null
+++ b/src/tint/utils/diagnostic/printer_posix.cc
@@ -0,0 +1,97 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <unistd.h>
+
+#include <cstring>
+
+#include "src/tint/utils/diagnostic/printer.h"
+
+namespace tint::diag {
+namespace {
+
+bool supports_colors(FILE* f) {
+ if (!isatty(fileno(f))) {
+ return false;
+ }
+
+ const char* cterm = getenv("TERM");
+ if (cterm == nullptr) {
+ return false;
+ }
+
+ std::string term = getenv("TERM");
+ if (term != "cygwin" && term != "linux" && term != "rxvt-unicode-256color" &&
+ term != "rxvt-unicode" && term != "screen-256color" && term != "screen" &&
+ term != "tmux-256color" && term != "tmux" && term != "xterm-256color" &&
+ term != "xterm-color" && term != "xterm") {
+ return false;
+ }
+
+ return true;
+}
+
+class PrinterPosix : public Printer {
+ public:
+ PrinterPosix(FILE* f, bool colors) : file(f), use_colors(colors && supports_colors(f)) {}
+
+ void write(const std::string& str, const Style& style) override {
+ write_color(style.color, style.bold);
+ fwrite(str.data(), 1, str.size(), file);
+ write_color(Color::kDefault, false);
+ }
+
+ private:
+ constexpr const char* color_code(Color color, bool bold) {
+ switch (color) {
+ case Color::kDefault:
+ return bold ? "\u001b[1m" : "\u001b[0m";
+ case Color::kBlack:
+ return bold ? "\u001b[30;1m" : "\u001b[30m";
+ case Color::kRed:
+ return bold ? "\u001b[31;1m" : "\u001b[31m";
+ case Color::kGreen:
+ return bold ? "\u001b[32;1m" : "\u001b[32m";
+ case Color::kYellow:
+ return bold ? "\u001b[33;1m" : "\u001b[33m";
+ case Color::kBlue:
+ return bold ? "\u001b[34;1m" : "\u001b[34m";
+ case Color::kMagenta:
+ return bold ? "\u001b[35;1m" : "\u001b[35m";
+ case Color::kCyan:
+ return bold ? "\u001b[36;1m" : "\u001b[36m";
+ case Color::kWhite:
+ return bold ? "\u001b[37;1m" : "\u001b[37m";
+ }
+ return ""; // unreachable
+ }
+
+ void write_color(Color color, bool bold) {
+ if (use_colors) {
+ auto* code = color_code(color, bold);
+ fwrite(code, 1, strlen(code), file);
+ }
+ }
+
+ FILE* const file;
+ const bool use_colors;
+};
+
+} // namespace
+
+std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) {
+ return std::make_unique<PrinterPosix>(out, use_colors);
+}
+
+} // namespace tint::diag
diff --git a/src/tint/utils/diagnostic/printer_test.cc b/src/tint/utils/diagnostic/printer_test.cc
new file mode 100644
index 0000000..6af9470
--- /dev/null
+++ b/src/tint/utils/diagnostic/printer_test.cc
@@ -0,0 +1,96 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/diagnostic/printer.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::diag {
+namespace {
+
+// Actually verifying that the expected colors are printed is exceptionally
+// difficult as:
+// a) The color emission varies by OS.
+// b) The logic checks to see if the printer is writing to a terminal, making
+// mocking hard.
+// c) Actually probing what gets written to a FILE* is notoriously tricky.
+//
+// The least we can do is to exersice the code - which is what we do here.
+// The test will print each of the colors, and can be examined with human
+// eyeballs.
+// This can be enabled or disabled with ENABLE_PRINTER_TESTS
+#define ENABLE_PRINTER_TESTS 0
+#if ENABLE_PRINTER_TESTS
+
+using PrinterTest = testing::Test;
+
+TEST_F(PrinterTest, WithColors) {
+ auto printer = Printer::create(stdout, true);
+ printer->write("Default", Style{Color::kDefault, false});
+ printer->write("Black", Style{Color::kBlack, false});
+ printer->write("Red", Style{Color::kRed, false});
+ printer->write("Green", Style{Color::kGreen, false});
+ printer->write("Yellow", Style{Color::kYellow, false});
+ printer->write("Blue", Style{Color::kBlue, false});
+ printer->write("Magenta", Style{Color::kMagenta, false});
+ printer->write("Cyan", Style{Color::kCyan, false});
+ printer->write("White", Style{Color::kWhite, false});
+ printf("\n");
+}
+
+TEST_F(PrinterTest, BoldWithColors) {
+ auto printer = Printer::create(stdout, true);
+ printer->write("Default", Style{Color::kDefault, true});
+ printer->write("Black", Style{Color::kBlack, true});
+ printer->write("Red", Style{Color::kRed, true});
+ printer->write("Green", Style{Color::kGreen, true});
+ printer->write("Yellow", Style{Color::kYellow, true});
+ printer->write("Blue", Style{Color::kBlue, true});
+ printer->write("Magenta", Style{Color::kMagenta, true});
+ printer->write("Cyan", Style{Color::kCyan, true});
+ printer->write("White", Style{Color::kWhite, true});
+ printf("\n");
+}
+
+TEST_F(PrinterTest, WithoutColors) {
+ auto printer = Printer::create(stdout, false);
+ printer->write("Default", Style{Color::kDefault, false});
+ printer->write("Black", Style{Color::kBlack, false});
+ printer->write("Red", Style{Color::kRed, false});
+ printer->write("Green", Style{Color::kGreen, false});
+ printer->write("Yellow", Style{Color::kYellow, false});
+ printer->write("Blue", Style{Color::kBlue, false});
+ printer->write("Magenta", Style{Color::kMagenta, false});
+ printer->write("Cyan", Style{Color::kCyan, false});
+ printer->write("White", Style{Color::kWhite, false});
+ printf("\n");
+}
+
+TEST_F(PrinterTest, BoldWithoutColors) {
+ auto printer = Printer::create(stdout, false);
+ printer->write("Default", Style{Color::kDefault, true});
+ printer->write("Black", Style{Color::kBlack, true});
+ printer->write("Red", Style{Color::kRed, true});
+ printer->write("Green", Style{Color::kGreen, true});
+ printer->write("Yellow", Style{Color::kYellow, true});
+ printer->write("Blue", Style{Color::kBlue, true});
+ printer->write("Magenta", Style{Color::kMagenta, true});
+ printer->write("Cyan", Style{Color::kCyan, true});
+ printer->write("White", Style{Color::kWhite, true});
+ printf("\n");
+}
+
+#endif // ENABLE_PRINTER_TESTS
+} // namespace
+} // namespace tint::diag
diff --git a/src/tint/utils/diagnostic/printer_windows.cc b/src/tint/utils/diagnostic/printer_windows.cc
new file mode 100644
index 0000000..a9582d4
--- /dev/null
+++ b/src/tint/utils/diagnostic/printer_windows.cc
@@ -0,0 +1,108 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstring>
+
+#include "src/tint/utils/diagnostic/printer.h"
+
+#define WIN32_LEAN_AND_MEAN 1
+#include <Windows.h>
+
+namespace tint::diag {
+namespace {
+
+struct ConsoleInfo {
+ HANDLE handle = INVALID_HANDLE_VALUE;
+ WORD default_attributes = 0;
+ operator bool() const { return handle != INVALID_HANDLE_VALUE; }
+};
+
+ConsoleInfo console_info(FILE* file) {
+ if (file == nullptr) {
+ return {};
+ }
+
+ ConsoleInfo console{};
+ if (file == stdout) {
+ console.handle = GetStdHandle(STD_OUTPUT_HANDLE);
+ } else if (file == stderr) {
+ console.handle = GetStdHandle(STD_ERROR_HANDLE);
+ } else {
+ return {};
+ }
+
+ CONSOLE_SCREEN_BUFFER_INFO info{};
+ if (GetConsoleScreenBufferInfo(console.handle, &info) == 0) {
+ return {};
+ }
+
+ console.default_attributes = info.wAttributes;
+ return console;
+}
+
+class PrinterWindows : public Printer {
+ public:
+ PrinterWindows(FILE* f, bool use_colors)
+ : file(f), console(console_info(use_colors ? f : nullptr)) {}
+
+ void write(const std::string& str, const Style& style) override {
+ write_color(style.color, style.bold);
+ fwrite(str.data(), 1, str.size(), file);
+ write_color(Color::kDefault, false);
+ }
+
+ private:
+ WORD attributes(Color color, bool bold) {
+ switch (color) {
+ case Color::kDefault:
+ return console.default_attributes;
+ case Color::kBlack:
+ return 0;
+ case Color::kRed:
+ return FOREGROUND_RED | (bold ? FOREGROUND_INTENSITY : 0);
+ case Color::kGreen:
+ return FOREGROUND_GREEN | (bold ? FOREGROUND_INTENSITY : 0);
+ case Color::kYellow:
+ return FOREGROUND_RED | FOREGROUND_GREEN | (bold ? FOREGROUND_INTENSITY : 0);
+ case Color::kBlue:
+ return FOREGROUND_BLUE | (bold ? FOREGROUND_INTENSITY : 0);
+ case Color::kMagenta:
+ return FOREGROUND_RED | FOREGROUND_BLUE | (bold ? FOREGROUND_INTENSITY : 0);
+ case Color::kCyan:
+ return FOREGROUND_GREEN | FOREGROUND_BLUE | (bold ? FOREGROUND_INTENSITY : 0);
+ case Color::kWhite:
+ return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
+ (bold ? FOREGROUND_INTENSITY : 0);
+ }
+ return 0; // unreachable
+ }
+
+ void write_color(Color color, bool bold) {
+ if (console) {
+ SetConsoleTextAttribute(console.handle, attributes(color, bold));
+ fflush(file);
+ }
+ }
+
+ FILE* const file;
+ const ConsoleInfo console;
+};
+
+} // namespace
+
+std::unique_ptr<Printer> Printer::create(FILE* out, bool use_colors) {
+ return std::make_unique<PrinterWindows>(out, use_colors);
+}
+
+} // namespace tint::diag
diff --git a/src/tint/utils/diagnostic/source.cc b/src/tint/utils/diagnostic/source.cc
new file mode 100644
index 0000000..d2b079c
--- /dev/null
+++ b/src/tint/utils/diagnostic/source.cc
@@ -0,0 +1,177 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/diagnostic/source.h"
+
+#include <algorithm>
+#include <string_view>
+#include <utility>
+
+#include "src/tint/utils/text/unicode.h"
+
+namespace tint {
+namespace {
+
+bool ParseLineBreak(std::string_view str, size_t i, bool* is_line_break, size_t* line_break_size) {
+ // See https://www.w3.org/TR/WGSL/#blankspace
+
+ auto* utf8 = reinterpret_cast<const uint8_t*>(&str[i]);
+ auto [cp, n] = utils::utf8::Decode(utf8, str.size() - i);
+
+ if (n == 0) {
+ return false;
+ }
+
+ static const auto kLF = utils::CodePoint(0x000A); // line feed
+ static const auto kVTab = utils::CodePoint(0x000B); // vertical tab
+ static const auto kFF = utils::CodePoint(0x000C); // form feed
+ static const auto kNL = utils::CodePoint(0x0085); // next line
+ static const auto kCR = utils::CodePoint(0x000D); // carriage return
+ static const auto kLS = utils::CodePoint(0x2028); // line separator
+ static const auto kPS = utils::CodePoint(0x2029); // parargraph separator
+
+ if (cp == kLF || cp == kVTab || cp == kFF || cp == kNL || cp == kPS || cp == kLS) {
+ *is_line_break = true;
+ *line_break_size = n;
+ return true;
+ }
+
+ // Handle CRLF as one line break, and CR alone as one line break
+ if (cp == kCR) {
+ *is_line_break = true;
+ *line_break_size = n;
+
+ if (auto next_i = i + n; next_i < str.size()) {
+ auto* next_utf8 = reinterpret_cast<const uint8_t*>(&str[next_i]);
+ auto [next_cp, next_n] = utils::utf8::Decode(next_utf8, str.size() - next_i);
+
+ if (next_n == 0) {
+ return false;
+ }
+
+ if (next_cp == kLF) {
+ // CRLF as one break
+ *line_break_size = n + next_n;
+ }
+ }
+
+ return true;
+ }
+
+ *is_line_break = false;
+ return true;
+}
+
+std::vector<std::string_view> SplitLines(std::string_view str) {
+ std::vector<std::string_view> lines;
+
+ size_t lineStart = 0;
+ for (size_t i = 0; i < str.size();) {
+ bool is_line_break{};
+ size_t line_break_size{};
+ // We don't handle decode errors from ParseLineBreak. Instead, we rely on
+ // the Lexer to do so.
+ ParseLineBreak(str, i, &is_line_break, &line_break_size);
+ if (is_line_break) {
+ lines.push_back(str.substr(lineStart, i - lineStart));
+ i += line_break_size;
+ lineStart = i;
+ } else {
+ ++i;
+ }
+ }
+ if (lineStart < str.size()) {
+ lines.push_back(str.substr(lineStart));
+ }
+
+ return lines;
+}
+
+std::vector<std::string_view> CopyRelativeStringViews(const std::vector<std::string_view>& src_list,
+ const std::string_view& src_view,
+ const std::string_view& dst_view) {
+ std::vector<std::string_view> out(src_list.size());
+ for (size_t i = 0; i < src_list.size(); i++) {
+ auto offset = static_cast<size_t>(&src_list[i].front() - &src_view.front());
+ auto count = src_list[i].length();
+ out[i] = dst_view.substr(offset, count);
+ }
+ return out;
+}
+
+} // namespace
+
+Source::FileContent::FileContent(const std::string& body) : data(body), lines(SplitLines(data)) {}
+
+Source::FileContent::FileContent(const FileContent& rhs)
+ : data(rhs.data), lines(CopyRelativeStringViews(rhs.lines, rhs.data, data)) {}
+
+Source::FileContent::~FileContent() = default;
+
+Source::File::~File() = default;
+
+utils::StringStream& operator<<(utils::StringStream& out, const Source& source) {
+ auto rng = source.range;
+
+ if (source.file) {
+ out << source.file->path << ":";
+ }
+ if (rng.begin.line) {
+ out << rng.begin.line << ":";
+ if (rng.begin.column) {
+ out << rng.begin.column;
+ }
+
+ if (source.file) {
+ out << std::endl << std::endl;
+
+ auto repeat = [&](char c, size_t n) {
+ while (n--) {
+ out << c;
+ }
+ };
+
+ for (size_t line = rng.begin.line; line <= rng.end.line; line++) {
+ if (line < source.file->content.lines.size() + 1) {
+ auto len = source.file->content.lines[line - 1].size();
+
+ out << source.file->content.lines[line - 1];
+
+ out << std::endl;
+
+ if (line == rng.begin.line && line == rng.end.line) {
+ // Single line
+ repeat(' ', rng.begin.column - 1);
+ repeat('^', std::max<size_t>(rng.end.column - rng.begin.column, 1));
+ } else if (line == rng.begin.line) {
+ // Start of multi-line
+ repeat(' ', rng.begin.column - 1);
+ repeat('^', len - (rng.begin.column - 1));
+ } else if (line == rng.end.line) {
+ // End of multi-line
+ repeat('^', rng.end.column - 1);
+ } else {
+ // Middle of multi-line
+ repeat('^', len);
+ }
+
+ out << std::endl;
+ }
+ }
+ }
+ }
+ return out;
+}
+
+} // namespace tint
diff --git a/src/tint/utils/diagnostic/source.h b/src/tint/utils/diagnostic/source.h
new file mode 100644
index 0000000..7f5f765
--- /dev/null
+++ b/src/tint/utils/diagnostic/source.h
@@ -0,0 +1,231 @@
+
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_DIAGNOSTIC_SOURCE_H_
+#define SRC_TINT_UTILS_DIAGNOSTIC_SOURCE_H_
+
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint {
+
+/// Source describes a range of characters within a source file.
+class Source {
+ public:
+ /// FileContent describes the content of a source file encoded using utf-8.
+ class FileContent {
+ public:
+ /// Constructs the FileContent with the given file content.
+ /// @param data the file contents
+ explicit FileContent(const std::string& data);
+
+ /// Copy constructor
+ /// @param rhs the FileContent to copy
+ FileContent(const FileContent& rhs);
+
+ /// Destructor
+ ~FileContent();
+
+ /// The original un-split file content
+ const std::string data;
+ /// #data split by lines
+ const std::vector<std::string_view> lines;
+ };
+
+ /// File describes a source file, including path and content.
+ class File {
+ public:
+ /// Constructs the File with the given file path and content.
+ /// @param p the path for this file
+ /// @param c the file contents
+ inline File(const std::string& p, const std::string& c) : path(p), content(c) {}
+
+ /// Copy constructor
+ File(const File&) = default;
+
+ /// Move constructor
+ File(File&&) = default;
+
+ /// Destructor
+ ~File();
+
+ /// file path
+ const std::string path;
+ /// file content
+ const FileContent content;
+ };
+
+ /// Location holds a 1-based line and column index.
+ class Location {
+ public:
+ /// the 1-based line number. 0 represents no line information.
+ size_t line = 0;
+ /// the 1-based column number in utf8-code units (bytes).
+ /// 0 represents no column information.
+ size_t column = 0;
+
+ /// Returns true if `this` location is lexicographically less than `rhs`
+ /// @param rhs location to compare against
+ /// @returns true if `this` < `rhs`
+ inline bool operator<(const Source::Location& rhs) {
+ return std::tie(line, column) < std::tie(rhs.line, rhs.column);
+ }
+
+ /// Returns true of `this` location is equal to `rhs`
+ /// @param rhs location to compare against
+ /// @returns true if `this` == `rhs`
+ inline bool operator==(const Location& rhs) const {
+ return line == rhs.line && column == rhs.column;
+ }
+
+ /// Returns true of `this` location is not equal to `rhs`
+ /// @param rhs location to compare against
+ /// @returns true if `this` != `rhs`
+ inline bool operator!=(const Location& rhs) const { return !(*this == rhs); }
+ };
+
+ /// Range holds a Location interval described by [begin, end).
+ class Range {
+ public:
+ /// Constructs a zero initialized Range.
+ inline Range() = default;
+
+ /// Constructs a zero-length Range starting at `loc`
+ /// @param loc the start and end location for the range
+ inline constexpr explicit Range(const Location& loc) : begin(loc), end(loc) {}
+
+ /// Constructs the Range beginning at `b` and ending at `e`
+ /// @param b the range start location
+ /// @param e the range end location
+ inline constexpr Range(const Location& b, const Location& e) : begin(b), end(e) {}
+
+ /// Return a column-shifted Range
+ /// @param n the number of characters to shift by
+ /// @returns a Range with a #begin and #end column shifted by `n`
+ inline Range operator+(size_t n) const {
+ return Range{{begin.line, begin.column + n}, {end.line, end.column + n}};
+ }
+
+ /// Returns true of `this` range is not equal to `rhs`
+ /// @param rhs range to compare against
+ /// @returns true if `this` != `rhs`
+ inline bool operator==(const Range& rhs) const {
+ return begin == rhs.begin && end == rhs.end;
+ }
+
+ /// Returns true of `this` range is equal to `rhs`
+ /// @param rhs range to compare against
+ /// @returns true if `this` == `rhs`
+ inline bool operator!=(const Range& rhs) const { return !(*this == rhs); }
+
+ /// The location of the first character in the range.
+ Location begin;
+ /// The location of one-past the last character in the range.
+ Location end;
+ };
+
+ /// Constructs the Source with an zero initialized Range and null File.
+ inline Source() : range() {}
+
+ /// Constructs the Source with the Range `rng` and a null File
+ /// @param rng the source range
+ inline explicit Source(const Range& rng) : range(rng) {}
+
+ /// Constructs the Source with the Range `loc` and a null File
+ /// @param loc the start and end location for the source range
+ inline explicit Source(const Location& loc) : range(Range(loc)) {}
+
+ /// Constructs the Source with the Range `rng` and File `file`
+ /// @param rng the source range
+ /// @param f the source file
+ inline Source(const Range& rng, File const* f) : range(rng), file(f) {}
+
+ /// @returns a Source that points to the begin range of this Source.
+ inline Source Begin() const { return Source(Range{range.begin}, file); }
+
+ /// @returns a Source that points to the end range of this Source.
+ inline Source End() const { return Source(Range{range.end}, file); }
+
+ /// Return a column-shifted Source
+ /// @param n the number of characters to shift by
+ /// @returns a Source with the range's columns shifted by `n`
+ inline Source operator+(size_t n) const { return Source(range + n, file); }
+
+ /// Returns true of `this` Source is lexicographically less than `rhs`
+ /// @param rhs source to compare against
+ /// @returns true if `this` < `rhs`
+ inline bool operator<(const Source& rhs) {
+ if (file != rhs.file) {
+ return false;
+ }
+ return range.begin < rhs.range.begin;
+ }
+
+ /// Helper function that returns the range union of two source locations. The
+ /// `start` and `end` locations are assumed to refer to the same source file.
+ /// @param start the start source of the range
+ /// @param end the end source of the range
+ /// @returns the combined source
+ inline static Source Combine(const Source& start, const Source& end) {
+ return Source(Source::Range(start.range.begin, end.range.end), start.file);
+ }
+
+ /// range is the span of text this source refers to in #file
+ Range range;
+ /// file is the optional source content this source refers to
+ const File* file = nullptr;
+};
+
+/// Writes the Source::Location to the stream.
+/// @param out the stream to write to
+/// @param loc the location to write
+/// @returns out so calls can be chained
+inline utils::StringStream& operator<<(utils::StringStream& out, const Source::Location& loc) {
+ out << loc.line << ":" << loc.column;
+ return out;
+}
+
+/// Writes the Source::Range to the stream.
+/// @param out the stream to write to
+/// @param range the range to write
+/// @returns out so calls can be chained
+inline utils::StringStream& operator<<(utils::StringStream& out, const Source::Range& range) {
+ out << "[" << range.begin << ", " << range.end << "]";
+ return out;
+}
+
+/// Writes the Source to the stream.
+/// @param out the stream to write to
+/// @param source the source to write
+/// @returns out so calls can be chained
+utils::StringStream& operator<<(utils::StringStream& out, const Source& source);
+
+/// Writes the Source::FileContent to the stream.
+/// @param out the stream to write to
+/// @param content the file content to write
+/// @returns out so calls can be chained
+inline utils::StringStream& operator<<(utils::StringStream& out,
+ const Source::FileContent& content) {
+ out << content.data;
+ return out;
+}
+
+} // namespace tint
+
+#endif // SRC_TINT_UTILS_DIAGNOSTIC_SOURCE_H_
diff --git a/src/tint/utils/diagnostic/source_test.cc b/src/tint/utils/diagnostic/source_test.cc
new file mode 100644
index 0000000..8d9ceed
--- /dev/null
+++ b/src/tint/utils/diagnostic/source_test.cc
@@ -0,0 +1,99 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/diagnostic/source.h"
+
+#include <memory>
+#include <utility>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+static constexpr const char* kSource = R"(line one
+line two
+line three)";
+
+using SourceFileContentTest = testing::Test;
+
+TEST_F(SourceFileContentTest, Init) {
+ Source::FileContent fc(kSource);
+ EXPECT_EQ(fc.data, kSource);
+ ASSERT_EQ(fc.lines.size(), 3u);
+ EXPECT_EQ(fc.lines[0], "line one");
+ EXPECT_EQ(fc.lines[1], "line two");
+ EXPECT_EQ(fc.lines[2], "line three");
+}
+
+TEST_F(SourceFileContentTest, CopyInit) {
+ auto src = std::make_unique<Source::FileContent>(kSource);
+ Source::FileContent fc{*src};
+ src.reset();
+ EXPECT_EQ(fc.data, kSource);
+ ASSERT_EQ(fc.lines.size(), 3u);
+ EXPECT_EQ(fc.lines[0], "line one");
+ EXPECT_EQ(fc.lines[1], "line two");
+ EXPECT_EQ(fc.lines[2], "line three");
+}
+
+TEST_F(SourceFileContentTest, MoveInit) {
+ auto src = std::make_unique<Source::FileContent>(kSource);
+ Source::FileContent fc{std::move(*src)};
+ src.reset();
+ EXPECT_EQ(fc.data, kSource);
+ ASSERT_EQ(fc.lines.size(), 3u);
+ EXPECT_EQ(fc.lines[0], "line one");
+ EXPECT_EQ(fc.lines[1], "line two");
+ EXPECT_EQ(fc.lines[2], "line three");
+}
+
+// Line break code points
+#define kCR "\r"
+#define kLF "\n"
+#define kVTab "\x0B"
+#define kFF "\x0C"
+#define kNL "\xC2\x85"
+#define kLS "\xE2\x80\xA8"
+#define kPS "\xE2\x80\xA9"
+
+using LineBreakTest = testing::TestWithParam<const char*>;
+TEST_P(LineBreakTest, Single) {
+ std::string src = "line one";
+ src += GetParam();
+ src += "line two";
+
+ Source::FileContent fc(src);
+ EXPECT_EQ(fc.lines.size(), 2u);
+ EXPECT_EQ(fc.lines[0], "line one");
+ EXPECT_EQ(fc.lines[1], "line two");
+}
+TEST_P(LineBreakTest, Double) {
+ std::string src = "line one";
+ src += GetParam();
+ src += GetParam();
+ src += "line two";
+
+ Source::FileContent fc(src);
+ EXPECT_EQ(fc.lines.size(), 3u);
+ EXPECT_EQ(fc.lines[0], "line one");
+ EXPECT_EQ(fc.lines[1], "");
+ EXPECT_EQ(fc.lines[2], "line two");
+}
+INSTANTIATE_TEST_SUITE_P(SourceFileContentTest,
+ LineBreakTest,
+ testing::Values(kVTab, kFF, kNL, kLS, kPS, kLF, kCR, kCR kLF));
+
+} // namespace
+} // namespace tint
diff --git a/src/tint/utils/file/tmpfile.h b/src/tint/utils/file/tmpfile.h
new file mode 100644
index 0000000..ed83918
--- /dev/null
+++ b/src/tint/utils/file/tmpfile.h
@@ -0,0 +1,75 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_FILE_TMPFILE_H_
+#define SRC_TINT_UTILS_FILE_TMPFILE_H_
+
+#include <string>
+
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::utils {
+
+/// TmpFile constructs a temporary file that can be written to, and is
+/// automatically deleted on destruction.
+class TmpFile {
+ public:
+ /// Constructor.
+ /// Creates a new temporary file which can be written to.
+ /// The temporary file will be automatically deleted on destruction.
+ /// @param extension optional file extension to use with the file. The file
+ /// have no extension by default.
+ explicit TmpFile(std::string extension = "");
+
+ /// Destructor.
+ /// Deletes the temporary file.
+ ~TmpFile();
+
+ /// @return true if the temporary file was successfully created.
+ operator bool() { return !path_.empty(); }
+
+ /// @return the path to the temporary file
+ std::string Path() const { return path_; }
+
+ /// Opens the temporary file and appends |size| bytes from |data| to the end
+ /// of the temporary file. The temporary file is closed again before
+ /// returning, allowing other processes to open the file on operating systems
+ /// that require exclusive ownership of opened files.
+ /// @param data the data to write to the end of the file
+ /// @param size the number of bytes to write from data
+ /// @returns true on success, otherwise false
+ bool Append(const void* data, size_t size) const;
+
+ /// Appends the argument to the end of the file.
+ /// @param data the data to write to the end of the file
+ /// @return a reference to this TmpFile
+ template <typename T>
+ inline TmpFile& operator<<(T&& data) {
+ utils::StringStream ss;
+ ss << data;
+ std::string str = ss.str();
+ Append(str.data(), str.size());
+ return *this;
+ }
+
+ private:
+ TmpFile(const TmpFile&) = delete;
+ TmpFile& operator=(const TmpFile&) = delete;
+
+ std::string path_;
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_FILE_TMPFILE_H_
diff --git a/src/tint/utils/file/tmpfile_other.cc b/src/tint/utils/file/tmpfile_other.cc
new file mode 100644
index 0000000..1a7da4c
--- /dev/null
+++ b/src/tint/utils/file/tmpfile_other.cc
@@ -0,0 +1,27 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/file/tmpfile.h"
+
+namespace tint::utils {
+
+TmpFile::TmpFile(std::string) {}
+
+TmpFile::~TmpFile() = default;
+
+bool TmpFile::Append(const void*, size_t) const {
+ return false;
+}
+
+} // namespace tint::utils
diff --git a/src/tint/utils/file/tmpfile_posix.cc b/src/tint/utils/file/tmpfile_posix.cc
new file mode 100644
index 0000000..f26747d
--- /dev/null
+++ b/src/tint/utils/file/tmpfile_posix.cc
@@ -0,0 +1,66 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/file/tmpfile.h"
+
+#include <unistd.h>
+#include <limits>
+
+#include "src/tint/utils/debug/debug.h"
+
+namespace tint::utils {
+
+namespace {
+
+std::string TmpFilePath(std::string ext) {
+ char const* dir = getenv("TMPDIR");
+ if (dir == nullptr) {
+ dir = "/tmp";
+ }
+
+ // mkstemps requires an `int` for the file extension name but STL represents
+ // size_t. Pre-C++20 there the behavior for unsigned-to-signed conversion
+ // (when the source value exceeds the representable range) is implementation
+ // defined. While such a large file extension is unlikely in practice, we
+ // enforce this here at runtime.
+ TINT_ASSERT(Utils, ext.length() <= static_cast<size_t>(std::numeric_limits<int>::max()));
+ std::string name = std::string(dir) + "/tint_XXXXXX" + ext;
+ int file = mkstemps(&name[0], static_cast<int>(ext.length()));
+ if (file != -1) {
+ close(file);
+ return name;
+ }
+ return "";
+}
+
+} // namespace
+
+TmpFile::TmpFile(std::string extension) : path_(TmpFilePath(std::move(extension))) {}
+
+TmpFile::~TmpFile() {
+ if (!path_.empty()) {
+ remove(path_.c_str());
+ }
+}
+
+bool TmpFile::Append(const void* data, size_t size) const {
+ if (auto* file = fopen(path_.c_str(), "ab")) {
+ fwrite(data, size, 1, file);
+ fclose(file);
+ return true;
+ }
+ return false;
+}
+
+} // namespace tint::utils
diff --git a/src/tint/utils/file/tmpfile_test.cc b/src/tint/utils/file/tmpfile_test.cc
new file mode 100644
index 0000000..e2fff23
--- /dev/null
+++ b/src/tint/utils/file/tmpfile_test.cc
@@ -0,0 +1,88 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/file/tmpfile.h"
+
+#include <fstream>
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(TmpFileTest, WriteReadAppendDelete) {
+ std::string path;
+ {
+ TmpFile tmp;
+ if (!tmp) {
+ GTEST_SKIP() << "Unable to create a temporary file";
+ }
+
+ path = tmp.Path();
+
+ // Write a string to the temporary file
+ tmp << "hello world\n";
+
+ // Check the content of the file
+ {
+ std::ifstream file(path);
+ ASSERT_TRUE(file);
+ std::string line;
+ EXPECT_TRUE(std::getline(file, line));
+ EXPECT_EQ(line, "hello world");
+ EXPECT_FALSE(std::getline(file, line));
+ }
+
+ // Write some more content to the file
+ tmp << 42;
+
+ // Check the content of the file again
+ {
+ std::ifstream file(path);
+ ASSERT_TRUE(file);
+ std::string line;
+ EXPECT_TRUE(std::getline(file, line));
+ EXPECT_EQ(line, "hello world");
+ EXPECT_TRUE(std::getline(file, line));
+ EXPECT_EQ(line, "42");
+ EXPECT_FALSE(std::getline(file, line));
+ }
+ }
+
+ // Check the file has been deleted when it fell out of scope
+ std::ifstream file(path);
+ ASSERT_FALSE(file);
+}
+
+TEST(TmpFileTest, FileExtension) {
+ const std::string kExt = ".foo";
+ std::string path;
+ {
+ TmpFile tmp(kExt);
+ if (!tmp) {
+ GTEST_SKIP() << "Unable create a temporary file";
+ }
+ path = tmp.Path();
+ }
+
+ ASSERT_GT(path.length(), kExt.length());
+ EXPECT_EQ(kExt, path.substr(path.length() - kExt.length()));
+
+ // Check the file has been deleted when it fell out of scope
+ std::ifstream file(path);
+ ASSERT_FALSE(file);
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/file/tmpfile_windows.cc b/src/tint/utils/file/tmpfile_windows.cc
new file mode 100644
index 0000000..0a4598e
--- /dev/null
+++ b/src/tint/utils/file/tmpfile_windows.cc
@@ -0,0 +1,61 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/file/tmpfile.h"
+
+#include <stdio.h>
+#include <cstdio>
+
+namespace tint::utils {
+
+namespace {
+
+std::string TmpFilePath(const std::string& ext) {
+ char name[L_tmpnam];
+ // As we're adding an extension, to ensure the file is really unique, try
+ // creating it, failing if it already exists.
+ while (tmpnam_s(name, L_tmpnam - 1) == 0) {
+ std::string name_with_ext = std::string(name) + ext;
+ FILE* f = nullptr;
+ // The "x" arg forces the function to fail if the file already exists.
+ fopen_s(&f, name_with_ext.c_str(), "wbx");
+ if (f) {
+ fclose(f);
+ return name_with_ext;
+ }
+ }
+ return {};
+}
+
+} // namespace
+
+TmpFile::TmpFile(std::string ext) : path_(TmpFilePath(ext)) {}
+
+TmpFile::~TmpFile() {
+ if (!path_.empty()) {
+ remove(path_.c_str());
+ }
+}
+
+bool TmpFile::Append(const void* data, size_t size) const {
+ FILE* file = nullptr;
+ if (fopen_s(&file, path_.c_str(), "ab") != 0) {
+ return false;
+ }
+ fwrite(data, size, 1, file);
+ fclose(file);
+ return true;
+}
+
+} // namespace tint::utils
diff --git a/src/tint/utils/macros/compiler.h b/src/tint/utils/macros/compiler.h
new file mode 100644
index 0000000..f0af760
--- /dev/null
+++ b/src/tint/utils/macros/compiler.h
@@ -0,0 +1,110 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/macros/concat.h"
+
+#ifndef SRC_TINT_UTILS_MACROS_COMPILER_H_
+#define SRC_TINT_UTILS_MACROS_COMPILER_H_
+
+#define TINT_REQUIRE_SEMICOLON static_assert(true)
+
+#if defined(_MSC_VER) && !defined(__clang__)
+////////////////////////////////////////////////////////////////////////////////
+// MSVC
+////////////////////////////////////////////////////////////////////////////////
+#define TINT_DISABLE_WARNING_CONSTANT_OVERFLOW __pragma(warning(disable : 4756))
+#define TINT_DISABLE_WARNING_MAYBE_UNINITIALIZED /* currently no-op */
+#define TINT_DISABLE_WARNING_NEWLINE_EOF /* currently no-op */
+#define TINT_DISABLE_WARNING_OLD_STYLE_CAST /* currently no-op */
+#define TINT_DISABLE_WARNING_SIGN_CONVERSION /* currently no-op */
+#define TINT_DISABLE_WARNING_UNREACHABLE_CODE __pragma(warning(disable : 4702))
+#define TINT_DISABLE_WARNING_WEAK_VTABLES /* currently no-op */
+#define TINT_DISABLE_WARNING_FLOAT_EQUAL /* currently no-op */
+
+// clang-format off
+#define TINT_BEGIN_DISABLE_WARNING(name) \
+ __pragma(warning(push)) \
+ TINT_CONCAT(TINT_DISABLE_WARNING_, name) \
+ TINT_REQUIRE_SEMICOLON
+#define TINT_END_DISABLE_WARNING(name) \
+ __pragma(warning(pop)) \
+ TINT_REQUIRE_SEMICOLON
+// clang-format on
+
+#define TINT_UNLIKELY(x) x /* currently no-op */
+#define TINT_LIKELY(x) x /* currently no-op */
+#elif defined(__clang__)
+////////////////////////////////////////////////////////////////////////////////
+// Clang
+////////////////////////////////////////////////////////////////////////////////
+#define TINT_DISABLE_WARNING_CONSTANT_OVERFLOW /* currently no-op */
+#define TINT_DISABLE_WARNING_MAYBE_UNINITIALIZED /* currently no-op */
+#define TINT_DISABLE_WARNING_NEWLINE_EOF _Pragma("clang diagnostic ignored \"-Wnewline-eof\"")
+#define TINT_DISABLE_WARNING_OLD_STYLE_CAST _Pragma("clang diagnostic ignored \"-Wold-style-cast\"")
+#define TINT_DISABLE_WARNING_SIGN_CONVERSION \
+ _Pragma("clang diagnostic ignored \"-Wsign-conversion\"")
+#define TINT_DISABLE_WARNING_UNREACHABLE_CODE /* currently no-op */
+#define TINT_DISABLE_WARNING_WEAK_VTABLES _Pragma("clang diagnostic ignored \"-Wweak-vtables\"")
+#define TINT_DISABLE_WARNING_FLOAT_EQUAL _Pragma("clang diagnostic ignored \"-Wfloat-equal\"")
+
+// clang-format off
+#define TINT_BEGIN_DISABLE_WARNING(name) \
+ _Pragma("clang diagnostic push") \
+ TINT_CONCAT(TINT_DISABLE_WARNING_, name) \
+ TINT_REQUIRE_SEMICOLON
+#define TINT_END_DISABLE_WARNING(name) \
+ _Pragma("clang diagnostic pop") \
+ TINT_REQUIRE_SEMICOLON
+// clang-format on
+
+#define TINT_UNLIKELY(x) __builtin_expect(!!(x), false)
+#define TINT_LIKELY(x) __builtin_expect(!!(x), true)
+#elif defined(__GNUC__)
+////////////////////////////////////////////////////////////////////////////////
+// GCC
+////////////////////////////////////////////////////////////////////////////////
+#define TINT_DISABLE_WARNING_CONSTANT_OVERFLOW /* currently no-op */
+#define TINT_DISABLE_WARNING_MAYBE_UNINITIALIZED \
+ _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+#define TINT_DISABLE_WARNING_NEWLINE_EOF /* currently no-op */
+#define TINT_DISABLE_WARNING_OLD_STYLE_CAST /* currently no-op */
+#define TINT_DISABLE_WARNING_SIGN_CONVERSION /* currently no-op */
+#define TINT_DISABLE_WARNING_UNREACHABLE_CODE /* currently no-op */
+#define TINT_DISABLE_WARNING_WEAK_VTABLES /* currently no-op */
+#define TINT_DISABLE_WARNING_FLOAT_EQUAL /* currently no-op */
+
+// clang-format off
+#define TINT_BEGIN_DISABLE_WARNING(name) \
+ _Pragma("GCC diagnostic push") \
+ TINT_CONCAT(TINT_DISABLE_WARNING_, name) \
+ TINT_REQUIRE_SEMICOLON
+#define TINT_END_DISABLE_WARNING(name) \
+ _Pragma("GCC diagnostic pop") \
+ TINT_REQUIRE_SEMICOLON
+// clang-format on
+
+#define TINT_UNLIKELY(x) __builtin_expect(!!(x), false)
+#define TINT_LIKELY(x) __builtin_expect(!!(x), true)
+#else
+////////////////////////////////////////////////////////////////////////////////
+// Other
+////////////////////////////////////////////////////////////////////////////////
+#define TINT_BEGIN_DISABLE_WARNING(name) TINT_REQUIRE_SEMICOLON
+#define TINT_END_DISABLE_WARNING(name) TINT_REQUIRE_SEMICOLON
+#define TINT_UNLIKELY(x) x
+#define TINT_LIKELY(x) x
+
+#endif
+
+#endif // SRC_TINT_UTILS_MACROS_COMPILER_H_
diff --git a/src/tint/utils/macros/concat.h b/src/tint/utils/macros/concat.h
new file mode 100644
index 0000000..56b47ce
--- /dev/null
+++ b/src/tint/utils/macros/concat.h
@@ -0,0 +1,22 @@
+
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MACROS_CONCAT_H_
+#define SRC_TINT_UTILS_MACROS_CONCAT_H_
+
+#define TINT_CONCAT_2(a, b) a##b
+#define TINT_CONCAT(a, b) TINT_CONCAT_2(a, b)
+
+#endif // SRC_TINT_UTILS_MACROS_CONCAT_H_
diff --git a/src/tint/utils/macros/defer.h b/src/tint/utils/macros/defer.h
new file mode 100644
index 0000000..7320f87
--- /dev/null
+++ b/src/tint/utils/macros/defer.h
@@ -0,0 +1,60 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MACROS_DEFER_H_
+#define SRC_TINT_UTILS_MACROS_DEFER_H_
+
+#include <utility>
+
+#include "src/tint/utils/macros/concat.h"
+
+namespace tint::utils {
+
+/// Defer executes a function or function like object when it is destructed.
+template <typename F>
+class Defer {
+ public:
+ /// Constructor
+ /// @param f the function to call when the Defer is destructed
+ explicit Defer(F&& f) : f_(std::move(f)) {}
+
+ /// Move constructor
+ Defer(Defer&&) = default;
+
+ /// Destructor
+ /// Calls the deferred function
+ ~Defer() { f_(); }
+
+ private:
+ Defer(const Defer&) = delete;
+ Defer& operator=(const Defer&) = delete;
+
+ F f_;
+};
+
+/// Constructor
+/// @param f the function to call when the Defer is destructed
+template <typename F>
+inline Defer<F> MakeDefer(F&& f) {
+ return Defer<F>(std::forward<F>(f));
+}
+
+} // namespace tint::utils
+
+/// TINT_DEFER(S) executes the statement(s) `S` when exiting the current lexical
+/// scope.
+#define TINT_DEFER(S) \
+ auto TINT_CONCAT(tint_defer_, __COUNTER__) = ::tint::utils::MakeDefer([&] { S; })
+
+#endif // SRC_TINT_UTILS_MACROS_DEFER_H_
diff --git a/src/tint/utils/macros/defer_test.cc b/src/tint/utils/macros/defer_test.cc
new file mode 100644
index 0000000..f131cc8
--- /dev/null
+++ b/src/tint/utils/macros/defer_test.cc
@@ -0,0 +1,42 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/macros/defer.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(DeferTest, Basic) {
+ bool deferCalled = false;
+ { TINT_DEFER(deferCalled = true); }
+ ASSERT_TRUE(deferCalled);
+}
+
+TEST(DeferTest, DeferOrder) {
+ int counter = 0;
+ int a = 0, b = 0, c = 0;
+ {
+ TINT_DEFER(a = ++counter);
+ TINT_DEFER(b = ++counter);
+ TINT_DEFER(c = ++counter);
+ }
+ ASSERT_EQ(a, 3);
+ ASSERT_EQ(b, 2);
+ ASSERT_EQ(c, 1);
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/macros/foreach.h b/src/tint/utils/macros/foreach.h
new file mode 100644
index 0000000..adfc974
--- /dev/null
+++ b/src/tint/utils/macros/foreach.h
@@ -0,0 +1,91 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MACROS_FOREACH_H_
+#define SRC_TINT_UTILS_MACROS_FOREACH_H_
+
+// Macro magic to perform macro variadic dispatch.
+// See:
+// https://renenyffenegger.ch/notes/development/languages/C-C-plus-plus/preprocessor/macros/__VA_ARGS__/count-arguments
+// Note, this doesn't attempt to use the ##__VA_ARGS__ trick to handle 0
+
+// Helper macro to force expanding __VA_ARGS__ to satisfy MSVC compiler.
+#define TINT_MSVC_EXPAND_BUG(X) X
+
+/// TINT_COUNT_ARGUMENTS_NTH_ARG is used by TINT_COUNT_ARGUMENTS to get the number of arguments in a
+/// variadic macro call.
+#define TINT_COUNT_ARGUMENTS_NTH_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, \
+ _15, _16, N, ...) \
+ N
+
+/// TINT_COUNT_ARGUMENTS evaluates to the number of arguments passed to the macro
+#define TINT_COUNT_ARGUMENTS(...) \
+ TINT_MSVC_EXPAND_BUG(TINT_COUNT_ARGUMENTS_NTH_ARG(__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, \
+ 8, 7, 6, 5, 4, 3, 2, 1, 0))
+
+// Correctness checks.
+static_assert(1 == TINT_COUNT_ARGUMENTS(a), "TINT_COUNT_ARGUMENTS broken");
+static_assert(2 == TINT_COUNT_ARGUMENTS(a, b), "TINT_COUNT_ARGUMENTS broken");
+static_assert(3 == TINT_COUNT_ARGUMENTS(a, b, c), "TINT_COUNT_ARGUMENTS broken");
+
+/// TINT_FOREACH calls CB with each of the variadic arguments.
+#define TINT_FOREACH(CB, ...) \
+ TINT_MSVC_EXPAND_BUG( \
+ TINT_CONCAT(TINT_FOREACH_, TINT_COUNT_ARGUMENTS(__VA_ARGS__))(CB, __VA_ARGS__))
+
+#define TINT_FOREACH_1(CB, _1) CB(_1)
+#define TINT_FOREACH_2(CB, _1, _2) \
+ TINT_FOREACH_1(CB, _1) \
+ CB(_2)
+#define TINT_FOREACH_3(CB, _1, _2, _3) \
+ TINT_FOREACH_2(CB, _1, _2) \
+ CB(_3)
+#define TINT_FOREACH_4(CB, _1, _2, _3, _4) \
+ TINT_FOREACH_3(CB, _1, _2, _3) \
+ CB(_4)
+#define TINT_FOREACH_5(CB, _1, _2, _3, _4, _5) \
+ TINT_FOREACH_4(CB, _1, _2, _3, _4) \
+ CB(_5)
+#define TINT_FOREACH_6(CB, _1, _2, _3, _4, _5, _6) \
+ TINT_FOREACH_5(CB, _1, _2, _3, _4, _5) \
+ CB(_6)
+#define TINT_FOREACH_7(CB, _1, _2, _3, _4, _5, _6, _7) \
+ TINT_FOREACH_6(CB, _1, _2, _3, _4, _5, _6) \
+ CB(_7)
+#define TINT_FOREACH_8(CB, _1, _2, _3, _4, _5, _6, _7, _8) \
+ TINT_FOREACH_7(CB, _1, _2, _3, _4, _5, _6, _7) \
+ CB(_8)
+#define TINT_FOREACH_9(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
+ TINT_FOREACH_8(CB, _1, _2, _3, _4, _5, _6, _7, _8) \
+ CB(_9)
+#define TINT_FOREACH_10(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
+ TINT_FOREACH_9(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
+ CB(_10)
+#define TINT_FOREACH_11(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
+ TINT_FOREACH_10(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
+ CB(_11)
+#define TINT_FOREACH_12(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
+ TINT_FOREACH_11(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
+ CB(_12)
+#define TINT_FOREACH_13(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
+ TINT_FOREACH_11(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
+ CB(_13)
+#define TINT_FOREACH_14(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
+ TINT_FOREACH_11(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
+ CB(_14)
+#define TINT_FOREACH_15(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
+ TINT_FOREACH_11(CB, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
+ CB(_15)
+
+#endif // SRC_TINT_UTILS_MACROS_FOREACH_H_
diff --git a/src/tint/utils/macros/scoped_assignment.h b/src/tint/utils/macros/scoped_assignment.h
new file mode 100644
index 0000000..d5279f8
--- /dev/null
+++ b/src/tint/utils/macros/scoped_assignment.h
@@ -0,0 +1,62 @@
+
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MACROS_SCOPED_ASSIGNMENT_H_
+#define SRC_TINT_UTILS_MACROS_SCOPED_ASSIGNMENT_H_
+
+#include <type_traits>
+
+#include "src/tint/utils/macros/concat.h"
+
+namespace tint::utils {
+
+/// Helper class that temporarily assigns a value to a variable for the lifetime
+/// of the ScopedAssignment object. Once the ScopedAssignment is destructed, the
+/// original value is restored.
+template <typename T>
+class ScopedAssignment {
+ public:
+ /// Constructor
+ /// @param var the variable to temporarily assign a new value to
+ /// @param val the value to assign to `ref` for the lifetime of this
+ /// ScopedAssignment.
+ ScopedAssignment(T& var, T val) : ref_(var) {
+ old_value_ = var;
+ var = val;
+ }
+
+ /// Destructor
+ /// Restores the original value of the variable.
+ ~ScopedAssignment() { ref_ = old_value_; }
+
+ private:
+ ScopedAssignment(const ScopedAssignment&) = delete;
+ ScopedAssignment& operator=(const ScopedAssignment&) = delete;
+
+ T& ref_;
+ T old_value_;
+};
+
+} // namespace tint::utils
+
+/// TINT_SCOPED_ASSIGNMENT(var, val) assigns `val` to `var`, and automatically
+/// restores the original value of `var` when exiting the current lexical scope.
+#define TINT_SCOPED_ASSIGNMENT(var, val) \
+ ::tint::utils::ScopedAssignment<std::remove_reference_t<decltype(var)>> TINT_CONCAT( \
+ tint_scoped_assignment_, __COUNTER__) { \
+ var, val \
+ }
+
+#endif // SRC_TINT_UTILS_MACROS_SCOPED_ASSIGNMENT_H_
diff --git a/src/tint/utils/macros/scoped_assignment_test.cc b/src/tint/utils/macros/scoped_assignment_test.cc
new file mode 100644
index 0000000..fa3b4ad
--- /dev/null
+++ b/src/tint/utils/macros/scoped_assignment_test.cc
@@ -0,0 +1,45 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/macros/scoped_assignment.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(ScopedAssignmentTest, Scopes) {
+ int i = 0;
+ EXPECT_EQ(i, 0);
+ {
+ EXPECT_EQ(i, 0);
+ TINT_SCOPED_ASSIGNMENT(i, 1);
+ EXPECT_EQ(i, 1);
+ {
+ EXPECT_EQ(i, 1);
+ TINT_SCOPED_ASSIGNMENT(i, 2);
+ EXPECT_EQ(i, 2);
+ }
+ {
+ EXPECT_EQ(i, 1);
+ TINT_SCOPED_ASSIGNMENT(i, 3);
+ EXPECT_EQ(i, 3);
+ }
+ EXPECT_EQ(i, 1);
+ }
+ EXPECT_EQ(i, 0);
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/math/crc32.h b/src/tint/utils/math/crc32.h
new file mode 100644
index 0000000..022eefd
--- /dev/null
+++ b/src/tint/utils/math/crc32.h
@@ -0,0 +1,81 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MATH_CRC32_H_
+#define SRC_TINT_UTILS_MATH_CRC32_H_
+
+#include <stdint.h>
+#include <cstddef>
+
+namespace tint::utils {
+
+constexpr uint32_t kCRC32LUT[] = {
+ 0, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
+
+/// @returns the CRC32 of the string @p s.
+/// @note this function can be used to calculate the CRC32 of a string literal
+/// at compile time.
+/// @see https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm
+constexpr uint32_t CRC32(const char* s) {
+ uint32_t crc = 0xffffffff;
+ for (auto* p = s; *p != '\0'; ++p) {
+ crc = (crc >> 8) ^ kCRC32LUT[static_cast<uint8_t>(crc) ^ static_cast<uint8_t>(*p)];
+ }
+ return crc ^ 0xffffffff;
+}
+
+/// @returns the CRC32 of the data at @p ptr of size @p size.
+inline uint32_t CRC32(const void* ptr, size_t size) {
+ auto* p = static_cast<const uint8_t*>(ptr);
+ uint32_t crc = 0xffffffff;
+ while (size--) {
+ crc = (crc >> 8) ^ kCRC32LUT[static_cast<uint8_t>(crc) ^ *p++];
+ }
+ return crc ^ 0xffffffff;
+}
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_MATH_CRC32_H_
diff --git a/src/tint/utils/math/crc32_test.cc b/src/tint/utils/math/crc32_test.cc
new file mode 100644
index 0000000..f029108
--- /dev/null
+++ b/src/tint/utils/math/crc32_test.cc
@@ -0,0 +1,35 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/math/crc32.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(CRC32Test, Compiletime) {
+ static_assert(CRC32("") == 0x00000000u);
+ static_assert(CRC32("hello world") == 0x0d4a1185u);
+ static_assert(CRC32("123456789") == 0xcbf43926u);
+}
+
+TEST(CRC32Test, Runtime) {
+ EXPECT_EQ(CRC32(""), 0x00000000u);
+ EXPECT_EQ(CRC32("hello world"), 0x0d4a1185u);
+ EXPECT_EQ(CRC32("123456789"), 0xcbf43926u);
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/math/hash.h b/src/tint/utils/math/hash.h
new file mode 100644
index 0000000..74f053b
--- /dev/null
+++ b/src/tint/utils/math/hash.h
@@ -0,0 +1,301 @@
+// Copyright 2021 The Tint Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MATH_HASH_H_
+#define SRC_TINT_UTILS_MATH_HASH_H_
+
+#include <stdint.h>
+#include <cstdio>
+#include <functional>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/math/crc32.h"
+
+namespace tint::utils {
+namespace detail {
+
+/// Helper for obtaining a seed bias value for HashCombine with a bit-width
+/// dependent on the size of size_t.
+template <int SIZE_OF_SIZE_T>
+struct HashCombineOffset {};
+
+/// Specialization of HashCombineOffset for size_t == 4.
+template <>
+struct HashCombineOffset<4> {
+ /// @returns the seed bias value for HashCombine()
+ static constexpr inline uint32_t value() {
+ constexpr uint32_t base = 0x7f4a7c16;
+#ifdef TINT_HASH_SEED
+ return base ^ static_cast<uint32_t>(TINT_HASH_SEED);
+#endif
+ return base;
+ }
+};
+
+/// Specialization of HashCombineOffset for size_t == 8.
+template <>
+struct HashCombineOffset<8> {
+ /// @returns the seed bias value for HashCombine()
+ static constexpr inline uint64_t value() {
+ constexpr uint64_t base = 0x9e3779b97f4a7c16;
+#ifdef TINT_HASH_SEED
+ return base ^ static_cast<uint64_t>(TINT_HASH_SEED);
+#endif
+ return base;
+ }
+};
+
+} // namespace detail
+
+/// Forward declarations (see below)
+template <typename... ARGS>
+size_t Hash(const ARGS&... values);
+
+template <typename... ARGS>
+size_t HashCombine(size_t hash, const ARGS&... values);
+
+/// A STL-compatible hasher that does a more thorough job than most implementations of std::hash.
+/// Hasher has been optimized for a better quality hash at the expense of increased computation
+/// costs.
+template <typename T>
+struct Hasher {
+ /// @param value the value to hash
+ /// @returns a hash of the value
+ size_t operator()(const T& value) const { return std::hash<T>()(value); }
+};
+
+/// Hasher specialization for pointers
+/// std::hash<T*> typically uses a reinterpret of the pointer to a size_t.
+/// As most pointers a 4 or 16 byte aligned, this usually results in the LSBs of the hash being 0,
+/// resulting in bad hashes for hashtables. This implementation mixes up those LSBs.
+template <typename T>
+struct Hasher<T*> {
+ /// @param ptr the pointer to hash
+ /// @returns a hash of the pointer
+ size_t operator()(T* ptr) const {
+ auto hash = std::hash<T*>()(ptr);
+#ifdef TINT_HASH_SEED
+ hash ^= static_cast<uint32_t>(TINT_HASH_SEED);
+#endif
+ return hash ^ (hash >> 4);
+ }
+};
+
+/// Hasher specialization for std::vector
+template <typename T>
+struct Hasher<std::vector<T>> {
+ /// @param vector the vector to hash
+ /// @returns a hash of the vector
+ size_t operator()(const std::vector<T>& vector) const {
+ auto hash = Hash(vector.size());
+ for (auto& el : vector) {
+ hash = HashCombine(hash, el);
+ }
+ return hash;
+ }
+};
+
+/// Hasher specialization for utils::Vector
+template <typename T, size_t N>
+struct Hasher<utils::Vector<T, N>> {
+ /// @param vector the Vector to hash
+ /// @returns a hash of the Vector
+ size_t operator()(const utils::Vector<T, N>& vector) const {
+ auto hash = Hash(vector.Length());
+ for (auto& el : vector) {
+ hash = HashCombine(hash, el);
+ }
+ return hash;
+ }
+};
+
+/// Hasher specialization for utils::VectorRef
+template <typename T>
+struct Hasher<utils::VectorRef<T>> {
+ /// @param vector the VectorRef reference to hash
+ /// @returns a hash of the Vector
+ size_t operator()(const utils::VectorRef<T>& vector) const {
+ auto hash = Hash(vector.Length());
+ for (auto& el : vector) {
+ hash = HashCombine(hash, el);
+ }
+ return hash;
+ }
+};
+
+/// Hasher specialization for std::tuple
+template <typename... TYPES>
+struct Hasher<std::tuple<TYPES...>> {
+ /// @param tuple the tuple to hash
+ /// @returns a hash of the tuple
+ size_t operator()(const std::tuple<TYPES...>& tuple) const {
+ return std::apply(Hash<TYPES...>, tuple);
+ }
+};
+
+/// Hasher specialization for std::pair
+template <typename A, typename B>
+struct Hasher<std::pair<A, B>> {
+ /// @param tuple the tuple to hash
+ /// @returns a hash of the tuple
+ size_t operator()(const std::pair<A, B>& tuple) const { return std::apply(Hash<A, B>, tuple); }
+};
+
+/// Hasher specialization for std::variant
+template <typename... TYPES>
+struct Hasher<std::variant<TYPES...>> {
+ /// @param variant the variant to hash
+ /// @returns a hash of the tuple
+ size_t operator()(const std::variant<TYPES...>& variant) const {
+ return std::visit([](auto&& val) { return Hash(val); }, variant);
+ }
+};
+
+/// Hasher specialization for std::string, which also supports hashing of const char* and
+/// std::string_view without first constructing a std::string.
+template <>
+struct Hasher<std::string> {
+ /// @param str the string to hash
+ /// @returns a hash of the string
+ size_t operator()(const std::string& str) const {
+ return std::hash<std::string_view>()(std::string_view(str));
+ }
+
+ /// @param str the string to hash
+ /// @returns a hash of the string
+ size_t operator()(const char* str) const {
+ return std::hash<std::string_view>()(std::string_view(str));
+ }
+
+ /// @param str the string to hash
+ /// @returns a hash of the string
+ size_t operator()(const std::string_view& str) const {
+ return std::hash<std::string_view>()(str);
+ }
+};
+
+/// @returns a hash of the variadic list of arguments.
+/// The returned hash is dependent on the order of the arguments.
+template <typename... ARGS>
+size_t Hash(const ARGS&... args) {
+ if constexpr (sizeof...(ARGS) == 0) {
+ return 0;
+ } else if constexpr (sizeof...(ARGS) == 1) {
+ using T = std::tuple_element_t<0, std::tuple<ARGS...>>;
+ return Hasher<T>()(args...);
+ } else {
+ size_t hash = 102931; // seed with an arbitrary prime
+ return HashCombine(hash, args...);
+ }
+}
+
+/// @returns a hash of the variadic list of arguments.
+/// The returned hash is dependent on the order of the arguments.
+template <typename... ARGS>
+size_t HashCombine(size_t hash, const ARGS&... values) {
+ constexpr size_t offset = utils::detail::HashCombineOffset<sizeof(size_t)>::value();
+ ((hash ^= Hash(values) + (offset ^ (hash >> 2))), ...);
+ return hash;
+}
+
+/// A STL-compatible equal_to implementation that specializes for types.
+template <typename T>
+struct EqualTo {
+ /// @param lhs the left hand side value
+ /// @param rhs the right hand side value
+ /// @returns true if the two values are equal
+ constexpr bool operator()(const T& lhs, const T& rhs) const {
+ return std::equal_to<T>()(lhs, rhs);
+ }
+};
+
+/// A specialization for EqualTo for std::string, which supports additional comparision with
+/// std::string_view and const char*.
+template <>
+struct EqualTo<std::string> {
+ /// @param lhs the left hand side value
+ /// @param rhs the right hand side value
+ /// @returns true if the two values are equal
+ bool operator()(const std::string& lhs, const std::string& rhs) const { return lhs == rhs; }
+
+ /// @param lhs the left hand side value
+ /// @param rhs the right hand side value
+ /// @returns true if the two values are equal
+ bool operator()(const std::string& lhs, const char* rhs) const { return lhs == rhs; }
+
+ /// @param lhs the left hand side value
+ /// @param rhs the right hand side value
+ /// @returns true if the two values are equal
+ bool operator()(const std::string& lhs, std::string_view rhs) const { return lhs == rhs; }
+
+ /// @param lhs the left hand side value
+ /// @param rhs the right hand side value
+ /// @returns true if the two values are equal
+ bool operator()(const char* lhs, const std::string& rhs) const { return lhs == rhs; }
+
+ /// @param lhs the left hand side value
+ /// @param rhs the right hand side value
+ /// @returns true if the two values are equal
+ bool operator()(std::string_view lhs, const std::string& rhs) const { return lhs == rhs; }
+};
+
+/// Wrapper for a hashable type enabling the wrapped value to be used as a key
+/// for an unordered_map or unordered_set.
+template <typename T>
+struct UnorderedKeyWrapper {
+ /// The wrapped value
+ T value;
+ /// The hash of value
+ size_t hash;
+
+ /// Constructor
+ /// @param v the value to wrap
+ explicit UnorderedKeyWrapper(const T& v) : value(v), hash(Hash(v)) {}
+
+ /// Move constructor
+ /// @param v the value to wrap
+ explicit UnorderedKeyWrapper(T&& v) : value(std::move(v)), hash(Hash(value)) {}
+
+ /// @returns true if this wrapper comes before other
+ /// @param other the RHS of the operator
+ bool operator<(const UnorderedKeyWrapper& other) const { return hash < other.hash; }
+
+ /// @returns true if this wrapped value is equal to the other wrapped value
+ /// @param other the RHS of the operator
+ bool operator==(const UnorderedKeyWrapper& other) const { return value == other.value; }
+};
+
+} // namespace tint::utils
+
+namespace std {
+
+/// Custom std::hash specialization for tint::utils::UnorderedKeyWrapper
+template <typename T>
+class hash<tint::utils::UnorderedKeyWrapper<T>> {
+ public:
+ /// @param w the UnorderedKeyWrapper
+ /// @return the hash value
+ inline std::size_t operator()(const tint::utils::UnorderedKeyWrapper<T>& w) const {
+ return w.hash;
+ }
+};
+
+} // namespace std
+
+#endif // SRC_TINT_UTILS_MATH_HASH_H_
diff --git a/src/tint/utils/math/hash_test.cc b/src/tint/utils/math/hash_test.cc
new file mode 100644
index 0000000..7083170
--- /dev/null
+++ b/src/tint/utils/math/hash_test.cc
@@ -0,0 +1,128 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/math/hash.h"
+
+#include <string>
+#include <tuple>
+#include <unordered_map>
+
+#include "gtest/gtest.h"
+#include "src/tint/utils/containers/vector.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(HashTests, Basic) {
+ EXPECT_EQ(Hash(123), Hash(123));
+ EXPECT_EQ(Hash(123, 456), Hash(123, 456));
+ EXPECT_EQ(Hash(123, 456, false), Hash(123, 456, false));
+ EXPECT_EQ(Hash(std::string("hello")), Hash(std::string("hello")));
+}
+
+TEST(HashTests, StdVector) {
+ EXPECT_EQ(Hash(std::vector<int>({})), Hash(std::vector<int>({})));
+ EXPECT_EQ(Hash(std::vector<int>({1, 2, 3})), Hash(std::vector<int>({1, 2, 3})));
+}
+
+TEST(HashTests, TintVector) {
+ EXPECT_EQ(Hash(Vector<int, 0>({})), Hash(Vector<int, 0>({})));
+ EXPECT_EQ(Hash(Vector<int, 0>({1, 2, 3})), Hash(Vector<int, 0>({1, 2, 3})));
+ EXPECT_EQ(Hash(Vector<int, 3>({1, 2, 3})), Hash(Vector<int, 4>({1, 2, 3})));
+ EXPECT_EQ(Hash(Vector<int, 3>({1, 2, 3})), Hash(Vector<int, 2>({1, 2, 3})));
+}
+
+TEST(HashTests, TintVectorRef) {
+ EXPECT_EQ(Hash(VectorRef<int>(Vector<int, 0>({}))), Hash(VectorRef<int>(Vector<int, 0>({}))));
+ EXPECT_EQ(Hash(VectorRef<int>(Vector<int, 0>({1, 2, 3}))),
+ Hash(VectorRef<int>(Vector<int, 0>({1, 2, 3}))));
+ EXPECT_EQ(Hash(VectorRef<int>(Vector<int, 3>({1, 2, 3}))),
+ Hash(VectorRef<int>(Vector<int, 4>({1, 2, 3}))));
+ EXPECT_EQ(Hash(VectorRef<int>(Vector<int, 3>({1, 2, 3}))),
+ Hash(VectorRef<int>(Vector<int, 2>({1, 2, 3}))));
+
+ EXPECT_EQ(Hash(VectorRef<int>(Vector<int, 0>({}))), Hash(Vector<int, 0>({})));
+ EXPECT_EQ(Hash(VectorRef<int>(Vector<int, 0>({1, 2, 3}))), Hash(Vector<int, 0>({1, 2, 3})));
+ EXPECT_EQ(Hash(VectorRef<int>(Vector<int, 3>({1, 2, 3}))), Hash(Vector<int, 4>({1, 2, 3})));
+ EXPECT_EQ(Hash(VectorRef<int>(Vector<int, 3>({1, 2, 3}))), Hash(Vector<int, 2>({1, 2, 3})));
+}
+
+TEST(HashTests, Tuple) {
+ EXPECT_EQ(Hash(std::make_tuple(1)), Hash(std::make_tuple(1)));
+ EXPECT_EQ(Hash(std::make_tuple(1, 2, 3)), Hash(std::make_tuple(1, 2, 3)));
+}
+
+TEST(HashTests, UnorderedKeyWrapper) {
+ using W = UnorderedKeyWrapper<std::vector<int>>;
+
+ std::unordered_map<W, int> m;
+
+ m.emplace(W{{1, 2}}, -1);
+ EXPECT_EQ(m.size(), 1u);
+ EXPECT_EQ(m[W({1, 2})], -1);
+
+ m.emplace(W{{3, 2}}, 1);
+ EXPECT_EQ(m.size(), 2u);
+ EXPECT_EQ(m[W({3, 2})], 1);
+ EXPECT_EQ(m[W({1, 2})], -1);
+
+ m.emplace(W{{100}}, 100);
+ EXPECT_EQ(m.size(), 3u);
+ EXPECT_EQ(m[W({100})], 100);
+ EXPECT_EQ(m[W({3, 2})], 1);
+ EXPECT_EQ(m[W({1, 2})], -1);
+
+ // Reversed vector element order
+ EXPECT_EQ(m[W({2, 3})], 0);
+ EXPECT_EQ(m[W({2, 1})], 0);
+}
+
+TEST(EqualTo, String) {
+ std::string str_a = "hello";
+ std::string str_b = "world";
+ const char* cstr_a = "hello";
+ const char* cstr_b = "world";
+ std::string_view sv_a = "hello";
+ std::string_view sv_b = "world";
+ EXPECT_TRUE(EqualTo<std::string>()(str_a, str_a));
+ EXPECT_TRUE(EqualTo<std::string>()(str_a, cstr_a));
+ EXPECT_TRUE(EqualTo<std::string>()(str_a, sv_a));
+ EXPECT_TRUE(EqualTo<std::string>()(str_a, str_a));
+ EXPECT_TRUE(EqualTo<std::string>()(cstr_a, str_a));
+ EXPECT_TRUE(EqualTo<std::string>()(sv_a, str_a));
+
+ EXPECT_FALSE(EqualTo<std::string>()(str_a, str_b));
+ EXPECT_FALSE(EqualTo<std::string>()(str_a, cstr_b));
+ EXPECT_FALSE(EqualTo<std::string>()(str_a, sv_b));
+ EXPECT_FALSE(EqualTo<std::string>()(str_a, str_b));
+ EXPECT_FALSE(EqualTo<std::string>()(cstr_a, str_b));
+ EXPECT_FALSE(EqualTo<std::string>()(sv_a, str_b));
+
+ EXPECT_FALSE(EqualTo<std::string>()(str_b, str_a));
+ EXPECT_FALSE(EqualTo<std::string>()(str_b, cstr_a));
+ EXPECT_FALSE(EqualTo<std::string>()(str_b, sv_a));
+ EXPECT_FALSE(EqualTo<std::string>()(str_b, str_a));
+ EXPECT_FALSE(EqualTo<std::string>()(cstr_b, str_a));
+ EXPECT_FALSE(EqualTo<std::string>()(sv_b, str_a));
+
+ EXPECT_TRUE(EqualTo<std::string>()(str_b, str_b));
+ EXPECT_TRUE(EqualTo<std::string>()(str_b, cstr_b));
+ EXPECT_TRUE(EqualTo<std::string>()(str_b, sv_b));
+ EXPECT_TRUE(EqualTo<std::string>()(str_b, str_b));
+ EXPECT_TRUE(EqualTo<std::string>()(cstr_b, str_b));
+ EXPECT_TRUE(EqualTo<std::string>()(sv_b, str_b));
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/math/math.h b/src/tint/utils/math/math.h
new file mode 100644
index 0000000..59bd36c
--- /dev/null
+++ b/src/tint/utils/math/math.h
@@ -0,0 +1,103 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MATH_MATH_H_
+#define SRC_TINT_UTILS_MATH_MATH_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <type_traits>
+
+namespace tint::utils {
+
+/// @param alignment the next multiple to round `value` to
+/// @param value the value to round to the next multiple of `alignment`
+/// @return `value` rounded to the next multiple of `alignment`
+/// @note `alignment` must be positive. An alignment of zero will cause a DBZ.
+template <typename T>
+inline constexpr T RoundUp(T alignment, T value) {
+ return ((value + alignment - 1) / alignment) * alignment;
+}
+
+/// @param value the value to check whether it is a power-of-two
+/// @returns true if `value` is a power-of-two
+/// @note `value` must be positive if `T` is signed
+template <typename T>
+inline constexpr bool IsPowerOfTwo(T value) {
+ return (value & (value - 1)) == 0;
+}
+
+/// @param value the input value
+/// @returns the base-2 logarithm of @p value
+inline constexpr uint32_t Log2(uint64_t value) {
+#if defined(__clang__) || defined(__GNUC__)
+ return 63 - static_cast<uint32_t>(__builtin_clzll(value));
+#elif defined(_MSC_VER) && !defined(__clang__) && __cplusplus >= 202002L // MSVC and C++20+
+ // note: std::is_constant_evaluated() added in C++20
+ // required here as _BitScanReverse64 is not constexpr
+ if (!std::is_constant_evaluated()) {
+ // NOLINTNEXTLINE(runtime/int)
+ if constexpr (sizeof(unsigned long) == 8) { // 64-bit
+ // NOLINTNEXTLINE(runtime/int)
+ unsigned long first_bit_index = 0;
+ _BitScanReverse64(&first_bit_index, value);
+ return first_bit_index;
+ } else { // 32-bit
+ // NOLINTNEXTLINE(runtime/int)
+ unsigned long first_bit_index = 0;
+ if (_BitScanReverse(&first_bit_index, value >> 32)) {
+ return first_bit_index + 32;
+ }
+ _BitScanReverse(&first_bit_index, value & 0xffffffff);
+ return first_bit_index;
+ }
+ }
+#endif
+
+ // Non intrinsic (slow) path. Supports constexpr evaluation.
+ for (uint64_t clz = 0; clz < 64; clz++) {
+ uint64_t bit = 63 - clz;
+ if (value & (static_cast<uint64_t>(1u) << bit)) {
+ return static_cast<uint32_t>(bit);
+ }
+ }
+ return 64;
+}
+
+/// @param value the input value
+/// @returns the next power of two number greater or equal to @p value
+inline constexpr uint64_t NextPowerOfTwo(uint64_t value) {
+ if (value <= 1) {
+ return 1;
+ } else {
+ return static_cast<uint64_t>(1) << (Log2(value - 1) + 1);
+ }
+}
+
+/// @param value the input value
+/// @returns the largest power of two that `value` is a multiple of
+template <typename T>
+inline std::enable_if_t<std::is_unsigned<T>::value, T> MaxAlignOf(T value) {
+ T pot = 1;
+ while (value && ((value & 1u) == 0)) {
+ pot <<= 1;
+ value >>= 1;
+ }
+ return pot;
+}
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_MATH_MATH_H_
diff --git a/src/tint/utils/math/math_test.cc b/src/tint/utils/math/math_test.cc
new file mode 100644
index 0000000..8da26ba
--- /dev/null
+++ b/src/tint/utils/math/math_test.cc
@@ -0,0 +1,145 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/math/math.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(MathTests, RoundUp) {
+ EXPECT_EQ(RoundUp(1, 0), 0);
+ EXPECT_EQ(RoundUp(1, 1), 1);
+ EXPECT_EQ(RoundUp(1, 2), 2);
+
+ EXPECT_EQ(RoundUp(1, 1), 1);
+ EXPECT_EQ(RoundUp(2, 1), 2);
+ EXPECT_EQ(RoundUp(3, 1), 3);
+ EXPECT_EQ(RoundUp(4, 1), 4);
+
+ EXPECT_EQ(RoundUp(1, 2), 2);
+ EXPECT_EQ(RoundUp(2, 2), 2);
+ EXPECT_EQ(RoundUp(3, 2), 3);
+ EXPECT_EQ(RoundUp(4, 2), 4);
+
+ EXPECT_EQ(RoundUp(1, 3), 3);
+ EXPECT_EQ(RoundUp(2, 3), 4);
+ EXPECT_EQ(RoundUp(3, 3), 3);
+ EXPECT_EQ(RoundUp(4, 3), 4);
+
+ EXPECT_EQ(RoundUp(1, 4), 4);
+ EXPECT_EQ(RoundUp(2, 4), 4);
+ EXPECT_EQ(RoundUp(3, 4), 6);
+ EXPECT_EQ(RoundUp(4, 4), 4);
+}
+
+TEST(MathTests, IsPowerOfTwo) {
+ EXPECT_EQ(IsPowerOfTwo(1), true);
+ EXPECT_EQ(IsPowerOfTwo(2), true);
+ EXPECT_EQ(IsPowerOfTwo(3), false);
+ EXPECT_EQ(IsPowerOfTwo(4), true);
+ EXPECT_EQ(IsPowerOfTwo(5), false);
+ EXPECT_EQ(IsPowerOfTwo(6), false);
+ EXPECT_EQ(IsPowerOfTwo(7), false);
+ EXPECT_EQ(IsPowerOfTwo(8), true);
+ EXPECT_EQ(IsPowerOfTwo(9), false);
+}
+
+TEST(MathTests, Log2) {
+ EXPECT_EQ(Log2(1), 0u);
+ EXPECT_EQ(Log2(2), 1u);
+ EXPECT_EQ(Log2(3), 1u);
+ EXPECT_EQ(Log2(4), 2u);
+ EXPECT_EQ(Log2(5), 2u);
+ EXPECT_EQ(Log2(6), 2u);
+ EXPECT_EQ(Log2(7), 2u);
+ EXPECT_EQ(Log2(8), 3u);
+ EXPECT_EQ(Log2(9), 3u);
+ EXPECT_EQ(Log2(0x7fffffffu), 30u);
+ EXPECT_EQ(Log2(0x80000000u), 31u);
+ EXPECT_EQ(Log2(0x80000001u), 31u);
+ EXPECT_EQ(Log2(0x7fffffffffffffffu), 62u);
+ EXPECT_EQ(Log2(0x8000000000000000u), 63u);
+
+ static_assert(Log2(1) == 0u);
+ static_assert(Log2(2) == 1u);
+ static_assert(Log2(3) == 1u);
+ static_assert(Log2(4) == 2u);
+ static_assert(Log2(5) == 2u);
+ static_assert(Log2(6) == 2u);
+ static_assert(Log2(7) == 2u);
+ static_assert(Log2(8) == 3u);
+ static_assert(Log2(9) == 3u);
+ static_assert(Log2(0x7fffffffu) == 30u);
+ static_assert(Log2(0x80000000u) == 31u);
+ static_assert(Log2(0x80000001u) == 31u);
+ static_assert(Log2(0x7fffffffffffffffu) == 62u);
+ static_assert(Log2(0x8000000000000000u) == 63u);
+}
+
+TEST(MathTests, NextPowerOfTwo) {
+ EXPECT_EQ(NextPowerOfTwo(0), 1u);
+ EXPECT_EQ(NextPowerOfTwo(1), 1u);
+ EXPECT_EQ(NextPowerOfTwo(2), 2u);
+ EXPECT_EQ(NextPowerOfTwo(3), 4u);
+ EXPECT_EQ(NextPowerOfTwo(4), 4u);
+ EXPECT_EQ(NextPowerOfTwo(5), 8u);
+ EXPECT_EQ(NextPowerOfTwo(6), 8u);
+ EXPECT_EQ(NextPowerOfTwo(7), 8u);
+ EXPECT_EQ(NextPowerOfTwo(8), 8u);
+ EXPECT_EQ(NextPowerOfTwo(9), 16u);
+ EXPECT_EQ(NextPowerOfTwo(0x7fffffffu), 0x80000000u);
+ EXPECT_EQ(NextPowerOfTwo(0x80000000u), 0x80000000u);
+ EXPECT_EQ(NextPowerOfTwo(0x80000001u), 0x100000000u);
+ EXPECT_EQ(NextPowerOfTwo(0x7fffffffffffffffu), 0x8000000000000000u);
+
+ static_assert(NextPowerOfTwo(0) == 1u);
+ static_assert(NextPowerOfTwo(1) == 1u);
+ static_assert(NextPowerOfTwo(2) == 2u);
+ static_assert(NextPowerOfTwo(3) == 4u);
+ static_assert(NextPowerOfTwo(4) == 4u);
+ static_assert(NextPowerOfTwo(5) == 8u);
+ static_assert(NextPowerOfTwo(6) == 8u);
+ static_assert(NextPowerOfTwo(7) == 8u);
+ static_assert(NextPowerOfTwo(8) == 8u);
+ static_assert(NextPowerOfTwo(9) == 16u);
+ static_assert(NextPowerOfTwo(0x7fffffffu) == 0x80000000u);
+ static_assert(NextPowerOfTwo(0x80000000u) == 0x80000000u);
+ static_assert(NextPowerOfTwo(0x80000001u) == 0x100000000u);
+ static_assert(NextPowerOfTwo(0x7fffffffffffffffu) == 0x8000000000000000u);
+}
+
+TEST(MathTests, MaxAlignOf) {
+ EXPECT_EQ(MaxAlignOf(0u), 1u);
+ EXPECT_EQ(MaxAlignOf(1u), 1u);
+ EXPECT_EQ(MaxAlignOf(2u), 2u);
+ EXPECT_EQ(MaxAlignOf(3u), 1u);
+ EXPECT_EQ(MaxAlignOf(4u), 4u);
+ EXPECT_EQ(MaxAlignOf(5u), 1u);
+ EXPECT_EQ(MaxAlignOf(6u), 2u);
+ EXPECT_EQ(MaxAlignOf(7u), 1u);
+ EXPECT_EQ(MaxAlignOf(8u), 8u);
+ EXPECT_EQ(MaxAlignOf(9u), 1u);
+ EXPECT_EQ(MaxAlignOf(10u), 2u);
+ EXPECT_EQ(MaxAlignOf(11u), 1u);
+ EXPECT_EQ(MaxAlignOf(12u), 4u);
+ EXPECT_EQ(MaxAlignOf(13u), 1u);
+ EXPECT_EQ(MaxAlignOf(14u), 2u);
+ EXPECT_EQ(MaxAlignOf(15u), 1u);
+ EXPECT_EQ(MaxAlignOf(16u), 16u);
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/memory/bitcast.h b/src/tint/utils/memory/bitcast.h
new file mode 100644
index 0000000..30f772b
--- /dev/null
+++ b/src/tint/utils/memory/bitcast.h
@@ -0,0 +1,53 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MEMORY_BITCAST_H_
+#define SRC_TINT_UTILS_MEMORY_BITCAST_H_
+
+#include <cstddef>
+#include <cstring>
+#include <type_traits>
+
+namespace tint::utils {
+
+/// Bitcast performs a cast of `from` to the `TO` type using a memcpy.
+/// This unsafe cast avoids triggering Clang's Control Flow Integrity checks.
+/// See: crbug.com/dawn/1406
+/// See: https://clang.llvm.org/docs/ControlFlowIntegrity.html#bad-cast-checking
+/// @param from the value to cast
+/// @tparam TO the value to cast to
+/// @returns the cast value
+template <typename TO, typename FROM>
+inline TO Bitcast(FROM&& from) {
+ static_assert(sizeof(FROM) == sizeof(TO));
+ // gcc warns in cases where either TO or FROM are classes, even if they are trivially
+ // copyable, with for example:
+ //
+ // error: ‘void* memcpy(void*, const void*, size_t)’ copying an object of
+ // non-trivial type ‘struct tint::Number<unsigned int>’ from an array of ‘float’
+ // [-Werror=class-memaccess]
+ //
+ // We avoid this by asserting that both types are indeed trivially copyable, and casting both
+ // args to std::byte*.
+ static_assert(std::is_trivially_copyable_v<std::decay_t<FROM>>);
+ static_assert(std::is_trivially_copyable_v<std::decay_t<TO>>);
+ TO to;
+ memcpy(reinterpret_cast<std::byte*>(&to), reinterpret_cast<const std::byte*>(&from),
+ sizeof(TO));
+ return to;
+}
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_MEMORY_BITCAST_H_
diff --git a/src/tint/utils/memory/bitcast_test.cc b/src/tint/utils/memory/bitcast_test.cc
new file mode 100644
index 0000000..558c52f
--- /dev/null
+++ b/src/tint/utils/memory/bitcast_test.cc
@@ -0,0 +1,37 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/memory/bitcast.h"
+
+#include <stdint.h>
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(Bitcast, Integer) {
+ uint32_t a = 123;
+ int32_t b = Bitcast<int32_t>(a);
+ EXPECT_EQ(a, static_cast<uint32_t>(b));
+}
+
+TEST(Bitcast, Pointer) {
+ uint32_t a = 123;
+ void* b = Bitcast<void*>(&a);
+ EXPECT_EQ(&a, static_cast<uint32_t*>(b));
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/memory/block_allocator.h b/src/tint/utils/memory/block_allocator.h
new file mode 100644
index 0000000..1cc382c
--- /dev/null
+++ b/src/tint/utils/memory/block_allocator.h
@@ -0,0 +1,341 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MEMORY_BLOCK_ALLOCATOR_H_
+#define SRC_TINT_UTILS_MEMORY_BLOCK_ALLOCATOR_H_
+
+#include <array>
+#include <cstring>
+#include <utility>
+
+#include "src/tint/utils/math/math.h"
+#include "src/tint/utils/memory/bitcast.h"
+
+namespace tint::utils {
+
+/// A container and allocator of objects of (or deriving from) the template type `T`.
+/// Objects are allocated by calling Create(), and are owned by the BlockAllocator.
+/// When the BlockAllocator is destructed, all constructed objects are automatically destructed and
+/// freed.
+///
+/// Objects held by the BlockAllocator can be iterated over using a View.
+template <typename T, size_t BLOCK_SIZE = 64 * 1024, size_t BLOCK_ALIGNMENT = 16>
+class BlockAllocator {
+ /// Pointers is a chunk of T* pointers, forming a linked list.
+ /// The list of Pointers are used to maintain the list of allocated objects.
+ /// Pointers are allocated out of the block memory.
+ struct Pointers {
+ static constexpr size_t kMax = 32;
+ std::array<T*, kMax> ptrs;
+ Pointers* next;
+ Pointers* prev;
+ };
+
+ /// Block is linked list of memory blocks.
+ /// Blocks are allocated out of heap memory.
+ ///
+ /// Note: We're not using std::aligned_storage here as this warns / errors on MSVC.
+ struct alignas(BLOCK_ALIGNMENT) Block {
+ uint8_t data[BLOCK_SIZE];
+ Block* next;
+ };
+
+ // Forward declaration
+ template <bool IS_CONST>
+ class TView;
+
+ /// An iterator for the objects owned by the BlockAllocator.
+ template <bool IS_CONST, bool FORWARD>
+ class TIterator {
+ using PointerTy = std::conditional_t<IS_CONST, const T*, T*>;
+
+ public:
+ /// Equality operator
+ /// @param other the iterator to compare this iterator to
+ /// @returns true if this iterator is equal to other
+ bool operator==(const TIterator& other) const {
+ return ptrs == other.ptrs && idx == other.idx;
+ }
+
+ /// Inequality operator
+ /// @param other the iterator to compare this iterator to
+ /// @returns true if this iterator is not equal to other
+ bool operator!=(const TIterator& other) const { return !(*this == other); }
+
+ /// Progress the iterator forward one element
+ /// @returns this iterator
+ TIterator& operator++() {
+ if (FORWARD) {
+ ProgressForward();
+ } else {
+ ProgressBackwards();
+ }
+ return *this;
+ }
+
+ /// Progress the iterator backwards one element
+ /// @returns this iterator
+ TIterator& operator--() {
+ if (FORWARD) {
+ ProgressBackwards();
+ } else {
+ ProgressForward();
+ }
+ return *this;
+ }
+
+ /// @returns the pointer to the object at the current iterator position
+ PointerTy operator*() const { return ptrs ? ptrs->ptrs[idx] : nullptr; }
+
+ private:
+ friend TView<IS_CONST>; // Keep internal iterator impl private.
+ explicit TIterator(const Pointers* p, size_t i) : ptrs(p), idx(i) {}
+
+ /// Progresses the iterator forwards
+ void ProgressForward() {
+ if (ptrs != nullptr) {
+ ++idx;
+ if (idx == Pointers::kMax) {
+ idx = 0;
+ ptrs = ptrs->next;
+ }
+ }
+ }
+ /// Progresses the iterator backwards
+ void ProgressBackwards() {
+ if (ptrs != nullptr) {
+ if (idx == 0) {
+ idx = Pointers::kMax - 1;
+ ptrs = ptrs->prev;
+ }
+ --idx;
+ }
+ }
+
+ const Pointers* ptrs;
+ size_t idx;
+ };
+
+ /// View provides begin() and end() methods for looping over the objects owned by the
+ /// BlockAllocator.
+ template <bool IS_CONST>
+ class TView {
+ public:
+ /// @returns an iterator to the beginning of the view
+ TIterator<IS_CONST, true> begin() const {
+ return TIterator<IS_CONST, true>{allocator_->data.pointers.root, 0};
+ }
+
+ /// @returns an iterator to the end of the view
+ TIterator<IS_CONST, true> end() const {
+ return allocator_->data.pointers.current_index >= Pointers::kMax
+ ? TIterator<IS_CONST, true>{nullptr, 0}
+ : TIterator<IS_CONST, true>{allocator_->data.pointers.current,
+ allocator_->data.pointers.current_index};
+ }
+
+ /// @returns an iterator to the beginning of the view
+ TIterator<IS_CONST, false> rbegin() const { return TIterator<IS_CONST, false>{nullptr, 0}; }
+
+ /// @returns an iterator to the end of the view
+ TIterator<IS_CONST, false> rend() const {
+ return TIterator<IS_CONST, false>{allocator_->data.pointers.current,
+ allocator_->data.pointers.current_index};
+ }
+
+ private:
+ friend BlockAllocator; // For BlockAllocator::operator View()
+ explicit TView(BlockAllocator const* allocator) : allocator_(allocator) {}
+ BlockAllocator const* const allocator_;
+ };
+
+ public:
+ /// A forward-iterator type over the objects of the BlockAllocator
+ using Iterator = TIterator</* const */ false, /* forward */ true>;
+
+ /// An immutable forward-iterator type over the objects of the BlockAllocator
+ using ConstIterator = TIterator</* const */ true, /* forward */ true>;
+
+ /// A reverse-iterator type over the objects of the BlockAllocator
+ using ReverseIterator = TIterator</* const */ false, /* forward */ false>;
+
+ /// An immutable reverse-iterator type over the objects of the BlockAllocator
+ using ReverseConstIterator = TIterator</* const */ true, /* forward */ false>;
+
+ /// View provides begin() and end() methods for looping over the objects owned by the
+ /// BlockAllocator.
+ using View = TView<false>;
+
+ /// ConstView provides begin() and end() methods for looping over the objects owned by the
+ /// BlockAllocator.
+ using ConstView = TView<true>;
+
+ /// Constructor
+ BlockAllocator() = default;
+
+ /// Move constructor
+ /// @param rhs the BlockAllocator to move
+ BlockAllocator(BlockAllocator&& rhs) { std::swap(data, rhs.data); }
+
+ /// Move assignment operator
+ /// @param rhs the BlockAllocator to move
+ /// @return this BlockAllocator
+ BlockAllocator& operator=(BlockAllocator&& rhs) {
+ if (this != &rhs) {
+ Reset();
+ std::swap(data, rhs.data);
+ }
+ return *this;
+ }
+
+ /// Destructor
+ ~BlockAllocator() { Reset(); }
+
+ /// @return a View of all objects owned by this BlockAllocator
+ View Objects() { return View(this); }
+
+ /// @return a ConstView of all objects owned by this BlockAllocator
+ ConstView Objects() const { return ConstView(this); }
+
+ /// Creates a new `TYPE` owned by the BlockAllocator.
+ /// When the BlockAllocator is destructed the object will be destructed and freed.
+ /// @param args the arguments to pass to the constructor
+ /// @returns the pointer to the constructed object
+ template <typename TYPE = T, typename... ARGS>
+ TYPE* Create(ARGS&&... args) {
+ static_assert(std::is_same<T, TYPE>::value || std::is_base_of<T, TYPE>::value,
+ "TYPE does not derive from T");
+ static_assert(std::is_same<T, TYPE>::value || std::has_virtual_destructor<T>::value,
+ "TYPE requires a virtual destructor when calling Create() for a type "
+ "that is not T");
+
+ auto* ptr = Allocate<TYPE>();
+ new (ptr) TYPE(std::forward<ARGS>(args)...);
+ AddObjectPointer(ptr);
+ data.count++;
+
+ return ptr;
+ }
+
+ /// Frees all allocations from the allocator.
+ void Reset() {
+ for (auto ptr : Objects()) {
+ ptr->~T();
+ }
+ auto* block = data.block.root;
+ while (block != nullptr) {
+ auto* next = block->next;
+ delete block;
+ block = next;
+ }
+ data = {};
+ }
+
+ /// @returns the total number of allocated objects.
+ size_t Count() const { return data.count; }
+
+ private:
+ BlockAllocator(const BlockAllocator&) = delete;
+ BlockAllocator& operator=(const BlockAllocator&) = delete;
+
+ /// Allocates an instance of TYPE from the current block, or from a newly allocated block if the
+ /// current block is full.
+ template <typename TYPE>
+ TYPE* Allocate() {
+ static_assert(sizeof(TYPE) <= BLOCK_SIZE,
+ "Cannot construct TYPE with size greater than BLOCK_SIZE");
+ static_assert(alignof(TYPE) <= BLOCK_ALIGNMENT, "alignof(TYPE) is greater than ALIGNMENT");
+
+ auto& block = data.block;
+
+ block.current_offset = utils::RoundUp(alignof(TYPE), block.current_offset);
+ if (block.current_offset + sizeof(TYPE) > BLOCK_SIZE) {
+ // Allocate a new block from the heap
+ auto* prev_block = block.current;
+ block.current = new Block;
+ if (!block.current) {
+ return nullptr; // out of memory
+ }
+ block.current->next = nullptr;
+ block.current_offset = 0;
+ if (prev_block) {
+ prev_block->next = block.current;
+ } else {
+ block.root = block.current;
+ }
+ }
+
+ auto* base = &block.current->data[0];
+ auto* ptr = utils::Bitcast<TYPE*>(base + block.current_offset);
+ block.current_offset += sizeof(TYPE);
+ return ptr;
+ }
+
+ /// Adds `ptr` to the linked list of objects owned by this BlockAllocator.
+ /// Once added, `ptr` will be tracked for destruction when the BlockAllocator is destructed.
+ void AddObjectPointer(T* ptr) {
+ auto& pointers = data.pointers;
+
+ if (pointers.current_index >= Pointers::kMax) {
+ auto* prev_pointers = pointers.current;
+ pointers.current = Allocate<Pointers>();
+ if (!pointers.current) {
+ return; // out of memory
+ }
+ pointers.current->next = nullptr;
+ pointers.current->prev = prev_pointers;
+ pointers.current_index = 0;
+
+ if (prev_pointers) {
+ prev_pointers->next = pointers.current;
+ } else {
+ pointers.root = pointers.current;
+ }
+ }
+
+ pointers.current->ptrs[pointers.current_index++] = ptr;
+ }
+
+ struct {
+ struct {
+ /// The root block of the block linked list
+ Block* root = nullptr;
+ /// The current (end) block of the blocked linked list.
+ /// New allocations come from this block
+ Block* current = nullptr;
+ /// The byte offset in #current for the next allocation.
+ /// Initialized with BLOCK_SIZE so that the first allocation triggers a block
+ /// allocation.
+ size_t current_offset = BLOCK_SIZE;
+ } block;
+
+ struct {
+ /// The root Pointers structure of the pointers linked list
+ Pointers* root = nullptr;
+ /// The current (end) Pointers structure of the pointers linked list.
+ /// AddObjectPointer() adds to this structure.
+ Pointers* current = nullptr;
+ /// The array index in #current for the next append.
+ /// Initialized with Pointers::kMax so that the first append triggers a allocation of
+ /// the Pointers structure.
+ size_t current_index = Pointers::kMax;
+ } pointers;
+
+ size_t count = 0;
+ } data;
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_MEMORY_BLOCK_ALLOCATOR_H_
diff --git a/src/tint/utils/memory/block_allocator_test.cc b/src/tint/utils/memory/block_allocator_test.cc
new file mode 100644
index 0000000..194769a
--- /dev/null
+++ b/src/tint/utils/memory/block_allocator_test.cc
@@ -0,0 +1,164 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/memory/block_allocator.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+struct LifetimeCounter {
+ explicit LifetimeCounter(size_t* count) : count_(count) { (*count)++; }
+ ~LifetimeCounter() { (*count_)--; }
+
+ size_t* const count_;
+};
+
+using BlockAllocatorTest = testing::Test;
+
+TEST_F(BlockAllocatorTest, Empty) {
+ using Allocator = BlockAllocator<int>;
+
+ Allocator allocator;
+
+ EXPECT_EQ(allocator.Count(), 0u);
+ for (int* i : allocator.Objects()) {
+ (void)i;
+ if ((true)) { // Workaround for "error: loop will run at most once"
+ FAIL() << "BlockAllocator should be empty";
+ }
+ }
+ for (const int* i : static_cast<const Allocator&>(allocator).Objects()) {
+ (void)i;
+ if ((true)) { // Workaround for "error: loop will run at most once"
+ FAIL() << "BlockAllocator should be empty";
+ }
+ }
+}
+
+TEST_F(BlockAllocatorTest, Count) {
+ using Allocator = BlockAllocator<int>;
+
+ for (size_t n : {0u, 1u, 10u, 16u, 20u, 32u, 50u, 64u, 100u, 256u, 300u, 512u, 500u, 512u}) {
+ Allocator allocator;
+ EXPECT_EQ(allocator.Count(), 0u);
+ for (size_t i = 0; i < n; i++) {
+ allocator.Create(123);
+ }
+ EXPECT_EQ(allocator.Count(), n);
+ }
+}
+
+TEST_F(BlockAllocatorTest, ObjectLifetime) {
+ using Allocator = BlockAllocator<LifetimeCounter>;
+
+ size_t count = 0;
+ {
+ Allocator allocator;
+ EXPECT_EQ(count, 0u);
+ allocator.Create(&count);
+ EXPECT_EQ(count, 1u);
+ allocator.Create(&count);
+ EXPECT_EQ(count, 2u);
+ allocator.Create(&count);
+ EXPECT_EQ(count, 3u);
+ }
+ EXPECT_EQ(count, 0u);
+}
+
+TEST_F(BlockAllocatorTest, MoveConstruct) {
+ using Allocator = BlockAllocator<LifetimeCounter>;
+
+ for (size_t n : {0u, 1u, 10u, 16u, 20u, 32u, 50u, 64u, 100u, 256u, 300u, 512u, 500u, 512u}) {
+ size_t count = 0;
+ {
+ Allocator allocator_a;
+ for (size_t i = 0; i < n; i++) {
+ allocator_a.Create(&count);
+ }
+ EXPECT_EQ(count, n);
+ EXPECT_EQ(allocator_a.Count(), n);
+
+ Allocator allocator_b{std::move(allocator_a)};
+ EXPECT_EQ(count, n);
+ EXPECT_EQ(allocator_b.Count(), n);
+ }
+
+ EXPECT_EQ(count, 0u);
+ }
+}
+
+TEST_F(BlockAllocatorTest, MoveAssign) {
+ using Allocator = BlockAllocator<LifetimeCounter>;
+
+ for (size_t n : {0u, 1u, 10u, 16u, 20u, 32u, 50u, 64u, 100u, 256u, 300u, 512u, 500u, 512u}) {
+ size_t count_a = 0;
+ size_t count_b = 0;
+
+ {
+ Allocator allocator_a;
+ for (size_t i = 0; i < n; i++) {
+ allocator_a.Create(&count_a);
+ }
+ EXPECT_EQ(count_a, n);
+ EXPECT_EQ(allocator_a.Count(), n);
+
+ Allocator allocator_b;
+ for (size_t i = 0; i < n; i++) {
+ allocator_b.Create(&count_b);
+ }
+ EXPECT_EQ(count_b, n);
+ EXPECT_EQ(allocator_b.Count(), n);
+
+ allocator_b = std::move(allocator_a);
+ EXPECT_EQ(count_a, n);
+ EXPECT_EQ(count_b, 0u);
+ EXPECT_EQ(allocator_b.Count(), n);
+ }
+
+ EXPECT_EQ(count_a, 0u);
+ EXPECT_EQ(count_b, 0u);
+ }
+}
+
+TEST_F(BlockAllocatorTest, ObjectOrder) {
+ using Allocator = BlockAllocator<int>;
+
+ Allocator allocator;
+ constexpr int N = 10000;
+ for (int i = 0; i < N; i++) {
+ allocator.Create(i);
+ }
+
+ {
+ int i = 0;
+ for (int* p : allocator.Objects()) {
+ EXPECT_EQ(*p, i);
+ i++;
+ }
+ EXPECT_EQ(i, N);
+ }
+ {
+ int i = 0;
+ for (const int* p : static_cast<const Allocator&>(allocator).Objects()) {
+ EXPECT_EQ(*p, i);
+ i++;
+ }
+ EXPECT_EQ(i, N);
+ }
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/memory/bump_allocator.h b/src/tint/utils/memory/bump_allocator.h
new file mode 100644
index 0000000..c4c07e8
--- /dev/null
+++ b/src/tint/utils/memory/bump_allocator.h
@@ -0,0 +1,127 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_MEMORY_BUMP_ALLOCATOR_H_
+#define SRC_TINT_UTILS_MEMORY_BUMP_ALLOCATOR_H_
+
+#include <array>
+#include <cstring>
+#include <utility>
+
+#include "src/tint/utils/math/math.h"
+#include "src/tint/utils/memory/bitcast.h"
+
+namespace tint::utils {
+
+constexpr size_t kBlockSize = 64 * 1024;
+
+/// A allocator for chunks of memory. The memory is owned by the BumpAllocator. When the
+/// BumpAllocator is freed all of the allocated memory is freed.
+class BumpAllocator {
+ /// Block is linked list of memory blocks.
+ /// Blocks are allocated out of heap memory.
+ struct Block {
+ uint8_t data[kBlockSize];
+ Block* next;
+ };
+
+ public:
+ /// Constructor
+ BumpAllocator() = default;
+
+ /// Move constructor
+ /// @param rhs the BumpAllocator to move
+ BumpAllocator(BumpAllocator&& rhs) { std::swap(data, rhs.data); }
+
+ /// Move assignment operator
+ /// @param rhs the BumpAllocator to move
+ /// @return this BumpAllocator
+ BumpAllocator& operator=(BumpAllocator&& rhs) {
+ if (this != &rhs) {
+ Reset();
+ std::swap(data, rhs.data);
+ }
+ return *this;
+ }
+
+ /// Destructor
+ ~BumpAllocator() { Reset(); }
+
+ /// Allocates @p size_in_bytes from the current block, or from a newly allocated block if the
+ /// current block is full.
+ /// @param size_in_bytes the number of bytes to allocate
+ /// @returns the pointer to the allocated memory or |nullptr| if the memory can not be allocated
+ char* Allocate(size_t size_in_bytes) {
+ auto& block = data.block;
+ if (block.current_offset + size_in_bytes > kBlockSize) {
+ // Allocate a new block from the heap
+ auto* prev_block = block.current;
+ block.current = new Block;
+ if (!block.current) {
+ return nullptr; // out of memory
+ }
+ block.current->next = nullptr;
+ block.current_offset = 0;
+ if (prev_block) {
+ prev_block->next = block.current;
+ } else {
+ block.root = block.current;
+ }
+ }
+
+ auto* base = &block.current->data[0];
+ auto* ptr = reinterpret_cast<char*>(base + block.current_offset);
+ block.current_offset += size_in_bytes;
+ data.count++;
+ return ptr;
+ }
+
+ /// Frees all allocations from the allocator.
+ void Reset() {
+ auto* block = data.block.root;
+ while (block != nullptr) {
+ auto* next = block->next;
+ delete block;
+ block = next;
+ }
+ data = {};
+ }
+
+ /// @returns the total number of allocations
+ size_t Count() const { return data.count; }
+
+ private:
+ BumpAllocator(const BumpAllocator&) = delete;
+ BumpAllocator& operator=(const BumpAllocator&) = delete;
+
+ struct {
+ struct {
+ /// The root block of the block linked list
+ Block* root = nullptr;
+ /// The current (end) block of the blocked linked list.
+ /// New allocations come from this block
+ Block* current = nullptr;
+ /// The byte offset in #current for the next allocation.
+ /// Initialized with kBlockSize so that the first allocation triggers a block
+ /// allocation.
+ size_t current_offset = kBlockSize;
+ } block;
+
+ size_t count = 0;
+ } data;
+};
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_MEMORY_BUMP_ALLOCATOR_H_
diff --git a/src/tint/utils/memory/bump_allocator_test.cc b/src/tint/utils/memory/bump_allocator_test.cc
new file mode 100644
index 0000000..0212586
--- /dev/null
+++ b/src/tint/utils/memory/bump_allocator_test.cc
@@ -0,0 +1,49 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/memory/bump_allocator.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+using BumpAllocatorTest = testing::Test;
+
+TEST_F(BumpAllocatorTest, Count) {
+ for (size_t n : {0u, 1u, 10u, 16u, 20u, 32u, 50u, 64u, 100u, 256u, 300u, 512u, 500u, 512u}) {
+ BumpAllocator allocator;
+ EXPECT_EQ(allocator.Count(), 0u);
+ for (size_t i = 0; i < n; i++) {
+ allocator.Allocate(5);
+ }
+ EXPECT_EQ(allocator.Count(), n);
+ }
+}
+
+TEST_F(BumpAllocatorTest, MoveConstruct) {
+ for (size_t n : {0u, 1u, 10u, 16u, 20u, 32u, 50u, 64u, 100u, 256u, 300u, 512u, 500u, 512u}) {
+ BumpAllocator allocator_a;
+ for (size_t i = 0; i < n; i++) {
+ allocator_a.Allocate(5);
+ }
+ EXPECT_EQ(allocator_a.Count(), n);
+
+ BumpAllocator allocator_b{std::move(allocator_a)};
+ EXPECT_EQ(allocator_b.Count(), n);
+ }
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/reflection/reflection.h b/src/tint/utils/reflection/reflection.h
new file mode 100644
index 0000000..c2ba4834
--- /dev/null
+++ b/src/tint/utils/reflection/reflection.h
@@ -0,0 +1,68 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_REFLECTION_REFLECTION_H_
+#define SRC_TINT_UTILS_REFLECTION_REFLECTION_H_
+
+#include <type_traits>
+
+#include "src/tint/utils/macros/concat.h"
+#include "src/tint/utils/macros/foreach.h"
+
+namespace tint {
+
+namespace detail {
+
+/// Helper for detecting whether the type T contains a nested Reflection class.
+template <typename T, typename ENABLE = void>
+struct HasReflection : std::false_type {};
+
+/// Specialization for types that have a nested Reflection class.
+template <typename T>
+struct HasReflection<T, std::void_t<typename T::Reflection>> : std::true_type {};
+
+} // namespace detail
+
+/// Is true if the class T has reflected its fields with TINT_REFLECT()
+template <typename T>
+static constexpr bool HasReflection = tint::detail::HasReflection<T>::value;
+
+/// Calls @p callback with each field of @p object
+/// @param object the object
+/// @param callback a function that is called for each field of @p object.
+/// @tparam CB a function with the signature `void(FIELD)`
+template <typename OBJECT, typename CB>
+void ForeachField(OBJECT&& object, CB&& callback) {
+ using T = std::decay_t<OBJECT>;
+ static_assert(HasReflection<T>, "object type requires a tint::Reflect<> specialization");
+ T::Reflection::Fields(object, callback);
+}
+
+/// Macro used by TINT_FOREACH() in TINT_REFLECT() to call the callback function with each field in
+/// the variadic.
+#define TINT_REFLECT_CALLBACK_FIELD(field) callback(object.field);
+
+// TINT_REFLECT(...) reflects each of the fields arguments so that the types can be used with
+// tint::ForeachField().
+#define TINT_REFLECT(...) \
+ struct Reflection { \
+ template <typename OBJECT, typename CB> \
+ static void Fields(OBJECT&& object, CB&& callback) { \
+ TINT_FOREACH(TINT_REFLECT_CALLBACK_FIELD, __VA_ARGS__) \
+ } \
+ }
+
+} // namespace tint
+
+#endif // SRC_TINT_UTILS_REFLECTION_REFLECTION_H_
diff --git a/src/tint/utils/reflection/reflection_test.cc b/src/tint/utils/reflection/reflection_test.cc
new file mode 100644
index 0000000..e55beea
--- /dev/null
+++ b/src/tint/utils/reflection/reflection_test.cc
@@ -0,0 +1,91 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/reflection/reflection.h"
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+struct S {
+ int i;
+ unsigned u;
+ bool b;
+ TINT_REFLECT(i, u, b);
+};
+
+static_assert(!HasReflection<int>);
+static_assert(HasReflection<S>);
+
+TEST(ReflectionTest, ForeachFieldConst) {
+ const S s{1, 2, true};
+ size_t field_idx = 0;
+ ForeachField(s, [&](auto& field) {
+ using T = std::decay_t<decltype(field)>;
+ switch (field_idx) {
+ case 0:
+ EXPECT_TRUE((std::is_same_v<T, int>));
+ EXPECT_EQ(field, static_cast<T>(1));
+ break;
+ case 1:
+ EXPECT_TRUE((std::is_same_v<T, unsigned>));
+ EXPECT_EQ(field, static_cast<T>(2));
+ break;
+ case 2:
+ EXPECT_TRUE((std::is_same_v<T, bool>));
+ EXPECT_EQ(field, static_cast<T>(true));
+ break;
+ default:
+ FAIL() << "unexpected field";
+ break;
+ }
+ field_idx++;
+ });
+}
+
+TEST(ReflectionTest, ForeachFieldNonConst) {
+ S s{1, 2, true};
+ size_t field_idx = 0;
+ ForeachField(s, [&](auto& field) {
+ using T = std::decay_t<decltype(field)>;
+ switch (field_idx) {
+ case 0:
+ EXPECT_TRUE((std::is_same_v<T, int>));
+ EXPECT_EQ(field, static_cast<T>(1));
+ field = static_cast<T>(10);
+ break;
+ case 1:
+ EXPECT_TRUE((std::is_same_v<T, unsigned>));
+ EXPECT_EQ(field, static_cast<T>(2));
+ field = static_cast<T>(20);
+ break;
+ case 2:
+ EXPECT_TRUE((std::is_same_v<T, bool>));
+ EXPECT_EQ(field, static_cast<T>(true));
+ field = static_cast<T>(false);
+ break;
+ default:
+ FAIL() << "unexpected field";
+ break;
+ }
+ field_idx++;
+ });
+
+ EXPECT_EQ(s.i, 10);
+ EXPECT_EQ(s.u, 20u);
+ EXPECT_EQ(s.b, false);
+}
+
+} // namespace
+} // namespace tint
diff --git a/src/tint/utils/result/result.h b/src/tint/utils/result/result.h
new file mode 100644
index 0000000..8117ad1
--- /dev/null
+++ b/src/tint/utils/result/result.h
@@ -0,0 +1,166 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_RESULT_RESULT_H_
+#define SRC_TINT_UTILS_RESULT_RESULT_H_
+
+#include <utility>
+#include <variant>
+
+#include "src/tint/utils/debug/debug.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::utils {
+
+/// Empty structure used as the default FAILURE_TYPE for a Result.
+struct FailureType {};
+
+static constexpr const FailureType Failure;
+
+/// Result is a helper for functions that need to return a value, or an failure value.
+/// Result can be constructed with either a 'success' or 'failure' value.
+/// @tparam SUCCESS_TYPE the 'success' value type.
+/// @tparam FAILURE_TYPE the 'failure' value type. Defaults to FailureType which provides no
+/// information about the failure, except that something failed. Must not be the same type
+/// as SUCCESS_TYPE.
+template <typename SUCCESS_TYPE, typename FAILURE_TYPE = FailureType>
+struct [[nodiscard]] Result {
+ static_assert(!std::is_same_v<SUCCESS_TYPE, FAILURE_TYPE>,
+ "Result must not have the same type for SUCCESS_TYPE and FAILURE_TYPE");
+
+ /// Default constructor initializes to invalid state
+ Result() : value(std::monostate{}) {}
+
+ /// Constructor
+ /// @param success the success result
+ Result(const SUCCESS_TYPE& success) // NOLINT(runtime/explicit):
+ : value{success} {}
+
+ /// Constructor
+ /// @param success the success result
+ Result(SUCCESS_TYPE&& success) // NOLINT(runtime/explicit):
+ : value(std::move(SUCCESS_TYPE(std::move(success)))) {}
+
+ /// Constructor
+ /// @param failure the failure result
+ Result(const FAILURE_TYPE& failure) // NOLINT(runtime/explicit):
+ : value{failure} {}
+
+ /// Constructor
+ /// @param failure the failure result
+ Result(FAILURE_TYPE&& failure) // NOLINT(runtime/explicit):
+ : value{std::move(failure)} {}
+
+ /// Copy constructor with success / failure casting
+ /// @param other the Result to copy
+ template <typename S,
+ typename F,
+ typename = std::void_t<decltype(SUCCESS_TYPE{std::declval<S>()}),
+ decltype(FAILURE_TYPE{std::declval<F>()})>>
+ Result(const Result<S, F>& other) { // NOLINT(runtime/explicit):
+ if (other) {
+ value = SUCCESS_TYPE{other.Get()};
+ } else {
+ value = FAILURE_TYPE{other.Failure()};
+ }
+ }
+
+ /// @returns true if the result was a success
+ operator bool() const {
+ Validate();
+ return std::holds_alternative<SUCCESS_TYPE>(value);
+ }
+
+ /// @returns true if the result was a failure
+ bool operator!() const {
+ Validate();
+ return std::holds_alternative<FAILURE_TYPE>(value);
+ }
+
+ /// @returns the success value
+ /// @warning attempting to call this when the Result holds an failure will result in UB.
+ const SUCCESS_TYPE* operator->() const {
+ Validate();
+ return &(Get());
+ }
+
+ /// @returns the success value
+ /// @warning attempting to call this when the Result holds an failure value will result in UB.
+ const SUCCESS_TYPE& Get() const {
+ Validate();
+ return std::get<SUCCESS_TYPE>(value);
+ }
+
+ /// @returns the success value
+ /// @warning attempting to call this when the Result holds an failure value will result in UB.
+ SUCCESS_TYPE& Get() {
+ Validate();
+ return std::get<SUCCESS_TYPE>(value);
+ }
+
+ /// @returns the success value
+ /// @warning attempting to call this when the Result holds an failure value will result in UB.
+ SUCCESS_TYPE&& Move() {
+ Validate();
+ return std::get<SUCCESS_TYPE>(std::move(value));
+ }
+
+ /// @returns the failure value
+ /// @warning attempting to call this when the Result holds a success value will result in UB.
+ const FAILURE_TYPE& Failure() const {
+ Validate();
+ return std::get<FAILURE_TYPE>(value);
+ }
+
+ /// Equality operator
+ /// @param val the value to compare this Result to
+ /// @returns true if this result holds a success value equal to `value`
+ bool operator==(SUCCESS_TYPE val) const {
+ Validate();
+ if (auto* v = std::get_if<SUCCESS_TYPE>(&value)) {
+ return *v == val;
+ }
+ return false;
+ }
+
+ /// Equality operator
+ /// @param val the value to compare this Result to
+ /// @returns true if this result holds a failure value equal to `value`
+ bool operator==(FAILURE_TYPE val) const {
+ Validate();
+ if (auto* v = std::get_if<FAILURE_TYPE>(&value)) {
+ return *v == val;
+ }
+ return false;
+ }
+
+ private:
+ void Validate() const { TINT_ASSERT(Utils, !std::holds_alternative<std::monostate>(value)); }
+
+ /// The result. Either a success of failure value.
+ std::variant<std::monostate, SUCCESS_TYPE, FAILURE_TYPE> value;
+};
+
+/// Writes the result to the stream.
+/// @param out the stream to write to
+/// @param res the result
+/// @return the stream so calls can be chained
+template <typename SUCCESS, typename FAILURE>
+inline utils::StringStream& operator<<(utils::StringStream& out, Result<SUCCESS, FAILURE> res) {
+ return res ? (out << "success: " << res.Get()) : (out << "failure: " << res.Failure());
+}
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_RESULT_RESULT_H_
diff --git a/src/tint/utils/result/result_test.cc b/src/tint/utils/result/result_test.cc
new file mode 100644
index 0000000..e7039cc
--- /dev/null
+++ b/src/tint/utils/result/result_test.cc
@@ -0,0 +1,67 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/result/result.h"
+
+#include <string>
+
+#include "gmock/gmock.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(ResultTest, SuccessInt) {
+ auto r = Result<int>(123);
+ EXPECT_TRUE(r);
+ EXPECT_FALSE(!r);
+ EXPECT_EQ(r.Get(), 123);
+}
+
+TEST(ResultTest, SuccessStruct) {
+ struct S {
+ int value;
+ };
+ auto r = Result<S>({123});
+ EXPECT_TRUE(r);
+ EXPECT_FALSE(!r);
+ EXPECT_EQ(r->value, 123);
+}
+
+TEST(ResultTest, Failure) {
+ auto r = Result<int>(Failure);
+ EXPECT_FALSE(r);
+ EXPECT_TRUE(!r);
+}
+
+TEST(ResultTest, CustomFailure) {
+ auto r = Result<int, std::string>("oh noes!");
+ EXPECT_FALSE(r);
+ EXPECT_TRUE(!r);
+ EXPECT_EQ(r.Failure(), "oh noes!");
+}
+
+TEST(ResultTest, ValueCast) {
+ struct X {};
+ struct Y : X {};
+
+ Y* y = nullptr;
+ auto r_y = Result<Y*>{y};
+ auto r_x = Result<X*>{r_y};
+
+ (void)r_x;
+ (void)r_y;
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/rtti/castable.cc b/src/tint/utils/rtti/castable.cc
new file mode 100644
index 0000000..dd4114a
--- /dev/null
+++ b/src/tint/utils/rtti/castable.cc
@@ -0,0 +1,33 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/rtti/castable.h"
+
+namespace tint::utils {
+
+/// The unique TypeInfo for the CastableBase type
+/// @return doxygen-thinks-this-static-field-is-a-function :(
+template <>
+const TypeInfo utils::detail::TypeInfoOf<CastableBase>::info{
+ nullptr,
+ "CastableBase",
+ tint::utils::TypeInfo::HashCodeOf<CastableBase>(),
+ tint::utils::TypeInfo::FullHashCodeOf<CastableBase>(),
+};
+
+CastableBase::CastableBase(const CastableBase&) = default;
+
+CastableBase::~CastableBase() = default;
+
+} // namespace tint::utils
diff --git a/src/tint/utils/rtti/castable.h b/src/tint/utils/rtti/castable.h
new file mode 100644
index 0000000..7b0c97e
--- /dev/null
+++ b/src/tint/utils/rtti/castable.h
@@ -0,0 +1,553 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_RTTI_CASTABLE_H_
+#define SRC_TINT_UTILS_RTTI_CASTABLE_H_
+
+#include <stdint.h>
+#include <functional>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "src/tint/utils/math/crc32.h"
+#include "src/tint/utils/traits/traits.h"
+
+#if defined(__clang__)
+/// Temporarily disable certain warnings when using Castable API
+#define TINT_CASTABLE_PUSH_DISABLE_WARNINGS() \
+ _Pragma("clang diagnostic push") /**/ \
+ _Pragma("clang diagnostic ignored \"-Wundefined-var-template\"") /**/ \
+ static_assert(true, "require extra semicolon")
+
+/// Restore disabled warnings
+#define TINT_CASTABLE_POP_DISABLE_WARNINGS() \
+ _Pragma("clang diagnostic pop") /**/ \
+ static_assert(true, "require extra semicolon")
+#else
+#define TINT_CASTABLE_PUSH_DISABLE_WARNINGS() static_assert(true, "require extra semicolon")
+#define TINT_CASTABLE_POP_DISABLE_WARNINGS() static_assert(true, "require extra semicolon")
+#endif
+
+TINT_CASTABLE_PUSH_DISABLE_WARNINGS();
+
+// Forward declarations
+namespace tint::utils {
+class CastableBase;
+
+/// Ignore is used as a special type used for skipping over types for trait
+/// helper functions.
+class Ignore {};
+} // namespace tint::utils
+
+namespace tint::utils::detail {
+template <typename T>
+struct TypeInfoOf;
+} // namespace tint::utils::detail
+
+namespace tint::utils {
+
+/// True if all template types that are not Ignore derive from CastableBase
+template <typename... TYPES>
+static constexpr bool IsCastable =
+ ((utils::traits::IsTypeOrDerived<TYPES, CastableBase> || std::is_same_v<TYPES, Ignore>)&&...) &&
+ !(std::is_same_v<TYPES, Ignore> && ...);
+
+/// Helper macro to instantiate the TypeInfo<T> template for `CLASS`.
+#define TINT_INSTANTIATE_TYPEINFO(CLASS) \
+ TINT_CASTABLE_PUSH_DISABLE_WARNINGS(); \
+ template <> \
+ const tint::utils::TypeInfo tint::utils::detail::TypeInfoOf<CLASS>::info{ \
+ &tint::utils::detail::TypeInfoOf<CLASS::TrueBase>::info, \
+ #CLASS, \
+ tint::utils::TypeInfo::HashCodeOf<CLASS>(), \
+ tint::utils::TypeInfo::FullHashCodeOf<CLASS>(), \
+ }; \
+ TINT_CASTABLE_POP_DISABLE_WARNINGS(); \
+ static_assert(std::is_same_v<CLASS, CLASS::Base::Class>, \
+ #CLASS " does not derive from Castable<" #CLASS "[, BASE]>")
+
+/// Bit flags that can be passed to the template parameter `FLAGS` of Is() and As().
+enum CastFlags {
+ /// Disables the static_assert() inside Is(), that compile-time-verifies that the cast is
+ /// possible. This flag may be useful for highly-generic template
+ /// code that needs to compile for template permutations that generate
+ /// impossible casts.
+ kDontErrorOnImpossibleCast = 1,
+};
+
+/// The type of a hash code
+using HashCode = uint64_t;
+
+/// Maybe checks to see if an object with the full hashcode @p object_full_hashcode could
+/// potentially be of, or derive from the type with the hashcode @p query_hashcode.
+/// @param type_hashcode the hashcode of the type
+/// @param object_full_hashcode the full hashcode of the object being queried
+/// @returns true if the object with the given full hashcode could be one of the template types.
+inline bool Maybe(HashCode type_hashcode, HashCode object_full_hashcode) {
+ return (object_full_hashcode & type_hashcode) == type_hashcode;
+}
+
+/// MaybeAnyOf checks to see if an object with the full hashcode @p object_full_hashcode could
+/// potentially be of, or derive from the types with the combined hashcode @p combined_hashcode.
+/// @param combined_hashcode the bitwise OR'd hashcodes of the types
+/// @param object_full_hashcode the full hashcode of the object being queried
+/// @returns true if the object with the given full hashcode could be one of the template types.
+inline bool MaybeAnyOf(HashCode combined_hashcode, HashCode object_full_hashcode) {
+ // Compare the object's hashcode to the bitwise-or of all the tested type's hashcodes. If
+ // there's no intersection of bits in the two masks, then we can guarantee that the type is not
+ // in `TO`.
+ HashCode mask = object_full_hashcode & combined_hashcode;
+ // HashCodeOf() ensures that two bits are always set for every hash, so we can quickly
+ // eliminate the bitmask where only one bit is set.
+ HashCode two_bits = mask & (mask - 1);
+ return two_bits != 0;
+}
+
+/// TypeInfo holds type information for a Castable type.
+struct TypeInfo {
+ /// The base class of this type
+ const TypeInfo* base;
+ /// The type name
+ const char* name;
+ /// The type hash code
+ const HashCode hashcode;
+ /// The type hash code bitwise-or'd with all ancestor's hashcodes.
+ const HashCode full_hashcode;
+
+ /// @returns true if `type` derives from the class `TO`
+ /// @param object the object type to test from, which must be, or derive from type `FROM`.
+ /// @see CastFlags
+ template <typename TO, typename FROM, int FLAGS = 0>
+ static inline bool Is(const tint::utils::TypeInfo* object) {
+ constexpr const bool downcast = std::is_base_of<FROM, TO>::value;
+ constexpr const bool upcast = std::is_base_of<TO, FROM>::value;
+ constexpr const bool nocast = std::is_same<FROM, TO>::value;
+ constexpr const bool assert_is_castable = (FLAGS & kDontErrorOnImpossibleCast) == 0;
+
+ static_assert(upcast || downcast || nocast || !assert_is_castable, "impossible cast");
+
+ return upcast || nocast || object->Is<TO>();
+ }
+
+ /// @returns true if this type derives from the class `T`
+ template <typename T>
+ inline bool Is() const {
+ auto* type = &Of<std::remove_cv_t<T>>();
+
+ if constexpr (std::is_final_v<T>) {
+ // T is final, so nothing can derive from T.
+ // We do not need to check ancestors, only whether this type is equal to the type T.
+ return type == this;
+ } else {
+ return Is(type);
+ }
+ }
+
+ /// @param type the test type info
+ /// @returns true if the class with this TypeInfo is of, or derives from the
+ /// class with the given TypeInfo.
+ inline bool Is(const tint::utils::TypeInfo* type) const {
+ if (!Maybe(type->hashcode, full_hashcode)) {
+ return false;
+ }
+
+ // Walk the base types, starting with this TypeInfo, to see if any of the pointers match
+ // `type`.
+ for (auto* ti = this; ti != nullptr; ti = ti->base) {
+ if (ti == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// @returns the static TypeInfo for the type T
+ template <typename T>
+ static const TypeInfo& Of() {
+ return utils::detail::TypeInfoOf<std::remove_cv_t<T>>::info;
+ }
+
+ /// @returns a compile-time hashcode for the type `T`.
+ /// @note the returned hashcode will have exactly 2 bits set, as the hashes are expected to be
+ /// used in bloom-filters which will quickly saturate when multiple hashcodes are bitwise-or'd
+ /// together.
+ template <typename T>
+ static constexpr HashCode HashCodeOf() {
+ static_assert(IsCastable<T>, "T is not Castable");
+ static_assert(std::is_same_v<T, std::remove_cv_t<T>>,
+ "Strip const / volatile decorations before calling HashCodeOf");
+ /// Use the compiler's "pretty" function name, which includes the template
+ /// type, to obtain a unique hash value.
+#ifdef _MSC_VER
+ constexpr uint32_t crc = utils::CRC32(__FUNCSIG__);
+#else
+ constexpr uint32_t crc = utils::CRC32(__PRETTY_FUNCTION__);
+#endif
+ constexpr uint32_t bit_a = (crc & 63);
+ constexpr uint32_t bit_b = ((crc >> 6) & 63);
+ constexpr uint32_t bit_c = (bit_a == bit_b) ? ((bit_a + 1) & 63) : bit_b;
+ return (static_cast<HashCode>(1) << bit_a) | (static_cast<HashCode>(1) << bit_c);
+ }
+
+ /// @returns the hashcode of the given type, bitwise-or'd with the hashcodes of all base
+ /// classes.
+ template <typename T>
+ static constexpr HashCode FullHashCodeOf() {
+ if constexpr (std::is_same_v<T, CastableBase>) {
+ return HashCodeOf<CastableBase>();
+ } else {
+ return HashCodeOf<T>() | FullHashCodeOf<typename T::TrueBase>();
+ }
+ }
+
+ /// @returns the bitwise-or'd hashcodes of all the types of the tuple `TUPLE`.
+ /// @see HashCodeOf
+ template <typename TUPLE>
+ static constexpr HashCode CombinedHashCodeOfTuple() {
+ constexpr auto kCount = std::tuple_size_v<TUPLE>;
+ if constexpr (kCount == 0) {
+ return 0;
+ } else if constexpr (kCount == 1) {
+ return HashCodeOf<std::remove_cv_t<std::tuple_element_t<0, TUPLE>>>();
+ } else {
+ constexpr auto kMid = kCount / 2;
+ return CombinedHashCodeOfTuple<utils::traits::SliceTuple<0, kMid, TUPLE>>() |
+ CombinedHashCodeOfTuple<utils::traits::SliceTuple<kMid, kCount - kMid, TUPLE>>();
+ }
+ }
+
+ /// @returns the bitwise-or'd hashcodes of all the template parameter types.
+ /// @see HashCodeOf
+ template <typename... TYPES>
+ static constexpr HashCode CombinedHashCodeOf() {
+ return CombinedHashCodeOfTuple<std::tuple<TYPES...>>();
+ }
+
+ /// @returns true if this TypeInfo is of, or derives from any of the types in `TUPLE`.
+ template <typename TUPLE>
+ inline bool IsAnyOfTuple() const {
+ constexpr auto kCount = std::tuple_size_v<TUPLE>;
+ if constexpr (kCount == 0) {
+ return false;
+ } else if constexpr (kCount == 1) {
+ return Is(&Of<std::tuple_element_t<0, TUPLE>>());
+ } else {
+ if (MaybeAnyOf(TypeInfo::CombinedHashCodeOfTuple<TUPLE>(), full_hashcode)) {
+ // Possibly one of the types in `TUPLE`.
+ // Split the search in two, and scan each block.
+ static constexpr auto kMid = kCount / 2;
+ return IsAnyOfTuple<utils::traits::SliceTuple<0, kMid, TUPLE>>() ||
+ IsAnyOfTuple<utils::traits::SliceTuple<kMid, kCount - kMid, TUPLE>>();
+ }
+ return false;
+ }
+ }
+
+ /// @returns true if this TypeInfo is of, or derives from any of the types in `TYPES`.
+ template <typename... TYPES>
+ inline bool IsAnyOf() const {
+ return IsAnyOfTuple<std::tuple<TYPES...>>();
+ }
+};
+
+namespace detail {
+
+/// TypeInfoOf contains a single TypeInfo field for the type T.
+/// TINT_INSTANTIATE_TYPEINFO() must be defined in a .cpp file for each type `T`.
+template <typename T>
+struct TypeInfoOf {
+ /// The unique TypeInfo for the type T.
+ static const TypeInfo info;
+};
+
+/// A placeholder structure used for template parameters that need a default type, but can always be
+/// automatically inferred.
+struct Infer;
+
+} // namespace detail
+
+/// @returns true if `obj` is a valid pointer, and is of, or derives from the class `TO`
+/// @param obj the object to test from
+/// @see CastFlags
+template <typename TO, int FLAGS = 0, typename FROM = utils::detail::Infer>
+inline bool Is(FROM* obj) {
+ if (obj == nullptr) {
+ return false;
+ }
+ return TypeInfo::Is<TO, FROM, FLAGS>(&obj->TypeInfo());
+}
+
+/// @returns true if `obj` is a valid pointer, and is of, or derives from the type `TYPE`, and
+/// pred(const TYPE*) returns true
+/// @param obj the object to test from
+/// @param pred predicate function with signature `bool(const TYPE*)` called iff object is of, or
+/// derives from the class `TYPE`.
+/// @see CastFlags
+template <typename TYPE,
+ int FLAGS = 0,
+ typename OBJ = utils::detail::Infer,
+ typename Pred = utils::detail::Infer>
+inline bool Is(OBJ* obj, Pred&& pred) {
+ return Is<TYPE, FLAGS, OBJ>(obj) && pred(static_cast<std::add_const_t<TYPE>*>(obj));
+}
+
+/// @returns true if `obj` is a valid pointer, and is of, or derives from any of the types in
+/// `TYPES`.
+/// @param obj the object to query.
+template <typename... TYPES, typename OBJ>
+inline bool IsAnyOf(OBJ* obj) {
+ if (!obj) {
+ return false;
+ }
+ return obj->TypeInfo().template IsAnyOf<TYPES...>();
+}
+
+/// @returns obj dynamically cast to the type `TO` or `nullptr` if this object does not derive from
+/// `TO`.
+/// @param obj the object to cast from
+/// @see CastFlags
+template <typename TO, int FLAGS = 0, typename FROM = utils::detail::Infer>
+inline TO* As(FROM* obj) {
+ auto* as_castable = static_cast<CastableBase*>(obj);
+ return Is<TO, FLAGS>(obj) ? static_cast<TO*>(as_castable) : nullptr;
+}
+
+/// @returns obj dynamically cast to the type `TO` or `nullptr` if this object does not derive from
+/// `TO`.
+/// @param obj the object to cast from
+/// @see CastFlags
+template <typename TO, int FLAGS = 0, typename FROM = utils::detail::Infer>
+inline const TO* As(const FROM* obj) {
+ auto* as_castable = static_cast<const CastableBase*>(obj);
+ return Is<TO, FLAGS>(obj) ? static_cast<const TO*>(as_castable) : nullptr;
+}
+
+/// CastableBase is the base class for all Castable objects.
+/// It is not encouraged to directly derive from CastableBase without using the Castable helper
+/// template.
+/// @see Castable
+class CastableBase {
+ public:
+ /// Copy constructor
+ CastableBase(const CastableBase&);
+
+ /// Destructor
+ virtual ~CastableBase();
+
+ /// Copy assignment
+ /// @param other the CastableBase to copy
+ /// @returns the new CastableBase
+ CastableBase& operator=(const CastableBase& other) = default;
+
+ /// @returns the TypeInfo of the object
+ inline const tint::utils::TypeInfo& TypeInfo() const { return *type_info_; }
+
+ /// @returns true if this object is of, or derives from the class `TO`
+ template <typename TO>
+ inline bool Is() const {
+ return tint::utils::Is<TO>(this);
+ }
+
+ /// @returns true if this object is of, or derives from the class `TO` and pred(const TO*)
+ /// returns true
+ /// @param pred predicate function with signature `bool(const TO*)` called iff object is of, or
+ /// derives from the class `TO`.
+ template <typename TO, int FLAGS = 0, typename Pred = utils::detail::Infer>
+ inline bool Is(Pred&& pred) const {
+ return tint::utils::Is<TO, FLAGS>(this, std::forward<Pred>(pred));
+ }
+
+ /// @returns true if this object is of, or derives from any of the `TO` classes.
+ template <typename... TO>
+ inline bool IsAnyOf() const {
+ return tint::utils::IsAnyOf<TO...>(this);
+ }
+
+ /// @returns this object dynamically cast to the type `TO` or `nullptr` if this object does not
+ /// derive from `TO`.
+ /// @see CastFlags
+ template <typename TO, int FLAGS = 0>
+ inline TO* As() {
+ return tint::utils::As<TO, FLAGS>(this);
+ }
+
+ /// @returns this object dynamically cast to the type `TO` or `nullptr` if this object does not
+ /// derive from `TO`.
+ /// @see CastFlags
+ template <typename TO, int FLAGS = 0>
+ inline const TO* As() const {
+ return tint::utils::As<const TO, FLAGS>(this);
+ }
+
+ protected:
+ CastableBase() = default;
+
+ /// The type information for the object
+ const tint::utils::TypeInfo* type_info_ = nullptr;
+};
+
+/// Castable is a helper to derive `CLASS` from `BASE`, automatically implementing the Is() and As()
+/// methods, along with a #Base type alias.
+///
+/// Example usage:
+///
+/// ```
+/// class Animal : public Castable<Animal> {};
+///
+/// class Sheep : public Castable<Sheep, Animal> {};
+///
+/// Sheep* cast_to_sheep(Animal* animal) {
+/// // You can query whether a Castable is of the given type with Is<T>():
+/// printf("animal is a sheep? %s", animal->Is<Sheep>() ? "yes" : "no");
+///
+/// // You can always just try the cast with As<T>().
+/// // If the object is not of the correct type, As<T>() will return nullptr:
+/// return animal->As<Sheep>();
+/// }
+/// ```
+template <typename CLASS, typename BASE = CastableBase>
+class Castable : public BASE {
+ public:
+ /// A type alias to this Castable. Commonly used in derived type constructors to forward
+ /// constructor arguments to BASE.
+ using Base = Castable;
+
+ /// A type alias for `BASE`.
+ using TrueBase = BASE;
+
+ /// A type alias for `CLASS`.
+ using Class = CLASS;
+
+ /// Constructor
+ /// @param arguments the arguments to forward to the base class.
+ template <typename... ARGS>
+ inline explicit Castable(ARGS&&... arguments) : TrueBase(std::forward<ARGS>(arguments)...) {
+ this->type_info_ = &TypeInfo::Of<CLASS>();
+ }
+
+ /// @returns true if this object is of, or derives from the class `TO`
+ /// @see CastFlags
+ template <typename TO, int FLAGS = 0>
+ inline bool Is() const {
+ return tint::utils::Is<TO, FLAGS>(static_cast<const CLASS*>(this));
+ }
+
+ /// @returns true if this object is of, or derives from the class `TO` and
+ /// pred(const TO*) returns true
+ /// @param pred predicate function with signature `bool(const TO*)` called iff
+ /// object is of, or derives from the class `TO`.
+ template <int FLAGS = 0, typename Pred = utils::detail::Infer>
+ inline bool Is(Pred&& pred) const {
+ using TO = typename std::remove_pointer<utils::traits::ParameterType<Pred, 0>>::type;
+ return tint::utils::Is<TO, FLAGS>(static_cast<const CLASS*>(this),
+ std::forward<Pred>(pred));
+ }
+
+ /// @returns true if this object is of, or derives from any of the `TO`
+ /// classes.
+ template <typename... TO>
+ inline bool IsAnyOf() const {
+ return tint::utils::IsAnyOf<TO...>(static_cast<const CLASS*>(this));
+ }
+
+ /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+ /// this object does not derive from `TO`.
+ /// @see CastFlags
+ template <typename TO, int FLAGS = 0>
+ inline TO* As() {
+ return tint::utils::As<TO, FLAGS>(this);
+ }
+
+ /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+ /// this object does not derive from `TO`.
+ /// @see CastFlags
+ template <typename TO, int FLAGS = 0>
+ inline const TO* As() const {
+ return tint::utils::As<const TO, FLAGS>(this);
+ }
+};
+
+namespace detail {
+/// <code>typename CastableCommonBaseImpl<TYPES>::type</code> resolves to the common base class for
+/// all of TYPES.
+template <typename... TYPES>
+struct CastableCommonBaseImpl {};
+
+/// Alias to typename CastableCommonBaseImpl<TYPES>::type
+template <typename... TYPES>
+using CastableCommonBase = typename CastableCommonBaseImpl<TYPES...>::type;
+
+/// CastableCommonBaseImpl template specialization for a single type
+template <typename T>
+struct CastableCommonBaseImpl<T> {
+ /// Common base class of a single type is itself
+ using type = T;
+};
+
+/// CastableCommonBaseImpl A <-> CastableBase specialization
+template <typename A>
+struct CastableCommonBaseImpl<A, CastableBase> {
+ /// Common base class for A and CastableBase is CastableBase
+ using type = CastableBase;
+};
+
+/// CastableCommonBaseImpl T <-> Ignore specialization
+template <typename T>
+struct CastableCommonBaseImpl<T, Ignore> {
+ /// Resolves to T as the other type is ignored
+ using type = T;
+};
+
+/// CastableCommonBaseImpl Ignore <-> T specialization
+template <typename T>
+struct CastableCommonBaseImpl<Ignore, T> {
+ /// Resolves to T as the other type is ignored
+ using type = T;
+};
+
+/// CastableCommonBaseImpl A <-> B specialization
+template <typename A, typename B>
+struct CastableCommonBaseImpl<A, B> {
+ /// The common base class for A, B and OTHERS
+ using type = std::conditional_t<utils::traits::IsTypeOrDerived<A, B>,
+ B, // A derives from B
+ CastableCommonBase<A, typename B::TrueBase>>;
+};
+
+/// CastableCommonBaseImpl 3+ types specialization
+template <typename A, typename B, typename... OTHERS>
+struct CastableCommonBaseImpl<A, B, OTHERS...> {
+ /// The common base class for A, B and OTHERS
+ using type = CastableCommonBase<CastableCommonBase<A, B>, OTHERS...>;
+};
+
+} // namespace detail
+
+/// Resolves to the common most derived type that each of the types in `TYPES` derives from.
+template <typename... TYPES>
+using CastableCommonBase = utils::detail::CastableCommonBase<TYPES...>;
+
+} // namespace tint::utils
+
+namespace tint {
+
+using utils::As;
+using utils::Is;
+
+} // namespace tint
+
+TINT_CASTABLE_POP_DISABLE_WARNINGS();
+
+#endif // SRC_TINT_UTILS_RTTI_CASTABLE_H_
diff --git a/src/tint/utils/rtti/castable_test.cc b/src/tint/utils/rtti/castable_test.cc
new file mode 100644
index 0000000..847602d
--- /dev/null
+++ b/src/tint/utils/rtti/castable_test.cc
@@ -0,0 +1,294 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/rtti/castable.h"
+
+#include <memory>
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+struct Animal : public tint::utils::Castable<Animal> {};
+struct Amphibian : public tint::utils::Castable<Amphibian, Animal> {};
+struct Mammal : public tint::utils::Castable<Mammal, Animal> {};
+struct Reptile : public tint::utils::Castable<Reptile, Animal> {};
+struct Frog : public tint::utils::Castable<Frog, Amphibian> {};
+struct Bear : public tint::utils::Castable<Bear, Mammal> {};
+struct Lizard : public tint::utils::Castable<Lizard, Reptile> {};
+struct Gecko : public tint::utils::Castable<Gecko, Lizard> {};
+struct Iguana : public tint::utils::Castable<Iguana, Lizard> {};
+
+TEST(CastableBase, Is) {
+ std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+ std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+ std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+ ASSERT_TRUE(frog->Is<Animal>());
+ ASSERT_TRUE(bear->Is<Animal>());
+ ASSERT_TRUE(gecko->Is<Animal>());
+
+ ASSERT_TRUE(frog->Is<Amphibian>());
+ ASSERT_FALSE(bear->Is<Amphibian>());
+ ASSERT_FALSE(gecko->Is<Amphibian>());
+
+ ASSERT_FALSE(frog->Is<Mammal>());
+ ASSERT_TRUE(bear->Is<Mammal>());
+ ASSERT_FALSE(gecko->Is<Mammal>());
+
+ ASSERT_FALSE(frog->Is<Reptile>());
+ ASSERT_FALSE(bear->Is<Reptile>());
+ ASSERT_TRUE(gecko->Is<Reptile>());
+}
+
+TEST(CastableBase, Is_kDontErrorOnImpossibleCast) {
+ // Unlike TEST(CastableBase, Is), we're dynamically querying [A -> B] without
+ // going via CastableBase.
+ auto frog = std::make_unique<Frog>();
+ auto bear = std::make_unique<Bear>();
+ auto gecko = std::make_unique<Gecko>();
+
+ ASSERT_TRUE((frog->Is<Animal, kDontErrorOnImpossibleCast>()));
+ ASSERT_TRUE((bear->Is<Animal, kDontErrorOnImpossibleCast>()));
+ ASSERT_TRUE((gecko->Is<Animal, kDontErrorOnImpossibleCast>()));
+
+ ASSERT_TRUE((frog->Is<Amphibian, kDontErrorOnImpossibleCast>()));
+ ASSERT_FALSE((bear->Is<Amphibian, kDontErrorOnImpossibleCast>()));
+ ASSERT_FALSE((gecko->Is<Amphibian, kDontErrorOnImpossibleCast>()));
+
+ ASSERT_FALSE((frog->Is<Mammal, kDontErrorOnImpossibleCast>()));
+ ASSERT_TRUE((bear->Is<Mammal, kDontErrorOnImpossibleCast>()));
+ ASSERT_FALSE((gecko->Is<Mammal, kDontErrorOnImpossibleCast>()));
+
+ ASSERT_FALSE((frog->Is<Reptile, kDontErrorOnImpossibleCast>()));
+ ASSERT_FALSE((bear->Is<Reptile, kDontErrorOnImpossibleCast>()));
+ ASSERT_TRUE((gecko->Is<Reptile, kDontErrorOnImpossibleCast>()));
+}
+
+TEST(CastableBase, IsWithPredicate) {
+ std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+
+ frog->Is<Animal>([&frog](const Animal* a) {
+ EXPECT_EQ(a, frog.get());
+ return true;
+ });
+
+ ASSERT_TRUE((frog->Is<Animal>([](const Animal*) { return true; })));
+ ASSERT_FALSE((frog->Is<Animal>([](const Animal*) { return false; })));
+
+ // Predicate not called if cast is invalid
+ auto expect_not_called = [] { FAIL() << "Should not be called"; };
+ ASSERT_FALSE((frog->Is<Bear>([&](const Animal*) {
+ expect_not_called();
+ return true;
+ })));
+}
+
+TEST(CastableBase, IsAnyOf) {
+ std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+ std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+ std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+ ASSERT_TRUE((frog->IsAnyOf<Animal, Mammal, Amphibian, Reptile>()));
+ ASSERT_TRUE((frog->IsAnyOf<Mammal, Amphibian>()));
+ ASSERT_TRUE((frog->IsAnyOf<Amphibian, Reptile>()));
+ ASSERT_FALSE((frog->IsAnyOf<Mammal, Reptile>()));
+
+ ASSERT_TRUE((bear->IsAnyOf<Animal, Mammal, Amphibian, Reptile>()));
+ ASSERT_TRUE((bear->IsAnyOf<Mammal, Amphibian>()));
+ ASSERT_TRUE((bear->IsAnyOf<Mammal, Reptile>()));
+ ASSERT_FALSE((bear->IsAnyOf<Amphibian, Reptile>()));
+
+ ASSERT_TRUE((gecko->IsAnyOf<Animal, Mammal, Amphibian, Reptile>()));
+ ASSERT_TRUE((gecko->IsAnyOf<Mammal, Reptile>()));
+ ASSERT_TRUE((gecko->IsAnyOf<Amphibian, Reptile>()));
+ ASSERT_FALSE((gecko->IsAnyOf<Mammal, Amphibian>()));
+}
+
+TEST(CastableBase, As) {
+ std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+ std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+ std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+ ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
+ ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
+ ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
+
+ ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
+ ASSERT_EQ(bear->As<Amphibian>(), nullptr);
+ ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
+
+ ASSERT_EQ(frog->As<Mammal>(), nullptr);
+ ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
+ ASSERT_EQ(gecko->As<Mammal>(), nullptr);
+
+ ASSERT_EQ(frog->As<Reptile>(), nullptr);
+ ASSERT_EQ(bear->As<Reptile>(), nullptr);
+ ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
+}
+
+TEST(CastableBase, As_kDontErrorOnImpossibleCast) {
+ // Unlike TEST(CastableBase, As), we're dynamically casting [A -> B] without
+ // going via CastableBase.
+ auto frog = std::make_unique<Frog>();
+ auto bear = std::make_unique<Bear>();
+ auto gecko = std::make_unique<Gecko>();
+
+ ASSERT_EQ((frog->As<Animal, kDontErrorOnImpossibleCast>()), static_cast<Animal*>(frog.get()));
+ ASSERT_EQ((bear->As<Animal, kDontErrorOnImpossibleCast>()), static_cast<Animal*>(bear.get()));
+ ASSERT_EQ((gecko->As<Animal, kDontErrorOnImpossibleCast>()), static_cast<Animal*>(gecko.get()));
+
+ ASSERT_EQ((frog->As<Amphibian, kDontErrorOnImpossibleCast>()),
+ static_cast<Amphibian*>(frog.get()));
+ ASSERT_EQ((bear->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr);
+ ASSERT_EQ((gecko->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr);
+
+ ASSERT_EQ((frog->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr);
+ ASSERT_EQ((bear->As<Mammal, kDontErrorOnImpossibleCast>()), static_cast<Mammal*>(bear.get()));
+ ASSERT_EQ((gecko->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr);
+
+ ASSERT_EQ((frog->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr);
+ ASSERT_EQ((bear->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr);
+ ASSERT_EQ((gecko->As<Reptile, kDontErrorOnImpossibleCast>()),
+ static_cast<Reptile*>(gecko.get()));
+}
+
+TEST(Castable, Is) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+
+ ASSERT_TRUE(frog->Is<Animal>());
+ ASSERT_TRUE(bear->Is<Animal>());
+ ASSERT_TRUE(gecko->Is<Animal>());
+
+ ASSERT_TRUE(frog->Is<Amphibian>());
+ ASSERT_FALSE(bear->Is<Amphibian>());
+ ASSERT_FALSE(gecko->Is<Amphibian>());
+
+ ASSERT_FALSE(frog->Is<Mammal>());
+ ASSERT_TRUE(bear->Is<Mammal>());
+ ASSERT_FALSE(gecko->Is<Mammal>());
+
+ ASSERT_FALSE(frog->Is<Reptile>());
+ ASSERT_FALSE(bear->Is<Reptile>());
+ ASSERT_TRUE(gecko->Is<Reptile>());
+}
+
+TEST(Castable, IsWithPredicate) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+
+ frog->Is([&frog](const Animal* a) {
+ EXPECT_EQ(a, frog.get());
+ return true;
+ });
+
+ ASSERT_TRUE((frog->Is([](const Animal*) { return true; })));
+ ASSERT_FALSE((frog->Is([](const Animal*) { return false; })));
+
+ // Predicate not called if cast is invalid
+ auto expect_not_called = [] { FAIL() << "Should not be called"; };
+ ASSERT_FALSE((frog->Is([&](const Bear*) {
+ expect_not_called();
+ return true;
+ })));
+}
+
+TEST(Castable, As) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+
+ ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
+ ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
+ ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
+
+ ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
+ ASSERT_EQ(bear->As<Amphibian>(), nullptr);
+ ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
+
+ ASSERT_EQ(frog->As<Mammal>(), nullptr);
+ ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
+ ASSERT_EQ(gecko->As<Mammal>(), nullptr);
+
+ ASSERT_EQ(frog->As<Reptile>(), nullptr);
+ ASSERT_EQ(bear->As<Reptile>(), nullptr);
+ ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
+}
+
+// IsCastable static tests
+static_assert(IsCastable<CastableBase>);
+static_assert(IsCastable<Animal>);
+static_assert(IsCastable<Ignore, Frog, Bear>);
+static_assert(IsCastable<Mammal, Ignore, Amphibian, Gecko>);
+static_assert(!IsCastable<Mammal, int, Amphibian, Ignore, Gecko>);
+static_assert(!IsCastable<bool>);
+static_assert(!IsCastable<int, float>);
+static_assert(!IsCastable<Ignore>);
+
+// CastableCommonBase static tests
+static_assert(std::is_same_v<Animal, CastableCommonBase<Animal>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian>>);
+static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal>>);
+static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile>>);
+static_assert(std::is_same_v<Frog, CastableCommonBase<Frog>>);
+static_assert(std::is_same_v<Bear, CastableCommonBase<Bear>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard>>);
+static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko>>);
+static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana>>);
+
+static_assert(std::is_same_v<Animal, CastableCommonBase<Animal, Animal>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Amphibian>>);
+static_assert(std::is_same_v<Mammal, CastableCommonBase<Mammal, Mammal>>);
+static_assert(std::is_same_v<Reptile, CastableCommonBase<Reptile, Reptile>>);
+static_assert(std::is_same_v<Frog, CastableCommonBase<Frog, Frog>>);
+static_assert(std::is_same_v<Bear, CastableCommonBase<Bear, Bear>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard, Lizard>>);
+static_assert(std::is_same_v<Gecko, CastableCommonBase<Gecko, Gecko>>);
+static_assert(std::is_same_v<Iguana, CastableCommonBase<Iguana, Iguana>>);
+
+static_assert(std::is_same_v<CastableBase, CastableCommonBase<CastableBase, Animal>>);
+static_assert(std::is_same_v<CastableBase, CastableCommonBase<Animal, CastableBase>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Amphibian, Frog>>);
+static_assert(std::is_same_v<Amphibian, CastableCommonBase<Frog, Amphibian>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Reptile, Frog>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Reptile>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Bear>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana>>);
+
+static_assert(std::is_same_v<Animal, CastableCommonBase<Bear, Frog, Iguana>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Lizard, Gecko, Iguana>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Gecko, Iguana, Lizard>>);
+static_assert(std::is_same_v<Lizard, CastableCommonBase<Gecko, Lizard, Iguana>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Frog, Gecko, Iguana>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Iguana, Frog>>);
+static_assert(std::is_same_v<Animal, CastableCommonBase<Gecko, Frog, Iguana>>);
+
+static_assert(std::is_same_v<CastableBase, CastableCommonBase<Bear, Frog, Iguana, CastableBase>>);
+
+} // namespace
+
+TINT_INSTANTIATE_TYPEINFO(Animal);
+TINT_INSTANTIATE_TYPEINFO(Amphibian);
+TINT_INSTANTIATE_TYPEINFO(Mammal);
+TINT_INSTANTIATE_TYPEINFO(Reptile);
+TINT_INSTANTIATE_TYPEINFO(Frog);
+TINT_INSTANTIATE_TYPEINFO(Bear);
+TINT_INSTANTIATE_TYPEINFO(Lizard);
+TINT_INSTANTIATE_TYPEINFO(Gecko);
+
+} // namespace tint::utils
diff --git a/src/tint/utils/rtti/switch.h b/src/tint/utils/rtti/switch.h
new file mode 100644
index 0000000..7c42eda
--- /dev/null
+++ b/src/tint/utils/rtti/switch.h
@@ -0,0 +1,266 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_RTTI_SWITCH_H_
+#define SRC_TINT_UTILS_RTTI_SWITCH_H_
+
+#include <tuple>
+#include <utility>
+
+#include "src/tint/utils/macros/defer.h"
+#include "src/tint/utils/memory/bitcast.h"
+#include "src/tint/utils/rtti/castable.h"
+
+namespace tint {
+
+/// Default can be used as the default case for a Switch(), when all previous cases failed to match.
+///
+/// Example:
+/// ```
+/// Switch(object,
+/// [&](TypeA*) { /* ... */ },
+/// [&](TypeB*) { /* ... */ },
+/// [&](Default) { /* If not TypeA or TypeB */ });
+/// ```
+struct Default {};
+
+} // namespace tint
+
+namespace tint::detail {
+
+/// Evaluates to the Switch case type being matched by the switch case function `FN`.
+/// @note does not handle the Default case
+/// @see Switch().
+template <typename FN>
+using SwitchCaseType =
+ std::remove_pointer_t<utils::traits::ParameterType<std::remove_reference_t<FN>, 0>>;
+
+/// Evaluates to true if the function `FN` has the signature of a Default case in a Switch().
+/// @see Switch().
+template <typename FN>
+inline constexpr bool IsDefaultCase =
+ std::is_same_v<utils::traits::ParameterType<std::remove_reference_t<FN>, 0>, Default>;
+
+/// Searches the list of Switch cases for a Default case, returning the index of the Default case.
+/// If the a Default case is not found in the tuple, then -1 is returned.
+template <typename TUPLE, std::size_t START_IDX = 0>
+constexpr int IndexOfDefaultCase() {
+ if constexpr (START_IDX < std::tuple_size_v<TUPLE>) {
+ return IsDefaultCase<std::tuple_element_t<START_IDX, TUPLE>>
+ ? static_cast<int>(START_IDX)
+ : IndexOfDefaultCase<TUPLE, START_IDX + 1>();
+ } else {
+ return -1;
+ }
+}
+
+/// Resolves to T if T is not nullptr_t, otherwise resolves to Ignore.
+template <typename T>
+using NullptrToIgnore = std::conditional_t<std::is_same_v<T, std::nullptr_t>, utils::Ignore, T>;
+
+/// Resolves to `const TYPE` if any of `CASE_RETURN_TYPES` are const or pointer-to-const, otherwise
+/// resolves to TYPE.
+template <typename TYPE, typename... CASE_RETURN_TYPES>
+using PropagateReturnConst = std::conditional_t<
+ // Are any of the pointer-stripped types const?
+ (std::is_const_v<std::remove_pointer_t<CASE_RETURN_TYPES>> || ...),
+ const TYPE, // Yes: Apply const to TYPE
+ TYPE>; // No: Passthrough
+
+/// SwitchReturnTypeImpl is the implementation of SwitchReturnType
+template <bool IS_CASTABLE, typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl;
+
+/// SwitchReturnTypeImpl specialization for non-castable case types and an explicitly specified
+/// return type.
+template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, REQUESTED_TYPE, CASE_RETURN_TYPES...> {
+ /// Resolves to `REQUESTED_TYPE`
+ using type = REQUESTED_TYPE;
+};
+
+/// SwitchReturnTypeImpl specialization for non-castable case types and an inferred return type.
+template <typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, utils::detail::Infer, CASE_RETURN_TYPES...> {
+ /// Resolves to the common type for all the cases return types.
+ using type = std::common_type_t<CASE_RETURN_TYPES...>;
+};
+
+/// SwitchReturnTypeImpl specialization for castable case types and an explicitly specified return
+/// type.
+template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, REQUESTED_TYPE, CASE_RETURN_TYPES...> {
+ public:
+ /// Resolves to `const REQUESTED_TYPE*` or `REQUESTED_TYPE*`
+ using type = PropagateReturnConst<std::remove_pointer_t<REQUESTED_TYPE>, CASE_RETURN_TYPES...>*;
+};
+
+/// SwitchReturnTypeImpl specialization for castable case types and an inferred return type.
+template <typename... CASE_RETURN_TYPES>
+struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, utils::detail::Infer, CASE_RETURN_TYPES...> {
+ private:
+ using InferredType =
+ utils::CastableCommonBase<NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>;
+
+ public:
+ /// `const T*` or `T*`, where T is the common base type for all the castable case types.
+ using type = PropagateReturnConst<InferredType, CASE_RETURN_TYPES...>*;
+};
+
+/// Resolves to the return type for a Switch() with the requested return type `REQUESTED_TYPE` and
+/// case statement return types. If `REQUESTED_TYPE` is Infer then the return type will be inferred
+/// from the case return types.
+template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
+using SwitchReturnType = typename SwitchReturnTypeImpl<
+ utils::IsCastable<NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>,
+ REQUESTED_TYPE,
+ CASE_RETURN_TYPES...>::type;
+
+} // namespace tint::detail
+
+namespace tint {
+
+/// Switch is used to dispatch one of the provided callback case handler functions based on the type
+/// of `object` and the parameter type of the case handlers. Switch will sequentially check the type
+/// of `object` against each of the switch case handler functions, and will invoke the first case
+/// handler function which has a parameter type that matches the object type. When a case handler is
+/// matched, it will be called with the single argument of `object` cast to the case handler's
+/// parameter type. Switch will invoke at most one case handler. Each of the case functions must
+/// have the signature `R(T*)` or `R(const T*)`, where `T` is the type matched by that case and `R`
+/// is the return type, consistent across all case handlers.
+///
+/// An optional default case function with the signature `R(Default)` can be used as the last case.
+/// This default case will be called if all previous cases failed to match.
+///
+/// If `object` is nullptr and a default case is provided, then the default case will be called. If
+/// `object` is nullptr and no default case is provided, then no cases will be called.
+///
+/// Example:
+/// ```
+/// Switch(object,
+/// [&](TypeA*) { /* ... */ },
+/// [&](TypeB*) { /* ... */ });
+///
+/// Switch(object,
+/// [&](TypeA*) { /* ... */ },
+/// [&](TypeB*) { /* ... */ },
+/// [&](Default) { /* Called if object is not TypeA or TypeB */ });
+/// ```
+///
+/// @param object the object who's type is used to
+/// @param cases the switch cases
+/// @return the value returned by the called case. If no cases matched, then the zero value for the
+/// consistent case type.
+template <typename RETURN_TYPE = utils::detail::Infer,
+ typename T = utils::CastableBase,
+ typename... CASES>
+inline auto Switch(T* object, CASES&&... cases) {
+ using ReturnType =
+ tint::detail::SwitchReturnType<RETURN_TYPE, utils::traits::ReturnType<CASES>...>;
+ static constexpr int kDefaultIndex = tint::detail::IndexOfDefaultCase<std::tuple<CASES...>>();
+ static constexpr bool kHasDefaultCase = kDefaultIndex >= 0;
+ static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>;
+
+ // Static assertions
+ static constexpr bool kDefaultIsOK =
+ kDefaultIndex == -1 || kDefaultIndex == static_cast<int>(sizeof...(CASES) - 1);
+ static constexpr bool kReturnIsOK =
+ kHasDefaultCase || !kHasReturnType || std::is_constructible_v<ReturnType>;
+ static_assert(kDefaultIsOK, "Default case must be last in Switch()");
+ static_assert(kReturnIsOK,
+ "Switch() requires either a Default case or a return type that is either void or "
+ "default-constructable");
+
+ if (!object) { // Object is nullptr, so no cases can match
+ if constexpr (kHasDefaultCase) {
+ // Evaluate default case.
+ auto&& default_case =
+ std::get<kDefaultIndex>(std::forward_as_tuple(std::forward<CASES>(cases)...));
+ return static_cast<ReturnType>(default_case(Default{}));
+ } else {
+ // No default case, no case can match.
+ if constexpr (kHasReturnType) {
+ return ReturnType{};
+ } else {
+ return;
+ }
+ }
+ }
+
+ // Replacement for std::aligned_storage as this is broken on earlier versions of MSVC.
+ using ReturnTypeOrU8 = std::conditional_t<kHasReturnType, ReturnType, uint8_t>;
+ struct alignas(alignof(ReturnTypeOrU8)) ReturnStorage {
+ uint8_t data[sizeof(ReturnTypeOrU8)];
+ };
+ ReturnStorage return_storage;
+ auto* result = utils::Bitcast<ReturnTypeOrU8*>(&return_storage);
+
+ const utils::TypeInfo& type_info = object->TypeInfo();
+
+ // Examines the parameter type of the case function.
+ // If the parameter is a pointer type that `object` is of, or derives from, then that case
+ // function is called with `object` cast to that type, and `try_case` returns true.
+ // If the parameter is of type `Default`, then that case function is called and `try_case`
+ // returns true.
+ // Otherwise `try_case` returns false.
+ // If the case function is called and it returns a value, then this is copy constructed to the
+ // `result` pointer.
+ auto try_case = [&](auto&& case_fn) {
+ using CaseFunc = std::decay_t<decltype(case_fn)>;
+ using CaseType = tint::detail::SwitchCaseType<CaseFunc>;
+ bool success = false;
+ if constexpr (std::is_same_v<CaseType, Default>) {
+ if constexpr (kHasReturnType) {
+ new (result) ReturnType(static_cast<ReturnType>(case_fn(Default{})));
+ } else {
+ case_fn(Default{});
+ }
+ success = true;
+ } else {
+ if (type_info.Is<CaseType>()) {
+ auto* v = static_cast<CaseType*>(object);
+ if constexpr (kHasReturnType) {
+ new (result) ReturnType(static_cast<ReturnType>(case_fn(v)));
+ } else {
+ case_fn(v);
+ }
+ success = true;
+ }
+ }
+ return success;
+ };
+
+ // Use a logical-or fold expression to try each of the cases in turn, until one matches the
+ // object type or a Default is reached. `handled` is true if a case function was called.
+ bool handled = ((try_case(std::forward<CASES>(cases)) || ...));
+
+ if constexpr (kHasReturnType) {
+ if constexpr (kHasDefaultCase) {
+ // Default case means there must be a returned value.
+ // No need to check handled, no requirement for a zero-initializer of ReturnType.
+ TINT_DEFER(result->~ReturnType());
+ return *result;
+ } else {
+ if (handled) {
+ TINT_DEFER(result->~ReturnType());
+ return *result;
+ }
+ return ReturnType{};
+ }
+ }
+}
+
+} // namespace tint
+
+#endif // SRC_TINT_UTILS_RTTI_SWITCH_H_
diff --git a/src/tint/utils/rtti/switch_bench.cc b/src/tint/utils/rtti/switch_bench.cc
new file mode 100644
index 0000000..d2bf9f1
--- /dev/null
+++ b/src/tint/utils/rtti/switch_bench.cc
@@ -0,0 +1,274 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <memory>
+
+#include "benchmark/benchmark.h"
+
+#include "src/tint/utils/rtti/switch.h"
+
+namespace tint {
+namespace {
+
+struct Base : public tint::utils::Castable<Base> {};
+struct A : public tint::utils::Castable<A, Base> {};
+struct AA : public tint::utils::Castable<AA, A> {};
+struct AAA : public tint::utils::Castable<AAA, AA> {};
+struct AAB : public tint::utils::Castable<AAB, AA> {};
+struct AAC : public tint::utils::Castable<AAC, AA> {};
+struct AB : public tint::utils::Castable<AB, A> {};
+struct ABA : public tint::utils::Castable<ABA, AB> {};
+struct ABB : public tint::utils::Castable<ABB, AB> {};
+struct ABC : public tint::utils::Castable<ABC, AB> {};
+struct AC : public tint::utils::Castable<AC, A> {};
+struct ACA : public tint::utils::Castable<ACA, AC> {};
+struct ACB : public tint::utils::Castable<ACB, AC> {};
+struct ACC : public tint::utils::Castable<ACC, AC> {};
+struct B : public tint::utils::Castable<B, Base> {};
+struct BA : public tint::utils::Castable<BA, B> {};
+struct BAA : public tint::utils::Castable<BAA, BA> {};
+struct BAB : public tint::utils::Castable<BAB, BA> {};
+struct BAC : public tint::utils::Castable<BAC, BA> {};
+struct BB : public tint::utils::Castable<BB, B> {};
+struct BBA : public tint::utils::Castable<BBA, BB> {};
+struct BBB : public tint::utils::Castable<BBB, BB> {};
+struct BBC : public tint::utils::Castable<BBC, BB> {};
+struct BC : public tint::utils::Castable<BC, B> {};
+struct BCA : public tint::utils::Castable<BCA, BC> {};
+struct BCB : public tint::utils::Castable<BCB, BC> {};
+struct BCC : public tint::utils::Castable<BCC, BC> {};
+struct C : public tint::utils::Castable<C, Base> {};
+struct CA : public tint::utils::Castable<CA, C> {};
+struct CAA : public tint::utils::Castable<CAA, CA> {};
+struct CAB : public tint::utils::Castable<CAB, CA> {};
+struct CAC : public tint::utils::Castable<CAC, CA> {};
+struct CB : public tint::utils::Castable<CB, C> {};
+struct CBA : public tint::utils::Castable<CBA, CB> {};
+struct CBB : public tint::utils::Castable<CBB, CB> {};
+struct CBC : public tint::utils::Castable<CBC, CB> {};
+struct CC : public tint::utils::Castable<CC, C> {};
+struct CCA : public tint::utils::Castable<CCA, CC> {};
+struct CCB : public tint::utils::Castable<CCB, CC> {};
+struct CCC : public tint::utils::Castable<CCC, CC> {};
+
+using AllTypes = std::tuple<Base,
+ A,
+ AA,
+ AAA,
+ AAB,
+ AAC,
+ AB,
+ ABA,
+ ABB,
+ ABC,
+ AC,
+ ACA,
+ ACB,
+ ACC,
+ B,
+ BA,
+ BAA,
+ BAB,
+ BAC,
+ BB,
+ BBA,
+ BBB,
+ BBC,
+ BC,
+ BCA,
+ BCB,
+ BCC,
+ C,
+ CA,
+ CAA,
+ CAB,
+ CAC,
+ CB,
+ CBA,
+ CBB,
+ CBC,
+ CC,
+ CCA,
+ CCB,
+ CCC>;
+
+std::vector<std::unique_ptr<Base>> MakeObjects() {
+ std::vector<std::unique_ptr<Base>> out;
+ out.emplace_back(std::make_unique<Base>());
+ out.emplace_back(std::make_unique<A>());
+ out.emplace_back(std::make_unique<AA>());
+ out.emplace_back(std::make_unique<AAA>());
+ out.emplace_back(std::make_unique<AAB>());
+ out.emplace_back(std::make_unique<AAC>());
+ out.emplace_back(std::make_unique<AB>());
+ out.emplace_back(std::make_unique<ABA>());
+ out.emplace_back(std::make_unique<ABB>());
+ out.emplace_back(std::make_unique<ABC>());
+ out.emplace_back(std::make_unique<AC>());
+ out.emplace_back(std::make_unique<ACA>());
+ out.emplace_back(std::make_unique<ACB>());
+ out.emplace_back(std::make_unique<ACC>());
+ out.emplace_back(std::make_unique<B>());
+ out.emplace_back(std::make_unique<BA>());
+ out.emplace_back(std::make_unique<BAA>());
+ out.emplace_back(std::make_unique<BAB>());
+ out.emplace_back(std::make_unique<BAC>());
+ out.emplace_back(std::make_unique<BB>());
+ out.emplace_back(std::make_unique<BBA>());
+ out.emplace_back(std::make_unique<BBB>());
+ out.emplace_back(std::make_unique<BBC>());
+ out.emplace_back(std::make_unique<BC>());
+ out.emplace_back(std::make_unique<BCA>());
+ out.emplace_back(std::make_unique<BCB>());
+ out.emplace_back(std::make_unique<BCC>());
+ out.emplace_back(std::make_unique<C>());
+ out.emplace_back(std::make_unique<CA>());
+ out.emplace_back(std::make_unique<CAA>());
+ out.emplace_back(std::make_unique<CAB>());
+ out.emplace_back(std::make_unique<CAC>());
+ out.emplace_back(std::make_unique<CB>());
+ out.emplace_back(std::make_unique<CBA>());
+ out.emplace_back(std::make_unique<CBB>());
+ out.emplace_back(std::make_unique<CBC>());
+ out.emplace_back(std::make_unique<CC>());
+ out.emplace_back(std::make_unique<CCA>());
+ out.emplace_back(std::make_unique<CCB>());
+ out.emplace_back(std::make_unique<CCC>());
+ return out;
+}
+
+void CastableLargeSwitch(::benchmark::State& state) {
+ auto objects = MakeObjects();
+ size_t i = 0;
+ for (auto _ : state) {
+ auto* object = objects[i % objects.size()].get();
+ Switch(
+ object, //
+ [&](const AAA*) { ::benchmark::DoNotOptimize(i += 40); },
+ [&](const AAB*) { ::benchmark::DoNotOptimize(i += 50); },
+ [&](const AAC*) { ::benchmark::DoNotOptimize(i += 60); },
+ [&](const ABA*) { ::benchmark::DoNotOptimize(i += 80); },
+ [&](const ABB*) { ::benchmark::DoNotOptimize(i += 90); },
+ [&](const ABC*) { ::benchmark::DoNotOptimize(i += 100); },
+ [&](const ACA*) { ::benchmark::DoNotOptimize(i += 120); },
+ [&](const ACB*) { ::benchmark::DoNotOptimize(i += 130); },
+ [&](const ACC*) { ::benchmark::DoNotOptimize(i += 140); },
+ [&](const BAA*) { ::benchmark::DoNotOptimize(i += 170); },
+ [&](const BAB*) { ::benchmark::DoNotOptimize(i += 180); },
+ [&](const BAC*) { ::benchmark::DoNotOptimize(i += 190); },
+ [&](const BBA*) { ::benchmark::DoNotOptimize(i += 210); },
+ [&](const BBB*) { ::benchmark::DoNotOptimize(i += 220); },
+ [&](const BBC*) { ::benchmark::DoNotOptimize(i += 230); },
+ [&](const BCA*) { ::benchmark::DoNotOptimize(i += 250); },
+ [&](const BCB*) { ::benchmark::DoNotOptimize(i += 260); },
+ [&](const BCC*) { ::benchmark::DoNotOptimize(i += 270); },
+ [&](const CA*) { ::benchmark::DoNotOptimize(i += 290); },
+ [&](const CAA*) { ::benchmark::DoNotOptimize(i += 300); },
+ [&](const CAB*) { ::benchmark::DoNotOptimize(i += 310); },
+ [&](const CAC*) { ::benchmark::DoNotOptimize(i += 320); },
+ [&](const CBA*) { ::benchmark::DoNotOptimize(i += 340); },
+ [&](const CBB*) { ::benchmark::DoNotOptimize(i += 350); },
+ [&](const CBC*) { ::benchmark::DoNotOptimize(i += 360); },
+ [&](const CCA*) { ::benchmark::DoNotOptimize(i += 380); },
+ [&](const CCB*) { ::benchmark::DoNotOptimize(i += 390); },
+ [&](const CCC*) { ::benchmark::DoNotOptimize(i += 400); },
+ [&](Default) { ::benchmark::DoNotOptimize(i += 123); });
+ i = (i * 31) ^ (i << 5);
+ }
+}
+
+BENCHMARK(CastableLargeSwitch);
+
+void CastableMediumSwitch(::benchmark::State& state) {
+ auto objects = MakeObjects();
+ size_t i = 0;
+ for (auto _ : state) {
+ auto* object = objects[i % objects.size()].get();
+ Switch(
+ object, //
+ [&](const ACB*) { ::benchmark::DoNotOptimize(i += 130); },
+ [&](const BAA*) { ::benchmark::DoNotOptimize(i += 170); },
+ [&](const BAB*) { ::benchmark::DoNotOptimize(i += 180); },
+ [&](const BBA*) { ::benchmark::DoNotOptimize(i += 210); },
+ [&](const BBB*) { ::benchmark::DoNotOptimize(i += 220); },
+ [&](const CAA*) { ::benchmark::DoNotOptimize(i += 300); },
+ [&](const CCA*) { ::benchmark::DoNotOptimize(i += 380); },
+ [&](const CCB*) { ::benchmark::DoNotOptimize(i += 390); },
+ [&](const CCC*) { ::benchmark::DoNotOptimize(i += 400); },
+ [&](Default) { ::benchmark::DoNotOptimize(i += 123); });
+ i = (i * 31) ^ (i << 5);
+ }
+}
+
+BENCHMARK(CastableMediumSwitch);
+
+void CastableSmallSwitch(::benchmark::State& state) {
+ auto objects = MakeObjects();
+ size_t i = 0;
+ for (auto _ : state) {
+ auto* object = objects[i % objects.size()].get();
+ Switch(
+ object, //
+ [&](const AAB*) { ::benchmark::DoNotOptimize(i += 30); },
+ [&](const CAC*) { ::benchmark::DoNotOptimize(i += 290); },
+ [&](const CAA*) { ::benchmark::DoNotOptimize(i += 300); });
+ i = (i * 31) ^ (i << 5);
+ }
+}
+
+BENCHMARK(CastableSmallSwitch);
+
+} // namespace
+} // namespace tint
+
+TINT_INSTANTIATE_TYPEINFO(tint::Base);
+TINT_INSTANTIATE_TYPEINFO(tint::A);
+TINT_INSTANTIATE_TYPEINFO(tint::AA);
+TINT_INSTANTIATE_TYPEINFO(tint::AAA);
+TINT_INSTANTIATE_TYPEINFO(tint::AAB);
+TINT_INSTANTIATE_TYPEINFO(tint::AAC);
+TINT_INSTANTIATE_TYPEINFO(tint::AB);
+TINT_INSTANTIATE_TYPEINFO(tint::ABA);
+TINT_INSTANTIATE_TYPEINFO(tint::ABB);
+TINT_INSTANTIATE_TYPEINFO(tint::ABC);
+TINT_INSTANTIATE_TYPEINFO(tint::AC);
+TINT_INSTANTIATE_TYPEINFO(tint::ACA);
+TINT_INSTANTIATE_TYPEINFO(tint::ACB);
+TINT_INSTANTIATE_TYPEINFO(tint::ACC);
+TINT_INSTANTIATE_TYPEINFO(tint::B);
+TINT_INSTANTIATE_TYPEINFO(tint::BA);
+TINT_INSTANTIATE_TYPEINFO(tint::BAA);
+TINT_INSTANTIATE_TYPEINFO(tint::BAB);
+TINT_INSTANTIATE_TYPEINFO(tint::BAC);
+TINT_INSTANTIATE_TYPEINFO(tint::BB);
+TINT_INSTANTIATE_TYPEINFO(tint::BBA);
+TINT_INSTANTIATE_TYPEINFO(tint::BBB);
+TINT_INSTANTIATE_TYPEINFO(tint::BBC);
+TINT_INSTANTIATE_TYPEINFO(tint::BC);
+TINT_INSTANTIATE_TYPEINFO(tint::BCA);
+TINT_INSTANTIATE_TYPEINFO(tint::BCB);
+TINT_INSTANTIATE_TYPEINFO(tint::BCC);
+TINT_INSTANTIATE_TYPEINFO(tint::C);
+TINT_INSTANTIATE_TYPEINFO(tint::CA);
+TINT_INSTANTIATE_TYPEINFO(tint::CAA);
+TINT_INSTANTIATE_TYPEINFO(tint::CAB);
+TINT_INSTANTIATE_TYPEINFO(tint::CAC);
+TINT_INSTANTIATE_TYPEINFO(tint::CB);
+TINT_INSTANTIATE_TYPEINFO(tint::CBA);
+TINT_INSTANTIATE_TYPEINFO(tint::CBB);
+TINT_INSTANTIATE_TYPEINFO(tint::CBC);
+TINT_INSTANTIATE_TYPEINFO(tint::CC);
+TINT_INSTANTIATE_TYPEINFO(tint::CCA);
+TINT_INSTANTIATE_TYPEINFO(tint::CCB);
+TINT_INSTANTIATE_TYPEINFO(tint::CCC);
diff --git a/src/tint/utils/rtti/switch_test.cc b/src/tint/utils/rtti/switch_test.cc
new file mode 100644
index 0000000..252fb18
--- /dev/null
+++ b/src/tint/utils/rtti/switch_test.cc
@@ -0,0 +1,552 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/rtti/switch.h"
+
+#include <memory>
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+struct Animal : public tint::utils::Castable<Animal> {};
+struct Amphibian : public tint::utils::Castable<Amphibian, Animal> {};
+struct Mammal : public tint::utils::Castable<Mammal, Animal> {};
+struct Reptile : public tint::utils::Castable<Reptile, Animal> {};
+struct Frog : public tint::utils::Castable<Frog, Amphibian> {};
+struct Bear : public tint::utils::Castable<Bear, Mammal> {};
+struct Lizard : public tint::utils::Castable<Lizard, Reptile> {};
+struct Gecko : public tint::utils::Castable<Gecko, Lizard> {};
+struct Iguana : public tint::utils::Castable<Iguana, Lizard> {};
+
+TEST(Castable, SwitchNoDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ bool frog_matched_amphibian = false;
+ Switch(
+ frog.get(), //
+ [&](Reptile*) { FAIL() << "frog is not reptile"; },
+ [&](Mammal*) { FAIL() << "frog is not mammal"; },
+ [&](Amphibian* amphibian) {
+ EXPECT_EQ(amphibian, frog.get());
+ frog_matched_amphibian = true;
+ });
+ EXPECT_TRUE(frog_matched_amphibian);
+ }
+ {
+ bool bear_matched_mammal = false;
+ Switch(
+ bear.get(), //
+ [&](Reptile*) { FAIL() << "bear is not reptile"; },
+ [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
+ [&](Mammal* mammal) {
+ EXPECT_EQ(mammal, bear.get());
+ bear_matched_mammal = true;
+ });
+ EXPECT_TRUE(bear_matched_mammal);
+ }
+ {
+ bool gecko_matched_reptile = false;
+ Switch(
+ gecko.get(), //
+ [&](Mammal*) { FAIL() << "gecko is not mammal"; },
+ [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
+ [&](Reptile* reptile) {
+ EXPECT_EQ(reptile, gecko.get());
+ gecko_matched_reptile = true;
+ });
+ EXPECT_TRUE(gecko_matched_reptile);
+ }
+}
+
+TEST(Castable, SwitchWithUnusedDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ bool frog_matched_amphibian = false;
+ Switch(
+ frog.get(), //
+ [&](Reptile*) { FAIL() << "frog is not reptile"; },
+ [&](Mammal*) { FAIL() << "frog is not mammal"; },
+ [&](Amphibian* amphibian) {
+ EXPECT_EQ(amphibian, frog.get());
+ frog_matched_amphibian = true;
+ },
+ [&](Default) { FAIL() << "default should not have been selected"; });
+ EXPECT_TRUE(frog_matched_amphibian);
+ }
+ {
+ bool bear_matched_mammal = false;
+ Switch(
+ bear.get(), //
+ [&](Reptile*) { FAIL() << "bear is not reptile"; },
+ [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
+ [&](Mammal* mammal) {
+ EXPECT_EQ(mammal, bear.get());
+ bear_matched_mammal = true;
+ },
+ [&](Default) { FAIL() << "default should not have been selected"; });
+ EXPECT_TRUE(bear_matched_mammal);
+ }
+ {
+ bool gecko_matched_reptile = false;
+ Switch(
+ gecko.get(), //
+ [&](Mammal*) { FAIL() << "gecko is not mammal"; },
+ [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
+ [&](Reptile* reptile) {
+ EXPECT_EQ(reptile, gecko.get());
+ gecko_matched_reptile = true;
+ },
+ [&](Default) { FAIL() << "default should not have been selected"; });
+ EXPECT_TRUE(gecko_matched_reptile);
+ }
+}
+
+TEST(Castable, SwitchDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ bool frog_matched_default = false;
+ Switch(
+ frog.get(), //
+ [&](Reptile*) { FAIL() << "frog is not reptile"; },
+ [&](Mammal*) { FAIL() << "frog is not mammal"; },
+ [&](Default) { frog_matched_default = true; });
+ EXPECT_TRUE(frog_matched_default);
+ }
+ {
+ bool bear_matched_default = false;
+ Switch(
+ bear.get(), //
+ [&](Reptile*) { FAIL() << "bear is not reptile"; },
+ [&](Amphibian*) { FAIL() << "bear is not amphibian"; },
+ [&](Default) { bear_matched_default = true; });
+ EXPECT_TRUE(bear_matched_default);
+ }
+ {
+ bool gecko_matched_default = false;
+ Switch(
+ gecko.get(), //
+ [&](Mammal*) { FAIL() << "gecko is not mammal"; },
+ [&](Amphibian*) { FAIL() << "gecko is not amphibian"; },
+ [&](Default) { gecko_matched_default = true; });
+ EXPECT_TRUE(gecko_matched_default);
+ }
+}
+
+TEST(Castable, SwitchMatchFirst) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ {
+ bool frog_matched_animal = false;
+ Switch(
+ frog.get(),
+ [&](Animal* animal) {
+ EXPECT_EQ(animal, frog.get());
+ frog_matched_animal = true;
+ },
+ [&](Amphibian*) { FAIL() << "animal should have been matched first"; });
+ EXPECT_TRUE(frog_matched_animal);
+ }
+ {
+ bool frog_matched_amphibian = false;
+ Switch(
+ frog.get(),
+ [&](Amphibian* amphibain) {
+ EXPECT_EQ(amphibain, frog.get());
+ frog_matched_amphibian = true;
+ },
+ [&](Animal*) { FAIL() << "amphibian should have been matched first"; });
+ EXPECT_TRUE(frog_matched_amphibian);
+ }
+}
+
+TEST(Castable, SwitchReturnValueWithDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ const char* result = Switch(
+ frog.get(), //
+ [](Mammal*) { return "mammal"; }, //
+ [](Amphibian*) { return "amphibian"; }, //
+ [](Default) { return "unknown"; });
+ static_assert(std::is_same_v<decltype(result), const char*>);
+ EXPECT_EQ(std::string(result), "amphibian");
+ }
+ {
+ const char* result = Switch(
+ bear.get(), //
+ [](Mammal*) { return "mammal"; }, //
+ [](Amphibian*) { return "amphibian"; }, //
+ [](Default) { return "unknown"; });
+ static_assert(std::is_same_v<decltype(result), const char*>);
+ EXPECT_EQ(std::string(result), "mammal");
+ }
+ {
+ const char* result = Switch(
+ gecko.get(), //
+ [](Mammal*) { return "mammal"; }, //
+ [](Amphibian*) { return "amphibian"; }, //
+ [](Default) { return "unknown"; });
+ static_assert(std::is_same_v<decltype(result), const char*>);
+ EXPECT_EQ(std::string(result), "unknown");
+ }
+}
+
+TEST(Castable, SwitchReturnValueWithoutDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ const char* result = Switch(
+ frog.get(), //
+ [](Mammal*) { return "mammal"; }, //
+ [](Amphibian*) { return "amphibian"; });
+ static_assert(std::is_same_v<decltype(result), const char*>);
+ EXPECT_EQ(std::string(result), "amphibian");
+ }
+ {
+ const char* result = Switch(
+ bear.get(), //
+ [](Mammal*) { return "mammal"; }, //
+ [](Amphibian*) { return "amphibian"; });
+ static_assert(std::is_same_v<decltype(result), const char*>);
+ EXPECT_EQ(std::string(result), "mammal");
+ }
+ {
+ auto* result = Switch(
+ gecko.get(), //
+ [](Mammal*) { return "mammal"; }, //
+ [](Amphibian*) { return "amphibian"; });
+ static_assert(std::is_same_v<decltype(result), const char*>);
+ EXPECT_EQ(result, nullptr);
+ }
+}
+
+TEST(Castable, SwitchInferPODReturnTypeWithDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ auto result = Switch(
+ frog.get(), //
+ [](Mammal*) { return 1; }, //
+ [](Amphibian*) { return 2.0f; }, //
+ [](Default) { return 3.0; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 2.0);
+ }
+ {
+ auto result = Switch(
+ bear.get(), //
+ [](Mammal*) { return 1.0; }, //
+ [](Amphibian*) { return 2.0f; }, //
+ [](Default) { return 3; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 1.0);
+ }
+ {
+ auto result = Switch(
+ gecko.get(), //
+ [](Mammal*) { return 1.0f; }, //
+ [](Amphibian*) { return 2; }, //
+ [](Default) { return 3.0; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 3.0);
+ }
+}
+
+TEST(Castable, SwitchInferPODReturnTypeWithoutDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ auto result = Switch(
+ frog.get(), //
+ [](Mammal*) { return 1; }, //
+ [](Amphibian*) { return 2.0f; });
+ static_assert(std::is_same_v<decltype(result), float>);
+ EXPECT_EQ(result, 2.0f);
+ }
+ {
+ auto result = Switch(
+ bear.get(), //
+ [](Mammal*) { return 1.0f; }, //
+ [](Amphibian*) { return 2; });
+ static_assert(std::is_same_v<decltype(result), float>);
+ EXPECT_EQ(result, 1.0f);
+ }
+ {
+ auto result = Switch(
+ gecko.get(), //
+ [](Mammal*) { return 1.0; }, //
+ [](Amphibian*) { return 2.0f; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 0.0);
+ }
+}
+
+TEST(Castable, SwitchInferCastableReturnTypeWithDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ auto* result = Switch(
+ frog.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian*) { return nullptr; }, //
+ [](Default) { return nullptr; });
+ static_assert(std::is_same_v<decltype(result), Mammal*>);
+ EXPECT_EQ(result, nullptr);
+ }
+ {
+ auto* result = Switch(
+ bear.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian* p) { return const_cast<const Amphibian*>(p); },
+ [](Default) { return nullptr; });
+ static_assert(std::is_same_v<decltype(result), const Animal*>);
+ EXPECT_EQ(result, bear.get());
+ }
+ {
+ auto* result = Switch(
+ gecko.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian* p) { return p; }, //
+ [](Default) -> utils::CastableBase* { return nullptr; });
+ static_assert(std::is_same_v<decltype(result), utils::CastableBase*>);
+ EXPECT_EQ(result, nullptr);
+ }
+}
+
+TEST(Castable, SwitchInferCastableReturnTypeWithoutDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ auto* result = Switch(
+ frog.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian*) { return nullptr; });
+ static_assert(std::is_same_v<decltype(result), Mammal*>);
+ EXPECT_EQ(result, nullptr);
+ }
+ {
+ auto* result = Switch(
+ bear.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian* p) { return const_cast<const Amphibian*>(p); }); //
+ static_assert(std::is_same_v<decltype(result), const Animal*>);
+ EXPECT_EQ(result, bear.get());
+ }
+ {
+ auto* result = Switch(
+ gecko.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian* p) { return p; });
+ static_assert(std::is_same_v<decltype(result), Animal*>);
+ EXPECT_EQ(result, nullptr);
+ }
+}
+
+TEST(Castable, SwitchExplicitPODReturnTypeWithDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ auto result = Switch<double>(
+ frog.get(), //
+ [](Mammal*) { return 1; }, //
+ [](Amphibian*) { return 2.0f; }, //
+ [](Default) { return 3.0; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 2.0f);
+ }
+ {
+ auto result = Switch<double>(
+ bear.get(), //
+ [](Mammal*) { return 1; }, //
+ [](Amphibian*) { return 2; }, //
+ [](Default) { return 3; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 1.0f);
+ }
+ {
+ auto result = Switch<double>(
+ gecko.get(), //
+ [](Mammal*) { return 1.0f; }, //
+ [](Amphibian*) { return 2.0f; }, //
+ [](Default) { return 3.0f; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 3.0f);
+ }
+}
+
+TEST(Castable, SwitchExplicitPODReturnTypeWithoutDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ auto result = Switch<double>(
+ frog.get(), //
+ [](Mammal*) { return 1; }, //
+ [](Amphibian*) { return 2.0f; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 2.0f);
+ }
+ {
+ auto result = Switch<double>(
+ bear.get(), //
+ [](Mammal*) { return 1.0f; }, //
+ [](Amphibian*) { return 2; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 1.0f);
+ }
+ {
+ auto result = Switch<double>(
+ gecko.get(), //
+ [](Mammal*) { return 1.0; }, //
+ [](Amphibian*) { return 2.0f; });
+ static_assert(std::is_same_v<decltype(result), double>);
+ EXPECT_EQ(result, 0.0);
+ }
+}
+
+TEST(Castable, SwitchExplicitCastableReturnTypeWithDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ auto* result = Switch<Animal>(
+ frog.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian*) { return nullptr; }, //
+ [](Default) { return nullptr; });
+ static_assert(std::is_same_v<decltype(result), Animal*>);
+ EXPECT_EQ(result, nullptr);
+ }
+ {
+ auto* result = Switch<utils::CastableBase>(
+ bear.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian* p) { return const_cast<const Amphibian*>(p); },
+ [](Default) { return nullptr; });
+ static_assert(std::is_same_v<decltype(result), const utils::CastableBase*>);
+ EXPECT_EQ(result, bear.get());
+ }
+ {
+ auto* result = Switch<const Animal>(
+ gecko.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian* p) { return p; }, //
+ [](Default) { return nullptr; });
+ static_assert(std::is_same_v<decltype(result), const Animal*>);
+ EXPECT_EQ(result, nullptr);
+ }
+}
+
+TEST(Castable, SwitchExplicitCastableReturnTypeWithoutDefault) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+ {
+ auto* result = Switch<Animal>(
+ frog.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian*) { return nullptr; });
+ static_assert(std::is_same_v<decltype(result), Animal*>);
+ EXPECT_EQ(result, nullptr);
+ }
+ {
+ auto* result = Switch<utils::CastableBase>(
+ bear.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian* p) { return const_cast<const Amphibian*>(p); }); //
+ static_assert(std::is_same_v<decltype(result), const utils::CastableBase*>);
+ EXPECT_EQ(result, bear.get());
+ }
+ {
+ auto* result = Switch<const Animal*>(
+ gecko.get(), //
+ [](Mammal* p) { return p; }, //
+ [](Amphibian* p) { return p; });
+ static_assert(std::is_same_v<decltype(result), const Animal*>);
+ EXPECT_EQ(result, nullptr);
+ }
+}
+
+TEST(Castable, SwitchNull) {
+ Animal* null = nullptr;
+ Switch(
+ null, //
+ [&](Amphibian*) { FAIL() << "should not be called"; },
+ [&](Animal*) { FAIL() << "should not be called"; });
+}
+
+TEST(Castable, SwitchNullNoDefault) {
+ Animal* null = nullptr;
+ bool default_called = false;
+ Switch(
+ null, //
+ [&](Amphibian*) { FAIL() << "should not be called"; },
+ [&](Animal*) { FAIL() << "should not be called"; },
+ [&](Default) { default_called = true; });
+ EXPECT_TRUE(default_called);
+}
+
+TEST(Castable, SwitchReturnNoDefaultInitializer) {
+ struct Object {
+ explicit Object(int v) : value(v) {}
+ int value;
+ };
+
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ {
+ auto result = Switch(
+ frog.get(), //
+ [](Mammal*) { return Object(1); }, //
+ [](Amphibian*) { return Object(2); }, //
+ [](Default) { return Object(3); });
+ static_assert(std::is_same_v<decltype(result), Object>);
+ EXPECT_EQ(result.value, 2);
+ }
+ {
+ auto result = Switch(
+ frog.get(), //
+ [](Mammal*) { return Object(1); }, //
+ [](Default) { return Object(3); });
+ static_assert(std::is_same_v<decltype(result), Object>);
+ EXPECT_EQ(result.value, 3);
+ }
+}
+
+} // namespace
+
+TINT_INSTANTIATE_TYPEINFO(Animal);
+TINT_INSTANTIATE_TYPEINFO(Amphibian);
+TINT_INSTANTIATE_TYPEINFO(Mammal);
+TINT_INSTANTIATE_TYPEINFO(Reptile);
+TINT_INSTANTIATE_TYPEINFO(Frog);
+TINT_INSTANTIATE_TYPEINFO(Bear);
+TINT_INSTANTIATE_TYPEINFO(Lizard);
+TINT_INSTANTIATE_TYPEINFO(Gecko);
+
+} // namespace tint
diff --git a/src/tint/utils/templates/enums.tmpl.inc b/src/tint/utils/templates/enums.tmpl.inc
new file mode 100644
index 0000000..adc06e0
--- /dev/null
+++ b/src/tint/utils/templates/enums.tmpl.inc
@@ -0,0 +1,197 @@
+{{- (Globals).Put "enum_override_names" Map -}}
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- define "OverrideEnumName" -}}
+{{- /* Overrides the C++ name for a sem.Enum. */ -}}
+{{- /* Arguments: */ -}}
+{{- /* * 'Enum' the sem::Enum */ -}}
+{{- /* * 'Name' the new C++ name for enum */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum_override_names := (Globals).Get "enum_override_names" -}}
+{{- $enum_override_names.Put $.Enum $.Name -}}
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- define "EnumName" -}}
+{{- /* Prints the C++ name for the given sem.Enum argument. */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum_override_names := (Globals).Get "enum_override_names" -}}
+{{- $override := $enum_override_names.Get $ -}}
+{{ if $override -}}
+{{ $override -}}
+{{ else -}}
+{{ PascalCase $.Name}}
+{{- end -}}
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- define "EnumCase" -}}
+{{- /* Prints the 'Enum::kEntry' name for the provided sem.EnumEntry */ -}}
+{{- /* argument. */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- Eval "EnumName" $.Enum}}::k{{PascalCase $.Name}}
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- define "DeclareEnum" -}}
+{{- /* Declares the 'enum class' for the provided sem.Enum argument. */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum := Eval "EnumName" $ -}}
+enum class {{$enum}} {
+ kUndefined,
+{{- range $entry := $.Entries }}
+ k{{PascalCase $entry.Name}},{{if $entry.IsInternal}} // Tint-internal enum entry - not parsed{{end}}
+{{- end }}
+};
+
+/// @param out the stream to write to
+/// @param value the {{$enum}}
+/// @returns `out` so calls can be chained
+utils::StringStream& operator<<(utils::StringStream& out, {{$enum}} value);
+
+/// Parse{{$enum}} parses a {{$enum}} from a string.
+/// @param str the string to parse
+/// @returns the parsed enum, or {{$enum}}::kUndefined if the string could not be parsed.
+{{$enum}} Parse{{$enum}}(std::string_view str);
+
+constexpr const char* k{{$enum}}Strings[] = {
+{{- range $entry := $.Entries }}
+{{- if not $entry.IsInternal}}
+ "{{$entry.Name}}",
+{{- end }}
+{{- end }}
+};
+
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- define "ParseEnum" -}}
+{{- /* Implements the 'ParseEnum' function for the provided sem.Enum */ -}}
+{{- /* argument. */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum := Eval "EnumName" $ -}}
+/// Parse{{$enum}} parses a {{$enum}} from a string.
+/// @param str the string to parse
+/// @returns the parsed enum, or {{$enum}}::kUndefined if the string could not be parsed.
+{{$enum}} Parse{{$enum}}(std::string_view str) {
+{{- range $entry := $.PublicEntries }}
+ if (str == "{{$entry.Name}}") {
+ return {{template "EnumCase" $entry}};
+ }
+{{- end }}
+ return {{$enum}}::kUndefined;
+}
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- define "EnumOStream" -}}
+{{- /* Implements the stream 'operator<<()' function to print the */ -}}
+{{- /* provided sem.Enum. */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum := Eval "EnumName" $ -}}
+ utils::StringStream& operator<<(utils::StringStream& out, {{$enum}} value) {
+ switch (value) {
+ case {{$enum}}::kUndefined:
+ return out << "undefined";
+{{- range $entry := $.Entries }}
+ case {{template "EnumCase" $entry}}:
+ return out << "{{$entry.Name}}";
+{{- end }}
+ }
+ return out << "<unknown>";
+}
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- define "TestParsePrintEnum" -}}
+{{- /* Implements unit tests for parsing and printing the provided */ -}}
+{{- /* sem.Enum argument. */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum := Eval "EnumName" $ -}}
+namespace parse_print_tests {
+
+struct Case {
+ const char* string;
+ {{$enum}} value;
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+ return out << "'" << std::string(c.string) << "'";
+}
+
+static constexpr Case kValidCases[] = {
+{{- range $entry := $.PublicEntries }}
+ {"{{$entry.Name}}", {{template "EnumCase" $entry}}},
+{{- end }}
+};
+
+static constexpr Case kInvalidCases[] = {
+{{- $exclude := $.NameSet -}}
+{{- range $entry := $.PublicEntries }}
+ {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kUndefined},
+ {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kUndefined},
+ {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kUndefined},
+{{- end }}
+};
+
+using {{$enum}}ParseTest = testing::TestWithParam<Case>;
+
+TEST_P({{$enum}}ParseTest, Parse) {
+ const char* string = GetParam().string;
+ {{$enum}} expect = GetParam().value;
+ EXPECT_EQ(expect, Parse{{$enum}}(string));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, {{$enum}}ParseTest, testing::ValuesIn(kValidCases));
+INSTANTIATE_TEST_SUITE_P(InvalidCases, {{$enum}}ParseTest, testing::ValuesIn(kInvalidCases));
+
+using {{$enum}}PrintTest = testing::TestWithParam<Case>;
+
+TEST_P({{$enum}}PrintTest, Print) {
+ {{$enum}} value = GetParam().value;
+ const char* expect = GetParam().string;
+ EXPECT_EQ(expect, utils::ToString(value));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidCases, {{$enum}}PrintTest, testing::ValuesIn(kValidCases));
+
+} // namespace parse_print_tests
+
+{{- end -}}
+
+
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- define "BenchmarkParseEnum" -}}
+{{- /* Implements a micro-benchmark for parsing the provided sem.Enum */ -}}
+{{- /* argument. */ -}}
+{{- /* ------------------------------------------------------------------ */ -}}
+{{- $enum := Eval "EnumName" $ -}}
+void {{$enum}}Parser(::benchmark::State& state) {
+ const char* kStrings[] = {
+{{- $exclude := $.NameSet -}}
+{{- range $entry := $.PublicEntries }}
+ "{{Scramble $entry.Name $exclude}}",
+ "{{Scramble $entry.Name $exclude}}",
+ "{{Scramble $entry.Name $exclude}}",
+ "{{$entry.Name}}",
+ "{{Scramble $entry.Name $exclude}}",
+ "{{Scramble $entry.Name $exclude}}",
+ "{{Scramble $entry.Name $exclude}}",
+{{- end }}
+ };
+ for (auto _ : state) {
+ for (auto* str : kStrings) {
+ auto result = Parse{{$enum}}(str);
+ benchmark::DoNotOptimize(result);
+ }
+ }
+} // NOLINT(readability/fn_size)
+
+BENCHMARK({{$enum}}Parser);
+{{- end -}}
diff --git a/src/tint/utils/text/float_to_string.cc b/src/tint/utils/text/float_to_string.cc
new file mode 100644
index 0000000..9f9878c
--- /dev/null
+++ b/src/tint/utils/text/float_to_string.cc
@@ -0,0 +1,177 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/float_to_string.h"
+
+#include <cmath>
+#include <cstring>
+#include <functional>
+#include <iomanip>
+#include <limits>
+
+#include "src/tint/utils/debug/debug.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::writer {
+
+namespace {
+
+template <typename T>
+struct Traits;
+
+template <>
+struct Traits<float> {
+ using uint_t = uint32_t;
+ static constexpr int kExponentBias = 127;
+ static constexpr uint_t kExponentMask = 0x7f800000;
+ static constexpr uint_t kMantissaMask = 0x007fffff;
+ static constexpr uint_t kSignMask = 0x80000000;
+ static constexpr int kMantissaBits = 23;
+};
+
+template <>
+struct Traits<double> {
+ using uint_t = uint64_t;
+ static constexpr int kExponentBias = 1023;
+ static constexpr uint_t kExponentMask = 0x7ff0000000000000;
+ static constexpr uint_t kMantissaMask = 0x000fffffffffffff;
+ static constexpr uint_t kSignMask = 0x8000000000000000;
+ static constexpr int kMantissaBits = 52;
+};
+
+template <typename F>
+std::string ToString(F f) {
+ utils::StringStream s;
+ s << f;
+ return s.str();
+}
+
+template <typename F>
+std::string ToBitPreservingString(F f) {
+ using T = Traits<F>;
+ using uint_t = typename T::uint_t;
+
+ // For the NaN case, avoid handling the number as a floating point value.
+ // Some machines will modify the top bit in the mantissa of a NaN.
+
+ std::stringstream ss;
+
+ typename T::uint_t float_bits = 0u;
+ static_assert(sizeof(float_bits) == sizeof(f));
+ std::memcpy(&float_bits, &f, sizeof(float_bits));
+
+ // Handle the sign.
+ if (float_bits & T::kSignMask) {
+ // If `f` is -0.0 print -0.0.
+ ss << '-';
+ // Strip sign bit.
+ float_bits = float_bits & (~T::kSignMask);
+ }
+
+ switch (std::fpclassify(f)) {
+ case FP_ZERO:
+ case FP_NORMAL:
+ std::memcpy(&f, &float_bits, sizeof(float_bits));
+ ss << ToString(f);
+ break;
+
+ default: {
+ // Infinity, NaN, and Subnormal
+ // TODO(dneto): It's unclear how Infinity and NaN should be handled.
+ // See https://github.com/gpuweb/gpuweb/issues/1769
+
+ // std::hexfloat prints 'nan' and 'inf' instead of an explicit representation like we
+ // want. Split it out manually.
+ int mantissa_nibbles = (T::kMantissaBits + 3) / 4;
+
+ const int biased_exponent =
+ static_cast<int>((float_bits & T::kExponentMask) >> T::kMantissaBits);
+ int exponent = biased_exponent - T::kExponentBias;
+ uint_t mantissa = float_bits & T::kMantissaMask;
+
+ ss << "0x";
+
+ if (exponent == T::kExponentBias + 1) {
+ if (mantissa == 0) {
+ // Infinity case.
+ ss << "1p+" << exponent;
+ } else {
+ // NaN case.
+ // Emit the mantissa bits as if they are left-justified after the binary point.
+ // This is what SPIRV-Tools hex float emitter does, and it's a justifiable
+ // choice independent of the bit width of the mantissa.
+ mantissa <<= (4 - (T::kMantissaBits % 4));
+ // Remove trailing zeroes, for tidiness.
+ while (0 == (0xf & mantissa)) {
+ mantissa >>= 4;
+ mantissa_nibbles--;
+ }
+ ss << "1." << std::hex << std::setfill('0') << std::setw(mantissa_nibbles)
+ << mantissa << "p+" << std::dec << exponent;
+ }
+ } else {
+ // Subnormal, and not zero.
+ TINT_ASSERT(Writer, mantissa != 0);
+ const auto kTopBit = static_cast<uint_t>(1u) << T::kMantissaBits;
+
+ // Shift left until we get 1.x
+ while (0 == (kTopBit & mantissa)) {
+ mantissa <<= 1;
+ exponent--;
+ }
+ // Emit the leading 1, and remove it from the mantissa.
+ ss << "1";
+ mantissa = mantissa ^ kTopBit;
+ exponent++;
+
+ // Left-justify mantissa to whole nibble.
+ mantissa <<= (4 - (T::kMantissaBits % 4));
+
+ // Emit the fractional part.
+ if (mantissa) {
+ // Remove trailing zeroes, for tidiness
+ while (0 == (0xf & mantissa)) {
+ mantissa >>= 4;
+ mantissa_nibbles--;
+ }
+ ss << "." << std::hex << std::setfill('0') << std::setw(mantissa_nibbles)
+ << mantissa;
+ }
+ // Emit the exponent
+ ss << "p" << std::showpos << std::dec << exponent;
+ }
+ }
+ }
+ return ss.str();
+}
+
+} // namespace
+
+std::string FloatToString(float f) {
+ return ToString(f);
+}
+
+std::string FloatToBitPreservingString(float f) {
+ return ToBitPreservingString(f);
+}
+
+std::string DoubleToString(double f) {
+ return ToString(f);
+}
+
+std::string DoubleToBitPreservingString(double f) {
+ return ToBitPreservingString(f);
+}
+
+} // namespace tint::writer
diff --git a/src/tint/utils/text/float_to_string.h b/src/tint/utils/text/float_to_string.h
new file mode 100644
index 0000000..dd4a2a3
--- /dev/null
+++ b/src/tint/utils/text/float_to_string.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TEXT_FLOAT_TO_STRING_H_
+#define SRC_TINT_UTILS_TEXT_FLOAT_TO_STRING_H_
+
+#include <string>
+
+namespace tint::writer {
+
+/// Converts the float `f` to a string using fixed-point notation (not
+/// scientific). The float will be printed with the full precision required to
+/// describe the float. All trailing `0`s will be omitted after the last
+/// non-zero fractional number, unless the fractional is zero, in which case the
+/// number will end with `.0`.
+/// @return the float f formatted to a string
+std::string FloatToString(float f);
+
+/// Converts the double `f` to a string using fixed-point notation (not
+/// scientific). The double will be printed with the full precision required to
+/// describe the double. All trailing `0`s will be omitted after the last
+/// non-zero fractional number, unless the fractional is zero, in which case the
+/// number will end with `.0`.
+/// @return the double f formatted to a string
+std::string DoubleToString(double f);
+
+/// Converts the float `f` to a string, using hex float notation for infinities,
+/// NaNs, or subnormal numbers. Otherwise behaves as FloatToString.
+/// @return the float f formatted to a string
+std::string FloatToBitPreservingString(float f);
+
+/// Converts the double `f` to a string, using hex double notation for infinities,
+/// NaNs, or subnormal numbers. Otherwise behaves as FloatToString.
+/// @return the double f formatted to a string
+std::string DoubleToBitPreservingString(double f);
+
+} // namespace tint::writer
+
+#endif // SRC_TINT_UTILS_TEXT_FLOAT_TO_STRING_H_
diff --git a/src/tint/utils/text/float_to_string_test.cc b/src/tint/utils/text/float_to_string_test.cc
new file mode 100644
index 0000000..20beb16
--- /dev/null
+++ b/src/tint/utils/text/float_to_string_test.cc
@@ -0,0 +1,335 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/float_to_string.h"
+
+#include <math.h>
+#include <cstring>
+#include <limits>
+
+#include "gtest/gtest.h"
+#include "src/tint/utils/memory/bitcast.h"
+
+namespace tint::writer {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+// FloatToString //
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(FloatToStringTest, Zero) {
+ EXPECT_EQ(FloatToString(0.0f), "0.0");
+}
+
+TEST(FloatToStringTest, One) {
+ EXPECT_EQ(FloatToString(1.0f), "1.0");
+}
+
+TEST(FloatToStringTest, MinusOne) {
+ EXPECT_EQ(FloatToString(-1.0f), "-1.0");
+}
+
+TEST(FloatToStringTest, Billion) {
+ EXPECT_EQ(FloatToString(1e9f), "1000000000.0");
+}
+
+TEST(FloatToStringTest, Small) {
+ EXPECT_NE(FloatToString(std::numeric_limits<float>::epsilon()), "0.0");
+}
+
+TEST(FloatToStringTest, Highest) {
+ const auto highest = std::numeric_limits<float>::max();
+ const auto expected_highest = 340282346638528859811704183484516925440.0f;
+ if (highest < expected_highest || highest > expected_highest) {
+ GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
+ "this target";
+ }
+ EXPECT_EQ(FloatToString(std::numeric_limits<float>::max()),
+ "340282346638528859811704183484516925440.0");
+}
+
+TEST(FloatToStringTest, Lowest) {
+ // Some compilers complain if you test floating point numbers for equality.
+ // So say it via two inequalities.
+ const auto lowest = std::numeric_limits<float>::lowest();
+ const auto expected_lowest = -340282346638528859811704183484516925440.0f;
+ if (lowest < expected_lowest || lowest > expected_lowest) {
+ GTEST_SKIP() << "std::numeric_limits<float>::lowest() is not as expected for "
+ "this target";
+ }
+ EXPECT_EQ(FloatToString(std::numeric_limits<float>::lowest()),
+ "-340282346638528859811704183484516925440.0");
+}
+
+TEST(FloatToStringTest, Precision) {
+ EXPECT_EQ(FloatToString(1e-8f), "0.00000000999999993923");
+ EXPECT_EQ(FloatToString(1e-9f), "0.00000000099999997172");
+ EXPECT_EQ(FloatToString(1e-10f), "0.00000000010000000134");
+ EXPECT_EQ(FloatToString(1e-20f), "0.00000000000000000001");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FloatToBitPreservingString //
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(FloatToBitPreservingStringTest, Zero) {
+ EXPECT_EQ(FloatToBitPreservingString(0.0f), "0.0");
+}
+
+TEST(FloatToBitPreservingStringTest, NegativeZero) {
+ EXPECT_EQ(FloatToBitPreservingString(-0.0f), "-0.0");
+}
+
+TEST(FloatToBitPreservingStringTest, One) {
+ EXPECT_EQ(FloatToBitPreservingString(1.0f), "1.0");
+}
+
+TEST(FloatToBitPreservingStringTest, MinusOne) {
+ EXPECT_EQ(FloatToBitPreservingString(-1.0f), "-1.0");
+}
+
+TEST(FloatToBitPreservingStringTest, Billion) {
+ EXPECT_EQ(FloatToBitPreservingString(1e9f), "1000000000.0");
+}
+
+TEST(FloatToBitPreservingStringTest, Small) {
+ EXPECT_NE(FloatToBitPreservingString(std::numeric_limits<float>::epsilon()), "0.0");
+}
+
+TEST(FloatToBitPreservingStringTest, Highest) {
+ const auto highest = std::numeric_limits<float>::max();
+ const auto expected_highest = 340282346638528859811704183484516925440.0f;
+ if (highest < expected_highest || highest > expected_highest) {
+ GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
+ "this target";
+ }
+ EXPECT_EQ(FloatToBitPreservingString(std::numeric_limits<float>::max()),
+ "340282346638528859811704183484516925440.0");
+}
+
+TEST(FloatToBitPreservingStringTest, Lowest) {
+ // Some compilers complain if you test floating point numbers for equality.
+ // So say it via two inequalities.
+ const auto lowest = std::numeric_limits<float>::lowest();
+ const auto expected_lowest = -340282346638528859811704183484516925440.0f;
+ if (lowest < expected_lowest || lowest > expected_lowest) {
+ GTEST_SKIP() << "std::numeric_limits<float>::lowest() is not as expected for "
+ "this target";
+ }
+ EXPECT_EQ(FloatToBitPreservingString(std::numeric_limits<float>::lowest()),
+ "-340282346638528859811704183484516925440.0");
+}
+
+TEST(FloatToBitPreservingStringTest, SmallestDenormal) {
+ EXPECT_EQ(FloatToBitPreservingString(0x1p-149f), "0x1p-149");
+ EXPECT_EQ(FloatToBitPreservingString(-0x1p-149f), "-0x1p-149");
+}
+
+TEST(FloatToBitPreservingStringTest, BiggerDenormal) {
+ EXPECT_EQ(FloatToBitPreservingString(0x1p-148f), "0x1p-148");
+ EXPECT_EQ(FloatToBitPreservingString(-0x1p-148f), "-0x1p-148");
+}
+
+TEST(FloatToBitPreservingStringTest, LargestDenormal) {
+ static_assert(0x0.fffffep-126f == 0x1.fffffcp-127f);
+ EXPECT_EQ(FloatToBitPreservingString(0x0.fffffep-126f), "0x1.fffffcp-127");
+}
+
+TEST(FloatToBitPreservingStringTest, Subnormal_cafebe) {
+ EXPECT_EQ(FloatToBitPreservingString(0x1.2bfaf8p-127f), "0x1.2bfaf8p-127");
+ EXPECT_EQ(FloatToBitPreservingString(-0x1.2bfaf8p-127f), "-0x1.2bfaf8p-127");
+}
+
+TEST(FloatToBitPreservingStringTest, Subnormal_aaaaa) {
+ EXPECT_EQ(FloatToBitPreservingString(0x1.55554p-130f), "0x1.55554p-130");
+ EXPECT_EQ(FloatToBitPreservingString(-0x1.55554p-130f), "-0x1.55554p-130");
+}
+
+TEST(FloatToBitPreservingStringTest, Infinity) {
+ EXPECT_EQ(FloatToBitPreservingString(INFINITY), "0x1p+128");
+ EXPECT_EQ(FloatToBitPreservingString(-INFINITY), "-0x1p+128");
+}
+
+TEST(FloatToBitPreservingStringTest, NaN) {
+ // TODO(crbug.com/tint/1714): On x86, this bitcast will set bit 22 (the highest mantissa bit) to
+ // 1, regardless of the bit value in the integer. This is likely due to IEEE 754's
+ // recommendation that that the highest mantissa bit differentiates quiet NaNs from signalling
+ // NaNs. On x86, float return values usually go via the FPU which can transform the signalling
+ // NaN bit (0) to quiet NaN (1). As NaN floating point numbers can be silently modified by the
+ // architecture, and the signalling bit is architecture defined, this test may fail on other
+ // architectures.
+ auto nan = utils::Bitcast<float>(0x7fc0beef);
+ EXPECT_EQ(FloatToBitPreservingString(nan), "0x1.817ddep+128");
+ EXPECT_EQ(FloatToBitPreservingString(-nan), "-0x1.817ddep+128");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DoubleToString //
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(DoubleToStringTest, Zero) {
+ EXPECT_EQ(DoubleToString(0.000000000), "0.0");
+}
+
+TEST(DoubleToStringTest, One) {
+ EXPECT_EQ(DoubleToString(1.000000000), "1.0");
+}
+
+TEST(DoubleToStringTest, MinusOne) {
+ EXPECT_EQ(DoubleToString(-1.000000000), "-1.0");
+}
+
+TEST(DoubleToStringTest, Billion) {
+ EXPECT_EQ(DoubleToString(1e9), "1000000000.0");
+}
+
+TEST(DoubleToStringTest, Small) {
+ EXPECT_NE(DoubleToString(std::numeric_limits<double>::epsilon()), "0.0");
+}
+
+TEST(DoubleToStringTest, Highest) {
+ const auto highest = std::numeric_limits<double>::max();
+ const auto expected_highest = 1.797693134862315708e+308;
+ if (highest < expected_highest || highest > expected_highest) {
+ GTEST_SKIP() << "std::numeric_limits<double>::max() is not as expected for "
+ "this target";
+ }
+ EXPECT_EQ(DoubleToString(std::numeric_limits<double>::max()),
+ "179769313486231570814527423731704356798070567525844996598917476803157260780028538760"
+ "589558632766878171540458953514382464234321326889464182768467546703537516986049910576"
+ "551282076245490090389328944075868508455133942304583236903222948165808559332123348274"
+ "797826204144723168738177180919299881250404026184124858368.0");
+}
+
+TEST(DoubleToStringTest, Lowest) {
+ // Some compilers complain if you test floating point numbers for equality.
+ // So say it via two inequalities.
+ const auto lowest = std::numeric_limits<double>::lowest();
+ const auto expected_lowest = -1.797693134862315708e+308;
+ if (lowest < expected_lowest || lowest > expected_lowest) {
+ GTEST_SKIP() << "std::numeric_limits<double>::lowest() is not as expected for "
+ "this target";
+ }
+ EXPECT_EQ(DoubleToString(std::numeric_limits<double>::lowest()),
+ "-17976931348623157081452742373170435679807056752584499659891747680315726078002853876"
+ "058955863276687817154045895351438246423432132688946418276846754670353751698604991057"
+ "655128207624549009038932894407586850845513394230458323690322294816580855933212334827"
+ "4797826204144723168738177180919299881250404026184124858368.0");
+}
+
+TEST(DoubleToStringTest, Precision) {
+ EXPECT_EQ(DoubleToString(1e-8), "0.00000001");
+ EXPECT_EQ(DoubleToString(1e-9), "0.000000001");
+ EXPECT_EQ(DoubleToString(1e-10), "0.0000000001");
+ EXPECT_EQ(DoubleToString(1e-15), "0.000000000000001");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DoubleToBitPreservingString //
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(DoubleToBitPreservingStringTest, Zero) {
+ EXPECT_EQ(DoubleToBitPreservingString(0.0), "0.0");
+}
+
+TEST(DoubleToBitPreservingStringTest, NegativeZero) {
+ EXPECT_EQ(DoubleToBitPreservingString(-0.0), "-0.0");
+}
+
+TEST(DoubleToBitPreservingStringTest, One) {
+ EXPECT_EQ(DoubleToBitPreservingString(1.0), "1.0");
+}
+
+TEST(DoubleToBitPreservingStringTest, MinusOne) {
+ EXPECT_EQ(DoubleToBitPreservingString(-1.0), "-1.0");
+}
+
+TEST(DoubleToBitPreservingStringTest, Billion) {
+ EXPECT_EQ(DoubleToBitPreservingString(1e9), "1000000000.0");
+}
+
+TEST(DoubleToBitPreservingStringTest, Small) {
+ EXPECT_NE(DoubleToBitPreservingString(std::numeric_limits<double>::epsilon()), "0.0");
+}
+
+TEST(DoubleToBitPreservingStringTest, Highest) {
+ const auto highest = std::numeric_limits<double>::max();
+ const auto expected_highest = 1.797693134862315708e+308;
+ if (highest < expected_highest || highest > expected_highest) {
+ GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
+ "this target";
+ }
+ EXPECT_EQ(DoubleToBitPreservingString(std::numeric_limits<double>::max()),
+ "179769313486231570814527423731704356798070567525844996598917476803157260780028538760"
+ "589558632766878171540458953514382464234321326889464182768467546703537516986049910576"
+ "551282076245490090389328944075868508455133942304583236903222948165808559332123348274"
+ "797826204144723168738177180919299881250404026184124858368.0");
+}
+
+TEST(DoubleToBitPreservingStringTest, Lowest) {
+ // Some compilers complain if you test floating point numbers for equality.
+ // So say it via two inequalities.
+ const auto lowest = std::numeric_limits<double>::lowest();
+ const auto expected_lowest = -1.797693134862315708e+308;
+ if (lowest < expected_lowest || lowest > expected_lowest) {
+ GTEST_SKIP() << "std::numeric_limits<float>::lowest() is not as expected for "
+ "this target";
+ }
+ EXPECT_EQ(DoubleToBitPreservingString(std::numeric_limits<double>::lowest()),
+ "-17976931348623157081452742373170435679807056752584499659891747680315726078002853876"
+ "058955863276687817154045895351438246423432132688946418276846754670353751698604991057"
+ "655128207624549009038932894407586850845513394230458323690322294816580855933212334827"
+ "4797826204144723168738177180919299881250404026184124858368.0");
+}
+
+TEST(DoubleToBitPreservingStringTest, SmallestDenormal) {
+ EXPECT_EQ(DoubleToBitPreservingString(0x1p-1074), "0x1p-1074");
+ EXPECT_EQ(DoubleToBitPreservingString(-0x1p-1074), "-0x1p-1074");
+}
+
+TEST(DoubleToBitPreservingStringTest, BiggerDenormal) {
+ EXPECT_EQ(DoubleToBitPreservingString(0x1p-1073), "0x1p-1073");
+ EXPECT_EQ(DoubleToBitPreservingString(-0x1p-1073), "-0x1p-1073");
+}
+
+TEST(DoubleToBitPreservingStringTest, LargestDenormal) {
+ static_assert(0x0.fffffffffffffp-1022 == 0x1.ffffffffffffep-1023);
+ EXPECT_EQ(DoubleToBitPreservingString(0x0.fffffffffffffp-1022), "0x1.ffffffffffffep-1023");
+ EXPECT_EQ(DoubleToBitPreservingString(-0x0.fffffffffffffp-1022), "-0x1.ffffffffffffep-1023");
+}
+
+TEST(DoubleToBitPreservingStringTest, Subnormal_cafef00dbeef) {
+ EXPECT_EQ(DoubleToBitPreservingString(0x1.cafef00dbeefp-1023), "0x1.cafef00dbeefp-1023");
+ EXPECT_EQ(DoubleToBitPreservingString(-0x1.cafef00dbeefp-1023), "-0x1.cafef00dbeefp-1023");
+}
+
+TEST(DoubleToBitPreservingStringTest, Subnormal_aaaaaaaaaaaaap) {
+ static_assert(0x0.aaaaaaaaaaaaap-1023 == 0x1.5555555555554p-1024);
+ EXPECT_EQ(DoubleToBitPreservingString(0x0.aaaaaaaaaaaaap-1023), "0x1.5555555555554p-1024");
+ EXPECT_EQ(DoubleToBitPreservingString(-0x0.aaaaaaaaaaaaap-1023), "-0x1.5555555555554p-1024");
+}
+
+TEST(DoubleToBitPreservingStringTest, Infinity) {
+ EXPECT_EQ(DoubleToBitPreservingString(static_cast<double>(INFINITY)), "0x1p+1024");
+ EXPECT_EQ(DoubleToBitPreservingString(static_cast<double>(-INFINITY)), "-0x1p+1024");
+}
+
+TEST(DoubleToBitPreservingStringTest, NaN) {
+ auto nan = utils::Bitcast<double>(0x7ff8cafef00dbeefull);
+ EXPECT_EQ(DoubleToBitPreservingString(static_cast<double>(nan)), "0x1.8cafef00dbeefp+1024");
+ EXPECT_EQ(DoubleToBitPreservingString(static_cast<double>(-nan)), "-0x1.8cafef00dbeefp+1024");
+}
+
+} // namespace
+} // namespace tint::writer
diff --git a/src/tint/utils/text/parse_num.cc b/src/tint/utils/text/parse_num.cc
new file mode 100644
index 0000000..c2a0eea
--- /dev/null
+++ b/src/tint/utils/text/parse_num.cc
@@ -0,0 +1,98 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/parse_num.h"
+
+#include <charconv>
+
+#include "absl/strings/charconv.h"
+
+namespace tint::utils {
+
+namespace {
+
+template <typename T>
+Result<T, ParseNumberError> Parse(std::string_view number) {
+ T val = 0;
+ if constexpr (std::is_floating_point_v<T>) {
+ auto result = absl::from_chars(number.data(), number.data() + number.size(), val);
+ if (result.ec == std::errc::result_out_of_range) {
+ return ParseNumberError::kResultOutOfRange;
+ }
+ if (result.ec != std::errc() || result.ptr != number.data() + number.size()) {
+ return ParseNumberError::kUnparsable;
+ }
+ } else {
+ auto result = std::from_chars(number.data(), number.data() + number.size(), val);
+ if (result.ec == std::errc::result_out_of_range) {
+ return ParseNumberError::kResultOutOfRange;
+ }
+ if (result.ec != std::errc() || result.ptr != number.data() + number.size()) {
+ return ParseNumberError::kUnparsable;
+ }
+ }
+ return val;
+}
+
+} // namespace
+
+Result<float, ParseNumberError> ParseFloat(std::string_view str) {
+ return Parse<float>(str);
+}
+
+Result<double, ParseNumberError> ParseDouble(std::string_view str) {
+ return Parse<double>(str);
+}
+
+Result<int, ParseNumberError> ParseInt(std::string_view str) {
+ return Parse<int>(str);
+}
+
+Result<unsigned int, ParseNumberError> ParseUint(std::string_view str) {
+ return Parse<unsigned int>(str);
+}
+
+Result<int64_t, ParseNumberError> ParseInt64(std::string_view str) {
+ return Parse<int64_t>(str);
+}
+
+Result<uint64_t, ParseNumberError> ParseUint64(std::string_view str) {
+ return Parse<uint64_t>(str);
+}
+
+Result<int32_t, ParseNumberError> ParseInt32(std::string_view str) {
+ return Parse<int32_t>(str);
+}
+
+Result<uint32_t, ParseNumberError> ParseUint32(std::string_view str) {
+ return Parse<uint32_t>(str);
+}
+
+Result<int16_t, ParseNumberError> ParseInt16(std::string_view str) {
+ return Parse<int16_t>(str);
+}
+
+Result<uint16_t, ParseNumberError> ParseUint16(std::string_view str) {
+ return Parse<uint16_t>(str);
+}
+
+Result<int8_t, ParseNumberError> ParseInt8(std::string_view str) {
+ return Parse<int8_t>(str);
+}
+
+Result<uint8_t, ParseNumberError> ParseUint8(std::string_view str) {
+ return Parse<uint8_t>(str);
+}
+
+} // namespace tint::utils
diff --git a/src/tint/utils/text/parse_num.h b/src/tint/utils/text/parse_num.h
new file mode 100644
index 0000000..936fd0f
--- /dev/null
+++ b/src/tint/utils/text/parse_num.h
@@ -0,0 +1,131 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TEXT_PARSE_NUM_H_
+#define SRC_TINT_UTILS_TEXT_PARSE_NUM_H_
+
+#include <optional>
+#include <string>
+
+#include "src/tint/utils/macros/compiler.h"
+#include "src/tint/utils/result/result.h"
+
+namespace tint::utils {
+
+/// Error returned by the number parsing functions
+enum class ParseNumberError {
+ /// The number was unparsable
+ kUnparsable,
+ /// The parsed number is not representable by the target datatype
+ kResultOutOfRange,
+};
+
+/// @param str the string
+/// @returns the string @p str parsed as a float
+Result<float, ParseNumberError> ParseFloat(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a double
+Result<double, ParseNumberError> ParseDouble(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a int
+Result<int, ParseNumberError> ParseInt(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a unsigned int
+Result<unsigned int, ParseNumberError> ParseUint(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a int64_t
+Result<int64_t, ParseNumberError> ParseInt64(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a uint64_t
+Result<uint64_t, ParseNumberError> ParseUint64(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a int32_t
+Result<int32_t, ParseNumberError> ParseInt32(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a uint32_t
+Result<uint32_t, ParseNumberError> ParseUint32(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a int16_t
+Result<int16_t, ParseNumberError> ParseInt16(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a uint16_t
+Result<uint16_t, ParseNumberError> ParseUint16(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a int8_t
+Result<int8_t, ParseNumberError> ParseInt8(std::string_view str);
+
+/// @param str the string
+/// @returns the string @p str parsed as a uint8_t
+Result<uint8_t, ParseNumberError> ParseUint8(std::string_view str);
+
+TINT_BEGIN_DISABLE_WARNING(UNREACHABLE_CODE);
+
+/// @param str the string
+/// @returns the string @p str parsed as a the number @p T
+template <typename T>
+inline Result<T, ParseNumberError> ParseNumber(std::string_view str) {
+ if constexpr (std::is_same_v<T, float>) {
+ return ParseFloat(str);
+ }
+ if constexpr (std::is_same_v<T, double>) {
+ return ParseDouble(str);
+ }
+ if constexpr (std::is_same_v<T, int>) {
+ return ParseInt(str);
+ }
+ if constexpr (std::is_same_v<T, unsigned int>) {
+ return ParseUint(str);
+ }
+ if constexpr (std::is_same_v<T, int64_t>) {
+ return ParseInt64(str);
+ }
+ if constexpr (std::is_same_v<T, uint64_t>) {
+ return ParseUint64(str);
+ }
+ if constexpr (std::is_same_v<T, int32_t>) {
+ return ParseInt32(str);
+ }
+ if constexpr (std::is_same_v<T, uint32_t>) {
+ return ParseUint32(str);
+ }
+ if constexpr (std::is_same_v<T, int16_t>) {
+ return ParseInt16(str);
+ }
+ if constexpr (std::is_same_v<T, uint16_t>) {
+ return ParseUint16(str);
+ }
+ if constexpr (std::is_same_v<T, int8_t>) {
+ return ParseInt8(str);
+ }
+ if constexpr (std::is_same_v<T, uint8_t>) {
+ return ParseUint8(str);
+ }
+ return ParseNumberError::kUnparsable;
+}
+
+TINT_END_DISABLE_WARNING(UNREACHABLE_CODE);
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_TEXT_PARSE_NUM_H_
diff --git a/src/tint/utils/text/string.cc b/src/tint/utils/text/string.cc
new file mode 100644
index 0000000..91644f8
--- /dev/null
+++ b/src/tint/utils/text/string.cc
@@ -0,0 +1,98 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <algorithm>
+
+#include "src/tint/utils/containers/transform.h"
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/text/string.h"
+
+namespace tint::utils {
+
+size_t Distance(std::string_view str_a, std::string_view str_b) {
+ const auto len_a = str_a.size();
+ const auto len_b = str_b.size();
+
+ Vector<size_t, 64> mat;
+ mat.Resize((len_a + 1) * (len_b + 1));
+
+ auto at = [&](size_t a, size_t b) -> size_t& { return mat[a + b * (len_a + 1)]; };
+
+ at(0, 0) = 0;
+ for (size_t a = 1; a <= len_a; a++) {
+ at(a, 0) = a;
+ }
+ for (size_t b = 1; b <= len_b; b++) {
+ at(0, b) = b;
+ }
+ for (size_t b = 1; b <= len_b; b++) {
+ for (size_t a = 1; a <= len_a; a++) {
+ bool eq = str_a[a - 1] == str_b[b - 1];
+ at(a, b) = std::min({
+ at(a - 1, b) + 1,
+ at(a, b - 1) + 1,
+ at(a - 1, b - 1) + (eq ? 0 : 1),
+ });
+ }
+ }
+ return at(len_a, len_b);
+}
+
+void SuggestAlternatives(std::string_view got,
+ Slice<char const* const> strings,
+ utils::StringStream& ss,
+ const SuggestAlternativeOptions& options /* = {} */) {
+ auto views = Transform<8>(strings, [](char const* const str) { return std::string_view(str); });
+ SuggestAlternatives(got, views.Slice(), ss, options);
+}
+
+void SuggestAlternatives(std::string_view got,
+ Slice<std::string_view> strings,
+ utils::StringStream& ss,
+ const SuggestAlternativeOptions& options /* = {} */) {
+ // If the string typed was within kSuggestionDistance of one of the possible enum values,
+ // suggest that. Don't bother with suggestions if the string was extremely long.
+ constexpr size_t kSuggestionDistance = 5;
+ constexpr size_t kSuggestionMaxLength = 64;
+ if (!got.empty() && got.size() < kSuggestionMaxLength) {
+ size_t candidate_dist = kSuggestionDistance;
+ std::string_view candidate;
+ for (auto str : strings) {
+ auto dist = utils::Distance(str, got);
+ if (dist < candidate_dist) {
+ candidate = str;
+ candidate_dist = dist;
+ }
+ }
+ if (!candidate.empty()) {
+ ss << "Did you mean '" << options.prefix << candidate << "'?";
+ if (options.list_possible_values) {
+ ss << "\n";
+ }
+ }
+ }
+
+ if (options.list_possible_values) {
+ // List all the possible enumerator values
+ ss << "Possible values: ";
+ for (auto str : strings) {
+ if (str != strings[0]) {
+ ss << ", ";
+ }
+ ss << "'" << options.prefix << str << "'";
+ }
+ }
+}
+
+} // namespace tint::utils
diff --git a/src/tint/utils/text/string.h b/src/tint/utils/text/string.h
new file mode 100644
index 0000000..96df884
--- /dev/null
+++ b/src/tint/utils/text/string.h
@@ -0,0 +1,225 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TEXT_STRING_H_
+#define SRC_TINT_UTILS_TEXT_STRING_H_
+
+#include <string>
+#include <variant>
+
+#include "src/tint/utils/containers/slice.h"
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::utils {
+
+/// @param str the string to apply replacements to
+/// @param substr the string to search for
+/// @param replacement the replacement string to use instead of `substr`
+/// @returns `str` with all occurrences of `substr` replaced with `replacement`
+[[nodiscard]] inline std::string ReplaceAll(std::string str,
+ std::string_view substr,
+ std::string_view replacement) {
+ size_t pos = 0;
+ while ((pos = str.find(substr, pos)) != std::string_view::npos) {
+ str.replace(pos, substr.length(), replacement);
+ pos += replacement.length();
+ }
+ return str;
+}
+
+/// @param value the boolean value to be printed as a string
+/// @returns value printed as a string via the stream `<<` operator
+inline std::string ToString(bool value) {
+ return value ? "true" : "false";
+}
+
+/// @param value the value to be printed as a string
+/// @returns value printed as a string via the stream `<<` operator
+template <typename T>
+std::string ToString(const T& value) {
+ utils::StringStream s;
+ s << value;
+ return s.str();
+}
+
+/// @param value the variant to be printed as a string
+/// @returns value printed as a string via the stream `<<` operator
+template <typename... TYs>
+std::string ToString(const std::variant<TYs...>& value) {
+ utils::StringStream s;
+ s << std::visit([&](auto& v) { return ToString(v); }, value);
+ return s.str();
+}
+
+/// @param str the input string
+/// @param prefix the prefix string
+/// @returns true iff @p str has the prefix @p prefix
+inline size_t HasPrefix(std::string_view str, std::string_view prefix) {
+ return str.length() >= prefix.length() && str.substr(0, prefix.length()) == prefix;
+}
+
+/// @param str the input string
+/// @param suffix the suffix string
+/// @returns true iff @p str has the suffix @p suffix
+inline size_t HasSuffix(std::string_view str, std::string_view suffix) {
+ return str.length() >= suffix.length() && str.substr(str.length() - suffix.length()) == suffix;
+}
+
+/// @param a the first string
+/// @param b the second string
+/// @returns the Levenshtein distance between @p a and @p b
+size_t Distance(std::string_view a, std::string_view b);
+
+/// Options for SuggestAlternatives()
+struct SuggestAlternativeOptions {
+ /// The prefix to apply to the strings when printing
+ std::string_view prefix;
+ /// List all the possible values
+ bool list_possible_values = true;
+};
+
+/// Suggest alternatives for an unrecognized string from a list of possible values.
+/// @param got the unrecognized string
+/// @param strings the list of possible values
+/// @param ss the stream to write the suggest and list of possible values to
+/// @param options options for the suggestion
+void SuggestAlternatives(std::string_view got,
+ Slice<char const* const> strings,
+ utils::StringStream& ss,
+ const SuggestAlternativeOptions& options = {});
+
+/// Suggest alternatives for an unrecognized string from a list of possible values.
+/// @param got the unrecognized string
+/// @param strings the list of possible values
+/// @param ss the stream to write the suggest and list of possible values to
+/// @param options options for the suggestion
+void SuggestAlternatives(std::string_view got,
+ Slice<std::string_view> strings,
+ utils::StringStream& ss,
+ const SuggestAlternativeOptions& options = {});
+
+/// @param str the input string
+/// @param pred the predicate function
+/// @return @p str with characters passing the predicate function @p pred removed from the start of
+/// the string.
+template <typename PREDICATE>
+std::string_view TrimLeft(std::string_view str, PREDICATE&& pred) {
+ while (!str.empty() && pred(str.front())) {
+ str = str.substr(1);
+ }
+ return str;
+}
+
+/// @param str the input string
+/// @param pred the predicate function
+/// @return @p str with characters passing the predicate function @p pred removed from the end of
+/// the string.
+template <typename PREDICATE>
+std::string_view TrimRight(std::string_view str, PREDICATE&& pred) {
+ while (!str.empty() && pred(str.back())) {
+ str = str.substr(0, str.length() - 1);
+ }
+ return str;
+}
+
+/// @param str the input string
+/// @param prefix the prefix to trim from @p str
+/// @return @p str with the prefix removed, if @p str has the prefix.
+inline std::string_view TrimPrefix(std::string_view str, std::string_view prefix) {
+ return HasPrefix(str, prefix) ? str.substr(prefix.length()) : str;
+}
+
+/// @param str the input string
+/// @param suffix the suffix to trim from @p str
+/// @return @p str with the suffix removed, if @p str has the suffix.
+inline std::string_view TrimSuffix(std::string_view str, std::string_view suffix) {
+ return HasSuffix(str, suffix) ? str.substr(0, str.length() - suffix.length()) : str;
+}
+
+/// @param str the input string
+/// @param pred the predicate function
+/// @return @p str with characters passing the predicate function @p pred removed from the start and
+/// end of the string.
+template <typename PREDICATE>
+std::string_view Trim(std::string_view str, PREDICATE&& pred) {
+ return TrimLeft(TrimRight(str, pred), pred);
+}
+
+/// @param c the character to test
+/// @returns true if @p c is one of the following:
+/// * space (' ')
+/// * form feed ('\f')
+/// * line feed ('\n')
+/// * carriage return ('\r')
+/// * horizontal tab ('\t')
+/// * vertical tab ('\v')
+inline bool IsSpace(char c) {
+ return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v';
+}
+
+/// @param str the input string
+/// @return @p str with all whitespace (' ') removed from the start and end of the string.
+inline std::string_view TrimSpace(std::string_view str) {
+ return Trim(str, IsSpace);
+}
+
+/// @param str the input string
+/// @param delimiter the delimiter
+/// @return @p str split at each occurrence of @p delimiter
+inline utils::Vector<std::string_view, 8> Split(std::string_view str, std::string_view delimiter) {
+ utils::Vector<std::string_view, 8> out;
+ while (str.length() > delimiter.length()) {
+ auto pos = str.find(delimiter);
+ if (pos == std::string_view::npos) {
+ break;
+ }
+ out.Push(str.substr(0, pos));
+ str = str.substr(pos + delimiter.length());
+ }
+ out.Push(str);
+ return out;
+}
+
+/// @returns @p str quoted with <code>'</code>
+inline std::string Quote(std::string_view str) {
+ return "'" + std::string(str) + "'";
+}
+
+/// @param parts the input parts
+/// @param delimiter the delimiter
+/// @return @p parts joined as a string, delimited with @p delimiter
+template <typename T>
+inline std::string Join(utils::VectorRef<T> parts, std::string_view delimiter) {
+ utils::StringStream s;
+ for (auto& part : parts) {
+ if (part != parts.Front()) {
+ s << delimiter;
+ }
+ s << part;
+ }
+ return s.str();
+}
+
+/// @param parts the input parts
+/// @param delimiter the delimiter
+/// @return @p parts joined as a string, delimited with @p delimiter
+template <typename T, size_t N>
+inline std::string Join(const utils::Vector<T, N>& parts, std::string_view delimiter) {
+ return Join(utils::VectorRef<T>(parts), delimiter);
+}
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_TEXT_STRING_H_
diff --git a/src/tint/utils/text/string_stream.cc b/src/tint/utils/text/string_stream.cc
new file mode 100644
index 0000000..ca84686
--- /dev/null
+++ b/src/tint/utils/text/string_stream.cc
@@ -0,0 +1,51 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::utils {
+
+StringStream::StringStream() {
+ sstream_.flags(sstream_.flags() | std::ios_base::showpoint | std::ios_base::fixed);
+ sstream_.imbue(std::locale::classic());
+ sstream_.precision(9);
+}
+
+StringStream::~StringStream() = default;
+
+utils::StringStream& operator<<(utils::StringStream& out, CodePoint code_point) {
+ if (code_point < 0x7f) {
+ // See https://en.cppreference.com/w/cpp/language/escape
+ switch (code_point) {
+ case '\a':
+ return out << R"('\a')";
+ case '\b':
+ return out << R"('\b')";
+ case '\f':
+ return out << R"('\f')";
+ case '\n':
+ return out << R"('\n')";
+ case '\r':
+ return out << R"('\r')";
+ case '\t':
+ return out << R"('\t')";
+ case '\v':
+ return out << R"('\v')";
+ }
+ return out << "'" << static_cast<char>(code_point) << "'";
+ }
+ return out << "'U+" << std::hex << code_point.value << "'";
+}
+
+} // namespace tint::utils
diff --git a/src/tint/utils/text/string_stream.h b/src/tint/utils/text/string_stream.h
new file mode 100644
index 0000000..3d65972
--- /dev/null
+++ b/src/tint/utils/text/string_stream.h
@@ -0,0 +1,197 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TEXT_STRING_STREAM_H_
+#define SRC_TINT_UTILS_TEXT_STRING_STREAM_H_
+
+#include <functional>
+#include <iomanip>
+#include <iterator>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "src/tint/utils/text/unicode.h"
+
+namespace tint::utils {
+
+/// Stringstream wrapper which automatically resets the locale and sets floating point emission
+/// settings needed for Tint.
+class StringStream {
+ using SetWRetTy = decltype(std::setw(std::declval<int>()));
+ using SetPrecisionRetTy = decltype(std::setprecision(std::declval<int>()));
+ using SetFillRetTy = decltype(std::setfill(std::declval<char>()));
+
+ /// Evaluates to true if `T` is the return type of std::setw, std:setprecision or std::setfill.
+ template <typename T>
+ static constexpr bool IsSetType = std::is_same_v<SetWRetTy, std::decay_t<T>> ||
+ std::is_same_v<SetPrecisionRetTy, std::decay_t<T>> ||
+ std::is_same_v<SetFillRetTy, std::decay_t<T>>;
+
+ public:
+ /// Constructor
+ StringStream();
+ /// Destructor
+ ~StringStream();
+
+ /// @returns the format flags for the stream
+ std::ios_base::fmtflags flags() const { return sstream_.flags(); }
+
+ /// @param flags the flags to set
+ /// @returns the original format flags
+ std::ios_base::fmtflags flags(std::ios_base::fmtflags flags) { return sstream_.flags(flags); }
+
+ /// Emit `value` to the stream
+ /// @param value the value to emit
+ /// @returns a reference to this
+ template <typename T,
+ typename std::enable_if_t<std::is_integral_v<std::decay_t<T>>, bool> = true>
+ StringStream& operator<<(T&& value) {
+ return EmitValue(std::forward<T>(value));
+ }
+
+ /// Emit `value` to the stream
+ /// @param value the value to emit
+ /// @returns a reference to this
+ StringStream& operator<<(const char* value) { return EmitValue(value); }
+ /// Emit `value` to the stream
+ /// @param value the value to emit
+ /// @returns a reference to this
+ StringStream& operator<<(const std::string& value) { return EmitValue(value); }
+ /// Emit `value` to the stream
+ /// @param value the value to emit
+ /// @returns a reference to this
+ StringStream& operator<<(std::string_view value) { return EmitValue(value); }
+
+ /// Emit `value` to the stream
+ /// @param value the value to emit
+ /// @returns a reference to this
+ StringStream& operator<<(const void* value) { return EmitValue(value); }
+
+ /// Emit `value` to the stream
+ /// @param value the value to emit
+ /// @returns a reference to this
+ template <typename T,
+ typename std::enable_if_t<std::is_floating_point_v<std::decay_t<T>>, bool> = true>
+ StringStream& operator<<(T&& value) {
+ return EmitFloat(std::forward<T>(value));
+ }
+
+ /// Emit `value` to the stream
+ /// @param value the value to emit
+ /// @returns a reference to this
+ template <typename T>
+ StringStream& EmitValue(T&& value) {
+ sstream_ << std::forward<T>(value);
+ return *this;
+ }
+
+ /// Emit `value` to the stream
+ /// @param value the value to emit
+ /// @returns a reference to this
+ template <typename T>
+ StringStream& EmitFloat(const T& value) {
+ // Try printing the float in fixed point, with a smallish limit on the precision
+ std::stringstream fixed;
+ fixed.flags(fixed.flags() | std::ios_base::showpoint | std::ios_base::fixed);
+ fixed.imbue(std::locale::classic());
+ fixed.precision(20);
+ fixed << value;
+
+ std::string str = fixed.str();
+
+ // If this string can be parsed without loss of information, use it.
+ // (Use double here to dodge a bug in older libc++ versions which would incorrectly read
+ // back FLT_MAX as INF.)
+ double roundtripped;
+ fixed >> roundtripped;
+
+ // Strip trailing zeros from the number.
+ auto float_equal_no_warning = std::equal_to<T>();
+ if (float_equal_no_warning(value, static_cast<T>(roundtripped))) {
+ while (str.length() >= 2 && str[str.size() - 1] == '0' && str[str.size() - 2] != '.') {
+ str.pop_back();
+ }
+
+ sstream_ << str;
+ return *this;
+ }
+
+ // Resort to scientific, with the minimum precision needed to preserve the whole float
+ std::stringstream sci;
+ sci.imbue(std::locale::classic());
+ sci.precision(std::numeric_limits<T>::max_digits10);
+ sci << value;
+ sstream_ << sci.str();
+
+ return *this;
+ }
+
+ /// Swaps streams
+ /// @param other stream to swap too
+ void swap(StringStream& other) { sstream_.swap(other.sstream_); }
+
+ /// repeat queues the character c to be written to the printer n times.
+ /// @param c the character to print `n` times
+ /// @param n the number of times to print character `c`
+ void repeat(char c, size_t n) { std::fill_n(std::ostream_iterator<char>(sstream_), n, c); }
+
+ /// The callback to emit a `endl` to the stream
+ using StdEndl = std::ostream& (*)(std::ostream&);
+
+ /// @param manipulator the callback to emit too
+ /// @returns a reference to this
+ StringStream& operator<<(StdEndl manipulator) {
+ // call the function, and return it's value
+ manipulator(sstream_);
+ return *this;
+ }
+
+ /// @param manipulator the callback to emit too
+ /// @returns a reference to this
+ StringStream& operator<<(decltype(std::hex) manipulator) {
+ // call the function, and return it's value
+ manipulator(sstream_);
+ return *this;
+ }
+
+ /// @param value the value to emit
+ /// @returns a reference to this
+ template <typename T, typename std::enable_if_t<IsSetType<T>, int> = 0>
+ StringStream& operator<<(T&& value) {
+ // call the function, and return it's value
+ sstream_ << std::forward<T>(value);
+ return *this;
+ }
+
+ /// @returns the current location in the output stream
+ uint32_t tellp() { return static_cast<uint32_t>(sstream_.tellp()); }
+
+ /// @returns the string contents of the stream
+ std::string str() const { return sstream_.str(); }
+
+ private:
+ std::stringstream sstream_;
+};
+
+/// Writes the CodePoint to the stream.
+/// @param out the stream to write to
+/// @param codepoint the CodePoint to write
+/// @returns out so calls can be chained
+utils::StringStream& operator<<(utils::StringStream& out, CodePoint codepoint);
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_TEXT_STRING_STREAM_H_
diff --git a/src/tint/utils/text/string_stream_test.cc b/src/tint/utils/text/string_stream_test.cc
new file mode 100644
index 0000000..b18ebb2
--- /dev/null
+++ b/src/tint/utils/text/string_stream_test.cc
@@ -0,0 +1,111 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/string_stream.h"
+
+#include <math.h>
+#include <cstring>
+#include <limits>
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+using StringStreamTest = testing::Test;
+
+TEST_F(StringStreamTest, Zero) {
+ StringStream s;
+ s << 0.0f;
+ EXPECT_EQ(s.str(), "0.0");
+}
+
+TEST_F(StringStreamTest, One) {
+ StringStream s;
+ s << 1.0f;
+ EXPECT_EQ(s.str(), "1.0");
+}
+
+TEST_F(StringStreamTest, MinusOne) {
+ StringStream s;
+ s << -1.0f;
+ EXPECT_EQ(s.str(), "-1.0");
+}
+
+TEST_F(StringStreamTest, Billion) {
+ StringStream s;
+ s << 1e9f;
+ EXPECT_EQ(s.str(), "1000000000.0");
+}
+
+TEST_F(StringStreamTest, Small) {
+ StringStream s;
+ s << std::numeric_limits<float>::epsilon();
+ EXPECT_NE(s.str(), "0.0");
+}
+
+TEST_F(StringStreamTest, Highest) {
+ const auto highest = std::numeric_limits<float>::max();
+ const auto expected_highest = 340282346638528859811704183484516925440.0f;
+
+ if (highest < expected_highest || highest > expected_highest) {
+ GTEST_SKIP() << "std::numeric_limits<float>::max() is not as expected for "
+ "this target";
+ }
+
+ StringStream s;
+ s << std::numeric_limits<float>::max();
+ EXPECT_EQ(s.str(), "340282346638528859811704183484516925440.0");
+}
+
+TEST_F(StringStreamTest, Lowest) {
+ // Some compilers complain if you test floating point numbers for equality.
+ // So say it via two inequalities.
+ const auto lowest = std::numeric_limits<float>::lowest();
+ const auto expected_lowest = -340282346638528859811704183484516925440.0f;
+ if (lowest < expected_lowest || lowest > expected_lowest) {
+ GTEST_SKIP() << "std::numeric_limits<float>::lowest() is not as expected for "
+ "this target";
+ }
+
+ StringStream s;
+ s << std::numeric_limits<float>::lowest();
+ EXPECT_EQ(s.str(), "-340282346638528859811704183484516925440.0");
+}
+
+TEST_F(StringStreamTest, Precision) {
+ {
+ StringStream s;
+ s << 1e-8f;
+ EXPECT_EQ(s.str(), "0.00000000999999993923");
+ }
+ {
+ StringStream s;
+ s << 1e-9f;
+ EXPECT_EQ(s.str(), "0.00000000099999997172");
+ }
+ {
+ StringStream s;
+ s << 1e-10f;
+ EXPECT_EQ(s.str(), "0.00000000010000000134");
+ }
+ {
+ StringStream s;
+ s << 1e-20f;
+ EXPECT_EQ(s.str(), "0.00000000000000000001");
+ }
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/text/string_test.cc b/src/tint/utils/text/string_test.cc
new file mode 100644
index 0000000..1caa879
--- /dev/null
+++ b/src/tint/utils/text/string_test.cc
@@ -0,0 +1,206 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/string.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/utils/text/string_stream.h"
+
+#include "src/tint/utils/containers/transform.h" // Used by ToStringList()
+
+namespace tint::utils {
+namespace {
+
+// Workaround for https://github.com/google/googletest/issues/3081
+// Remove when using C++20
+template <size_t N>
+utils::Vector<std::string, N> ToStringList(const utils::Vector<std::string_view, N>& views) {
+ return Transform(views, [](std::string_view view) { return std::string(view); });
+}
+
+TEST(StringTest, ReplaceAll) {
+ EXPECT_EQ("xybbcc", ReplaceAll("aabbcc", "aa", "xy"));
+ EXPECT_EQ("aaxycc", ReplaceAll("aabbcc", "bb", "xy"));
+ EXPECT_EQ("aabbxy", ReplaceAll("aabbcc", "cc", "xy"));
+ EXPECT_EQ("xyxybbcc", ReplaceAll("aabbcc", "a", "xy"));
+ EXPECT_EQ("aaxyxycc", ReplaceAll("aabbcc", "b", "xy"));
+ EXPECT_EQ("aabbxyxy", ReplaceAll("aabbcc", "c", "xy"));
+ // Replacement string includes the searched-for string.
+ // This proves that the algorithm needs to advance 'pos'
+ // past the replacement.
+ EXPECT_EQ("aabxybbxybcc", ReplaceAll("aabbcc", "b", "bxyb"));
+}
+
+TEST(StringTest, ToString) {
+ EXPECT_EQ("true", ToString(true));
+ EXPECT_EQ("false", ToString(false));
+ EXPECT_EQ("123", ToString(123));
+ EXPECT_EQ("hello", ToString("hello"));
+}
+
+TEST(StringTest, HasPrefix) {
+ EXPECT_TRUE(HasPrefix("abc", "a"));
+ EXPECT_TRUE(HasPrefix("abc", "ab"));
+ EXPECT_TRUE(HasPrefix("abc", "abc"));
+ EXPECT_FALSE(HasPrefix("abc", "abc1"));
+ EXPECT_FALSE(HasPrefix("abc", "ac"));
+ EXPECT_FALSE(HasPrefix("abc", "b"));
+}
+
+TEST(StringTest, HasSuffix) {
+ EXPECT_TRUE(HasSuffix("abc", "c"));
+ EXPECT_TRUE(HasSuffix("abc", "bc"));
+ EXPECT_TRUE(HasSuffix("abc", "abc"));
+ EXPECT_FALSE(HasSuffix("abc", "1abc"));
+ EXPECT_FALSE(HasSuffix("abc", "ac"));
+ EXPECT_FALSE(HasSuffix("abc", "b"));
+}
+
+TEST(StringTest, Distance) {
+ EXPECT_EQ(Distance("hello world", "hello world"), 0u);
+ EXPECT_EQ(Distance("hello world", "helloworld"), 1u);
+ EXPECT_EQ(Distance("helloworld", "hello world"), 1u);
+ EXPECT_EQ(Distance("hello world", "hello world"), 1u);
+ EXPECT_EQ(Distance("hello world", "hello world"), 1u);
+ EXPECT_EQ(Distance("Hello World", "hello world"), 2u);
+ EXPECT_EQ(Distance("hello world", "Hello World"), 2u);
+ EXPECT_EQ(Distance("Hello world", ""), 11u);
+ EXPECT_EQ(Distance("", "Hello world"), 11u);
+}
+
+TEST(StringTest, SuggestAlternatives) {
+ {
+ const char* alternatives[] = {"hello world", "Hello World"};
+ utils::StringStream ss;
+ SuggestAlternatives("hello wordl", alternatives, ss);
+ EXPECT_EQ(ss.str(), R"(Did you mean 'hello world'?
+Possible values: 'hello world', 'Hello World')");
+ }
+ {
+ const char* alternatives[] = {"foobar", "something else"};
+ utils::StringStream ss;
+ SuggestAlternatives("hello world", alternatives, ss);
+ EXPECT_EQ(ss.str(), R"(Possible values: 'foobar', 'something else')");
+ }
+ {
+ const char* alternatives[] = {"hello world", "Hello World"};
+ utils::StringStream ss;
+ SuggestAlternativeOptions opts;
+ opts.prefix = "$";
+ SuggestAlternatives("hello wordl", alternatives, ss, opts);
+ EXPECT_EQ(ss.str(), R"(Did you mean '$hello world'?
+Possible values: '$hello world', '$Hello World')");
+ }
+ {
+ const char* alternatives[] = {"hello world", "Hello World"};
+ utils::StringStream ss;
+ SuggestAlternativeOptions opts;
+ opts.list_possible_values = false;
+ SuggestAlternatives("hello world", alternatives, ss, opts);
+ EXPECT_EQ(ss.str(), R"(Did you mean 'hello world'?)");
+ }
+}
+
+TEST(StringTest, TrimLeft) {
+ EXPECT_EQ(TrimLeft("hello world", [](char) { return false; }), "hello world");
+ EXPECT_EQ(TrimLeft("hello world", [](char c) { return c == 'h'; }), "ello world");
+ EXPECT_EQ(TrimLeft("hello world", [](char c) { return c == 'h' || c == 'e'; }), "llo world");
+ EXPECT_EQ(TrimLeft("hello world", [](char c) { return c == 'e'; }), "hello world");
+ EXPECT_EQ(TrimLeft("hello world", [](char) { return true; }), "");
+ EXPECT_EQ(TrimLeft("", [](char) { return false; }), "");
+ EXPECT_EQ(TrimLeft("", [](char) { return true; }), "");
+}
+
+TEST(StringTest, TrimRight) {
+ EXPECT_EQ(TrimRight("hello world", [](char) { return false; }), "hello world");
+ EXPECT_EQ(TrimRight("hello world", [](char c) { return c == 'd'; }), "hello worl");
+ EXPECT_EQ(TrimRight("hello world", [](char c) { return c == 'd' || c == 'l'; }), "hello wor");
+ EXPECT_EQ(TrimRight("hello world", [](char c) { return c == 'l'; }), "hello world");
+ EXPECT_EQ(TrimRight("hello world", [](char) { return true; }), "");
+ EXPECT_EQ(TrimRight("", [](char) { return false; }), "");
+ EXPECT_EQ(TrimRight("", [](char) { return true; }), "");
+}
+
+TEST(StringTest, TrimPrefix) {
+ EXPECT_EQ(TrimPrefix("abc", "a"), "bc");
+ EXPECT_EQ(TrimPrefix("abc", "ab"), "c");
+ EXPECT_EQ(TrimPrefix("abc", "abc"), "");
+ EXPECT_EQ(TrimPrefix("abc", "abc1"), "abc");
+ EXPECT_EQ(TrimPrefix("abc", "ac"), "abc");
+ EXPECT_EQ(TrimPrefix("abc", "b"), "abc");
+ EXPECT_EQ(TrimPrefix("abc", "c"), "abc");
+}
+
+TEST(StringTest, TrimSuffix) {
+ EXPECT_EQ(TrimSuffix("abc", "c"), "ab");
+ EXPECT_EQ(TrimSuffix("abc", "bc"), "a");
+ EXPECT_EQ(TrimSuffix("abc", "abc"), "");
+ EXPECT_EQ(TrimSuffix("abc", "1abc"), "abc");
+ EXPECT_EQ(TrimSuffix("abc", "ac"), "abc");
+ EXPECT_EQ(TrimSuffix("abc", "b"), "abc");
+ EXPECT_EQ(TrimSuffix("abc", "a"), "abc");
+}
+
+TEST(StringTest, Trim) {
+ EXPECT_EQ(Trim("hello world", [](char) { return false; }), "hello world");
+ EXPECT_EQ(Trim("hello world", [](char c) { return c == 'h'; }), "ello world");
+ EXPECT_EQ(Trim("hello world", [](char c) { return c == 'd'; }), "hello worl");
+ EXPECT_EQ(Trim("hello world", [](char c) { return c == 'h' || c == 'd'; }), "ello worl");
+ EXPECT_EQ(Trim("hello world", [](char) { return true; }), "");
+ EXPECT_EQ(Trim("", [](char) { return false; }), "");
+ EXPECT_EQ(Trim("", [](char) { return true; }), "");
+}
+
+TEST(StringTest, IsSpace) {
+ EXPECT_FALSE(IsSpace('a'));
+ EXPECT_FALSE(IsSpace('z'));
+ EXPECT_FALSE(IsSpace('\0'));
+ EXPECT_TRUE(IsSpace(' '));
+ EXPECT_TRUE(IsSpace('\f'));
+ EXPECT_TRUE(IsSpace('\n'));
+ EXPECT_TRUE(IsSpace('\r'));
+ EXPECT_TRUE(IsSpace('\t'));
+ EXPECT_TRUE(IsSpace('\v'));
+}
+
+TEST(StringTest, TrimSpace) {
+ EXPECT_EQ(TrimSpace("hello world"), "hello world");
+ EXPECT_EQ(TrimSpace(" \t hello world\v\f"), "hello world");
+ EXPECT_EQ(TrimSpace("hello \t world"), "hello \t world");
+ EXPECT_EQ(TrimSpace(""), "");
+}
+
+TEST(StringTest, Quote) {
+ EXPECT_EQ("'meow'", Quote("meow"));
+}
+
+TEST(StringTest, Split) {
+ EXPECT_THAT(ToStringList(Split("", ",")), testing::ElementsAre(""));
+ EXPECT_THAT(ToStringList(Split("cat", ",")), testing::ElementsAre("cat"));
+ EXPECT_THAT(ToStringList(Split("cat,", ",")), testing::ElementsAre("cat", ""));
+ EXPECT_THAT(ToStringList(Split(",cat", ",")), testing::ElementsAre("", "cat"));
+ EXPECT_THAT(ToStringList(Split("cat,dog,fish", ",")),
+ testing::ElementsAre("cat", "dog", "fish"));
+ EXPECT_THAT(ToStringList(Split("catdogfish", "dog")), testing::ElementsAre("cat", "fish"));
+}
+
+TEST(StringTest, Join) {
+ EXPECT_EQ(Join(utils::Vector<int, 1>{}, ","), "");
+ EXPECT_EQ(Join(utils::Vector{1, 2, 3}, ","), "1,2,3");
+ EXPECT_EQ(Join(utils::Vector{"cat"}, ","), "cat");
+ EXPECT_EQ(Join(utils::Vector{"cat", "dog"}, ","), "cat,dog");
+}
+
+} // namespace
+} // namespace tint::utils
diff --git a/src/tint/utils/text/symbol.cc b/src/tint/utils/text/symbol.cc
new file mode 100644
index 0000000..731f66e
--- /dev/null
+++ b/src/tint/utils/text/symbol.cc
@@ -0,0 +1,63 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/symbol.h"
+
+#include <utility>
+
+namespace tint {
+
+Symbol::Symbol() = default;
+
+Symbol::Symbol(uint32_t val, tint::ProgramID pid, std::string_view name)
+ : val_(val), program_id_(pid), name_(name) {}
+
+Symbol::Symbol(const Symbol& o) = default;
+
+Symbol::Symbol(Symbol&& o) = default;
+
+Symbol::~Symbol() = default;
+
+Symbol& Symbol::operator=(const Symbol& o) = default;
+
+Symbol& Symbol::operator=(Symbol&& o) = default;
+
+bool Symbol::operator==(const Symbol& other) const {
+ TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Symbol, program_id_, other.program_id_);
+ return val_ == other.val_;
+}
+
+bool Symbol::operator!=(const Symbol& other) const {
+ TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Symbol, program_id_, other.program_id_);
+ return val_ != other.val_;
+}
+
+bool Symbol::operator<(const Symbol& other) const {
+ TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Symbol, program_id_, other.program_id_);
+ return val_ < other.val_;
+}
+
+std::string Symbol::to_str() const {
+ return "$" + std::to_string(val_);
+}
+
+std::string_view Symbol::NameView() const {
+ return name_;
+}
+
+std::string Symbol::Name() const {
+ return std::string(name_);
+}
+
+} // namespace tint
diff --git a/src/tint/utils/text/symbol.h b/src/tint/utils/text/symbol.h
new file mode 100644
index 0000000..381f745
--- /dev/null
+++ b/src/tint/utils/text/symbol.h
@@ -0,0 +1,123 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TEXT_SYMBOL_H_
+#define SRC_TINT_UTILS_TEXT_SYMBOL_H_
+
+#include <string>
+
+#include "src/tint/program_id.h"
+
+namespace tint {
+
+/// A symbol representing a string in the system
+class Symbol {
+ public:
+ /// Constructor
+ /// An invalid symbol
+ Symbol();
+ /// Constructor
+ /// @param val the symbol value
+ /// @param pid the identifier of the program that owns this Symbol
+ /// @param name the name this symbol represents
+ Symbol(uint32_t val, tint::ProgramID pid, std::string_view name);
+
+ /// Copy constructor
+ /// @param o the symbol to copy
+ Symbol(const Symbol& o);
+ /// Move constructor
+ /// @param o the symbol to move
+ Symbol(Symbol&& o);
+ /// Destructor
+ ~Symbol();
+
+ /// Copy assignment
+ /// @param o the other symbol
+ /// @returns the symbol after doing the copy
+ Symbol& operator=(const Symbol& o);
+ /// Move assignment
+ /// @param o the other symbol
+ /// @returns teh symbol after doing the move
+ Symbol& operator=(Symbol&& o);
+
+ /// Equality operator
+ /// @param o the other symbol
+ /// @returns true if the symbols are the same
+ bool operator==(const Symbol& o) const;
+
+ /// Inequality operator
+ /// @param o the other symbol
+ /// @returns true if the symbols are the different
+ bool operator!=(const Symbol& o) const;
+
+ /// Less-than operator
+ /// @param o the other symbol
+ /// @returns true if this symbol is ordered before symbol `o`
+ bool operator<(const Symbol& o) const;
+
+ /// @returns true if the symbol is valid
+ bool IsValid() const { return val_ != static_cast<uint32_t>(-1); }
+
+ /// @returns true if the symbol is valid
+ operator bool() const { return IsValid(); }
+
+ /// @returns the value for the symbol
+ uint32_t value() const { return val_; }
+
+ /// Convert the symbol to a string
+ /// @return the string representation of the symbol
+ std::string to_str() const;
+
+ /// Converts the symbol to the registered name
+ /// @returns the string_view representing the name of the symbol
+ std::string_view NameView() const;
+
+ /// Converts the symbol to the registered name
+ /// @returns the string representing the name of the symbol
+ std::string Name() const;
+
+ /// @returns the identifier of the Program that owns this symbol.
+ tint::ProgramID ProgramID() const { return program_id_; }
+
+ private:
+ uint32_t val_ = static_cast<uint32_t>(-1);
+ tint::ProgramID program_id_;
+ std::string_view name_;
+};
+
+/// @param sym the Symbol
+/// @returns the ProgramID that owns the given Symbol
+inline ProgramID ProgramIDOf(Symbol sym) {
+ return sym.IsValid() ? sym.ProgramID() : ProgramID();
+}
+
+} // namespace tint
+
+namespace std {
+
+/// Custom std::hash specialization for tint::Symbol so symbols can be used as
+/// keys for std::unordered_map and std::unordered_set.
+template <>
+class hash<tint::Symbol> {
+ public:
+ /// @param sym the symbol to return
+ /// @return the Symbol internal value
+ inline std::size_t operator()(const tint::Symbol& sym) const {
+ return static_cast<std::size_t>(sym.value());
+ }
+};
+
+} // namespace std
+
+#endif // SRC_TINT_UTILS_TEXT_SYMBOL_H_
diff --git a/src/tint/utils/text/symbol_table.cc b/src/tint/utils/text/symbol_table.cc
new file mode 100644
index 0000000..cc11724
--- /dev/null
+++ b/src/tint/utils/text/symbol_table.cc
@@ -0,0 +1,94 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/symbol_table.h"
+
+#include "src/tint/utils/debug/debug.h"
+
+namespace tint {
+
+SymbolTable::SymbolTable(tint::ProgramID program_id) : program_id_(program_id) {}
+
+SymbolTable::SymbolTable(SymbolTable&&) = default;
+
+SymbolTable::~SymbolTable() = default;
+
+SymbolTable& SymbolTable::operator=(SymbolTable&&) = default;
+
+Symbol SymbolTable::Register(std::string_view name) {
+ TINT_ASSERT(Symbol, !name.empty());
+
+ auto it = name_to_symbol_.Find(name);
+ if (it) {
+ return *it;
+ }
+ return RegisterInternal(name);
+}
+
+Symbol SymbolTable::RegisterInternal(std::string_view name) {
+ char* name_mem = name_allocator_.Allocate(name.length() + 1);
+ if (name_mem == nullptr) {
+ return Symbol();
+ }
+
+ memcpy(name_mem, name.data(), name.length() + 1);
+ std::string_view nv(name_mem, name.length());
+
+ Symbol sym(next_symbol_, program_id_, nv);
+ ++next_symbol_;
+ name_to_symbol_.Add(sym.NameView(), sym);
+
+ return sym;
+}
+
+Symbol SymbolTable::Get(std::string_view name) const {
+ auto it = name_to_symbol_.Find(name);
+ return it ? *it : Symbol();
+}
+
+Symbol SymbolTable::New(std::string_view prefix_view /* = "" */) {
+ std::string prefix;
+ if (prefix_view.empty()) {
+ prefix = "tint_symbol";
+ } else {
+ prefix = std::string(prefix_view);
+ }
+
+ auto it = name_to_symbol_.Find(prefix);
+ if (!it) {
+ return RegisterInternal(prefix);
+ }
+
+ size_t i = 0;
+ auto last_prefix = last_prefix_to_index_.Find(prefix);
+ if (last_prefix) {
+ i = *last_prefix;
+ }
+
+ std::string name;
+ do {
+ ++i;
+ name = prefix + "_" + std::to_string(i);
+ } while (name_to_symbol_.Contains(name));
+
+ auto sym = RegisterInternal(name);
+ if (last_prefix) {
+ *last_prefix = i;
+ } else {
+ last_prefix_to_index_.Add(prefix, i);
+ }
+ return sym;
+}
+
+} // namespace tint
diff --git a/src/tint/utils/text/symbol_table.h b/src/tint/utils/text/symbol_table.h
new file mode 100644
index 0000000..419f632
--- /dev/null
+++ b/src/tint/utils/text/symbol_table.h
@@ -0,0 +1,111 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TEXT_SYMBOL_TABLE_H_
+#define SRC_TINT_UTILS_TEXT_SYMBOL_TABLE_H_
+
+#include <string>
+
+#include "src/tint/utils/containers/hashmap.h"
+#include "src/tint/utils/memory/bump_allocator.h"
+#include "src/tint/utils/text/symbol.h"
+
+namespace tint {
+
+/// Holds mappings from symbols to their associated string names
+class SymbolTable {
+ public:
+ /// Constructor
+ /// @param program_id the identifier of the program that owns this symbol
+ /// table
+ explicit SymbolTable(tint::ProgramID program_id);
+ /// Move Constructor
+ SymbolTable(SymbolTable&&);
+ /// Destructor
+ ~SymbolTable();
+
+ /// Move assignment
+ /// @param other the symbol table to move
+ /// @returns the symbol table
+ SymbolTable& operator=(SymbolTable&& other);
+
+ /// Wrap sets this symbol table to hold symbols which point to the allocated names in @p o.
+ /// The symbol table after Wrap is intended to temporarily extend the objects
+ /// of an existing immutable SymbolTable
+ /// As the copied objects are owned by @p o, @p o must not be destructed
+ /// or assigned while using this symbol table.
+ /// @param o the immutable SymbolTable to extend
+ void Wrap(const SymbolTable& o) {
+ next_symbol_ = o.next_symbol_;
+ name_to_symbol_ = o.name_to_symbol_;
+ last_prefix_to_index_ = o.last_prefix_to_index_;
+ program_id_ = o.program_id_;
+ }
+
+ /// Registers a name into the symbol table, returning the Symbol.
+ /// @param name the name to register
+ /// @returns the symbol representing the given name
+ Symbol Register(std::string_view name);
+
+ /// Returns the symbol for the given `name`
+ /// @param name the name to lookup
+ /// @returns the symbol for the name or Symbol() if not found.
+ Symbol Get(std::string_view name) const;
+
+ /// Returns a new unique symbol with the given name, possibly suffixed with a
+ /// unique number.
+ /// @param name the symbol name
+ /// @returns a new, unnamed symbol with the given name. If the name is already
+ /// taken then this will be suffixed with an underscore and a unique numerical
+ /// value
+ Symbol New(std::string_view name = "");
+
+ /// Foreach calls the callback function `F` for each symbol in the table.
+ /// @param callback must be a function or function-like object with the
+ /// signature: `void(Symbol)`
+ template <typename F>
+ void Foreach(F&& callback) const {
+ for (auto it : name_to_symbol_) {
+ callback(it.value);
+ }
+ }
+
+ /// @returns the identifier of the Program that owns this symbol table.
+ tint::ProgramID ProgramID() const { return program_id_; }
+
+ private:
+ SymbolTable(const SymbolTable&) = delete;
+ SymbolTable& operator=(const SymbolTable& other) = delete;
+
+ Symbol RegisterInternal(std::string_view name);
+
+ // The value to be associated to the next registered symbol table entry.
+ uint32_t next_symbol_ = 1;
+
+ utils::Hashmap<std::string_view, Symbol, 0> name_to_symbol_;
+ utils::Hashmap<std::string, size_t, 0> last_prefix_to_index_;
+ tint::ProgramID program_id_;
+
+ utils::BumpAllocator name_allocator_;
+};
+
+/// @param symbol_table the SymbolTable
+/// @returns the ProgramID that owns the given SymbolTable
+inline ProgramID ProgramIDOf(const SymbolTable& symbol_table) {
+ return symbol_table.ProgramID();
+}
+
+} // namespace tint
+
+#endif // SRC_TINT_UTILS_TEXT_SYMBOL_TABLE_H_
diff --git a/src/tint/utils/text/symbol_table_test.cc b/src/tint/utils/text/symbol_table_test.cc
new file mode 100644
index 0000000..ee36a66
--- /dev/null
+++ b/src/tint/utils/text/symbol_table_test.cc
@@ -0,0 +1,50 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/symbol_table.h"
+
+#include "gtest/gtest-spi.h"
+
+namespace tint {
+namespace {
+
+using SymbolTableTest = testing::Test;
+
+TEST_F(SymbolTableTest, GeneratesSymbolForName) {
+ auto program_id = ProgramID::New();
+ SymbolTable s{program_id};
+ EXPECT_EQ(Symbol(1, program_id, "name"), s.Register("name"));
+ EXPECT_EQ(Symbol(2, program_id, "another_name"), s.Register("another_name"));
+}
+
+TEST_F(SymbolTableTest, DeduplicatesNames) {
+ auto program_id = ProgramID::New();
+ SymbolTable s{program_id};
+ EXPECT_EQ(Symbol(1, program_id, "name"), s.Register("name"));
+ EXPECT_EQ(Symbol(2, program_id, "another_name"), s.Register("another_name"));
+ EXPECT_EQ(Symbol(1, program_id, "name"), s.Register("name"));
+}
+
+TEST_F(SymbolTableTest, AssertsForBlankString) {
+ EXPECT_FATAL_FAILURE(
+ {
+ auto program_id = ProgramID::New();
+ SymbolTable s{program_id};
+ s.Register("");
+ },
+ "internal compiler error");
+}
+
+} // namespace
+} // namespace tint
diff --git a/src/tint/utils/text/symbol_test.cc b/src/tint/utils/text/symbol_test.cc
new file mode 100644
index 0000000..8fbafea
--- /dev/null
+++ b/src/tint/utils/text/symbol_test.cc
@@ -0,0 +1,54 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/symbol.h"
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+using SymbolTest = testing::Test;
+
+TEST_F(SymbolTest, ToStr) {
+ Symbol sym(1, ProgramID::New(), "");
+ EXPECT_EQ("$1", sym.to_str());
+}
+
+TEST_F(SymbolTest, CopyAssign) {
+ Symbol sym1(1, ProgramID::New(), "");
+ Symbol sym2;
+
+ EXPECT_FALSE(sym2.IsValid());
+ sym2 = sym1;
+ EXPECT_TRUE(sym2.IsValid());
+ EXPECT_EQ(sym2, sym1);
+}
+
+TEST_F(SymbolTest, Comparison) {
+ auto program_id = ProgramID::New();
+ Symbol sym1(1, program_id, "1");
+ Symbol sym2(2, program_id, "2");
+ Symbol sym3(1, program_id, "3");
+
+ EXPECT_TRUE(sym1 == sym3);
+ EXPECT_FALSE(sym1 != sym3);
+ EXPECT_FALSE(sym1 == sym2);
+ EXPECT_TRUE(sym1 != sym2);
+ EXPECT_FALSE(sym3 == sym2);
+ EXPECT_TRUE(sym3 != sym2);
+}
+
+} // namespace
+} // namespace tint
diff --git a/src/tint/utils/text/text_generator.cc b/src/tint/utils/text/text_generator.cc
new file mode 100644
index 0000000..13fecb4
--- /dev/null
+++ b/src/tint/utils/text/text_generator.cc
@@ -0,0 +1,135 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/text_generator.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "src/tint/utils/containers/map.h"
+#include "src/tint/utils/debug/debug.h"
+
+namespace tint::writer {
+
+TextGenerator::TextGenerator() = default;
+
+TextGenerator::~TextGenerator() = default;
+
+std::string TextGenerator::StructName(const type::Struct* s) {
+ auto name = s->Name().Name();
+ if (name.size() > 1 && name[0] == '_' && name[1] == '_') {
+ name = utils::GetOrCreate(builtin_struct_names_, s,
+ [&] { return UniqueIdentifier(name.substr(2)); });
+ }
+ return name;
+}
+
+TextGenerator::LineWriter::LineWriter(TextBuffer* buf) : buffer(buf) {}
+
+TextGenerator::LineWriter::LineWriter(LineWriter&& other) {
+ buffer = other.buffer;
+ other.buffer = nullptr;
+}
+
+TextGenerator::LineWriter::~LineWriter() {
+ if (buffer) {
+ buffer->Append(os.str());
+ }
+}
+
+TextGenerator::TextBuffer::TextBuffer() = default;
+TextGenerator::TextBuffer::~TextBuffer() = default;
+
+void TextGenerator::TextBuffer::IncrementIndent() {
+ current_indent += 2;
+}
+
+void TextGenerator::TextBuffer::DecrementIndent() {
+ current_indent = std::max(2u, current_indent) - 2u;
+}
+
+void TextGenerator::TextBuffer::Append(const std::string& line) {
+ lines.emplace_back(LineInfo{current_indent, line});
+}
+
+void TextGenerator::TextBuffer::Insert(const std::string& line, size_t before, uint32_t indent) {
+ if (TINT_UNLIKELY(before >= lines.size())) {
+ diag::List d;
+ TINT_ICE(Writer, d) << "TextBuffer::Insert() called with before >= lines.size()\n"
+ << " before:" << before << "\n"
+ << " lines.size(): " << lines.size();
+ return;
+ }
+ using DT = decltype(lines)::difference_type;
+ lines.insert(lines.begin() + static_cast<DT>(before), LineInfo{indent, line});
+}
+
+void TextGenerator::TextBuffer::Append(const TextBuffer& tb) {
+ for (auto& line : tb.lines) {
+ // TODO(bclayton): inefficient, consider optimizing
+ lines.emplace_back(LineInfo{current_indent + line.indent, line.content});
+ }
+}
+
+void TextGenerator::TextBuffer::Insert(const TextBuffer& tb, size_t before, uint32_t indent) {
+ if (TINT_UNLIKELY(before >= lines.size())) {
+ diag::List d;
+ TINT_ICE(Writer, d) << "TextBuffer::Insert() called with before >= lines.size()\n"
+ << " before:" << before << "\n"
+ << " lines.size(): " << lines.size();
+ return;
+ }
+ size_t idx = 0;
+ for (auto& line : tb.lines) {
+ // TODO(bclayton): inefficient, consider optimizing
+ using DT = decltype(lines)::difference_type;
+ lines.insert(lines.begin() + static_cast<DT>(before + idx),
+ LineInfo{indent + line.indent, line.content});
+ idx++;
+ }
+}
+
+std::string TextGenerator::TextBuffer::String(uint32_t indent /* = 0 */) const {
+ utils::StringStream ss;
+ for (auto& line : lines) {
+ if (!line.content.empty()) {
+ for (uint32_t i = 0; i < indent + line.indent; i++) {
+ ss << " ";
+ }
+ ss << line.content;
+ }
+ ss << std::endl;
+ }
+ return ss.str();
+}
+
+TextGenerator::ScopedParen::ScopedParen(utils::StringStream& stream) : s(stream) {
+ s << "(";
+}
+
+TextGenerator::ScopedParen::~ScopedParen() {
+ s << ")";
+}
+
+TextGenerator::ScopedIndent::ScopedIndent(TextGenerator* generator)
+ : ScopedIndent(generator->current_buffer_) {}
+
+TextGenerator::ScopedIndent::ScopedIndent(TextBuffer* buffer) : buffer_(buffer) {
+ buffer_->IncrementIndent();
+}
+TextGenerator::ScopedIndent::~ScopedIndent() {
+ buffer_->DecrementIndent();
+}
+
+} // namespace tint::writer
diff --git a/src/tint/utils/text/text_generator.h b/src/tint/utils/text/text_generator.h
new file mode 100644
index 0000000..68ca893
--- /dev/null
+++ b/src/tint/utils/text/text_generator.h
@@ -0,0 +1,206 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TEXT_TEXT_GENERATOR_H_
+#define SRC_TINT_UTILS_TEXT_TEXT_GENERATOR_H_
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "src/tint/lang/base/type/struct.h"
+#include "src/tint/utils/diagnostic/diagnostic.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::writer {
+
+/// Helper methods for generators which are creating text output
+class TextGenerator {
+ public:
+ /// LineInfo holds a single line of text
+ struct LineInfo {
+ /// The indentation of the line in blankspace
+ uint32_t indent = 0;
+ /// The content of the line, without a trailing newline character
+ std::string content;
+ };
+
+ /// TextBuffer holds a list of lines of text.
+ struct TextBuffer {
+ // Constructor
+ TextBuffer();
+
+ // Destructor
+ ~TextBuffer();
+
+ /// IncrementIndent increases the indentation of lines that will be written
+ /// to the TextBuffer
+ void IncrementIndent();
+
+ /// DecrementIndent decreases the indentation of lines that will be written
+ /// to the TextBuffer
+ void DecrementIndent();
+
+ /// Appends the line to the end of the TextBuffer
+ /// @param line the line to append to the TextBuffer
+ void Append(const std::string& line);
+
+ /// Inserts the line to the TextBuffer before the line with index `before`
+ /// @param line the line to append to the TextBuffer
+ /// @param before the zero-based index of the line to insert the text before
+ /// @param indent the indentation to apply to the inserted lines
+ void Insert(const std::string& line, size_t before, uint32_t indent);
+
+ /// Appends the lines of `tb` to the end of this TextBuffer
+ /// @param tb the TextBuffer to append to the end of this TextBuffer
+ void Append(const TextBuffer& tb);
+
+ /// Inserts the lines of `tb` to the TextBuffer before the line with index
+ /// `before`
+ /// @param tb the TextBuffer to insert into this TextBuffer
+ /// @param before the zero-based index of the line to insert the text before
+ /// @param indent the indentation to apply to the inserted lines
+ void Insert(const TextBuffer& tb, size_t before, uint32_t indent);
+
+ /// @returns the buffer's content as a single string
+ /// @param indent additional indentation to apply to each line
+ std::string String(uint32_t indent = 0) const;
+
+ /// The current indentation of the TextBuffer. Lines appended to the
+ /// TextBuffer will use this indentation.
+ uint32_t current_indent = 0;
+
+ /// The lines
+ std::vector<LineInfo> lines;
+ };
+ /// LineWriter is a helper that acts as a string buffer, who's content is
+ /// emitted to the TextBuffer as a single line on destruction.
+ struct LineWriter {
+ public:
+ /// Constructor
+ /// @param buffer the TextBuffer that the LineWriter will append its
+ /// content to on destruction, at the end of the buffer.
+ explicit LineWriter(TextBuffer* buffer);
+
+ /// Move constructor
+ /// @param rhs the LineWriter to move
+ LineWriter(LineWriter&& rhs);
+ /// Destructor
+ ~LineWriter();
+
+ /// @returns the utils::StringStream
+ operator utils::StringStream&() { return os; }
+
+ /// @param rhs the value to write to the line
+ /// @returns the utils::StringStream so calls can be chained
+ template <typename T>
+ utils::StringStream& operator<<(T&& rhs) {
+ return os << std::forward<T>(rhs);
+ }
+
+ private:
+ LineWriter(const LineWriter&) = delete;
+ LineWriter& operator=(const LineWriter&) = delete;
+
+ utils::StringStream os;
+ TextBuffer* buffer;
+ };
+
+ /// Increment the emitter indent level
+ void IncrementIndent() { current_buffer_->IncrementIndent(); }
+ /// Decrement the emitter indent level
+ void DecrementIndent() { current_buffer_->DecrementIndent(); }
+
+ /// @returns a new LineWriter, used for buffering and writing a line to
+ /// the end of #current_buffer_.
+ LineWriter Line() { return LineWriter(current_buffer_); }
+ /// @param buffer the TextBuffer to write the line to
+ /// @returns a new LineWriter, used for buffering and writing a line to
+ /// the end of `buffer`.
+ static LineWriter Line(TextBuffer* buffer) { return LineWriter(buffer); }
+
+ /// @return a new, unique identifier with the given prefix.
+ /// @param prefix optional prefix to apply to the generated identifier. If
+ /// empty "tint_symbol" will be used.
+ virtual std::string UniqueIdentifier(const std::string& prefix = "") = 0;
+
+ /// @param s the structure
+ /// @returns the name of the structure, taking special care of builtin structures that start
+ /// with double underscores. If the structure is a builtin, then the returned name will be a
+ /// unique name without the leading underscores.
+ std::string StructName(const type::Struct* s);
+
+ /// @returns the result data
+ virtual std::string Result() const { return main_buffer_.String(); }
+
+ /// @returns the list of diagnostics raised by the generator.
+ const diag::List& Diagnostics() const { return diagnostics_; }
+
+ protected:
+ /// Helper for writing a '(' on construction and a ')' destruction.
+ struct ScopedParen {
+ /// Constructor
+ /// @param stream the utils::StringStream that will be written to
+ explicit ScopedParen(utils::StringStream& stream);
+ /// Destructor
+ ~ScopedParen();
+
+ private:
+ ScopedParen(ScopedParen&& rhs) = delete;
+ ScopedParen(const ScopedParen&) = delete;
+ ScopedParen& operator=(const ScopedParen&) = delete;
+ utils::StringStream& s;
+ };
+
+ /// Helper for incrementing indentation on construction and decrementing
+ /// indentation on destruction.
+ struct ScopedIndent {
+ /// Constructor
+ /// @param buffer the TextBuffer that the ScopedIndent will indent
+ explicit ScopedIndent(TextBuffer* buffer);
+ /// Constructor
+ /// @param generator ScopedIndent will indent the generator's
+ /// `current_buffer_`
+ explicit ScopedIndent(TextGenerator* generator);
+ /// Destructor
+ ~ScopedIndent();
+
+ private:
+ ScopedIndent(ScopedIndent&& rhs) = delete;
+ ScopedIndent(const ScopedIndent&) = delete;
+ ScopedIndent& operator=(const ScopedIndent&) = delete;
+ TextBuffer* buffer_;
+ };
+
+ /// Constructor
+ TextGenerator();
+ virtual ~TextGenerator();
+
+ /// Diagnostics generated by the generator
+ diag::List diagnostics_;
+ /// The buffer the TextGenerator is currently appending lines to
+ TextBuffer* current_buffer_ = &main_buffer_;
+
+ /// The primary text buffer that the generator will emit
+ TextBuffer main_buffer_;
+
+ private:
+ /// Map of builtin structure to unique generated name
+ std::unordered_map<const type::Struct*, std::string> builtin_struct_names_;
+};
+
+} // namespace tint::writer
+
+#endif // SRC_TINT_UTILS_TEXT_TEXT_GENERATOR_H_
diff --git a/src/tint/utils/text/unicode.cc b/src/tint/utils/text/unicode.cc
new file mode 100644
index 0000000..7bb64b5
--- /dev/null
+++ b/src/tint/utils/text/unicode.cc
@@ -0,0 +1,425 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/unicode.h"
+
+#include <algorithm>
+
+namespace tint::utils {
+namespace {
+
+struct CodePointRange {
+ uint32_t first; // First code point in the interval
+ uint32_t last; // Last code point in the interval (inclusive)
+};
+
+inline bool operator<(CodePoint code_point, CodePointRange range) {
+ return code_point < range.first;
+}
+inline bool operator<(CodePointRange range, CodePoint code_point) {
+ return range.last < code_point;
+}
+
+// Interval ranges of all code points in the Unicode 14 XID_Start set
+// This array needs to be in ascending order.
+constexpr CodePointRange kXIDStartRanges[] = {
+ {0x00041, 0x0005a}, {0x00061, 0x0007a}, {0x000aa, 0x000aa}, {0x000b5, 0x000b5},
+ {0x000ba, 0x000ba}, {0x000c0, 0x000d6}, {0x000d8, 0x000f6}, {0x000f8, 0x002c1},
+ {0x002c6, 0x002d1}, {0x002e0, 0x002e4}, {0x002ec, 0x002ec}, {0x002ee, 0x002ee},
+ {0x00370, 0x00374}, {0x00376, 0x00377}, {0x0037b, 0x0037d}, {0x0037f, 0x0037f},
+ {0x00386, 0x00386}, {0x00388, 0x0038a}, {0x0038c, 0x0038c}, {0x0038e, 0x003a1},
+ {0x003a3, 0x003f5}, {0x003f7, 0x00481}, {0x0048a, 0x0052f}, {0x00531, 0x00556},
+ {0x00559, 0x00559}, {0x00560, 0x00588}, {0x005d0, 0x005ea}, {0x005ef, 0x005f2},
+ {0x00620, 0x0064a}, {0x0066e, 0x0066f}, {0x00671, 0x006d3}, {0x006d5, 0x006d5},
+ {0x006e5, 0x006e6}, {0x006ee, 0x006ef}, {0x006fa, 0x006fc}, {0x006ff, 0x006ff},
+ {0x00710, 0x00710}, {0x00712, 0x0072f}, {0x0074d, 0x007a5}, {0x007b1, 0x007b1},
+ {0x007ca, 0x007ea}, {0x007f4, 0x007f5}, {0x007fa, 0x007fa}, {0x00800, 0x00815},
+ {0x0081a, 0x0081a}, {0x00824, 0x00824}, {0x00828, 0x00828}, {0x00840, 0x00858},
+ {0x00860, 0x0086a}, {0x00870, 0x00887}, {0x00889, 0x0088e}, {0x008a0, 0x008c9},
+ {0x00904, 0x00939}, {0x0093d, 0x0093d}, {0x00950, 0x00950}, {0x00958, 0x00961},
+ {0x00971, 0x00980}, {0x00985, 0x0098c}, {0x0098f, 0x00990}, {0x00993, 0x009a8},
+ {0x009aa, 0x009b0}, {0x009b2, 0x009b2}, {0x009b6, 0x009b9}, {0x009bd, 0x009bd},
+ {0x009ce, 0x009ce}, {0x009dc, 0x009dd}, {0x009df, 0x009e1}, {0x009f0, 0x009f1},
+ {0x009fc, 0x009fc}, {0x00a05, 0x00a0a}, {0x00a0f, 0x00a10}, {0x00a13, 0x00a28},
+ {0x00a2a, 0x00a30}, {0x00a32, 0x00a33}, {0x00a35, 0x00a36}, {0x00a38, 0x00a39},
+ {0x00a59, 0x00a5c}, {0x00a5e, 0x00a5e}, {0x00a72, 0x00a74}, {0x00a85, 0x00a8d},
+ {0x00a8f, 0x00a91}, {0x00a93, 0x00aa8}, {0x00aaa, 0x00ab0}, {0x00ab2, 0x00ab3},
+ {0x00ab5, 0x00ab9}, {0x00abd, 0x00abd}, {0x00ad0, 0x00ad0}, {0x00ae0, 0x00ae1},
+ {0x00af9, 0x00af9}, {0x00b05, 0x00b0c}, {0x00b0f, 0x00b10}, {0x00b13, 0x00b28},
+ {0x00b2a, 0x00b30}, {0x00b32, 0x00b33}, {0x00b35, 0x00b39}, {0x00b3d, 0x00b3d},
+ {0x00b5c, 0x00b5d}, {0x00b5f, 0x00b61}, {0x00b71, 0x00b71}, {0x00b83, 0x00b83},
+ {0x00b85, 0x00b8a}, {0x00b8e, 0x00b90}, {0x00b92, 0x00b95}, {0x00b99, 0x00b9a},
+ {0x00b9c, 0x00b9c}, {0x00b9e, 0x00b9f}, {0x00ba3, 0x00ba4}, {0x00ba8, 0x00baa},
+ {0x00bae, 0x00bb9}, {0x00bd0, 0x00bd0}, {0x00c05, 0x00c0c}, {0x00c0e, 0x00c10},
+ {0x00c12, 0x00c28}, {0x00c2a, 0x00c39}, {0x00c3d, 0x00c3d}, {0x00c58, 0x00c5a},
+ {0x00c5d, 0x00c5d}, {0x00c60, 0x00c61}, {0x00c80, 0x00c80}, {0x00c85, 0x00c8c},
+ {0x00c8e, 0x00c90}, {0x00c92, 0x00ca8}, {0x00caa, 0x00cb3}, {0x00cb5, 0x00cb9},
+ {0x00cbd, 0x00cbd}, {0x00cdd, 0x00cde}, {0x00ce0, 0x00ce1}, {0x00cf1, 0x00cf2},
+ {0x00d04, 0x00d0c}, {0x00d0e, 0x00d10}, {0x00d12, 0x00d3a}, {0x00d3d, 0x00d3d},
+ {0x00d4e, 0x00d4e}, {0x00d54, 0x00d56}, {0x00d5f, 0x00d61}, {0x00d7a, 0x00d7f},
+ {0x00d85, 0x00d96}, {0x00d9a, 0x00db1}, {0x00db3, 0x00dbb}, {0x00dbd, 0x00dbd},
+ {0x00dc0, 0x00dc6}, {0x00e01, 0x00e30}, {0x00e32, 0x00e32}, {0x00e40, 0x00e46},
+ {0x00e81, 0x00e82}, {0x00e84, 0x00e84}, {0x00e86, 0x00e8a}, {0x00e8c, 0x00ea3},
+ {0x00ea5, 0x00ea5}, {0x00ea7, 0x00eb0}, {0x00eb2, 0x00eb2}, {0x00ebd, 0x00ebd},
+ {0x00ec0, 0x00ec4}, {0x00ec6, 0x00ec6}, {0x00edc, 0x00edf}, {0x00f00, 0x00f00},
+ {0x00f40, 0x00f47}, {0x00f49, 0x00f6c}, {0x00f88, 0x00f8c}, {0x01000, 0x0102a},
+ {0x0103f, 0x0103f}, {0x01050, 0x01055}, {0x0105a, 0x0105d}, {0x01061, 0x01061},
+ {0x01065, 0x01066}, {0x0106e, 0x01070}, {0x01075, 0x01081}, {0x0108e, 0x0108e},
+ {0x010a0, 0x010c5}, {0x010c7, 0x010c7}, {0x010cd, 0x010cd}, {0x010d0, 0x010fa},
+ {0x010fc, 0x01248}, {0x0124a, 0x0124d}, {0x01250, 0x01256}, {0x01258, 0x01258},
+ {0x0125a, 0x0125d}, {0x01260, 0x01288}, {0x0128a, 0x0128d}, {0x01290, 0x012b0},
+ {0x012b2, 0x012b5}, {0x012b8, 0x012be}, {0x012c0, 0x012c0}, {0x012c2, 0x012c5},
+ {0x012c8, 0x012d6}, {0x012d8, 0x01310}, {0x01312, 0x01315}, {0x01318, 0x0135a},
+ {0x01380, 0x0138f}, {0x013a0, 0x013f5}, {0x013f8, 0x013fd}, {0x01401, 0x0166c},
+ {0x0166f, 0x0167f}, {0x01681, 0x0169a}, {0x016a0, 0x016ea}, {0x016ee, 0x016f8},
+ {0x01700, 0x01711}, {0x0171f, 0x01731}, {0x01740, 0x01751}, {0x01760, 0x0176c},
+ {0x0176e, 0x01770}, {0x01780, 0x017b3}, {0x017d7, 0x017d7}, {0x017dc, 0x017dc},
+ {0x01820, 0x01878}, {0x01880, 0x018a8}, {0x018aa, 0x018aa}, {0x018b0, 0x018f5},
+ {0x01900, 0x0191e}, {0x01950, 0x0196d}, {0x01970, 0x01974}, {0x01980, 0x019ab},
+ {0x019b0, 0x019c9}, {0x01a00, 0x01a16}, {0x01a20, 0x01a54}, {0x01aa7, 0x01aa7},
+ {0x01b05, 0x01b33}, {0x01b45, 0x01b4c}, {0x01b83, 0x01ba0}, {0x01bae, 0x01baf},
+ {0x01bba, 0x01be5}, {0x01c00, 0x01c23}, {0x01c4d, 0x01c4f}, {0x01c5a, 0x01c7d},
+ {0x01c80, 0x01c88}, {0x01c90, 0x01cba}, {0x01cbd, 0x01cbf}, {0x01ce9, 0x01cec},
+ {0x01cee, 0x01cf3}, {0x01cf5, 0x01cf6}, {0x01cfa, 0x01cfa}, {0x01d00, 0x01dbf},
+ {0x01e00, 0x01f15}, {0x01f18, 0x01f1d}, {0x01f20, 0x01f45}, {0x01f48, 0x01f4d},
+ {0x01f50, 0x01f57}, {0x01f59, 0x01f59}, {0x01f5b, 0x01f5b}, {0x01f5d, 0x01f5d},
+ {0x01f5f, 0x01f7d}, {0x01f80, 0x01fb4}, {0x01fb6, 0x01fbc}, {0x01fbe, 0x01fbe},
+ {0x01fc2, 0x01fc4}, {0x01fc6, 0x01fcc}, {0x01fd0, 0x01fd3}, {0x01fd6, 0x01fdb},
+ {0x01fe0, 0x01fec}, {0x01ff2, 0x01ff4}, {0x01ff6, 0x01ffc}, {0x02071, 0x02071},
+ {0x0207f, 0x0207f}, {0x02090, 0x0209c}, {0x02102, 0x02102}, {0x02107, 0x02107},
+ {0x0210a, 0x02113}, {0x02115, 0x02115}, {0x02118, 0x0211d}, {0x02124, 0x02124},
+ {0x02126, 0x02126}, {0x02128, 0x02128}, {0x0212a, 0x02139}, {0x0213c, 0x0213f},
+ {0x02145, 0x02149}, {0x0214e, 0x0214e}, {0x02160, 0x02188}, {0x02c00, 0x02ce4},
+ {0x02ceb, 0x02cee}, {0x02cf2, 0x02cf3}, {0x02d00, 0x02d25}, {0x02d27, 0x02d27},
+ {0x02d2d, 0x02d2d}, {0x02d30, 0x02d67}, {0x02d6f, 0x02d6f}, {0x02d80, 0x02d96},
+ {0x02da0, 0x02da6}, {0x02da8, 0x02dae}, {0x02db0, 0x02db6}, {0x02db8, 0x02dbe},
+ {0x02dc0, 0x02dc6}, {0x02dc8, 0x02dce}, {0x02dd0, 0x02dd6}, {0x02dd8, 0x02dde},
+ {0x03005, 0x03007}, {0x03021, 0x03029}, {0x03031, 0x03035}, {0x03038, 0x0303c},
+ {0x03041, 0x03096}, {0x0309d, 0x0309f}, {0x030a1, 0x030fa}, {0x030fc, 0x030ff},
+ {0x03105, 0x0312f}, {0x03131, 0x0318e}, {0x031a0, 0x031bf}, {0x031f0, 0x031ff},
+ {0x03400, 0x04dbf}, {0x04e00, 0x0a48c}, {0x0a4d0, 0x0a4fd}, {0x0a500, 0x0a60c},
+ {0x0a610, 0x0a61f}, {0x0a62a, 0x0a62b}, {0x0a640, 0x0a66e}, {0x0a67f, 0x0a69d},
+ {0x0a6a0, 0x0a6ef}, {0x0a717, 0x0a71f}, {0x0a722, 0x0a788}, {0x0a78b, 0x0a7ca},
+ {0x0a7d0, 0x0a7d1}, {0x0a7d3, 0x0a7d3}, {0x0a7d5, 0x0a7d9}, {0x0a7f2, 0x0a801},
+ {0x0a803, 0x0a805}, {0x0a807, 0x0a80a}, {0x0a80c, 0x0a822}, {0x0a840, 0x0a873},
+ {0x0a882, 0x0a8b3}, {0x0a8f2, 0x0a8f7}, {0x0a8fb, 0x0a8fb}, {0x0a8fd, 0x0a8fe},
+ {0x0a90a, 0x0a925}, {0x0a930, 0x0a946}, {0x0a960, 0x0a97c}, {0x0a984, 0x0a9b2},
+ {0x0a9cf, 0x0a9cf}, {0x0a9e0, 0x0a9e4}, {0x0a9e6, 0x0a9ef}, {0x0a9fa, 0x0a9fe},
+ {0x0aa00, 0x0aa28}, {0x0aa40, 0x0aa42}, {0x0aa44, 0x0aa4b}, {0x0aa60, 0x0aa76},
+ {0x0aa7a, 0x0aa7a}, {0x0aa7e, 0x0aaaf}, {0x0aab1, 0x0aab1}, {0x0aab5, 0x0aab6},
+ {0x0aab9, 0x0aabd}, {0x0aac0, 0x0aac0}, {0x0aac2, 0x0aac2}, {0x0aadb, 0x0aadd},
+ {0x0aae0, 0x0aaea}, {0x0aaf2, 0x0aaf4}, {0x0ab01, 0x0ab06}, {0x0ab09, 0x0ab0e},
+ {0x0ab11, 0x0ab16}, {0x0ab20, 0x0ab26}, {0x0ab28, 0x0ab2e}, {0x0ab30, 0x0ab5a},
+ {0x0ab5c, 0x0ab69}, {0x0ab70, 0x0abe2}, {0x0ac00, 0x0d7a3}, {0x0d7b0, 0x0d7c6},
+ {0x0d7cb, 0x0d7fb}, {0x0f900, 0x0fa6d}, {0x0fa70, 0x0fad9}, {0x0fb00, 0x0fb06},
+ {0x0fb13, 0x0fb17}, {0x0fb1d, 0x0fb1d}, {0x0fb1f, 0x0fb28}, {0x0fb2a, 0x0fb36},
+ {0x0fb38, 0x0fb3c}, {0x0fb3e, 0x0fb3e}, {0x0fb40, 0x0fb41}, {0x0fb43, 0x0fb44},
+ {0x0fb46, 0x0fbb1}, {0x0fbd3, 0x0fc5d}, {0x0fc64, 0x0fd3d}, {0x0fd50, 0x0fd8f},
+ {0x0fd92, 0x0fdc7}, {0x0fdf0, 0x0fdf9}, {0x0fe71, 0x0fe71}, {0x0fe73, 0x0fe73},
+ {0x0fe77, 0x0fe77}, {0x0fe79, 0x0fe79}, {0x0fe7b, 0x0fe7b}, {0x0fe7d, 0x0fe7d},
+ {0x0fe7f, 0x0fefc}, {0x0ff21, 0x0ff3a}, {0x0ff41, 0x0ff5a}, {0x0ff66, 0x0ff9d},
+ {0x0ffa0, 0x0ffbe}, {0x0ffc2, 0x0ffc7}, {0x0ffca, 0x0ffcf}, {0x0ffd2, 0x0ffd7},
+ {0x0ffda, 0x0ffdc}, {0x10000, 0x1000b}, {0x1000d, 0x10026}, {0x10028, 0x1003a},
+ {0x1003c, 0x1003d}, {0x1003f, 0x1004d}, {0x10050, 0x1005d}, {0x10080, 0x100fa},
+ {0x10140, 0x10174}, {0x10280, 0x1029c}, {0x102a0, 0x102d0}, {0x10300, 0x1031f},
+ {0x1032d, 0x1034a}, {0x10350, 0x10375}, {0x10380, 0x1039d}, {0x103a0, 0x103c3},
+ {0x103c8, 0x103cf}, {0x103d1, 0x103d5}, {0x10400, 0x1049d}, {0x104b0, 0x104d3},
+ {0x104d8, 0x104fb}, {0x10500, 0x10527}, {0x10530, 0x10563}, {0x10570, 0x1057a},
+ {0x1057c, 0x1058a}, {0x1058c, 0x10592}, {0x10594, 0x10595}, {0x10597, 0x105a1},
+ {0x105a3, 0x105b1}, {0x105b3, 0x105b9}, {0x105bb, 0x105bc}, {0x10600, 0x10736},
+ {0x10740, 0x10755}, {0x10760, 0x10767}, {0x10780, 0x10785}, {0x10787, 0x107b0},
+ {0x107b2, 0x107ba}, {0x10800, 0x10805}, {0x10808, 0x10808}, {0x1080a, 0x10835},
+ {0x10837, 0x10838}, {0x1083c, 0x1083c}, {0x1083f, 0x10855}, {0x10860, 0x10876},
+ {0x10880, 0x1089e}, {0x108e0, 0x108f2}, {0x108f4, 0x108f5}, {0x10900, 0x10915},
+ {0x10920, 0x10939}, {0x10980, 0x109b7}, {0x109be, 0x109bf}, {0x10a00, 0x10a00},
+ {0x10a10, 0x10a13}, {0x10a15, 0x10a17}, {0x10a19, 0x10a35}, {0x10a60, 0x10a7c},
+ {0x10a80, 0x10a9c}, {0x10ac0, 0x10ac7}, {0x10ac9, 0x10ae4}, {0x10b00, 0x10b35},
+ {0x10b40, 0x10b55}, {0x10b60, 0x10b72}, {0x10b80, 0x10b91}, {0x10c00, 0x10c48},
+ {0x10c80, 0x10cb2}, {0x10cc0, 0x10cf2}, {0x10d00, 0x10d23}, {0x10e80, 0x10ea9},
+ {0x10eb0, 0x10eb1}, {0x10f00, 0x10f1c}, {0x10f27, 0x10f27}, {0x10f30, 0x10f45},
+ {0x10f70, 0x10f81}, {0x10fb0, 0x10fc4}, {0x10fe0, 0x10ff6}, {0x11003, 0x11037},
+ {0x11071, 0x11072}, {0x11075, 0x11075}, {0x11083, 0x110af}, {0x110d0, 0x110e8},
+ {0x11103, 0x11126}, {0x11144, 0x11144}, {0x11147, 0x11147}, {0x11150, 0x11172},
+ {0x11176, 0x11176}, {0x11183, 0x111b2}, {0x111c1, 0x111c4}, {0x111da, 0x111da},
+ {0x111dc, 0x111dc}, {0x11200, 0x11211}, {0x11213, 0x1122b}, {0x11280, 0x11286},
+ {0x11288, 0x11288}, {0x1128a, 0x1128d}, {0x1128f, 0x1129d}, {0x1129f, 0x112a8},
+ {0x112b0, 0x112de}, {0x11305, 0x1130c}, {0x1130f, 0x11310}, {0x11313, 0x11328},
+ {0x1132a, 0x11330}, {0x11332, 0x11333}, {0x11335, 0x11339}, {0x1133d, 0x1133d},
+ {0x11350, 0x11350}, {0x1135d, 0x11361}, {0x11400, 0x11434}, {0x11447, 0x1144a},
+ {0x1145f, 0x11461}, {0x11480, 0x114af}, {0x114c4, 0x114c5}, {0x114c7, 0x114c7},
+ {0x11580, 0x115ae}, {0x115d8, 0x115db}, {0x11600, 0x1162f}, {0x11644, 0x11644},
+ {0x11680, 0x116aa}, {0x116b8, 0x116b8}, {0x11700, 0x1171a}, {0x11740, 0x11746},
+ {0x11800, 0x1182b}, {0x118a0, 0x118df}, {0x118ff, 0x11906}, {0x11909, 0x11909},
+ {0x1190c, 0x11913}, {0x11915, 0x11916}, {0x11918, 0x1192f}, {0x1193f, 0x1193f},
+ {0x11941, 0x11941}, {0x119a0, 0x119a7}, {0x119aa, 0x119d0}, {0x119e1, 0x119e1},
+ {0x119e3, 0x119e3}, {0x11a00, 0x11a00}, {0x11a0b, 0x11a32}, {0x11a3a, 0x11a3a},
+ {0x11a50, 0x11a50}, {0x11a5c, 0x11a89}, {0x11a9d, 0x11a9d}, {0x11ab0, 0x11af8},
+ {0x11c00, 0x11c08}, {0x11c0a, 0x11c2e}, {0x11c40, 0x11c40}, {0x11c72, 0x11c8f},
+ {0x11d00, 0x11d06}, {0x11d08, 0x11d09}, {0x11d0b, 0x11d30}, {0x11d46, 0x11d46},
+ {0x11d60, 0x11d65}, {0x11d67, 0x11d68}, {0x11d6a, 0x11d89}, {0x11d98, 0x11d98},
+ {0x11ee0, 0x11ef2}, {0x11fb0, 0x11fb0}, {0x12000, 0x12399}, {0x12400, 0x1246e},
+ {0x12480, 0x12543}, {0x12f90, 0x12ff0}, {0x13000, 0x1342e}, {0x14400, 0x14646},
+ {0x16800, 0x16a38}, {0x16a40, 0x16a5e}, {0x16a70, 0x16abe}, {0x16ad0, 0x16aed},
+ {0x16b00, 0x16b2f}, {0x16b40, 0x16b43}, {0x16b63, 0x16b77}, {0x16b7d, 0x16b8f},
+ {0x16e40, 0x16e7f}, {0x16f00, 0x16f4a}, {0x16f50, 0x16f50}, {0x16f93, 0x16f9f},
+ {0x16fe0, 0x16fe1}, {0x16fe3, 0x16fe3}, {0x17000, 0x187f7}, {0x18800, 0x18cd5},
+ {0x18d00, 0x18d08}, {0x1aff0, 0x1aff3}, {0x1aff5, 0x1affb}, {0x1affd, 0x1affe},
+ {0x1b000, 0x1b122}, {0x1b150, 0x1b152}, {0x1b164, 0x1b167}, {0x1b170, 0x1b2fb},
+ {0x1bc00, 0x1bc6a}, {0x1bc70, 0x1bc7c}, {0x1bc80, 0x1bc88}, {0x1bc90, 0x1bc99},
+ {0x1d400, 0x1d454}, {0x1d456, 0x1d49c}, {0x1d49e, 0x1d49f}, {0x1d4a2, 0x1d4a2},
+ {0x1d4a5, 0x1d4a6}, {0x1d4a9, 0x1d4ac}, {0x1d4ae, 0x1d4b9}, {0x1d4bb, 0x1d4bb},
+ {0x1d4bd, 0x1d4c3}, {0x1d4c5, 0x1d505}, {0x1d507, 0x1d50a}, {0x1d50d, 0x1d514},
+ {0x1d516, 0x1d51c}, {0x1d51e, 0x1d539}, {0x1d53b, 0x1d53e}, {0x1d540, 0x1d544},
+ {0x1d546, 0x1d546}, {0x1d54a, 0x1d550}, {0x1d552, 0x1d6a5}, {0x1d6a8, 0x1d6c0},
+ {0x1d6c2, 0x1d6da}, {0x1d6dc, 0x1d6fa}, {0x1d6fc, 0x1d714}, {0x1d716, 0x1d734},
+ {0x1d736, 0x1d74e}, {0x1d750, 0x1d76e}, {0x1d770, 0x1d788}, {0x1d78a, 0x1d7a8},
+ {0x1d7aa, 0x1d7c2}, {0x1d7c4, 0x1d7cb}, {0x1df00, 0x1df1e}, {0x1e100, 0x1e12c},
+ {0x1e137, 0x1e13d}, {0x1e14e, 0x1e14e}, {0x1e290, 0x1e2ad}, {0x1e2c0, 0x1e2eb},
+ {0x1e7e0, 0x1e7e6}, {0x1e7e8, 0x1e7eb}, {0x1e7ed, 0x1e7ee}, {0x1e7f0, 0x1e7fe},
+ {0x1e800, 0x1e8c4}, {0x1e900, 0x1e943}, {0x1e94b, 0x1e94b}, {0x1ee00, 0x1ee03},
+ {0x1ee05, 0x1ee1f}, {0x1ee21, 0x1ee22}, {0x1ee24, 0x1ee24}, {0x1ee27, 0x1ee27},
+ {0x1ee29, 0x1ee32}, {0x1ee34, 0x1ee37}, {0x1ee39, 0x1ee39}, {0x1ee3b, 0x1ee3b},
+ {0x1ee42, 0x1ee42}, {0x1ee47, 0x1ee47}, {0x1ee49, 0x1ee49}, {0x1ee4b, 0x1ee4b},
+ {0x1ee4d, 0x1ee4f}, {0x1ee51, 0x1ee52}, {0x1ee54, 0x1ee54}, {0x1ee57, 0x1ee57},
+ {0x1ee59, 0x1ee59}, {0x1ee5b, 0x1ee5b}, {0x1ee5d, 0x1ee5d}, {0x1ee5f, 0x1ee5f},
+ {0x1ee61, 0x1ee62}, {0x1ee64, 0x1ee64}, {0x1ee67, 0x1ee6a}, {0x1ee6c, 0x1ee72},
+ {0x1ee74, 0x1ee77}, {0x1ee79, 0x1ee7c}, {0x1ee7e, 0x1ee7e}, {0x1ee80, 0x1ee89},
+ {0x1ee8b, 0x1ee9b}, {0x1eea1, 0x1eea3}, {0x1eea5, 0x1eea9}, {0x1eeab, 0x1eebb},
+ {0x20000, 0x2a6df}, {0x2a700, 0x2b738}, {0x2b740, 0x2b81d}, {0x2b820, 0x2cea1},
+ {0x2ceb0, 0x2ebe0}, {0x2f800, 0x2fa1d}, {0x30000, 0x3134a},
+};
+
+// Number of ranges in kXIDStartRanges
+constexpr size_t kNumXIDStartRanges = sizeof(kXIDStartRanges) / sizeof(kXIDStartRanges[0]);
+
+// The additional code point interval ranges for the Unicode 14 XID_Continue
+// set. This extends the values in kXIDStartRanges.
+// This array needs to be in ascending order.
+constexpr CodePointRange kXIDContinueRanges[] = {
+ {0x00030, 0x00039}, {0x0005f, 0x0005f}, {0x000b7, 0x000b7}, {0x00300, 0x0036f},
+ {0x00387, 0x00387}, {0x00483, 0x00487}, {0x00591, 0x005bd}, {0x005bf, 0x005bf},
+ {0x005c1, 0x005c2}, {0x005c4, 0x005c5}, {0x005c7, 0x005c7}, {0x00610, 0x0061a},
+ {0x0064b, 0x00669}, {0x00670, 0x00670}, {0x006d6, 0x006dc}, {0x006df, 0x006e4},
+ {0x006e7, 0x006e8}, {0x006ea, 0x006ed}, {0x006f0, 0x006f9}, {0x00711, 0x00711},
+ {0x00730, 0x0074a}, {0x007a6, 0x007b0}, {0x007c0, 0x007c9}, {0x007eb, 0x007f3},
+ {0x007fd, 0x007fd}, {0x00816, 0x00819}, {0x0081b, 0x00823}, {0x00825, 0x00827},
+ {0x00829, 0x0082d}, {0x00859, 0x0085b}, {0x00898, 0x0089f}, {0x008ca, 0x008e1},
+ {0x008e3, 0x00903}, {0x0093a, 0x0093c}, {0x0093e, 0x0094f}, {0x00951, 0x00957},
+ {0x00962, 0x00963}, {0x00966, 0x0096f}, {0x00981, 0x00983}, {0x009bc, 0x009bc},
+ {0x009be, 0x009c4}, {0x009c7, 0x009c8}, {0x009cb, 0x009cd}, {0x009d7, 0x009d7},
+ {0x009e2, 0x009e3}, {0x009e6, 0x009ef}, {0x009fe, 0x009fe}, {0x00a01, 0x00a03},
+ {0x00a3c, 0x00a3c}, {0x00a3e, 0x00a42}, {0x00a47, 0x00a48}, {0x00a4b, 0x00a4d},
+ {0x00a51, 0x00a51}, {0x00a66, 0x00a71}, {0x00a75, 0x00a75}, {0x00a81, 0x00a83},
+ {0x00abc, 0x00abc}, {0x00abe, 0x00ac5}, {0x00ac7, 0x00ac9}, {0x00acb, 0x00acd},
+ {0x00ae2, 0x00ae3}, {0x00ae6, 0x00aef}, {0x00afa, 0x00aff}, {0x00b01, 0x00b03},
+ {0x00b3c, 0x00b3c}, {0x00b3e, 0x00b44}, {0x00b47, 0x00b48}, {0x00b4b, 0x00b4d},
+ {0x00b55, 0x00b57}, {0x00b62, 0x00b63}, {0x00b66, 0x00b6f}, {0x00b82, 0x00b82},
+ {0x00bbe, 0x00bc2}, {0x00bc6, 0x00bc8}, {0x00bca, 0x00bcd}, {0x00bd7, 0x00bd7},
+ {0x00be6, 0x00bef}, {0x00c00, 0x00c04}, {0x00c3c, 0x00c3c}, {0x00c3e, 0x00c44},
+ {0x00c46, 0x00c48}, {0x00c4a, 0x00c4d}, {0x00c55, 0x00c56}, {0x00c62, 0x00c63},
+ {0x00c66, 0x00c6f}, {0x00c81, 0x00c83}, {0x00cbc, 0x00cbc}, {0x00cbe, 0x00cc4},
+ {0x00cc6, 0x00cc8}, {0x00cca, 0x00ccd}, {0x00cd5, 0x00cd6}, {0x00ce2, 0x00ce3},
+ {0x00ce6, 0x00cef}, {0x00d00, 0x00d03}, {0x00d3b, 0x00d3c}, {0x00d3e, 0x00d44},
+ {0x00d46, 0x00d48}, {0x00d4a, 0x00d4d}, {0x00d57, 0x00d57}, {0x00d62, 0x00d63},
+ {0x00d66, 0x00d6f}, {0x00d81, 0x00d83}, {0x00dca, 0x00dca}, {0x00dcf, 0x00dd4},
+ {0x00dd6, 0x00dd6}, {0x00dd8, 0x00ddf}, {0x00de6, 0x00def}, {0x00df2, 0x00df3},
+ {0x00e31, 0x00e31}, {0x00e33, 0x00e3a}, {0x00e47, 0x00e4e}, {0x00e50, 0x00e59},
+ {0x00eb1, 0x00eb1}, {0x00eb3, 0x00ebc}, {0x00ec8, 0x00ecd}, {0x00ed0, 0x00ed9},
+ {0x00f18, 0x00f19}, {0x00f20, 0x00f29}, {0x00f35, 0x00f35}, {0x00f37, 0x00f37},
+ {0x00f39, 0x00f39}, {0x00f3e, 0x00f3f}, {0x00f71, 0x00f84}, {0x00f86, 0x00f87},
+ {0x00f8d, 0x00f97}, {0x00f99, 0x00fbc}, {0x00fc6, 0x00fc6}, {0x0102b, 0x0103e},
+ {0x01040, 0x01049}, {0x01056, 0x01059}, {0x0105e, 0x01060}, {0x01062, 0x01064},
+ {0x01067, 0x0106d}, {0x01071, 0x01074}, {0x01082, 0x0108d}, {0x0108f, 0x0109d},
+ {0x0135d, 0x0135f}, {0x01369, 0x01371}, {0x01712, 0x01715}, {0x01732, 0x01734},
+ {0x01752, 0x01753}, {0x01772, 0x01773}, {0x017b4, 0x017d3}, {0x017dd, 0x017dd},
+ {0x017e0, 0x017e9}, {0x0180b, 0x0180d}, {0x0180f, 0x01819}, {0x018a9, 0x018a9},
+ {0x01920, 0x0192b}, {0x01930, 0x0193b}, {0x01946, 0x0194f}, {0x019d0, 0x019da},
+ {0x01a17, 0x01a1b}, {0x01a55, 0x01a5e}, {0x01a60, 0x01a7c}, {0x01a7f, 0x01a89},
+ {0x01a90, 0x01a99}, {0x01ab0, 0x01abd}, {0x01abf, 0x01ace}, {0x01b00, 0x01b04},
+ {0x01b34, 0x01b44}, {0x01b50, 0x01b59}, {0x01b6b, 0x01b73}, {0x01b80, 0x01b82},
+ {0x01ba1, 0x01bad}, {0x01bb0, 0x01bb9}, {0x01be6, 0x01bf3}, {0x01c24, 0x01c37},
+ {0x01c40, 0x01c49}, {0x01c50, 0x01c59}, {0x01cd0, 0x01cd2}, {0x01cd4, 0x01ce8},
+ {0x01ced, 0x01ced}, {0x01cf4, 0x01cf4}, {0x01cf7, 0x01cf9}, {0x01dc0, 0x01dff},
+ {0x0203f, 0x02040}, {0x02054, 0x02054}, {0x020d0, 0x020dc}, {0x020e1, 0x020e1},
+ {0x020e5, 0x020f0}, {0x02cef, 0x02cf1}, {0x02d7f, 0x02d7f}, {0x02de0, 0x02dff},
+ {0x0302a, 0x0302f}, {0x03099, 0x0309a}, {0x0a620, 0x0a629}, {0x0a66f, 0x0a66f},
+ {0x0a674, 0x0a67d}, {0x0a69e, 0x0a69f}, {0x0a6f0, 0x0a6f1}, {0x0a802, 0x0a802},
+ {0x0a806, 0x0a806}, {0x0a80b, 0x0a80b}, {0x0a823, 0x0a827}, {0x0a82c, 0x0a82c},
+ {0x0a880, 0x0a881}, {0x0a8b4, 0x0a8c5}, {0x0a8d0, 0x0a8d9}, {0x0a8e0, 0x0a8f1},
+ {0x0a8ff, 0x0a909}, {0x0a926, 0x0a92d}, {0x0a947, 0x0a953}, {0x0a980, 0x0a983},
+ {0x0a9b3, 0x0a9c0}, {0x0a9d0, 0x0a9d9}, {0x0a9e5, 0x0a9e5}, {0x0a9f0, 0x0a9f9},
+ {0x0aa29, 0x0aa36}, {0x0aa43, 0x0aa43}, {0x0aa4c, 0x0aa4d}, {0x0aa50, 0x0aa59},
+ {0x0aa7b, 0x0aa7d}, {0x0aab0, 0x0aab0}, {0x0aab2, 0x0aab4}, {0x0aab7, 0x0aab8},
+ {0x0aabe, 0x0aabf}, {0x0aac1, 0x0aac1}, {0x0aaeb, 0x0aaef}, {0x0aaf5, 0x0aaf6},
+ {0x0abe3, 0x0abea}, {0x0abec, 0x0abed}, {0x0abf0, 0x0abf9}, {0x0fb1e, 0x0fb1e},
+ {0x0fe00, 0x0fe0f}, {0x0fe20, 0x0fe2f}, {0x0fe33, 0x0fe34}, {0x0fe4d, 0x0fe4f},
+ {0x0ff10, 0x0ff19}, {0x0ff3f, 0x0ff3f}, {0x0ff9e, 0x0ff9f}, {0x101fd, 0x101fd},
+ {0x102e0, 0x102e0}, {0x10376, 0x1037a}, {0x104a0, 0x104a9}, {0x10a01, 0x10a03},
+ {0x10a05, 0x10a06}, {0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f},
+ {0x10ae5, 0x10ae6}, {0x10d24, 0x10d27}, {0x10d30, 0x10d39}, {0x10eab, 0x10eac},
+ {0x10f46, 0x10f50}, {0x10f82, 0x10f85}, {0x11000, 0x11002}, {0x11038, 0x11046},
+ {0x11066, 0x11070}, {0x11073, 0x11074}, {0x1107f, 0x11082}, {0x110b0, 0x110ba},
+ {0x110c2, 0x110c2}, {0x110f0, 0x110f9}, {0x11100, 0x11102}, {0x11127, 0x11134},
+ {0x11136, 0x1113f}, {0x11145, 0x11146}, {0x11173, 0x11173}, {0x11180, 0x11182},
+ {0x111b3, 0x111c0}, {0x111c9, 0x111cc}, {0x111ce, 0x111d9}, {0x1122c, 0x11237},
+ {0x1123e, 0x1123e}, {0x112df, 0x112ea}, {0x112f0, 0x112f9}, {0x11300, 0x11303},
+ {0x1133b, 0x1133c}, {0x1133e, 0x11344}, {0x11347, 0x11348}, {0x1134b, 0x1134d},
+ {0x11357, 0x11357}, {0x11362, 0x11363}, {0x11366, 0x1136c}, {0x11370, 0x11374},
+ {0x11435, 0x11446}, {0x11450, 0x11459}, {0x1145e, 0x1145e}, {0x114b0, 0x114c3},
+ {0x114d0, 0x114d9}, {0x115af, 0x115b5}, {0x115b8, 0x115c0}, {0x115dc, 0x115dd},
+ {0x11630, 0x11640}, {0x11650, 0x11659}, {0x116ab, 0x116b7}, {0x116c0, 0x116c9},
+ {0x1171d, 0x1172b}, {0x11730, 0x11739}, {0x1182c, 0x1183a}, {0x118e0, 0x118e9},
+ {0x11930, 0x11935}, {0x11937, 0x11938}, {0x1193b, 0x1193e}, {0x11940, 0x11940},
+ {0x11942, 0x11943}, {0x11950, 0x11959}, {0x119d1, 0x119d7}, {0x119da, 0x119e0},
+ {0x119e4, 0x119e4}, {0x11a01, 0x11a0a}, {0x11a33, 0x11a39}, {0x11a3b, 0x11a3e},
+ {0x11a47, 0x11a47}, {0x11a51, 0x11a5b}, {0x11a8a, 0x11a99}, {0x11c2f, 0x11c36},
+ {0x11c38, 0x11c3f}, {0x11c50, 0x11c59}, {0x11c92, 0x11ca7}, {0x11ca9, 0x11cb6},
+ {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a}, {0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45},
+ {0x11d47, 0x11d47}, {0x11d50, 0x11d59}, {0x11d8a, 0x11d8e}, {0x11d90, 0x11d91},
+ {0x11d93, 0x11d97}, {0x11da0, 0x11da9}, {0x11ef3, 0x11ef6}, {0x16a60, 0x16a69},
+ {0x16ac0, 0x16ac9}, {0x16af0, 0x16af4}, {0x16b30, 0x16b36}, {0x16b50, 0x16b59},
+ {0x16f4f, 0x16f4f}, {0x16f51, 0x16f87}, {0x16f8f, 0x16f92}, {0x16fe4, 0x16fe4},
+ {0x16ff0, 0x16ff1}, {0x1bc9d, 0x1bc9e}, {0x1cf00, 0x1cf2d}, {0x1cf30, 0x1cf46},
+ {0x1d165, 0x1d169}, {0x1d16d, 0x1d172}, {0x1d17b, 0x1d182}, {0x1d185, 0x1d18b},
+ {0x1d1aa, 0x1d1ad}, {0x1d242, 0x1d244}, {0x1d7ce, 0x1d7ff}, {0x1da00, 0x1da36},
+ {0x1da3b, 0x1da6c}, {0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f},
+ {0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018}, {0x1e01b, 0x1e021},
+ {0x1e023, 0x1e024}, {0x1e026, 0x1e02a}, {0x1e130, 0x1e136}, {0x1e140, 0x1e149},
+ {0x1e2ae, 0x1e2ae}, {0x1e2ec, 0x1e2f9}, {0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a},
+ {0x1e950, 0x1e959}, {0x1fbf0, 0x1fbf9}, {0xe0100, 0xe01ef},
+};
+
+// Number of ranges in kXIDContinueRanges
+constexpr size_t kNumXIDContinueRanges = sizeof(kXIDContinueRanges) / sizeof(kXIDContinueRanges[0]);
+
+} // namespace
+
+bool CodePoint::IsXIDStart() const {
+ // Short circuit ASCII. The binary search will find these last, but most
+ // of our current source is ASCII, so handle them quicker.
+ if ((value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z')) {
+ return true;
+ }
+ // With [a-zA-Z] handled, nothing less then the next sequence start can be
+ // XIDStart, so filter them all out. This catches most of the common symbols
+ // that are used in ASCII.
+ if (value < 0x000aa) {
+ return false;
+ }
+ return std::binary_search(kXIDStartRanges, kXIDStartRanges + kNumXIDStartRanges, *this);
+}
+
+bool CodePoint::IsXIDContinue() const {
+ // Short circuit ASCII. The binary search will find these last, but most
+ // of our current source is ASCII, so handle them quicker.
+ if ((value >= '0' && value <= '9') || value == '_') {
+ return true;
+ }
+ return IsXIDStart() || std::binary_search(kXIDContinueRanges,
+ kXIDContinueRanges + kNumXIDContinueRanges, *this);
+}
+
+namespace utf8 {
+
+std::pair<CodePoint, size_t> Decode(const uint8_t* ptr, size_t len) {
+ if (len < 1) {
+ return {};
+ }
+ // Fast-path ASCII characters as they're always valid
+ if (ptr[0] <= 0x7f) {
+ return {CodePoint{ptr[0]}, 1};
+ }
+
+ // Lookup table for the first byte of a UTF-8 sequence.
+ // 0 indicates an invalid length.
+ // Note that bit encodings that can fit in a smaller number of bytes are
+ // invalid (e.g. 0xc0). Code points that exceed the unicode maximum of
+ // 0x10FFFF are also invalid (0xf5+).
+ // See: https://en.wikipedia.org/wiki/UTF-8#Encoding and
+ // https://datatracker.ietf.org/doc/html/rfc3629#section-3
+ static constexpr uint8_t kSequenceLength[256] = {
+ // 0 1 2 3 4 5 6 7 8 9 a b c d e f
+ /* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x10 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x20 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x40 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x60 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 0xc0 */ 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ /* 0xd0 */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ /* 0xe0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ /* 0xf0 */ 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+
+ uint8_t n = kSequenceLength[ptr[0]];
+ if (n > len) {
+ return {};
+ }
+
+ CodePoint c;
+
+ uint8_t valid = 0x80;
+ switch (n) {
+ // Note: n=0 (invalid) is correctly handled without a case.
+ case 1:
+ c = CodePoint{ptr[0]};
+ break;
+ case 2:
+ valid &= ptr[1];
+ c = CodePoint{(static_cast<uint32_t>(ptr[0] & 0b00011111) << 6) |
+ (static_cast<uint32_t>(ptr[1] & 0b00111111))};
+ break;
+ case 3:
+ valid &= ptr[1] & ptr[2];
+ c = CodePoint{(static_cast<uint32_t>(ptr[0] & 0b00001111) << 12) |
+ (static_cast<uint32_t>(ptr[1] & 0b00111111) << 6) |
+ (static_cast<uint32_t>(ptr[2] & 0b00111111))};
+ break;
+ case 4:
+ valid &= ptr[1] & ptr[2] & ptr[3];
+ c = CodePoint{(static_cast<uint32_t>(ptr[0] & 0b00000111) << 18) |
+ (static_cast<uint32_t>(ptr[1] & 0b00111111) << 12) |
+ (static_cast<uint32_t>(ptr[2] & 0b00111111) << 6) |
+ (static_cast<uint32_t>(ptr[3] & 0b00111111))};
+ break;
+ }
+ if (!valid) {
+ n = 0;
+ c = 0;
+ }
+ return {c, n};
+}
+
+std::pair<CodePoint, size_t> Decode(std::string_view utf8_string) {
+ return Decode(reinterpret_cast<const uint8_t*>(utf8_string.data()), utf8_string.size());
+}
+
+bool IsASCII(std::string_view str) {
+ for (auto c : str) {
+ if (c & 0x80) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace utf8
+
+} // namespace tint::utils
diff --git a/src/tint/utils/text/unicode.h b/src/tint/utils/text/unicode.h
new file mode 100644
index 0000000..e5e32c0
--- /dev/null
+++ b/src/tint/utils/text/unicode.h
@@ -0,0 +1,80 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TEXT_UNICODE_H_
+#define SRC_TINT_UTILS_TEXT_UNICODE_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+#include <utility>
+
+namespace tint::utils {
+
+/// CodePoint is a unicode code point.
+struct CodePoint {
+ /// Constructor
+ inline CodePoint() = default;
+
+ /// Constructor
+ /// @param v the code point value
+ inline explicit CodePoint(uint32_t v) : value(v) {}
+
+ /// @returns the code point value
+ inline operator uint32_t() const { return value; }
+
+ /// Assignment operator
+ /// @param v the new value for the code point
+ /// @returns this CodePoint
+ inline CodePoint& operator=(uint32_t v) {
+ value = v;
+ return *this;
+ }
+
+ /// @returns true if this CodePoint is in the XID_Start set.
+ /// @see https://unicode.org/reports/tr31/
+ bool IsXIDStart() const;
+
+ /// @returns true if this CodePoint is in the XID_Continue set.
+ /// @see https://unicode.org/reports/tr31/
+ bool IsXIDContinue() const;
+
+ /// The code point value
+ uint32_t value = 0;
+};
+
+namespace utf8 {
+
+/// Decodes the first code point in the utf8 string.
+/// @param ptr the pointer to the first byte of the utf8 sequence
+/// @param len the maximum number of bytes to read
+/// @returns a pair of CodePoint and width in code units (bytes).
+/// If the next code point cannot be decoded then returns [0,0].
+std::pair<CodePoint, size_t> Decode(const uint8_t* ptr, size_t len);
+
+/// Decodes the first code point in the utf8 string.
+/// @param utf8_string the string view that contains the utf8 sequence
+/// @returns a pair of CodePoint and width in code units (bytes).
+/// If the next code point cannot be decoded then returns [0,0].
+std::pair<CodePoint, size_t> Decode(std::string_view utf8_string);
+
+/// @returns true if all the utf-8 code points in the string are ASCII
+/// (code-points 0x00..0x7f).
+bool IsASCII(std::string_view);
+
+} // namespace utf8
+
+} // namespace tint::utils
+
+#endif // SRC_TINT_UTILS_TEXT_UNICODE_H_
diff --git a/src/tint/utils/text/unicode_test.cc b/src/tint/utils/text/unicode_test.cc
new file mode 100644
index 0000000..9f87b04
--- /dev/null
+++ b/src/tint/utils/text/unicode_test.cc
@@ -0,0 +1,490 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/text/unicode.h"
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+
+/// Helper for constructing a CodePoint
+#define C(x) CodePoint(x)
+
+namespace tint::utils {
+
+////////////////////////////////////////////////////////////////////////////////
+// CodePoint character set tests
+////////////////////////////////////////////////////////////////////////////////
+namespace {
+
+struct CodePointCase {
+ CodePoint code_point;
+ bool is_xid_start;
+ bool is_xid_continue;
+};
+
+std::ostream& operator<<(std::ostream& out, CodePointCase c) {
+ return out << c.code_point;
+}
+
+class CodePointTest : public testing::TestWithParam<CodePointCase> {};
+
+TEST_P(CodePointTest, CharacterSets) {
+ auto param = GetParam();
+ EXPECT_EQ(param.code_point.IsXIDStart(), param.is_xid_start);
+ EXPECT_EQ(param.code_point.IsXIDContinue(), param.is_xid_continue);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ CodePointTest,
+ CodePointTest,
+ ::testing::ValuesIn({
+ CodePointCase{C(' '), /* start */ false, /* continue */ false},
+ CodePointCase{C('\t'), /* start */ false, /* continue */ false},
+ CodePointCase{C('\n'), /* start */ false, /* continue */ false},
+ CodePointCase{C('\r'), /* start */ false, /* continue */ false},
+ CodePointCase{C('!'), /* start */ false, /* continue */ false},
+ CodePointCase{C('"'), /* start */ false, /* continue */ false},
+ CodePointCase{C('#'), /* start */ false, /* continue */ false},
+ CodePointCase{C('$'), /* start */ false, /* continue */ false},
+ CodePointCase{C('%'), /* start */ false, /* continue */ false},
+ CodePointCase{C('&'), /* start */ false, /* continue */ false},
+ CodePointCase{C('\\'), /* start */ false, /* continue */ false},
+ CodePointCase{C('/'), /* start */ false, /* continue */ false},
+ CodePointCase{C('('), /* start */ false, /* continue */ false},
+ CodePointCase{C(')'), /* start */ false, /* continue */ false},
+ CodePointCase{C('*'), /* start */ false, /* continue */ false},
+ CodePointCase{C(','), /* start */ false, /* continue */ false},
+ CodePointCase{C('-'), /* start */ false, /* continue */ false},
+ CodePointCase{C('/'), /* start */ false, /* continue */ false},
+ CodePointCase{C('`'), /* start */ false, /* continue */ false},
+ CodePointCase{C('@'), /* start */ false, /* continue */ false},
+ CodePointCase{C('^'), /* start */ false, /* continue */ false},
+ CodePointCase{C('['), /* start */ false, /* continue */ false},
+ CodePointCase{C(']'), /* start */ false, /* continue */ false},
+ CodePointCase{C('|'), /* start */ false, /* continue */ false},
+ CodePointCase{C('('), /* start */ false, /* continue */ false},
+ CodePointCase{C(','), /* start */ false, /* continue */ false},
+ CodePointCase{C('}'), /* start */ false, /* continue */ false},
+ CodePointCase{C('a'), /* start */ true, /* continue */ true},
+ CodePointCase{C('b'), /* start */ true, /* continue */ true},
+ CodePointCase{C('c'), /* start */ true, /* continue */ true},
+ CodePointCase{C('x'), /* start */ true, /* continue */ true},
+ CodePointCase{C('y'), /* start */ true, /* continue */ true},
+ CodePointCase{C('z'), /* start */ true, /* continue */ true},
+ CodePointCase{C('A'), /* start */ true, /* continue */ true},
+ CodePointCase{C('B'), /* start */ true, /* continue */ true},
+ CodePointCase{C('C'), /* start */ true, /* continue */ true},
+ CodePointCase{C('X'), /* start */ true, /* continue */ true},
+ CodePointCase{C('Y'), /* start */ true, /* continue */ true},
+ CodePointCase{C('Z'), /* start */ true, /* continue */ true},
+ CodePointCase{C('_'), /* start */ false, /* continue */ true},
+ CodePointCase{C('0'), /* start */ false, /* continue */ true},
+ CodePointCase{C('1'), /* start */ false, /* continue */ true},
+ CodePointCase{C('2'), /* start */ false, /* continue */ true},
+ CodePointCase{C('8'), /* start */ false, /* continue */ true},
+ CodePointCase{C('9'), /* start */ false, /* continue */ true},
+ CodePointCase{C('0'), /* start */ false, /* continue */ true},
+
+ // First in XID_Start
+ CodePointCase{C(0x00041), /* start */ true, /* continue */ true},
+ // Last in XID_Start
+ CodePointCase{C(0x3134a), /* start */ true, /* continue */ true},
+
+ // Random selection from XID_Start, using the interval's first
+ CodePointCase{C(0x002ee), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x005ef), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x009f0), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x00d3d), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x00d54), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x00e86), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x00edc), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x01c00), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x01c80), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x02071), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x02dd0), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x0a4d0), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x0aac0), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x0ab5c), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x0ffda), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x11313), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x1ee49), /* start */ true, /* continue */ true},
+
+ // Random selection from XID_Start, using the interval's last
+ CodePointCase{C(0x00710), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x00b83), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x00b9a), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x00ec4), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x01081), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x012be), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x02107), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x03029), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x03035), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x0aadd), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x10805), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x11075), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x1d4a2), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x1e7fe), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x1ee27), /* start */ true, /* continue */ true},
+ CodePointCase{C(0x2b738), /* start */ true, /* continue */ true},
+
+ // Random selection from XID_Continue, using the interval's first
+ CodePointCase{C(0x16ac0), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00dca), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x16f4f), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x0fe00), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00ec8), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x009be), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x11d47), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x11d50), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x0a926), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x0aac1), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00f18), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x11145), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x017dd), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x0aaeb), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x11173), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00a51), /* start */ false, /* continue */ true},
+
+ // Random selection from XID_Continue, using the interval's last
+ CodePointCase{C(0x00f84), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x10a3a), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x1e018), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x0a827), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x01abd), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x009d7), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00b6f), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x0096f), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x11146), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x10eac), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00f39), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x1e136), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00def), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x0fe34), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x009c8), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00fbc), /* start */ false, /* continue */ true},
+
+ // Random code points that are one less than an interval of XID_Start
+ CodePointCase{C(0x003f6), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x005ee), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x009ef), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00d3c), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x00d53), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x00e85), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x00edb), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x01bff), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x02070), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x02dcf), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x0a4cf), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x0aabf), /* start */ false, /* continue */ true},
+ CodePointCase{C(0x0ab5b), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x0ffd9), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x11312), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x1ee48), /* start */ false, /* continue */ false},
+
+ // Random code points that are one more than an interval of XID_Continue
+ CodePointCase{C(0x00060), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x00a4e), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x00a84), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x00cce), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x00eda), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x00f85), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x01b74), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x01c38), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x0fe30), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x11174), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x112eb), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x115de), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x1172c), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x11a3f), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x11c37), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x11d92), /* start */ false, /* continue */ false},
+ CodePointCase{C(0x1e2af), /* start */ false, /* continue */ false},
+ }));
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// DecodeUTF8 valid tests
+////////////////////////////////////////////////////////////////////////////////
+namespace {
+
+struct CodePointAndWidth {
+ CodePoint code_point;
+ size_t width;
+};
+
+bool operator==(const CodePointAndWidth& a, const CodePointAndWidth& b) {
+ return a.code_point == b.code_point && a.width == b.width;
+}
+
+std::ostream& operator<<(std::ostream& out, CodePointAndWidth cpw) {
+ return out << "code_point: " << cpw.code_point << ", width: " << cpw.width;
+}
+
+struct DecodeUTF8Case {
+ std::string string;
+ std::vector<CodePointAndWidth> expected;
+};
+
+std::ostream& operator<<(std::ostream& out, DecodeUTF8Case c) {
+ return out << "'" << c.string << "'";
+}
+
+class DecodeUTF8Test : public testing::TestWithParam<DecodeUTF8Case> {};
+
+TEST_P(DecodeUTF8Test, Valid) {
+ auto param = GetParam();
+
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(param.string.data());
+ const size_t len = param.string.size();
+
+ std::vector<CodePointAndWidth> got;
+ size_t offset = 0;
+ while (offset < len) {
+ auto [code_point, width] = utf8::Decode(data + offset, len - offset);
+ if (width == 0) {
+ FAIL() << "Decode() failed at byte offset " << offset;
+ }
+ offset += width;
+ got.emplace_back(CodePointAndWidth{code_point, width});
+ }
+
+ EXPECT_THAT(got, ::testing::ElementsAreArray(param.expected));
+}
+
+INSTANTIATE_TEST_SUITE_P(AsciiLetters,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({
+ DecodeUTF8Case{"a", {{C('a'), 1}}},
+ DecodeUTF8Case{"abc", {{C('a'), 1}, {C('b'), 1}, {C('c'), 1}}},
+ DecodeUTF8Case{"def", {{C('d'), 1}, {C('e'), 1}, {C('f'), 1}}},
+ DecodeUTF8Case{"gh", {{C('g'), 1}, {C('h'), 1}}},
+ DecodeUTF8Case{"ij", {{C('i'), 1}, {C('j'), 1}}},
+ DecodeUTF8Case{"klm", {{C('k'), 1}, {C('l'), 1}, {C('m'), 1}}},
+ DecodeUTF8Case{"nop", {{C('n'), 1}, {C('o'), 1}, {C('p'), 1}}},
+ DecodeUTF8Case{"qr", {{C('q'), 1}, {C('r'), 1}}},
+ DecodeUTF8Case{"stu", {{C('s'), 1}, {C('t'), 1}, {C('u'), 1}}},
+ DecodeUTF8Case{"vw", {{C('v'), 1}, {C('w'), 1}}},
+ DecodeUTF8Case{"xyz", {{C('x'), 1}, {C('y'), 1}, {C('z'), 1}}},
+ DecodeUTF8Case{"A", {{C('A'), 1}}},
+ DecodeUTF8Case{"ABC", {{C('A'), 1}, {C('B'), 1}, {C('C'), 1}}},
+ DecodeUTF8Case{"DEF", {{C('D'), 1}, {C('E'), 1}, {C('F'), 1}}},
+ DecodeUTF8Case{"GH", {{C('G'), 1}, {C('H'), 1}}},
+ DecodeUTF8Case{"IJ", {{C('I'), 1}, {C('J'), 1}}},
+ DecodeUTF8Case{"KLM", {{C('K'), 1}, {C('L'), 1}, {C('M'), 1}}},
+ DecodeUTF8Case{"NOP", {{C('N'), 1}, {C('O'), 1}, {C('P'), 1}}},
+ DecodeUTF8Case{"QR", {{C('Q'), 1}, {C('R'), 1}}},
+ DecodeUTF8Case{"STU", {{C('S'), 1}, {C('T'), 1}, {C('U'), 1}}},
+ DecodeUTF8Case{"VW", {{C('V'), 1}, {C('W'), 1}}},
+ DecodeUTF8Case{"XYZ", {{C('X'), 1}, {C('Y'), 1}, {C('Z'), 1}}},
+ }));
+
+INSTANTIATE_TEST_SUITE_P(AsciiNumbers,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({
+ DecodeUTF8Case{"012", {{C('0'), 1}, {C('1'), 1}, {C('2'), 1}}},
+ DecodeUTF8Case{"345", {{C('3'), 1}, {C('4'), 1}, {C('5'), 1}}},
+ DecodeUTF8Case{"678", {{C('6'), 1}, {C('7'), 1}, {C('8'), 1}}},
+ DecodeUTF8Case{"9", {{C('9'), 1}}},
+ }));
+
+INSTANTIATE_TEST_SUITE_P(AsciiSymbols,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({
+ DecodeUTF8Case{"!\"#", {{C('!'), 1}, {C('"'), 1}, {C('#'), 1}}},
+ DecodeUTF8Case{"$%&", {{C('$'), 1}, {C('%'), 1}, {C('&'), 1}}},
+ DecodeUTF8Case{"'()", {{C('\''), 1}, {C('('), 1}, {C(')'), 1}}},
+ DecodeUTF8Case{"*,-", {{C('*'), 1}, {C(','), 1}, {C('-'), 1}}},
+ DecodeUTF8Case{"/`@", {{C('/'), 1}, {C('`'), 1}, {C('@'), 1}}},
+ DecodeUTF8Case{"^\\[", {{C('^'), 1}, {C('\\'), 1}, {C('['), 1}}},
+ DecodeUTF8Case{"]_|", {{C(']'), 1}, {C('_'), 1}, {C('|'), 1}}},
+ DecodeUTF8Case{"{}", {{C('{'), 1}, {C('}'), 1}}},
+ }));
+
+INSTANTIATE_TEST_SUITE_P(AsciiSpecial,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({
+ DecodeUTF8Case{"", {}},
+ DecodeUTF8Case{" \t\n", {{C(' '), 1}, {C('\t'), 1}, {C('\n'), 1}}},
+ DecodeUTF8Case{"\a\b\f", {{C('\a'), 1}, {C('\b'), 1}, {C('\f'), 1}}},
+ DecodeUTF8Case{"\n\r\t", {{C('\n'), 1}, {C('\r'), 1}, {C('\t'), 1}}},
+ DecodeUTF8Case{"\v", {{C('\v'), 1}}},
+ }));
+
+INSTANTIATE_TEST_SUITE_P(Hindi,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({DecodeUTF8Case{
+ // नमस्ते दुनिया
+ "\xe0\xa4\xa8\xe0\xa4\xae\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa5"
+ "\x87\x20\xe0\xa4\xa6\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xaf"
+ "\xe0\xa4\xbe",
+ {
+ {C(0x0928), 3}, // न
+ {C(0x092e), 3}, // म
+ {C(0x0938), 3}, // स
+ {C(0x094d), 3}, // ् //
+ {C(0x0924), 3}, // त
+ {C(0x0947), 3}, // े //
+ {C(' '), 1},
+ {C(0x0926), 3}, // द
+ {C(0x0941), 3}, // ु //
+ {C(0x0928), 3}, // न
+ {C(0x093f), 3}, // ि //
+ {C(0x092f), 3}, // य
+ {C(0x093e), 3}, // ा //
+ },
+ }}));
+
+INSTANTIATE_TEST_SUITE_P(Mandarin,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({DecodeUTF8Case{
+ // 你好世界
+ "\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c",
+ {
+ {C(0x4f60), 3}, // 你
+ {C(0x597d), 3}, // 好
+ {C(0x4e16), 3}, // 世
+ {C(0x754c), 3}, // 界
+ },
+ }}));
+
+INSTANTIATE_TEST_SUITE_P(Japanese,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({DecodeUTF8Case{
+ // こんにちは世界
+ "\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1"
+ "\xe3\x81\xaf\xe4\xb8\x96\xe7\x95\x8c",
+ {
+ {C(0x3053), 3}, // こ
+ {C(0x3093), 3}, // ん
+ {C(0x306B), 3}, // に
+ {C(0x3061), 3}, // ち
+ {C(0x306F), 3}, // は
+ {C(0x4E16), 3}, // 世
+ {C(0x754C), 3}, // 界
+ },
+ }}));
+
+INSTANTIATE_TEST_SUITE_P(Korean,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({DecodeUTF8Case{
+ // 안녕하세요 세계
+ "\xec\x95\x88\xeb\x85\x95\xed\x95\x98\xec\x84\xb8"
+ "\xec\x9a\x94\x20\xec\x84\xb8\xea\xb3\x84",
+ {
+ {C(0xc548), 3}, // 안
+ {C(0xb155), 3}, // 녕
+ {C(0xd558), 3}, // 하
+ {C(0xc138), 3}, // 세
+ {C(0xc694), 3}, // 요
+ {C(' '), 1}, //
+ {C(0xc138), 3}, // 세
+ {C(0xacc4), 3}, // 계
+ },
+ }}));
+
+INSTANTIATE_TEST_SUITE_P(Emoji,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({DecodeUTF8Case{
+ // 👋🌎
+ "\xf0\x9f\x91\x8b\xf0\x9f\x8c\x8e",
+ {
+ {C(0x1f44b), 4}, // 👋
+ {C(0x1f30e), 4}, // 🌎
+ },
+ }}));
+
+INSTANTIATE_TEST_SUITE_P(Random,
+ DecodeUTF8Test,
+ ::testing::ValuesIn({DecodeUTF8Case{
+ // Øⓑꚫ쁹Ǵ𐌒岾🥍ⴵ㍨又ᮗ
+ "\xc3\x98\xe2\x93\x91\xea\x9a\xab\xec\x81\xb9\xc7\xb4\xf0\x90\x8c\x92"
+ "\xe5\xb2\xbe\xf0\x9f\xa5\x8d\xe2\xb4\xb5\xe3\x8d\xa8\xe5\x8f\x88\xe1"
+ "\xae\x97",
+ {
+ {C(0x000d8), 2}, // Ø
+ {C(0x024d1), 3}, // ⓑ
+ {C(0x0a6ab), 3}, // ꚫ
+ {C(0x0c079), 3}, // 쁹
+ {C(0x001f4), 2}, // Ǵ
+ {C(0x10312), 4}, // 𐌒
+ {C(0x05cbe), 3}, // 岾
+ {C(0x1f94d), 4}, // 🥍
+ {C(0x02d35), 3}, // ⴵ
+ {C(0x03368), 3}, // ㍨
+ {C(0x053c8), 3}, // 又
+ {C(0x01b97), 3}, // ᮗ
+ },
+ }}));
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// DecodeUTF8 invalid tests
+////////////////////////////////////////////////////////////////////////////////
+namespace {
+class DecodeUTF8InvalidTest : public testing::TestWithParam<const char*> {};
+
+TEST_P(DecodeUTF8InvalidTest, Invalid) {
+ auto* param = GetParam();
+
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(param);
+ const size_t len = std::string(param).size();
+
+ auto [code_point, width] = utf8::Decode(data, len);
+ EXPECT_EQ(code_point, CodePoint(0));
+ EXPECT_EQ(width, 0u);
+}
+
+INSTANTIATE_TEST_SUITE_P(Invalid,
+ DecodeUTF8InvalidTest,
+ ::testing::ValuesIn({
+ "\x80\x80\x80\x80", // 10000000
+ "\x81\x80\x80\x80", // 10000001
+ "\x8f\x80\x80\x80", // 10001111
+ "\x90\x80\x80\x80", // 10010000
+ "\x91\x80\x80\x80", // 10010001
+ "\x9f\x80\x80\x80", // 10011111
+ "\xa0\x80\x80\x80", // 10100000
+ "\xa1\x80\x80\x80", // 10100001
+ "\xaf\x80\x80\x80", // 10101111
+ "\xb0\x80\x80\x80", // 10110000
+ "\xb1\x80\x80\x80", // 10110001
+ "\xbf\x80\x80\x80", // 10111111
+ "\xc0\x80\x80\x80", // 11000000
+ "\xc1\x80\x80\x80", // 11000001
+ "\xf5\x80\x80\x80", // 11110101
+ "\xf6\x80\x80\x80", // 11110110
+ "\xf7\x80\x80\x80", // 11110111
+ "\xf8\x80\x80\x80", // 11111000
+ "\xfe\x80\x80\x80", // 11111110
+ "\xff\x80\x80\x80", // 11111111
+
+ "\xd0", // 2-bytes, missing second byte
+ "\xe8\x8f", // 3-bytes, missing third byte
+ "\xf4\x8f\x8f", // 4-bytes, missing fourth byte
+
+ "\xd0\x7f", // 2-bytes, second byte MSB unset
+ "\xe8\x7f\x8f", // 3-bytes, second byte MSB unset
+ "\xe8\x8f\x7f", // 3-bytes, third byte MSB unset
+ "\xf4\x7f\x8f\x8f", // 4-bytes, second byte MSB unset
+ "\xf4\x8f\x7f\x8f", // 4-bytes, third byte MSB unset
+ "\xf4\x8f\x8f\x7f", // 4-bytes, fourth byte MSB unset
+ }));
+
+} // namespace
+
+} // namespace tint::utils
diff --git a/src/tint/utils/traits/traits.h b/src/tint/utils/traits/traits.h
new file mode 100644
index 0000000..f9c9a91
--- /dev/null
+++ b/src/tint/utils/traits/traits.h
@@ -0,0 +1,214 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_UTILS_TRAITS_TRAITS_H_
+#define SRC_TINT_UTILS_TRAITS_TRAITS_H_
+
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+namespace tint::utils::traits {
+
+/// Convience type definition for std::decay<T>::type
+template <typename T>
+using Decay = typename std::decay<T>::type;
+
+/// NthTypeOf returns the `N`th type in `Types`
+template <int N, typename... Types>
+using NthTypeOf = typename std::tuple_element<N, std::tuple<Types...>>::type;
+
+/// Signature describes the signature of a function.
+template <typename RETURN, typename... PARAMETERS>
+struct Signature {
+ /// The return type of the function signature
+ using ret = RETURN;
+ /// The parameters of the function signature held in a std::tuple
+ using parameters = std::tuple<PARAMETERS...>;
+ /// The type of the Nth parameter of function signature
+ template <std::size_t N>
+ using parameter = NthTypeOf<N, PARAMETERS...>;
+ /// The total number of parameters
+ static constexpr std::size_t parameter_count = sizeof...(PARAMETERS);
+};
+
+/// SignatureOf is a traits helper that infers the signature of the function,
+/// method, static method, lambda, or function-like object `F`.
+template <typename F>
+struct SignatureOf {
+ /// The signature of the function-like object `F`
+ using type = typename SignatureOf<decltype(&F::operator())>::type;
+};
+
+/// SignatureOf specialization for a regular function or static method.
+template <typename R, typename... ARGS>
+struct SignatureOf<R (*)(ARGS...)> {
+ /// The signature of the function-like object `F`
+ using type = Signature<typename std::decay<R>::type, typename std::decay<ARGS>::type...>;
+};
+
+/// SignatureOf specialization for a non-static method.
+template <typename R, typename C, typename... ARGS>
+struct SignatureOf<R (C::*)(ARGS...)> {
+ /// The signature of the function-like object `F`
+ using type = Signature<typename std::decay<R>::type, typename std::decay<ARGS>::type...>;
+};
+
+/// SignatureOf specialization for a non-static, const method.
+template <typename R, typename C, typename... ARGS>
+struct SignatureOf<R (C::*)(ARGS...) const> {
+ /// The signature of the function-like object `F`
+ using type = Signature<typename std::decay<R>::type, typename std::decay<ARGS>::type...>;
+};
+
+/// SignatureOfT is an alias to `typename SignatureOf<F>::type`.
+template <typename F>
+using SignatureOfT = typename SignatureOf<Decay<F>>::type;
+
+/// ParameterType is an alias to `typename SignatureOf<F>::type::parameter<N>`.
+template <typename F, std::size_t N>
+using ParameterType = typename SignatureOfT<Decay<F>>::template parameter<N>;
+
+/// LastParameterType returns the type of the last parameter of `F`. `F` must have at least one
+/// parameter.
+template <typename F>
+using LastParameterType = ParameterType<F, SignatureOfT<Decay<F>>::parameter_count - 1>;
+
+/// ReturnType is an alias to `typename SignatureOf<F>::type::ret`.
+template <typename F>
+using ReturnType = typename SignatureOfT<Decay<F>>::ret;
+
+/// Returns true iff decayed T and decayed U are the same.
+template <typename T, typename U>
+static constexpr bool IsType = std::is_same<Decay<T>, Decay<U>>::value;
+
+/// IsTypeOrDerived<T, BASE> is true iff `T` is of type `BASE`, or derives from
+/// `BASE`.
+template <typename T, typename BASE>
+static constexpr bool IsTypeOrDerived =
+ std::is_base_of<BASE, Decay<T>>::value || std::is_same<BASE, Decay<T>>::value;
+
+/// If `CONDITION` is true then EnableIf resolves to type T, otherwise an
+/// invalid type.
+template <bool CONDITION, typename T = void>
+using EnableIf = typename std::enable_if<CONDITION, T>::type;
+
+/// If `T` is of type `BASE`, or derives from `BASE`, then EnableIfIsType
+/// resolves to type `T`, otherwise an invalid type.
+template <typename T, typename BASE>
+using EnableIfIsType = EnableIf<IsTypeOrDerived<T, BASE>, T>;
+
+/// @returns the std::index_sequence with all the indices shifted by OFFSET.
+template <std::size_t OFFSET, std::size_t... INDICES>
+constexpr auto Shift(std::index_sequence<INDICES...>) {
+ return std::integer_sequence<std::size_t, OFFSET + INDICES...>{};
+}
+
+/// @returns a std::integer_sequence with the integers `[OFFSET..OFFSET+COUNT)`
+template <std::size_t OFFSET, std::size_t COUNT>
+constexpr auto Range() {
+ return Shift<OFFSET>(std::make_index_sequence<COUNT>{});
+}
+
+namespace detail {
+
+/// @returns the tuple `t` swizzled by `INDICES`
+template <typename TUPLE, std::size_t... INDICES>
+constexpr auto Swizzle(TUPLE&& t, std::index_sequence<INDICES...>)
+ -> std::tuple<std::tuple_element_t<INDICES, std::remove_reference_t<TUPLE>>...> {
+ return {std::forward<std::tuple_element_t<INDICES, std::remove_reference_t<TUPLE>>>(
+ std::get<INDICES>(std::forward<TUPLE>(t)))...};
+}
+
+/// @returns a nullptr of the tuple type `TUPLE` swizzled by `INDICES`.
+/// @note: This function is intended to be used in a `decltype()` expression,
+/// and returns a pointer-to-tuple as the tuple may hold non-constructable
+/// types.
+template <typename TUPLE, std::size_t... INDICES>
+constexpr auto* SwizzlePtrTy(std::index_sequence<INDICES...>) {
+ using Swizzled = std::tuple<std::tuple_element_t<INDICES, TUPLE>...>;
+ return static_cast<Swizzled*>(nullptr);
+}
+
+} // namespace detail
+
+/// @returns the slice of the tuple `t` with the tuple elements
+/// `[OFFSET..OFFSET+COUNT)`
+template <std::size_t OFFSET, std::size_t COUNT, typename TUPLE>
+constexpr auto Slice(TUPLE&& t) {
+ return traits::detail::Swizzle<TUPLE>(std::forward<TUPLE>(t), Range<OFFSET, COUNT>());
+}
+
+/// Resolves to the slice of the tuple `t` with the tuple elements
+/// `[OFFSET..OFFSET+COUNT)`
+template <std::size_t OFFSET, std::size_t COUNT, typename TUPLE>
+using SliceTuple =
+ std::remove_pointer_t<decltype(traits::detail::SwizzlePtrTy<TUPLE>(Range<OFFSET, COUNT>()))>;
+
+namespace detail {
+/// Base template for IsTypeIn
+template <typename T, typename TypeList>
+struct IsTypeIn;
+
+/// Specialization for IsTypeIn
+template <typename T, template <typename...> typename TypeContainer, typename... Ts>
+struct IsTypeIn<T, TypeContainer<Ts...>> : std::disjunction<std::is_same<T, Ts>...> {};
+} // namespace detail
+
+/// Evaluates to true if T is one of the types in the TypeContainer's template arguments.
+/// Works for std::variant, std::tuple, std::pair, or any typename template where all parameters are
+/// types.
+template <typename T, typename TypeContainer>
+static constexpr bool IsTypeIn = traits::detail::IsTypeIn<T, TypeContainer>::value;
+
+/// Evaluates to the decayed pointer element type, or the decayed type T if T is not a pointer.
+template <typename T>
+using PtrElTy = Decay<std::remove_pointer_t<Decay<T>>>;
+
+/// Evaluates to true if `T` decayed is a `std::string`, `std::string_view` or `const char*`
+template <typename T>
+static constexpr bool IsStringLike =
+ std::is_same_v<Decay<T>, std::string> || std::is_same_v<Decay<T>, std::string_view> ||
+ std::is_same_v<Decay<T>, const char*>;
+
+namespace detail {
+/// Helper for CharArrayToCharPtr
+template <typename T>
+struct CharArrayToCharPtrImpl {
+ /// Evaluates to T
+ using type = T;
+};
+/// Specialization of CharArrayToCharPtrImpl for `char[N]`
+template <size_t N>
+struct CharArrayToCharPtrImpl<char[N]> {
+ /// Evaluates to `char*`
+ using type = char*;
+};
+/// Specialization of CharArrayToCharPtrImpl for `const char[N]`
+template <size_t N>
+struct CharArrayToCharPtrImpl<const char[N]> {
+ /// Evaluates to `const char*`
+ using type = const char*;
+};
+} // namespace detail
+
+/// Evaluates to `char*` or `const char*` if `T` is `char[N]` or `const char[N]`, respectively,
+/// otherwise T.
+template <typename T>
+using CharArrayToCharPtr = typename traits::detail::CharArrayToCharPtrImpl<T>::type;
+
+} // namespace tint::utils::traits
+
+#endif // SRC_TINT_UTILS_TRAITS_TRAITS_H_
diff --git a/src/tint/utils/traits/traits_test.cc b/src/tint/utils/traits/traits_test.cc
new file mode 100644
index 0000000..da78f27
--- /dev/null
+++ b/src/tint/utils/traits/traits_test.cc
@@ -0,0 +1,249 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/utils/traits/traits.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils::traits {
+
+namespace {
+
+static_assert(std::is_same_v<PtrElTy<int*>, int>);
+static_assert(std::is_same_v<PtrElTy<int const*>, int>);
+static_assert(std::is_same_v<PtrElTy<int const* const>, int>);
+static_assert(std::is_same_v<PtrElTy<int const* const volatile>, int>);
+static_assert(std::is_same_v<PtrElTy<int>, int>);
+static_assert(std::is_same_v<PtrElTy<int const>, int>);
+static_assert(std::is_same_v<PtrElTy<int const volatile>, int>);
+
+static_assert(IsStringLike<std::string>);
+static_assert(IsStringLike<std::string_view>);
+static_assert(IsStringLike<const char*>);
+static_assert(IsStringLike<const std::string&>);
+static_assert(IsStringLike<const std::string_view&>);
+static_assert(IsStringLike<const char*>);
+static_assert(!IsStringLike<bool>);
+static_assert(!IsStringLike<int>);
+static_assert(!IsStringLike<const char**>);
+
+struct S {};
+void F1(S) {}
+void F3(int, S, float) {}
+} // namespace
+
+TEST(ParamType, Function) {
+ F1({}); // Avoid unused method warning
+ F3(0, {}, 0); // Avoid unused method warning
+ static_assert(std::is_same_v<ParameterType<decltype(&F1), 0>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(&F3), 0>, int>);
+ static_assert(std::is_same_v<ParameterType<decltype(&F3), 1>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(&F3), 2>, float>);
+ static_assert(std::is_same_v<ReturnType<decltype(&F1)>, void>);
+ static_assert(std::is_same_v<ReturnType<decltype(&F3)>, void>);
+ static_assert(SignatureOfT<decltype(&F1)>::parameter_count == 1);
+ static_assert(SignatureOfT<decltype(&F3)>::parameter_count == 3);
+}
+
+TEST(ParamType, Method) {
+ class C {
+ public:
+ void F1(S) {}
+ void F3(int, S, float) {}
+ };
+ C().F1({}); // Avoid unused method warning
+ C().F3(0, {}, 0); // Avoid unused method warning
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>);
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>);
+ static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>);
+ static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>);
+ static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1);
+ static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3);
+}
+
+TEST(ParamType, ConstMethod) {
+ class C {
+ public:
+ void F1(S) const {}
+ void F3(int, S, float) const {}
+ };
+ C().F1({}); // Avoid unused method warning
+ C().F3(0, {}, 0); // Avoid unused method warning
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>);
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>);
+ static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>);
+ static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>);
+ static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1);
+ static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3);
+}
+
+TEST(ParamType, StaticMethod) {
+ class C {
+ public:
+ static void F1(S) {}
+ static void F3(int, S, float) {}
+ };
+ C::F1({}); // Avoid unused method warning
+ C::F3(0, {}, 0); // Avoid unused method warning
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F1), 0>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 0>, int>);
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 1>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(&C::F3), 2>, float>);
+ static_assert(std::is_same_v<ReturnType<decltype(&C::F1)>, void>);
+ static_assert(std::is_same_v<ReturnType<decltype(&C::F3)>, void>);
+ static_assert(SignatureOfT<decltype(&C::F1)>::parameter_count == 1);
+ static_assert(SignatureOfT<decltype(&C::F3)>::parameter_count == 3);
+}
+
+TEST(ParamType, FunctionLike) {
+ using F1 = std::function<void(S)>;
+ using F3 = std::function<void(int, S, float)>;
+ static_assert(std::is_same_v<ParameterType<F1, 0>, S>);
+ static_assert(std::is_same_v<ParameterType<F3, 0>, int>);
+ static_assert(std::is_same_v<ParameterType<F3, 1>, S>);
+ static_assert(std::is_same_v<ParameterType<F3, 2>, float>);
+ static_assert(std::is_same_v<ReturnType<F1>, void>);
+ static_assert(std::is_same_v<ReturnType<F3>, void>);
+ static_assert(SignatureOfT<F1>::parameter_count == 1);
+ static_assert(SignatureOfT<F3>::parameter_count == 3);
+}
+
+TEST(ParamType, Lambda) {
+ auto l1 = [](S) {};
+ auto l3 = [](int, S, float) {};
+ static_assert(std::is_same_v<ParameterType<decltype(l1), 0>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(l3), 0>, int>);
+ static_assert(std::is_same_v<ParameterType<decltype(l3), 1>, S>);
+ static_assert(std::is_same_v<ParameterType<decltype(l3), 2>, float>);
+ static_assert(std::is_same_v<ReturnType<decltype(l1)>, void>);
+ static_assert(std::is_same_v<ReturnType<decltype(l3)>, void>);
+ static_assert(SignatureOfT<decltype(l1)>::parameter_count == 1);
+ static_assert(SignatureOfT<decltype(l3)>::parameter_count == 3);
+}
+
+TEST(Slice, Empty) {
+ auto sliced = Slice<0, 0>(std::make_tuple<>());
+ static_assert(std::tuple_size_v<decltype(sliced)> == 0);
+}
+
+TEST(Slice, SingleElementSliceEmpty) {
+ auto sliced = Slice<0, 0>(std::make_tuple<int>(1));
+ static_assert(std::tuple_size_v<decltype(sliced)> == 0);
+}
+
+TEST(Slice, SingleElementSliceFull) {
+ auto sliced = Slice<0, 1>(std::make_tuple<int>(1));
+ static_assert(std::tuple_size_v<decltype(sliced)> == 1);
+ static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>, "");
+ EXPECT_EQ(std::get<0>(sliced), 1);
+}
+
+TEST(Slice, MixedTupleSliceEmpty) {
+ auto sliced = Slice<1, 0>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+ static_assert(std::tuple_size_v<decltype(sliced)> == 0);
+}
+
+TEST(Slice, MixedTupleSliceFull) {
+ auto sliced = Slice<0, 3>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+ static_assert(std::tuple_size_v<decltype(sliced)> == 3);
+ static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>, "");
+ static_assert(std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, bool>, "");
+ static_assert(std::is_same_v<std::tuple_element_t<2, decltype(sliced)>, float>);
+ EXPECT_EQ(std::get<0>(sliced), 1);
+ EXPECT_EQ(std::get<1>(sliced), true);
+ EXPECT_EQ(std::get<2>(sliced), 2.0f);
+}
+
+TEST(Slice, MixedTupleSliceLowPart) {
+ auto sliced = Slice<0, 2>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+ static_assert(std::tuple_size_v<decltype(sliced)> == 2);
+ static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, int>, "");
+ static_assert(std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, bool>, "");
+ EXPECT_EQ(std::get<0>(sliced), 1);
+ EXPECT_EQ(std::get<1>(sliced), true);
+}
+
+TEST(Slice, MixedTupleSliceHighPart) {
+ auto sliced = Slice<1, 2>(std::make_tuple<int, bool, float>(1, true, 2.0f));
+ static_assert(std::tuple_size_v<decltype(sliced)> == 2);
+ static_assert(std::is_same_v<std::tuple_element_t<0, decltype(sliced)>, bool>, "");
+ static_assert(std::is_same_v<std::tuple_element_t<1, decltype(sliced)>, float>);
+ EXPECT_EQ(std::get<0>(sliced), true);
+ EXPECT_EQ(std::get<1>(sliced), 2.0f);
+}
+
+TEST(Slice, PreservesRValueRef) {
+ int i;
+ int& int_ref = i;
+ auto tuple = std::forward_as_tuple(std::move(int_ref));
+ static_assert(std::is_same_v<int&&, //
+ std::tuple_element_t<0, decltype(tuple)>>);
+ auto sliced = Slice<0, 1>(std::move(tuple));
+ static_assert(std::is_same_v<int&&, //
+ std::tuple_element_t<0, decltype(sliced)>>);
+}
+
+TEST(SliceTuple, Empty) {
+ using sliced = SliceTuple<0, 0, std::tuple<>>;
+ static_assert(std::tuple_size_v<sliced> == 0);
+}
+
+TEST(SliceTuple, SingleElementSliceEmpty) {
+ using sliced = SliceTuple<0, 0, std::tuple<int>>;
+ static_assert(std::tuple_size_v<sliced> == 0);
+}
+
+TEST(SliceTuple, SingleElementSliceFull) {
+ using sliced = SliceTuple<0, 1, std::tuple<int>>;
+ static_assert(std::tuple_size_v<sliced> == 1);
+ static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>);
+}
+
+TEST(SliceTuple, MixedTupleSliceEmpty) {
+ using sliced = SliceTuple<1, 0, std::tuple<int, bool, float>>;
+ static_assert(std::tuple_size_v<sliced> == 0);
+}
+
+TEST(SliceTuple, MixedTupleSliceFull) {
+ using sliced = SliceTuple<0, 3, std::tuple<int, bool, float>>;
+ static_assert(std::tuple_size_v<sliced> == 3);
+ static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>);
+ static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, bool>);
+ static_assert(std::is_same_v<std::tuple_element_t<2, sliced>, float>);
+}
+
+TEST(SliceTuple, MixedTupleSliceLowPart) {
+ using sliced = SliceTuple<0, 2, std::tuple<int, bool, float>>;
+ static_assert(std::tuple_size_v<sliced> == 2);
+ static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, int>);
+ static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, bool>);
+}
+
+TEST(SliceTuple, MixedTupleSliceHighPart) {
+ using sliced = SliceTuple<1, 2, std::tuple<int, bool, float>>;
+ static_assert(std::tuple_size_v<sliced> == 2);
+ static_assert(std::is_same_v<std::tuple_element_t<0, sliced>, bool>);
+ static_assert(std::is_same_v<std::tuple_element_t<1, sliced>, float>);
+}
+
+static_assert(std::is_same_v<char*, CharArrayToCharPtr<char[2]>>);
+static_assert(std::is_same_v<const char*, CharArrayToCharPtr<const char[2]>>);
+static_assert(std::is_same_v<int, CharArrayToCharPtr<int>>);
+static_assert(std::is_same_v<int[2], CharArrayToCharPtr<int[2]>>);
+
+} // namespace tint::utils::traits