standardese / cppast

Library to parse and work with the C++ AST
Other
1.7k stars 164 forks source link

Ability to navigate external definitions (query base classes/ inherited member methods etc...) #126

Closed deadlocklogic closed 2 years ago

deadlocklogic commented 2 years ago

I am trying to navigate a parsed file C.h for instance:

#include "B.h"
class C : public B {
}

where B.h:

#include "A.h"
class B : public A {
    void method();
}

So far with the referring cpp_class of C I can only get B as base class, unfortunately B type cannot be converted to a cpp_class using lookup/lookup_definition. So basically I cannot query any information from the outer base class B and the API doesn't provide any hints about the outer file path if I want to do separate parsing. Any ideas how to work it out? @foonathan as most likely you have good knowledge with libclang/lib tooling, do you think that these api can solve my problem better? Even thought I really liked your lib.

foonathan commented 2 years ago

This should work, lookup gives you a vector of references to cpp_entity you can then downcast to cpp_class.

However, if you're in a position to use libtooling (i.e. integrate with clang, deal with breaking interface changes etc.), I recommend you do so. I wouldn't use libclang directly, however, it doesn't expose as much info as cppast.

deadlocklogic commented 2 years ago

I would really appreciate it if I can use cppast only, because it is clean, concise and safe. My problem is like so, consider I want to parse the class cpp_function_template of the cppast itself just for testing and list all its bases classes/functions etc...

void list_all_base_classes(cppast::cpp_entity_index& idx, const cppast::cpp_class& _class) {
    for (auto& _base : _class.bases()) {
        auto& _type = _base.type();
        if (_type.kind() == cppast::cpp_type_kind::user_defined_t) {
            std::cout << "type resolved" << std::endl;
            auto& _user_defined_type = static_cast<const cppast::cpp_user_defined_type&>(_type);
            auto _ids = _user_defined_type.entity().id();
            for (auto _id : _ids) {
                auto _entity = idx.lookup(_id);
                if (_entity) {
                    std::cout << "base class resolved" << std::endl;
                }
            }
        }
    }
}

int main() {
    auto _path =
        "C:/Users/user/Desktop/cppast/include/cppast/cpp_function_template.hpp";

    cppast::cpp_entity_index _idx;
    cppast::libclang_parser _parser;
    cppast::libclang_compile_config _config;

    _config.set_flags(cppast::cpp_standard::cpp_latest);
    _config.add_include_dir("C:/Users/user/Desktop/cppast/include/");
    _config.add_include_dir("C:/Users/user/Desktop/Library/cmake-build-debug-mingw/_deps/type_safe-src/include/");
    _config.add_include_dir("C:/Users/user/Desktop/Library/cmake-build-debug-mingw/_deps/type_safe-src/external/debug_assert/");

    std::unique_ptr<cppast::cpp_file> _file = _parser.parse(_idx, _path, _config);
    cppast::visit(*_file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) {
        if (info.event != cppast::visitor_info::container_entity_exit) {
            if (e.kind() == cppast::cpp_entity_kind::class_t) {
                if (e.scope_name().has_value() && e.scope_name().value().name() == "cpp_function_template") {
                    list_all_base_classes(_idx, static_cast<const cppast::cpp_class&>(e));
                }
            }
        }
        return true;
    });
    return 0;
}

The problem is that lookup always returns an empty optional_ref. So @foonathan what is going wrong here?

foonathan commented 2 years ago

lookup() only works if you have parsed the definition as well. You have to not only parse the file of the derived class but also of the base class.

Anything that is defined in a header included by a file will not be processed by cppast.

deadlocklogic commented 2 years ago

So you mean I should use this?https://github.com/foonathan/cppast/blob/bf7ec70ea5b46add55d04ba03c8bc4269d4a391c/include/cppast/parser.hpp#L190 Is there a specific example in tests which I could rely on? Because I am not integrating with CMake, just I am doing external parsing. What is the best approach to parse multiple external files with cppast? I can't really use https://github.com/foonathan/cppast/blob/main/test/integration.cpp because I am not integrating with CMake so no generated compile_commands.json.

deadlocklogic commented 2 years ago

So basically I reworked my code using parse_files but still the lookup returns empty, even though now I have 2 parsed files.

int main() {
    cppast::cpp_entity_index _idx;
    cppast::simple_file_parser<libclang_parser> _parser(type_safe::ref(_idx), default_logger());
    cppast::libclang_compile_config _config;

    _config.set_flags(cppast::cpp_standard::cpp_latest);
    _config.add_include_dir("C:/Users/user/Desktop/cppast/include/");
    _config.add_include_dir("C:/Users/user/Desktop/Library/cmake-build-debug-mingw/_deps/type_safe-src/include/");
    _config.add_include_dir("C:/Users/user/Desktop/Library/cmake-build-debug-mingw/_deps/type_safe-src/external/debug_assert/");

    cppast::parse_files(_parser,
                        std::vector<std::string>{
                            "C:/Users/user/Desktop/cppast/include/cppast/cpp_template.hpp",
                            "C:/Users/user/Desktop/cppast/include/cppast/cpp_function_template.hpp",
                        },
                        _config);
    for (auto& _file : _parser.files()) {
        cppast::visit(_file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) {
            if (info.event != cppast::visitor_info::container_entity_exit) {
                if (e.kind() == cppast::cpp_entity_kind::class_t) {
                    if (e.scope_name().has_value() && e.scope_name().value().name() == "cpp_function_template") {
                        list_all_base_classes(_idx, static_cast<const cppast::cpp_class&>(e));
                    }
                }
            }
            return true;
        });
    }
    return 0;
}

@foonathan is there something wrong with this snippet?

deadlocklogic commented 2 years ago

After using parse_files, apparently, this works:

auto& _user_defined_type = static_cast<const cppast::cpp_user_defined_type&>(_type);

auto _resolutions = _user_defined_type.entity().get(idx);
if (_resolutions.size() > 0) {
    auto _resolution = _resolutions[0];
    std::cout << "resolved kind: " << cppast::to_string(_resolution.get().kind()) << std::endl;
    std::cout << "resolved name: " << _resolution.get().name() << std::endl;
}

but this doesn't and I don't know why:

auto _ids = _user_defined_type.entity().id();
for (auto _id : _ids) {
    auto _resolution = idx.lookup(_id);
    if (_resolution) {
        std::cout << "resolved kind: " << cppast::to_string(_resolution.get().kind()) << std::endl;
        std::cout << "resolved name: " << _resolution.get().name() << std::endl;
    }
}