From b183513166fed6a6e4698505e8043646e9a33f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Wed, 22 Feb 2017 22:42:20 +0100 Subject: [PATCH] Parse cpp_type_alias and simple cpp_type's --- include/cppast/cpp_entity_ref.hpp | 15 +- include/cppast/cpp_type.hpp | 2 +- include/cppast/cpp_type_alias.hpp | 2 + src/cpp_entity_ref.cpp | 2 +- src/cpp_type_alias.cpp | 7 +- src/libclang/debug_helper.cpp | 5 + src/libclang/debug_helper.hpp | 2 + src/libclang/language_linkage_parser.cpp | 8 - src/libclang/parse_error.hpp | 16 ++ src/libclang/parse_functions.cpp | 3 + src/libclang/parse_functions.hpp | 7 + src/libclang/type_parser.cpp | 309 +++++++++++++++++++++++ test/cpp_type_alias.cpp | 168 ++++++++++++ 13 files changed, 533 insertions(+), 13 deletions(-) create mode 100644 src/libclang/type_parser.cpp create mode 100644 test/cpp_type_alias.cpp diff --git a/include/cppast/cpp_entity_ref.hpp b/include/cppast/cpp_entity_ref.hpp index 3c78170..6c2a8c2 100644 --- a/include/cppast/cpp_entity_ref.hpp +++ b/include/cppast/cpp_entity_ref.hpp @@ -14,7 +14,18 @@ namespace cppast /// \exclude namespace detail { - void check_entity_cast(const cpp_entity& e, cpp_entity_kind expected_kind); + void check_entity_cast_impl(const cpp_entity& e, cpp_entity_kind expected_kind); + + template + void check_entity_cast(const cpp_entity& e) + { + check_entity_cast_impl(e, T::kind()); + } + + template <> + inline void check_entity_cast(const cpp_entity&) + { + } } // namespace detail /// A basic reference to some kind of [cppast::cpp_entity](). @@ -46,7 +57,7 @@ namespace cppast auto entity = idx.lookup(target_); if (!entity) return type_safe::nullopt; - detail::check_entity_cast(entity.value(), T::kind()); + detail::check_entity_cast(entity.value()); return static_cast(entity.value()); } diff --git a/include/cppast/cpp_type.hpp b/include/cppast/cpp_type.hpp index 3572a48..b5ed571 100644 --- a/include/cppast/cpp_type.hpp +++ b/include/cppast/cpp_type.hpp @@ -77,7 +77,7 @@ namespace cppast { public: /// \returns A newly created unexposed type. - std::unique_ptr build(std::string name) + static std::unique_ptr build(std::string name) { return std::unique_ptr(new cpp_unexposed_type(std::move(name))); } diff --git a/include/cppast/cpp_type_alias.hpp b/include/cppast/cpp_type_alias.hpp index 7b67ff3..48c89ac 100644 --- a/include/cppast/cpp_type_alias.hpp +++ b/include/cppast/cpp_type_alias.hpp @@ -15,6 +15,8 @@ namespace cppast class cpp_type_alias final : public cpp_entity { public: + static cpp_entity_kind kind() noexcept; + /// \returns A newly created and registered type alias. static std::unique_ptr build(const cpp_entity_index& idx, cpp_entity_id id, std::string name, diff --git a/src/cpp_entity_ref.cpp b/src/cpp_entity_ref.cpp index 1dd27de..6b67f31 100644 --- a/src/cpp_entity_ref.cpp +++ b/src/cpp_entity_ref.cpp @@ -8,7 +8,7 @@ using namespace cppast; -void detail::check_entity_cast(const cpp_entity& e, cpp_entity_kind expected_kind) +void detail::check_entity_cast_impl(const cpp_entity& e, cpp_entity_kind expected_kind) { DEBUG_ASSERT(e.kind() == expected_kind, detail::precondition_error_handler{}, "mismatched entity kind"); diff --git a/src/cpp_type_alias.cpp b/src/cpp_type_alias.cpp index 360c34f..a22ec1f 100644 --- a/src/cpp_type_alias.cpp +++ b/src/cpp_type_alias.cpp @@ -8,6 +8,11 @@ using namespace cppast; +cpp_entity_kind cpp_type_alias::kind() noexcept +{ + return cpp_entity_kind::type_alias_t; +} + std::unique_ptr cpp_type_alias::build(const cpp_entity_index& idx, cpp_entity_id id, std::string name, std::unique_ptr type) @@ -25,5 +30,5 @@ std::unique_ptr cpp_type_alias::build(std::string cpp_entity_kind cpp_type_alias::do_get_entity_kind() const noexcept { - return cpp_entity_kind::type_alias_t; + return kind(); } diff --git a/src/libclang/debug_helper.cpp b/src/libclang/debug_helper.cpp index 4b38029..bbfe688 100644 --- a/src/libclang/debug_helper.cpp +++ b/src/libclang/debug_helper.cpp @@ -21,6 +21,11 @@ detail::cxstring detail::get_cursor_kind_spelling(const CXCursor& cur) noexcept return cxstring(clang_getCursorKindSpelling(clang_getCursorKind(cur))); } +detail::cxstring detail::get_type_kind_spelling(const CXType& type) noexcept +{ + return cxstring(clang_getTypeKindSpelling(type.kind)); +} + namespace { std::mutex mtx; diff --git a/src/libclang/debug_helper.hpp b/src/libclang/debug_helper.hpp index 36479aa..4fcc116 100644 --- a/src/libclang/debug_helper.hpp +++ b/src/libclang/debug_helper.hpp @@ -15,6 +15,8 @@ namespace cppast cxstring get_cursor_kind_spelling(const CXCursor& cur) noexcept; + cxstring get_type_kind_spelling(const CXType& type) noexcept; + void print_cursor_info(const CXCursor& cur) noexcept; void print_tokens(const cxtranslation_unit& tu, const CXFile& file, diff --git a/src/libclang/language_linkage_parser.cpp b/src/libclang/language_linkage_parser.cpp index 1e907fc..6b15853 100644 --- a/src/libclang/language_linkage_parser.cpp +++ b/src/libclang/language_linkage_parser.cpp @@ -11,14 +11,6 @@ using namespace cppast; -namespace -{ - cpp_language_linkage::builder make_ll_builder(const detail::parse_context& context, - const CXCursor& cur) - { - } -} - std::unique_ptr detail::try_parse_cpp_language_linkage(const parse_context& context, const CXCursor& cur) { diff --git a/src/libclang/parse_error.hpp b/src/libclang/parse_error.hpp index 483ceec..c841506 100644 --- a/src/libclang/parse_error.hpp +++ b/src/libclang/parse_error.hpp @@ -22,6 +22,11 @@ namespace cppast return source_location::make(get_display_name(cur).c_str()); } + inline source_location make_location(const CXType& type) + { + return source_location::make(cxstring(clang_getTypeSpelling(type)).c_str()); + } + // thrown on a parsing error // not meant to escape to the user class parse_error : public std::logic_error @@ -37,6 +42,11 @@ namespace cppast { } + parse_error(const CXType& type, std::string message) + : parse_error(make_location(type), std::move(message)) + { + } + diagnostic get_diagnostic() const { return diagnostic{what(), location_, severity::error}; @@ -55,6 +65,12 @@ namespace cppast { throw parse_error(cur, std::move(message)); } + + static void handle(const debug_assert::source_location&, const char*, + const CXType& type, std::string message) + { + throw parse_error(type, std::move(message)); + } }; template diff --git a/src/libclang/parse_functions.cpp b/src/libclang/parse_functions.cpp index 1a1a3c1..53a487e 100644 --- a/src/libclang/parse_functions.cpp +++ b/src/libclang/parse_functions.cpp @@ -34,6 +34,9 @@ std::unique_ptr detail::parse_entity(const detail::parse_context& co case CXCursor_UsingDeclaration: return parse_cpp_using_declaration(context, cur); + case CXCursor_TypeAliasDecl: + return parse_cpp_type_alias(context, cur); + default: break; } diff --git a/src/libclang/parse_functions.hpp b/src/libclang/parse_functions.hpp index b86d57c..8a70850 100644 --- a/src/libclang/parse_functions.hpp +++ b/src/libclang/parse_functions.hpp @@ -14,6 +14,8 @@ namespace cppast { + class cpp_type; + namespace detail { cpp_entity_id get_entity_id(const CXCursor& cur); @@ -26,6 +28,8 @@ namespace cppast type_safe::object_ref idx; }; + std::unique_ptr parse_type(const parse_context& context, const CXType& type); + // parse_entity() dispatches on the cursor type // it calls one of the other parse functions defined elsewhere // try_parse_XXX are not exposed entities @@ -43,6 +47,9 @@ namespace cppast std::unique_ptr parse_cpp_using_declaration(const parse_context& context, const CXCursor& cur); + std::unique_ptr parse_cpp_type_alias(const parse_context& context, + const CXCursor& cur); + std::unique_ptr parse_entity(const parse_context& context, const CXCursor& cur); } } // namespace cppast::detail diff --git a/src/libclang/type_parser.cpp b/src/libclang/type_parser.cpp new file mode 100644 index 0000000..bcc978b --- /dev/null +++ b/src/libclang/type_parser.cpp @@ -0,0 +1,309 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include "parse_functions.hpp" + +#include +#include +#include +#include + +using namespace cppast; + +namespace +{ + void remove_trailing_ws(std::string& str) + { + while (!str.empty() && std::isspace(str.back())) + str.pop_back(); + } + + void remove_leading_ws(std::string& str) + { + while (!str.empty() && std::isspace(str.front())) + str.erase(0, 1); + } + + bool remove_suffix(std::string& str, const char* suffix) + { + auto length = std::strlen(suffix); + if (str.length() >= length && str.compare(str.length() - length, length, suffix) == 0) + { + str.erase(str.length() - length); + remove_trailing_ws(str); + return true; + } + else + return false; + } + + bool remove_prefix(std::string& str, const char* prefix) + { + auto length = std::strlen(prefix); + if (str.length() >= length && str.compare(0u, length, prefix) == 0) + { + str.erase(0u, length); + remove_leading_ws(str); + return true; + } + else + return false; + } + + std::string get_type_spelling(const CXType& type) + { + return detail::cxstring(clang_getTypeSpelling(type)).c_str(); + } + + // const/volatile at the end (because people do that apparently!) + cpp_cv suffix_cv(std::string& spelling) + { + auto cv = cpp_cv_none; + if (remove_suffix(spelling, "const")) + { + if (remove_suffix(spelling, "volatile")) + cv = cpp_cv_const_volatile; + else + cv = cpp_cv_const; + } + else if (remove_suffix(spelling, "volatile")) + { + if (remove_suffix(spelling, "const")) + cv = cpp_cv_const_volatile; + else + cv = cpp_cv_volatile; + } + + return cv; + } + + // const/volatile at the beginning + // (weird that the better version is less performant, isn't it?) + cpp_cv prefix_cv(std::string& spelling) + { + auto cv = cpp_cv_none; + if (remove_prefix(spelling, "const")) + { + if (remove_prefix(spelling, "volatile")) + cv = cpp_cv_const_volatile; + else + cv = cpp_cv_const; + } + else if (remove_prefix(spelling, "volatile")) + { + if (remove_prefix(spelling, "const")) + cv = cpp_cv_const_volatile; + else + cv = cpp_cv_volatile; + } + + return cv; + } + + cpp_cv merge_cv(cpp_cv a, cpp_cv b) + { + switch (a) + { + case cpp_cv_none: + return b; + + case cpp_cv_const: + switch (b) + { + case cpp_cv_none: + return cpp_cv_const; + case cpp_cv_const: + return cpp_cv_const; + case cpp_cv_volatile: + return cpp_cv_const_volatile; + case cpp_cv_const_volatile: + return cpp_cv_const_volatile; + } + break; + + case cpp_cv_volatile: + switch (b) + { + case cpp_cv_none: + return cpp_cv_volatile; + case cpp_cv_const: + return cpp_cv_const_volatile; + case cpp_cv_volatile: + return cpp_cv_volatile; + case cpp_cv_const_volatile: + return cpp_cv_const_volatile; + } + break; + + case cpp_cv_const_volatile: + return cpp_cv_const_volatile; + } + + DEBUG_UNREACHABLE(detail::assert_handler{}); + return cpp_cv_none; + } + + std::unique_ptr make_cv_qualified(std::unique_ptr entity, cpp_cv cv) + { + if (cv == cpp_cv_none) + return std::move(entity); + return cpp_cv_qualified_type::build(std::move(entity), cv); + } + + template + std::unique_ptr make_leave_type(const CXType& type, Builder b) + { + auto spelling = get_type_spelling(type); + + // check for cv qualifiers on the leave type + auto prefix = prefix_cv(spelling); + auto suffix = suffix_cv(spelling); + auto cv = merge_cv(prefix, suffix); + + auto entity = b(std::move(spelling)); + return make_cv_qualified(std::move(entity), cv); + } + + cpp_reference get_reference_kind(const CXType& type) + { + if (type.kind == CXType_LValueReference) + return cpp_ref_lvalue; + else if (type.kind == CXType_RValueReference) + return cpp_ref_rvalue; + return cpp_ref_none; + } + + std::unique_ptr parse_type_impl(const detail::parse_context& context, + const CXType& type) + { + switch (type.kind) + { + // stuff I can't parse + // or have no idea what it is and wait for bug report + case CXType_Invalid: + case CXType_Overload: + case CXType_ObjCId: + case CXType_ObjCClass: + case CXType_ObjCSel: + case CXType_Complex: + case CXType_BlockPointer: + case CXType_Vector: + case CXType_ObjCInterface: + case CXType_ObjCObjectPointer: + { + auto msg = detail::format("unexpected type of kind '", + detail::get_type_kind_spelling(type).c_str(), "'"); + auto location = source_location::make(get_type_spelling(type).c_str()); + context.logger->log("libclang parser", diagnostic{msg, location, severity::warning}); + } + // fallthrough + case CXType_Unexposed: + return cpp_unexposed_type::build(get_type_spelling(type).c_str()); + + case CXType_Void: + case CXType_Bool: + case CXType_Char_U: + case CXType_UChar: + case CXType_Char16: + case CXType_Char32: + case CXType_UShort: + case CXType_UInt: + case CXType_ULong: + case CXType_ULongLong: + case CXType_UInt128: + case CXType_Char_S: + case CXType_SChar: + case CXType_WChar: + case CXType_Short: + case CXType_Int: + case CXType_Long: + case CXType_LongLong: + case CXType_Int128: + case CXType_Float: + case CXType_Double: + case CXType_LongDouble: + case CXType_NullPtr: + case CXType_Float128: + return make_leave_type(type, [](std::string&& spelling) { + return cpp_builtin_type::build(std::move(spelling)); + }); + + case CXType_Record: + case CXType_Enum: + case CXType_Typedef: + case CXType_Elaborated: + return make_leave_type(type, [&](std::string&& spelling) { + auto decl = clang_getTypeDeclaration(type); + return cpp_user_defined_type::build( + cpp_type_ref(detail::get_entity_id(decl), std::move(spelling))); + }); + + case CXType_Pointer: + { + auto pointee = parse_type_impl(context, clang_getPointeeType(type)); + auto pointer = cpp_pointer_type::build(std::move(pointee)); + + auto spelling = get_type_spelling(type); + auto cv = suffix_cv(spelling); + return make_cv_qualified(std::move(pointer), cv); + } + case CXType_LValueReference: + case CXType_RValueReference: + { + auto referee = parse_type_impl(context, clang_getPointeeType(type)); + return cpp_reference_type::build(std::move(referee), get_reference_kind(type)); + } + + // TODO + case CXType_IncompleteArray: + case CXType_VariableArray: + case CXType_DependentSizedArray: + case CXType_ConstantArray: + { + break; + } + + case CXType_Dependent: + break; + + case CXType_FunctionNoProto: + break; + case CXType_FunctionProto: + break; + case CXType_MemberPointer: + break; + + case CXType_Auto: + break; + } + + return nullptr; + } +} + +std::unique_ptr detail::parse_type(const detail::parse_context& context, + const CXType& type) +{ + auto result = parse_type_impl(context, type); + DEBUG_ASSERT(result && is_valid(*result), detail::parse_error_handler{}, type, + "invalid type parsed"); + return std::move(result); +} + +std::unique_ptr detail::parse_cpp_type_alias(const detail::parse_context& context, + const CXCursor& cur) +{ + DEBUG_ASSERT(cur.kind == CXCursor_TypeAliasDecl, detail::assert_handler{}); + + detail::tokenizer tokenizer(context.tu, context.file, cur); + detail::token_stream stream(tokenizer, cur); + + // using = ... + detail::skip(stream, "using"); + auto& name = stream.get().value(); + detail::skip(stream, "="); + + auto type = parse_type(context, clang_getTypedefDeclUnderlyingType(cur)); + return cpp_type_alias::build(*context.idx, get_entity_id(cur), name.c_str(), std::move(type)); +} diff --git a/test/cpp_type_alias.cpp b/test/cpp_type_alias.cpp new file mode 100644 index 0000000..2bf6f68 --- /dev/null +++ b/test/cpp_type_alias.cpp @@ -0,0 +1,168 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include "test_parser.hpp" + +using namespace cppast; + +bool equal_types(const cpp_entity_index& idx, const cpp_type& parsed, const cpp_type& synthesized) +{ + if (parsed.kind() != synthesized.kind()) + return false; + + switch (parsed.kind()) + { + case cpp_type_kind::builtin: + return static_cast(parsed).name() + == static_cast(synthesized).name(); + + case cpp_type_kind::user_defined: + { + auto user_parsed = static_cast(parsed).entity(); + auto user_synthesized = static_cast(synthesized).entity(); + if (user_parsed.name() != user_synthesized.name()) + return false; + // check that the referring also works + auto entity = user_parsed.get(idx); + return entity.has_value() && entity.value().name() == user_parsed.name(); + } + + case cpp_type_kind::cv_qualified: + { + auto& cv_a = static_cast(parsed); + auto& cv_b = static_cast(synthesized); + return cv_a.cv_qualifier() == cv_b.cv_qualifier() + && equal_types(idx, cv_a.type(), cv_b.type()); + } + + case cpp_type_kind::pointer: + return equal_types(idx, static_cast(parsed).pointee(), + static_cast(synthesized).pointee()); + case cpp_type_kind::reference: + { + auto& ref_a = static_cast(parsed); + auto& ref_b = static_cast(synthesized); + return ref_a.reference_kind() == ref_b.reference_kind() + && equal_types(idx, ref_a.referee(), ref_b.referee()); + } + + // TODO + case cpp_type_kind::array: + break; + case cpp_type_kind::function: + break; + case cpp_type_kind::member_function: + break; + case cpp_type_kind::member_object: + break; + case cpp_type_kind::template_parameter: + break; + case cpp_type_kind::template_instantiation: + break; + case cpp_type_kind::dependent: + break; + + case cpp_type_kind::unexposed: + return static_cast(parsed).name() + == static_cast(synthesized).name(); + } + + return false; +} + +// also tests the type parsing code +// other test cases don't need that anymore +TEST_CASE("cpp_type_alias") +{ + auto code = R"( +// basic +using a = int; +using b = const long double volatile; + +// pointers +using c = int*; +using d = const unsigned int*; +using e = unsigned const * volatile; + +// references +using f = int&; +using g = const int&&; + +// user-defined types +using h = c; +using i = const d; +using j = e*; +)"; + + auto add_cv = [](std::unique_ptr type, cpp_cv cv) { + return cpp_cv_qualified_type::build(std::move(type), cv); + }; + + cpp_entity_index idx; + auto file = parse(idx, "cpp_type_alias.cpp", code); + auto count = test_visit(*file, [&](const cpp_type_alias& alias) { + if (alias.name() == "a") + { + auto type = cpp_builtin_type::build("int"); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else if (alias.name() == "b") + { + auto type = add_cv(cpp_builtin_type::build("long double"), cpp_cv_const_volatile); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else if (alias.name() == "c") + { + auto type = cpp_pointer_type::build(cpp_builtin_type::build("int")); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else if (alias.name() == "d") + { + auto type = cpp_pointer_type::build( + add_cv(cpp_builtin_type::build("unsigned int"), cpp_cv_const)); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else if (alias.name() == "e") + { + auto type = add_cv(cpp_pointer_type::build( + add_cv(cpp_builtin_type::build("unsigned int"), cpp_cv_const)), + cpp_cv_volatile); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else if (alias.name() == "f") + { + auto type = cpp_reference_type::build(cpp_builtin_type::build("int"), cpp_ref_lvalue); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else if (alias.name() == "g") + { + auto type = + cpp_reference_type::build(add_cv(cpp_builtin_type::build("int"), cpp_cv_const), + cpp_ref_rvalue); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else if (alias.name() == "h") + { + auto type = cpp_user_defined_type::build(cpp_type_ref(cpp_entity_id(""), "c")); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else if (alias.name() == "i") + { + auto type = add_cv(cpp_user_defined_type::build(cpp_type_ref(cpp_entity_id(""), "d")), + cpp_cv_const); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else if (alias.name() == "j") + { + auto type = cpp_pointer_type::build( + cpp_user_defined_type::build(cpp_type_ref(cpp_entity_id(""), "e"))); + REQUIRE(equal_types(idx, alias.underlying_type(), *type)); + } + else + REQUIRE(false); + }); + REQUIRE(count == 10u); +}