Fixed handling of nested lambda expressions in sequence diagrams
This commit is contained in:
@@ -132,6 +132,9 @@ void call_expression_context::update(
|
|||||||
|
|
||||||
std::int64_t call_expression_context::caller_id() const
|
std::int64_t call_expression_context::caller_id() const
|
||||||
{
|
{
|
||||||
|
if (lambda_caller_id() != 0)
|
||||||
|
return lambda_caller_id();
|
||||||
|
|
||||||
return current_caller_id_;
|
return current_caller_id_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -455,8 +455,10 @@ bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr)
|
|||||||
lambda_method_model_ptr->set_method_name(method_name);
|
lambda_method_model_ptr->set_method_name(method_name);
|
||||||
|
|
||||||
lambda_method_model_ptr->set_class_id(cls_id);
|
lambda_method_model_ptr->set_class_id(cls_id);
|
||||||
lambda_method_model_ptr->set_class_full_name(
|
|
||||||
lambda_class_model_ptr->full_name(false));
|
// If this is a nested lambda, prepend the parent lambda name to this lambda
|
||||||
|
auto lambda_class_full_name = lambda_class_model_ptr->full_name(false);
|
||||||
|
lambda_method_model_ptr->set_class_full_name(lambda_class_full_name);
|
||||||
|
|
||||||
diagram().add_participant(std::move(lambda_class_model_ptr));
|
diagram().add_participant(std::move(lambda_class_model_ptr));
|
||||||
|
|
||||||
@@ -469,9 +471,8 @@ bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr)
|
|||||||
// If lambda expression is in an argument to a method/function, and that
|
// If lambda expression is in an argument to a method/function, and that
|
||||||
// method function would be excluded by filters
|
// method function would be excluded by filters
|
||||||
if (std::holds_alternative<clang::CallExpr *>(
|
if (std::holds_alternative<clang::CallExpr *>(
|
||||||
context().current_callexpr())/* &&
|
context().current_callexpr()) &&
|
||||||
!should_include(
|
(context().lambda_caller_id() == 0)) {
|
||||||
std::get<clang::CallExpr *>(context().current_callexpr()))*/) {
|
|
||||||
using clanguml::common::model::message_t;
|
using clanguml::common::model::message_t;
|
||||||
using clanguml::sequence_diagram::model::message;
|
using clanguml::sequence_diagram::model::message;
|
||||||
|
|
||||||
@@ -504,9 +505,6 @@ bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr)
|
|||||||
|
|
||||||
bool translation_unit_visitor::TraverseLambdaExpr(clang::LambdaExpr *expr)
|
bool translation_unit_visitor::TraverseLambdaExpr(clang::LambdaExpr *expr)
|
||||||
{
|
{
|
||||||
const auto lambda_full_name =
|
|
||||||
expr->getLambdaClass()->getCanonicalDecl()->getNameAsString();
|
|
||||||
|
|
||||||
RecursiveASTVisitor<translation_unit_visitor>::TraverseLambdaExpr(expr);
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseLambdaExpr(expr);
|
||||||
|
|
||||||
// lambda context is entered inside the visitor
|
// lambda context is entered inside the visitor
|
||||||
@@ -543,10 +541,8 @@ bool translation_unit_visitor::TraverseCXXMemberCallExpr(
|
|||||||
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
|
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
LOG_DBG("Entering member call expression at {} to {}::{}",
|
LOG_DBG("Entering member call expression at {}",
|
||||||
expr->getBeginLoc().printToString(source_manager()),
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
common::to_string(expr->getObjectType(), context().get_ast_context()),
|
|
||||||
common::to_string(expr->getMethodDecl()));
|
|
||||||
|
|
||||||
context().enter_callexpr(expr);
|
context().enter_callexpr(expr);
|
||||||
|
|
||||||
@@ -599,6 +595,9 @@ bool translation_unit_visitor::TraverseCXXTemporaryObjectExpr(
|
|||||||
bool translation_unit_visitor::TraverseCXXConstructExpr(
|
bool translation_unit_visitor::TraverseCXXConstructExpr(
|
||||||
clang::CXXConstructExpr *expr)
|
clang::CXXConstructExpr *expr)
|
||||||
{
|
{
|
||||||
|
LOG_DBG("Entering cxx construct call expression at {}",
|
||||||
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
|
|
||||||
context().enter_callexpr(expr);
|
context().enter_callexpr(expr);
|
||||||
|
|
||||||
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXConstructExpr(
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXConstructExpr(
|
||||||
@@ -606,6 +605,9 @@ bool translation_unit_visitor::TraverseCXXConstructExpr(
|
|||||||
|
|
||||||
translation_unit_visitor::VisitCXXConstructExpr(expr);
|
translation_unit_visitor::VisitCXXConstructExpr(expr);
|
||||||
|
|
||||||
|
LOG_DBG("Leaving member call expression at {}",
|
||||||
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
|
|
||||||
context().leave_callexpr();
|
context().leave_callexpr();
|
||||||
|
|
||||||
pop_message_to_diagram(expr);
|
pop_message_to_diagram(expr);
|
||||||
@@ -1057,13 +1059,6 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
if (!generated_message_from_comment && !should_include(expr))
|
if (!generated_message_from_comment && !should_include(expr))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// If we're currently inside a lambda expression, set it's id as
|
|
||||||
// message source rather then enclosing context
|
|
||||||
// Unless the lambda is declared in a function or method call
|
|
||||||
if (context().lambda_caller_id() != 0) {
|
|
||||||
m.set_from(context().lambda_caller_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context().is_expr_in_current_control_statement_condition(expr)) {
|
if (context().is_expr_in_current_control_statement_condition(expr)) {
|
||||||
m.set_message_scope(common::model::message_scope_t::kCondition);
|
m.set_message_scope(common::model::message_scope_t::kCondition);
|
||||||
}
|
}
|
||||||
@@ -1100,9 +1095,12 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
if (callee_decl == nullptr) {
|
if (callee_decl == nullptr) {
|
||||||
LOG_DBG("Cannot get callee declaration - trying direct function "
|
LOG_DBG("Cannot get callee declaration - trying direct function "
|
||||||
"callee...");
|
"callee...");
|
||||||
|
|
||||||
callee_decl = expr->getDirectCallee();
|
callee_decl = expr->getDirectCallee();
|
||||||
LOG_DBG(
|
|
||||||
"Found function/method callee in: {}", common::to_string(expr));
|
if (callee_decl != nullptr)
|
||||||
|
LOG_DBG("Found function/method callee in: {}",
|
||||||
|
common::to_string(expr));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callee_decl == nullptr) {
|
if (callee_decl == nullptr) {
|
||||||
@@ -1124,6 +1122,17 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
if (!process_unresolved_lookup_call_expression(m, expr))
|
if (!process_unresolved_lookup_call_expression(m, expr))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (clang::dyn_cast_or_null<clang::LambdaExpr>(
|
||||||
|
expr->getCallee()) != nullptr) {
|
||||||
|
LOG_DBG("Processing lambda expression callee");
|
||||||
|
if (!process_lambda_call_expression(m, expr))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG_DBG("Found unsupported callee decl type for: {} at {}",
|
||||||
|
common::to_string(expr),
|
||||||
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auto success = process_function_call_expression(m, expr);
|
auto success = process_function_call_expression(m, expr);
|
||||||
@@ -1219,10 +1228,6 @@ bool translation_unit_visitor::VisitCXXConstructExpr(
|
|||||||
|
|
||||||
set_source_location(*expr, m);
|
set_source_location(*expr, m);
|
||||||
|
|
||||||
if (context().lambda_caller_id() != 0) {
|
|
||||||
m.set_from(context().lambda_caller_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context().is_expr_in_current_control_statement_condition(expr)) {
|
if (context().is_expr_in_current_control_statement_condition(expr)) {
|
||||||
m.set_message_scope(common::model::message_scope_t::kCondition);
|
m.set_message_scope(common::model::message_scope_t::kCondition);
|
||||||
}
|
}
|
||||||
@@ -1487,6 +1492,26 @@ bool translation_unit_visitor::process_function_call_expression(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool translation_unit_visitor::process_lambda_call_expression(
|
||||||
|
model::message &m, const clang::CallExpr *expr) const
|
||||||
|
{
|
||||||
|
const auto *lambda_expr =
|
||||||
|
clang::dyn_cast_or_null<clang::LambdaExpr>(expr->getCallee());
|
||||||
|
|
||||||
|
if (lambda_expr == nullptr)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const auto lambda_class_id = lambda_expr->getLambdaClass()->getID();
|
||||||
|
const auto maybe_id = get_unique_id(lambda_class_id);
|
||||||
|
if (!maybe_id.has_value())
|
||||||
|
m.set_to(lambda_class_id);
|
||||||
|
else {
|
||||||
|
m.set_to(maybe_id.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool translation_unit_visitor::process_unresolved_lookup_call_expression(
|
bool translation_unit_visitor::process_unresolved_lookup_call_expression(
|
||||||
model::message &m, const clang::CallExpr *expr) const
|
model::message &m, const clang::CallExpr *expr) const
|
||||||
{
|
{
|
||||||
@@ -1498,7 +1523,6 @@ bool translation_unit_visitor::process_unresolved_lookup_call_expression(
|
|||||||
for (const auto *decl : unresolved_expr->decls()) {
|
for (const auto *decl : unresolved_expr->decls()) {
|
||||||
if (clang::dyn_cast_or_null<clang::FunctionTemplateDecl>(decl) !=
|
if (clang::dyn_cast_or_null<clang::FunctionTemplateDecl>(decl) !=
|
||||||
nullptr) {
|
nullptr) {
|
||||||
// Yes, it's a template
|
|
||||||
const auto *ftd =
|
const auto *ftd =
|
||||||
clang::dyn_cast_or_null<clang::FunctionTemplateDecl>(decl);
|
clang::dyn_cast_or_null<clang::FunctionTemplateDecl>(decl);
|
||||||
|
|
||||||
@@ -1511,6 +1535,23 @@ bool translation_unit_visitor::process_unresolved_lookup_call_expression(
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else if (clang::dyn_cast_or_null<clang::FunctionDecl>(decl) !=
|
||||||
|
nullptr) {
|
||||||
|
const auto *fd =
|
||||||
|
clang::dyn_cast_or_null<clang::FunctionDecl>(decl);
|
||||||
|
|
||||||
|
const auto maybe_id = get_unique_id(fd->getID());
|
||||||
|
if (!maybe_id.has_value())
|
||||||
|
m.set_to(fd->getID());
|
||||||
|
else {
|
||||||
|
m.set_to(maybe_id.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG_DBG("Unknown unresolved lookup expression");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1568,9 +1609,10 @@ translation_unit_visitor::create_class_model(clang::CXXRecordDecl *cls)
|
|||||||
const auto *parent = cls->getParent();
|
const auto *parent = cls->getParent();
|
||||||
|
|
||||||
if ((parent != nullptr) && parent->isRecord()) {
|
if ((parent != nullptr) && parent->isRecord()) {
|
||||||
// Here we have 2 options, either:
|
// Here we have 3 options, either:
|
||||||
// - the parent is a regular C++ class/struct
|
// - the parent is a regular C++ class/struct
|
||||||
// - the parent is a class template declaration/specialization
|
// - the parent is a class template declaration/specialization
|
||||||
|
// - the parent is a lambda (i.e. this is a nested lambda expression)
|
||||||
std::optional<common::id_t> id_opt;
|
std::optional<common::id_t> id_opt;
|
||||||
const auto *parent_record_decl =
|
const auto *parent_record_decl =
|
||||||
clang::dyn_cast<clang::RecordDecl>(parent);
|
clang::dyn_cast<clang::RecordDecl>(parent);
|
||||||
@@ -1817,7 +1859,31 @@ std::string translation_unit_visitor::make_lambda_name(
|
|||||||
const auto location = cls->getLocation();
|
const auto location = cls->getLocation();
|
||||||
const std::string source_location{lambda_source_location(location)};
|
const std::string source_location{lambda_source_location(location)};
|
||||||
|
|
||||||
if (context().caller_id() != 0 &&
|
if (context().lambda_caller_id() != 0) {
|
||||||
|
// Parent is also a lambda (this id points to a lambda operator())
|
||||||
|
std::string parent_lambda_class_name{"()"};
|
||||||
|
if (diagram().get_participant<model::method>(
|
||||||
|
context().lambda_caller_id())) {
|
||||||
|
auto parent_lambda_class_id = diagram()
|
||||||
|
.get_participant<model::method>(
|
||||||
|
context().lambda_caller_id())
|
||||||
|
.value()
|
||||||
|
.class_id();
|
||||||
|
|
||||||
|
if (diagram().get_participant<model::class_>(
|
||||||
|
parent_lambda_class_id)) {
|
||||||
|
parent_lambda_class_name =
|
||||||
|
diagram()
|
||||||
|
.get_participant<model::class_>(parent_lambda_class_id)
|
||||||
|
.value()
|
||||||
|
.full_name(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = fmt::format(
|
||||||
|
"{}##(lambda {})", parent_lambda_class_name, source_location);
|
||||||
|
}
|
||||||
|
else if (context().caller_id() != 0 &&
|
||||||
get_participant(context().caller_id()).has_value()) {
|
get_participant(context().caller_id()).has_value()) {
|
||||||
auto parent_full_name =
|
auto parent_full_name =
|
||||||
get_participant(context().caller_id()).value().full_name_no_ns();
|
get_participant(context().caller_id()).value().full_name_no_ns();
|
||||||
|
|||||||
@@ -436,6 +436,9 @@ private:
|
|||||||
bool process_unresolved_lookup_call_expression(
|
bool process_unresolved_lookup_call_expression(
|
||||||
model::message &m, const clang::CallExpr *expr) const;
|
model::message &m, const clang::CallExpr *expr) const;
|
||||||
|
|
||||||
|
bool process_lambda_call_expression(
|
||||||
|
model::message &m, const clang::CallExpr *expr) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register a message model `m` with a call expression
|
* @brief Register a message model `m` with a call expression
|
||||||
*
|
*
|
||||||
|
|||||||
13
tests/t20045/.clang-uml
Normal file
13
tests/t20045/.clang-uml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
add_compile_flags:
|
||||||
|
- -fparse-all-comments
|
||||||
|
diagrams:
|
||||||
|
t20045_sequence:
|
||||||
|
type: sequence
|
||||||
|
glob:
|
||||||
|
- t20045.cc
|
||||||
|
include:
|
||||||
|
namespaces:
|
||||||
|
- clanguml::t20045
|
||||||
|
using_namespace: clanguml::t20045
|
||||||
|
from:
|
||||||
|
- function: "clanguml::t20045::tmain()"
|
||||||
47
tests/t20045/t20045.cc
Normal file
47
tests/t20045/t20045.cc
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
namespace clanguml {
|
||||||
|
namespace t20045 {
|
||||||
|
|
||||||
|
template <typename F> int a1(F &&f) { return f(42); }
|
||||||
|
|
||||||
|
int a2(int x) { return 2; }
|
||||||
|
|
||||||
|
int a3(int x) { return 3; }
|
||||||
|
|
||||||
|
struct B {
|
||||||
|
int b1(int x) { return x + 1; }
|
||||||
|
int b2(int x) { return x + 2; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class C {
|
||||||
|
public:
|
||||||
|
explicit C(int x)
|
||||||
|
: x_{x}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_x() const { return x_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int x_;
|
||||||
|
};
|
||||||
|
|
||||||
|
int tmain()
|
||||||
|
{
|
||||||
|
B b;
|
||||||
|
|
||||||
|
// \uml{call clanguml::t20045::a2(int)}
|
||||||
|
auto v1 = a1(a2);
|
||||||
|
|
||||||
|
auto v2 = a1([](auto &&arg) { return a3(arg); });
|
||||||
|
|
||||||
|
auto v3 = a1([&](auto &&arg) { return b.b1(arg); });
|
||||||
|
|
||||||
|
auto v4 = a1([](auto &&arg) {
|
||||||
|
C c(arg);
|
||||||
|
return c.get_x();
|
||||||
|
});
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
tests/t20045/test_case.h
Normal file
82
tests/t20045/test_case.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* tests/t20045/test_case.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
TEST_CASE("t20045", "[test-case][sequence]")
|
||||||
|
{
|
||||||
|
auto [config, db] = load_config("t20045");
|
||||||
|
|
||||||
|
auto diagram = config.diagrams["t20045_sequence"];
|
||||||
|
|
||||||
|
REQUIRE(diagram->name == "t20045_sequence");
|
||||||
|
|
||||||
|
auto model = generate_sequence_diagram(*db, diagram);
|
||||||
|
|
||||||
|
REQUIRE(model->name() == "t20045_sequence");
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_puml(diagram, *model);
|
||||||
|
AliasMatcher _A(src);
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, StartsWith("@startuml"));
|
||||||
|
REQUIRE_THAT(src, EndsWith("@enduml\n"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("a2(int)"), ""));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()"),
|
||||||
|
_A("a1<(lambda at t20045.cc:35:18)>((lambda at "
|
||||||
|
"t20045.cc:35:18) &&)"),
|
||||||
|
""));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("a1<(lambda at t20045.cc:35:18)>((lambda at "
|
||||||
|
"t20045.cc:35:18) &&)"),
|
||||||
|
_A("tmain()::(lambda t20045.cc:35:18)"), "operator()()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(
|
||||||
|
_A("tmain()::(lambda t20045.cc:35:18)"), _A("a3(int)"), ""));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(
|
||||||
|
_A("tmain()::(lambda t20045.cc:37:18)"), _A("B"), "b1(int)"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(
|
||||||
|
_A("tmain()::(lambda t20045.cc:39:18)"), _A("C"), "get_x()"));
|
||||||
|
|
||||||
|
save_puml(config.output_directory(), diagram->name + ".puml", src);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto j = generate_sequence_json(diagram, *model);
|
||||||
|
|
||||||
|
using namespace json;
|
||||||
|
|
||||||
|
save_json(config.output_directory(), diagram->name + ".json", j);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_mermaid(diagram, *model);
|
||||||
|
|
||||||
|
mermaid::AliasMatcher _A(src);
|
||||||
|
using mermaid::IsClass;
|
||||||
|
|
||||||
|
save_mermaid(config.output_directory(), diagram->name + ".mmd", src);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
tests/t20046/.clang-uml
Normal file
13
tests/t20046/.clang-uml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
add_compile_flags:
|
||||||
|
- -fparse-all-comments
|
||||||
|
diagrams:
|
||||||
|
t20046_sequence:
|
||||||
|
type: sequence
|
||||||
|
glob:
|
||||||
|
- t20046.cc
|
||||||
|
include:
|
||||||
|
namespaces:
|
||||||
|
- clanguml::t20046
|
||||||
|
using_namespace: clanguml::t20046
|
||||||
|
from:
|
||||||
|
- function: "clanguml::t20046::tmain()"
|
||||||
24
tests/t20046/t20046.cc
Normal file
24
tests/t20046/t20046.cc
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace clanguml {
|
||||||
|
namespace t20046 {
|
||||||
|
|
||||||
|
template <typename F> int a1(F &&f) { return f(42); }
|
||||||
|
|
||||||
|
int a2(int x) { return 2; }
|
||||||
|
|
||||||
|
int a3(int x) { return 3; }
|
||||||
|
|
||||||
|
int tmain()
|
||||||
|
{
|
||||||
|
// Call expression in a nested lambda
|
||||||
|
auto v1 = [](auto &&arg1) {
|
||||||
|
return [](auto &&arg2) { return a2(arg2); }(arg1);
|
||||||
|
}(0);
|
||||||
|
|
||||||
|
// Call expression in a nested lambda in call expression
|
||||||
|
auto v4 = a1(
|
||||||
|
[](auto &&arg1) { return [](auto &&arg2) { return a3(arg2); }(arg1); });
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
94
tests/t20046/test_case.h
Normal file
94
tests/t20046/test_case.h
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* tests/t20046/test_case.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
TEST_CASE("t20046", "[test-case][sequence]")
|
||||||
|
{
|
||||||
|
auto [config, db] = load_config("t20046");
|
||||||
|
|
||||||
|
auto diagram = config.diagrams["t20046_sequence"];
|
||||||
|
|
||||||
|
REQUIRE(diagram->name == "t20046_sequence");
|
||||||
|
|
||||||
|
auto model = generate_sequence_diagram(*db, diagram);
|
||||||
|
|
||||||
|
REQUIRE(model->name() == "t20046_sequence");
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_puml(diagram, *model);
|
||||||
|
AliasMatcher _A(src);
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, StartsWith("@startuml"));
|
||||||
|
REQUIRE_THAT(src, EndsWith("@enduml\n"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()"), _A("tmain()::(lambda t20046.cc:13:15)"),
|
||||||
|
"operator()()"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()::(lambda t20046.cc:13:15)"),
|
||||||
|
_A("tmain()::(lambda t20046.cc:13:15)::(lambda "
|
||||||
|
"t20046.cc:14:16)"),
|
||||||
|
"operator()()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()::(lambda t20046.cc:13:15)::(lambda "
|
||||||
|
"t20046.cc:14:16)"),
|
||||||
|
_A("a2(int)"), ""));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()"),
|
||||||
|
_A("a1<(lambda at t20046.cc:19:9)>((lambda at t20046.cc:19:9) "
|
||||||
|
"&&)"),
|
||||||
|
""));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(
|
||||||
|
_A("a1<(lambda at t20046.cc:19:9)>((lambda at t20046.cc:19:9) "
|
||||||
|
"&&)"),
|
||||||
|
_A("tmain()::(lambda t20046.cc:19:9)"), "operator()()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()::(lambda t20046.cc:19:9)"),
|
||||||
|
_A("tmain()::(lambda t20046.cc:19:9)::(lambda "
|
||||||
|
"t20046.cc:19:34)"),
|
||||||
|
"operator()()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()::(lambda t20046.cc:19:9)::(lambda "
|
||||||
|
"t20046.cc:19:34)"),
|
||||||
|
_A("a3(int)"), ""));
|
||||||
|
|
||||||
|
save_puml(config.output_directory(), diagram->name + ".puml", src);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto j = generate_sequence_json(diagram, *model);
|
||||||
|
|
||||||
|
using namespace json;
|
||||||
|
|
||||||
|
save_json(config.output_directory(), diagram->name + ".json", j);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_mermaid(diagram, *model);
|
||||||
|
|
||||||
|
mermaid::AliasMatcher _A(src);
|
||||||
|
using mermaid::IsClass;
|
||||||
|
|
||||||
|
save_mermaid(config.output_directory(), diagram->name + ".mmd", src);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -471,6 +471,8 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t20042/test_case.h"
|
#include "t20042/test_case.h"
|
||||||
#include "t20043/test_case.h"
|
#include "t20043/test_case.h"
|
||||||
#include "t20044/test_case.h"
|
#include "t20044/test_case.h"
|
||||||
|
#include "t20045/test_case.h"
|
||||||
|
#include "t20046/test_case.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Package diagram tests
|
/// Package diagram tests
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ struct AliasMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "__INVALID__ALIAS__";
|
return fmt::format("__INVALID__ALIAS__({})", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::string> puml;
|
const std::vector<std::string> puml;
|
||||||
|
|||||||
@@ -352,6 +352,15 @@ test_cases:
|
|||||||
- name: t20043
|
- name: t20043
|
||||||
title: Test case for elements diagram filter in sequence diagrams
|
title: Test case for elements diagram filter in sequence diagrams
|
||||||
description:
|
description:
|
||||||
|
- name: t20044
|
||||||
|
title: Test case for template method call expressions with callables
|
||||||
|
description:
|
||||||
|
- name: t20045
|
||||||
|
title: Test case for template function call expressions with callables
|
||||||
|
description:
|
||||||
|
- name: t20046
|
||||||
|
title: Test case for call expressions in nested lambdas
|
||||||
|
description:
|
||||||
Package diagrams:
|
Package diagrams:
|
||||||
- name: t30001
|
- name: t30001
|
||||||
title: Basic package diagram test case
|
title: Basic package diagram test case
|
||||||
|
|||||||
@@ -26,63 +26,64 @@ TEST_CASE_MULTIPLIER = 10000
|
|||||||
|
|
||||||
CLASS_DIAGRAM_TEST_CASE_EXAMPLES = """
|
CLASS_DIAGRAM_TEST_CASE_EXAMPLES = """
|
||||||
// Check if all classes exist
|
// Check if all classes exist
|
||||||
//REQUIRE_THAT(puml, IsClass(_A("A")));
|
//REQUIRE_THAT(src, IsClass(_A("A")));
|
||||||
|
|
||||||
// Check if class templates exist
|
// Check if class templates exist
|
||||||
//REQUIRE_THAT(puml, IsClassTemplate("A", "T,P,CMP,int N"));
|
//REQUIRE_THAT(src, IsClassTemplate("A", "T,P,CMP,int N"));
|
||||||
|
|
||||||
// Check concepts
|
// Check concepts
|
||||||
//REQUIRE_THAT(puml, IsConcept(_A("AConcept<T>")));
|
//REQUIRE_THAT(src, IsConcept(_A("AConcept<T>")));
|
||||||
//REQUIRE_THAT(puml,
|
//REQUIRE_THAT(src,
|
||||||
// IsConceptRequirement(
|
// IsConceptRequirement(
|
||||||
// _A("AConcept<T,P>"), "sizeof (T) > sizeof (P)"));
|
// _A("AConcept<T,P>"), "sizeof (T) > sizeof (P)"));
|
||||||
|
|
||||||
// Check if all enums exist
|
// Check if all enums exist
|
||||||
//REQUIRE_THAT(puml, IsEnum(_A("Lights")));
|
//REQUIRE_THAT(src, IsEnum(_A("Lights")));
|
||||||
|
|
||||||
// Check if all inner classes exist
|
// Check if all inner classes exist
|
||||||
//REQUIRE_THAT(puml, IsInnerClass(_A("A"), _A("AA")));
|
//REQUIRE_THAT(src, IsInnerClass(_A("A"), _A("AA")));
|
||||||
|
|
||||||
// Check if all inheritance relationships exist
|
// Check if all inheritance relationships exist
|
||||||
//REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("Child")));
|
//REQUIRE_THAT(src, IsBaseClass(_A("Base"), _A("Child")));
|
||||||
|
|
||||||
// Check if all methods exist
|
// Check if all methods exist
|
||||||
//REQUIRE_THAT(puml, (IsMethod<Public, Const>("foo")));
|
//REQUIRE_THAT(src, (IsMethod<Public, Const>("foo")));
|
||||||
|
|
||||||
// Check if all fields exist
|
// Check if all fields exist
|
||||||
//REQUIRE_THAT(puml, (IsField<Private>("private_member", "int")));
|
//REQUIRE_THAT(src, (IsField<Private>("private_member", "int")));
|
||||||
|
|
||||||
// Check if all relationships exist
|
// Check if all relationships exist
|
||||||
//REQUIRE_THAT(puml, IsAssociation(_A("D"), _A("A"), "-as"));
|
//REQUIRE_THAT(src, IsAssociation(_A("D"), _A("A"), "-as"));
|
||||||
//REQUIRE_THAT(puml, IsDependency(_A("R"), _A("B")));
|
//REQUIRE_THAT(src, IsDependency(_A("R"), _A("B")));
|
||||||
//REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("D"), "-ag"));
|
//REQUIRE_THAT(src, IsAggregation(_A("R"), _A("D"), "-ag"));
|
||||||
//REQUIRE_THAT(puml, IsComposition(_A("R"), _A("D"), "-ac"));
|
//REQUIRE_THAT(src, IsComposition(_A("R"), _A("D"), "-ac"));
|
||||||
//REQUIRE_THAT(puml, IsInstantiation(_A("ABCD::F<T>"), _A("F<int>")));
|
//REQUIRE_THAT(src, IsInstantiation(_A("ABCD::F<T>"), _A("F<int>")));
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SEQUENCE_DIAGRAM_TEST_CASE_EXAMPLES = """
|
SEQUENCE_DIAGRAM_TEST_CASE_EXAMPLES = """
|
||||||
// Check if all calls exist
|
// Check if all calls exist
|
||||||
//REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a()"));
|
//REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("A"), "a()"));
|
||||||
//REQUIRE_THAT(puml, HasCall(_A("A"), "a()"));
|
//REQUIRE_THAT(src, HasCall(_A("A"), "a()"));
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PACKAGE_DIAGRAM_TEST_CASE_EXAMPLES = """
|
PACKAGE_DIAGRAM_TEST_CASE_EXAMPLES = """
|
||||||
// Check if all packages exist
|
// Check if all packages exist
|
||||||
//REQUIRE_THAT(puml, IsPackage("ns1"));
|
//REQUIRE_THAT(src, IsPackage("ns1"));
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INCLUDE_DIAGRAM_TEST_CASE_EXAMPLES = """
|
INCLUDE_DIAGRAM_TEST_CASE_EXAMPLES = """
|
||||||
// Check all folders exist
|
// Check all folders exist
|
||||||
//REQUIRE_THAT(puml, IsFolder("lib1"));
|
//REQUIRE_THAT(src, IsFolder("lib1"));
|
||||||
|
|
||||||
// Check if all files exist
|
// Check if all files exist
|
||||||
//REQUIRE_THAT(puml, IsFile("lib1.h"));
|
//REQUIRE_THAT(src, IsFile("lib1.h"));
|
||||||
|
|
||||||
// Check if all includes exists
|
// Check if all includes exists
|
||||||
//REQUIRE_THAT(puml, IsAssociation(_A("t40002.cc"), _A("lib1.h")));
|
//REQUIRE_THAT(src, IsAssociation(_A("t40002.cc"), _A("lib1.h")));
|
||||||
//REQUIRE_THAT(puml, IsDependency(_A("t40001_include1.h"), _A("string")));
|
//REQUIRE_THAT(src, IsDependency(_A("t40001_include1.h"), _A("string")));
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def test_case_already_exists(name):
|
def test_case_already_exists(name):
|
||||||
return os.path.isdir(os.path.join(os.path.dirname(__file__), '..', name))
|
return os.path.isdir(os.path.join(os.path.dirname(__file__), '..', name))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user