Clang-tidy Recursively Traverse the Nodes

Hi there,

I have a question that I could not answer myself by checking the forum.

Here it is:

How can I recursively check field declarations of a struct until there is no more record type left (class, struct) but only a basic type without specifying the max_depth?

struct A {};

struct B {
    A a;
};

struct C {
   B b;
};

I can already do it hardcoded for this specific case, for example:

recordDecl(
    has(
        fieldDecl(
            hasType(recordDecl(
                has(fieldDecl(hasType(recordDecl())))
            )) 
        )
    )) ...

But since I will not have the information of the depth, I should be able to reach there recursively.

So to sum up, I want to write a checker, which would go inside of a complex type and check its own members until it reaches a basic type, which is not a complex type that has other types in it.

I am looking forward to your answers!

https://clang.llvm.org/docs/LibASTMatchersReference.html

Look into hasDescendant/forEachDescendant.
If you need information about depth, you may check clang::RecursiveASTVisitor, or always you can use things like getParent and walk from bottom to up. This will work for basic traversal.

As for types checking, you may need to write own AST matcher, and then you can use recurrence call on that matcher if needed. Or just write normal function that will do that, depend what you need in final form.

As for check, you need to provide more information, what you want to get as a final product. Just find all complex classes, or non complex, or what.

Thanks for the answer! But forEachDescendant and forEach are simply checking all the childs, but I need to go to childs of the childs as far as it goes.

Is it also applicable in that case?

Thanks!

Just to illustrate my goal:

See here, that I should be able to do recursively, not with a limited depth…

Plus, I always need to go up when there is a struct instantiated within a struct, because each struct is a different node within the translation unit, so I cannot simply always go to child node, which won’t work.

But, I need to make “has” somehow recursive, so whenever it sees a new Struct, it should call “has” …

Hi Johnny,

I’ve written the following matcher that I believe satisfies your requirements:

const RecordDecl *rec_record_decl(const RecordDecl *r) {
	auto name = r->getNameAsString();
	std::cout << name << std::endl;
	auto f_decls = r->fields();
	// "...which is not a complex type that has other types in it."
	// I interpret this as a type with no fields
	if (f_decls.empty()) {
		std::cout << "is empty " << std::endl;
		return r;
	}
	for (const auto f : f_decls) {
		auto t = f->getType();
		if (t->isRecordType()) {
			const auto *as_r = t->getAsRecordDecl();
			auto rd = rec_record_decl(as_r);
			if (rd != nullptr) {
				return rd;
			}
		}
	}
	return nullptr;
}

AST_MATCHER(RecordDecl, has_rec_record_decl) {
	Node.dumpAsDecl();
	if(!Node.isThisDeclarationADefinition()){
		return false; // Don't care about declarations (otherwise we match twice)
	}
	if(rec_record_decl(&Node)){
		return true;
	}
	return false;
}

The matcher will recursively match all RecordDecls that have a field that is a RecordDecl with no fields.

In the example below it matches A, B and C but not D or E:

struct A {};

struct B {
	A a;
};

struct C {
	B b;
};

struct D {
	int a;
};

struct E {
	D d;
};

It can be used as follows:

auto recordTest = recordDecl(matchers::has_rec_record_decl()).bind("decl");

It should be noted that the last time I did something like this it resulted in poor performance on larger programs.

Please let me know if you need me to elaborate on anything.

Edit: I should mention that this is something I have quickly spun up and that it hasn’t been tested thoroughly