Parse cpp_alias_template
This commit is contained in:
parent
cd4a25e959
commit
d6f0997fb6
14 changed files with 550 additions and 82 deletions
|
|
@ -65,6 +65,7 @@ namespace cppast
|
|||
bool is_type(cpp_entity_kind kind) noexcept;
|
||||
|
||||
/// \returns Whether or not a given entity kind is a C++ template.
|
||||
/// \notes A template template parameter is not considered a template for this function.
|
||||
bool is_template(cpp_entity_kind kind) noexcept;
|
||||
} // namespace cppast
|
||||
|
||||
|
|
|
|||
|
|
@ -164,8 +164,8 @@ namespace cppast
|
|||
|
||||
class cpp_template;
|
||||
|
||||
/// A reference to a [cppast::cpp_template]().
|
||||
using cpp_template_ref = basic_cpp_entity_ref<cpp_template, detail::cpp_template_ref_predicate>;
|
||||
/// A reference to a [cppast::cpp_template]() or a [cppast::cpp_template_template_parameter]().
|
||||
using cpp_template_ref = basic_cpp_entity_ref<cpp_entity, detail::cpp_template_ref_predicate>;
|
||||
|
||||
/// A [cppast::cpp_entity]() modelling a C++ template template parameter.
|
||||
class cpp_template_template_parameter final
|
||||
|
|
@ -243,12 +243,67 @@ namespace cppast
|
|||
|
||||
/// An argument for a [cppast::cpp_template_parameter]().
|
||||
///
|
||||
/// It is a [ts::variant]() of [cppast::cpp_type]() (for [cppast::cpp_template_type_parameter]()),
|
||||
/// It is based on a [ts::variant]() of [cppast::cpp_type]() (for [cppast::cpp_template_type_parameter]()),
|
||||
/// [cppast::cpp_expression]() (for [cppast::cpp_non_type_template_parameter]()) and [cppast::cpp_template_ref]()
|
||||
/// (for [cppast::cpp_template_template_parameter]().
|
||||
using cpp_template_argument =
|
||||
class cpp_template_argument
|
||||
{
|
||||
public:
|
||||
/// \effects Initializes it passing a type as argument.
|
||||
/// This corresponds to a [cppast::cpp_template_type_parameter]().
|
||||
/// \notes This constructor only participates in overload resolution if `T` is dervied from [cppast::cpp_type]().
|
||||
/// \param 1
|
||||
/// \exclude
|
||||
template <typename T,
|
||||
typename std::enable_if<std::is_base_of<cpp_type, T>::value, int>::type = 0>
|
||||
cpp_template_argument(std::unique_ptr<T> type)
|
||||
: arg_(std::unique_ptr<cpp_type>(std::move(type)))
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Initializes it passing an expression as argument.
|
||||
/// This corresponds to a [cppast::cpp_non_type_template_parameter]().
|
||||
/// \notes This constructor only participates in overload resolution if `T` is dervied from [cppast::cpp_expression]().
|
||||
/// \param 1
|
||||
/// \exclude
|
||||
template <
|
||||
typename T,
|
||||
typename = typename std::enable_if<std::is_base_of<cpp_expression, T>::value>::type>
|
||||
cpp_template_argument(std::unique_ptr<T> expr)
|
||||
: arg_(std::unique_ptr<cpp_expression>(std::move(expr)))
|
||||
{
|
||||
}
|
||||
|
||||
/// \effects Initializes it passing a template as argument.
|
||||
/// This corresponds to a [cppast::cpp_template_template_parameter]().
|
||||
cpp_template_argument(cpp_template_ref templ) : arg_(std::move(templ))
|
||||
{
|
||||
}
|
||||
|
||||
type_safe::optional_ref<const cpp_type> type() const noexcept
|
||||
{
|
||||
return arg_.optional_value(type_safe::variant_type<std::unique_ptr<cpp_type>>{})
|
||||
.map([](const std::unique_ptr<cpp_type>& type) { return type_safe::ref(*type); });
|
||||
}
|
||||
|
||||
type_safe::optional_ref<const cpp_expression> expression() const noexcept
|
||||
{
|
||||
return arg_.optional_value(type_safe::variant_type<std::unique_ptr<cpp_expression>>{})
|
||||
.map([](const std::unique_ptr<cpp_expression>& expr) {
|
||||
return type_safe::ref(*expr);
|
||||
});
|
||||
}
|
||||
|
||||
type_safe::optional_ref<const cpp_template_ref> template_ref() const noexcept
|
||||
{
|
||||
return arg_.optional_value(type_safe::variant_type<cpp_template_ref>{});
|
||||
}
|
||||
|
||||
private:
|
||||
type_safe::variant<std::unique_ptr<cpp_type>, std::unique_ptr<cpp_expression>,
|
||||
cpp_template_ref>;
|
||||
cpp_template_ref>
|
||||
arg_;
|
||||
};
|
||||
} // namespace cppast
|
||||
|
||||
#endif // CPPAST_CPP_TEMPLATE_PARAMETER_HPP_INCLUDED
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ cpp_entity_kind cpp_non_type_template_parameter::do_get_entity_kind() const noex
|
|||
|
||||
bool detail::cpp_template_ref_predicate::operator()(const cpp_entity& e)
|
||||
{
|
||||
return is_template(e.kind());
|
||||
return is_template(e.kind()) || e.kind() == cpp_entity_kind::template_template_parameter_t;
|
||||
}
|
||||
|
||||
cpp_entity_kind cpp_template_template_parameter::do_get_entity_kind() const noexcept
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ namespace
|
|||
std::unique_ptr<cpp_type> type;
|
||||
if (detail::skip_if(stream, ":"))
|
||||
// parse type, explictly given one
|
||||
type = detail::parse_type(context, clang_getEnumDeclIntegerType(cur));
|
||||
type = detail::parse_type(context, cur, clang_getEnumDeclIntegerType(cur));
|
||||
|
||||
return cpp_enum::builder(name.c_str(), scoped, std::move(type));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ std::unique_ptr<cpp_expression> detail::parse_expression(const detail::parse_con
|
|||
detail::tokenizer tokenizer(context.tu, context.file, cur);
|
||||
detail::token_stream stream(tokenizer, cur);
|
||||
|
||||
auto type = parse_type(context, clang_getCursorType(cur));
|
||||
auto type = parse_type(context, cur, clang_getCursorType(cur));
|
||||
auto expr = get_expression_str(stream, stream.end());
|
||||
if (kind == CXCursor_CallExpr && (expr.empty() || expr.back() != ')'))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace
|
|||
const CXCursor& cur)
|
||||
{
|
||||
auto name = detail::get_cursor_name(cur);
|
||||
auto type = detail::parse_type(context, clang_getCursorType(cur));
|
||||
auto type = detail::parse_type(context, cur, clang_getCursorType(cur));
|
||||
|
||||
std::unique_ptr<cpp_expression> default_value;
|
||||
detail::visit_children(cur, [&](const CXCursor& child) {
|
||||
|
|
@ -278,7 +278,8 @@ namespace
|
|||
auto name = detail::get_cursor_name(cur);
|
||||
|
||||
cpp_function::builder builder(name.c_str(),
|
||||
detail::parse_type(context, clang_getCursorResultType(cur)));
|
||||
detail::parse_type(context, cur,
|
||||
clang_getCursorResultType(cur)));
|
||||
context.comments.match(builder.get(), cur);
|
||||
add_parameters(context, builder, cur);
|
||||
if (clang_Cursor_isVariadic(cur))
|
||||
|
|
@ -413,7 +414,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_member_function(const detail::pars
|
|||
auto name = detail::get_cursor_name(cur);
|
||||
|
||||
cpp_member_function::builder builder(name.c_str(),
|
||||
detail::parse_type(context,
|
||||
detail::parse_type(context, cur,
|
||||
clang_getCursorResultType(cur)));
|
||||
context.comments.match(builder.get(), cur);
|
||||
add_parameters(context, builder, cur);
|
||||
|
|
@ -435,7 +436,8 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_conversion_op(const detail::parse_
|
|||
const CXCursor& cur)
|
||||
{
|
||||
DEBUG_ASSERT(clang_getCursorKind(cur) == CXCursor_ConversionFunction, detail::assert_handler{});
|
||||
cpp_conversion_op::builder builder(detail::parse_type(context, clang_getCursorResultType(cur)));
|
||||
cpp_conversion_op::builder builder(
|
||||
detail::parse_type(context, cur, clang_getCursorResultType(cur)));
|
||||
context.comments.match(builder.get(), cur);
|
||||
|
||||
detail::tokenizer tokenizer(context.tu, context.file, cur);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ void detail::comment_context::match(cpp_entity& e, unsigned line) const
|
|||
}
|
||||
|
||||
std::unique_ptr<cpp_entity> detail::parse_entity(const detail::parse_context& context,
|
||||
const CXCursor& cur, bool as_template) try
|
||||
const CXCursor& cur,
|
||||
const CXCursor& template_cur) try
|
||||
{
|
||||
auto kind = clang_getCursorKind(cur);
|
||||
switch (kind)
|
||||
|
|
@ -90,7 +91,7 @@ std::unique_ptr<cpp_entity> detail::parse_entity(const detail::parse_context& co
|
|||
|
||||
case CXCursor_TypeAliasDecl:
|
||||
case CXCursor_TypedefDecl:
|
||||
return parse_cpp_type_alias(context, cur, as_template);
|
||||
return parse_cpp_type_alias(context, cur, template_cur);
|
||||
case CXCursor_EnumDecl:
|
||||
return parse_cpp_enum(context, cur);
|
||||
case CXCursor_ClassDecl:
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ namespace cppast
|
|||
comment_context comments;
|
||||
};
|
||||
|
||||
std::unique_ptr<cpp_type> parse_type(const parse_context& context, const CXType& type);
|
||||
std::unique_ptr<cpp_type> parse_type(const parse_context& context, const CXCursor& cur,
|
||||
const CXType& type);
|
||||
|
||||
// parse the type starting at the current token stream
|
||||
// and ends at the given iterator
|
||||
|
|
@ -100,7 +101,8 @@ namespace cppast
|
|||
const CXCursor& cur);
|
||||
|
||||
std::unique_ptr<cpp_entity> parse_cpp_type_alias(const parse_context& context,
|
||||
const CXCursor& cur, bool as_template);
|
||||
const CXCursor& cur,
|
||||
const CXCursor& template_cur);
|
||||
std::unique_ptr<cpp_entity> parse_cpp_enum(const parse_context& context,
|
||||
const CXCursor& cur);
|
||||
std::unique_ptr<cpp_entity> parse_cpp_class(const parse_context& context,
|
||||
|
|
@ -127,8 +129,9 @@ namespace cppast
|
|||
const CXCursor& cur);
|
||||
|
||||
// as_template: true, iff currently parsing a template
|
||||
std::unique_ptr<cpp_entity> parse_entity(const parse_context& context, const CXCursor& cur,
|
||||
bool as_template = false);
|
||||
std::unique_ptr<cpp_entity> parse_entity(
|
||||
const parse_context& context, const CXCursor& cur,
|
||||
const CXCursor& template_cur = clang_getNullCursor());
|
||||
}
|
||||
} // namespace cppast::detail
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ using namespace cppast;
|
|||
namespace
|
||||
{
|
||||
template <typename TemplateT, typename EntityT>
|
||||
typename TemplateT::builder get_builder(const detail::parse_context& context,
|
||||
const CXCursor& cur)
|
||||
type_safe::optional<typename TemplateT::builder> get_builder(
|
||||
const detail::parse_context& context, const CXCursor& cur)
|
||||
{
|
||||
// we need the actual entity first, then the parameters
|
||||
// so two visit calls are required
|
||||
|
|
@ -31,7 +31,9 @@ namespace
|
|||
DEBUG_ASSERT(!clang_Cursor_isNull(result), detail::parse_error_handler{}, cur,
|
||||
"missing child of template");
|
||||
|
||||
auto entity = detail::parse_entity(context, result, true);
|
||||
auto entity = detail::parse_entity(context, result, cur);
|
||||
if (!entity)
|
||||
return type_safe::nullopt;
|
||||
DEBUG_ASSERT(entity->kind() == EntityT::kind(), detail::parse_error_handler{}, cur,
|
||||
"wrong child of template");
|
||||
return typename TemplateT::builder(
|
||||
|
|
@ -111,7 +113,7 @@ namespace
|
|||
|
||||
return cpp_non_type_template_parameter::build(*context.idx, detail::get_entity_id(cur),
|
||||
name.c_str(),
|
||||
detail::parse_type(context, type),
|
||||
detail::parse_type(context, cur, type),
|
||||
is_variadic, std::move(def));
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +207,9 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_alias_template(const detail::parse
|
|||
DEBUG_ASSERT(clang_getCursorKind(cur) == CXCursor_TypeAliasTemplateDecl,
|
||||
detail::assert_handler{});
|
||||
auto builder = get_builder<cpp_alias_template, cpp_type_alias>(context, cur);
|
||||
context.comments.match(builder.get(), cur);
|
||||
parse_parameters(builder, context, cur);
|
||||
return builder.finish(*context.idx, detail::get_entity_id(cur));
|
||||
if (!builder)
|
||||
return nullptr;
|
||||
context.comments.match(builder.value().get(), cur);
|
||||
parse_parameters(builder.value(), context, cur);
|
||||
return builder.value().finish(*context.idx, detail::get_entity_id(cur));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,9 +272,9 @@ detail::token_iterator detail::find_closing_bracket(detail::token_stream stream)
|
|||
else if (paren_count == 0 && template_bracket && cur == ">>")
|
||||
// maximal munch
|
||||
bracket_count -= 2u;
|
||||
else if (cur == "(")
|
||||
else if (cur == "(" || cur == "{" || cur == "[")
|
||||
++paren_count;
|
||||
else if (cur == ")")
|
||||
else if (cur == ")" || cur == "}" || cur == "]")
|
||||
--paren_count;
|
||||
}
|
||||
stream.bump_back();
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@
|
|||
#include <cppast/cpp_array_type.hpp>
|
||||
#include <cppast/cpp_expression.hpp>
|
||||
#include <cppast/cpp_function_type.hpp>
|
||||
#include <cppast/cpp_template.hpp>
|
||||
#include <cppast/cpp_template_parameter.hpp>
|
||||
#include <cppast/cpp_type.hpp>
|
||||
#include <cppast/cpp_type_alias.hpp>
|
||||
#include <clang-c/Index.h>
|
||||
|
||||
#include "libclang_visitor.hpp"
|
||||
|
||||
using namespace cppast;
|
||||
|
||||
|
|
@ -171,6 +174,8 @@ namespace
|
|||
remove_prefix(spelling, "union");
|
||||
|
||||
auto entity = b(std::move(spelling));
|
||||
if (!entity)
|
||||
return nullptr;
|
||||
return make_cv_qualified(std::move(entity), cv);
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +189,7 @@ namespace
|
|||
}
|
||||
|
||||
std::unique_ptr<cpp_type> parse_type_impl(const detail::parse_context& context,
|
||||
const CXType& type);
|
||||
const CXCursor& cur, const CXType& type);
|
||||
|
||||
std::unique_ptr<cpp_expression> parse_array_size(const CXType& type)
|
||||
{
|
||||
|
|
@ -218,7 +223,7 @@ namespace
|
|||
}
|
||||
|
||||
std::unique_ptr<cpp_type> try_parse_array_type(const detail::parse_context& context,
|
||||
const CXType& type)
|
||||
const CXCursor& cur, const CXType& type)
|
||||
{
|
||||
auto canonical = clang_getCanonicalType(type);
|
||||
auto value_type = clang_getArrayElementType(type);
|
||||
|
|
@ -236,18 +241,18 @@ namespace
|
|||
}
|
||||
|
||||
auto size = parse_array_size(canonical); // type may not work, see above
|
||||
return cpp_array_type::build(parse_type_impl(context, value_type), std::move(size));
|
||||
return cpp_array_type::build(parse_type_impl(context, cur, value_type), std::move(size));
|
||||
}
|
||||
|
||||
template <class Builder>
|
||||
std::unique_ptr<cpp_type> add_parameters(Builder& builder, const detail::parse_context& context,
|
||||
const CXType& type)
|
||||
const CXCursor& cur, const CXType& type)
|
||||
{
|
||||
auto no_args = clang_getNumArgTypes(type);
|
||||
DEBUG_ASSERT(no_args >= 0, detail::parse_error_handler{}, type,
|
||||
"invalid number of arguments");
|
||||
for (auto i = 0u; i != unsigned(no_args); ++i)
|
||||
builder.add_parameter(parse_type_impl(context, clang_getArgType(type, i)));
|
||||
builder.add_parameter(parse_type_impl(context, cur, clang_getArgType(type, i)));
|
||||
|
||||
if (clang_isFunctionTypeVariadic(type))
|
||||
builder.is_variadic();
|
||||
|
|
@ -256,15 +261,15 @@ namespace
|
|||
}
|
||||
|
||||
std::unique_ptr<cpp_type> try_parse_function_type(const detail::parse_context& context,
|
||||
const CXType& type)
|
||||
const CXCursor& cur, const CXType& type)
|
||||
{
|
||||
auto result = clang_getResultType(type);
|
||||
if (result.kind == CXType_Invalid)
|
||||
// not a function type
|
||||
return nullptr;
|
||||
|
||||
cpp_function_type::builder builder(parse_type_impl(context, result));
|
||||
return add_parameters(builder, context, type);
|
||||
cpp_function_type::builder builder(parse_type_impl(context, cur, result));
|
||||
return add_parameters(builder, context, cur, type);
|
||||
}
|
||||
|
||||
cpp_reference member_function_ref_qualifier(std::string& spelling)
|
||||
|
|
@ -284,7 +289,7 @@ namespace
|
|||
}
|
||||
|
||||
std::unique_ptr<cpp_type> parse_member_pointee_type(const detail::parse_context& context,
|
||||
const CXType& type)
|
||||
const CXCursor& cur, const CXType& type)
|
||||
{
|
||||
auto spelling = get_type_spelling(type);
|
||||
auto ref = member_function_ref_qualifier(spelling);
|
||||
|
|
@ -292,7 +297,7 @@ namespace
|
|||
|
||||
auto class_t = clang_Type_getClassType(type);
|
||||
auto class_entity =
|
||||
make_ref_qualified(make_cv_qualified(parse_type_impl(context, class_t), cv), ref);
|
||||
make_ref_qualified(make_cv_qualified(parse_type_impl(context, cur, class_t), cv), ref);
|
||||
|
||||
auto pointee = clang_getPointeeType(type); // for everything except the class type
|
||||
auto result = clang_getResultType(pointee);
|
||||
|
|
@ -300,18 +305,227 @@ namespace
|
|||
{
|
||||
// member data pointer
|
||||
return cpp_member_object_type::build(std::move(class_entity),
|
||||
parse_type_impl(context, pointee));
|
||||
parse_type_impl(context, cur, pointee));
|
||||
}
|
||||
else
|
||||
{
|
||||
cpp_member_function_type::builder builder(std::move(class_entity),
|
||||
parse_type_impl(context, result));
|
||||
return add_parameters(builder, context, pointee);
|
||||
parse_type_impl(context, cur, result));
|
||||
return add_parameters(builder, context, cur, pointee);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_direct_templated(const CXCursor& cur)
|
||||
{
|
||||
// TODO: variable template
|
||||
auto kind = clang_getCursorKind(cur);
|
||||
return kind == CXCursor_TypeAliasTemplateDecl || kind == CXCursor_ClassTemplate
|
||||
|| kind == CXCursor_ClassTemplatePartialSpecialization
|
||||
|| kind == CXCursor_FunctionTemplate;
|
||||
}
|
||||
|
||||
bool check_parent(const CXCursor& parent)
|
||||
{
|
||||
if (clang_Cursor_isNull(parent))
|
||||
return false;
|
||||
auto kind = clang_getCursorKind(parent);
|
||||
return kind != CXCursor_Namespace && kind != CXCursor_TranslationUnit;
|
||||
}
|
||||
|
||||
CXCursor get_template(CXCursor cur)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (is_direct_templated(cur))
|
||||
return cur;
|
||||
cur = clang_getCursorSemanticParent(cur);
|
||||
} while (check_parent(cur));
|
||||
return clang_getNullCursor();
|
||||
}
|
||||
|
||||
std::unique_ptr<cpp_type> try_parse_template_parameter_type(
|
||||
const detail::parse_context& context, const CXCursor& cur, const CXType& type)
|
||||
{
|
||||
// see if we have a parent template
|
||||
auto templ = get_template(cur);
|
||||
if (clang_Cursor_isNull(templ))
|
||||
// not a template
|
||||
return nullptr;
|
||||
|
||||
// doesn't respect cv qualifiers properly
|
||||
auto result =
|
||||
make_leave_type(type, [&](std::string&& type_spelling) -> std::unique_ptr<cpp_type> {
|
||||
// look at the template parameters,
|
||||
// see if we find a matching one
|
||||
auto param = clang_getNullCursor();
|
||||
detail::visit_children(templ, [&](const CXCursor& child) {
|
||||
if (clang_getCursorKind(child) == CXCursor_TemplateTypeParameter
|
||||
&& get_type_spelling(clang_getCursorType(child)) == type_spelling)
|
||||
{
|
||||
// found one
|
||||
DEBUG_ASSERT(clang_Cursor_isNull(param), detail::assert_handler{});
|
||||
param = child;
|
||||
}
|
||||
});
|
||||
|
||||
if (clang_Cursor_isNull(param))
|
||||
return nullptr;
|
||||
else
|
||||
// found matching parameter
|
||||
return cpp_template_parameter_type::build(
|
||||
cpp_template_type_parameter_ref(detail::get_entity_id(param),
|
||||
std::move(type_spelling)));
|
||||
});
|
||||
|
||||
if (result)
|
||||
return result;
|
||||
else
|
||||
// try again in a possible parent template
|
||||
return try_parse_template_parameter_type(context, clang_getCursorSemanticParent(templ),
|
||||
type);
|
||||
}
|
||||
|
||||
const char* find_closing_bracket(const char* ptr)
|
||||
{
|
||||
for (auto paren_count = 0; *ptr; ++ptr)
|
||||
{
|
||||
if (*ptr == '(' || *ptr == '[' || *ptr == '{')
|
||||
++paren_count;
|
||||
else if (*ptr == ')' || *ptr == ']' || *ptr == '}')
|
||||
--paren_count;
|
||||
else if (paren_count == 0 && *ptr == '>' && !std::isalnum(*ptr) && *ptr != '_')
|
||||
// heuristic: this could be in fact a closing bracket
|
||||
return ptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string parse_argument(const CXCursor& cur, const char*& ptr, bool is_expression)
|
||||
{
|
||||
std::string arg;
|
||||
|
||||
auto paren_count = 0; // non angle brackets
|
||||
auto bracket_count = 0; // angle brackets
|
||||
while (*ptr && ((paren_count + bracket_count) != 0 || *ptr != ','))
|
||||
{
|
||||
if (*ptr == '(' || *ptr == '[' || *ptr == '{')
|
||||
++paren_count;
|
||||
else if (*ptr == ')' || *ptr == ']' || *ptr == '}')
|
||||
--paren_count;
|
||||
// angle brackets are tricky
|
||||
// as they can be both brackets and operators
|
||||
// we only need to take care of those that aren't nested inside other brackets, luckily
|
||||
else if (paren_count == 0)
|
||||
{
|
||||
if (is_expression && *ptr == '<')
|
||||
{
|
||||
// treat as brackets and see if it got a closing one
|
||||
auto closing = find_closing_bracket(ptr);
|
||||
if (closing)
|
||||
{
|
||||
// assume this is a closing bracket
|
||||
while (ptr != closing)
|
||||
arg += *ptr++;
|
||||
}
|
||||
}
|
||||
// not an expression
|
||||
// all top-level angle brackets are actually brackets
|
||||
else if (*ptr == '<')
|
||||
++bracket_count;
|
||||
else if (*ptr == '>')
|
||||
--bracket_count;
|
||||
}
|
||||
|
||||
arg += *ptr++;
|
||||
}
|
||||
|
||||
DEBUG_ASSERT(*ptr == ',', detail::parse_error_handler{}, cur,
|
||||
"unable to parse template argument");
|
||||
++ptr;
|
||||
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
while (!arg.empty() && arg.back() == ' ')
|
||||
arg.pop_back();
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
CXCursor get_instantiation_template(const CXCursor& cur, const CXType& type,
|
||||
const std::string& templ_name)
|
||||
{
|
||||
// look if the type has a declaration that is a template
|
||||
auto decl = clang_getTypeDeclaration(type);
|
||||
if (is_direct_templated(decl))
|
||||
return decl;
|
||||
|
||||
// look if the templ_name matches a template template parameter
|
||||
auto param = clang_getNullCursor();
|
||||
detail::visit_children(cur, [&](const CXCursor& child) {
|
||||
if (clang_getCursorKind(child) == CXCursor_TemplateTemplateParameter
|
||||
&& detail::get_cursor_name(child) == templ_name.c_str())
|
||||
{
|
||||
DEBUG_ASSERT(clang_Cursor_isNull(param), detail::parse_error_handler{}, cur,
|
||||
"multiple template template parameters with the same name?!");
|
||||
param = child;
|
||||
}
|
||||
});
|
||||
return param;
|
||||
}
|
||||
|
||||
std::unique_ptr<cpp_type> try_parse_instantiation_type(const detail::parse_context& context,
|
||||
const CXCursor& cur, const CXType& type)
|
||||
{
|
||||
return make_leave_type(type, [&](std::string&& spelling) -> std::unique_ptr<cpp_type> {
|
||||
spelling.back() = ','; // to easily terminate the last argument
|
||||
auto ptr = spelling.c_str();
|
||||
std::string templ_name;
|
||||
for (; *ptr && *ptr != '<'; ++ptr)
|
||||
templ_name += *ptr;
|
||||
++ptr;
|
||||
|
||||
auto templ = get_instantiation_template(cur, type, templ_name);
|
||||
if (clang_Cursor_isNull(templ))
|
||||
return nullptr;
|
||||
|
||||
cpp_template_instantiation_type::builder builder(
|
||||
cpp_template_ref(detail::get_entity_id(templ), std::move(templ_name)));
|
||||
|
||||
// parse arguments
|
||||
// visit children of declaration to get the kind of argument expected
|
||||
// then parse the string
|
||||
detail::visit_children(templ, [&](const CXCursor& child) {
|
||||
if (!*ptr)
|
||||
return;
|
||||
|
||||
auto kind = clang_getCursorKind(child);
|
||||
if (kind == CXCursor_TemplateTypeParameter)
|
||||
{
|
||||
auto arg = parse_argument(cur, ptr, false);
|
||||
builder.add_argument(
|
||||
std::unique_ptr<cpp_type>(cpp_unexposed_type::build(std::move(arg))));
|
||||
}
|
||||
else if (kind == CXCursor_NonTypeTemplateParameter)
|
||||
{
|
||||
auto arg = parse_argument(cur, ptr, true);
|
||||
auto arg_type = detail::parse_type(context, child, clang_getCursorType(child));
|
||||
builder.add_argument(std::unique_ptr<cpp_expression>(
|
||||
cpp_unexposed_expression::build(std::move(arg_type), std::move(arg))));
|
||||
}
|
||||
else if (kind == CXCursor_TemplateTemplateParameter)
|
||||
{
|
||||
auto arg = parse_argument(cur, ptr, false);
|
||||
builder.add_argument(cpp_template_ref(cpp_entity_id(""), std::move(arg)));
|
||||
}
|
||||
});
|
||||
|
||||
return builder.finish();
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<cpp_type> parse_type_impl(const detail::parse_context& context,
|
||||
const CXType& type)
|
||||
const CXCursor& cur, const CXType& type)
|
||||
{
|
||||
switch (type.kind)
|
||||
{
|
||||
|
|
@ -335,13 +549,19 @@ namespace
|
|||
}
|
||||
// fallthrough
|
||||
case CXType_Unexposed:
|
||||
if (auto ftype = try_parse_function_type(context, type))
|
||||
if (auto ftype = try_parse_function_type(context, cur, type))
|
||||
// guess what: after you've called clang_getPointeeType() on a function pointer
|
||||
// you'll get an unexposed type
|
||||
return ftype;
|
||||
else if (auto atype = try_parse_array_type(context, type))
|
||||
else if (auto atype = try_parse_array_type(context, cur, type))
|
||||
// same deal here
|
||||
return atype;
|
||||
else if (auto itype = try_parse_instantiation_type(context, cur, type))
|
||||
// instantiation unexposed
|
||||
return itype;
|
||||
else if (auto ptype = try_parse_template_parameter_type(context, cur, type))
|
||||
// template parameter type is unexposed
|
||||
return ptype;
|
||||
return cpp_unexposed_type::build(get_type_spelling(type).c_str());
|
||||
|
||||
case CXType_Void:
|
||||
|
|
@ -386,7 +606,7 @@ namespace
|
|||
|
||||
case CXType_Pointer:
|
||||
{
|
||||
auto pointee = parse_type_impl(context, clang_getPointeeType(type));
|
||||
auto pointee = parse_type_impl(context, cur, clang_getPointeeType(type));
|
||||
auto pointer = cpp_pointer_type::build(std::move(pointee));
|
||||
|
||||
auto spelling = get_type_spelling(type);
|
||||
|
|
@ -396,7 +616,7 @@ namespace
|
|||
case CXType_LValueReference:
|
||||
case CXType_RValueReference:
|
||||
{
|
||||
auto referee = parse_type_impl(context, clang_getPointeeType(type));
|
||||
auto referee = parse_type_impl(context, cur, clang_getPointeeType(type));
|
||||
return cpp_reference_type::build(std::move(referee), get_reference_kind(type));
|
||||
}
|
||||
|
||||
|
|
@ -404,14 +624,14 @@ namespace
|
|||
case CXType_VariableArray:
|
||||
case CXType_DependentSizedArray:
|
||||
case CXType_ConstantArray:
|
||||
return try_parse_array_type(context, type);
|
||||
return try_parse_array_type(context, cur, type);
|
||||
|
||||
case CXType_FunctionNoProto:
|
||||
case CXType_FunctionProto:
|
||||
return try_parse_function_type(context, type);
|
||||
return try_parse_function_type(context, cur, type);
|
||||
|
||||
case CXType_MemberPointer:
|
||||
return cpp_pointer_type::build(parse_member_pointee_type(context, type));
|
||||
return cpp_pointer_type::build(parse_member_pointee_type(context, cur, type));
|
||||
|
||||
// TODO: everything template related
|
||||
case CXType_Dependent:
|
||||
|
|
@ -428,9 +648,9 @@ namespace
|
|||
}
|
||||
|
||||
std::unique_ptr<cpp_type> detail::parse_type(const detail::parse_context& context,
|
||||
const CXType& type)
|
||||
const CXCursor& cur, const CXType& type)
|
||||
{
|
||||
auto result = parse_type_impl(context, type);
|
||||
auto result = parse_type_impl(context, cur, type);
|
||||
DEBUG_ASSERT(result && is_valid(*result), detail::parse_error_handler{}, type, "invalid type");
|
||||
return std::move(result);
|
||||
}
|
||||
|
|
@ -465,15 +685,17 @@ std::unique_ptr<cpp_type> detail::parse_raw_type(const detail::parse_context&,
|
|||
}
|
||||
|
||||
std::unique_ptr<cpp_entity> detail::parse_cpp_type_alias(const detail::parse_context& context,
|
||||
const CXCursor& cur, bool as_template)
|
||||
const CXCursor& cur,
|
||||
const CXCursor& template_cur)
|
||||
{
|
||||
DEBUG_ASSERT(cur.kind == CXCursor_TypeAliasDecl || cur.kind == CXCursor_TypedefDecl,
|
||||
detail::assert_handler{});
|
||||
|
||||
auto name = detail::get_cursor_name(cur);
|
||||
auto type = parse_type(context, clang_getTypedefDeclUnderlyingType(cur));
|
||||
auto type = parse_type(context, clang_Cursor_isNull(template_cur) ? cur : template_cur,
|
||||
clang_getTypedefDeclUnderlyingType(cur));
|
||||
|
||||
if (as_template)
|
||||
if (!clang_Cursor_isNull(template_cur))
|
||||
return cpp_type_alias::build(name.c_str(), std::move(type));
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_variable(const detail::parse_conte
|
|||
DEBUG_ASSERT(cur.kind == CXCursor_VarDecl, detail::assert_handler{});
|
||||
|
||||
auto name = get_cursor_name(cur);
|
||||
auto type = parse_type(context, clang_getCursorType(cur));
|
||||
auto type = parse_type(context, cur, clang_getCursorType(cur));
|
||||
auto storage_class = get_storage_class(cur);
|
||||
auto is_constexpr = false;
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_member_variable(const detail::pars
|
|||
DEBUG_ASSERT(cur.kind == CXCursor_FieldDecl, detail::assert_handler{});
|
||||
|
||||
auto name = get_cursor_name(cur);
|
||||
auto type = parse_type(context, clang_getCursorType(cur));
|
||||
auto type = parse_type(context, cur, clang_getCursorType(cur));
|
||||
auto is_mutable = clang_CXXField_isMutable(cur) != 0u;
|
||||
|
||||
std::unique_ptr<cpp_member_variable_base> result;
|
||||
|
|
@ -94,8 +94,9 @@ std::unique_ptr<cpp_entity> detail::parse_cpp_member_variable(const detail::pars
|
|||
// look for the equal sign, default value starts there
|
||||
while (!stream.done() && !skip_if(stream, "="))
|
||||
stream.bump();
|
||||
auto default_value = parse_raw_expression(context, stream, stream.end(),
|
||||
parse_type(context, clang_getCursorType(cur)));
|
||||
auto default_value =
|
||||
parse_raw_expression(context, stream, stream.end(),
|
||||
parse_type(context, cur, clang_getCursorType(cur)));
|
||||
|
||||
result = cpp_member_variable::build(*context.idx, get_entity_id(cur), name.c_str(),
|
||||
std::move(type), std::move(default_value), is_mutable);
|
||||
|
|
|
|||
136
test/cpp_alias_template.cpp
Normal file
136
test/cpp_alias_template.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
|
||||
// This file is subject to the license terms in the LICENSE file
|
||||
// found in the top-level directory of this distribution.
|
||||
|
||||
#include <cppast/cpp_alias_template.hpp>
|
||||
|
||||
#include "test_parser.hpp"
|
||||
|
||||
using namespace cppast;
|
||||
|
||||
TEST_CASE("cpp_alias_template")
|
||||
{
|
||||
// no need to check advanced types here nor template parameters
|
||||
auto code = R"(
|
||||
template <typename T>
|
||||
using a = int;
|
||||
|
||||
template <int I, typename T = void>
|
||||
using b = T;
|
||||
|
||||
template <typename T>
|
||||
using c = const T*;
|
||||
|
||||
template <typename T>
|
||||
using d = a<void>;
|
||||
|
||||
template <int I>
|
||||
using e = const b<I>;
|
||||
|
||||
template <int I>
|
||||
using f = b<I < a<int>{(0,1)}, int>;
|
||||
|
||||
template <typename T, template <typename> class Templ>
|
||||
using g = Templ<T>;
|
||||
|
||||
template <typename T>
|
||||
using h = g<T, a>;
|
||||
)";
|
||||
|
||||
auto check_parameters =
|
||||
[](const cpp_alias_template& alias,
|
||||
std::initializer_list<std::pair<cpp_entity_kind, const char*>> params) {
|
||||
// no need to check more
|
||||
auto cur = params.begin();
|
||||
for (auto& param : alias.parameters())
|
||||
{
|
||||
REQUIRE(cur != params.end());
|
||||
REQUIRE(param.kind() == cur->first);
|
||||
REQUIRE(param.name() == cur->second);
|
||||
++cur;
|
||||
}
|
||||
REQUIRE(cur == params.end());
|
||||
};
|
||||
|
||||
cpp_entity_index idx;
|
||||
auto file = parse(idx, "cpp_alias_template.cpp", code);
|
||||
auto count = test_visit<cpp_alias_template>(*file, [&](const cpp_alias_template& alias) {
|
||||
if (alias.name() == "a")
|
||||
{
|
||||
check_parameters(alias, {{cpp_entity_kind::template_type_parameter_t, "T"}});
|
||||
REQUIRE(equal_types(idx, alias.type_alias().underlying_type(),
|
||||
*cpp_builtin_type::build("int")));
|
||||
}
|
||||
else if (alias.name() == "b")
|
||||
{
|
||||
check_parameters(alias, {{cpp_entity_kind::non_type_template_parameter_t, "I"},
|
||||
{cpp_entity_kind::template_type_parameter_t, "T"}});
|
||||
REQUIRE(equal_types(idx, alias.type_alias().underlying_type(),
|
||||
*cpp_template_parameter_type::build(
|
||||
cpp_template_type_parameter_ref(cpp_entity_id(""), "T"))));
|
||||
}
|
||||
else if (alias.name() == "c")
|
||||
{
|
||||
check_parameters(alias, {{cpp_entity_kind::template_type_parameter_t, "T"}});
|
||||
auto param = cpp_template_parameter_type::build(
|
||||
cpp_template_type_parameter_ref(cpp_entity_id(""), "T"));
|
||||
REQUIRE(equal_types(idx, alias.type_alias().underlying_type(),
|
||||
*cpp_pointer_type::build(
|
||||
cpp_cv_qualified_type::build(std::move(param), cpp_cv_const))));
|
||||
}
|
||||
else if (alias.name() == "d")
|
||||
{
|
||||
check_parameters(alias, {{cpp_entity_kind::template_type_parameter_t, "T"}});
|
||||
|
||||
cpp_template_instantiation_type::builder builder(
|
||||
cpp_template_ref(cpp_entity_id(""), "a"));
|
||||
builder.add_argument(cpp_unexposed_type::build("void"));
|
||||
REQUIRE(equal_types(idx, alias.type_alias().underlying_type(), *builder.finish()));
|
||||
}
|
||||
else if (alias.name() == "e")
|
||||
{
|
||||
check_parameters(alias, {{cpp_entity_kind::non_type_template_parameter_t, "I"}});
|
||||
|
||||
cpp_template_instantiation_type::builder builder(
|
||||
cpp_template_ref(cpp_entity_id(""), "b"));
|
||||
builder.add_argument(
|
||||
cpp_unexposed_expression::build(cpp_builtin_type::build("int"), "I"));
|
||||
REQUIRE(equal_types(idx, alias.type_alias().underlying_type(),
|
||||
*cpp_cv_qualified_type::build(builder.finish(), cpp_cv_const)));
|
||||
}
|
||||
else if (alias.name() == "f")
|
||||
{
|
||||
check_parameters(alias, {{cpp_entity_kind::non_type_template_parameter_t, "I"}});
|
||||
|
||||
cpp_template_instantiation_type::builder builder(
|
||||
cpp_template_ref(cpp_entity_id(""), "b"));
|
||||
builder.add_argument(cpp_unexposed_expression::build(cpp_builtin_type::build("int"),
|
||||
"I < a<int>{(0 , 1)}"));
|
||||
builder.add_argument(cpp_unexposed_type::build("int"));
|
||||
REQUIRE(equal_types(idx, alias.type_alias().underlying_type(), *builder.finish()));
|
||||
}
|
||||
else if (alias.name() == "g")
|
||||
{
|
||||
check_parameters(alias, {{cpp_entity_kind::template_type_parameter_t, "T"},
|
||||
{cpp_entity_kind::template_template_parameter_t, "Templ"}});
|
||||
|
||||
cpp_template_instantiation_type::builder builder(
|
||||
cpp_template_ref(cpp_entity_id(""), "Templ"));
|
||||
builder.add_argument(cpp_unexposed_type::build("T"));
|
||||
REQUIRE(equal_types(idx, alias.type_alias().underlying_type(), *builder.finish()));
|
||||
}
|
||||
else if (alias.name() == "h")
|
||||
{
|
||||
check_parameters(alias, {{cpp_entity_kind::template_type_parameter_t, "T"}});
|
||||
|
||||
cpp_template_instantiation_type::builder builder(
|
||||
cpp_template_ref(cpp_entity_id(""), "g"));
|
||||
builder.add_argument(cpp_unexposed_type::build("T"));
|
||||
builder.add_argument(cpp_template_ref(cpp_entity_id("magic-allow-empty"), "a"));
|
||||
REQUIRE(equal_types(idx, alias.type_alias().underlying_type(), *builder.finish()));
|
||||
}
|
||||
else
|
||||
REQUIRE(false);
|
||||
});
|
||||
REQUIRE(count == 8u);
|
||||
}
|
||||
|
|
@ -6,11 +6,30 @@
|
|||
|
||||
#include <cppast/cpp_array_type.hpp>
|
||||
#include <cppast/cpp_function_type.hpp>
|
||||
#include <cppast/cpp_template.hpp>
|
||||
#include <cppast/cpp_template_parameter.hpp>
|
||||
|
||||
#include "test_parser.hpp"
|
||||
|
||||
using namespace cppast;
|
||||
|
||||
template <typename T, class Predicate>
|
||||
bool equal_ref(const cpp_entity_index& idx, const basic_cpp_entity_ref<T, Predicate>& parsed,
|
||||
const basic_cpp_entity_ref<T, Predicate>& synthesized)
|
||||
{
|
||||
if (parsed.name() != synthesized.name())
|
||||
return false;
|
||||
else if (parsed.is_overloaded() != synthesized.is_overloaded())
|
||||
return false;
|
||||
else if (parsed.is_overloaded())
|
||||
return false;
|
||||
|
||||
auto entities = parsed.get(idx);
|
||||
if (entities.size() != 1u)
|
||||
return synthesized.id()[0u] == cpp_entity_id("magic-allow-empty");
|
||||
return entities[0u]->name().empty() || full_name(*entities[0u]) == parsed.name();
|
||||
}
|
||||
|
||||
bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_type& synthesized)
|
||||
{
|
||||
if (parsed.kind() != synthesized.kind())
|
||||
|
|
@ -24,15 +43,9 @@ bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_
|
|||
|
||||
case cpp_type_kind::user_defined:
|
||||
{
|
||||
auto user_parsed = static_cast<const cpp_user_defined_type&>(parsed).entity();
|
||||
auto user_synthesized = static_cast<const cpp_user_defined_type&>(synthesized).entity();
|
||||
if (user_parsed.name() != user_synthesized.name())
|
||||
return false;
|
||||
else if (user_parsed.is_overloaded())
|
||||
return false;
|
||||
auto entities = user_parsed.get(idx);
|
||||
REQUIRE(entities.size() == 1u);
|
||||
return entities[0u]->name().empty() || full_name(*entities[0u]) == user_parsed.name();
|
||||
auto& user_parsed = static_cast<const cpp_user_defined_type&>(parsed);
|
||||
auto& user_synthesized = static_cast<const cpp_user_defined_type&>(synthesized);
|
||||
return equal_ref(idx, user_parsed.entity(), user_synthesized.entity());
|
||||
}
|
||||
|
||||
case cpp_type_kind::cv_qualified:
|
||||
|
|
@ -69,16 +82,7 @@ bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_
|
|||
|
||||
auto& size_a = array_a.size().value();
|
||||
auto& size_b = array_b.size().value();
|
||||
if (size_a.kind() != size_b.kind())
|
||||
return false;
|
||||
else if (size_a.kind() == cpp_expression_kind::literal)
|
||||
return static_cast<const cpp_literal_expression&>(size_a).value()
|
||||
== static_cast<const cpp_literal_expression&>(size_b).value();
|
||||
else if (size_a.kind() == cpp_expression_kind::unexposed)
|
||||
return static_cast<const cpp_unexposed_expression&>(size_a).expression()
|
||||
== static_cast<const cpp_unexposed_expression&>(size_b).expression();
|
||||
else
|
||||
break;
|
||||
return equal_expressions(size_a, size_b);
|
||||
}
|
||||
|
||||
case cpp_type_kind::function:
|
||||
|
|
@ -92,7 +96,7 @@ bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_
|
|||
return false;
|
||||
|
||||
auto iter_a = func_a.parameter_types().begin();
|
||||
auto iter_b = func_a.parameter_types().begin();
|
||||
auto iter_b = func_b.parameter_types().begin();
|
||||
while (iter_a != func_a.parameter_types().end() && iter_b != func_b.parameter_types().end())
|
||||
{
|
||||
if (!equal_types(idx, *iter_a, *iter_b))
|
||||
|
|
@ -115,7 +119,7 @@ bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_
|
|||
return false;
|
||||
|
||||
auto iter_a = func_a.parameter_types().begin();
|
||||
auto iter_b = func_a.parameter_types().begin();
|
||||
auto iter_b = func_b.parameter_types().begin();
|
||||
while (iter_a != func_a.parameter_types().end() && iter_b != func_b.parameter_types().end())
|
||||
{
|
||||
if (!equal_types(idx, *iter_a, *iter_b))
|
||||
|
|
@ -135,11 +139,50 @@ bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_
|
|||
return equal_types(idx, obj_a.object_type(), obj_b.object_type());
|
||||
}
|
||||
|
||||
// TODO: implement equality when those can be parsed
|
||||
case cpp_type_kind::template_parameter:
|
||||
break;
|
||||
{
|
||||
auto& entity_parsed = static_cast<const cpp_template_parameter_type&>(parsed).entity();
|
||||
auto& entity_synthesized =
|
||||
static_cast<const cpp_template_parameter_type&>(synthesized).entity();
|
||||
return equal_ref(idx, entity_parsed, entity_synthesized);
|
||||
}
|
||||
case cpp_type_kind::template_instantiation:
|
||||
break;
|
||||
{
|
||||
auto& inst_parsed = static_cast<const cpp_template_instantiation_type&>(parsed);
|
||||
auto& inst_synthesized = static_cast<const cpp_template_instantiation_type&>(synthesized);
|
||||
|
||||
if (!equal_ref(idx, inst_parsed.primary_template(), inst_synthesized.primary_template()))
|
||||
return false;
|
||||
|
||||
auto iter_a = inst_parsed.arguments().begin();
|
||||
auto iter_b = inst_synthesized.arguments().begin();
|
||||
while (iter_a != inst_parsed.arguments().end()
|
||||
&& iter_b != inst_synthesized.arguments().end())
|
||||
{
|
||||
if (iter_a->type().has_value() && iter_b->type().has_value())
|
||||
{
|
||||
if (!equal_types(idx, iter_a->type().value(), iter_b->type().value()))
|
||||
return false;
|
||||
}
|
||||
else if (iter_a->expression().has_value() && iter_b->expression().has_value())
|
||||
{
|
||||
if (!equal_expressions(iter_a->expression().value(), iter_b->expression().value()))
|
||||
return false;
|
||||
}
|
||||
else if (iter_a->template_ref().has_value() && iter_b->template_ref().has_value())
|
||||
{
|
||||
if (!equal_ref(idx, iter_a->template_ref().value(), iter_b->template_ref().value()))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
++iter_a;
|
||||
++iter_b;
|
||||
}
|
||||
return iter_a == inst_parsed.arguments().end()
|
||||
&& iter_b == inst_synthesized.arguments().end();
|
||||
}
|
||||
// TODO: implement equality when those can be parsed
|
||||
case cpp_type_kind::dependent:
|
||||
break;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue