tint: Replace VectorRef with ConstVectorRef.

The elements of the VectorRef is now immutable, but can be
moved, if the caller relinquishes ownership by explicitly using
std::move() at the callsite.

Also add utils::Empty as a way of signalling that a vector should be
constructed with no elements. This is helpful in templated code where
{} cannot be used due to overload ambiguity.

Change-Id: I24a50a13956b0692771a8bc9046336ad46261562
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/97842
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Ben Clayton 2022-08-02 16:52:25 +00:00 committed by Dawn LUCI CQ
parent bdc619f5ef
commit 34d46731bb
12 changed files with 423 additions and 483 deletions

View File

@ -2082,7 +2082,7 @@ INSTANTIATE_TEST_SUITE_P(ResolverTest,
testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases())); testing::ValuesIn(ast::builtin::test::TextureOverloadCase::ValidCases()));
static std::string to_str(const std::string& function, static std::string to_str(const std::string& function,
utils::ConstVectorRef<const sem::Parameter*> params) { utils::VectorRef<const sem::Parameter*> params) {
std::stringstream out; std::stringstream out;
out << function << "("; out << function << "(";
bool first = true; bool first = true;

View File

@ -510,9 +510,8 @@ const sem::Constant* ConstEval::Literal(const sem::Type* ty,
}); });
} }
const sem::Constant* ConstEval::ArrayOrStructCtor( const sem::Constant* ConstEval::ArrayOrStructCtor(const sem::Type* ty,
const sem::Type* ty, utils::VectorRef<const sem::Expression*> args) {
utils::ConstVectorRef<const sem::Expression*> args) {
if (args.IsEmpty()) { if (args.IsEmpty()) {
return ZeroValue(builder, ty); return ZeroValue(builder, ty);
} }
@ -532,7 +531,7 @@ const sem::Constant* ConstEval::ArrayOrStructCtor(
} }
const sem::Constant* ConstEval::Conv(const sem::Type* ty, const sem::Constant* ConstEval::Conv(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
uint32_t el_count = 0; uint32_t el_count = 0;
auto* el_ty = sem::Type::ElementOf(ty, &el_count); auto* el_ty = sem::Type::ElementOf(ty, &el_count);
if (!el_ty) { if (!el_ty) {
@ -553,17 +552,17 @@ const sem::Constant* ConstEval::Conv(const sem::Type* ty,
} }
const sem::Constant* ConstEval::Zero(const sem::Type* ty, const sem::Constant* ConstEval::Zero(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*>) { utils::VectorRef<const sem::Expression*>) {
return ZeroValue(builder, ty); return ZeroValue(builder, ty);
} }
const sem::Constant* ConstEval::Identity(const sem::Type*, const sem::Constant* ConstEval::Identity(const sem::Type*,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
return args[0]->ConstantValue(); return args[0]->ConstantValue();
} }
const sem::Constant* ConstEval::VecSplat(const sem::Type* ty, const sem::Constant* ConstEval::VecSplat(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
if (auto* arg = args[0]->ConstantValue()) { if (auto* arg = args[0]->ConstantValue()) {
return builder.create<Splat>(ty, arg, static_cast<const sem::Vector*>(ty)->Width()); return builder.create<Splat>(ty, arg, static_cast<const sem::Vector*>(ty)->Width());
} }
@ -571,7 +570,7 @@ const sem::Constant* ConstEval::VecSplat(const sem::Type* ty,
} }
const sem::Constant* ConstEval::VecCtorS(const sem::Type* ty, const sem::Constant* ConstEval::VecCtorS(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
utils::Vector<const sem::Constant*, 4> els; utils::Vector<const sem::Constant*, 4> els;
for (auto* arg : args) { for (auto* arg : args) {
els.Push(arg->ConstantValue()); els.Push(arg->ConstantValue());
@ -580,7 +579,7 @@ const sem::Constant* ConstEval::VecCtorS(const sem::Type* ty,
} }
const sem::Constant* ConstEval::VecCtorM(const sem::Type* ty, const sem::Constant* ConstEval::VecCtorM(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
utils::Vector<const sem::Constant*, 4> els; utils::Vector<const sem::Constant*, 4> els;
for (auto* arg : args) { for (auto* arg : args) {
auto* val = arg->ConstantValue(); auto* val = arg->ConstantValue();
@ -605,7 +604,7 @@ const sem::Constant* ConstEval::VecCtorM(const sem::Type* ty,
} }
const sem::Constant* ConstEval::MatCtorS(const sem::Type* ty, const sem::Constant* ConstEval::MatCtorS(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
auto* m = static_cast<const sem::Matrix*>(ty); auto* m = static_cast<const sem::Matrix*>(ty);
utils::Vector<const sem::Constant*, 4> els; utils::Vector<const sem::Constant*, 4> els;
@ -621,7 +620,7 @@ const sem::Constant* ConstEval::MatCtorS(const sem::Type* ty,
} }
const sem::Constant* ConstEval::MatCtorV(const sem::Type* ty, const sem::Constant* ConstEval::MatCtorV(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
utils::Vector<const sem::Constant*, 4> els; utils::Vector<const sem::Constant*, 4> els;
for (auto* arg : args) { for (auto* arg : args) {
els.Push(arg->ConstantValue()); els.Push(arg->ConstantValue());
@ -668,7 +667,7 @@ const sem::Constant* ConstEval::MemberAccess(const sem::Expression* obj_expr,
const sem::Constant* ConstEval::Swizzle(const sem::Type* ty, const sem::Constant* ConstEval::Swizzle(const sem::Type* ty,
const sem::Expression* vec_expr, const sem::Expression* vec_expr,
utils::ConstVectorRef<uint32_t> indices) { utils::VectorRef<uint32_t> indices) {
auto* vec_val = vec_expr->ConstantValue(); auto* vec_val = vec_expr->ConstantValue();
if (!vec_val) { if (!vec_val) {
return nullptr; return nullptr;
@ -688,7 +687,7 @@ const sem::Constant* ConstEval::Bitcast(const sem::Type*, const sem::Expression*
} }
const sem::Constant* ConstEval::OpComplement(const sem::Type*, const sem::Constant* ConstEval::OpComplement(const sem::Type*,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
auto transform = [&](const sem::Constant* c) { auto transform = [&](const sem::Constant* c) {
auto create = [&](auto i) { auto create = [&](auto i) {
return CreateElement(builder, c->Type(), decltype(i)(~i.value)); return CreateElement(builder, c->Type(), decltype(i)(~i.value));
@ -699,7 +698,7 @@ const sem::Constant* ConstEval::OpComplement(const sem::Type*,
} }
const sem::Constant* ConstEval::OpMinus(const sem::Type*, const sem::Constant* ConstEval::OpMinus(const sem::Type*,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
auto transform = [&](const sem::Constant* c) { auto transform = [&](const sem::Constant* c) {
auto create = [&](auto i) { // auto create = [&](auto i) { //
// For signed integrals, avoid C++ UB by not negating the // For signed integrals, avoid C++ UB by not negating the
@ -723,7 +722,7 @@ const sem::Constant* ConstEval::OpMinus(const sem::Type*,
} }
const sem::Constant* ConstEval::atan2(const sem::Type*, const sem::Constant* ConstEval::atan2(const sem::Type*,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) { auto transform = [&](const sem::Constant* c0, const sem::Constant* c1) {
auto create = [&](auto i, auto j) { auto create = [&](auto i, auto j) {
return CreateElement(builder, c0->Type(), decltype(i)(std::atan2(i.value, j.value))); return CreateElement(builder, c0->Type(), decltype(i)(std::atan2(i.value, j.value)));
@ -735,7 +734,7 @@ const sem::Constant* ConstEval::atan2(const sem::Type*,
} }
const sem::Constant* ConstEval::clamp(const sem::Type*, const sem::Constant* ConstEval::clamp(const sem::Type*,
utils::ConstVectorRef<const sem::Expression*> args) { utils::VectorRef<const sem::Expression*> args) {
auto transform = [&](const sem::Constant* c0, const sem::Constant* c1, auto transform = [&](const sem::Constant* c0, const sem::Constant* c1,
const sem::Constant* c2) { const sem::Constant* c2) {
auto create = [&](auto e, auto low, auto high) { auto create = [&](auto e, auto low, auto high) {

View File

@ -45,8 +45,8 @@ namespace tint::resolver {
class ConstEval { class ConstEval {
public: public:
/// Typedef for a constant evaluation function /// Typedef for a constant evaluation function
using Function = const sem::Constant* ( using Function = const sem::Constant* (ConstEval::*)(const sem::Type* result_ty,
ConstEval::*)(const sem::Type* result_ty, utils::ConstVectorRef<const sem::Expression*>); utils::VectorRef<const sem::Expression*>);
/// The result type of a method that may raise a diagnostic error and the caller should abort /// The result type of a method that may raise a diagnostic error and the caller should abort
/// resolving. Can be one of three distinct values: /// resolving. Can be one of three distinct values:
@ -71,7 +71,7 @@ class ConstEval {
/// @param args the input arguments /// @param args the input arguments
/// @return the constructed value, or null if the value cannot be calculated /// @return the constructed value, or null if the value cannot be calculated
const sem::Constant* ArrayOrStructCtor(const sem::Type* ty, const sem::Constant* ArrayOrStructCtor(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args); utils::VectorRef<const sem::Expression*> args);
/// @param ty the target type /// @param ty the target type
/// @param expr the input expression /// @param expr the input expression
@ -100,7 +100,7 @@ class ConstEval {
/// @return the result of the swizzle, or null if the value cannot be calculated /// @return the result of the swizzle, or null if the value cannot be calculated
const sem::Constant* Swizzle(const sem::Type* ty, const sem::Constant* Swizzle(const sem::Type* ty,
const sem::Expression* vector, const sem::Expression* vector,
utils::ConstVectorRef<uint32_t> indices); utils::VectorRef<uint32_t> indices);
/// Convert the `value` to `target_type` /// Convert the `value` to `target_type`
/// @param ty the result type /// @param ty the result type
@ -117,57 +117,55 @@ class ConstEval {
/// @param ty the result type /// @param ty the result type
/// @param args the input arguments /// @param args the input arguments
/// @return the converted value, or null if the value cannot be calculated /// @return the converted value, or null if the value cannot be calculated
const sem::Constant* Conv(const sem::Type* ty, const sem::Constant* Conv(const sem::Type* ty, utils::VectorRef<const sem::Expression*> args);
utils::ConstVectorRef<const sem::Expression*> args);
/// Zero value type constructor /// Zero value type constructor
/// @param ty the result type /// @param ty the result type
/// @param args the input arguments (no arguments provided) /// @param args the input arguments (no arguments provided)
/// @return the constructed value, or null if the value cannot be calculated /// @return the constructed value, or null if the value cannot be calculated
const sem::Constant* Zero(const sem::Type* ty, const sem::Constant* Zero(const sem::Type* ty, utils::VectorRef<const sem::Expression*> args);
utils::ConstVectorRef<const sem::Expression*> args);
/// Identity value type constructor /// Identity value type constructor
/// @param ty the result type /// @param ty the result type
/// @param args the input arguments /// @param args the input arguments
/// @return the constructed value, or null if the value cannot be calculated /// @return the constructed value, or null if the value cannot be calculated
const sem::Constant* Identity(const sem::Type* ty, const sem::Constant* Identity(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args); utils::VectorRef<const sem::Expression*> args);
/// Vector splat constructor /// Vector splat constructor
/// @param ty the vector type /// @param ty the vector type
/// @param args the input arguments /// @param args the input arguments
/// @return the constructed value, or null if the value cannot be calculated /// @return the constructed value, or null if the value cannot be calculated
const sem::Constant* VecSplat(const sem::Type* ty, const sem::Constant* VecSplat(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args); utils::VectorRef<const sem::Expression*> args);
/// Vector constructor using scalars /// Vector constructor using scalars
/// @param ty the vector type /// @param ty the vector type
/// @param args the input arguments /// @param args the input arguments
/// @return the constructed value, or null if the value cannot be calculated /// @return the constructed value, or null if the value cannot be calculated
const sem::Constant* VecCtorS(const sem::Type* ty, const sem::Constant* VecCtorS(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args); utils::VectorRef<const sem::Expression*> args);
/// Vector constructor using a mix of scalars and smaller vectors /// Vector constructor using a mix of scalars and smaller vectors
/// @param ty the vector type /// @param ty the vector type
/// @param args the input arguments /// @param args the input arguments
/// @return the constructed value, or null if the value cannot be calculated /// @return the constructed value, or null if the value cannot be calculated
const sem::Constant* VecCtorM(const sem::Type* ty, const sem::Constant* VecCtorM(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args); utils::VectorRef<const sem::Expression*> args);
/// Matrix constructor using scalar values /// Matrix constructor using scalar values
/// @param ty the matrix type /// @param ty the matrix type
/// @param args the input arguments /// @param args the input arguments
/// @return the constructed value, or null if the value cannot be calculated /// @return the constructed value, or null if the value cannot be calculated
const sem::Constant* MatCtorS(const sem::Type* ty, const sem::Constant* MatCtorS(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args); utils::VectorRef<const sem::Expression*> args);
/// Matrix constructor using column vectors /// Matrix constructor using column vectors
/// @param ty the matrix type /// @param ty the matrix type
/// @param args the input arguments /// @param args the input arguments
/// @return the constructed value, or null if the value cannot be calculated /// @return the constructed value, or null if the value cannot be calculated
const sem::Constant* MatCtorV(const sem::Type* ty, const sem::Constant* MatCtorV(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args); utils::VectorRef<const sem::Expression*> args);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Operators // Operators
@ -178,14 +176,14 @@ class ConstEval {
/// @param args the input arguments /// @param args the input arguments
/// @return the result value, or null if the value cannot be calculated /// @return the result value, or null if the value cannot be calculated
const sem::Constant* OpComplement(const sem::Type* ty, const sem::Constant* OpComplement(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args); utils::VectorRef<const sem::Expression*> args);
/// Minus operator '-' /// Minus operator '-'
/// @param ty the expression type /// @param ty the expression type
/// @param args the input arguments /// @param args the input arguments
/// @return the result value, or null if the value cannot be calculated /// @return the result value, or null if the value cannot be calculated
const sem::Constant* OpMinus(const sem::Type* ty, const sem::Constant* OpMinus(const sem::Type* ty,
utils::ConstVectorRef<const sem::Expression*> args); utils::VectorRef<const sem::Expression*> args);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Builtins // Builtins
@ -195,15 +193,13 @@ class ConstEval {
/// @param ty the expression type /// @param ty the expression type
/// @param args the input arguments /// @param args the input arguments
/// @return the result value, or null if the value cannot be calculated /// @return the result value, or null if the value cannot be calculated
const sem::Constant* atan2(const sem::Type* ty, const sem::Constant* atan2(const sem::Type* ty, utils::VectorRef<const sem::Expression*> args);
utils::ConstVectorRef<const sem::Expression*> args);
/// clamp builtin /// clamp builtin
/// @param ty the expression type /// @param ty the expression type
/// @param args the input arguments /// @param args the input arguments
/// @return the result value, or null if the value cannot be calculated /// @return the result value, or null if the value cannot be calculated
const sem::Constant* clamp(const sem::Type* ty, const sem::Constant* clamp(const sem::Type* ty, utils::VectorRef<const sem::Expression*> args);
utils::ConstVectorRef<const sem::Expression*> args);
private: private:
/// Adds the given error message to the diagnostics /// Adds the given error message to the diagnostics

View File

@ -1026,14 +1026,14 @@ class Impl : public IntrinsicTable {
// Prints the list of candidates for emitting diagnostics // Prints the list of candidates for emitting diagnostics
void PrintCandidates(std::ostream& ss, void PrintCandidates(std::ostream& ss,
utils::ConstVectorRef<Candidate> candidates, utils::VectorRef<Candidate> candidates,
const char* intrinsic_name) const; const char* intrinsic_name) const;
/// Raises an error when no overload is a clear winner of overload resolution /// Raises an error when no overload is a clear winner of overload resolution
void ErrAmbiguousOverload(const char* intrinsic_name, void ErrAmbiguousOverload(const char* intrinsic_name,
utils::ConstVectorRef<const sem::Type*> args, utils::VectorRef<const sem::Type*> args,
TemplateState templates, TemplateState templates,
utils::ConstVectorRef<Candidate> candidates) const; utils::VectorRef<Candidate> candidates) const;
ProgramBuilder& builder; ProgramBuilder& builder;
Matchers matchers; Matchers matchers;
@ -1604,7 +1604,7 @@ void Impl::PrintOverload(std::ostream& ss,
} }
void Impl::PrintCandidates(std::ostream& ss, void Impl::PrintCandidates(std::ostream& ss,
utils::ConstVectorRef<Candidate> candidates, utils::VectorRef<Candidate> candidates,
const char* intrinsic_name) const { const char* intrinsic_name) const {
for (auto& candidate : candidates) { for (auto& candidate : candidates) {
ss << " "; ss << " ";
@ -1638,9 +1638,9 @@ std::string MatchState::NumName() {
} }
void Impl::ErrAmbiguousOverload(const char* intrinsic_name, void Impl::ErrAmbiguousOverload(const char* intrinsic_name,
utils::ConstVectorRef<const sem::Type*> args, utils::VectorRef<const sem::Type*> args,
TemplateState templates, TemplateState templates,
utils::ConstVectorRef<Candidate> candidates) const { utils::VectorRef<Candidate> candidates) const {
std::stringstream ss; std::stringstream ss;
ss << "ambiguous overload while attempting to match " << intrinsic_name; ss << "ambiguous overload while attempting to match " << intrinsic_name;
for (size_t i = 0; i < std::numeric_limits<size_t>::max(); i++) { for (size_t i = 0; i < std::numeric_limits<size_t>::max(); i++) {

View File

@ -1406,7 +1406,8 @@ const sem::Expression* Resolver::Materialize(const sem::Expression* expr,
return m; return m;
} }
bool Resolver::MaterializeArguments(utils::VectorRef<const sem::Expression*> args, template <size_t N>
bool Resolver::MaterializeArguments(utils::Vector<const sem::Expression*, N>& args,
const sem::CallTarget* target) { const sem::CallTarget* target) {
for (size_t i = 0, n = std::min(args.Length(), target->Parameters().Length()); i < n; i++) { for (size_t i = 0, n = std::min(args.Length(), target->Parameters().Length()); i < n; i++) {
const auto* param_ty = target->Parameters()[i]->Type(); const auto* param_ty = target->Parameters()[i]->Type();
@ -1778,9 +1779,7 @@ sem::Call* Resolver::Call(const ast::CallExpression* expr) {
// there's no need to infer element types. // there's no need to infer element types.
return ty_ctor_or_conv(ty); return ty_ctor_or_conv(ty);
}, },
[&](sem::Function* func) { [&](sem::Function* func) { return FunctionCall(expr, func, args, arg_behaviors); },
return FunctionCall(expr, func, std::move(args), arg_behaviors);
},
[&](sem::Variable* var) { [&](sem::Variable* var) {
auto name = builder_->Symbols().NameFor(var->Declaration()->symbol); auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
AddError("cannot call variable '" + name + "'", ident->source); AddError("cannot call variable '" + name + "'", ident->source);
@ -1791,7 +1790,7 @@ sem::Call* Resolver::Call(const ast::CallExpression* expr) {
auto name = builder_->Symbols().NameFor(ident->symbol); auto name = builder_->Symbols().NameFor(ident->symbol);
auto builtin_type = sem::ParseBuiltinType(name); auto builtin_type = sem::ParseBuiltinType(name);
if (builtin_type != sem::BuiltinType::kNone) { if (builtin_type != sem::BuiltinType::kNone) {
return BuiltinCall(expr, builtin_type, std::move(args)); return BuiltinCall(expr, builtin_type, args);
} }
TINT_ICE(Resolver, diagnostics_) TINT_ICE(Resolver, diagnostics_)
@ -1809,12 +1808,13 @@ sem::Call* Resolver::Call(const ast::CallExpression* expr) {
return validator_.Call(call, current_statement_) ? call : nullptr; return validator_.Call(call, current_statement_) ? call : nullptr;
} }
template <size_t N>
sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr, sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
sem::BuiltinType builtin_type, sem::BuiltinType builtin_type,
utils::VectorRef<const sem::Expression*> args) { utils::Vector<const sem::Expression*, N>& args) {
IntrinsicTable::Builtin builtin; IntrinsicTable::Builtin builtin;
{ {
auto arg_tys = utils::Transform<8>(args, [](auto* arg) { return arg->Type(); }); auto arg_tys = utils::Transform(args, [](auto* arg) { return arg->Type(); });
builtin = intrinsic_table_->Lookup(builtin_type, arg_tys, expr->source); builtin = intrinsic_table_->Lookup(builtin_type, arg_tys, expr->source);
if (!builtin.sem) { if (!builtin.sem) {
return nullptr; return nullptr;
@ -1876,9 +1876,8 @@ sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
return call; return call;
} }
void Resolver::CollectTextureSamplerPairs( void Resolver::CollectTextureSamplerPairs(const sem::Builtin* builtin,
const sem::Builtin* builtin, utils::VectorRef<const sem::Expression*> args) const {
utils::ConstVectorRef<const sem::Expression*> args) const {
// Collect a texture/sampler pair for this builtin. // Collect a texture/sampler pair for this builtin.
const auto& signature = builtin->Signature(); const auto& signature = builtin->Signature();
int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture); int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
@ -1896,9 +1895,10 @@ void Resolver::CollectTextureSamplerPairs(
} }
} }
template <size_t N>
sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr, sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr,
sem::Function* target, sem::Function* target,
utils::VectorRef<const sem::Expression*> args, utils::Vector<const sem::Expression*, N>& args,
sem::Behaviors arg_behaviors) { sem::Behaviors arg_behaviors) {
auto sym = expr->target.name->symbol; auto sym = expr->target.name->symbol;
auto name = builder_->Symbols().NameFor(sym); auto name = builder_->Symbols().NameFor(sym);
@ -1944,9 +1944,8 @@ sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr,
return call; return call;
} }
void Resolver::CollectTextureSamplerPairs( void Resolver::CollectTextureSamplerPairs(sem::Function* func,
sem::Function* func, utils::VectorRef<const sem::Expression*> args) const {
utils::ConstVectorRef<const sem::Expression*> args) const {
// Map all texture/sampler pairs from the target function to the // Map all texture/sampler pairs from the target function to the
// current function. These can only be global or parameter // current function. These can only be global or parameter
// variables. Resolve any parameter variables to the corresponding // variables. Resolve any parameter variables to the corresponding

View File

@ -193,14 +193,16 @@ class Resolver {
sem::Expression* Bitcast(const ast::BitcastExpression*); sem::Expression* Bitcast(const ast::BitcastExpression*);
sem::Call* Call(const ast::CallExpression*); sem::Call* Call(const ast::CallExpression*);
sem::Function* Function(const ast::Function*); sem::Function* Function(const ast::Function*);
template <size_t N>
sem::Call* FunctionCall(const ast::CallExpression*, sem::Call* FunctionCall(const ast::CallExpression*,
sem::Function* target, sem::Function* target,
utils::VectorRef<const sem::Expression*> args, utils::Vector<const sem::Expression*, N>& args,
sem::Behaviors arg_behaviors); sem::Behaviors arg_behaviors);
sem::Expression* Identifier(const ast::IdentifierExpression*); sem::Expression* Identifier(const ast::IdentifierExpression*);
template <size_t N>
sem::Call* BuiltinCall(const ast::CallExpression*, sem::Call* BuiltinCall(const ast::CallExpression*,
sem::BuiltinType, sem::BuiltinType,
utils::VectorRef<const sem::Expression*> args); utils::Vector<const sem::Expression*, N>& args);
sem::Expression* Literal(const ast::LiteralExpression*); sem::Expression* Literal(const ast::LiteralExpression*);
sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*); sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
sem::Expression* UnaryOp(const ast::UnaryOpExpression*); sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
@ -223,7 +225,8 @@ class Resolver {
/// Materializes all the arguments in `args` to the parameter types of `target`. /// Materializes all the arguments in `args` to the parameter types of `target`.
/// @returns true on success, false on failure. /// @returns true on success, false on failure.
bool MaterializeArguments(utils::VectorRef<const sem::Expression*> args, template <size_t N>
bool MaterializeArguments(utils::Vector<const sem::Expression*, N>& args,
const sem::CallTarget* target); const sem::CallTarget* target);
/// @returns true if an argument of an abstract numeric type, passed to a parameter of type /// @returns true if an argument of an abstract numeric type, passed to a parameter of type
@ -267,9 +270,9 @@ class Resolver {
// CollectTextureSamplerPairs() collects all the texture/sampler pairs from the target function // CollectTextureSamplerPairs() collects all the texture/sampler pairs from the target function
// / builtin, and records these on the current function by calling AddTextureSamplerPair(). // / builtin, and records these on the current function by calling AddTextureSamplerPair().
void CollectTextureSamplerPairs(sem::Function* func, void CollectTextureSamplerPairs(sem::Function* func,
utils::ConstVectorRef<const sem::Expression*> args) const; utils::VectorRef<const sem::Expression*> args) const;
void CollectTextureSamplerPairs(const sem::Builtin* builtin, void CollectTextureSamplerPairs(const sem::Builtin* builtin,
utils::ConstVectorRef<const sem::Expression*> args) const; utils::VectorRef<const sem::Expression*> args) const;
/// Resolves the WorkgroupSize for the given function, assigning it to /// Resolves the WorkgroupSize for the given function, assigning it to
/// current_function_ /// current_function_

View File

@ -272,7 +272,7 @@ const Type* Type::DeepestElementOf(const Type* ty, uint32_t* count /* = nullptr
return el_ty; return el_ty;
} }
const sem::Type* Type::Common(utils::ConstVectorRef<const Type*> types) { const sem::Type* Type::Common(utils::VectorRef<const Type*> types) {
const auto count = types.Length(); const auto count = types.Length();
if (count == 0) { if (count == 0) {
return nullptr; return nullptr;

View File

@ -161,7 +161,7 @@ class Type : public Castable<Type, Node> {
/// @returns the lowest-ranking type that all types in `types` can be implicitly converted to, /// @returns the lowest-ranking type that all types in `types` can be implicitly converted to,
/// or nullptr if there is no consistent common type across all types in `types`. /// or nullptr if there is no consistent common type across all types in `types`.
/// @see https://www.w3.org/TR/WGSL/#conversion-rank /// @see https://www.w3.org/TR/WGSL/#conversion-rank
static const sem::Type* Common(utils::ConstVectorRef<const Type*> types); static const sem::Type* Common(utils::VectorRef<const Type*> types);
protected: protected:
Type(); Type();

View File

@ -119,40 +119,6 @@ auto Transform(const VectorRef<IN>& in, TRANSFORMER&& transform)
return result; 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(ConstVectorRef<IN> 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 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(ConstVectorRef<IN> 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;
}
/// TransformN performs an element-wise transformation of a vector, transforming and returning at /// TransformN performs an element-wise transformation of a vector, transforming and returning at
/// most `n` elements. /// most `n` elements.
/// @param in the input vector. /// @param in the input vector.

View File

@ -345,72 +345,5 @@ TEST(TransformTest, TransformVectorRefDifferentType) {
} }
} }
TEST(TransformTest, ConstVectorRefEmpty) {
const Vector<int, 4> empty{};
ConstVectorRef<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, ConstVectorRefIdentity) {
const Vector<int, 4> input{1, 2, 3, 4};
ConstVectorRef<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, ConstVectorRefIdentityWithIndex) {
const Vector<int, 4> input{1, 2, 3, 4};
ConstVectorRef<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, ConstVectorRefIndex) {
const Vector<int, 4> input{10, 20, 30, 40};
ConstVectorRef<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, TransformConstVectorRefSameType) {
const Vector<int, 4> input{1, 2, 3, 4};
ConstVectorRef<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, TransformConstVectorRefDifferentType) {
const Vector<int, 4> input{1, 2, 3, 4};
ConstVectorRef<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
} // namespace tint::utils } // namespace tint::utils

View File

@ -32,12 +32,18 @@ namespace tint::utils {
template <typename> template <typename>
class VectorRef; class VectorRef;
template <typename> template <typename>
class ConstVectorRef; class VectorRef;
} // namespace tint::utils } // namespace tint::utils
namespace tint::utils { namespace tint::utils {
/// A type used to indicate an empty array.
struct EmptyType {};
/// An instance of the EmptyType.
static constexpr EmptyType Empty;
/// A slice represents a contigious array of elements of type T. /// A slice represents a contigious array of elements of type T.
template <typename T> template <typename T>
struct Slice { struct Slice {
@ -164,6 +170,9 @@ class Vector {
/// Constructor /// Constructor
Vector() = default; Vector() = default;
/// Constructor
Vector(EmptyType) {} // NOLINT(runtime/explicit)
/// Constructor /// Constructor
/// @param elements the elements to place into the vector /// @param elements the elements to place into the vector
Vector(std::initializer_list<T> elements) { Vector(std::initializer_list<T> elements) {
@ -217,10 +226,7 @@ class Vector {
/// Copy constructor from an immutable vector reference /// Copy constructor from an immutable vector reference
/// @param other the vector reference to copy /// @param other the vector reference to copy
explicit Vector(const ConstVectorRef<T>& other) { Copy(other.slice_); } explicit Vector(const VectorRef<T>& other) { Copy(other.slice_); }
/// Move constructor from an immutable vector reference (invalid)
Vector(ConstVectorRef<T>&&) = delete; // NOLINT(runtime/explicit)
/// Destructor /// Destructor
~Vector() { ClearAndFree(); } ~Vector() { ClearAndFree(); }
@ -263,6 +269,26 @@ class Vector {
return *this; 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;
}
/// Index operator /// Index operator
/// @param i the element index. Must be less than `len`. /// @param i the element index. Must be less than `len`.
/// @returns a reference to the i'th element. /// @returns a reference to the i'th element.
@ -367,7 +393,12 @@ class Vector {
/// Removes and returns the last element from the vector. /// Removes and returns the last element from the vector.
/// @returns the popped element /// @returns the popped element
T Pop() { return std::move(impl_.slice.data[--impl_.slice.len]); } T Pop() {
auto& el = impl_.slice.data[--impl_.slice.len];
auto val = std::move(el);
el.~T();
return val;
}
/// @returns true if the vector is empty. /// @returns true if the vector is empty.
bool IsEmpty() const { return impl_.slice.len == 0; } bool IsEmpty() const { return impl_.slice.len == 0; }
@ -419,7 +450,7 @@ class Vector {
/// Friend class /// Friend class
template <typename> template <typename>
friend class ConstVectorRef; friend class VectorRef;
/// The slice type used by this vector /// The slice type used by this vector
using Slice = utils::Slice<T>; using Slice = utils::Slice<T>;
@ -573,11 +604,10 @@ Vector(Ts...) -> Vector<VectorCommonType<Ts...>, sizeof...(Ts)>;
/// VectorRef is a weak reference to a Vector, used to pass vectors as parameters, avoiding copies /// VectorRef is a weak reference to a Vector, used to pass vectors as parameters, avoiding copies
/// between the caller and the callee. VectorRef can accept a Vector of any 'N' value, decoupling /// between the caller and the callee. VectorRef can accept a Vector of any 'N' value, decoupling
/// the caller's vector internal size from the callee's vector size. /// 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
/// A VectorRef tracks the usage of moves either side of the call. If at the call site, a Vector /// parameter, and within the callee, the VectorRef parameter is moved to a Vector, then the Vector
/// argument is moved to a VectorRef parameter, and within the callee, the VectorRef parameter is /// heap allocation will be moved. For example:
/// moved to a Vector, then the Vector heap allocation will be moved. For example:
/// ///
/// ``` /// ```
/// void func_a() { /// void func_a() {
@ -596,12 +626,30 @@ class VectorRef {
/// The slice type used by this vector reference /// The slice type used by this vector reference
using Slice = utils::Slice<T>; using Slice = utils::Slice<T>;
/// @returns an empty slice.
static Slice& EmptySlice() {
static Slice empty;
return empty;
}
public: public:
/// Constructor - empty reference
VectorRef() : slice_(EmptySlice()) {}
/// Constructor
VectorRef(EmptyType) : slice_(EmptySlice()) {} // NOLINT(runtime/explicit)
/// Constructor from a Vector /// Constructor from a Vector
/// @param vector the vector to create a reference of /// @param vector the vector to create a reference of
template <size_t N> template <size_t N>
VectorRef(Vector<T, N>& vector) // NOLINT(runtime/explicit) VectorRef(Vector<T, N>& vector) // NOLINT(runtime/explicit)
: slice_(vector.impl_.slice), can_move_(false) {} : 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<Slice&>(vector.impl_.slice)) {}
/// Constructor from a moved Vector /// Constructor from a moved Vector
/// @param vector the vector being moved /// @param vector the vector being moved
@ -611,7 +659,7 @@ class VectorRef {
/// Copy constructor /// Copy constructor
/// @param other the vector reference /// @param other the vector reference
VectorRef(const VectorRef& other) : slice_(other.slice_), can_move_(false) {} VectorRef(const VectorRef& other) : slice_(other.slice_) {}
/// Move constructor /// Move constructor
/// @param other the vector reference /// @param other the vector reference
@ -621,7 +669,7 @@ class VectorRef {
/// @param other the other vector reference /// @param other the other vector reference
template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>> template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
VectorRef(const VectorRef<U>& other) // NOLINT(runtime/explicit) VectorRef(const VectorRef<U>& other) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&other.slice_)), can_move_(false) {} : slice_(*ReinterpretSlice<T>(&other.slice_)) {}
/// Move constructor with covariance / const conversion /// Move constructor with covariance / const conversion
/// @param other the vector reference /// @param other the vector reference
@ -634,7 +682,7 @@ class VectorRef {
/// @see CanReinterpretSlice for rules about conversion /// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>> template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
VectorRef(Vector<U, N>& vector) // NOLINT(runtime/explicit) VectorRef(Vector<U, N>& vector) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&vector.impl_.slice)), can_move_(false) {} : slice_(*ReinterpretSlice<T>(&vector.impl_.slice)) {}
/// Constructor from a moved Vector with covariance / const conversion /// Constructor from a moved Vector with covariance / const conversion
/// @param vector the vector to create a reference of /// @param vector the vector to create a reference of
@ -643,11 +691,6 @@ class VectorRef {
VectorRef(Vector<U, N>&& vector) // NOLINT(runtime/explicit) VectorRef(Vector<U, N>&& vector) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&vector.impl_.slice)), can_move_(vector.impl_.CanMove()) {} : slice_(*ReinterpretSlice<T>(&vector.impl_.slice)), 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.
T& operator[](size_t i) { return slice_[i]; }
/// Index operator /// Index operator
/// @param i the element index. Must be less than `len`. /// @param i the element index. Must be less than `len`.
/// @returns a reference to the i'th element. /// @returns a reference to the i'th element.
@ -710,7 +753,7 @@ class VectorRef {
/// Friend class /// Friend class
template <typename> template <typename>
friend class ConstVectorRef; friend class VectorRef;
/// The slice of the vector being referenced. /// The slice of the vector being referenced.
Slice& slice_; Slice& slice_;
@ -718,99 +761,6 @@ class VectorRef {
bool can_move_ = false; bool can_move_ = false;
}; };
/// ConstVectorRef is a weak, immutable reference to a Vector, used to pass vectors as parameters,
/// avoiding copies between the caller and the callee. VectorRef can accept a Vector of any 'N'
/// value, decoupling the caller's vector internal size from the callee's vector size.
template <typename T>
class ConstVectorRef {
/// The slice type used by this vector reference
using Slice = utils::Slice<T>;
public:
/// Constructor from a Vector.
/// @param vector the vector reference
template <size_t N>
ConstVectorRef(const Vector<T, N>& vector) // NOLINT(runtime/explicit)
: slice_(vector.impl_.slice) {}
/// Copy constructor
/// @param other the vector reference
ConstVectorRef(const ConstVectorRef& other) = default;
/// Conversion constructor to convert from a non-const to const vector reference
/// @param other the vector reference
ConstVectorRef(const VectorRef<T>& other) : slice_(other.slice_) {} // NOLINT(runtime/explicit)
/// Move constructor. Deleted as this won't move anything.
ConstVectorRef(ConstVectorRef&&) = delete;
/// 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<T, U>>>
ConstVectorRef(const Vector<U, N>& vector) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&vector.impl_.slice)) {}
/// Constructor from a VectorRef with covariance / const conversion
/// @param other the vector reference
/// @see CanReinterpretSlice for rules about conversion
template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
ConstVectorRef(const VectorRef<U>& other) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&other.slice_)) {}
/// Constructor from a ConstVectorRef with covariance / const conversion
/// @param other the vector reference
/// @see CanReinterpretSlice for rules about conversion
template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
ConstVectorRef(const ConstVectorRef<U>& other) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&other.slice_)) {}
/// 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; }
/// @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 ConstVectorRef;
/// The slice of the vector being referenced.
const Slice& slice_;
};
/// Helper for converting a Vector to a std::vector. /// Helper for converting a Vector to a std::vector.
/// @note This helper exists to help code migration. Avoid if possible. /// @note This helper exists to help code migration. Avoid if possible.
template <typename T, size_t N> template <typename T, size_t N>

View File

@ -105,12 +105,24 @@ TEST(TintVectorTest, SmallArray_Empty) {
EXPECT_EQ(vec.Capacity(), 2u); EXPECT_EQ(vec.Capacity(), 2u);
} }
TEST(TintVectorTest, Empty_NoSmallArray) { TEST(TintVectorTest, NoSmallArray) {
Vector<int, 0> vec; Vector<int, 0> vec;
EXPECT_EQ(vec.Length(), 0u); EXPECT_EQ(vec.Length(), 0u);
EXPECT_EQ(vec.Capacity(), 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) { TEST(TintVectorTest, InitializerList_NoSpill) {
Vector<std::string, 2> vec{"one", "two"}; Vector<std::string, 2> vec{"one", "two"};
EXPECT_EQ(vec.Length(), 2u); EXPECT_EQ(vec.Length(), 2u);
@ -800,7 +812,7 @@ TEST(TintVectorTest, RepeatMoveAssign_NoSpill) {
EXPECT_TRUE(AllInternallyHeld(vec)); EXPECT_TRUE(AllInternallyHeld(vec));
} }
TEST(TintVectorTest, DoubleMoveAssign_WithSpill) { TEST(TintVectorTest, RepeatMoveAssign_WithSpill) {
Vector<std::string, 1> vec_a{"hello", "world"}; Vector<std::string, 1> vec_a{"hello", "world"};
Vector<std::string, 1> vec_b{"Ciao", "mondo"}; Vector<std::string, 1> vec_b{"Ciao", "mondo"};
Vector<std::string, 1> vec_c{"bonjour", "le", "monde"}; Vector<std::string, 1> vec_c{"bonjour", "le", "monde"};
@ -816,6 +828,288 @@ TEST(TintVectorTest, DoubleMoveAssign_WithSpill) {
EXPECT_TRUE(AllExternallyHeld(vec)); 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, Index) { TEST(TintVectorTest, Index) {
Vector<std::string, 2> vec{"hello", "world"}; Vector<std::string, 2> vec{"hello", "world"};
static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec[0])>>); static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec[0])>>);
@ -1684,7 +1978,7 @@ TEST(TintVectorRefTest, MoveVector_UpcastAndAddConst) {
TEST(TintVectorRefTest, Index) { TEST(TintVectorRefTest, Index) {
Vector<std::string, 2> vec{"one", "two"}; Vector<std::string, 2> vec{"one", "two"};
VectorRef<std::string> vec_ref(vec); VectorRef<std::string> vec_ref(vec);
static_assert(!std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>); static_assert(std::is_const_v<std::remove_reference_t<decltype(vec_ref[0])>>);
EXPECT_EQ(vec_ref[0], "one"); EXPECT_EQ(vec_ref[0], "one");
EXPECT_EQ(vec_ref[1], "two"); EXPECT_EQ(vec_ref[1], "two");
} }
@ -1755,206 +2049,6 @@ TEST(TintVectorRefTest, ConstBeginEnd) {
EXPECT_EQ(vec_ref.end(), &vec[0] + 3); EXPECT_EQ(vec_ref.end(), &vec[0] + 3);
} }
////////////////////////////////////////////////////////////////////////////////
// TintVectorConstRefTest
////////////////////////////////////////////////////////////////////////////////
TEST(TintVectorConstRefTest, CopyVectorConstRef) {
Vector<std::string, 1> vec_a{"one", "two"};
ConstVectorRef<std::string> vec_ref_a(vec_a);
ConstVectorRef<std::string> vec_ref_b(vec_ref_a);
Vector<std::string, 2> vec_b(vec_ref_b);
EXPECT_EQ(vec_b[0], "one");
EXPECT_EQ(vec_b[1], "two");
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, CopyVectorConstRef_Upcast) {
C2a c2a;
C2b c2b;
Vector<C1*, 1> vec_a{&c2a, &c2b};
ConstVectorRef<C1*> vec_ref_a(vec_a);
ConstVectorRef<C0*> vec_ref_b(vec_ref_a); // Up-cast
Vector<C0*, 2> vec_b(vec_ref_b);
EXPECT_EQ(vec_b[0], &c2a);
EXPECT_EQ(vec_b[1], &c2b);
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, CopyVectorConstRef_AddConst) {
C2a c2a;
C2b c2b;
Vector<C1*, 1> vec_a{&c2a, &c2b};
ConstVectorRef<C1*> vec_ref_a(vec_a);
ConstVectorRef<const C1*> vec_ref_b(vec_ref_a); // Up-cast
Vector<const C1*, 2> vec_b(vec_ref_b);
EXPECT_EQ(vec_b[0], &c2a);
EXPECT_EQ(vec_b[1], &c2b);
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, CopyVectorConstRef_UpcastAndAddConst) {
C2a c2a;
C2b c2b;
Vector<C1*, 1> vec_a{&c2a, &c2b};
ConstVectorRef<C1*> vec_ref_a(vec_a);
ConstVectorRef<const C0*> vec_ref_b(vec_ref_a); // Up-cast
Vector<const C0*, 2> vec_b(vec_ref_b);
EXPECT_EQ(vec_b[0], &c2a);
EXPECT_EQ(vec_b[1], &c2b);
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, CopyVector) {
Vector<std::string, 1> vec_a{"one", "two"};
ConstVectorRef<std::string> vec_ref(vec_a);
Vector<std::string, 2> vec_b(vec_ref);
EXPECT_EQ(vec_b[0], "one");
EXPECT_EQ(vec_b[1], "two");
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, CopyVector_Upcast) {
C2a c2a;
C2b c2b;
Vector<C1*, 1> vec_a{&c2a, &c2b};
ConstVectorRef<C0*> vec_ref(vec_a);
EXPECT_EQ(vec_ref[0], &c2a);
EXPECT_EQ(vec_ref[1], &c2b);
Vector<C0*, 2> vec_b(vec_ref);
EXPECT_EQ(vec_b[0], &c2a);
EXPECT_EQ(vec_b[1], &c2b);
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, CopyVector_AddConst) {
C2a c2a;
C2b c2b;
Vector<C1*, 1> vec_a{&c2a, &c2b};
ConstVectorRef<const C1*> vec_ref(vec_a);
EXPECT_EQ(vec_ref[0], &c2a);
EXPECT_EQ(vec_ref[1], &c2b);
Vector<const C1*, 2> vec_b(vec_ref);
EXPECT_EQ(vec_b[0], &c2a);
EXPECT_EQ(vec_b[1], &c2b);
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, CopyVectorRef_Upcast) {
C2a c2a;
C2b c2b;
Vector<C1*, 1> vec_a{&c2a, &c2b};
VectorRef<C1*> vec_ref_a(vec_a);
ConstVectorRef<C0*> vec_ref_b(vec_ref_a);
EXPECT_EQ(vec_ref_b[0], &c2a);
EXPECT_EQ(vec_ref_b[1], &c2b);
Vector<C0*, 2> vec_b(vec_ref_b);
EXPECT_EQ(vec_b[0], &c2a);
EXPECT_EQ(vec_b[1], &c2b);
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, CopyVectorRef_AddConst) {
C2a c2a;
C2b c2b;
Vector<C1*, 1> vec_a{&c2a, &c2b};
VectorRef<C1*> vec_ref_a(vec_a);
ConstVectorRef<const C1*> vec_ref_b(vec_ref_a);
EXPECT_EQ(vec_ref_b[0], &c2a);
EXPECT_EQ(vec_ref_b[1], &c2b);
Vector<const C1*, 2> vec_b(vec_ref_b);
EXPECT_EQ(vec_b[0], &c2a);
EXPECT_EQ(vec_b[1], &c2b);
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, CopyVectorRef_UpcastAndAddConst) {
C2a c2a;
C2b c2b;
Vector<C1*, 1> vec_a{&c2a, &c2b};
VectorRef<C1*> vec_ref_a(vec_a);
ConstVectorRef<const C0*> vec_ref_b(vec_ref_a);
EXPECT_EQ(vec_ref_b[0], &c2a);
EXPECT_EQ(vec_ref_b[1], &c2b);
Vector<const C0*, 2> vec_b(vec_ref_b);
EXPECT_EQ(vec_b[0], &c2a);
EXPECT_EQ(vec_b[1], &c2b);
EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copied, not moved
}
TEST(TintVectorConstRefTest, Index) {
Vector<std::string, 2> vec{"one", "two"};
ConstVectorRef<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(TintVectorConstRefTest, ConstIndex) {
Vector<std::string, 2> vec{"one", "two"};
const ConstVectorRef<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(TintVectorConstRefTest, Length) {
Vector<std::string, 2> vec{"one", "two", "three"};
ConstVectorRef<std::string> vec_ref(vec);
EXPECT_EQ(vec_ref.Length(), 3u);
}
TEST(TintVectorConstRefTest, Capacity) {
Vector<std::string, 5> vec{"one", "two", "three"};
ConstVectorRef<std::string> vec_ref(vec);
EXPECT_EQ(vec_ref.Capacity(), 5u);
}
TEST(TintVectorConstRefTest, IsEmpty) {
Vector<std::string, 1> vec;
ConstVectorRef<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(TintVectorConstRefTest, FrontBack) {
Vector<std::string, 3> vec{"front", "mid", "back"};
ConstVectorRef<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(TintVectorConstRefTest, ConstFrontBack) {
Vector<std::string, 3> vec{"front", "mid", "back"};
const ConstVectorRef<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(TintVectorConstRefTest, BeginEnd) {
Vector<std::string, 3> vec{"front", "mid", "back"};
ConstVectorRef<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(TintVectorConstRefTest, ConstBeginEnd) {
Vector<std::string, 3> vec{"front", "mid", "back"};
const ConstVectorRef<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);
}
} // namespace } // namespace
} // namespace tint::utils } // namespace tint::utils