Added option inline_lambda_messages to omit lambda expressions from sequence diagrams (#261)
This commit is contained in:
@@ -252,6 +252,17 @@ results in the following diagram:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
In case lambda expressions are redundant and we are only interested in the calls
|
||||||
|
generate from the lambda expressions, it is possible to inline lambda
|
||||||
|
expressions in the generated diagrams by specifying the following option:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
inline_lambda_messages: true
|
||||||
|
```
|
||||||
|
|
||||||
|
For example compare the test cases [t20012](test_cases/t20012.md) and
|
||||||
|
[t20052](test_cases/t20052.md).
|
||||||
|
|
||||||
## Customizing participants order
|
## Customizing participants order
|
||||||
The default participant order in the sequence diagram can be suboptimal in the
|
The default participant order in the sequence diagram can be suboptimal in the
|
||||||
sense that consecutive calls can go right, then left, then right again
|
sense that consecutive calls can go right, then left, then right again
|
||||||
|
|||||||
@@ -561,6 +561,9 @@ cli_flow_t cli_handler::add_config_diagram(
|
|||||||
doc["diagrams"][name]["glob"] = std::vector<std::string>{{"src/*.cpp"}};
|
doc["diagrams"][name]["glob"] = std::vector<std::string>{{"src/*.cpp"}};
|
||||||
doc["diagrams"][name]["combine_free_functions_into_file_participants"] =
|
doc["diagrams"][name]["combine_free_functions_into_file_participants"] =
|
||||||
true;
|
true;
|
||||||
|
doc["diagrams"][name]["inline_lambda_messages"] = false;
|
||||||
|
doc["diagrams"][name]["generate_message_comments"] = false;
|
||||||
|
doc["diagrams"][name]["generate_condition_statements"] = false;
|
||||||
doc["diagrams"][name]["using_namespace"] =
|
doc["diagrams"][name]["using_namespace"] =
|
||||||
std::vector<std::string>{{"myproject"}};
|
std::vector<std::string>{{"myproject"}};
|
||||||
doc["diagrams"][name]["include"]["paths"] =
|
doc["diagrams"][name]["include"]["paths"] =
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ class diagram {
|
|||||||
public:
|
public:
|
||||||
diagram();
|
diagram();
|
||||||
|
|
||||||
|
diagram(const diagram &) = delete;
|
||||||
|
diagram(diagram && /*unused*/) noexcept;
|
||||||
|
diagram &operator=(const diagram &) = delete;
|
||||||
|
diagram &operator=(diagram && /*unused*/) noexcept;
|
||||||
|
|
||||||
virtual ~diagram();
|
virtual ~diagram();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,11 +82,6 @@ public:
|
|||||||
virtual common::optional_ref<clanguml::common::model::diagram_element>
|
virtual common::optional_ref<clanguml::common::model::diagram_element>
|
||||||
get_with_namespace(const std::string &name, const namespace_ &ns) const;
|
get_with_namespace(const std::string &name, const namespace_ &ns) const;
|
||||||
|
|
||||||
diagram(const diagram &) = delete;
|
|
||||||
diagram(diagram && /*unused*/) noexcept;
|
|
||||||
diagram &operator=(const diagram &) = delete;
|
|
||||||
diagram &operator=(diagram && /*unused*/) noexcept;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set diagram name.
|
* Set diagram name.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ void inheritable_diagram_options::inherit(
|
|||||||
comment_parser.override(parent.comment_parser);
|
comment_parser.override(parent.comment_parser);
|
||||||
combine_free_functions_into_file_participants.override(
|
combine_free_functions_into_file_participants.override(
|
||||||
parent.combine_free_functions_into_file_participants);
|
parent.combine_free_functions_into_file_participants);
|
||||||
|
inline_lambda_messages.override(parent.inline_lambda_messages);
|
||||||
generate_return_types.override(parent.generate_return_types);
|
generate_return_types.override(parent.generate_return_types);
|
||||||
generate_condition_statements.override(
|
generate_condition_statements.override(
|
||||||
parent.generate_condition_statements);
|
parent.generate_condition_statements);
|
||||||
|
|||||||
@@ -570,6 +570,7 @@ struct inheritable_diagram_options {
|
|||||||
"comment_parser", comment_parser_t::plain};
|
"comment_parser", comment_parser_t::plain};
|
||||||
option<bool> combine_free_functions_into_file_participants{
|
option<bool> combine_free_functions_into_file_participants{
|
||||||
"combine_free_functions_into_file_participants", false};
|
"combine_free_functions_into_file_participants", false};
|
||||||
|
option<bool> inline_lambda_messages{"inline_lambda_messages", false};
|
||||||
option<bool> generate_return_types{"generate_return_types", false};
|
option<bool> generate_return_types{"generate_return_types", false};
|
||||||
option<bool> generate_condition_statements{
|
option<bool> generate_condition_statements{
|
||||||
"generate_condition_statements", false};
|
"generate_condition_statements", false};
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ types:
|
|||||||
#
|
#
|
||||||
generate_method_arguments: !optional generate_method_arguments_t
|
generate_method_arguments: !optional generate_method_arguments_t
|
||||||
combine_free_functions_into_file_participants: !optional bool
|
combine_free_functions_into_file_participants: !optional bool
|
||||||
|
inline_lambda_messages: !optional bool
|
||||||
generate_return_types: !optional bool
|
generate_return_types: !optional bool
|
||||||
generate_condition_statements: !optional bool
|
generate_condition_statements: !optional bool
|
||||||
generate_message_comments: !optional bool
|
generate_message_comments: !optional bool
|
||||||
@@ -340,6 +341,7 @@ root:
|
|||||||
include_relations_also_as_members: !optional bool
|
include_relations_also_as_members: !optional bool
|
||||||
generate_method_arguments: !optional generate_method_arguments_t
|
generate_method_arguments: !optional generate_method_arguments_t
|
||||||
combine_free_functions_into_file_participants: !optional bool
|
combine_free_functions_into_file_participants: !optional bool
|
||||||
|
inline_lambda_messages: !optional bool
|
||||||
generate_concept_requirements: !optional bool
|
generate_concept_requirements: !optional bool
|
||||||
generate_return_types: !optional bool
|
generate_return_types: !optional bool
|
||||||
generate_condition_statements: !optional bool
|
generate_condition_statements: !optional bool
|
||||||
|
|||||||
@@ -665,6 +665,7 @@ template <> struct convert<sequence_diagram> {
|
|||||||
get_option(node, rhs.from_to);
|
get_option(node, rhs.from_to);
|
||||||
get_option(node, rhs.to);
|
get_option(node, rhs.to);
|
||||||
get_option(node, rhs.combine_free_functions_into_file_participants);
|
get_option(node, rhs.combine_free_functions_into_file_participants);
|
||||||
|
get_option(node, rhs.inline_lambda_messages);
|
||||||
get_option(node, rhs.generate_return_types);
|
get_option(node, rhs.generate_return_types);
|
||||||
get_option(node, rhs.generate_condition_statements);
|
get_option(node, rhs.generate_condition_statements);
|
||||||
get_option(node, rhs.participants_order);
|
get_option(node, rhs.participants_order);
|
||||||
@@ -844,6 +845,7 @@ template <> struct convert<config> {
|
|||||||
get_option(node, rhs.debug_mode);
|
get_option(node, rhs.debug_mode);
|
||||||
get_option(node, rhs.generate_metadata);
|
get_option(node, rhs.generate_metadata);
|
||||||
get_option(node, rhs.combine_free_functions_into_file_participants);
|
get_option(node, rhs.combine_free_functions_into_file_participants);
|
||||||
|
get_option(node, rhs.inline_lambda_messages);
|
||||||
get_option(node, rhs.generate_return_types);
|
get_option(node, rhs.generate_return_types);
|
||||||
get_option(node, rhs.generate_condition_statements);
|
get_option(node, rhs.generate_condition_statements);
|
||||||
get_option(node, rhs.generate_message_comments);
|
get_option(node, rhs.generate_message_comments);
|
||||||
|
|||||||
@@ -347,6 +347,7 @@ YAML::Emitter &operator<<(
|
|||||||
sd != nullptr) {
|
sd != nullptr) {
|
||||||
out << sd->title;
|
out << sd->title;
|
||||||
out << c.combine_free_functions_into_file_participants;
|
out << c.combine_free_functions_into_file_participants;
|
||||||
|
out << c.inline_lambda_messages;
|
||||||
out << c.generate_condition_statements;
|
out << c.generate_condition_statements;
|
||||||
out << c.generate_method_arguments;
|
out << c.generate_method_arguments;
|
||||||
out << c.generate_return_types;
|
out << c.generate_return_types;
|
||||||
|
|||||||
@@ -125,8 +125,8 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
|
|||||||
void generator::generate_return(const message &m, std::ostream &ostr) const
|
void generator::generate_return(const message &m, std::ostream &ostr) const
|
||||||
{
|
{
|
||||||
|
|
||||||
// Add return activity only for messages between different actors and
|
// Add return activity only for messages between different actors
|
||||||
// only if the return type is different than void
|
// and only if the return type is different than void
|
||||||
if (m.from() == m.to())
|
if (m.from() == m.to())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -338,7 +338,8 @@ void generator::generate_participant(
|
|||||||
auto p = model().get(name);
|
auto p = model().get(name);
|
||||||
|
|
||||||
if (!p.has_value()) {
|
if (!p.has_value()) {
|
||||||
LOG_WARN("Cannot find participant {} from `participants_order` option",
|
LOG_WARN("Cannot find participant {} from `participants_order` "
|
||||||
|
"option",
|
||||||
name);
|
name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -629,10 +630,10 @@ void generator::generate_diagram(std::ostream &ostr) const
|
|||||||
model::function::message_render_mode render_mode =
|
model::function::message_render_mode render_mode =
|
||||||
select_method_arguments_render_mode();
|
select_method_arguments_render_mode();
|
||||||
|
|
||||||
// For methods or functions in diagrams where they are combined into
|
// For methods or functions in diagrams where they are
|
||||||
// file participants, we need to add an 'entry' point call to know
|
// combined into file participants, we need to add an
|
||||||
// which method relates to the first activity for this 'start_from'
|
// 'entry' point call to know which method relates to the
|
||||||
// condition
|
// first activity for this 'start_from' condition
|
||||||
if (from.value().type_name() == "method" ||
|
if (from.value().type_name() == "method" ||
|
||||||
config().combine_free_functions_into_file_participants()) {
|
config().combine_free_functions_into_file_participants()) {
|
||||||
ostr << "[->"
|
ostr << "[->"
|
||||||
|
|||||||
@@ -95,22 +95,22 @@ void diagram::add_active_participant(common::id_t id)
|
|||||||
|
|
||||||
const activity &diagram::get_activity(common::id_t id) const
|
const activity &diagram::get_activity(common::id_t id) const
|
||||||
{
|
{
|
||||||
return sequences_.at(id);
|
return activities_.at(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool diagram::has_activity(common::id_t id) const
|
bool diagram::has_activity(common::id_t id) const
|
||||||
{
|
{
|
||||||
return sequences_.count(id) > 0;
|
return activities_.count(id) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
activity &diagram::get_activity(common::id_t id) { return sequences_.at(id); }
|
activity &diagram::get_activity(common::id_t id) { return activities_.at(id); }
|
||||||
|
|
||||||
void diagram::add_message(model::message &&message)
|
void diagram::add_message(model::message &&message)
|
||||||
{
|
{
|
||||||
const auto caller_id = message.from();
|
const auto caller_id = message.from();
|
||||||
if (sequences_.find(caller_id) == sequences_.end()) {
|
if (activities_.find(caller_id) == activities_.end()) {
|
||||||
activity a{caller_id};
|
activity a{caller_id};
|
||||||
sequences_.insert({caller_id, std::move(a)});
|
activities_.insert({caller_id, std::move(a)});
|
||||||
}
|
}
|
||||||
|
|
||||||
get_activity(caller_id).add_message(std::move(message));
|
get_activity(caller_id).add_message(std::move(message));
|
||||||
@@ -126,7 +126,7 @@ void diagram::end_block_message(
|
|||||||
{
|
{
|
||||||
const auto caller_id = message.from();
|
const auto caller_id = message.from();
|
||||||
|
|
||||||
if (sequences_.find(caller_id) != sequences_.end()) {
|
if (activities_.find(caller_id) != activities_.end()) {
|
||||||
auto ¤t_messages = get_activity(caller_id).messages();
|
auto ¤t_messages = get_activity(caller_id).messages();
|
||||||
|
|
||||||
fold_or_end_block_statement(
|
fold_or_end_block_statement(
|
||||||
@@ -139,7 +139,7 @@ void diagram::add_case_stmt_message(model::message &&m)
|
|||||||
using clanguml::common::model::message_t;
|
using clanguml::common::model::message_t;
|
||||||
const auto caller_id = m.from();
|
const auto caller_id = m.from();
|
||||||
|
|
||||||
if (sequences_.find(caller_id) != sequences_.end()) {
|
if (activities_.find(caller_id) != activities_.end()) {
|
||||||
auto ¤t_messages = get_activity(caller_id).messages();
|
auto ¤t_messages = get_activity(caller_id).messages();
|
||||||
|
|
||||||
if (current_messages.back().type() == message_t::kCase) {
|
if (current_messages.back().type() == message_t::kCase) {
|
||||||
@@ -151,11 +151,11 @@ void diagram::add_case_stmt_message(model::message &&m)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<common::id_t, activity> &diagram::sequences() { return sequences_; }
|
std::map<common::id_t, activity> &diagram::sequences() { return activities_; }
|
||||||
|
|
||||||
const std::map<common::id_t, activity> &diagram::sequences() const
|
const std::map<common::id_t, activity> &diagram::sequences() const
|
||||||
{
|
{
|
||||||
return sequences_;
|
return activities_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<common::id_t, std::unique_ptr<participant>> &diagram::participants()
|
std::map<common::id_t, std::unique_ptr<participant>> &diagram::participants()
|
||||||
@@ -191,7 +191,7 @@ std::vector<std::string> diagram::list_from_values() const
|
|||||||
{
|
{
|
||||||
std::vector<std::string> result;
|
std::vector<std::string> result;
|
||||||
|
|
||||||
for (const auto &[from_id, act] : sequences_) {
|
for (const auto &[from_id, act] : activities_) {
|
||||||
|
|
||||||
const auto &from_activity = *(participants_.at(from_id));
|
const auto &from_activity = *(participants_.at(from_id));
|
||||||
const auto &full_name = from_activity.full_name(false);
|
const auto &full_name = from_activity.full_name(false);
|
||||||
@@ -209,7 +209,7 @@ std::vector<std::string> diagram::list_to_values() const
|
|||||||
{
|
{
|
||||||
std::vector<std::string> result;
|
std::vector<std::string> result;
|
||||||
|
|
||||||
for (const auto &[from_id, act] : sequences_) {
|
for (const auto &[from_id, act] : activities_) {
|
||||||
for (const auto &m : act.messages()) {
|
for (const auto &m : act.messages()) {
|
||||||
if (participants_.count(m.to()) > 0) {
|
if (participants_.count(m.to()) > 0) {
|
||||||
const auto &to_activity = *(participants_.at(m.to()));
|
const auto &to_activity = *(participants_.at(m.to()));
|
||||||
@@ -406,7 +406,7 @@ std::vector<message_chain_t> diagram::get_all_from_to_message_chains(
|
|||||||
|
|
||||||
bool diagram::is_empty() const
|
bool diagram::is_empty() const
|
||||||
{
|
{
|
||||||
return sequences_.empty() || participants_.empty();
|
return activities_.empty() || participants_.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void diagram::print() const
|
void diagram::print() const
|
||||||
@@ -417,7 +417,7 @@ void diagram::print() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOG_TRACE(" --- Activities ---");
|
LOG_TRACE(" --- Activities ---");
|
||||||
for (const auto &[from_id, act] : sequences_) {
|
for (const auto &[from_id, act] : activities_) {
|
||||||
if (participants_.count(from_id) == 0)
|
if (participants_.count(from_id) == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -487,7 +487,7 @@ void diagram::finalize()
|
|||||||
|
|
||||||
// First in each sequence (activity) filter out any remaining
|
// First in each sequence (activity) filter out any remaining
|
||||||
// uninteresting calls
|
// uninteresting calls
|
||||||
for (auto &[id, act] : sequences_) {
|
for (auto &[id, act] : activities_) {
|
||||||
util::erase_if(act.messages(), [this](auto &m) {
|
util::erase_if(act.messages(), [this](auto &m) {
|
||||||
if (m.type() != message_t::kCall)
|
if (m.type() != message_t::kCall)
|
||||||
return false;
|
return false;
|
||||||
@@ -507,7 +507,7 @@ void diagram::finalize()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now remove any empty block statements, e.g. if/endif
|
// Now remove any empty block statements, e.g. if/endif
|
||||||
for (auto &[id, act] : sequences_) {
|
for (auto &[id, act] : activities_) {
|
||||||
int64_t block_nest_level{0};
|
int64_t block_nest_level{0};
|
||||||
std::vector<std::vector<message>> block_message_stack;
|
std::vector<std::vector<message>> block_message_stack;
|
||||||
// Add first stack level - this level will contain the filtered
|
// Add first stack level - this level will contain the filtered
|
||||||
|
|||||||
@@ -295,6 +295,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool is_empty() const override;
|
bool is_empty() const override;
|
||||||
|
|
||||||
|
void update_sequences_from_diagram(diagram &other)
|
||||||
|
{
|
||||||
|
activities_ = std::move(other.activities_);
|
||||||
|
participants_ = std::move(other.participants_);
|
||||||
|
active_participants_ = std::move(other.active_participants_);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* This method checks the last messages in sequence (current_messages),
|
* This method checks the last messages in sequence (current_messages),
|
||||||
@@ -337,7 +344,7 @@ private:
|
|||||||
return block_end_types.count(mt) > 0;
|
return block_end_types.count(mt) > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::map<common::id_t, activity> sequences_;
|
std::map<common::id_t, activity> activities_;
|
||||||
|
|
||||||
std::map<common::id_t, std::unique_ptr<participant>> participants_;
|
std::map<common::id_t, std::unique_ptr<participant>> participants_;
|
||||||
|
|
||||||
|
|||||||
@@ -630,7 +630,7 @@ bool translation_unit_visitor::TraverseCXXConstructExpr(
|
|||||||
|
|
||||||
translation_unit_visitor::VisitCXXConstructExpr(expr);
|
translation_unit_visitor::VisitCXXConstructExpr(expr);
|
||||||
|
|
||||||
LOG_TRACE("Leaving member call expression at {}",
|
LOG_TRACE("Leaving cxx construct call expression at {}",
|
||||||
expr->getBeginLoc().printToString(source_manager()));
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
|
|
||||||
context().leave_callexpr();
|
context().leave_callexpr();
|
||||||
@@ -2018,6 +2018,38 @@ void translation_unit_visitor::pop_message_to_diagram(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void translation_unit_visitor::finalize()
|
void translation_unit_visitor::finalize()
|
||||||
|
{
|
||||||
|
resolve_ids_to_global();
|
||||||
|
|
||||||
|
// Change all messages with target set to an id of a lambda expression to
|
||||||
|
// to the ID of their operator() - this is necessary, as some calls to
|
||||||
|
// lambda expressions are visited before the actual lambda expressions
|
||||||
|
// are visited...
|
||||||
|
ensure_lambda_messages_have_operator_as_target();
|
||||||
|
|
||||||
|
if (config().inline_lambda_messages())
|
||||||
|
inline_lambda_operator_calls();
|
||||||
|
}
|
||||||
|
|
||||||
|
void translation_unit_visitor::ensure_lambda_messages_have_operator_as_target()
|
||||||
|
{
|
||||||
|
for (auto &[id, activity] : diagram().sequences()) {
|
||||||
|
for (auto &m : activity.messages()) {
|
||||||
|
auto participant = diagram().get_participant<model::class_>(m.to());
|
||||||
|
|
||||||
|
if (participant && participant.value().is_lambda() &&
|
||||||
|
participant.value().lambda_operator_id() != 0) {
|
||||||
|
LOG_DBG("Changing lambda expression target id from {} to {}",
|
||||||
|
m.to(), participant.value().lambda_operator_id());
|
||||||
|
|
||||||
|
m.set_to(participant.value().lambda_operator_id());
|
||||||
|
m.set_message_name("operator()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void translation_unit_visitor::resolve_ids_to_global()
|
||||||
{
|
{
|
||||||
std::set<common::id_t> active_participants_unique;
|
std::set<common::id_t> active_participants_unique;
|
||||||
|
|
||||||
@@ -2041,25 +2073,116 @@ void translation_unit_visitor::finalize()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Change all messages with target set to an id of a lambda expression to
|
void translation_unit_visitor::inline_lambda_operator_calls()
|
||||||
// to the ID of their operator() - this is necessary, as some calls to
|
{ // If option to inline lambda calls is enabled, we need to modify the
|
||||||
// lambda expressions are visited before the actual lambda expressions
|
// sequences to skip the lambda calls. In case lambda call does not lead
|
||||||
// are visited...
|
// to a non-lambda call, omit it entirely
|
||||||
for (auto &[id, activity] : diagram().sequences()) {
|
|
||||||
for (auto &m : activity.messages()) {
|
|
||||||
auto participant = diagram().get_participant<model::class_>(m.to());
|
|
||||||
|
|
||||||
if (participant && participant.value().is_lambda() &&
|
model::diagram lambdaless_diagram;
|
||||||
participant.value().lambda_operator_id() != 0) {
|
|
||||||
LOG_DBG("Changing lambda expression target id from {} to {}",
|
|
||||||
m.to(), participant.value().lambda_operator_id());
|
|
||||||
|
|
||||||
m.set_to(participant.value().lambda_operator_id());
|
for (auto &[id, act] : diagram().sequences()) {
|
||||||
m.set_message_name("operator()");
|
model::activity new_activity{id};
|
||||||
|
|
||||||
|
// If activity is a lambda operator() - skip it
|
||||||
|
auto maybe_lambda_activity =
|
||||||
|
diagram().get_participant<model::method>(id);
|
||||||
|
|
||||||
|
if (maybe_lambda_activity) {
|
||||||
|
const auto parent_class_id =
|
||||||
|
maybe_lambda_activity.value().class_id();
|
||||||
|
auto maybe_parent_class =
|
||||||
|
diagram().get_participant<model::class_>(parent_class_id);
|
||||||
|
|
||||||
|
if (maybe_parent_class && maybe_parent_class.value().is_lambda()) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For other activities, check each message - if it calls lambda
|
||||||
|
// operator() - reattach the message to the next activity in the chain
|
||||||
|
// (assuming it's not lambda)
|
||||||
|
for (auto &m : act.messages()) {
|
||||||
|
|
||||||
|
auto message_call_to_lambda{false};
|
||||||
|
|
||||||
|
message_call_to_lambda =
|
||||||
|
inline_lambda_operator_call(id, new_activity, m);
|
||||||
|
|
||||||
|
if (!message_call_to_lambda)
|
||||||
|
new_activity.add_message(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add activity
|
||||||
|
lambdaless_diagram.sequences().insert({id, std::move(new_activity)});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &&[id, p] : diagram().participants()) {
|
||||||
|
// Skip participants which are lambda classes
|
||||||
|
if (const auto *maybe_class =
|
||||||
|
dynamic_cast<const model::class_ *>(p.get());
|
||||||
|
maybe_class && maybe_class->is_lambda()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip participants which are lambda operator methods
|
||||||
|
if (const auto *maybe_method =
|
||||||
|
dynamic_cast<const model::method *>(p.get());
|
||||||
|
maybe_method) {
|
||||||
|
auto maybe_class = diagram().get_participant<model::class_>(
|
||||||
|
maybe_method->class_id());
|
||||||
|
if (maybe_class && maybe_class.value().is_lambda())
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise move the participant to the new diagram model
|
||||||
|
lambdaless_diagram.add_participant(std::move(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip active participants which are not in lambdaless_diagram participants
|
||||||
|
for (auto id : diagram().active_participants()) {
|
||||||
|
if (lambdaless_diagram.participants().count(id)) {
|
||||||
|
lambdaless_diagram.add_active_participant(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diagram().update_sequences_from_diagram(lambdaless_diagram);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool translation_unit_visitor::inline_lambda_operator_call(
|
||||||
|
const long id, model::activity &new_activity, const model::message &m)
|
||||||
|
{
|
||||||
|
bool message_call_to_lambda{false};
|
||||||
|
auto maybe_lambda_operator =
|
||||||
|
diagram().get_participant<model::method>(m.to());
|
||||||
|
|
||||||
|
if (maybe_lambda_operator) {
|
||||||
|
const auto parent_class_id = maybe_lambda_operator.value().class_id();
|
||||||
|
auto maybe_parent_class =
|
||||||
|
diagram().get_participant<model::class_>(parent_class_id);
|
||||||
|
|
||||||
|
if (maybe_parent_class && maybe_parent_class.value().is_lambda()) {
|
||||||
|
// auto new_message{m};
|
||||||
|
// new_message.set_
|
||||||
|
auto lambda_operator_activity = diagram().get_activity(m.to());
|
||||||
|
|
||||||
|
// For each call in that lambda activity - reattach this
|
||||||
|
// call to the current activity
|
||||||
|
for (auto &mm : lambda_operator_activity.messages()) {
|
||||||
|
if (!inline_lambda_operator_call(id, new_activity, mm)) {
|
||||||
|
auto new_message{mm};
|
||||||
|
|
||||||
|
new_message.set_from(id);
|
||||||
|
new_activity.add_message(new_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message_call_to_lambda = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return message_call_to_lambda;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<clanguml::sequence_diagram::model::method>
|
std::unique_ptr<clanguml::sequence_diagram::model::method>
|
||||||
|
|||||||
@@ -487,6 +487,11 @@ private:
|
|||||||
*/
|
*/
|
||||||
template_builder_t &tbuilder() { return template_builder_; }
|
template_builder_t &tbuilder() { return template_builder_; }
|
||||||
|
|
||||||
|
void inline_lambda_operator_calls();
|
||||||
|
|
||||||
|
bool inline_lambda_operator_call(
|
||||||
|
const long id, model::activity &new_activity, const model::message &m);
|
||||||
|
|
||||||
call_expression_context call_expression_context_;
|
call_expression_context call_expression_context_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -522,5 +527,7 @@ private:
|
|||||||
processed_comments_by_caller_id_;
|
processed_comments_by_caller_id_;
|
||||||
|
|
||||||
template_builder_t template_builder_;
|
template_builder_t template_builder_;
|
||||||
|
void resolve_ids_to_global();
|
||||||
|
void ensure_lambda_messages_have_operator_as_target();
|
||||||
};
|
};
|
||||||
} // namespace clanguml::sequence_diagram::visitor
|
} // namespace clanguml::sequence_diagram::visitor
|
||||||
|
|||||||
12
tests/t20052/.clang-uml
Normal file
12
tests/t20052/.clang-uml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
diagrams:
|
||||||
|
t20052_sequence:
|
||||||
|
type: sequence
|
||||||
|
glob:
|
||||||
|
- t20052.cc
|
||||||
|
include:
|
||||||
|
namespaces:
|
||||||
|
- clanguml::t20052
|
||||||
|
using_namespace: clanguml::t20052
|
||||||
|
inline_lambda_messages: true
|
||||||
|
from:
|
||||||
|
- function: "clanguml::t20052::tmain()"
|
||||||
105
tests/t20052/t20052.cc
Normal file
105
tests/t20052/t20052.cc
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace clanguml {
|
||||||
|
namespace t20052 {
|
||||||
|
struct A {
|
||||||
|
void a() { aa(); }
|
||||||
|
|
||||||
|
void aa() { aaa(); }
|
||||||
|
|
||||||
|
void aaa() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct B {
|
||||||
|
void b() { bb(); }
|
||||||
|
|
||||||
|
void bb() { bbb(); }
|
||||||
|
|
||||||
|
void bbb() { }
|
||||||
|
|
||||||
|
void eb() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct C {
|
||||||
|
void c() { cc(); }
|
||||||
|
|
||||||
|
void cc() { ccc(); }
|
||||||
|
|
||||||
|
void ccc() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct D {
|
||||||
|
int add5(int arg) const { return arg + 5; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class E {
|
||||||
|
std::optional<std::shared_ptr<B>> maybe_b;
|
||||||
|
std::shared_ptr<A> a;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename F> void setup(F &&f) { f(maybe_b); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename F> struct R {
|
||||||
|
R(F &&f)
|
||||||
|
: f_{std::move(f)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void r() { f_(); }
|
||||||
|
|
||||||
|
F f_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void tmain()
|
||||||
|
{
|
||||||
|
A a;
|
||||||
|
B b;
|
||||||
|
C c;
|
||||||
|
|
||||||
|
// The activity shouldn't be marked at the lambda definition, but
|
||||||
|
// wherever it is actually called...
|
||||||
|
auto alambda = [&a, &b]() {
|
||||||
|
a.a();
|
||||||
|
b.b();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...like here
|
||||||
|
alambda();
|
||||||
|
|
||||||
|
// There should be no call to B in the sequence diagram as the blambda
|
||||||
|
// is never called
|
||||||
|
[[maybe_unused]] auto blambda = [&b]() { b.b(); };
|
||||||
|
|
||||||
|
// Nested lambdas should also work
|
||||||
|
auto clambda = [alambda, &c]() {
|
||||||
|
c.c();
|
||||||
|
alambda();
|
||||||
|
};
|
||||||
|
clambda();
|
||||||
|
|
||||||
|
R r{[&c]() { c.c(); }};
|
||||||
|
|
||||||
|
r.r();
|
||||||
|
|
||||||
|
D d;
|
||||||
|
|
||||||
|
std::vector<int> ints{0, 1, 2, 3, 4};
|
||||||
|
std::transform(ints.begin(), ints.end(), ints.begin(),
|
||||||
|
[&d](auto i) { return d.add5(i); });
|
||||||
|
|
||||||
|
// TODO: Fix naming function call arguments which are lambdas
|
||||||
|
// E e;
|
||||||
|
//
|
||||||
|
// e.setup([](auto &&arg) mutable {
|
||||||
|
// // We cannot know here what 'arg' might be
|
||||||
|
// arg.value()->eb();
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
tests/t20052/test_case.h
Normal file
176
tests/t20052/test_case.h
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* tests/t20052/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("t20052", "[test-case][sequence]")
|
||||||
|
{
|
||||||
|
auto [config, db] = load_config("t20052");
|
||||||
|
|
||||||
|
auto diagram = config.diagrams["t20052_sequence"];
|
||||||
|
|
||||||
|
REQUIRE(diagram->name == "t20052_sequence");
|
||||||
|
|
||||||
|
auto model = generate_sequence_diagram(*db, diagram);
|
||||||
|
|
||||||
|
REQUIRE(model->name() == "t20052_sequence");
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_puml(diagram, *model);
|
||||||
|
AliasMatcher _A(src);
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, StartsWith("@startuml"));
|
||||||
|
REQUIRE_THAT(src, EndsWith("@enduml\n"));
|
||||||
|
|
||||||
|
// Check if all calls exist
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()"), _A("tmain()::(lambda t20052.cc:67:20)"),
|
||||||
|
"operator()() const"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:67:20)"), _A("A"), "a()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("A"), "a()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("A"), _A("A"), "aa()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("A"), _A("A"), "aaa()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:67:20)"), _A("B"), "b()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "b()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("B"), _A("B"), "bb()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("B"), _A("B"), "bbb()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:80:20)"), _A("C"), "c()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("C"), "c()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "cc()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "ccc()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:80:20)"),
|
||||||
|
_A("tmain()::(lambda t20052.cc:67:20)"), "operator()() const"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "ccc()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()"), _A("R<(lambda at t20052.cc:86:9)>"),
|
||||||
|
"R((lambda at t20052.cc:86:9) &&)"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()"), _A("R<(lambda at t20052.cc:86:9)>"), "r()"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("R<(lambda at t20052.cc:86:9)>"),
|
||||||
|
_A("tmain()::(lambda t20052.cc:86:9)"), "operator()() const"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(
|
||||||
|
src, HasCall(_A("R<(lambda at t20052.cc:86:9)>"), _A("C"), "c()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()"), _A("tmain()::(lambda t20052.cc:94:9)"),
|
||||||
|
"operator()(auto) const"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:94:9)"), _A("D"),
|
||||||
|
"add5(int) const"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("D"), "add5(int) const"));
|
||||||
|
|
||||||
|
save_puml(config.output_directory(), diagram->name + ".puml", src);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto j = generate_sequence_json(diagram, *model);
|
||||||
|
|
||||||
|
using namespace json;
|
||||||
|
|
||||||
|
std::vector<int> messages = {
|
||||||
|
FindMessage(j, "tmain()", "A", "a()"),
|
||||||
|
FindMessage(j, "A", "A", "aa()"),
|
||||||
|
FindMessage(j, "A", "A", "aaa()"),
|
||||||
|
FindMessage(j, "tmain()", "B", "b()"),
|
||||||
|
FindMessage(j, "B", "B", "bb()"),
|
||||||
|
FindMessage(j, "B", "B", "bbb()"),
|
||||||
|
FindMessage(j, "tmain()", "C", "c()"),
|
||||||
|
FindMessage(j, "C", "C", "cc()"),
|
||||||
|
FindMessage(j, "C", "C", "ccc()"),
|
||||||
|
FindMessage(j, "tmain()", "R<(lambda at t20052.cc:86:9)>", "r()"),
|
||||||
|
FindMessage(j, "R<(lambda at t20052.cc:86:9)>", "C", "c()"),
|
||||||
|
};
|
||||||
|
|
||||||
|
REQUIRE(std::is_sorted(messages.begin(), messages.end()));
|
||||||
|
|
||||||
|
save_json(config.output_directory(), diagram->name + ".json", j);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_mermaid(diagram, *model);
|
||||||
|
|
||||||
|
mermaid::SequenceDiagramAliasMatcher _A(src);
|
||||||
|
using mermaid::HasCall;
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()"), _A("tmain()::(lambda t20052.cc:67:20)"),
|
||||||
|
"operator()() const"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:67:20)"), _A("A"), "a()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("A"), "a()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("A"), _A("A"), "aa()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("A"), _A("A"), "aaa()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:67:20)"), _A("B"), "b()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "b()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("B"), _A("B"), "bb()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("B"), _A("B"), "bbb()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:80:20)"), _A("C"), "c()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("C"), "c()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "cc()"));
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "ccc()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:80:20)"),
|
||||||
|
_A("tmain()::(lambda t20052.cc:67:20)"), "operator()() const"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("C"), _A("C"), "ccc()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()"), _A("R<(lambda at t20052.cc:86:9)>"),
|
||||||
|
"R((lambda at t20052.cc:86:9) &&)"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()"), _A("R<(lambda at t20052.cc:86:9)>"), "r()"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("R<(lambda at t20052.cc:86:9)>"),
|
||||||
|
_A("tmain()::(lambda t20052.cc:86:9)"), "operator()() const"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(
|
||||||
|
src, HasCall(_A("R<(lambda at t20052.cc:86:9)>"), _A("C"), "c()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()"), _A("tmain()::(lambda t20052.cc:94:9)"),
|
||||||
|
"operator()(auto) const"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
!HasCall(_A("tmain()::(lambda t20052.cc:94:9)"), _A("D"),
|
||||||
|
"add5(int) const"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("D"), "add5(int) const"));
|
||||||
|
|
||||||
|
save_mermaid(config.output_directory(), diagram->name + ".mmd", src);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -480,6 +480,7 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t20050/test_case.h"
|
#include "t20050/test_case.h"
|
||||||
#include "t20051/test_case.h"
|
#include "t20051/test_case.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "t20052/test_case.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Package diagram tests
|
/// Package diagram tests
|
||||||
|
|||||||
@@ -376,6 +376,9 @@ test_cases:
|
|||||||
- name: t20051
|
- name: t20051
|
||||||
title: Test case for CUDA calls callee_type filter
|
title: Test case for CUDA calls callee_type filter
|
||||||
description:
|
description:
|
||||||
|
- name: t20052
|
||||||
|
title: Test case for CUDA calls callee_type filter
|
||||||
|
description:
|
||||||
Package diagrams:
|
Package diagrams:
|
||||||
- name: t30001
|
- name: t30001
|
||||||
title: Basic package diagram test case
|
title: Basic package diagram test case
|
||||||
|
|||||||
Reference in New Issue
Block a user