Why are builtin types showing as Unexposed through a member function pointer?

I’m trying to get at the signatures of member function pointers. e.g. given this:

class Test
{
public:
    template <typename T>
    T do_templated_thing(T *arg)
    {
        return *arg;
    }
};

// somewhat similar to pybind11...
auto _ = Class<Test>("Test")
             .m(&Test::do_templated_thing<int>);

Then inspecting the AST I get (the dump printing is my own here):

 ▸ UnaryOperator:
      𝜏 int (Test::*)(int *): MemberPointer   [pod] ()
    ▸ DeclRefExpr: do_templated_thing
          𝜏 int (int *): FunctionProto    ()
        ↪ CXXMethod: do_templated_thing<>(int *) c:@S@Test@F@do_templated_thing<#I>#*I#  [def]
              𝜏 int (int *): FunctionProto    ()
            ↪ FunctionTemplate: do_templated_thing(T *) c:@S@Test@FT@>1#Tdo_templated_thing#*t0.0#S1_#  🗸
            ▸ ParmDecl: arg c:test_bind.cpp@549@S@Test@F@do_templated_thing<#I>#*I#@arg  [def]
                  𝜏 int *: Pointer   [pod] ()
                    𝜏 int: Unexposed   [pod] ()

You can see that even though all the decls and types know that it’s an int there, because it’s in all of their display names and spellings, the underlying type in the AST (last line) ultimately ends up as TypeKind::Unexposed instead of TypeKind::Int.

How is this information getting lost? And where could I fix it?

That is a surprise to me – I’m not certain how you would get that behavior, but the way I’d debug it is to put breakpoints in CXType.cpp in the GetTypeKind() and GetBuiltinTypeKind() functions where they return an unexposed index. From there you can look to see what the unexposed type actually is.

Thanks Aaron! I actually ended up writing a RecursiveASTVisitor last night to be able to inspect the AST directly. Bit slow-going since it’s been a while since I touched the C++ API, but that gives me this for the same snippet:

UnaryOperatorStmt
DeclRefExprStmt
FunctionProtoType 0x56512f563650 'int (int *)' cdecl
|-SubstTemplateTypeParmType 0x56512f563510 'int' sugar
| |-TemplateTypeParmType 0x56512f53e890 'T' dependent depth 0 index 0
| | `-TemplateTypeParm 0x56512f53e840 'T'
| `-BuiltinType 0x56512f4f78d0 'int'
`-PointerType 0x56512f563580 'int *'
  `-SubstTemplateTypeParmType 0x56512f563510 'int' sugar
    |-TemplateTypeParmType 0x56512f53e890 'T' dependent depth 0 index 0
    | `-TemplateTypeParm 0x56512f53e840 'T'
    `-BuiltinType 0x56512f4f78d0 'int'
  DECL: Test::do_templated_thing
    FunctionTemplate

You can see that it’s a SubstTemplateTypeParmType. Interestingly the decl for the method is FunctionTemplate which is getting exposed in libclang as a CXXMethod, which I would have interpreted as being a CXXMethodDecl underneath. How does that association work?

So for my specific problem, I guess the obvious answer is to check for SubstTemplateTypeParmType and handle that. Would it make sense just to return the substitution type directly, or would it be better to add API to get the substitution type (clang_Type_getTemplateSubstitution()?), and add a new CXType_SubstTemplate that could be used to identify this situation and call the new function?

Ah okay, that makes sense! We don’t seem to expose a cursor for that.

Hmmm, I would have expected FunctionTemplateDecl to be exposed as CXCursor_FunctionTemplate and not CXCursor_CXXMethod. A FunctionTemplateDecl is a template type that wraps a FunctionDecl which represents the uninstantiated template and it carries a list of all the specializations of that function template.

I think it might be best to expose the substitution type. 1) I think we want the C indexing API to model the AST so it’s easier to reason about from things like an AST dump. 2) This type exists to make it clear that the type came out of a template substitution and is not a canonical type. 3) The type exposes more information, like getting the replaced TemplateTypeParmDecl object from the substituted type.

Sounds good. I just did a quick hack to return the replacement type and it works. I’ll do the proper solution when I get time and submit a patch.

Do you know when 16 will be released (ie how much time do I have to get a patch in to make it into 16)?

I believe we’re creating the release branch at the end of Jan 2023 and hoping to release the final product around mid-March.

Ok thanks that’s plenty of time then.

Side note: what’s the deal with Linux release builds? There used to be ubuntu18 builds for the releases but theres none for 15.x, only a rhel and a sle build and only for 15.02.

@tobiashieta or @tstellar may have more details there.

Our binaries are done by volunteers, no one has done them in a while.

Ah right I see. If I wanted to submit a binary for the release is there a process for that?