DeclContext lookup strange behavior: cannot find local declarations

Hi all,

In our code, we find variables, and then lookup other declarations in the declaration context of this variable. But lookups from DeclContext returns null if given variable (from which we got declaration context) is local.

Let’s consider an example. Say we have a simple matcher to match all declarations of integer variables:

 const auto Matcher = decl(

Then a callback to process on all integer variables:

const NamedDecl *Decl = 

if (Decl) {
    const DeclContext *DC = Decl->getDeclContext();
    // ...

Then I want to find a variable in Decl’s declaration context (DC), e.g.

DeclarationName FDName(&Decl->getASTContext().Idents.get("y"));

(for global variables)

or, e.g.

DeclarationName FDName(&Decl->getASTContext().Idents.get("b"));

(for local variables)

and then I try to lookup this variable by FDName and check result:

auto Lookup = DC->lookup(FDName);
if (Lookup.empty()) {
    llvm::outs() << "Empty \n";

ValueDecl *ValD = dyn_cast<ValueDecl>(Lookup.front());
if (ValD == nullptr)


I try to run it with a simple C code:

int x, y, z;

int main() {
  int a, b, c;
  return 0;

In my experiments, all lookups of global variables (x, y, z) in the declaration context of a global variable return the desired declaration (as expected), meanwhile all lookups of local variables (a, b, c) in the declaration context of local variable return emptiness.

Note, that even if I lookup the declaration itself:

auto Lookup = DC->lookup(Decl->getDeclName());

Is it a bug, or am I wrong understanding of lookup behavior?

Clang 13.0.1.

It would be very nice to be able to do lookup in AST tools in this way, and others have asked. The problem is that a) matching and most other AST tooling must be performed within ASTConsumer::HandleTranslationUnit(), which is called after Parsing/Sema the entire TU is complete; and b) lookup requires scope information which is managed within Sema (not the AST) during parsing and discarded as soon as the scope is fully parsed.

The ideal solution would be to introduce hooks called by Sema for handling each node as it is parsed, providing a RecursiveASTVisitor-like interface directly in ASTConsumer; ASTMatchers could then implement this interface instead of RecursiveASTVisitor, so that all temporary Sema state information would be available to one’s callbacks. However this change might require substantial work, could conceivably have performance costs, and I gather there it not yet sufficient appetite to pursue this from the people who maintain clang for a living.

The only other option if you really need this to work is to manually recreate the proper scope state information in Sema using the declarations in your function bodies. I believe someone once gave some pointers on how to do this awhile back if you search around, though I suspect it will be difficult and will require additional maintenance burdens.

1 Like

Thanks a lot for the detailed answer! It was really interesting for me.

I tried a bit to address the issue, as you advised. However, I eventually get rid from looking up the names. I needed in names, because I want to modify them. Instead, I do operations with the source code itself and just modify a source code by the desired rule.