Merge pull request #6 from bkryza/add-template-overload-pattern-test-case

Add template overload pattern test case
This commit is contained in:
Bartek Kryza
2021-08-04 00:00:17 +02:00
committed by GitHub
39 changed files with 434 additions and 154 deletions

View File

@@ -62,4 +62,4 @@ init_compile_commands: debug
.PHONY: clang-format .PHONY: clang-format
clang-format: clang-format:
docker run --rm -v $(CURDIR):/root/sources bkryza/clang-format-check:1.2 docker run --rm -v $(CURDIR):/root/sources bkryza/clang-format-check:1.3

View File

@@ -163,6 +163,17 @@ The following decorators are currently supported:
- [aggregation](docs/test_cases/t00030.md) - document the property as aggregation - [aggregation](docs/test_cases/t00030.md) - document the property as aggregation
- [style](docs/test_cases/t00031.md) - add PlantUML style to a C++ entity - [style](docs/test_cases/t00031.md) - add PlantUML style to a C++ entity
##### Doxygen integration
`clang-uml` can be omitted completed in ![Doxygen](https://www.doxygen.nl/index.html), by adding the following
lines to the Doxygen config file:
```
ALIASES += clanguml=""
ALIASES += clanguml{1}=""
ALIASES += clanguml{2}=""
ALIASES += clanguml{3}=""
```
### Sequence diagrams ### Sequence diagrams
#### Example #### Example

View File

@@ -30,6 +30,7 @@
* [t00029](./test_cases/t00029.md) - PlantUML skip decorator test case * [t00029](./test_cases/t00029.md) - PlantUML skip decorator test case
* [t00030](./test_cases/t00030.md) - PlantUML relationship decorators test case * [t00030](./test_cases/t00030.md) - PlantUML relationship decorators test case
* [t00031](./test_cases/t00031.md) - PlantUML style decorator test case * [t00031](./test_cases/t00031.md) - PlantUML style decorator test case
* [t00032](./test_cases/t00032.md) - Class template with template base classes test case
## Sequence diagrams ## Sequence diagrams
* [t20001](./test_cases/t20001.md) - Basic sequence diagram * [t20001](./test_cases/t20001.md) - Basic sequence diagram
## Configuration diagrams ## Configuration diagrams

View File

@@ -28,7 +28,8 @@ class A {
} }
namespace ns2_v0_9_0 { namespace ns2_v0_9_0 {
class [[deprecated]] A {}; class [[deprecated]] A {
};
} }
namespace { namespace {

62
docs/test_cases/t00032.md Normal file
View File

@@ -0,0 +1,62 @@
# t00032 - Class template with template base classes test case
## Config
```yaml
compilation_database_dir: ..
output_directory: puml
diagrams:
t00032_class:
type: class
glob:
- ../../tests/t00032/t00032.cc
using_namespace:
- clanguml::t00032
include:
namespaces:
- clanguml::t00032
```
## Source code
File t00032.cc
```cpp
#include <memory>
#include <vector>
namespace clanguml {
namespace t00032 {
struct Base {
};
struct TBase {
};
struct A {
void operator()() { }
};
struct B {
void operator()() { }
};
struct C {
void operator()() { }
};
template <typename T, typename L, typename... Ts>
struct Overload : public Base, public T, public Ts... {
using Ts::operator()...;
L counter;
};
template <class... Ts> Overload(Ts...) -> Overload<Ts...>;
struct R {
Overload<TBase, int, A, B, C> overload;
};
} // namespace t00032
} // namespace clanguml
```
## Generated UML diagrams
![t00032_class](./t00032_class.png "Class template with template base classes test case")

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -31,6 +31,8 @@
#include <cppast/cpp_variable.hpp> #include <cppast/cpp_variable.hpp>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <deque>
namespace clanguml { namespace clanguml {
namespace visitor { namespace visitor {
namespace class_diagram { namespace class_diagram {
@@ -1185,6 +1187,8 @@ class_ tu_visitor::build_template_instantiation(
class_ tinst; class_ tinst;
std::string full_template_name; std::string full_template_name;
std::deque<std::tuple<std::string, int, bool>> template_base_params{};
if (t.primary_template().get(ctx.entity_index).size()) { if (t.primary_template().get(ctx.entity_index).size()) {
const auto &primary_template_ref = const auto &primary_template_ref =
static_cast<const cppast::cpp_class_template &>( static_cast<const cppast::cpp_class_template &>(
@@ -1201,6 +1205,54 @@ class_ tu_visitor::build_template_instantiation(
if (full_template_name.back() == ':') if (full_template_name.back() == ':')
tinst.name = full_template_name + tinst.name; tinst.name = full_template_name + tinst.name;
std::vector<std::pair<std::string, bool>> template_parameter_names{};
if (primary_template_ref.scope_name().has_value()) {
for (const auto &tp : primary_template_ref.scope_name()
.value()
.template_parameters()) {
template_parameter_names.emplace_back(
tp.name(), tp.is_variadic());
}
}
// Check if the primary template has any base classes
int base_index = 0;
for (const auto &base : primary_template_ref.bases()) {
if (base.kind() == cppast::cpp_entity_kind::base_class_t) {
const auto &base_class =
static_cast<const cppast::cpp_base_class &>(base);
const auto base_class_name =
cppast::to_string(base_class.type());
LOG_DBG("Found template instantiation base: {}, {}, {}",
cppast::to_string(base.kind()), base_class_name,
base_index);
// Check if any of the primary template arguments has a
// parameter equal to this type
auto it = std::find_if(template_parameter_names.begin(),
template_parameter_names.end(),
[&base_class_name](
const auto &p) { return p.first == base_class_name; });
if (it != template_parameter_names.end()) {
// Found base class which is a template parameter
LOG_DBG("Found base class which is a template parameter "
"{}, {}, {}",
it->first, it->second,
std::distance(template_parameter_names.begin(), it));
template_base_params.emplace_back(it->first, it->second,
std::distance(template_parameter_names.begin(), it));
}
else {
// This is a regular base class - it is handled by
// process_template
}
}
base_index++;
}
if (primary_template_ref.user_data()) { if (primary_template_ref.user_data()) {
tinst.base_template_usr = tinst.base_template_usr =
static_cast<const char *>(primary_template_ref.user_data()); static_cast<const char *>(primary_template_ref.user_data());
@@ -1232,7 +1284,11 @@ class_ tu_visitor::build_template_instantiation(
tinst.is_template_instantiation = true; tinst.is_template_instantiation = true;
// Process template argumetns
int arg_index{0};
bool variadic_params{false};
for (const auto &targ : t.arguments().value()) { for (const auto &targ : t.arguments().value()) {
bool add_template_argument_as_base_class{false};
class_template ct; class_template ct;
if (targ.type()) { if (targ.type()) {
ct.type = cppast::to_string(targ.type().value()); ct.type = cppast::to_string(targ.type().value());
@@ -1250,6 +1306,31 @@ class_ tu_visitor::build_template_instantiation(
.as_string(); .as_string();
} }
// In case any of the template arguments are base classes, add
// them as parents of the current template instantiation class
if (template_base_params.size() > 0) {
auto [arg_name, is_variadic, index] = template_base_params.front();
if (variadic_params)
add_template_argument_as_base_class = true;
else {
variadic_params = is_variadic;
if (arg_index == index) {
add_template_argument_as_base_class = true;
template_base_params.pop_front();
}
}
if (add_template_argument_as_base_class) {
LOG_DBG("Adding template argument '{}' as base class", ct.type);
class_parent cp;
cp.access = class_parent::access_t::kPublic;
cp.name = ct.type;
tinst.bases.emplace_back(std::move(cp));
}
}
LOG_DBG("Adding template argument '{}'", ct.type); LOG_DBG("Adding template argument '{}'", ct.type);
tinst.templates.emplace_back(std::move(ct)); tinst.templates.emplace_back(std::move(ct));

View File

@@ -853,8 +853,9 @@ constexpr auto operator"" _catch_sr(
INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))
#else #else
#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) \ #define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) \
INTERNAL_CATCH_EXPAND_VARGS(decltype( \ INTERNAL_CATCH_EXPAND_VARGS( \
get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN( \
__VA_ARGS__)>()))
#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) \ #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) \
INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2( \ INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2( \
INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)))
@@ -1277,8 +1278,8 @@ template <typename> struct true_given : std::true_type {
}; };
struct is_callable_tester { struct is_callable_tester {
template <typename Fun, typename... Args> template <typename Fun, typename... Args>
true_given<decltype( true_given<decltype(std::declval<Fun>()(
std::declval<Fun>()(std::declval<Args>()...))> static test(int); std::declval<Args>()...))> static test(int);
template <typename...> std::false_type static test(...); template <typename...> std::false_type static test(...);
}; };
@@ -1634,8 +1635,8 @@ struct AutoReg : NonCopyable {
}; \ }; \
static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \
using TestInit = typename create<TestName, \ using TestInit = typename create<TestName, \
decltype( \ decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS( \
get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), \ TmplTypes)>()), \
TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES( \ TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES( \
INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \ INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \
TestInit t; \ TestInit t; \
@@ -1851,8 +1852,8 @@ struct AutoReg : NonCopyable {
}; \ }; \
static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \ static int INTERNAL_CATCH_UNIQUE_NAME(globalRegistrar) = []() { \
using TestInit = typename create<TestNameClass, \ using TestInit = typename create<TestNameClass, \
decltype( \ decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS( \
get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), \ TmplTypes)>()), \
TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES( \ TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES( \
INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \ INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \
TestInit t; \ TestInit t; \
@@ -2194,8 +2195,9 @@ template <typename T> std::string rawMemoryToString(const T &object)
template <typename T> class IsStreamInsertable { template <typename T> class IsStreamInsertable {
template <typename Stream, typename U> template <typename Stream, typename U>
static auto test(int) -> decltype( static auto test(int)
std::declval<Stream &>() << std::declval<U>(), std::true_type()); -> decltype(std::declval<Stream &>() << std::declval<U>(),
std::true_type());
template <typename, typename> static auto test(...) -> std::false_type; template <typename, typename> static auto test(...) -> std::false_type;
@@ -4698,8 +4700,10 @@ struct IGeneratorTracker {
namespace Catch { namespace Catch {
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
template <typename Ex> template <typename Ex> [[noreturn]] void throw_exception(Ex const &e)
[[noreturn]] void throw_exception(Ex const &e) { throw e; } {
throw e;
}
#else // ^^ Exceptions are enabled // Exceptions are disabled vv #else // ^^ Exceptions are enabled // Exceptions are disabled vv
[[noreturn]] void throw_exception(std::exception const &e); [[noreturn]] void throw_exception(std::exception const &e);
#endif #endif
@@ -8153,8 +8157,7 @@ EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(
volatile auto ignored = Clock::now(); volatile auto ignored = Clock::now();
(void)ignored; (void)ignored;
} }
}) }).elapsed;
.elapsed;
}; };
time_clock(1); time_clock(1);
int iters = clock_cost_estimation_iterations; int iters = clock_cost_estimation_iterations;
@@ -11868,7 +11871,8 @@ void formatReconstructedExpression(std::ostream &os, std::string const &lhs,
namespace Catch { namespace Catch {
#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && \ #if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && \
!defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER)
[[noreturn]] void throw_exception(std::exception const &e) { [[noreturn]] void throw_exception(std::exception const &e)
{
Catch::cerr() Catch::cerr()
<< "Catch will terminate because it needed to throw an exception.\n" << "Catch will terminate because it needed to throw an exception.\n"
<< "The message was: " << e.what() << '\n'; << "The message was: " << e.what() << '\n';
@@ -11881,8 +11885,10 @@ namespace Catch {
throw_exception(std::logic_error(msg)); throw_exception(std::logic_error(msg));
} }
[[noreturn]] void throw_domain_error( [[noreturn]] void throw_domain_error(std::string const &msg)
std::string const &msg) { throw_exception(std::domain_error(msg)); } {
throw_exception(std::domain_error(msg));
}
[[noreturn]] void throw_runtime_error(std::string const &msg) [[noreturn]] void throw_runtime_error(std::string const &msg)
{ {
@@ -15056,8 +15062,8 @@ int Session::runInternal()
// the return value to 255 prevents false negative when some multiple // the return value to 255 prevents false negative when some multiple
// of 256 tests has failed // of 256 tests has failed
return (std::min)(MaxExitCode, return (std::min)(MaxExitCode,
(std::max)( (std::max)(totals.error,
totals.error, static_cast<int>(totals.assertions.failed))); static_cast<int>(totals.assertions.failed)));
} }
#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) #if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
catch (std::exception &ex) catch (std::exception &ex)

View File

@@ -8,7 +8,8 @@ class A {
} }
namespace ns2_v0_9_0 { namespace ns2_v0_9_0 {
class [[deprecated]] A {}; class [[deprecated]] A {
};
} }
namespace { namespace {

12
tests/t00032/.clang-uml Normal file
View File

@@ -0,0 +1,12 @@
compilation_database_dir: ..
output_directory: puml
diagrams:
t00032_class:
type: class
glob:
- ../../tests/t00032/t00032.cc
using_namespace:
- clanguml::t00032
include:
namespaces:
- clanguml::t00032

38
tests/t00032/t00032.cc Normal file
View File

@@ -0,0 +1,38 @@
#include <memory>
#include <vector>
namespace clanguml {
namespace t00032 {
struct Base {
};
struct TBase {
};
struct A {
void operator()() { }
};
struct B {
void operator()() { }
};
struct C {
void operator()() { }
};
template <typename T, typename L, typename... Ts>
struct Overload : public Base, public T, public Ts... {
using Ts::operator()...;
L counter;
};
template <class... Ts> Overload(Ts...) -> Overload<Ts...>;
struct R {
Overload<TBase, int, A, B, C> overload;
};
} // namespace t00032
} // namespace clanguml

63
tests/t00032/test_case.h Normal file
View File

@@ -0,0 +1,63 @@
/**
* tests/t00032/test_case.cc
*
* Copyright (c) 2021 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("t00032", "[test-case][class]")
{
auto [config, db] = load_config("t00032");
auto diagram = config.diagrams["t00032_class"];
REQUIRE(diagram->name == "t00032_class");
REQUIRE(diagram->include.namespaces.size() == 1);
REQUIRE_THAT(diagram->include.namespaces,
VectorContains(std::string{"clanguml::t00032"}));
REQUIRE(diagram->exclude.namespaces.size() == 0);
REQUIRE(diagram->should_include("clanguml::t00032::A"));
auto model = generate_class_diagram(db, diagram);
REQUIRE(model.name == "t00032_class");
auto puml = generate_class_puml(diagram, model);
AliasMatcher _A(puml);
REQUIRE_THAT(puml, StartsWith("@startuml"));
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
REQUIRE_THAT(puml, IsClass(_A("Base")));
REQUIRE_THAT(puml, IsClass(_A("TBase")));
REQUIRE_THAT(puml, IsClass(_A("A")));
REQUIRE_THAT(puml, IsClass(_A("B")));
REQUIRE_THAT(puml, IsClass(_A("C")));
REQUIRE_THAT(puml, IsClass(_A("R")));
REQUIRE_THAT(puml, IsClassTemplate("Overload", "T,L,Ts..."));
REQUIRE_THAT(puml, IsBaseClass(_A("Base"), _A("Overload<T,L,Ts...>")));
REQUIRE_THAT(
puml, IsBaseClass(_A("TBase"), _A("Overload<TBase,int,A,B,C>")));
REQUIRE_THAT(puml, IsBaseClass(_A("A"), _A("Overload<TBase,int,A,B,C>")));
REQUIRE_THAT(puml, IsBaseClass(_A("B"), _A("Overload<TBase,int,A,B,C>")));
REQUIRE_THAT(puml, IsBaseClass(_A("C"), _A("Overload<TBase,int,A,B,C>")));
save_puml(
"./" + config.output_directory + "/" + diagram->name + ".puml", puml);
}

View File

@@ -135,6 +135,7 @@ using namespace clanguml::test::matchers;
#include "t00029/test_case.h" #include "t00029/test_case.h"
#include "t00030/test_case.h" #include "t00030/test_case.h"
#include "t00031/test_case.h" #include "t00031/test_case.h"
#include "t00032/test_case.h"
// //
// Sequence diagram tests // Sequence diagram tests

View File

@@ -90,6 +90,9 @@ test_cases:
- name: t00031 - name: t00031
title: PlantUML style decorator test case title: PlantUML style decorator test case
description: description:
- name: t00032
title: Class template with template base classes test case
description:
Sequence diagrams: Sequence diagrams:
- name: t20001 - name: t20001
title: Basic sequence diagram title: Basic sequence diagram