type completion

I’m trying to implement a DWARFASTParser for go, and have hit an issue with fields for structs.
As I understand it, DWARFASTParserClang loads minimal type info for structs at first, and registers the type in SymbolFileDWARF’s m_forward_decl_clang_type_to_die.
Then later when you call type.GetFullCompilerType() it calls back into the DWARFASTParser to complete the type.

But it seems like the SBValue’s in the python API only get Forward types, and never complete them. For example, I do:
var = frame.FindVariable(‘theStruct’)
self.assertEqual(2, var.GetNumChildren())

But I get 0 children, because the type is incomplete and there doesn’t seem to be any way to complete it.
ValueObject::GetNumChildren goes through ValueObject::MaybeCalculateCompleteType, which seems promising. But it only completes the type for objc.
Would a clang variable already have a full type at this point for some reason? If so, how do I arrange that for go?

Also, TypeSystem has a GetCompleteType method. I don’t understand when this would be called or how I can implement it without a reference to SymbolFileDWARF.

The story goes:

lldb_private::ClangASTContext inherits from lldb_private::TypeSystem so it is the type system. The ClangASTContext also signs up to be a clang::ExternalASTSource so that it can complete types via:

ClangASTContext::CompleteTagDecl()
ClangASTContext::CompleteObjCInterfaceDecl()

ClangASTContext::CompleteTagDecl() completes C and C++ structs, unions and classes.

ClangASTContext::CompleteObjCInterfaceDecl() completes Objective C stuff.

The function that actually figures out how many children will be routed through "CompilerType::GetNumChildren(bool)" with code like this:

size_t
ValueObjectVariable::CalculateNumChildren()
{
    CompilerType type(GetCompilerType());
    
    if (!type.IsValid())
        return 0;
    
    const bool omit_empty_base_classes = true;
    return type.GetNumChildren(omit_empty_base_classes);
}

CompilerType passes the ball back to the TypeSystem class (ClangASTContext in this case):

uint32_t
CompilerType::GetNumChildren (bool omit_empty_base_classes) const
{
    if (!IsValid())
        return 0;
    return m_type_system->GetNumChildren(m_type, omit_empty_base_classes);
}

Down in ClangASTContext it does:

uint32_t
ClangASTContext::GetNumChildren (void* type, bool omit_empty_base_classes)
{
    if (!type)
        return 0;
    
    uint32_t num_children = 0;
    clang::QualType qual_type(GetQualType(type));
    const clang::Type::TypeClass type_class = qual_type->getTypeClass();
    switch (type_class)
    {
        ...
        case clang::Type::Record:
            if (GetCompleteQualType (getASTContext(), qual_type))
            {

So it is a static function named GetCompleteQualType() in ClangASTContext.cpp that completes the type if it needs to:

static bool
GetCompleteQualType (clang::ASTContext *ast, clang::QualType qual_type, bool allow_completion = true)
{
    const clang::Type::TypeClass type_class = qual_type->getTypeClass();
    switch (type_class)
    {
        ...
        case clang::Type::Record:
        case clang::Type::Enum:
        {
            const clang::TagType *tag_type = llvm::dyn_cast<clang::TagType>(qual_type.getTypePtr());
            if (tag_type)
            {
                clang::TagDecl *tag_decl = tag_type->getDecl();
                if (tag_decl)
                {
                    if (tag_decl->isCompleteDefinition())
                        return true;
                    
                    if (!allow_completion)
                        return false;
                    
                    if (tag_decl->hasExternalLexicalStorage())
                    {
                        if (ast)
                        {
                            clang::ExternalASTSource *external_ast_source = ast->getExternalSource();
                            if (external_ast_source)
                            {
                                external_ast_source->CompleteType(tag_decl);

So it will ask the clang::ExternalASTSource to complete the type which should call ClangASTContext::CompleteTagDecl() to complete your type.

So this is how it would work if you used DWARFASTParserClang. Are you using that? Or are you using your own DWARFASTParserGo? If so, then you need to implement this type completion in your own TypeSystem. Your type system subclass is required to implement many different function for type introspection, one of which is:

class TypeSystem {

   virtual uint32_t
    GetNumChildren (void *type, bool omit_empty_base_classes) = 0;

};

So the clang version in ClangASTContext::GetNumChildren() knows to complete the type if it a forward declaration. If you have a GoASTContext class that inherits from TypeSystem, then all function that might need to know about the contents of a class or struct, will need to know to complete the type. Or, you can fully parse the structs/unions/classes as you parse the DWARF and not worry about implementing lazy type completion for Go.

So the main questions I have for you are:
- Do you have a GoASTContext that inherits from TypeSystem?
- If you do, in order for DWARF to parse Go types that use the GoASTContext, you will need to write a DWARFASTParser subclass that constructs Go types from DWARF. Then your TypeSystem subclass will need to implement:

TypeSystem {

    virtual DWARFASTParser *
    GetDWARFParser ();

};

Also: lldb_private::TypeSystem has a "SymbolFile *" registered with it:

    virtual SymbolFile *
    GetSymbolFile () const
    {
        return m_sym_file;
    }

    // Returns true if the symbol file changed during the set accessor.
    virtual void
    SetSymbolFile (SymbolFile *sym_file)
    {
        m_sym_file = sym_file;
    }

So it can use the:

bool
SymbolFile::CompleteType (CompilerType &clang_type);

This is what ClangASTContext::CompleteTagDecl() uses:

void
ClangASTContext::CompleteTagDecl (void *baton, clang::TagDecl *decl)
{
    ClangASTContext *ast = (ClangASTContext *)baton;
    SymbolFile *sym_file = ast->GetSymbolFile();
    if (sym_file)
    {
        CompilerType clang_type = GetTypeForDecl (decl);
        if (clang_type)
            sym_file->CompleteType (clang_type);
    }
}

Note that completing types is a bit more complex with clang::ASTContext based types because of it often uses the clang::Decl to do the type completion so we need to maintain that mapping. It might not be that hard in Go as you could just do:

CompilerType my_type = ...;

if (my_type.GetCompleteType())
{
    ...
}

And this could just call down into your SymbolFile. But in clang we often get to the point where we are playing around with types from a pure clang::ASTContext stand point (like in the expression parser) and the clang expression parser knows how to complete types via clang::ExternalASTSource, so the expression parser when playing around with clang::Decl objects can cause them to complete themselves. This allows us to hand the clang expression parser a forward decl to a "Foo" type and let the expression parser complete the type only if it needs to. If the expression parser just has a "Foo *" that it uses and it just passed that to a function, it never needs to complete "Foo", but if you do "Foo *foo = ...; foo->m_int" then you would need to complete it. So we make all struct, unions, and classes able to lazily complete themselves for performance reasons.

Greg