|  | // 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 <string> | 
|  | #include <tuple> | 
|  | #include <utility> | 
|  |  | 
|  | #include "gmock/gmock.h" | 
|  | #include "src/tint/resolver/dependency_graph.h" | 
|  | #include "src/tint/resolver/resolver_test_helper.h" | 
|  |  | 
|  | using namespace tint::number_suffixes;  // NOLINT | 
|  |  | 
|  | namespace tint::resolver { | 
|  | namespace { | 
|  |  | 
|  | using ::testing::ElementsAre; | 
|  |  | 
|  | template <typename T> | 
|  | class ResolverDependencyGraphTestWithParam : public ResolverTestWithParam<T> { | 
|  | 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 << "<unknown>"; | 
|  | } | 
|  |  | 
|  | /// @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 << "<unknown>"; | 
|  | } | 
|  |  | 
|  | /// @returns the the diagnostic message name used for the given use | 
|  | std::string DiagString(SymbolUseKind kind) { | 
|  | switch (kind) { | 
|  | case SymbolUseKind::GlobalVarType: | 
|  | case SymbolUseKind::GlobalVarArrayElemType: | 
|  | case SymbolUseKind::GlobalVarVectorElemType: | 
|  | case SymbolUseKind::GlobalVarMatrixElemType: | 
|  | case SymbolUseKind::GlobalVarSampledTexElemType: | 
|  | case SymbolUseKind::GlobalVarMultisampledTexElemType: | 
|  | case SymbolUseKind::GlobalConstType: | 
|  | case SymbolUseKind::GlobalConstArrayElemType: | 
|  | case SymbolUseKind::GlobalConstVectorElemType: | 
|  | case SymbolUseKind::GlobalConstMatrixElemType: | 
|  | case SymbolUseKind::AliasType: | 
|  | case SymbolUseKind::StructMemberType: | 
|  | case SymbolUseKind::ParameterType: | 
|  | case SymbolUseKind::LocalVarType: | 
|  | case SymbolUseKind::LocalVarArrayElemType: | 
|  | case SymbolUseKind::LocalVarVectorElemType: | 
|  | case SymbolUseKind::LocalVarMatrixElemType: | 
|  | case SymbolUseKind::LocalLetType: | 
|  | case SymbolUseKind::NestedLocalVarType: | 
|  | case SymbolUseKind::NestedLocalLetType: | 
|  | return "type"; | 
|  | 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 "<unknown>"; | 
|  | } | 
|  |  | 
|  | /// @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<const ast::Parameter*, 8> parameters; | 
|  | /// Shallow function var / let declaration statements | 
|  | utils::Vector<const ast::Statement*, 8> statements; | 
|  | /// Nested function local var / let declaration statements | 
|  | utils::Vector<const ast::Statement*, 8> nested_statements; | 
|  | /// Function attributes | 
|  | utils::Vector<const ast::Attribute*, 8> func_attrs; | 
|  |  | 
|  | /// Constructor | 
|  | /// @param builder the program builder | 
|  | explicit SymbolTestHelper(ProgramBuilder* builder); | 
|  |  | 
|  | /// Destructor. | 
|  | ~SymbolTestHelper(); | 
|  |  | 
|  | /// Declares a symbol with the given kind | 
|  | /// @param kind the kind of symbol declaration | 
|  | /// @param symbol the symbol to use for the declaration | 
|  | /// @param source the source of the declaration | 
|  | /// @returns the declaration node | 
|  | const ast::Node* Add(SymbolDeclKind kind, Symbol symbol, Source source); | 
|  |  | 
|  | /// Declares a use of a symbol 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::Node* 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(), ast::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::Node* SymbolTestHelper::Add(SymbolUseKind kind, Symbol symbol, Source source) { | 
|  | auto& b = *builder; | 
|  | switch (kind) { | 
|  | case SymbolUseKind::GlobalVarType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalVar(b.Sym(), node, ast::AddressSpace::kPrivate); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalVarArrayElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalVar(b.Sym(), b.ty.array(node, 4_i), ast::AddressSpace::kPrivate); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalVarArraySizeValue: { | 
|  | auto* node = b.Expr(source, symbol); | 
|  | b.GlobalVar(b.Sym(), b.ty.array(b.ty.i32(), node), ast::AddressSpace::kPrivate); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalVarVectorElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalVar(b.Sym(), b.ty.vec3(node), ast::AddressSpace::kPrivate); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalVarMatrixElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalVar(b.Sym(), b.ty.mat3x4(node), ast::AddressSpace::kPrivate); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalVarSampledTexElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalVar(b.Sym(), b.ty.sampled_texture(ast::TextureDimension::k2d, node)); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalVarMultisampledTexElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalVar(b.Sym(), b.ty.multisampled_texture(ast::TextureDimension::k2d, node)); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalVarValue: { | 
|  | auto* node = b.Expr(source, symbol); | 
|  | b.GlobalVar(b.Sym(), b.ty.i32(), ast::AddressSpace::kPrivate, node); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalConstType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalConst(b.Sym(), node, b.Expr(1_i)); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalConstArrayElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalConst(b.Sym(), b.ty.array(node, 4_i), b.Expr(1_i)); | 
|  | return node; | 
|  | } | 
|  | 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; | 
|  | } | 
|  | case SymbolUseKind::GlobalConstVectorElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalConst(b.Sym(), b.ty.vec3(node), b.Expr(1_i)); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalConstMatrixElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.GlobalConst(b.Sym(), b.ty.mat3x4(node), b.Expr(1_i)); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::GlobalConstValue: { | 
|  | auto* node = b.Expr(source, symbol); | 
|  | b.GlobalConst(b.Sym(), b.ty.i32(), node); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::AliasType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.Alias(b.Sym(), node); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::StructMemberType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | b.Structure(b.Sym(), utils::Vector{b.Member("m", node)}); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::CallFunction: { | 
|  | auto* node = b.Expr(source, symbol); | 
|  | statements.Push(b.CallStmt(b.Call(node))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::ParameterType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | parameters.Push(b.Param(b.Sym(), node)); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::LocalVarType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | statements.Push(b.Decl(b.Var(b.Sym(), node))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::LocalVarArrayElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | statements.Push(b.Decl(b.Var(b.Sym(), b.ty.array(node, 4_u), b.Expr(1_i)))); | 
|  | return node; | 
|  | } | 
|  | 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; | 
|  | } | 
|  | case SymbolUseKind::LocalVarVectorElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | statements.Push(b.Decl(b.Var(b.Sym(), b.ty.vec3(node)))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::LocalVarMatrixElemType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | statements.Push(b.Decl(b.Var(b.Sym(), b.ty.mat3x4(node)))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::LocalVarValue: { | 
|  | auto* node = b.Expr(source, symbol); | 
|  | statements.Push(b.Decl(b.Var(b.Sym(), b.ty.i32(), node))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::LocalLetType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | statements.Push(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i)))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::LocalLetValue: { | 
|  | auto* node = b.Expr(source, symbol); | 
|  | statements.Push(b.Decl(b.Let(b.Sym(), b.ty.i32(), node))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::NestedLocalVarType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | nested_statements.Push(b.Decl(b.Var(b.Sym(), node))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::NestedLocalVarValue: { | 
|  | auto* node = b.Expr(source, symbol); | 
|  | nested_statements.Push(b.Decl(b.Var(b.Sym(), b.ty.i32(), node))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::NestedLocalLetType: { | 
|  | auto* node = b.ty.type_name(source, symbol); | 
|  | nested_statements.Push(b.Decl(b.Let(b.Sym(), node, b.Expr(1_i)))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::NestedLocalLetValue: { | 
|  | auto* node = b.Expr(source, symbol); | 
|  | nested_statements.Push(b.Decl(b.Let(b.Sym(), b.ty.i32(), node))); | 
|  | return node; | 
|  | } | 
|  | case SymbolUseKind::WorkgroupSizeValue: { | 
|  | auto* node = b.Expr(source, symbol); | 
|  | func_attrs.Push(b.WorkgroupSize(1_i, node, 2_i)); | 
|  | return node; | 
|  | } | 
|  | } | 
|  | 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-declarated 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(Expr(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(Construct(ty.type_name(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.type_name(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.type_name(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.type_name(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.type_name(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(), ast::AddressSpace::kPrivate, Expr(2.1_f)); | 
|  |  | 
|  | Build(); | 
|  | } | 
|  |  | 
|  | }  // namespace used_before_decl_tests | 
|  |  | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | // Undeclared symbol tests | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | namespace undeclared_tests { | 
|  |  | 
|  | using ResolverDependencyGraphUndeclaredSymbolTest = | 
|  | ResolverDependencyGraphTestWithParam<SymbolUseKind>; | 
|  |  | 
|  | 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(Expr(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(Expr(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(Expr(Source{{3, 10}}, "e"))), | 
|  | CallStmt(Call(Expr(Source{{3, 10}}, "b"))), | 
|  | }); | 
|  | Func(Source{{4, 1}}, "c", utils::Empty, ty.void_(), | 
|  | utils::Vector{CallStmt(Call(Expr(Source{{4, 10}}, "d")))}); | 
|  | Func(Source{{5, 1}}, "b", utils::Empty, ty.void_(), | 
|  | utils::Vector{CallStmt(Call(Expr(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.type_name(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.type_name(Source{{1, 10}}, "Z")); | 
|  | Alias(Source{{2, 1}}, "X", ty.type_name(Source{{2, 10}}, "Y")); | 
|  | Alias(Source{{3, 1}}, "Z", ty.type_name(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.type_name(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.type_name(Source{{1, 10}}, "Z"))}); | 
|  | Structure(Source{{2, 1}}, "X", utils::Vector{Member("y", ty.type_name(Source{{2, 10}}, "Y"))}); | 
|  | Structure(Source{{3, 1}}, "Z", utils::Vector{Member("x", ty.type_name(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<private> 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<private> Y : i32 = Z; | 
|  | // 2: var<private> X : i32 = Y; | 
|  | // 3: var<private> 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.type_name(Source{{1, 5}}, "R"), | 
|  | utils::Vector{Return(Expr(Source{{1, 10}}, "Z"))}); | 
|  | Alias(Source{{2, 1}}, "A", ty.type_name(Source{{2, 10}}, "S")); | 
|  | Structure(Source{{3, 1}}, "S", utils::Vector{Member("a", ty.type_name(Source{{3, 10}}, "A"))}); | 
|  | GlobalVar(Source{{4, 1}}, "Z", Expr(Source{{4, 10}}, "L")); | 
|  | Alias(Source{{5, 1}}, "R", ty.type_name(Source{{5, 10}}, "A")); | 
|  | GlobalConst(Source{{6, 1}}, "L", ty.type_name(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<std::tuple<SymbolDeclKind, SymbolDeclKind>>; | 
|  |  | 
|  | 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<std::tuple<SymbolDeclKind, SymbolUseKind>>; | 
|  |  | 
|  | 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, EnableFirst) { | 
|  | // Test that enable nodes always go before any other global declaration. | 
|  | // Although all enable 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 enable | 
|  | // nodes. DependencyGraph should deal with these cases. | 
|  | auto* var_1 = GlobalVar("SYMBOL1", ty.i32()); | 
|  | auto* enable_1 = Enable(ast::Extension::kF16); | 
|  | auto* var_2 = GlobalVar("SYMBOL2", ty.f32()); | 
|  | auto* enable_2 = Enable(ast::Extension::kF16); | 
|  |  | 
|  | EXPECT_THAT(AST().GlobalDeclarations(), ElementsAre(var_1, enable_1, var_2, enable_2)); | 
|  | EXPECT_THAT(Build().ordered_globals, ElementsAre(enable_1, enable_2, var_1, var_2)); | 
|  | } | 
|  | }  // namespace ordered_globals | 
|  |  | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | // Resolved symbols tests | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | namespace resolved_symbols { | 
|  |  | 
|  | using ResolverDependencyGraphResolvedSymbolTest = | 
|  | ResolverDependencyGraphTestWithParam<std::tuple<SymbolDeclKind, SymbolUseKind>>; | 
|  |  | 
|  | TEST_P(ResolverDependencyGraphResolvedSymbolTest, 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_symbol = graph.resolved_symbols.Find(use); | 
|  | ASSERT_TRUE(resolved_symbol); | 
|  | EXPECT_EQ(*resolved_symbol, decl) | 
|  | << "resolved: " << (*resolved_symbol ? (*resolved_symbol)->TypeInfo().name : "<null>") | 
|  | << "\n" | 
|  | << "decl:     " << decl->TypeInfo().name; | 
|  | } | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(Types, | 
|  | ResolverDependencyGraphResolvedSymbolTest, | 
|  | testing::Combine(testing::ValuesIn(kTypeDeclKinds), | 
|  | testing::ValuesIn(kTypeUseKinds))); | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(Values, | 
|  | ResolverDependencyGraphResolvedSymbolTest, | 
|  | testing::Combine(testing::ValuesIn(kValueDeclKinds), | 
|  | testing::ValuesIn(kValueUseKinds))); | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(Functions, | 
|  | ResolverDependencyGraphResolvedSymbolTest, | 
|  | testing::Combine(testing::ValuesIn(kFuncDeclKinds), | 
|  | testing::ValuesIn(kFuncUseKinds))); | 
|  |  | 
|  | }  // namespace resolved_symbols | 
|  |  | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | // Shadowing tests | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | namespace shadowing { | 
|  |  | 
|  | using ResolverDependencyShadowTest = | 
|  | ResolverDependencyGraphTestWithParam<std::tuple<SymbolDeclKind, SymbolDeclKind>>; | 
|  |  | 
|  | TEST_P(ResolverDependencyShadowTest, 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<ast::VariableDeclStatement>()->variable | 
|  | : helper.statements.Length() | 
|  | ? helper.statements[0]->As<ast::VariableDeclStatement>()->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, | 
|  | ResolverDependencyShadowTest, | 
|  | testing::Combine(testing::ValuesIn(kGlobalDeclKinds), | 
|  | testing::ValuesIn(kLocalDeclKinds))); | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(NestedLocalShadowLocal, | 
|  | ResolverDependencyShadowTest, | 
|  | testing::Combine(testing::Values(SymbolDeclKind::Parameter, | 
|  | SymbolDeclKind::LocalVar, | 
|  | SymbolDeclKind::LocalLet), | 
|  | testing::Values(SymbolDeclKind::NestedLocalVar, | 
|  | SymbolDeclKind::NestedLocalLet))); | 
|  |  | 
|  | }  // namespace shadowing | 
|  |  | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | // AST traversal tests | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | namespace ast_traversal { | 
|  |  | 
|  | 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(), ast::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::Node* use = nullptr; | 
|  | std::string where; | 
|  | }; | 
|  |  | 
|  | utils::Vector<SymbolUse, 64> symbol_uses; | 
|  |  | 
|  | auto add_use = [&](const ast::Node* decl, auto* use, int line, const char* kind) { | 
|  | symbol_uses.Push( | 
|  | SymbolUse{decl, 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_name(type_sym), __LINE__, "T()") | 
|  | #define F add_use(func_decl, Expr(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, 4)); | 
|  | GlobalVar(Sym(), ty.vec3(T)); | 
|  | GlobalVar(Sym(), ty.mat3x2(T)); | 
|  | GlobalVar(Sym(), ty.pointer(T, ast::AddressSpace::kPrivate)); | 
|  | GlobalVar(Sym(), ty.sampled_texture(ast::TextureDimension::k2d, T)); | 
|  | GlobalVar(Sym(), ty.depth_texture(ast::TextureDimension::k2d)); | 
|  | GlobalVar(Sym(), ty.depth_multisampled_texture(ast::TextureDimension::k2d)); | 
|  | GlobalVar(Sym(), ty.external_texture()); | 
|  | GlobalVar(Sym(), ty.multisampled_texture(ast::TextureDimension::k2d, T)); | 
|  | GlobalVar(Sym(), ty.storage_texture(ast::TextureDimension::k2d, ast::TexelFormat::kR32Float, | 
|  | ast::Access::kRead));  // | 
|  | GlobalVar(Sym(), ty.sampler(ast::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_symbol = graph.resolved_symbols.Find(use.use); | 
|  | ASSERT_TRUE(resolved_symbol) << use.where; | 
|  | EXPECT_EQ(*resolved_symbol, 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.type_name("A"))}, ty.type_name("B"), | 
|  | utils::Vector{ | 
|  | Return(Construct(ty.type_name("B"))), | 
|  | }); | 
|  | Build(); | 
|  | } | 
|  |  | 
|  | }  // namespace ast_traversal | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace tint::resolver |