// 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/resolver/dependency_graph.h" #include "src/resolver/resolver_test_helper.h" namespace tint { namespace resolver { namespace { using ::testing::ElementsAre; template class ResolverDependencyGraphTestWithParam : public ResolverTestWithParam { public: bool allow_out_of_order_decls = true; DependencyGraph Build(std::string expected_error = "") { DependencyGraph graph; auto result = DependencyGraph::Build(this->AST(), this->Symbols(), this->Diagnostics(), graph, allow_out_of_order_decls); 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, GlobalLet, Alias, Struct, Function, Parameter, LocalVar, LocalLet, NestedLocalVar, NestedLocalLet, }; static constexpr SymbolDeclKind kAllSymbolDeclKinds[] = { SymbolDeclKind::GlobalVar, SymbolDeclKind::GlobalLet, 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::GlobalLet, SymbolDeclKind::Parameter, SymbolDeclKind::LocalVar, SymbolDeclKind::LocalLet, SymbolDeclKind::NestedLocalVar, SymbolDeclKind::NestedLocalLet, }; static constexpr SymbolDeclKind kGlobalDeclKinds[] = { SymbolDeclKind::GlobalVar, SymbolDeclKind::GlobalLet, 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::GlobalLet, }; 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, GlobalLetType, GlobalLetArrayElemType, GlobalLetArraySizeValue, GlobalLetVectorElemType, GlobalLetMatrixElemType, GlobalLetValue, 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::GlobalLetType, SymbolUseKind::GlobalLetArrayElemType, SymbolUseKind::GlobalLetArraySizeValue, SymbolUseKind::GlobalLetVectorElemType, SymbolUseKind::GlobalLetMatrixElemType, 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::GlobalLetValue, 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::GlobalLet: return out << "global let"; 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::GlobalLetType: return out << "global let type"; case SymbolUseKind::GlobalLetValue: return out << "global let value"; case SymbolUseKind::GlobalLetArrayElemType: return out << "global let array element type"; case SymbolUseKind::GlobalLetArraySizeValue: return out << "global let array size value"; case SymbolUseKind::GlobalLetVectorElemType: return out << "global let vector element type"; case SymbolUseKind::GlobalLetMatrixElemType: return out << "global let 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::GlobalVarArrayElemType: case SymbolUseKind::GlobalVarVectorElemType: case SymbolUseKind::GlobalVarMatrixElemType: case SymbolUseKind::GlobalVarSampledTexElemType: case SymbolUseKind::GlobalVarMultisampledTexElemType: case SymbolUseKind::GlobalLetType: case SymbolUseKind::GlobalLetArrayElemType: case SymbolUseKind::GlobalLetVectorElemType: case SymbolUseKind::GlobalLetMatrixElemType: 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::GlobalLetValue: case SymbolUseKind::GlobalLetArraySizeValue: 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::GlobalLet: 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::GlobalLetType: case SymbolUseKind::GlobalLetValue: case SymbolUseKind::GlobalLetArrayElemType: case SymbolUseKind::GlobalLetArraySizeValue: case SymbolUseKind::GlobalLetVectorElemType: case SymbolUseKind::GlobalLetMatrixElemType: 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 std::vector parameters; /// Shallow function var / let declaration statements std::vector statements; /// Nested function local var / let declaration statements std::vector nested_statements; /// Function decorations ast::DecorationList func_decos; /// 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.Global(source, symbol, b.ty.i32(), ast::StorageClass::kPrivate); case SymbolDeclKind::GlobalLet: return b.GlobalConst(source, symbol, b.ty.i32(), b.Expr(1)); case SymbolDeclKind::Alias: return b.Alias(source, symbol, b.ty.i32()); case SymbolDeclKind::Struct: return b.Structure(source, symbol, {b.Member("m", b.ty.i32())}); case SymbolDeclKind::Function: return b.Func(source, symbol, {}, b.ty.void_(), {}); case SymbolDeclKind::Parameter: { auto* node = b.Param(source, symbol, b.ty.i32()); parameters.emplace_back(node); return node; } case SymbolDeclKind::LocalVar: { auto* node = b.Var(source, symbol, b.ty.i32()); statements.emplace_back(b.Decl(node)); return node; } case SymbolDeclKind::LocalLet: { auto* node = b.Const(source, symbol, b.ty.i32(), b.Expr(1)); statements.emplace_back(b.Decl(node)); return node; } case SymbolDeclKind::NestedLocalVar: { auto* node = b.Var(source, symbol, b.ty.i32()); nested_statements.emplace_back(b.Decl(node)); return node; } case SymbolDeclKind::NestedLocalLet: { auto* node = b.Const(source, symbol, b.ty.i32(), b.Expr(1)); nested_statements.emplace_back(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.Global(b.Sym(), node, ast::StorageClass::kPrivate); return node; } case SymbolUseKind::GlobalVarArrayElemType: { auto* node = b.ty.type_name(source, symbol); b.Global(b.Sym(), b.ty.array(node, 4), ast::StorageClass::kPrivate); return node; } case SymbolUseKind::GlobalVarArraySizeValue: { auto* node = b.Expr(source, symbol); b.Global(b.Sym(), b.ty.array(b.ty.i32(), node), ast::StorageClass::kPrivate); return node; } case SymbolUseKind::GlobalVarVectorElemType: { auto* node = b.ty.type_name(source, symbol); b.Global(b.Sym(), b.ty.vec3(node), ast::StorageClass::kPrivate); return node; } case SymbolUseKind::GlobalVarMatrixElemType: { auto* node = b.ty.type_name(source, symbol); b.Global(b.Sym(), b.ty.mat3x4(node), ast::StorageClass::kPrivate); return node; } case SymbolUseKind::GlobalVarSampledTexElemType: { auto* node = b.ty.type_name(source, symbol); b.Global(b.Sym(), b.ty.sampled_texture(ast::TextureDimension::k2d, node)); return node; } case SymbolUseKind::GlobalVarMultisampledTexElemType: { auto* node = b.ty.type_name(source, symbol); b.Global(b.Sym(), b.ty.multisampled_texture(ast::TextureDimension::k2d, node)); return node; } case SymbolUseKind::GlobalVarValue: { auto* node = b.Expr(source, symbol); b.Global(b.Sym(), b.ty.i32(), ast::StorageClass::kPrivate, node); return node; } case SymbolUseKind::GlobalLetType: { auto* node = b.ty.type_name(source, symbol); b.GlobalConst(b.Sym(), node, b.Expr(1)); return node; } case SymbolUseKind::GlobalLetArrayElemType: { auto* node = b.ty.type_name(source, symbol); b.GlobalConst(b.Sym(), b.ty.array(node, 4), b.Expr(1)); return node; } case SymbolUseKind::GlobalLetArraySizeValue: { auto* node = b.Expr(source, symbol); b.GlobalConst(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1)); return node; } case SymbolUseKind::GlobalLetVectorElemType: { auto* node = b.ty.type_name(source, symbol); b.GlobalConst(b.Sym(), b.ty.vec3(node), b.Expr(1)); return node; } case SymbolUseKind::GlobalLetMatrixElemType: { auto* node = b.ty.type_name(source, symbol); b.GlobalConst(b.Sym(), b.ty.mat3x4(node), b.Expr(1)); return node; } case SymbolUseKind::GlobalLetValue: { 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(), {b.Member("m", node)}); return node; } case SymbolUseKind::CallFunction: { auto* node = b.Expr(source, symbol); statements.emplace_back(b.CallStmt(b.Call(node))); return node; } case SymbolUseKind::ParameterType: { auto* node = b.ty.type_name(source, symbol); parameters.emplace_back(b.Param(b.Sym(), node)); return node; } case SymbolUseKind::LocalVarType: { auto* node = b.ty.type_name(source, symbol); statements.emplace_back(b.Decl(b.Var(b.Sym(), node))); return node; } case SymbolUseKind::LocalVarArrayElemType: { auto* node = b.ty.type_name(source, symbol); statements.emplace_back( b.Decl(b.Var(b.Sym(), b.ty.array(node, 4), b.Expr(1)))); return node; } case SymbolUseKind::LocalVarArraySizeValue: { auto* node = b.Expr(source, symbol); statements.emplace_back( b.Decl(b.Var(b.Sym(), b.ty.array(b.ty.i32(), node), b.Expr(1)))); return node; } case SymbolUseKind::LocalVarVectorElemType: { auto* node = b.ty.type_name(source, symbol); statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.vec3(node)))); return node; } case SymbolUseKind::LocalVarMatrixElemType: { auto* node = b.ty.type_name(source, symbol); statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.mat3x4(node)))); return node; } case SymbolUseKind::LocalVarValue: { auto* node = b.Expr(source, symbol); statements.emplace_back(b.Decl(b.Var(b.Sym(), b.ty.i32(), node))); return node; } case SymbolUseKind::LocalLetType: { auto* node = b.ty.type_name(source, symbol); statements.emplace_back(b.Decl(b.Const(b.Sym(), node, b.Expr(1)))); return node; } case SymbolUseKind::LocalLetValue: { auto* node = b.Expr(source, symbol); statements.emplace_back(b.Decl(b.Const(b.Sym(), b.ty.i32(), node))); return node; } case SymbolUseKind::NestedLocalVarType: { auto* node = b.ty.type_name(source, symbol); nested_statements.emplace_back(b.Decl(b.Var(b.Sym(), node))); return node; } case SymbolUseKind::NestedLocalVarValue: { auto* node = b.Expr(source, symbol); nested_statements.emplace_back(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.emplace_back(b.Decl(b.Const(b.Sym(), node, b.Expr(1)))); return node; } case SymbolUseKind::NestedLocalLetValue: { auto* node = b.Expr(source, symbol); nested_statements.emplace_back( b.Decl(b.Const(b.Sym(), b.ty.i32(), node))); return node; } case SymbolUseKind::WorkgroupSizeValue: { auto* node = b.Expr(source, symbol); func_decos.emplace_back(b.WorkgroupSize(1, node, 2)); return node; } } return nullptr; } void SymbolTestHelper::Build() { auto& b = *builder; if (!nested_statements.empty()) { statements.emplace_back(b.Block(nested_statements)); nested_statements.clear(); } if (!parameters.empty() || !statements.empty() || !func_decos.empty()) { b.Func("func", parameters, b.ty.void_(), statements, func_decos); parameters.clear(); statements.clear(); func_decos.clear(); } } //////////////////////////////////////////////////////////////////////////////// // Used-before-declarated tests //////////////////////////////////////////////////////////////////////////////// namespace used_before_decl_tests { class ResolverDependencyGraphUsedBeforeDeclTest : public ResolverDependencyGraphTest { public: ResolverDependencyGraphUsedBeforeDeclTest() { allow_out_of_order_decls = false; } }; TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, FuncCall) { // fn A() { B(); } // fn B() {} Func("A", {}, ty.void_(), {CallStmt(Call(Expr(Source{{12, 34}}, "B")))}); Func(Source{{56, 78}}, "B", {}, ty.void_(), {Return()}); Build(R"(12:34 error: function 'B' used before it has been declared 56:78 note: function 'B' declared here)"); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeConstructed) { // fn F() { // { _ = T(); } // } // type T = i32; Func("F", {}, ty.void_(), {Block(Ignore(Construct(ty.type_name(Source{{12, 34}}, "T"))))}); Alias(Source{{56, 78}}, "T", ty.i32()); Build(R"(12:34 error: alias 'T' used before it has been declared 56:78 note: alias 'T' declared here)"); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedByLocal) { // fn F() { // { var v : T; } // } // type T = i32; Func("F", {}, ty.void_(), {Block(Decl(Var("v", ty.type_name(Source{{12, 34}}, "T"))))}); Alias(Source{{56, 78}}, "T", ty.i32()); Build(R"(12:34 error: alias 'T' used before it has been declared 56:78 note: alias 'T' declared here)"); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedByParam) { // fn F(p : T) {} // type T = i32; Func("F", {Param("p", ty.type_name(Source{{12, 34}}, "T"))}, ty.void_(), {}); Alias(Source{{56, 78}}, "T", ty.i32()); Build(R"(12:34 error: alias 'T' used before it has been declared 56:78 note: alias 'T' declared here)"); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeUsedAsReturnType) { // fn F() -> T {} // type T = i32; Func("F", {}, ty.type_name(Source{{12, 34}}, "T"), {}); Alias(Source{{56, 78}}, "T", ty.i32()); Build(R"(12:34 error: alias 'T' used before it has been declared 56:78 note: alias 'T' declared here)"); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, TypeByStructMember) { // struct S { m : T }; // type T = i32; Structure("S", {Member("m", ty.type_name(Source{{12, 34}}, "T"))}); Alias(Source{{56, 78}}, "T", ty.i32()); Build(R"(12:34 error: alias 'T' used before it has been declared 56:78 note: alias 'T' declared here)"); } TEST_F(ResolverDependencyGraphUsedBeforeDeclTest, VarUsed) { // fn F() { // { G = 3.14f; } // } // var G: f32 = 2.1; Func("F", ast::VariableList{}, ty.void_(), {Block(Assign(Expr(Source{{12, 34}}, "G"), 3.14f))}); Global(Source{{56, 78}}, "G", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f)); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(12:34 error: var 'G' used before it has been declared 56:78 note: var 'G' declared here)"); } } // 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"); Global(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123)); Build(R"(error: cyclic dependency found: 'SYMBOL' -> 'SYMBOL' 12:34 note: var 'SYMBOL' references var 'SYMBOL' here)"); } TEST_F(ResolverDependencyGraphDeclSelfUse, GlobalLet) { const Symbol symbol = Sym("SYMBOL"); GlobalConst(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123)); Build(R"(error: cyclic dependency found: 'SYMBOL' -> 'SYMBOL' 12:34 note: let 'SYMBOL' references let 'SYMBOL' here)"); } TEST_F(ResolverDependencyGraphDeclSelfUse, LocalVar) { const Symbol symbol = Sym("SYMBOL"); WrapInFunction( Decl(Var(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123)))); Build("12:34 error: unknown identifier: 'SYMBOL'"); } TEST_F(ResolverDependencyGraphDeclSelfUse, LocalLet) { const Symbol symbol = Sym("SYMBOL"); WrapInFunction( Decl(Const(symbol, ty.i32(), Mul(Expr(Source{{12, 34}}, symbol), 123)))); 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", {}, ty.void_(), {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", {}, ty.void_(), {CallStmt(Call(Expr(Source{{1, 10}}, "b")))}); Func(Source{{2, 1}}, "e", {}, ty.void_(), {}); Func(Source{{3, 1}}, "d", {}, ty.void_(), { CallStmt(Call(Expr(Source{{3, 10}}, "e"))), CallStmt(Call(Expr(Source{{3, 10}}, "b"))), }); Func(Source{{4, 1}}, "c", {}, ty.void_(), {CallStmt(Call(Expr(Source{{4, 10}}, "d")))}); Func(Source{{5, 1}}, "b", {}, ty.void_(), {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", {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", {Member("z", ty.type_name(Source{{1, 10}}, "Z"))}); Structure(Source{{2, 1}}, "X", {Member("y", ty.type_name(Source{{2, 10}}, "Y"))}); Structure(Source{{3, 1}}, "Z", {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 V : i32 = V; Global(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, GlobalLet_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: let 'V' references let 'V' here)"); } TEST_F(ResolverDependencyGraphCyclicRefTest, GlobalVar_Indirect) { // 1: var Y : i32 = Z; // 2: var X : i32 = Y; // 3: var Z : i32 = X; Global(Source{{1, 1}}, "Y", ty.i32(), Expr(Source{{1, 10}}, "Z")); Global(Source{{2, 1}}, "X", ty.i32(), Expr(Source{{2, 10}}, "Y")); Global(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, GlobalLet_Indirect) { // 1: let Y : i32 = Z; // 2: let X : i32 = Y; // 3: let 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: let 'Y' references let 'Z' here 3:10 note: let 'Z' references let 'X' here 2:10 note: let 'X' references let '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: let L : S = Z; Func(Source{{1, 1}}, "F", {}, ty.type_name(Source{{1, 5}}, "R"), {Return(Expr(Source{{1, 10}}, "Z"))}); Alias(Source{{2, 1}}, "A", ty.type_name(Source{{2, 10}}, "S")); Structure(Source{{3, 1}}, "S", {Member("a", ty.type_name(Source{{3, 10}}, "A"))}); Global(Source{{4, 1}}, "Z", nullptr, 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"(3:1 error: cyclic dependency found: 'S' -> 'A' -> 'S' 3:10 note: struct 'S' references alias 'A' here 2:10 note: alias 'A' references struct 'S' here 4:1 error: cyclic dependency found: 'Z' -> 'L' -> 'Z' 4:10 note: var 'Z' references let 'L' here 5:10 note: let 'L' references var 'Z' here)"); } } // namespace recursive_tests //////////////////////////////////////////////////////////////////////////////// // Symbol Redeclaration tests //////////////////////////////////////////////////////////////////////////////// namespace redeclaration_tests { using ResolverDependencyGraphRedeclarationTest = ResolverDependencyGraphTestWithParam< std::tuple>; 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>; 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().size(), 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().size(), 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))); } // namespace ordered_globals //////////////////////////////////////////////////////////////////////////////// // Resolved symbols tests //////////////////////////////////////////////////////////////////////////////// namespace resolved_symbols { using ResolverDependencyGraphResolvedSymbolTest = ResolverDependencyGraphTestWithParam< std::tuple>; 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[use]; EXPECT_EQ(resolved_symbol, decl) << "resolved: " << (resolved_symbol ? resolved_symbol->TypeInfo().name : "") << "\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>; 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.size() ? helper.nested_statements[0] ->As() ->variable : helper.statements.size() ? helper.statements[0] ->As() ->variable : helper.parameters[0]; helper.Build(); EXPECT_EQ(Build().shadows[inner_var], 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 = Global(value_sym, ty.i32(), ast::StorageClass::kPrivate); const auto* type_decl = Alias(type_sym, ty.i32()); const auto* func_decl = Func(func_sym, {}, ty.void_(), {}); struct SymbolUse { const ast::Node* decl = nullptr; const ast::Node* use = nullptr; std::string where = nullptr; }; std::vector symbol_uses; auto add_use = [&](const ast::Node* decl, auto* use, int line, const char* kind) { symbol_uses.emplace_back(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(), {Member(Sym(), T)}); Global(Sym(), T, V); GlobalConst(Sym(), T, V); Func(Sym(), // {Param(Sym(), T)}, // T, // Return type { Decl(Var(Sym(), T, V)), // Decl(Const(Sym(), T, V)), // CallStmt(Call(F, V)), // Block( // Assign(V, V)), // If(V, // Block(Assign(V, V)), // Else(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))), // Loop(Block(Assign(V, V)), // Block(Assign(V, V))), // Switch(V, // Case(Expr(1), // Block(Assign(V, V))), // Case(Expr(2), // Block(Fallthrough())), // DefaultCase(Block(Assign(V, V)))), // Return(V), // Break(), // Discard(), // }); // // Exercise type traversal Global(Sym(), ty.atomic(T)); Global(Sym(), ty.bool_()); Global(Sym(), ty.i32()); Global(Sym(), ty.u32()); Global(Sym(), ty.f32()); Global(Sym(), ty.array(T, V, 4)); Global(Sym(), ty.vec3(T)); Global(Sym(), ty.mat3x2(T)); Global(Sym(), ty.pointer(T, ast::StorageClass::kPrivate)); Global(Sym(), ty.sampled_texture(ast::TextureDimension::k2d, T)); Global(Sym(), ty.depth_texture(ast::TextureDimension::k2d)); Global(Sym(), ty.depth_multisampled_texture(ast::TextureDimension::k2d)); Global(Sym(), ty.external_texture()); Global(Sym(), ty.multisampled_texture(ast::TextureDimension::k2d, T)); Global(Sym(), ty.storage_texture(ast::TextureDimension::k2d, ast::ImageFormat::kR16Float, ast::Access::kRead)); // Global(Sym(), ty.sampler(ast::SamplerKind::kSampler)); Func(Sym(), {}, ty.void_(), {}); #undef V #undef T #undef F auto graph = Build(); for (auto use : symbol_uses) { auto* resolved_symbol = graph.resolved_symbols[use.use]; EXPECT_EQ(resolved_symbol, use.decl) << use.where; } } TEST_F(ResolverDependencyGraphTraversalTest, InferredType) { // Check that the nullptr of the var / let type doesn't make things explode Global("a", nullptr, Expr(1)); GlobalConst("b", nullptr, Expr(1)); WrapInFunction(Var("c", nullptr, Expr(1)), // Const("d", nullptr, Expr(1))); 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", {Member("a", ty.i32())}); Structure("B", {Member("b", ty.i32())}); Func("f", {Param("a", ty.type_name("A"))}, ty.type_name("B"), { Return(Construct(ty.type_name("B"))), }); Build(); } } // namespace ast_traversal } // namespace } // namespace resolver } // namespace tint