diff --git a/src/resolver/builtins_validation_test.cc b/src/resolver/builtins_validation_test.cc index d526f1ef98..de9603e90a 100644 --- a/src/resolver/builtins_validation_test.cc +++ b/src/resolver/builtins_validation_test.cc @@ -20,6 +20,73 @@ namespace { class ResolverBuiltinsValidationTest : public resolver::TestHelper, public testing::Test {}; +TEST_F(ResolverBuiltinsValidationTest, FrontFacingParamIsBool_Pass) { + // [[stage(fragment)]] + // fn fs_main( + // [[builtin(front_facing)]] is_front: bool + // ) -> [[location(0)]] f32 { return 1.0; } + auto* is_front = + Param("is_front", ty.bool_(), + ast::DecorationList{Builtin(ast::Builtin::kFrontFacing)}); + Func("fs_main", ast::VariableList{is_front}, ty.f32(), {Return(1.0f)}, + ast::DecorationList{Stage(ast::PipelineStage::kFragment)}, + {Location(0)}); + EXPECT_TRUE(r()->Resolve()) << r()->error(); +} + +TEST_F(ResolverBuiltinsValidationTest, FrontFacingMemberIsBool_Pass) { + // struct MyInputs { + // [[builtin(front_facing)]] pos: bool; + // }; + // [[stage(fragment)]] + // fn fragShader(is_front: MyInputs) -> [[location(0)]] f32 { return 1.0; } + + auto* s = Structure( + "MyInputs", + {Member("pos", ty.bool_(), + ast::DecorationList{Builtin(ast::Builtin::kFrontFacing)})}); + Func("fragShader", {Param("is_front", ty.Of(s))}, ty.f32(), {Return(1.0f)}, + {Stage(ast::PipelineStage::kFragment)}, {Location(0)}); + EXPECT_TRUE(r()->Resolve()) << r()->error(); +} + +TEST_F(ResolverBuiltinsValidationTest, FrontFacingParamIsNotBool_Fail) { + // [[stage(fragment)]] + // fn fs_main( + // [[builtin(front_facing)]] is_front: i32; + // ) -> [[location(0)]] f32 { return 1.0; } + + auto* is_front = Param("is_front", ty.i32(), + ast::DecorationList{Builtin( + Source{{12, 34}}, ast::Builtin::kFrontFacing)}); + Func("fs_main", ast::VariableList{is_front}, ty.f32(), {Return(1.0f)}, + ast::DecorationList{Stage(ast::PipelineStage::kFragment)}, + {Location(0)}); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error v-15001: front_facing builtin must be boolean"); +} + +TEST_F(ResolverBuiltinsValidationTest, FrontFacingMemberIsNotBool_Fail) { + // struct MyInputs { + // [[builtin(front_facing)]] pos: f32; + // }; + // [[stage(fragment)]] + // fn fragShader(is_front: MyInputs) -> [[location(0)]] f32 { return 1.0; } + + auto* s = Structure( + "MyInputs", {Member("pos", ty.f32(), + ast::DecorationList{Builtin( + Source{{12, 34}}, ast::Builtin::kFrontFacing)})}); + Func("fragShader", {Param("is_front", ty.Of(s))}, ty.f32(), {Return(1.0f)}, + {Stage(ast::PipelineStage::kFragment)}, {Location(0)}); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error v-15001: front_facing builtin must be boolean"); +} + TEST_F(ResolverBuiltinsValidationTest, Length_Float_Scalar) { auto* builtin = Call("length", 1.0f); WrapInFunction(builtin); diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc index df95064ac9..fa34926d9a 100644 --- a/src/resolver/resolver.cc +++ b/src/resolver/resolver.cc @@ -808,6 +808,21 @@ bool Resolver::ValidateVariable(const VariableInfo* info) { } bool Resolver::ValidateParameter(const VariableInfo* info) { + auto* var = info->declaration; + for (auto* deco : var->decorations()) { + if (auto* builtin = deco->As()) { + if (builtin->value() == ast::Builtin::kFrontFacing) { + auto* storage_type = info->type->UnwrapRef(); + if (!(storage_type->Is())) { + diagnostics_.add_error("v-15001", + "front_facing builtin must be boolean", + deco->source()); + return false; + } + } + } + } + return ValidateVariable(info); } @@ -2876,6 +2891,16 @@ bool Resolver::ValidateStructure(const sem::Struct* str) { deco->source()); return false; } + if (auto* builtin = deco->As()) { + if (builtin->value() == ast::Builtin::kFrontFacing) { + if (!(member->Type()->Is())) { + diagnostics_.add_error("v-15001", + "front_facing builtin must be boolean", + deco->source()); + return false; + } + } + } } }