// 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 #include #include #include "gmock/gmock.h" #include "src/tint/builtin/address_space.h" #include "src/tint/resolver/dependency_graph.h" #include "src/tint/resolver/resolver_test_helper.h" #include "src/tint/type/texture_dimension.h" using namespace tint::number_suffixes; // NOLINT namespace tint::resolver { namespace { using ::testing::ElementsAre; template class ResolverDependencyGraphTestWithParam : public ResolverTestWithParam { public: DependencyGraph Build(std::string expected_error = "") { DependencyGraph graph; auto result = DependencyGraph::Build(this->AST(), this->Symbols(), this->Diagnostics(), graph); if (expected_error.empty()) { EXPECT_TRUE(result) << this->Diagnostics().str(); } else { EXPECT_FALSE(result); EXPECT_EQ(expected_error, this->Diagnostics().str()); } return graph; } }; using ResolverDependencyGraphTest = ResolverDependencyGraphTestWithParam<::testing::Test>; //////////////////////////////////////////////////////////////////////////////// // Parameterized test helpers //////////////////////////////////////////////////////////////////////////////// /// SymbolDeclKind is used by parameterized tests to enumerate the different /// kinds of symbol declarations. enum class SymbolDeclKind { GlobalVar, GlobalConst, Alias, Struct, Function, Parameter, LocalVar, LocalLet, NestedLocalVar, NestedLocalLet, }; static constexpr SymbolDeclKind kAllSymbolDeclKinds[] = { SymbolDeclKind::GlobalVar, SymbolDeclKind::GlobalConst, SymbolDeclKind::Alias, SymbolDeclKind::Struct, SymbolDeclKind::Function, SymbolDeclKind::Parameter, SymbolDeclKind::LocalVar, SymbolDeclKind::LocalLet, SymbolDeclKind::NestedLocalVar, SymbolDeclKind::NestedLocalLet, }; static constexpr SymbolDeclKind kTypeDeclKinds[] = { SymbolDeclKind::Alias, SymbolDeclKind::Struct, }; static constexpr SymbolDeclKind kValueDeclKinds[] = { SymbolDeclKind::GlobalVar, SymbolDeclKind::GlobalConst, SymbolDeclKind::Parameter, SymbolDeclKind::LocalVar, SymbolDeclKind::LocalLet, SymbolDeclKind::NestedLocalVar, SymbolDeclKind::NestedLocalLet, }; static constexpr SymbolDeclKind kGlobalDeclKinds[] = { SymbolDeclKind::GlobalVar, SymbolDeclKind::GlobalConst, SymbolDeclKind::Alias, SymbolDeclKind::Struct, SymbolDeclKind::Function, }; static constexpr SymbolDeclKind kLocalDeclKinds[] = { SymbolDeclKind::Parameter, SymbolDeclKind::LocalVar, SymbolDeclKind::LocalLet, SymbolDeclKind::NestedLocalVar, SymbolDeclKind::NestedLocalLet, }; static constexpr SymbolDeclKind kGlobalValueDeclKinds[] = { SymbolDeclKind::GlobalVar, SymbolDeclKind::GlobalConst, }; static constexpr SymbolDeclKind kFuncDeclKinds[] = { SymbolDeclKind::Function, }; /// SymbolUseKind is used by parameterized tests to enumerate the different kinds of symbol uses. enum class SymbolUseKind { GlobalVarType, GlobalVarArrayElemType, GlobalVarArraySizeValue, GlobalVarVectorElemType, GlobalVarMatrixElemType, GlobalVarSampledTexElemType, GlobalVarMultisampledTexElemType, GlobalVarValue, GlobalConstType, GlobalConstArrayElemType, GlobalConstArraySizeValue, GlobalConstVectorElemType, GlobalConstMatrixElemType, GlobalConstValue, AliasType, StructMemberType, CallFunction, ParameterType, LocalVarType, LocalVarArrayElemType, LocalVarArraySizeValue, LocalVarVectorElemType, LocalVarMatrixElemType, LocalVarValue, LocalLetType, LocalLetValue, NestedLocalVarType, NestedLocalVarValue, NestedLocalLetType, NestedLocalLetValue, WorkgroupSizeValue, }; static constexpr SymbolUseKind kTypeUseKinds[] = { SymbolUseKind::GlobalVarType, SymbolUseKind::GlobalVarArrayElemType, SymbolUseKind::GlobalVarArraySizeValue, SymbolUseKind::GlobalVarVectorElemType, SymbolUseKind::GlobalVarMatrixElemType, SymbolUseKind::GlobalVarSampledTexElemType, SymbolUseKind::GlobalVarMultisampledTexElemType, SymbolUseKind::GlobalConstType, SymbolUseKind::GlobalConstArrayElemType, SymbolUseKind::GlobalConstArraySizeValue, SymbolUseKind::GlobalConstVectorElemType, SymbolUseKind::GlobalConstMatrixElemType, SymbolUseKind::AliasType, SymbolUseKind::StructMemberType, SymbolUseKind::ParameterType, SymbolUseKind::LocalVarType, SymbolUseKind::LocalVarArrayElemType, SymbolUseKind::LocalVarArraySizeValue, SymbolUseKind::LocalVarVectorElemType, SymbolUseKind::LocalVarMatrixElemType, SymbolUseKind::LocalLetType, SymbolUseKind::NestedLocalVarType, SymbolUseKind::NestedLocalLetType, }; static constexpr SymbolUseKind kValueUseKinds[] = { SymbolUseKind::GlobalVarValue, SymbolUseKind::GlobalConstValue, SymbolUseKind::LocalVarValue, SymbolUseKind::LocalLetValue, SymbolUseKind::NestedLocalVarValue, SymbolUseKind::NestedLocalLetValue, SymbolUseKind::WorkgroupSizeValue, }; static constexpr SymbolUseKind kFuncUseKinds[] = { SymbolUseKind::CallFunction, }; /// @returns the description of the symbol declaration kind. /// @note: This differs from the strings used in diagnostic messages. std::ostream& operator<<(std::ostream& out, SymbolDeclKind kind) { switch (kind) { case SymbolDeclKind::GlobalVar: return out << "global var"; case SymbolDeclKind::GlobalConst: return out << "global const"; case SymbolDeclKind::Alias: return out << "alias"; case SymbolDeclKind::Struct: return out << "struct"; case SymbolDeclKind::Function: return out << "function"; case SymbolDeclKind::Parameter: return out << "parameter"; case SymbolDeclKind::LocalVar: return out << "local var"; case SymbolDeclKind::LocalLet: return out << "local let"; case SymbolDeclKind::NestedLocalVar: return out << "nested local var"; case SymbolDeclKind::NestedLocalLet: return out << "nested local let"; } return out << ""; } /// @returns the description of the symbol use kind. /// @note: This differs from the strings used in diagnostic messages. std::ostream& operator<<(std::ostream& out, SymbolUseKind kind) { switch (kind) { case SymbolUseKind::GlobalVarType: return out << "global var type"; case SymbolUseKind::GlobalVarValue: return out << "global var value"; case SymbolUseKind::GlobalVarArrayElemType: return out << "global var array element type"; case SymbolUseKind::GlobalVarArraySizeValue: return out << "global var array size value"; case SymbolUseKind::GlobalVarVectorElemType: return out << "global var vector element type"; case SymbolUseKind::GlobalVarMatrixElemType: return out << "global var matrix element type"; case SymbolUseKind::GlobalVarSampledTexElemType: return out << "global var sampled_texture element type"; case SymbolUseKind::GlobalVarMultisampledTexElemType: return out << "global var multisampled_texture element type"; case SymbolUseKind::GlobalConstType: return out << "global const type"; case SymbolUseKind::GlobalConstValue: return out << "global const value"; case SymbolUseKind::GlobalConstArrayElemType: return out << "global const array element type"; case SymbolUseKind::GlobalConstArraySizeValue: return out << "global const array size value"; case SymbolUseKind::GlobalConstVectorElemType: return out << "global const vector element type"; case SymbolUseKind::GlobalConstMatrixElemType: return out << "global const matrix element type"; case SymbolUseKind::AliasType: return out << "alias type"; case SymbolUseKind::StructMemberType: return out << "struct member type"; case SymbolUseKind::CallFunction: return out << "call function"; case SymbolUseKind::ParameterType: return out << "parameter type"; case SymbolUseKind::LocalVarType: return out << "local var type"; case SymbolUseKind::LocalVarArrayElemType: return out << "local var array element type"; case SymbolUseKind::LocalVarArraySizeValue: return out << "local var array size value"; case SymbolUseKind::LocalVarVectorElemType: return out << "local var vector element type"; case SymbolUseKind::LocalVarMatrixElemType: return out << "local var matrix element type"; case SymbolUseKind::LocalVarValue: return out << "local var value"; case SymbolUseKind::LocalLetType: return out << "local let type"; case SymbolUseKind::LocalLetValue: return out << "local let value"; case SymbolUseKind::NestedLocalVarType: return out << "nested local var type"; case SymbolUseKind::NestedLocalVarValue: return out << "nested local var value"; case SymbolUseKind::NestedLocalLetType: return out << "nested local let type"; case SymbolUseKind::NestedLocalLetValue: return out << "nested local let value"; case SymbolUseKind::WorkgroupSizeValue: return out << "workgroup size value"; } return out << ""; } /// @returns the the diagnostic message name used for the given use std::string DiagString(SymbolUseKind kind) { switch (kind) { case SymbolUseKind::GlobalVarType: case SymbolUseKind::GlobalConstType: case SymbolUseKind::AliasType: case SymbolUseKind::StructMemberType: case SymbolUseKind::ParameterType: case SymbolUseKind::LocalVarType: case SymbolUseKind::LocalLetType: case SymbolUseKind::NestedLocalVarType: case SymbolUseKind::NestedLocalLetType: return "type"; case SymbolUseKind::GlobalVarArrayElemType: case SymbolUseKind::GlobalVarVectorElemType: case SymbolUseKind::GlobalVarMatrixElemType: case SymbolUseKind::GlobalVarSampledTexElemType: case SymbolUseKind::GlobalVarMultisampledTexElemType: case SymbolUseKind::GlobalConstArrayElemType: case SymbolUseKind::GlobalConstVectorElemType: case SymbolUseKind::GlobalConstMatrixElemType: case SymbolUseKind::LocalVarArrayElemType: case SymbolUseKind::LocalVarVectorElemType: case SymbolUseKind::LocalVarMatrixElemType: case SymbolUseKind::GlobalVarValue: case SymbolUseKind::GlobalVarArraySizeValue: case SymbolUseKind::GlobalConstValue: case SymbolUseKind::GlobalConstArraySizeValue: case SymbolUseKind::LocalVarValue: case SymbolUseKind::LocalVarArraySizeValue: case SymbolUseKind::LocalLetValue: case SymbolUseKind::NestedLocalVarValue: case SymbolUseKind::NestedLocalLetValue: case SymbolUseKind::WorkgroupSizeValue: return "identifier"; case SymbolUseKind::CallFunction: return "function"; } return ""; } /// @returns the declaration scope depth for the symbol declaration kind. /// Globals are at depth 0, parameters and locals are at depth 1, /// nested locals are at depth 2. int ScopeDepth(SymbolDeclKind kind) { switch (kind) { case SymbolDeclKind::GlobalVar: case SymbolDeclKind::GlobalConst: case SymbolDeclKind::Alias: case SymbolDeclKind::Struct: case SymbolDeclKind::Function: return 0; case SymbolDeclKind::Parameter: case SymbolDeclKind::LocalVar: case SymbolDeclKind::LocalLet: return 1; case SymbolDeclKind::NestedLocalVar: case SymbolDeclKind::NestedLocalLet: return 2; } return -1; } /// @returns the use depth for the symbol use kind. /// Globals are at depth 0, parameters and locals are at depth 1, /// nested locals are at depth 2. int ScopeDepth(SymbolUseKind kind) { switch (kind) { case SymbolUseKind::GlobalVarType: case SymbolUseKind::GlobalVarValue: case SymbolUseKind::GlobalVarArrayElemType: case SymbolUseKind::GlobalVarArraySizeValue: case SymbolUseKind::GlobalVarVectorElemType: case SymbolUseKind::GlobalVarMatrixElemType: case SymbolUseKind::GlobalVarSampledTexElemType: case SymbolUseKind::GlobalVarMultisampledTexElemType: case SymbolUseKind::GlobalConstType: case SymbolUseKind::GlobalConstValue: case SymbolUseKind::GlobalConstArrayElemType: case SymbolUseKind::GlobalConstArraySizeValue: case SymbolUseKind::GlobalConstVectorElemType: case SymbolUseKind::GlobalConstMatrixElemType: case SymbolUseKind::AliasType: case SymbolUseKind::StructMemberType: case SymbolUseKind::WorkgroupSizeValue: return 0; case SymbolUseKind::CallFunction: case SymbolUseKind::ParameterType: case SymbolUseKind::LocalVarType: case SymbolUseKind::LocalVarArrayElemType: case SymbolUseKind::LocalVarArraySizeValue: case SymbolUseKind::LocalVarVectorElemType: case SymbolUseKind::LocalVarMatrixElemType: case SymbolUseKind::LocalVarValue: case SymbolUseKind::LocalLetType: case SymbolUseKind::LocalLetValue: return 1; case SymbolUseKind::NestedLocalVarType: case SymbolUseKind::NestedLocalVarValue: case SymbolUseKind::NestedLocalLetType: case SymbolUseKind::NestedLocalLetValue: return 2; } return -1; } /// A helper for building programs that exercise symbol declaration tests. struct SymbolTestHelper { /// The program builder ProgramBuilder* const builder; /// Parameters to a function that may need to be built utils::Vector parameters; /// Shallow function var / let declaration statements utils::Vector statements; /// Nested function local var / let declaration statements utils::Vector nested_statements; /// Function attributes utils::Vector func_attrs; /// Constructor /// @param builder the program builder explicit SymbolTestHelper(ProgramBuilder* builder); /// Destructor. ~SymbolTestHelper(); /// Declares an identifier with the given kind /// @param kind the kind of identifier declaration /// @param symbol the symbol to use for the declaration /// @param source the source of the declaration /// @returns the identifier node const ast::Node* Add(SymbolDeclKind kind, Symbol symbol, Source source); /// Declares a use of an identifier with the given kind /// @param kind the kind of symbol use /// @param symbol the declaration symbol to use /// @param source the source of the use /// @returns the use node const ast::Identifier* Add(SymbolUseKind kind, Symbol symbol, Source source); /// Builds a function, if any parameter or local declarations have been added void Build(); }; SymbolTestHelper::SymbolTestHelper(ProgramBuilder* b) : builder(b) {} SymbolTestHelper::~SymbolTestHelper() {} const ast::Node* SymbolTestHelper::Add(SymbolDeclKind kind, Symbol symbol, Source source = {}) { auto& b = *builder; switch (kind) { case SymbolDeclKind::GlobalVar: return b.GlobalVar(source, symbol, b.ty.i32(), builtin::AddressSpace::kPrivate); case SymbolDeclKind::GlobalConst: return b.GlobalConst(source, symbol, b.ty.i32(), b.Expr(1_i)); case SymbolDeclKind::Alias: return b.Alias(source, symbol, b.ty.i32()); case SymbolDeclKind::Struct: return b.Structure(source, symbol, utils::Vector{b.Member("m", b.ty.i32())}); case SymbolDeclKind::Function: return b.Func(source, symbol, utils::Empty, b.ty.void_(), utils::Empty); case SymbolDeclKind::Parameter: { auto* node = b.Param(source, symbol, b.ty.i32()); parameters.Push(node); return node; } case SymbolDeclKind::LocalVar: { auto* node = b.Var(source, symbol, b.ty.i32()); statements.Push(b.Decl(node)); return node; } case SymbolDeclKind::LocalLet: { auto* node = b.Let(source, symbol, b.ty.i32(), b.Expr(1_i)); statements.Push(b.Decl(node)); return node; } case SymbolDeclKind::NestedLocalVar: { auto* node = b.Var(source, symbol, b.ty.i32()); nested_statements.Push(b.Decl(node)); return node; } case SymbolDeclKind::NestedLocalLet: { auto* node = b.Let(source, symbol, b.ty.i32(), b.Expr(1_i)); nested_statements.Push(b.Decl(node)); return node; } } return nullptr; } const ast::Identifier* SymbolTestHelper::Add(SymbolUseKind kind, Symbol symbol, Source source = {}) { auto& b = *builder; switch (kind) { case SymbolUseKind::GlobalVarType: { auto node = b.ty(source, symbol); b.GlobalVar(b.Sym(), node, builtin::AddressSpace::kPrivate); return node->identifier; } case SymbolUseKind::GlobalVarArrayElemType: { auto node = b.ty(source, symbol); b.GlobalVar(b.Sym(), b.ty.array(node, 4_i), builtin::AddressSpace::kPrivate); return node->identifier; } case SymbolUseKind::GlobalVarArraySizeValue: { auto* node = b.Expr(source, symbol); b.GlobalVar(b.Sym(), b.ty.array(b.ty.i32(), node), builtin::AddressSpace::kPrivate); return node->identifier; } case SymbolUseKind::GlobalVarVectorElemType: { auto node = b.ty(source, symbol); b.GlobalVar(b.Sym(), b.ty.vec3(node), builtin::AddressSpace::kPrivate); return node->identifier; } case SymbolUseKind::GlobalVarMatrixElemType: { ast::Type node = b.ty(source, symbol); b.GlobalVar(b.Sym(), b.ty.mat3x4(node), builtin::AddressSpace::kPrivate); return node->identifier; } case SymbolUseKind::GlobalVarSampledTexElemType: { ast::Type node = b.ty(source, symbol); b.GlobalVar(b.Sym(), b.ty.sampled_texture(type::TextureDimension::k2d, node)); return node->identifier; } case SymbolUseKind::GlobalVarMultisampledTexElemType: { ast::Type node = b.ty(source, symbol); b.GlobalVar(b.Sym(), b.ty.multisampled_texture(type::TextureDimension::k2d, node)); return node->identifier; } case SymbolUseKind::GlobalVarValue: { auto* node = b.Expr(source, symbol); b.GlobalVar(b.Sym(), b.ty.i32(), builtin::AddressSpace::kPrivate, node); return node->identifier; } case SymbolUseKind::GlobalConstType: { auto node = b.ty(source, symbol); b.GlobalConst(b.Sym(), node, b.Expr(1_i)); return node->identifier; } case SymbolUseKind::GlobalConstArrayElemType: { auto node = b.ty(source, symbol); b.GlobalConst(b.Sym(), b.ty.array(node, 4_i), b.Expr(1_i)); return node->identifier; } case SymbolUseKind::GlobalConstArraySizeValue: { auto* node = b.Expr(source, symbol); b.GlobalConst(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1_i)); return node->identifier; } case SymbolUseKind::GlobalConstVectorElemType: { auto node = b.ty(source, symbol); b.GlobalConst(b.Sym(), b.ty.vec3(node), b.Expr(1_i)); return node->identifier; } case SymbolUseKind::GlobalConstMatrixElemType: { auto node = b.ty(source, symbol); b.GlobalConst(b.Sym(), b.ty.mat3x4(node), b.Expr(1_i)); return node->identifier; } case SymbolUseKind::GlobalConstValue: { auto* node = b.Expr(source, symbol); b.GlobalConst(b.Sym(), b.ty.i32(), node); return node->identifier; } case SymbolUseKind::AliasType: { auto node = b.ty(source, symbol); b.Alias(b.Sym(), node); return node->identifier; } case SymbolUseKind::StructMemberType: { auto node = b.ty(source, symbol); b.Structure(b.Sym(), utils::Vector{b.Member("m", node)}); return node->identifier; } case SymbolUseKind::CallFunction: { auto* node = b.Ident(source, symbol); statements.Push(b.CallStmt(b.Call(node))); return node; } case SymbolUseKind::ParameterType: { auto node = b.ty(source, symbol); parameters.Push(b.Param(b.Sym(), node)); return node->identifier; } case SymbolUseKind::LocalVarType: { auto node = b.ty(source, symbol); statements.Push(b.Decl(b.Var(b.Sym(), node))); return node->identifier; } case SymbolUseKind::LocalVarArrayElemType: { auto node = b.ty(source, symbol); statements.Push(b.Decl(b.Var(b.Sym(), b.ty.array(node, 4_u), b.Expr(1_i)))); return node->identifier; } case SymbolUseKind::LocalVarArraySizeValue: { auto* node = b.Expr(source, symbol); statements.Push(b.Decl(b.Var(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1_i)))); return node->identifier; } case SymbolUseKind::LocalVarVectorElemType: { auto node = b.ty(source, symbol); statements.Push(b.Decl(b.Var(b.Sym(), b.ty.vec3(node)))); return node->identifier; } case SymbolUseKind::LocalVarMatrixElemType: { auto node = b.ty(source, symbol); statements.Push(b.Decl(b.Var(b.Sym(), b.ty.mat3x4(node)))); return node->identifier; } case SymbolUseKind::LocalVarValue: { auto* node = b.Expr(source, symbol); statements.Push(b.Decl(b.Var(b.Sym(), b.ty.i32(), node))); return node->identifier; } case SymbolUseKind::LocalLetType: { auto node = b.ty(source, symbol); statements.Push(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i)))); return node->identifier; } case SymbolUseKind::LocalLetValue: { auto* node = b.Expr(source, symbol); statements.Push(b.Decl(b.Let(b.Sym(), b.ty.i32(), node))); return node->identifier; } case SymbolUseKind::NestedLocalVarType: { auto node = b.ty(source, symbol); nested_statements.Push(b.Decl(b.Var(b.Sym(), node))); return node->identifier; } case SymbolUseKind::NestedLocalVarValue: { auto* node = b.Expr(source, symbol); nested_statements.Push(b.Decl(b.Var(b.Sym(), b.ty.i32(), node))); return node->identifier; } case SymbolUseKind::NestedLocalLetType: { auto node = b.ty(source, symbol); nested_statements.Push(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i)))); return node->identifier; } case SymbolUseKind::NestedLocalLetValue: { auto* node = b.Expr(source, symbol); nested_statements.Push(b.Decl(b.Let(b.Sym(), b.ty.i32(), node))); return node->identifier; } case SymbolUseKind::WorkgroupSizeValue: { auto* node = b.Expr(source, symbol); func_attrs.Push(b.WorkgroupSize(1_i, node, 2_i)); return node->identifier; } } return nullptr; } void SymbolTestHelper::Build() { auto& b = *builder; if (!nested_statements.IsEmpty()) { statements.Push(b.Block(nested_statements)); nested_statements.Clear(); } if (!parameters.IsEmpty() || !statements.IsEmpty() || !func_attrs.IsEmpty()) { b.Func("func", parameters, b.ty.void_(), statements, func_attrs); parameters.Clear(); statements.Clear(); func_attrs.Clear(); } } //////////////////////////////////////////////////////////////////////////////// // Used-before-declared tests //////////////////////////////////////////////////////////////////////////////// namespace used_before_decl_tests { using ResolverDependencyGraphUsedBeforeDeclTest = ResolverDependencyGraphTest; TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, FuncCall) { // fn A() { B(); } // fn B() {} Func("A", utils::Empty, ty.void_(), utils::Vector{CallStmt(Call(Ident(Source{{12, 34}}, "B")))}); Func(Source{{56, 78}}, "B", utils::Empty, ty.void_(), utils::Vector{Return()}); Build(); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeConstructed) { // fn F() { // { _ = T(); } // } // type T = i32; Func("F", utils::Empty, ty.void_(), utils::Vector{Block(Ignore(Call(Ident(Source{{12, 34}}, "T"))))}); Alias(Source{{56, 78}}, "T", ty.i32()); Build(); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedByLocal) { // fn F() { // { var v : T; } // } // type T = i32; Func("F", utils::Empty, ty.void_(), utils::Vector{Block(Decl(Var("v", ty(Source{{12, 34}}, "T"))))}); Alias(Source{{56, 78}}, "T", ty.i32()); Build(); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedByParam) { // fn F(p : T) {} // type T = i32; Func("F", utils::Vector{Param("p", ty(Source{{12, 34}}, "T"))}, ty.void_(), utils::Empty); Alias(Source{{56, 78}}, "T", ty.i32()); Build(); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedAsReturnType) { // fn F() -> T {} // type T = i32; Func("F", utils::Empty, ty(Source{{12, 34}}, "T"), utils::Empty); Alias(Source{{56, 78}}, "T", ty.i32()); Build(); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeByStructMember) { // struct S { m : T }; // type T = i32; Structure("S", utils::Vector{Member("m", ty(Source{{12, 34}}, "T"))}); Alias(Source{{56, 78}}, "T", ty.i32()); Build(); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, VarUsed) { // fn F() { // { G = 3.14f; } // } // var G: f32 = 2.1; Func("F", utils::Empty, ty.void_(), utils::Vector{ Block(Assign(Expr(Source{{12, 34}}, "G"), 3.14_f)), }); GlobalVar(Source{{56, 78}}, "G", ty.f32(), builtin::AddressSpace::kPrivate, Expr(2.1_f)); Build(); } } // namespace used_before_decl_tests //////////////////////////////////////////////////////////////////////////////// // Undeclared symbol tests //////////////////////////////////////////////////////////////////////////////// namespace undeclared_tests { using ResolverDependencyGraphUndeclaredSymbolTest = ResolverDependencyGraphTestWithParam; TEST_P(ResolverDependencyGraphUndeclaredSymbolTest, Test) { const Symbol symbol = Sym("SYMBOL"); const auto use_kind = GetParam(); // Build a use of a non-existent symbol SymbolTestHelper helper(this); helper.Add(use_kind, symbol, Source{{56, 78}}); helper.Build(); Build("56:78 error: unknown " + DiagString(use_kind) + ": 'SYMBOL'"); } INSTANTIATE_TEST_SUITE_P(Types, ResolverDependencyGraphUndeclaredSymbolTest, testing::ValuesIn(kTypeUseKinds)); INSTANTIATE_TEST_SUITE_P(Values, ResolverDependencyGraphUndeclaredSymbolTest, testing::ValuesIn(kValueUseKinds)); INSTANTIATE_TEST_SUITE_P(Functions, ResolverDependencyGraphUndeclaredSymbolTest, testing::ValuesIn(kFuncUseKinds)); } // namespace undeclared_tests //////////////////////////////////////////////////////////////////////////////// // Self reference by decl //////////////////////////////////////////////////////////////////////////////// namespace undeclared_tests { using ResolverDependencyGraphDeclSelfUse = ResolverDependencyGraphTest; TEST_F(ResolverDependencyGraphDeclSelfUse, GlobalVar) { const Symbol symbol = Sym("SYMBOL"); GlobalVar(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123_i)); Build(R"(error: cyclic dependency found: 'SYMBOL' -> 'SYMBOL' 12:34 note: var 'SYMBOL' references var 'SYMBOL' here)"); } TEST_F(ResolverDependencyGraphDeclSelfUse, GlobalConst) { const Symbol symbol = Sym("SYMBOL"); GlobalConst(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123_i)); Build(R"(error: cyclic dependency found: 'SYMBOL' -> 'SYMBOL' 12:34 note: const 'SYMBOL' references const 'SYMBOL' here)"); } TEST_F(ResolverDependencyGraphDeclSelfUse, LocalVar) { const Symbol symbol = Sym("SYMBOL"); WrapInFunction(Decl(Var(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123_i)))); Build("12:34 error: unknown identifier: 'SYMBOL'"); } TEST_F(ResolverDependencyGraphDeclSelfUse, LocalLet) { const Symbol symbol = Sym("SYMBOL"); WrapInFunction(Decl(Let(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123_i)))); Build("12:34 error: unknown identifier: 'SYMBOL'"); } } // namespace undeclared_tests //////////////////////////////////////////////////////////////////////////////// // Recursive dependency tests //////////////////////////////////////////////////////////////////////////////// namespace recursive_tests { using ResolverDependencyGraphCyclicRefTest = ResolverDependencyGraphTest; TEST_F(ResolverDependencyGraphCyclicRefTest, DirectCall) { // fn main() { main(); } Func(Source{{12, 34}}, "main", utils::Empty, ty.void_(), utils::Vector{CallStmt(Call(Ident(Source{{56, 78}}, "main")))}); Build(R"(12:34 error: cyclic dependency found: 'main' -> 'main' 56:78 note: function 'main' calls function 'main' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, IndirectCall) { // 1: fn a() { b(); } // 2: fn e() { } // 3: fn d() { e(); b(); } // 4: fn c() { d(); } // 5: fn b() { c(); } Func(Source{{1, 1}}, "a", utils::Empty, ty.void_(), utils::Vector{CallStmt(Call(Ident(Source{{1, 10}}, "b")))}); Func(Source{{2, 1}}, "e", utils::Empty, ty.void_(), utils::Empty); Func(Source{{3, 1}}, "d", utils::Empty, ty.void_(), utils::Vector{ CallStmt(Call(Ident(Source{{3, 10}}, "e"))), CallStmt(Call(Ident(Source{{3, 10}}, "b"))), }); Func(Source{{4, 1}}, "c", utils::Empty, ty.void_(), utils::Vector{CallStmt(Call(Ident(Source{{4, 10}}, "d")))}); Func(Source{{5, 1}}, "b", utils::Empty, ty.void_(), utils::Vector{CallStmt(Call(Ident(Source{{5, 10}}, "c")))}); Build(R"(5:1 error: cyclic dependency found: 'b' -> 'c' -> 'd' -> 'b' 5:10 note: function 'b' calls function 'c' here 4:10 note: function 'c' calls function 'd' here 3:10 note: function 'd' calls function 'b' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, Alias_Direct) { // type T = T; Alias(Source{{12, 34}}, "T", ty(Source{{56, 78}}, "T")); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(12:34 error: cyclic dependency found: 'T' -> 'T' 56:78 note: alias 'T' references alias 'T' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, Alias_Indirect) { // 1: type Y = Z; // 2: type X = Y; // 3: type Z = X; Alias(Source{{1, 1}}, "Y", ty(Source{{1, 10}}, "Z")); Alias(Source{{2, 1}}, "X", ty(Source{{2, 10}}, "Y")); Alias(Source{{3, 1}}, "Z", ty(Source{{3, 10}}, "X")); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y' 1:10 note: alias 'Y' references alias 'Z' here 3:10 note: alias 'Z' references alias 'X' here 2:10 note: alias 'X' references alias 'Y' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, Struct_Direct) { // struct S { // a: S; // }; Structure(Source{{12, 34}}, "S", utils::Vector{Member("a", ty(Source{{56, 78}}, "S"))}); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(12:34 error: cyclic dependency found: 'S' -> 'S' 56:78 note: struct 'S' references struct 'S' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, Struct_Indirect) { // 1: struct Y { z: Z; }; // 2: struct X { y: Y; }; // 3: struct Z { x: X; }; Structure(Source{{1, 1}}, "Y", utils::Vector{Member("z", ty(Source{{1, 10}}, "Z"))}); Structure(Source{{2, 1}}, "X", utils::Vector{Member("y", ty(Source{{2, 10}}, "Y"))}); Structure(Source{{3, 1}}, "Z", utils::Vector{Member("x", ty(Source{{3, 10}}, "X"))}); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y' 1:10 note: struct 'Y' references struct 'Z' here 3:10 note: struct 'Z' references struct 'X' here 2:10 note: struct 'X' references struct 'Y' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalVar_Direct) { // var V : i32 = V; GlobalVar(Source{{12, 34}}, "V", ty.i32(), Expr(Source{{56, 78}}, "V")); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(12:34 error: cyclic dependency found: 'V' -> 'V' 56:78 note: var 'V' references var 'V' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalConst_Direct) { // let V : i32 = V; GlobalConst(Source{{12, 34}}, "V", ty.i32(), Expr(Source{{56, 78}}, "V")); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(12:34 error: cyclic dependency found: 'V' -> 'V' 56:78 note: const 'V' references const 'V' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalVar_Indirect) { // 1: var Y : i32 = Z; // 2: var X : i32 = Y; // 3: var Z : i32 = X; GlobalVar(Source{{1, 1}}, "Y", ty.i32(), Expr(Source{{1, 10}}, "Z")); GlobalVar(Source{{2, 1}}, "X", ty.i32(), Expr(Source{{2, 10}}, "Y")); GlobalVar(Source{{3, 1}}, "Z", ty.i32(), Expr(Source{{3, 10}}, "X")); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y' 1:10 note: var 'Y' references var 'Z' here 3:10 note: var 'Z' references var 'X' here 2:10 note: var 'X' references var 'Y' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalConst_Indirect) { // 1: const Y : i32 = Z; // 2: const X : i32 = Y; // 3: const Z : i32 = X; GlobalConst(Source{{1, 1}}, "Y", ty.i32(), Expr(Source{{1, 10}}, "Z")); GlobalConst(Source{{2, 1}}, "X", ty.i32(), Expr(Source{{2, 10}}, "Y")); GlobalConst(Source{{3, 1}}, "Z", ty.i32(), Expr(Source{{3, 10}}, "X")); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(1:1 error: cyclic dependency found: 'Y' -> 'Z' -> 'X' -> 'Y' 1:10 note: const 'Y' references const 'Z' here 3:10 note: const 'Z' references const 'X' here 2:10 note: const 'X' references const 'Y' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, Mixed_RecursiveDependencies) { // 1: fn F() -> R { return Z; } // 2: type A = S; // 3: struct S { a : A }; // 4: var Z = L; // 5: type R = A; // 6: const L : S = Z; Func(Source{{1, 1}}, "F", utils::Empty, ty(Source{{1, 5}}, "R"), utils::Vector{Return(Expr(Source{{1, 10}}, "Z"))}); Alias(Source{{2, 1}}, "A", ty(Source{{2, 10}}, "S")); Structure(Source{{3, 1}}, "S", utils::Vector{Member("a", ty(Source{{3, 10}}, "A"))}); GlobalVar(Source{{4, 1}}, "Z", Expr(Source{{4, 10}}, "L")); Alias(Source{{5, 1}}, "R", ty(Source{{5, 10}}, "A")); GlobalConst(Source{{6, 1}}, "L", ty(Source{{5, 5}}, "S"), Expr(Source{{5, 10}}, "Z")); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(2:1 error: cyclic dependency found: 'A' -> 'S' -> 'A' 2:10 note: alias 'A' references struct 'S' here 3:10 note: struct 'S' references alias 'A' here 4:1 error: cyclic dependency found: 'Z' -> 'L' -> 'Z' 4:10 note: var 'Z' references const 'L' here 5:10 note: const 'L' references var 'Z' here)"); } } // namespace recursive_tests //////////////////////////////////////////////////////////////////////////////// // Symbol Redeclaration tests //////////////////////////////////////////////////////////////////////////////// namespace redeclaration_tests { using ResolverDependencyGraphRedeclarationTest = ResolverDependencyGraphTestWithParam>; TEST_P(ResolverDependencyGraphRedeclarationTest, Test) { const auto symbol = Sym("SYMBOL"); auto a_kind = std::get<0>(GetParam()); auto b_kind = std::get<1>(GetParam()); auto a_source = Source{{12, 34}}; auto b_source = Source{{56, 78}}; if (a_kind != SymbolDeclKind::Parameter && b_kind == SymbolDeclKind::Parameter) { std::swap(a_source, b_source); // Parameters are declared before locals } SymbolTestHelper helper(this); helper.Add(a_kind, symbol, a_source); helper.Add(b_kind, symbol, b_source); helper.Build(); bool error = ScopeDepth(a_kind) == ScopeDepth(b_kind); Build(error ? R"(56:78 error: redeclaration of 'SYMBOL' 12:34 note: 'SYMBOL' previously declared here)" : ""); } INSTANTIATE_TEST_SUITE_P(ResolverTest, ResolverDependencyGraphRedeclarationTest, testing::Combine(testing::ValuesIn(kAllSymbolDeclKinds), testing::ValuesIn(kAllSymbolDeclKinds))); } // namespace redeclaration_tests //////////////////////////////////////////////////////////////////////////////// // Ordered global tests //////////////////////////////////////////////////////////////////////////////// namespace ordered_globals { using ResolverDependencyGraphOrderedGlobalsTest = ResolverDependencyGraphTestWithParam>; TEST_P(ResolverDependencyGraphOrderedGlobalsTest, InOrder) { const Symbol symbol = Sym("SYMBOL"); const auto decl_kind = std::get<0>(GetParam()); const auto use_kind = std::get<1>(GetParam()); // Declaration before use SymbolTestHelper helper(this); helper.Add(decl_kind, symbol, Source{{12, 34}}); helper.Add(use_kind, symbol, Source{{56, 78}}); helper.Build(); ASSERT_EQ(AST().GlobalDeclarations().Length(), 2u); auto* decl = AST().GlobalDeclarations()[0]; auto* use = AST().GlobalDeclarations()[1]; EXPECT_THAT(Build().ordered_globals, ElementsAre(decl, use)); } TEST_P(ResolverDependencyGraphOrderedGlobalsTest, OutOfOrder) { const Symbol symbol = Sym("SYMBOL"); const auto decl_kind = std::get<0>(GetParam()); const auto use_kind = std::get<1>(GetParam()); // Use before declaration SymbolTestHelper helper(this); helper.Add(use_kind, symbol, Source{{56, 78}}); helper.Build(); // If the use is in a function, then ensure this function is // built before the symbol declaration helper.Add(decl_kind, symbol, Source{{12, 34}}); helper.Build(); ASSERT_EQ(AST().GlobalDeclarations().Length(), 2u); auto* use = AST().GlobalDeclarations()[0]; auto* decl = AST().GlobalDeclarations()[1]; EXPECT_THAT(Build().ordered_globals, ElementsAre(decl, use)); } INSTANTIATE_TEST_SUITE_P(Types, ResolverDependencyGraphOrderedGlobalsTest, testing::Combine(testing::ValuesIn(kTypeDeclKinds), testing::ValuesIn(kTypeUseKinds))); INSTANTIATE_TEST_SUITE_P(Values, ResolverDependencyGraphOrderedGlobalsTest, testing::Combine(testing::ValuesIn(kGlobalValueDeclKinds), testing::ValuesIn(kValueUseKinds))); INSTANTIATE_TEST_SUITE_P(Functions, ResolverDependencyGraphOrderedGlobalsTest, testing::Combine(testing::ValuesIn(kFuncDeclKinds), testing::ValuesIn(kFuncUseKinds))); TEST_F(ResolverDependencyGraphOrderedGlobalsTest, DirectiveFirst) { // Test that directive nodes always go before any other global declaration. // Although all directives in a valid WGSL program must go before any other global declaration, // a transform may produce such a AST tree that has some declarations before directive nodes. // DependencyGraph should deal with these cases. auto* var_1 = GlobalVar("SYMBOL1", ty.i32()); auto* enable = Enable(builtin::Extension::kF16); auto* var_2 = GlobalVar("SYMBOL2", ty.f32()); auto* diagnostic = DiagnosticDirective(ast::DiagnosticSeverity::kWarning, "foo"); EXPECT_THAT(AST().GlobalDeclarations(), ElementsAre(var_1, enable, var_2, diagnostic)); EXPECT_THAT(Build().ordered_globals, ElementsAre(enable, diagnostic, var_1, var_2)); } } // namespace ordered_globals //////////////////////////////////////////////////////////////////////////////// // Resolve to user-declaration tests //////////////////////////////////////////////////////////////////////////////// namespace resolve_to_user_decl { using ResolverDependencyGraphResolveToUserDeclTest = ResolverDependencyGraphTestWithParam>; TEST_P(ResolverDependencyGraphResolveToUserDeclTest, Test) { const Symbol symbol = Sym("SYMBOL"); const auto decl_kind = std::get<0>(GetParam()); const auto use_kind = std::get<1>(GetParam()); // Build a symbol declaration and a use of that symbol SymbolTestHelper helper(this); auto* decl = helper.Add(decl_kind, symbol, Source{{12, 34}}); auto* use = helper.Add(use_kind, symbol, Source{{56, 78}}); helper.Build(); // If the declaration is visible to the use, then we expect the analysis to // succeed. bool expect_pass = ScopeDepth(decl_kind) <= ScopeDepth(use_kind); auto graph = Build(expect_pass ? "" : "56:78 error: unknown identifier: 'SYMBOL'"); if (expect_pass) { // Check that the use resolves to the declaration auto resolved_identifier = graph.resolved_identifiers.Find(use); ASSERT_TRUE(resolved_identifier); auto* resolved_node = resolved_identifier->Node(); EXPECT_EQ(resolved_node, decl) << "resolved: " << (resolved_node ? resolved_node->TypeInfo().name : "") << "\n" << "decl: " << decl->TypeInfo().name; } } INSTANTIATE_TEST_SUITE_P(Types, ResolverDependencyGraphResolveToUserDeclTest, testing::Combine(testing::ValuesIn(kTypeDeclKinds), testing::ValuesIn(kTypeUseKinds))); INSTANTIATE_TEST_SUITE_P(Values, ResolverDependencyGraphResolveToUserDeclTest, testing::Combine(testing::ValuesIn(kValueDeclKinds), testing::ValuesIn(kValueUseKinds))); INSTANTIATE_TEST_SUITE_P(Functions, ResolverDependencyGraphResolveToUserDeclTest, testing::Combine(testing::ValuesIn(kFuncDeclKinds), testing::ValuesIn(kFuncUseKinds))); } // namespace resolve_to_user_decl //////////////////////////////////////////////////////////////////////////////// // Resolve to builtin func tests //////////////////////////////////////////////////////////////////////////////// namespace resolve_to_builtin_func { using ResolverDependencyGraphResolveToBuiltinFunc = ResolverDependencyGraphTestWithParam>; TEST_P(ResolverDependencyGraphResolveToBuiltinFunc, Resolve) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->BuiltinFunction(), builtin) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToBuiltinFunc, ShadowedByGlobalVar) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::GlobalVar, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToBuiltinFunc, ShadowedByStruct) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::Struct, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToBuiltinFunc, ShadowedByFunc) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::Function, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } INSTANTIATE_TEST_SUITE_P(Types, ResolverDependencyGraphResolveToBuiltinFunc, testing::Combine(testing::ValuesIn(kTypeUseKinds), testing::ValuesIn(sem::kBuiltinTypes))); INSTANTIATE_TEST_SUITE_P(Values, ResolverDependencyGraphResolveToBuiltinFunc, testing::Combine(testing::ValuesIn(kValueUseKinds), testing::ValuesIn(sem::kBuiltinTypes))); INSTANTIATE_TEST_SUITE_P(Functions, ResolverDependencyGraphResolveToBuiltinFunc, testing::Combine(testing::ValuesIn(kFuncUseKinds), testing::ValuesIn(sem::kBuiltinTypes))); } // namespace resolve_to_builtin_func //////////////////////////////////////////////////////////////////////////////// // Resolve to builtin type tests //////////////////////////////////////////////////////////////////////////////// namespace resolve_to_builtin_type { using ResolverDependencyGraphResolveToBuiltinType = ResolverDependencyGraphTestWithParam>; TEST_P(ResolverDependencyGraphResolveToBuiltinType, Resolve) { const auto use = std::get<0>(GetParam()); const auto name = std::get<1>(GetParam()); const auto symbol = Symbols().New(name); SymbolTestHelper helper(this); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->BuiltinType(), type::ParseBuiltin(name)) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToBuiltinType, ShadowedByGlobalVar) { const auto use = std::get<0>(GetParam()); const std::string name = std::get<1>(GetParam()); const auto symbol = Symbols().New(name); auto* decl = GlobalVar(symbol, name == "i32" ? ty.u32() : ty.i32(), builtin::AddressSpace::kPrivate); SymbolTestHelper helper(this); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToBuiltinType, ShadowedByStruct) { const auto use = std::get<0>(GetParam()); const std::string name = std::get<1>(GetParam()); const auto symbol = Symbols().New(name); auto* decl = Structure(symbol, utils::Vector{ Member("m", name == "i32" ? ty.u32() : ty.i32()), }); SymbolTestHelper helper(this); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToBuiltinType, ShadowedByFunc) { const auto use = std::get<0>(GetParam()); const auto name = std::get<1>(GetParam()); const auto symbol = Symbols().New(name); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::Function, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } INSTANTIATE_TEST_SUITE_P(Types, ResolverDependencyGraphResolveToBuiltinType, testing::Combine(testing::ValuesIn(kTypeUseKinds), testing::ValuesIn(type::kBuiltinStrings))); INSTANTIATE_TEST_SUITE_P(Values, ResolverDependencyGraphResolveToBuiltinType, testing::Combine(testing::ValuesIn(kValueUseKinds), testing::ValuesIn(type::kBuiltinStrings))); INSTANTIATE_TEST_SUITE_P(Functions, ResolverDependencyGraphResolveToBuiltinType, testing::Combine(testing::ValuesIn(kFuncUseKinds), testing::ValuesIn(type::kBuiltinStrings))); } // namespace resolve_to_builtin_type //////////////////////////////////////////////////////////////////////////////// // Resolve to builtin::Access tests //////////////////////////////////////////////////////////////////////////////// namespace resolve_to_access { using ResolverDependencyGraphResolveToAccess = ResolverDependencyGraphTestWithParam>; TEST_P(ResolverDependencyGraphResolveToAccess, Resolve) { const auto use = std::get<0>(GetParam()); const auto name = std::get<1>(GetParam()); const auto symbol = Symbols().New(name); SymbolTestHelper helper(this); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Access(), builtin::ParseAccess(name)) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToAccess, ShadowedByGlobalVar) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::GlobalVar, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToAccess, ShadowedByStruct) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::Struct, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToAccess, ShadowedByFunc) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::Function, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } INSTANTIATE_TEST_SUITE_P(Types, ResolverDependencyGraphResolveToAccess, testing::Combine(testing::ValuesIn(kTypeUseKinds), testing::ValuesIn(builtin::kAccessStrings))); INSTANTIATE_TEST_SUITE_P(Values, ResolverDependencyGraphResolveToAccess, testing::Combine(testing::ValuesIn(kValueUseKinds), testing::ValuesIn(builtin::kAccessStrings))); INSTANTIATE_TEST_SUITE_P(Functions, ResolverDependencyGraphResolveToAccess, testing::Combine(testing::ValuesIn(kFuncUseKinds), testing::ValuesIn(builtin::kAccessStrings))); } // namespace resolve_to_access //////////////////////////////////////////////////////////////////////////////// // Resolve to builtin::AddressSpace tests //////////////////////////////////////////////////////////////////////////////// namespace resolve_to_address_space { using ResolverDependencyGraphResolveToAddressSpace = ResolverDependencyGraphTestWithParam>; TEST_P(ResolverDependencyGraphResolveToAddressSpace, Resolve) { const auto use = std::get<0>(GetParam()); const auto name = std::get<1>(GetParam()); const auto symbol = Symbols().New(name); SymbolTestHelper helper(this); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->AddressSpace(), builtin::ParseAddressSpace(name)) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToAddressSpace, ShadowedByGlobalConst) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::GlobalConst, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToAddressSpace, ShadowedByStruct) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::Struct, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToAddressSpace, ShadowedByFunc) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::Function, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } INSTANTIATE_TEST_SUITE_P(Types, ResolverDependencyGraphResolveToAddressSpace, testing::Combine(testing::ValuesIn(kTypeUseKinds), testing::ValuesIn(builtin::kAddressSpaceStrings))); INSTANTIATE_TEST_SUITE_P(Values, ResolverDependencyGraphResolveToAddressSpace, testing::Combine(testing::ValuesIn(kValueUseKinds), testing::ValuesIn(builtin::kAddressSpaceStrings))); INSTANTIATE_TEST_SUITE_P(Functions, ResolverDependencyGraphResolveToAddressSpace, testing::Combine(testing::ValuesIn(kFuncUseKinds), testing::ValuesIn(builtin::kAddressSpaceStrings))); } // namespace resolve_to_address_space //////////////////////////////////////////////////////////////////////////////// // Resolve to type::TexelFormat tests //////////////////////////////////////////////////////////////////////////////// namespace resolve_to_texel_format { using ResolverDependencyGraphResolveToTexelFormat = ResolverDependencyGraphTestWithParam>; TEST_P(ResolverDependencyGraphResolveToTexelFormat, Resolve) { const auto use = std::get<0>(GetParam()); const auto name = std::get<1>(GetParam()); const auto symbol = Symbols().New(name); SymbolTestHelper helper(this); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->TexelFormat(), type::ParseTexelFormat(name)) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToTexelFormat, ShadowedByGlobalVar) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::GlobalVar, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToTexelFormat, ShadowedByStruct) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::Struct, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } TEST_P(ResolverDependencyGraphResolveToTexelFormat, ShadowedByFunc) { const auto use = std::get<0>(GetParam()); const auto builtin = std::get<1>(GetParam()); const auto symbol = Symbols().New(utils::ToString(builtin)); SymbolTestHelper helper(this); auto* decl = helper.Add(SymbolDeclKind::Function, symbol); auto* ident = helper.Add(use, symbol); helper.Build(); auto resolved = Build().resolved_identifiers.Get(ident); ASSERT_TRUE(resolved); EXPECT_EQ(resolved->Node(), decl) << resolved->String(Symbols(), Diagnostics()); } INSTANTIATE_TEST_SUITE_P(Types, ResolverDependencyGraphResolveToTexelFormat, testing::Combine(testing::ValuesIn(kTypeUseKinds), testing::ValuesIn(type::kTexelFormatStrings))); INSTANTIATE_TEST_SUITE_P(Values, ResolverDependencyGraphResolveToTexelFormat, testing::Combine(testing::ValuesIn(kValueUseKinds), testing::ValuesIn(type::kTexelFormatStrings))); INSTANTIATE_TEST_SUITE_P(Functions, ResolverDependencyGraphResolveToTexelFormat, testing::Combine(testing::ValuesIn(kFuncUseKinds), testing::ValuesIn(type::kTexelFormatStrings))); } // namespace resolve_to_texel_format //////////////////////////////////////////////////////////////////////////////// // Shadowing tests //////////////////////////////////////////////////////////////////////////////// namespace shadowing { using ResolverDependencyGraphShadowTest = ResolverDependencyGraphTestWithParam>; TEST_P(ResolverDependencyGraphShadowTest, Test) { const Symbol symbol = Sym("SYMBOL"); const auto outer_kind = std::get<0>(GetParam()); const auto inner_kind = std::get<1>(GetParam()); // Build a symbol declaration and a use of that symbol SymbolTestHelper helper(this); auto* outer = helper.Add(outer_kind, symbol, Source{{12, 34}}); helper.Add(inner_kind, symbol, Source{{56, 78}}); auto* inner_var = helper.nested_statements.Length() ? helper.nested_statements[0]->As()->variable : helper.statements.Length() ? helper.statements[0]->As()->variable : helper.parameters[0]; helper.Build(); auto shadows = Build().shadows; auto shadow = shadows.Find(inner_var); ASSERT_TRUE(shadow); EXPECT_EQ(*shadow, outer); } INSTANTIATE_TEST_SUITE_P(LocalShadowGlobal, ResolverDependencyGraphShadowTest, testing::Combine(testing::ValuesIn(kGlobalDeclKinds), testing::ValuesIn(kLocalDeclKinds))); INSTANTIATE_TEST_SUITE_P(NestedLocalShadowLocal, ResolverDependencyGraphShadowTest, testing::Combine(testing::Values(SymbolDeclKind::Parameter, SymbolDeclKind::LocalVar, SymbolDeclKind::LocalLet), testing::Values(SymbolDeclKind::NestedLocalVar, SymbolDeclKind::NestedLocalLet))); } // namespace shadowing //////////////////////////////////////////////////////////////////////////////// // AST traversal tests //////////////////////////////////////////////////////////////////////////////// namespace ast_traversal { static const ast::Identifier* IdentifierOf(const ast::IdentifierExpression* expr) { return expr->identifier; } static const ast::Identifier* IdentifierOf(const ast::Identifier* ident) { return ident; } using ResolverDependencyGraphTraversalTest = ResolverDependencyGraphTest; TEST_F(ResolverDependencyGraphTraversalTest, SymbolsReached) { const auto value_sym = Sym("VALUE"); const auto type_sym = Sym("TYPE"); const auto func_sym = Sym("FUNC"); const auto* value_decl = GlobalVar(value_sym, ty.i32(), builtin::AddressSpace::kPrivate); const auto* type_decl = Alias(type_sym, ty.i32()); const auto* func_decl = Func(func_sym, utils::Empty, ty.void_(), utils::Empty); struct SymbolUse { const ast::Node* decl = nullptr; const ast::Identifier* use = nullptr; std::string where; }; utils::Vector symbol_uses; auto add_use = [&](const ast::Node* decl, auto use, int line, const char* kind) { symbol_uses.Push( SymbolUse{decl, IdentifierOf(use), std::string(__FILE__) + ":" + std::to_string(line) + ": " + kind}); return use; }; #define V add_use(value_decl, Expr(value_sym), __LINE__, "V()") #define T add_use(type_decl, ty(type_sym), __LINE__, "T()") #define F add_use(func_decl, Ident(func_sym), __LINE__, "F()") Alias(Sym(), T); Structure(Sym(), // utils::Vector{Member(Sym(), T, utils::Vector{ // MemberAlign(V), MemberSize(V) // })}); GlobalVar(Sym(), T, V); GlobalConst(Sym(), T, V); Func(Sym(), utils::Vector{ Param(Sym(), T, utils::Vector{ Location(V), // Parameter attributes }), }, T, // Return type utils::Vector{ Decl(Var(Sym(), T, V)), // Decl(Let(Sym(), T, V)), // CallStmt(Call(F, V)), // Block( // Assign(V, V)), // If(V, // Block(Assign(V, V)), // Else(If(V, // Block(Assign(V, V))))), // Ignore(Bitcast(T, V)), // For(Decl(Var(Sym(), T, V)), // Equal(V, V), // Assign(V, V), // Block( // Assign(V, V))), // While(Equal(V, V), // Block( // Assign(V, V))), // Loop(Block(Assign(V, V)), // Block(Assign(V, V), BreakIf(V))), // Switch(V, // Case(CaseSelector(1_i), // Block(Assign(V, V))), // DefaultCase(Block(Assign(V, V)))), // Return(V), // Break(), // Discard(), // }, utils::Empty, // function attributes utils::Vector{Location(V)}); // return attributes // Exercise type traversal GlobalVar(Sym(), ty.atomic(T)); GlobalVar(Sym(), ty.bool_()); GlobalVar(Sym(), ty.i32()); GlobalVar(Sym(), ty.u32()); GlobalVar(Sym(), ty.f32()); GlobalVar(Sym(), ty.array(T, V)); GlobalVar(Sym(), ty.vec3(T)); GlobalVar(Sym(), ty.mat3x2(T)); GlobalVar(Sym(), ty.pointer(T, builtin::AddressSpace::kPrivate)); GlobalVar(Sym(), ty.sampled_texture(type::TextureDimension::k2d, T)); GlobalVar(Sym(), ty.depth_texture(type::TextureDimension::k2d)); GlobalVar(Sym(), ty.depth_multisampled_texture(type::TextureDimension::k2d)); GlobalVar(Sym(), ty.external_texture()); GlobalVar(Sym(), ty.multisampled_texture(type::TextureDimension::k2d, T)); GlobalVar(Sym(), ty.storage_texture(type::TextureDimension::k2d, type::TexelFormat::kR32Float, builtin::Access::kRead)); GlobalVar(Sym(), ty.sampler(type::SamplerKind::kSampler)); GlobalVar(Sym(), ty.i32(), utils::Vector{Binding(V), Group(V)}); GlobalVar(Sym(), ty.i32(), utils::Vector{Location(V)}); Override(Sym(), ty.i32(), utils::Vector{Id(V)}); Func(Sym(), utils::Empty, ty.void_(), utils::Empty); #undef V #undef T #undef F auto graph = Build(); for (auto use : symbol_uses) { auto resolved_identifier = graph.resolved_identifiers.Find(use.use); ASSERT_TRUE(resolved_identifier) << use.where; EXPECT_EQ(*resolved_identifier, use.decl) << use.where; } } TEST_F(ResolverDependencyGraphTraversalTest, InferredType) { // Check that the nullptr of the var / const / let type doesn't make things explode GlobalVar("a", Expr(1_i)); GlobalConst("b", Expr(1_i)); WrapInFunction(Var("c", Expr(1_i)), // Let("d", Expr(1_i))); Build(); } // Reproduces an unbalanced stack push / pop bug in // DependencyAnalysis::SortGlobals(), found by clusterfuzz. // See: crbug.com/chromium/1273451 TEST_F(ResolverDependencyGraphTraversalTest, chromium_1273451) { Structure("A", utils::Vector{Member("a", ty.i32())}); Structure("B", utils::Vector{Member("b", ty.i32())}); Func("f", utils::Vector{Param("a", ty("A"))}, ty("B"), utils::Vector{ Return(Call("B")), }); Build(); } } // namespace ast_traversal } // namespace } // namespace tint::resolver