IntrinsicTable: Fix a number of TODOs

And add tests for IntrinsicTable.

Drop all the type unwrapping - be precise:
* Display the actual argument types in the signature mismatch message
* Only dereference pointer arguments if the parameter does not expect a pointer

Correctly match access control on storage types

Note that I was mistaken in tint:486 - the TypeDeterminer is resolving identifiers to variables correctly as pointer types. The confustion here was probably due to all the UnwrapAll() calls, which have now all gone.

Fixed: tint:486
Change-Id: I239eabd1fedfc082566c4af616ccfc58786cae25
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/41280
Commit-Queue: Ben Clayton <bclayton@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
Ben Clayton 2021-02-10 21:34:25 +00:00 committed by Commit Bot service account
parent 2101c35f3b
commit faca02d438
7 changed files with 650 additions and 53 deletions

View File

@ -835,6 +835,7 @@ source_set("tint_unittests_core_src") {
"src/diagnostic/formatter_test.cc",
"src/diagnostic/printer_test.cc",
"src/inspector/inspector_test.cc",
"src/intrinsic_table_test.cc",
"src/namer_test.cc",
"src/program_builder_test.cc",
"src/program_test.cc",

View File

@ -465,6 +465,7 @@ if(${TINT_BUILD_TESTS})
diagnostic/formatter_test.cc
diagnostic/printer_test.cc
inspector/inspector_test.cc
intrinsic_table_test.cc
namer_test.cc
program_test.cc
scope_stack_test.cc

View File

@ -23,6 +23,7 @@
#include "src/block_allocator.h"
#include "src/program_builder.h"
#include "src/semantic/intrinsic.h"
#include "src/type/access_control_type.h"
#include "src/type/depth_texture_type.h"
#include "src/type/f32_type.h"
#include "src/type/multisampled_texture_type.h"
@ -91,15 +92,33 @@ class Matcher {
virtual ~Matcher() = default;
/// Checks whether the given argument type matches.
/// Aliases are automatically unwrapped before matching.
/// Match may add to, or compare against the open types and numbers in state.
/// @returns true if the argument type is as expected.
virtual bool Match(MatchState& state, type::Type* argument_type) const = 0;
bool Match(MatchState& state, type::Type* argument_type) const {
auto* unwrapped = argument_type;
while (auto* alias = unwrapped->As<type::Alias>()) {
unwrapped = alias->type();
}
return MatchUnwrapped(state, unwrapped);
}
/// @return true if the matcher is expecting a pointer. If this method returns
/// false and the argument is a pointer type, then the argument should be
/// dereferenced before calling.
virtual bool ExpectsPointer() const { return false; }
/// @return a string representation of the matcher. Used for printing error
/// messages when no overload is found.
virtual std::string str() const = 0;
protected:
/// Checks whether the given alias-unwrapped argument type matches.
/// Match may add to, or compare against the open types and numbers in state.
/// @returns true if the argument type is as expected.
virtual bool MatchUnwrapped(MatchState& state,
type::Type* argument_type) const = 0;
/// Checks `state.open_type` to see if the OpenType `t` is equal to the type
/// `ty`. If `state.open_type` does not contain an entry for `t`, then `ty`
/// is added and returns true.
@ -154,7 +173,7 @@ class OpenTypeBuilder : public Builder {
public:
explicit OpenTypeBuilder(OpenType open_type) : open_type_(open_type) {}
bool Match(MatchState& state, type::Type* ty) const override {
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
return MatchOpenType(state, open_type_, ty);
}
@ -171,7 +190,7 @@ class OpenTypeBuilder : public Builder {
/// VoidBuilder is a Matcher / Builder for void types.
class VoidBuilder : public Builder {
public:
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
return ty->Is<type::Void>();
}
type::Type* Build(BuildState& state) const override {
@ -183,7 +202,7 @@ class VoidBuilder : public Builder {
/// BoolBuilder is a Matcher / Builder for boolean types.
class BoolBuilder : public Builder {
public:
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
return ty->Is<type::Bool>();
}
type::Type* Build(BuildState& state) const override {
@ -195,7 +214,7 @@ class BoolBuilder : public Builder {
/// F32Builder is a Matcher / Builder for f32 types.
class F32Builder : public Builder {
public:
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
return ty->Is<type::F32>();
}
type::Type* Build(BuildState& state) const override {
@ -207,7 +226,7 @@ class F32Builder : public Builder {
/// U32Builder is a Matcher / Builder for u32 types.
class U32Builder : public Builder {
public:
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
return ty->Is<type::U32>();
}
type::Type* Build(BuildState& state) const override {
@ -219,7 +238,7 @@ class U32Builder : public Builder {
/// I32Builder is a Matcher / Builder for i32 types.
class I32Builder : public Builder {
public:
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
return ty->Is<type::I32>();
}
type::Type* Build(BuildState& state) const override {
@ -231,7 +250,7 @@ class I32Builder : public Builder {
/// IU32Matcher is a Matcher for i32 or u32 types.
class IU32Matcher : public Matcher {
public:
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
return ty->Is<type::I32>() || ty->Is<type::U32>();
}
std::string str() const override { return "i32 or u32"; }
@ -240,7 +259,7 @@ class IU32Matcher : public Matcher {
/// FIU32Matcher is a Matcher for f32, i32 or u32 types.
class FIU32Matcher : public Matcher {
public:
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
return ty->Is<type::F32>() || ty->Is<type::I32>() || ty->Is<type::U32>();
}
std::string str() const override { return "f32, i32 or u32"; }
@ -249,7 +268,7 @@ class FIU32Matcher : public Matcher {
/// ScalarMatcher is a Matcher for f32, i32, u32 or boolean types.
class ScalarMatcher : public Matcher {
public:
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
return ty->is_scalar();
}
std::string str() const override { return "scalar"; }
@ -262,8 +281,8 @@ class OpenSizeVecBuilder : public Builder {
OpenSizeVecBuilder(OpenNumber size, Builder* element_builder)
: size_(size), element_builder_(element_builder) {}
bool Match(MatchState& state, type::Type* ty) const override {
if (auto* vec = ty->UnwrapAll()->As<type::Vector>()) {
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
if (auto* vec = ty->As<type::Vector>()) {
if (!MatchOpenNumber(state, size_, vec->size())) {
return false;
}
@ -294,8 +313,8 @@ class VecBuilder : public Builder {
VecBuilder(uint32_t size, Builder* element_builder)
: size_(size), element_builder_(element_builder) {}
bool Match(MatchState& state, type::Type* ty) const override {
if (auto* vec = ty->UnwrapAll()->As<type::Vector>()) {
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
if (auto* vec = ty->As<type::Vector>()) {
if (vec->size() == size_) {
return element_builder_->Match(state, vec->type());
}
@ -326,8 +345,8 @@ class OpenSizeMatBuilder : public Builder {
Builder* element_builder)
: columns_(columns), rows_(rows), element_builder_(element_builder) {}
bool Match(MatchState& state, type::Type* ty) const override {
if (auto* mat = ty->UnwrapAll()->As<type::Matrix>()) {
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
if (auto* mat = ty->As<type::Matrix>()) {
if (!MatchOpenNumber(state, columns_, mat->columns())) {
return false;
}
@ -347,7 +366,7 @@ class OpenSizeMatBuilder : public Builder {
}
std::string str() const override {
return "max" + std::string(tint::str(columns_)) + "x" +
return "mat" + std::string(tint::str(columns_)) + "x" +
std::string(tint::str(rows_)) + "<" + element_builder_->str() + ">";
}
@ -363,15 +382,11 @@ class PtrBuilder : public Builder {
explicit PtrBuilder(Builder* element_builder)
: element_builder_(element_builder) {}
bool Match(MatchState& state, type::Type* ty) const override {
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
if (auto* ptr = ty->As<type::Pointer>()) {
return element_builder_->Match(state, ptr->type());
}
// TODO(bclayton): https://crbug.com/tint/486
// TypeDeterminer currently folds away the pointers on expressions.
// We'll need to fix this to ensure that pointer parameters are not fed
// non-pointer arguments, but for now just accept them.
return element_builder_->Match(state, ty);
return false;
}
type::Type* Build(BuildState& state) const override {
@ -379,6 +394,8 @@ class PtrBuilder : public Builder {
return state.ty_mgr.Get<type::Pointer>(el, ast::StorageClass::kNone);
}
bool ExpectsPointer() const override { return true; }
std::string str() const override {
return "ptr<" + element_builder_->str() + ">";
}
@ -393,7 +410,7 @@ class ArrayBuilder : public Builder {
explicit ArrayBuilder(Builder* element_builder)
: element_builder_(element_builder) {}
bool Match(MatchState& state, type::Type* ty) const override {
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
if (auto* arr = ty->As<type::Array>()) {
if (arr->size() == 0) {
return element_builder_->Match(state, arr->type());
@ -422,7 +439,7 @@ class SampledTextureBuilder : public Builder {
Builder* type_builder)
: dimensions_(dimensions), type_builder_(type_builder) {}
bool Match(MatchState& state, type::Type* ty) const override {
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
if (auto* tex = ty->As<type::SampledTexture>()) {
if (tex->dim() == dimensions_) {
return type_builder_->Match(state, tex->type());
@ -455,7 +472,7 @@ class MultisampledTextureBuilder : public Builder {
Builder* type_builder)
: dimensions_(dimensions), type_builder_(type_builder) {}
bool Match(MatchState& state, type::Type* ty) const override {
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
if (auto* tex = ty->As<type::MultisampledTexture>()) {
if (tex->dim() == dimensions_) {
return type_builder_->Match(state, tex->type());
@ -487,7 +504,7 @@ class DepthTextureBuilder : public Builder {
explicit DepthTextureBuilder(type::TextureDimension dimensions)
: dimensions_(dimensions) {}
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
if (auto* tex = ty->As<type::DepthTexture>()) {
return tex->dim() == dimensions_;
}
@ -520,7 +537,15 @@ class StorageTextureBuilder : public Builder {
texel_format_(texel_format),
channel_format_(channel_format) {}
bool Match(MatchState& state, type::Type* ty) const override {
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
if (auto* ac = ty->As<type::AccessControl>()) {
// If we have an storage texture argument that's got an access control
// type wrapped around it, accept it. Signatures that don't include an
// access control imply any access. Example:
// textureDimensions(t : texture_storage_1d<F>) -> i32
ty = ac->type();
}
if (auto* tex = ty->As<type::StorageTexture>()) {
if (MatchOpenNumber(state, texel_format_,
static_cast<uint32_t>(tex->image_format()))) {
@ -557,7 +582,7 @@ class SamplerBuilder : public Builder {
public:
explicit SamplerBuilder(type::SamplerKind kind) : kind_(kind) {}
bool Match(MatchState&, type::Type* ty) const override {
bool MatchUnwrapped(MatchState&, type::Type* ty) const override {
if (auto* sampler = ty->As<type::Sampler>()) {
return sampler->kind() == kind_;
}
@ -582,6 +607,38 @@ class SamplerBuilder : public Builder {
type::SamplerKind const kind_;
};
/// AccessControlBuilder is a Matcher / Builder for AccessControl types
class AccessControlBuilder : public Builder {
public:
explicit AccessControlBuilder(ast::AccessControl access_control,
Builder* type)
: access_control_(access_control), type_(type) {}
bool MatchUnwrapped(MatchState& state, type::Type* ty) const override {
if (auto* ac = ty->As<type::AccessControl>()) {
if (ac->access_control() == access_control_) {
return type_->Match(state, ty);
}
}
return false;
}
type::Type* Build(BuildState& state) const override {
auto* ty = type_->Build(state);
return state.ty_mgr.Get<type::AccessControl>(access_control_, ty);
}
std::string str() const override {
std::stringstream ss;
ss << "[[access(" << access_control_ << ")]] " << type_->str();
return ss.str();
}
private:
ast::AccessControl const access_control_;
Builder* const type_;
};
/// Impl is the private implementation of the IntrinsicTable interface.
class Impl : public IntrinsicTable {
public:
@ -717,6 +774,12 @@ class Impl : public IntrinsicTable {
return matcher_allocator_.Create<SamplerBuilder>(kind);
}
/// @returns a Matcher / Builder that matches an access control type
Builder* access_control(ast::AccessControl access_control, Builder* type) {
return matcher_allocator_.Create<AccessControlBuilder>(access_control,
type);
}
/// Registers an overload with the given intrinsic type, return type Matcher /
/// Builder, and parameter Matcher / Builders.
/// This overload of Register does not constrain any OpenTypes.
@ -1044,6 +1107,26 @@ Impl::Impl() {
storage_texture(Dim::k2dArray, OpenNumber::F, OpenType::T);
auto* tex_storage_3d_FT =
storage_texture(Dim::k3d, OpenNumber::F, OpenType::T);
auto* tex_storage_ro_1d_FT =
access_control(ast::AccessControl::kReadOnly, tex_storage_1d_FT);
auto* tex_storage_ro_1d_array_FT =
access_control(ast::AccessControl::kReadOnly, tex_storage_1d_array_FT);
auto* tex_storage_ro_2d_FT =
access_control(ast::AccessControl::kReadOnly, tex_storage_2d_FT);
auto* tex_storage_ro_2d_array_FT =
access_control(ast::AccessControl::kReadOnly, tex_storage_2d_array_FT);
auto* tex_storage_ro_3d_FT =
access_control(ast::AccessControl::kReadOnly, tex_storage_3d_FT);
auto* tex_storage_wo_1d_FT =
access_control(ast::AccessControl::kWriteOnly, tex_storage_1d_FT);
auto* tex_storage_wo_1d_array_FT =
access_control(ast::AccessControl::kWriteOnly, tex_storage_1d_array_FT);
auto* tex_storage_wo_2d_FT =
access_control(ast::AccessControl::kWriteOnly, tex_storage_2d_FT);
auto* tex_storage_wo_2d_array_FT =
access_control(ast::AccessControl::kWriteOnly, tex_storage_2d_array_FT);
auto* tex_storage_wo_3d_FT =
access_control(ast::AccessControl::kWriteOnly, tex_storage_3d_FT);
auto* sampler = this->sampler(type::SamplerKind::kSampler);
auto* sampler_comparison =
this->sampler(type::SamplerKind::kComparisonSampler);
@ -1170,14 +1253,12 @@ Impl::Impl() {
Register(I::kTextureSampleLevel, f32, {{t, tex_depth_cube}, {s, sampler}, {coords, vec3_f32}, {level, i32}, }); // NOLINT
Register(I::kTextureSampleLevel, f32, {{t, tex_depth_cube_array},{s, sampler}, {coords, vec3_f32}, {array_index, i32}, {level, i32}, }); // NOLINT
// TODO(bclayton): Check for [[access(write)]]
Register(I::kTextureStore, void_, {{t, tex_storage_1d_FT}, {coords, i32}, {value, vec4_T}, }); // NOLINT
Register(I::kTextureStore, void_, {{t, tex_storage_1d_array_FT},{coords, i32}, {array_index, i32}, {value, vec4_T}, }); // NOLINT
Register(I::kTextureStore, void_, {{t, tex_storage_2d_FT}, {coords, vec2_i32}, {value, vec4_T}, }); // NOLINT
Register(I::kTextureStore, void_, {{t, tex_storage_2d_array_FT},{coords, vec2_i32}, {array_index, i32}, {value, vec4_T}, }); // NOLINT
Register(I::kTextureStore, void_, {{t, tex_storage_3d_FT}, {coords, vec3_i32}, {value, vec4_T}, }); // NOLINT
Register(I::kTextureStore, void_, {{t, tex_storage_wo_1d_FT}, {coords, i32}, {value, vec4_T}, }); // NOLINT
Register(I::kTextureStore, void_, {{t, tex_storage_wo_1d_array_FT},{coords, i32}, {array_index, i32}, {value, vec4_T}, }); // NOLINT
Register(I::kTextureStore, void_, {{t, tex_storage_wo_2d_FT}, {coords, vec2_i32}, {value, vec4_T}, }); // NOLINT
Register(I::kTextureStore, void_, {{t, tex_storage_wo_2d_array_FT},{coords, vec2_i32}, {array_index, i32}, {value, vec4_T}, }); // NOLINT
Register(I::kTextureStore, void_, {{t, tex_storage_wo_3d_FT}, {coords, vec3_i32}, {value, vec4_T}, }); // NOLINT
// TODO(bclayton): Check for [[access(read)]]
Register(I::kTextureLoad, vec4_T, {{t, tex_2d_T}, {coords, vec2_i32}, {level, i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_2d_array_T}, {coords, vec2_i32}, {array_index, i32}, {level, i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_3d_T}, {coords, vec3_i32}, {level, i32}, }); // NOLINT
@ -1185,11 +1266,11 @@ Impl::Impl() {
Register(I::kTextureLoad, vec4_T, {{t, tex_ms_2d_array_T}, {coords, vec2_i32}, {array_index, i32}, {sample_index, i32}, }); // NOLINT
Register(I::kTextureLoad, f32, {{t, tex_depth_2d}, {coords, vec2_i32}, {level, i32}, }); // NOLINT
Register(I::kTextureLoad, f32, {{t, tex_depth_2d_array}, {coords, vec2_i32}, {array_index, i32}, {level, i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_1d_FT}, {coords, i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_1d_array_FT},{coords, i32}, {array_index, i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_2d_FT}, {coords, vec2_i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_2d_array_FT},{coords, vec2_i32}, {array_index, i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_3d_FT}, {coords, vec3_i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_ro_1d_FT}, {coords, i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_ro_1d_array_FT},{coords, i32}, {array_index, i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_ro_2d_FT}, {coords, vec2_i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_ro_2d_array_FT},{coords, vec2_i32}, {array_index, i32}, }); // NOLINT
Register(I::kTextureLoad, vec4_T, {{t, tex_storage_ro_3d_FT}, {coords, vec3_i32}, }); // NOLINT
// TODO(bclayton): Update the rest of tint to reflect the spec changes made in
// https://github.com/gpuweb/gpuweb/pull/1301:
@ -1287,7 +1368,7 @@ IntrinsicTable::Result Impl::Lookup(
ss << ", ";
}
first = false;
ss << arg->UnwrapAll()->FriendlyName(builder.Symbols());
ss << arg->FriendlyName(builder.Symbols());
}
}
ss << ")" << std::endl;
@ -1327,13 +1408,21 @@ semantic::Intrinsic* Impl::Overload::Match(ProgramBuilder& builder,
auto count = std::min(parameters.size(), args.size());
for (size_t i = 0; i < count; i++) {
assert(args[i]);
auto* arg_ty = args[i]->UnwrapAll();
if (!parameters[i].matcher->Match(matcher_state, arg_ty)) {
matched = false;
continue;
auto* arg_ty = args[i];
if (auto* ptr = arg_ty->As<type::Pointer>()) {
if (!parameters[i].matcher->ExpectsPointer()) {
// Argument is a pointer, but the matcher isn't expecting one.
// Perform an implicit dereference.
arg_ty = ptr->type();
}
}
if (parameters[i].matcher->Match(matcher_state, arg_ty)) {
// A correct parameter match is scored higher than number of parameters to
// arguments.
match_score += 2;
} else {
matched = false;
}
// Weight correct parameter matches more than exact number of arguments
match_score += 2;
}
if (!matched) {
return nullptr;

488
src/intrinsic_table_test.cc Normal file
View File

@ -0,0 +1,488 @@
// 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/intrinsic_table.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/program_builder.h"
#include "src/type/access_control_type.h"
#include "src/type/depth_texture_type.h"
#include "src/type/multisampled_texture_type.h"
#include "src/type/sampled_texture_type.h"
#include "src/type/storage_texture_type.h"
namespace tint {
namespace {
using ::testing::ElementsAre;
using ::testing::HasSubstr;
using IntrinsicType = semantic::IntrinsicType;
using Parameter = semantic::Parameter;
class IntrinsicTableTest : public testing::Test, public ProgramBuilder {
public:
std::unique_ptr<IntrinsicTable> table = IntrinsicTable::Create();
};
TEST_F(IntrinsicTableTest, MatchF32) {
auto result = table->Lookup(*this, IntrinsicType::kCos, {ty.f32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCos);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32());
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()}));
}
TEST_F(IntrinsicTableTest, MismatchF32) {
auto result = table->Lookup(*this, IntrinsicType::kCos, {ty.i32()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchU32) {
auto result =
table->Lookup(*this, IntrinsicType::kUnpack2x16Float, {ty.u32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kUnpack2x16Float);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec2<f32>());
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.u32()}));
}
TEST_F(IntrinsicTableTest, MismatchU32) {
auto result =
table->Lookup(*this, IntrinsicType::kUnpack2x16Float, {ty.f32()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchI32) {
auto* tex =
create<type::SampledTexture>(type::TextureDimension::k1d, ty.f32());
auto result =
table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, ty.i32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4<f32>());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{tex, Parameter::Usage::kTexture},
Parameter{ty.i32(), Parameter::Usage::kCoords}));
}
TEST_F(IntrinsicTableTest, MismatchI32) {
auto* tex =
create<type::SampledTexture>(type::TextureDimension::k1d, ty.f32());
auto result =
table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, ty.f32()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchIU32AsI32) {
auto result = table->Lookup(*this, IntrinsicType::kCountOneBits, {ty.i32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCountOneBits);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.i32());
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.i32()}));
}
TEST_F(IntrinsicTableTest, MatchIU32AsU32) {
auto result = table->Lookup(*this, IntrinsicType::kCountOneBits, {ty.u32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCountOneBits);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.u32());
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.u32()}));
}
TEST_F(IntrinsicTableTest, MismatchIU32) {
auto result = table->Lookup(*this, IntrinsicType::kCountOneBits, {ty.f32()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchFIU32AsI32) {
auto result = table->Lookup(*this, IntrinsicType::kClamp,
{ty.i32(), ty.i32(), ty.i32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.i32());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{ty.i32()}, Parameter{ty.i32()},
Parameter{ty.i32()}));
}
TEST_F(IntrinsicTableTest, MatchFIU32AsU32) {
auto result = table->Lookup(*this, IntrinsicType::kClamp,
{ty.u32(), ty.u32(), ty.u32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.u32());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{ty.u32()}, Parameter{ty.u32()},
Parameter{ty.u32()}));
}
TEST_F(IntrinsicTableTest, MatchFIU32AsF32) {
auto result = table->Lookup(*this, IntrinsicType::kClamp,
{ty.f32(), ty.f32(), ty.f32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{ty.f32()}, Parameter{ty.f32()},
Parameter{ty.f32()}));
}
TEST_F(IntrinsicTableTest, MismatchFIU32) {
auto result = table->Lookup(*this, IntrinsicType::kClamp,
{ty.bool_(), ty.bool_(), ty.bool_()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchBool) {
auto result = table->Lookup(*this, IntrinsicType::kSelect,
{ty.f32(), ty.f32(), ty.bool_()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kSelect);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{ty.f32()}, Parameter{ty.f32()},
Parameter{ty.bool_()}));
}
TEST_F(IntrinsicTableTest, MismatchBool) {
auto result = table->Lookup(*this, IntrinsicType::kSelect,
{ty.f32(), ty.f32(), ty.f32()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchPointer) {
auto result =
table->Lookup(*this, IntrinsicType::kModf,
{ty.f32(), ty.pointer<f32>(ast::StorageClass::kNone)});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kModf);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32());
EXPECT_THAT(
result.intrinsic->Parameters(),
ElementsAre(Parameter{ty.f32()},
Parameter{ty.pointer<f32>(ast::StorageClass::kNone)}));
}
TEST_F(IntrinsicTableTest, MismatchPointer) {
auto result =
table->Lookup(*this, IntrinsicType::kModf, {ty.f32(), ty.f32()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchArray) {
auto result =
table->Lookup(*this, IntrinsicType::kArrayLength, {ty.array<f32>()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kArrayLength);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.u32());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{ty.array<f32>()}));
}
TEST_F(IntrinsicTableTest, MismatchArray) {
auto result = table->Lookup(*this, IntrinsicType::kArrayLength, {ty.f32()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchSampler) {
auto* tex =
create<type::SampledTexture>(type::TextureDimension::k2d, ty.f32());
auto* sampler = create<type::Sampler>(type::SamplerKind::kSampler);
auto result = table->Lookup(*this, IntrinsicType::kTextureSample,
{tex, sampler, ty.vec2<f32>()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureSample);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4<f32>());
EXPECT_THAT(
result.intrinsic->Parameters(),
ElementsAre(Parameter{tex, Parameter::Usage::kTexture},
Parameter{sampler, Parameter::Usage::kSampler},
Parameter{ty.vec2<f32>(), Parameter::Usage::kCoords}));
}
TEST_F(IntrinsicTableTest, MismatchSampler) {
auto* tex =
create<type::SampledTexture>(type::TextureDimension::k2d, ty.f32());
auto result = table->Lookup(*this, IntrinsicType::kTextureSample,
{tex, ty.f32(), ty.vec2<f32>()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchSampledTexture) {
auto* tex =
create<type::SampledTexture>(type::TextureDimension::k2d, ty.f32());
auto result =
table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, ty.vec2<i32>()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4<f32>());
EXPECT_THAT(
result.intrinsic->Parameters(),
ElementsAre(Parameter{tex, Parameter::Usage::kTexture},
Parameter{ty.vec2<i32>(), Parameter::Usage::kCoords}));
}
TEST_F(IntrinsicTableTest, MatchMultisampledTexture) {
auto* tex =
create<type::MultisampledTexture>(type::TextureDimension::k2d, ty.f32());
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad,
{tex, ty.vec2<i32>(), ty.i32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4<f32>());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{tex, Parameter::Usage::kTexture},
Parameter{ty.vec2<i32>(), Parameter::Usage::kCoords},
Parameter{ty.i32(), Parameter::Usage::kSampleIndex}));
}
TEST_F(IntrinsicTableTest, MatchDepthTexture) {
auto* tex = create<type::DepthTexture>(type::TextureDimension::k2d);
auto result =
table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, ty.vec2<i32>()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32());
EXPECT_THAT(
result.intrinsic->Parameters(),
ElementsAre(Parameter{tex, Parameter::Usage::kTexture},
Parameter{ty.vec2<i32>(), Parameter::Usage::kCoords}));
}
TEST_F(IntrinsicTableTest, MatchROStorageTexture) {
auto* tex = create<type::StorageTexture>(
type::TextureDimension::k2d, type::ImageFormat::kR16Float,
type::StorageTexture::SubtypeFor(type::ImageFormat::kR16Float, Types()));
auto* tex_ac =
create<type::AccessControl>(ast::AccessControl::kReadOnly, tex);
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad,
{tex_ac, ty.vec2<i32>()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4<f32>());
EXPECT_THAT(
result.intrinsic->Parameters(),
ElementsAre(Parameter{tex_ac, Parameter::Usage::kTexture},
Parameter{ty.vec2<i32>(), Parameter::Usage::kCoords}));
}
TEST_F(IntrinsicTableTest, MatchWOStorageTexture) {
auto* tex = create<type::StorageTexture>(
type::TextureDimension::k2d, type::ImageFormat::kR16Float,
type::StorageTexture::SubtypeFor(type::ImageFormat::kR16Float, Types()));
auto* tex_ac =
create<type::AccessControl>(ast::AccessControl::kWriteOnly, tex);
auto result = table->Lookup(*this, IntrinsicType::kTextureStore,
{tex_ac, ty.vec2<i32>(), ty.vec4<f32>()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureStore);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.void_());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{tex_ac, Parameter::Usage::kTexture},
Parameter{ty.vec2<i32>(), Parameter::Usage::kCoords},
Parameter{ty.vec4<f32>(), Parameter::Usage::kValue}));
}
TEST_F(IntrinsicTableTest, MismatchTexture) {
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad,
{ty.f32(), ty.vec2<i32>()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchAutoPointerDereference) {
auto result = table->Lookup(*this, IntrinsicType::kCos,
{ty.pointer<f32>(ast::StorageClass::kNone)});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCos);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32());
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()}));
}
TEST_F(IntrinsicTableTest, MatchWithAliasUnwrapping) {
auto* alias_a = ty.alias("alias_a", ty.f32());
auto* alias_b = ty.alias("alias_b", alias_a);
auto* alias_c = ty.alias("alias_c", alias_b);
auto result = table->Lookup(*this, IntrinsicType::kCos, {alias_c});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCos);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32());
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()}));
}
TEST_F(IntrinsicTableTest, MatchOpenType) {
auto result = table->Lookup(*this, IntrinsicType::kClamp,
{ty.f32(), ty.f32(), ty.f32()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{ty.f32()}, Parameter{ty.f32()},
Parameter{ty.f32()}));
}
TEST_F(IntrinsicTableTest, MismatchOpenType) {
auto result = table->Lookup(*this, IntrinsicType::kClamp,
{ty.f32(), ty.u32(), ty.f32()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchOpenSizeVector) {
auto result = table->Lookup(*this, IntrinsicType::kClamp,
{ty.vec2<f32>(), ty.vec2<f32>(), ty.vec2<f32>()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec2<f32>());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{ty.vec2<f32>()}, Parameter{ty.vec2<f32>()},
Parameter{ty.vec2<f32>()}));
}
TEST_F(IntrinsicTableTest, MismatchOpenSizeVector) {
auto result = table->Lookup(*this, IntrinsicType::kClamp,
{ty.vec2<f32>(), ty.vec2<u32>(), ty.vec2<f32>()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, MatchOpenSizeMatrix) {
auto result =
table->Lookup(*this, IntrinsicType::kDeterminant, {ty.mat3x3<f32>()});
ASSERT_NE(result.intrinsic, nullptr);
ASSERT_EQ(result.error, "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kDeterminant);
EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32());
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{ty.mat3x3<f32>()}));
}
TEST_F(IntrinsicTableTest, MismatchOpenSizeMatrix) {
auto result =
table->Lookup(*this, IntrinsicType::kDeterminant, {ty.mat3x2<f32>()});
ASSERT_EQ(result.intrinsic, nullptr);
ASSERT_THAT(result.error, HasSubstr("no matching call"));
}
TEST_F(IntrinsicTableTest, OverloadOrderByNumberOfParameters) {
// None of the arguments match, so expect the overloads with 2 parameters to
// come first
auto result = table->Lookup(*this, IntrinsicType::kTextureDimensions,
{ty.bool_(), ty.bool_()});
ASSERT_EQ(result.error,
R"(no matching call to textureDimensions(bool, bool)
27 candidate functions:
textureDimensions(texture : texture_2d<T>, level : i32) -> vec2<i32>
textureDimensions(texture : texture_2d_array<T>, level : i32) -> vec2<i32>
textureDimensions(texture : texture_3d<T>, level : i32) -> vec3<i32>
textureDimensions(texture : texture_cube<T>, level : i32) -> vec3<i32>
textureDimensions(texture : texture_cube_array<T>, level : i32) -> vec3<i32>
textureDimensions(texture : texture_depth_2d, level : i32) -> vec2<i32>
textureDimensions(texture : texture_depth_2d_array, level : i32) -> vec2<i32>
textureDimensions(texture : texture_depth_cube, level : i32) -> vec3<i32>
textureDimensions(texture : texture_depth_cube_array, level : i32) -> vec3<i32>
textureDimensions(texture : texture_1d<T>) -> i32
textureDimensions(texture : texture_1d_array<T>) -> i32
textureDimensions(texture : texture_2d<T>) -> vec2<i32>
textureDimensions(texture : texture_2d_array<T>) -> vec2<i32>
textureDimensions(texture : texture_3d<T>) -> vec3<i32>
textureDimensions(texture : texture_cube<T>) -> vec3<i32>
textureDimensions(texture : texture_cube_array<T>) -> vec3<i32>
textureDimensions(texture : texture_multisampled_2d<T>) -> vec2<i32>
textureDimensions(texture : texture_multisampled_2d_array<T>) -> vec2<i32>
textureDimensions(texture : texture_depth_2d) -> vec2<i32>
textureDimensions(texture : texture_depth_2d_array) -> vec2<i32>
textureDimensions(texture : texture_depth_cube) -> vec3<i32>
textureDimensions(texture : texture_depth_cube_array) -> vec3<i32>
textureDimensions(texture : texture_storage_1d<F>) -> i32
textureDimensions(texture : texture_storage_1d_array<F>) -> i32
textureDimensions(texture : texture_storage_2d<F>) -> vec2<i32>
textureDimensions(texture : texture_storage_2d_array<F>) -> vec2<i32>
textureDimensions(texture : texture_storage_3d<F>) -> vec3<i32>
)");
}
TEST_F(IntrinsicTableTest, OverloadOrderByMatchingParameter) {
auto* tex = create<type::DepthTexture>(type::TextureDimension::k2d);
auto result = table->Lookup(*this, IntrinsicType::kTextureDimensions,
{tex, ty.bool_()});
ASSERT_EQ(result.error,
R"(no matching call to textureDimensions(texture_depth_2d, bool)
27 candidate functions:
textureDimensions(texture : texture_depth_2d, level : i32) -> vec2<i32>
textureDimensions(texture : texture_depth_2d) -> vec2<i32>
textureDimensions(texture : texture_2d<T>, level : i32) -> vec2<i32>
textureDimensions(texture : texture_2d_array<T>, level : i32) -> vec2<i32>
textureDimensions(texture : texture_3d<T>, level : i32) -> vec3<i32>
textureDimensions(texture : texture_cube<T>, level : i32) -> vec3<i32>
textureDimensions(texture : texture_cube_array<T>, level : i32) -> vec3<i32>
textureDimensions(texture : texture_depth_2d_array, level : i32) -> vec2<i32>
textureDimensions(texture : texture_depth_cube, level : i32) -> vec3<i32>
textureDimensions(texture : texture_depth_cube_array, level : i32) -> vec3<i32>
textureDimensions(texture : texture_1d<T>) -> i32
textureDimensions(texture : texture_1d_array<T>) -> i32
textureDimensions(texture : texture_2d<T>) -> vec2<i32>
textureDimensions(texture : texture_2d_array<T>) -> vec2<i32>
textureDimensions(texture : texture_3d<T>) -> vec3<i32>
textureDimensions(texture : texture_cube<T>) -> vec3<i32>
textureDimensions(texture : texture_cube_array<T>) -> vec3<i32>
textureDimensions(texture : texture_multisampled_2d<T>) -> vec2<i32>
textureDimensions(texture : texture_multisampled_2d_array<T>) -> vec2<i32>
textureDimensions(texture : texture_depth_2d_array) -> vec2<i32>
textureDimensions(texture : texture_depth_cube) -> vec3<i32>
textureDimensions(texture : texture_depth_cube_array) -> vec3<i32>
textureDimensions(texture : texture_storage_1d<F>) -> i32
textureDimensions(texture : texture_storage_1d_array<F>) -> i32
textureDimensions(texture : texture_storage_2d<F>) -> vec2<i32>
textureDimensions(texture : texture_storage_2d_array<F>) -> vec2<i32>
textureDimensions(texture : texture_storage_3d<F>) -> vec3<i32>
)");
}
} // namespace
} // namespace tint

View File

@ -56,6 +56,13 @@ struct Parameter {
Usage const usage = Usage::kNone;
};
std::ostream& operator<<(std::ostream& out, Parameter parameter);
/// Comparison operator for Parameters
static inline bool operator==(const Parameter& a, const Parameter& b) {
return a.type == b.type && a.usage == b.usage;
}
/// @returns a string representation of the given parameter usage.
const char* str(Parameter::Usage usage);

View File

@ -14,6 +14,7 @@
#include "src/semantic/call_target.h"
#include "src/symbol_table.h"
#include "src/type/type.h"
TINT_INSTANTIATE_CLASS_ID(tint::semantic::CallTarget);
@ -65,5 +66,12 @@ const char* str(Parameter::Usage usage) {
return "<unknown>";
}
}
std::ostream& operator<<(std::ostream& out, Parameter parameter) {
out << "[type: " << parameter.type->FriendlyName(SymbolTable{})
<< ", usage: " << str(parameter.usage) << "]";
return out;
}
} // namespace semantic
} // namespace tint

View File

@ -55,6 +55,7 @@
#include "src/semantic/expression.h"
#include "src/semantic/function.h"
#include "src/semantic/variable.h"
#include "src/type/access_control_type.h"
#include "src/type/alias_type.h"
#include "src/type/array_type.h"
#include "src/type/bool_type.h"
@ -1379,13 +1380,13 @@ TEST_P(Intrinsic_FloatMethod, TooManyParams) {
Global("my_var", ast::StorageClass::kNone, ty.f32());
auto* expr = Call(name, "my_var", "my_var");
auto* expr = Call(name, "my_var", 1.23f);
WrapInFunction(expr);
EXPECT_FALSE(td()->Determine());
EXPECT_EQ(td()->error(), "no matching call to " + name +
"(f32, f32)\n\n"
"(ptr<f32>, f32)\n\n"
"2 candidate functions:\n " +
name + "(f32) -> bool\n " + name +
"(vecN<f32>) -> vecN<bool>\n");
@ -1469,11 +1470,13 @@ TEST_P(Intrinsic_StorageTextureOperation, TextureLoadRo) {
auto* coords_type = GetCoordsType(dim, ty.i32());
auto* subtype = type::StorageTexture::SubtypeFor(format, Types());
type::Type* texture_type = create<type::StorageTexture>(dim, format, subtype);
auto* texture_type = create<type::StorageTexture>(dim, format, subtype);
auto* ro_texture_type =
create<type::AccessControl>(ast::AccessControl::kReadOnly, texture_type);
ast::ExpressionList call_params;
add_call_param("texture", texture_type, &call_params);
add_call_param("texture", ro_texture_type, &call_params);
add_call_param("coords", coords_type, &call_params);
if (type::IsTextureArray(dim)) {
@ -2504,7 +2507,7 @@ TEST_P(ImportData_Matrix_OneParam_Test, NoParams) {
"no matching call to " + std::string(param.name) + R"(()
1 candidate function:
determinant(maxNxN<f32>) -> f32
determinant(matNxN<f32>) -> f32
)");
}