Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

740 lines
25 KiB
Go

package languages
import (
"fmt"
"strings"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/parser"
sitter "github.com/zzet/gortex/internal/parser/tsitter"
"github.com/zzet/gortex/internal/parser/tsitter/cpp"
)
// qCppAll is a single tree-sitter query alternating over every pattern
// the C++ extractor needs. One tree walk per file replaces the 8
// `parser.RunQuery` calls the previous design made (each of which
// recompiled its query and ran an independent cursor over the whole
// tree). Capture names are disjoint across patterns so the dispatch
// in Extract can branch on which name is set. Class-method extraction
// still walks the class_specifier body inline — C++ methods can have
// declarators other than bare identifiers (destructor_name,
// field_identifier, qualified_identifier), which the legacy code
// handled via extractFuncName and an explicit body walk; keeping that
// walk inside the class.def dispatch preserves behaviour while still
// collapsing the repeated whole-tree scans into one.
const qCppAll = `
[
(namespace_definition
name: (namespace_identifier) @ns.name) @ns.def
(class_specifier
name: (type_identifier) @class.name) @class.def
(struct_specifier
name: (type_identifier) @struct.name) @struct.def
(enum_specifier
name: (type_identifier) @enum.name) @enum.def
(function_definition
declarator: (function_declarator
declarator: (identifier) @func.name)) @func.def
(function_definition
declarator: (pointer_declarator
declarator: (function_declarator
declarator: (identifier) @func.name))) @func.def
(function_definition
declarator: (pointer_declarator
declarator: (pointer_declarator
declarator: (function_declarator
declarator: (identifier) @func.name)))) @func.def
(function_definition
declarator: (reference_declarator
(function_declarator
declarator: (identifier) @func.name))) @func.def
(preproc_include
path: (_) @include.path) @include.def
(preproc_def
name: (identifier) @macro.name) @macro.def
(preproc_function_def
name: (identifier) @macrofn.name) @macrofn.def
(call_expression
function: (identifier) @call.name) @call.expr
(call_expression
function: (field_expression
field: (field_identifier) @callm.method)) @callm.expr
]
`
// CppExtractor extracts C++ source files into graph nodes and edges.
type CppExtractor struct {
lang *sitter.Language
qAll *parser.PreparedQuery
}
func NewCppExtractor() *CppExtractor {
lang := cpp.GetLanguage()
return &CppExtractor{
lang: lang,
qAll: parser.MustPreparedQuery(qCppAll, lang),
}
}
func (e *CppExtractor) Language() string { return "cpp" }
func (e *CppExtractor) Extensions() []string { return []string{".cpp", ".cc", ".cxx", ".hpp"} }
// --- Deferred call buffer ----------------------------------------
// cppDeferredCall buffers a call site discovered during the
// per-match walk so the post-pass can attribute it to the enclosing
// function once funcRanges is built. argTypes carries the C++ ADL
// hint set populated by extractCppCallArgTypes.
type cppDeferredCall struct {
name string
line int
isMember bool
receiver string
argTypes []string
}
func (e *CppExtractor) Extract(filePath string, src []byte) (*parser.ExtractionResult, error) {
tree, err := parser.ParseFile(src, e.lang)
if err != nil {
return nil, err
}
defer tree.Close()
root := tree.RootNode()
result := &parser.ExtractionResult{}
fileNode := &graph.Node{
ID: filePath, Kind: graph.KindFile, Name: filePath,
FilePath: filePath, StartLine: 1, EndLine: int(root.EndPoint().Row) + 1,
Language: "cpp",
}
fileID := fileNode.ID
result.Nodes = append(result.Nodes, fileNode)
seen := make(map[string]bool)
var calls []cppDeferredCall
parser.EachMatch(e.qAll, root, src, func(m parser.QueryResult) {
switch {
case m.Captures["ns.def"] != nil:
e.emitNamespace(m, filePath, fileID, result)
case m.Captures["class.def"] != nil:
e.emitClass(m, filePath, fileID, src, result, seen)
case m.Captures["struct.def"] != nil:
e.emitStruct(m, filePath, fileID, src, result, seen)
case m.Captures["enum.def"] != nil:
e.emitEnum(m, filePath, fileID, result, seen)
case m.Captures["func.def"] != nil:
e.emitFunction(m, filePath, fileID, src, result, seen)
case m.Captures["include.def"] != nil:
e.emitInclude(m, filePath, fileID, result)
case m.Captures["macro.def"] != nil:
emitCMacro(m.Captures["macro.def"].Node, false, filePath, fileID, "cpp", src, result, seen)
case m.Captures["macrofn.def"] != nil:
emitCMacro(m.Captures["macrofn.def"].Node, true, filePath, fileID, "cpp", src, result, seen)
case m.Captures["callm.expr"] != nil:
expr := m.Captures["callm.expr"]
calls = append(calls, cppDeferredCall{
name: m.Captures["callm.method"].Text,
line: expr.StartLine + 1,
isMember: true,
receiver: cppCallReceiverText(expr.Node, src),
argTypes: extractCppCallArgTypes(expr.Node, src),
})
case m.Captures["call.expr"] != nil:
expr := m.Captures["call.expr"]
calls = append(calls, cppDeferredCall{
name: m.Captures["call.name"].Text,
line: expr.StartLine + 1,
argTypes: extractCppCallArgTypes(expr.Node, src),
})
}
})
// Resolve call edges against funcRanges.
funcRanges := buildFuncRanges(result)
// Emit type-use edges (EdgeTypedAs) for declaration positions the
// per-match pass leaves edge-less: local variable declarations,
// function parameters, and return types — each attributed to the
// enclosing function/method via funcRanges. Member/field type-uses
// are attributed to the owning class/struct node during the body
// walk above, so they're already in result.Edges.
collectCppTypeUseEdges(root, funcRanges, filePath, src, result)
// Emit the remaining reference forms a type can appear in beyond a
// declaration position: construction (new / stack), base-class
// inheritance, casts, and Capitalized scope/static access. These are
// EdgeInstantiates (construction) and EdgeReferences with a
// ref_context subkind (inherit / cast / static_access).
emitCppReferenceForms(root, src, filePath, fileID, funcRanges, result)
for _, c := range calls {
callerID := findEnclosingFunc(funcRanges, c.line)
if callerID == "" {
continue
}
edge := &graph.Edge{
Kind: graph.EdgeCalls, FilePath: filePath, Line: c.line,
From: callerID,
}
if c.isMember {
edge.To = "unresolved::*." + c.name
} else {
edge.To = "unresolved::" + c.name
}
if len(c.argTypes) > 0 {
edge.Meta = map[string]any{
"scope_arg_types": strings.Join(c.argTypes, ","),
}
}
if c.isMember && c.receiver != "" {
stampFactoryChainReceiver(edge, c.receiver, resolveChainType(c.receiver, nil, result))
}
result.Edges = append(result.Edges, edge)
}
captureCFnPointerDispatch(result, root, filePath, src)
captureFnValueCandidates(result, root, filePath, src)
return result, nil
}
// --- Per-match emit helpers -----------------------------------------
func (e *CppExtractor) emitNamespace(m parser.QueryResult, filePath, fileID string, result *parser.ExtractionResult) {
name := m.Captures["ns.name"].Text
def := m.Captures["ns.def"]
id := filePath + "::" + name
result.Nodes = append(result.Nodes, &graph.Node{
ID: id, Kind: graph.KindPackage, Name: name,
FilePath: filePath, StartLine: def.StartLine + 1, EndLine: def.EndLine + 1,
Language: "cpp",
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: def.StartLine + 1,
})
}
// emitClass emits the class node and walks its body inline for methods.
// The inline body walk replaces legacy extractClassMethods and catches
// declarators the outer function_definition query misses
// (field_identifier, destructor_name, qualified_identifier).
func (e *CppExtractor) emitClass(m parser.QueryResult, filePath, fileID string, src []byte, result *parser.ExtractionResult, seen map[string]bool) {
className := m.Captures["class.name"].Text
def := m.Captures["class.def"]
classID := filePath + "::" + className
if seen[classID] {
return
}
seen[classID] = true
meta := map[string]any{"type_flavor": "class"}
if ns := enclosingCppNamespace(def.Node, src); ns != "" {
meta["scope_ns"] = ns
}
if parent := extractCppParentClass(def.Node, src); parent != "" {
meta["scope_parent"] = parent
}
result.Nodes = append(result.Nodes, &graph.Node{
ID: classID, Kind: graph.KindType, Name: className,
FilePath: filePath, StartLine: def.StartLine + 1, EndLine: def.EndLine + 1,
Language: "cpp", Meta: meta,
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileID, To: classID, Kind: graph.EdgeDefines, FilePath: filePath, Line: def.StartLine + 1,
})
e.walkClassBody(def.Node, src, filePath, fileID, className, classID, seen, result)
}
func (e *CppExtractor) walkClassBody(classNode *sitter.Node, src []byte, filePath, fileID, className, classID string, seen map[string]bool, result *parser.ExtractionResult) {
var body *sitter.Node
for i, _nc := 0, int(classNode.NamedChildCount()); i < _nc; i++ {
child := classNode.NamedChild(i)
if child.Type() == "field_declaration_list" {
body = child
break
}
}
if body == nil {
return
}
typeSeen := make(map[string]bool)
for i, _nc := 0, int(body.NamedChildCount()); i < _nc; i++ {
child := body.NamedChild(i)
switch child.Type() {
case "access_specifier":
continue
case "function_definition":
e.addMethodFromNode(child, src, filePath, fileID, className, classID, seen, result)
case "field_declaration":
// Member/field type-use: a `Foo bar;` member references Foo.
// Attribute to the owning class node (the extractor doesn't
// materialise a per-field node). Smart-pointer / container
// fields unwrap to the inner type via the canonicaliser.
line := int(child.StartPoint().Row) + 1
if tn := child.ChildByFieldName("type"); tn != nil {
emitCppTypeUseEdges(classID, tn.Content(src), filePath, line, result, typeSeen)
}
case "declaration_list":
for j, _nc := 0, int(child.NamedChildCount()); j < _nc; j++ {
gc := child.NamedChild(j)
if gc.Type() == "function_definition" {
e.addMethodFromNode(gc, src, filePath, fileID, className, classID, seen, result)
}
}
}
}
}
func (e *CppExtractor) addMethodFromNode(funcNode *sitter.Node, src []byte, filePath, fileID, className, classID string, seen map[string]bool, result *parser.ExtractionResult) {
methodName := extractFuncName(funcNode, src)
if methodName == "" {
return
}
startLine := int(funcNode.StartPoint().Row) + 1
endLine := int(funcNode.EndPoint().Row) + 1
id := filePath + "::" + className + "." + methodName
if seen[id] {
id = filePath + "::" + className + "." + methodName + "_L" + fmt.Sprint(startLine)
}
if seen[id] {
return
}
seen[id] = true
// Mark line so the function_definition dispatcher skips this.
seen[filePath+"::_method_L"+fmt.Sprint(startLine)] = true
meta := map[string]any{"receiver": className, "scope_class": className}
if ns := enclosingCppNamespace(funcNode, src); ns != "" {
meta["scope_ns"] = ns
}
if rt := cppReturnType(funcNode, src); rt != "" {
meta["return_type"] = rt
}
stampCppSignature(meta, funcNode, src)
result.Nodes = append(result.Nodes, &graph.Node{
ID: id, Kind: graph.KindMethod, Name: methodName,
FilePath: filePath, StartLine: startLine, EndLine: endLine,
Language: "cpp",
Meta: meta,
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: startLine,
})
result.Edges = append(result.Edges, &graph.Edge{
From: id, To: classID, Kind: graph.EdgeMemberOf, FilePath: filePath, Line: startLine,
})
}
// cppReturnType returns the base return type of a C++ function/method node (its
// `type` field), stripping reference/pointer/const/template decoration to the
// bare type name — the seed for chained-factory receiver inference
// (`Foo::make().x()`).
func cppReturnType(node *sitter.Node, src []byte) string {
t := node.ChildByFieldName("type")
if t == nil {
return ""
}
rt := strings.TrimSpace(t.Content(src))
// Unwrap a smart-pointer / optional return (`unique_ptr<Widget>` → Widget)
// so a chained factory call (`make_widget()->draw()`) infers the pointee as
// the receiver, not the wrapper.
rt = graph.UnwrapCppSmartPointer(rt)
rt = strings.TrimPrefix(rt, "const ")
rt = strings.TrimRight(rt, " &*")
if i := strings.IndexByte(rt, '<'); i >= 0 {
rt = strings.TrimSpace(rt[:i])
}
return rt
}
func (e *CppExtractor) emitStruct(m parser.QueryResult, filePath, fileID string, src []byte, result *parser.ExtractionResult, seen map[string]bool) {
name := m.Captures["struct.name"].Text
def := m.Captures["struct.def"]
id := filePath + "::" + name
if seen[id] {
return
}
seen[id] = true
result.Nodes = append(result.Nodes, &graph.Node{
ID: id, Kind: graph.KindType, Name: name,
FilePath: filePath, StartLine: def.StartLine + 1, EndLine: def.EndLine + 1,
Language: "cpp", Meta: map[string]any{"type_flavor": "struct"},
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: def.StartLine + 1,
})
e.emitCppStructFieldTypeUse(def.Node, id, filePath, src, result)
}
// emitCppStructFieldTypeUse walks a struct_specifier (or class_specifier)
// body's direct field_declaration members and emits EdgeTypedAs from the
// owning type node to each member's referenced type. Structs don't get
// the inline method/field walk classes do, so this is the field-position
// type-use pass for them. Methods nested in a struct are handled
// elsewhere; only data members carry a `type` field here.
func (e *CppExtractor) emitCppStructFieldTypeUse(structNode *sitter.Node, ownerID, filePath string, src []byte, result *parser.ExtractionResult) {
if structNode == nil {
return
}
var body *sitter.Node
for i, _nc := 0, int(structNode.NamedChildCount()); i < _nc; i++ {
child := structNode.NamedChild(i)
if child.Type() == "field_declaration_list" {
body = child
break
}
}
if body == nil {
return
}
typeSeen := make(map[string]bool)
for i, _nc := 0, int(body.NamedChildCount()); i < _nc; i++ {
child := body.NamedChild(i)
if child.Type() != "field_declaration" {
continue
}
line := int(child.StartPoint().Row) + 1
if tn := child.ChildByFieldName("type"); tn != nil {
emitCppTypeUseEdges(ownerID, tn.Content(src), filePath, line, result, typeSeen)
}
}
}
func (e *CppExtractor) emitEnum(m parser.QueryResult, filePath, fileID string, result *parser.ExtractionResult, seen map[string]bool) {
name := m.Captures["enum.name"].Text
def := m.Captures["enum.def"]
id := filePath + "::" + name
if seen[id] {
return
}
seen[id] = true
result.Nodes = append(result.Nodes, &graph.Node{
ID: id, Kind: graph.KindType, Name: name,
FilePath: filePath, StartLine: def.StartLine + 1, EndLine: def.EndLine + 1,
Language: "cpp", Meta: map[string]any{"type_flavor": "enum"},
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: def.StartLine + 1,
})
}
// emitFunction emits a free function. When the same line was already
// claimed by the class-body walk (seen "_method_L<line>"), this is a
// class method with a bare identifier declarator that was emitted
// through addMethodFromNode — skip the duplicate.
func (e *CppExtractor) emitFunction(m parser.QueryResult, filePath, fileID string, src []byte, result *parser.ExtractionResult, seen map[string]bool) {
name := m.Captures["func.name"].Text
def := m.Captures["func.def"]
startLine := def.StartLine + 1
lineKey := filePath + "::_method_L" + fmt.Sprint(startLine)
if seen[lineKey] {
return
}
id := filePath + "::" + name
if seen[id] {
id = filePath + "::" + name + "_L" + fmt.Sprint(startLine)
}
if seen[id] {
return
}
seen[id] = true
meta := map[string]any{}
if ns := enclosingCppNamespace(def.Node, src); ns != "" {
meta["scope_ns"] = ns
}
// Free-function return type (smart-pointer-unwrapped) seeds chained-factory
// receiver inference for a bare factory call (`make_widget()->draw()`), the
// same way addMethodFromNode seeds `Foo::make().x()`.
if rt := cppReturnType(def.Node, src); rt != "" {
meta["return_type"] = rt
}
stampCppSignature(meta, def.Node, src)
result.Nodes = append(result.Nodes, &graph.Node{
ID: id, Kind: graph.KindFunction, Name: name,
FilePath: filePath, StartLine: startLine, EndLine: def.EndLine + 1,
Language: "cpp", Meta: meta,
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: startLine,
})
}
func (e *CppExtractor) emitInclude(m parser.QueryResult, filePath, fileID string, result *parser.ExtractionResult) {
pathCap := m.Captures["include.path"]
raw := strings.TrimSpace(pathCap.Text)
kind := "system"
if strings.HasPrefix(raw, `"`) {
kind = "quoted"
}
includePath := strings.Trim(raw, `"<>`)
result.Edges = append(result.Edges, &graph.Edge{
From: fileID,
To: "unresolved::import::" + includePath,
Kind: graph.EdgeImports,
FilePath: filePath,
Line: pathCap.StartLine + 1,
Meta: map[string]any{"include_kind": kind},
})
}
// --- Helpers --------------------------------------------------------
// extractFuncName walks a function_definition node to find the function name.
// It handles both `identifier` (free functions) and `field_identifier` (methods),
// and peels pointer / reference / parenthesized declarator wrappers so a
// pointer-return method (`robj *Cls::bar() {…}`, `Widget &make() {…}`) is not
// dropped — the function_declarator nests one or two levels below the outer
// declarator in those signatures.
func extractFuncName(funcNode *sitter.Node, src []byte) string {
fd := cppFunctionDeclarator(funcNode.ChildByFieldName("declarator"))
if fd == nil {
return ""
}
for j, _nc := 0, int(fd.NamedChildCount()); j < _nc; j++ {
gc := fd.NamedChild(j)
switch gc.Type() {
case "identifier", "field_identifier", "destructor_name", "operator_name":
return gc.Content(src)
case "qualified_identifier":
return lastIdentifier(gc, src)
}
}
return ""
}
// cppFunctionDeclarator descends a declarator through pointer / reference /
// parenthesized wrappers to the function_declarator that carries the name, or
// nil when the declarator does not resolve to a function. Bounds the walk so a
// pathological tree cannot loop.
func cppFunctionDeclarator(decl *sitter.Node) *sitter.Node {
for i := 0; decl != nil && i < 8; i++ {
switch decl.Type() {
case "function_declarator":
return decl
case "pointer_declarator", "reference_declarator", "parenthesized_declarator":
decl = cppInnerDeclarator(decl)
default:
return nil
}
}
return nil
}
// cppInnerDeclarator returns the declarator nested inside a wrapper declarator:
// the `declarator` field when the grammar names it (pointer_declarator), else
// the first declarator-shaped named child (reference_declarator and
// parenthesized_declarator carry it positionally).
func cppInnerDeclarator(decl *sitter.Node) *sitter.Node {
if inner := decl.ChildByFieldName("declarator"); inner != nil {
return inner
}
for i, _nc := 0, int(decl.NamedChildCount()); i < _nc; i++ {
c := decl.NamedChild(i)
switch c.Type() {
case "function_declarator", "pointer_declarator", "reference_declarator",
"parenthesized_declarator", "identifier", "field_identifier",
"qualified_identifier", "destructor_name", "operator_name":
return c
}
}
return nil
}
// lastIdentifier extracts the last identifier from a qualified_identifier.
func lastIdentifier(node *sitter.Node, src []byte) string {
name := ""
for i, _nc := 0, int(node.NamedChildCount()); i < _nc; i++ {
child := node.NamedChild(i)
switch child.Type() {
case "identifier", "field_identifier", "destructor_name":
name = child.Content(src)
}
}
return name
}
// enclosingCppNamespace walks node up through the tree-sitter AST
// looking for namespace_definition ancestors and concatenates their
// names with "::" (so `namespace a { namespace b { void foo() {} } }`
// produces "a::b"). Anonymous namespaces are skipped — a function
// inside one still belongs to the surrounding namespace for ADL.
//
// Stamped onto every function / method / type node so the resolver's
// scope-based static resolver can prefer same-namespace candidates
// before falling back to directory-locality.
func enclosingCppNamespace(node *sitter.Node, src []byte) string {
if node == nil {
return ""
}
var parts []string
for p := node.Parent(); p != nil; p = p.Parent() {
if p.Type() != "namespace_definition" {
continue
}
nameNode := p.ChildByFieldName("name")
if nameNode == nil {
continue
}
name := strings.TrimSpace(nameNode.Content(src))
if name == "" {
continue
}
parts = append([]string{name}, parts...)
}
if len(parts) == 0 {
return ""
}
return strings.Join(parts, "::")
}
// extractCppParentClass returns the name of the direct base class for
// a C++ class_specifier, or "" if the class has no base. Used by the
// scope-based static resolver to walk the inheritance chain when
// resolving `super`-style calls (C++ doesn't have a literal `super`
// keyword, but Base::method() qualifications follow the same chain).
func extractCppParentClass(classNode *sitter.Node, src []byte) string {
if classNode == nil {
return ""
}
for i, _nc := 0, int(classNode.NamedChildCount()); i < _nc; i++ {
child := classNode.NamedChild(i)
if child.Type() != "base_class_clause" {
continue
}
for j, _nc := 0, int(child.NamedChildCount()); j < _nc; j++ {
sub := child.NamedChild(j)
switch sub.Type() {
case "type_identifier", "qualified_identifier":
return strings.TrimSpace(sub.Content(src))
}
}
}
return ""
}
// extractCppCallArgTypes returns the type-name hints harvested from a
// C++ call_expression's argument list, used to seed Argument-Dependent
// Lookup. We restrict the harvest to the cases where the argument
// type is structurally unambiguous from the call site alone:
//
// - `new Type(...)` → "Type"
// - `Type{...}` → "Type" (compound literal / temporary)
// - `Type(arg)` → "Type" (functional cast / explicit ctor)
//
// Anything else (bare variables, method-chain returns, expressions)
// is skipped — ADL is best-effort here, and a partial type list is
// strictly better than guessing. The resolver treats an empty hint
// set as "no ADL evidence" and falls through to the regular cascade.
func extractCppCallArgTypes(callNode *sitter.Node, src []byte) []string {
if callNode == nil {
return nil
}
args := callNode.ChildByFieldName("arguments")
if args == nil {
return nil
}
var out []string
for i, _nc := 0, int(args.NamedChildCount()); i < _nc; i++ {
arg := args.NamedChild(i)
typeName := cppArgTypeHint(arg, src)
if typeName == "" {
// Positional placeholder so the overload ranker keeps argument
// alignment (an unknown arg is compatible with any param). The ADL
// namespace pass ignores "?" (it yields no namespace).
typeName = "?"
}
out = append(out, typeName)
}
return out
}
func cppArgTypeHint(arg *sitter.Node, src []byte) string {
if arg == nil {
return ""
}
switch arg.Type() {
case "number_literal":
if strings.ContainsAny(arg.Content(src), ".eE") && !strings.HasPrefix(arg.Content(src), "0x") {
return "double"
}
return "int"
case "string_literal", "raw_string_literal", "concatenated_string":
return "string"
case "char_literal":
return "char"
case "true", "false":
return "bool"
case "null", "nullptr":
return "null"
case "new_expression":
if t := arg.ChildByFieldName("type"); t != nil {
return strings.TrimSpace(t.Content(src))
}
case "compound_literal_expression":
if t := arg.ChildByFieldName("type"); t != nil {
return strings.TrimSpace(t.Content(src))
}
case "call_expression":
// Functional-cast `Type(arg)`: the function position is a
// type_identifier or qualified_identifier whose text is the
// type itself. Distinguishes from regular method calls by
// checking that the function position resolves to a type
// name (proper-cased, single-segment) — heuristic but safe
// because the resolver treats ADL hints as evidence, not
// truth.
if f := arg.ChildByFieldName("function"); f != nil {
switch f.Type() {
case "type_identifier", "qualified_identifier":
return strings.TrimSpace(f.Content(src))
}
}
}
return ""
}
// cppCallReceiverText returns the receiver expression text of a member call
// `recv.method(...)` / `recv->method(...)` -- the object of the call's
// field_expression -- so a factory chain (`make().with().build()`) can be
// typed by resolveChainType.
func cppCallReceiverText(callNode *sitter.Node, src []byte) string {
if callNode == nil {
return ""
}
fn := callNode.ChildByFieldName("function")
if fn == nil || fn.Type() != "field_expression" {
return ""
}
obj := fn.ChildByFieldName("argument")
if obj == nil && fn.NamedChildCount() > 0 {
obj = fn.NamedChild(0)
}
if obj == nil {
return ""
}
return strings.TrimSpace(obj.Content(src))
}