mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 22:02:49 +08:00
fix(c,cpp): a pointer, array or function member is not a supertype (#2394)
extractInheritance carried Go's struct-embedding check (a field_declaration with no direct field_identifier names an embedded type) into every grammar with a field_declaration_list. tree-sitter-c/cpp, and Objective-C's C structs, nest a member's field_identifier inside its pointer/array/ function/reference declarator, so `SharedState* shared;`, `jv elements[];` and `virtual Status Put(...) = 0;` all became `extends` edges to the member's type: false supertypes in the type hierarchy, blast radius and codegraph_explore, and false cpp-override call edges built on them (google/leveldb: 246 of 302 extends edges). Gate the branch to Go in the TS extractor, drop the mirrored arm from the C/C++ kernel port (and the "kept for parity" quirk note), and drop the inert copy from the Rust-language port. C++ base_class_clause, Objective-C superclasses and Go embedding are unchanged. Validated on leveldb, jq, llm.c and redis: every removed extends edge is a member site, no base-clause edge is lost; gin, SDWebImage and ripgrep dumps are byte-identical; kernel/wasm parity sweeps show 0 diffs. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In Dart, a call written through an import prefix, like `http.get(…)` after `import 'package:http/http.dart' as http;`, or `utils.Listener()` and `kit.Box.create()` through a prefix of your own, now links to what the library imported with that prefix exports, following its `export` chains, `show` and `hide`, and to nothing when that library is a package outside your project. Before, the prefix was taken for a variable, so `http.get(…)` could link to an unrelated `Http` class's `get` method, and calls made through a prefix into your own libraries were missing from `callers` and impact. Re-index Dart projects after upgrading.
|
||||
- `codegraph sync` and the file watcher now link an import to the file it names when that file is added after the importing file was indexed, or comes back after being deleted: `import 'package:app/b.dart';` in Dart, `import './side'` in JavaScript or TypeScript, `#include "net/socket.h"` in C and C++, `require 'inc/db.php'` in PHP, `import pkg.mod` in Python, a Lua `require`, or a C# `using` of a namespace declared later. Before, the import stayed unlinked until the importing file itself changed or the project was re-indexed, so `codegraph affected` and the files listed as using the new file missed it. Re-index after upgrading.
|
||||
- JSON files in a `templates` or `sections` folder are now read as Shopify Liquid only inside a Shopify theme, where the folder that contains `templates` or `sections` also has `layout/theme.liquid` or `config/settings_schema.json`. Before, all such JSON was, so a .NET project template's `template.json`, a schematic or a CMS's content showed up in the index as an empty Liquid file, and one that lists `sections` could be linked to an unrelated Liquid file of the same name. Shopify themes, including one kept in a subfolder of a repository, are indexed as before, and the next sync removes JSON that an earlier version indexed this way.
|
||||
- In C, C++ and Objective-C, a struct or class member declared as a pointer, reference, array or function, like `SharedState* shared;`, `jv elements[];` or `virtual Status Put(…) = 0;`, no longer makes the struct look like it inherits from the member's type. Before, `codegraph_explore` and impact listed those types as base classes, every struct holding a pointer to a type as one of its subclasses, and a method of that struct as an override of the type's method with the same name. Real base classes like `class Derived : public Base`, Objective-C superclasses and Go's embedded fields are unchanged. Re-index C, C++ and Objective-C projects after upgrading.
|
||||
|
||||
## [1.6.2] - 2026-10-03
|
||||
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* A C or C++ struct/class member whose declarator is a pointer, reference,
|
||||
* array or function was recorded as an `extends` edge to the member's type:
|
||||
*
|
||||
* struct ThreadState { SharedState* shared; }; // ThreadState extends SharedState
|
||||
* typedef struct { int length; jv elements[]; } jvp_array; // jvp_array extends jv
|
||||
* class DB { virtual Status Put(const Slice& k) = 0; }; // DB extends Status
|
||||
*
|
||||
* Both extractors carried Go's struct-embedding check (`type DB struct { *Head }`
|
||||
* — a `field_declaration` with no `field_identifier` names an embedded type)
|
||||
* and ran it for every grammar with a `field_declaration_list`. tree-sitter-c
|
||||
* and -cpp, and the C structs of Objective-C, nest the member's name inside
|
||||
* its declarator, so every such member looked embedded and its type showed up
|
||||
* as a supertype: in the type hierarchy, the "extends" chips, blast radius
|
||||
* and codegraph_explore. google/leveldb had 246 of its 302 `extends` edges
|
||||
* from members.
|
||||
*
|
||||
* Embedding is Go's alone, so the check now runs for Go only. C++ base
|
||||
* classes and Objective-C superclasses come from their own clauses and are
|
||||
* untouched. Runs against the native kernel (when built) and the wasm
|
||||
* extractor, which must agree.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import CodeGraph from '../src/index';
|
||||
import { extractFromSource } from '../src/extraction';
|
||||
import { initGrammars, loadGrammarsForLanguages } from '../src/extraction/grammars';
|
||||
import { tryKernelExtract, resetKernelForTests } from '../src/extraction/kernel';
|
||||
import { buildTypeHierarchy } from '../src/graph/type-hierarchy';
|
||||
import type { ExtractionResult, Language, Node } from '../src/types';
|
||||
|
||||
const KERNEL_PATH = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'codegraph-kernel',
|
||||
'prebuilds',
|
||||
`${process.platform}-${process.arch}`,
|
||||
'codegraph-kernel.node'
|
||||
);
|
||||
const kernelAvailable = fs.existsSync(KERNEL_PATH) || process.env.CODEGRAPH_KERNEL_EXPECT === '1';
|
||||
|
||||
/** jqlang/jq's shapes: flexible and fixed arrays, pointers, function pointers in a union. */
|
||||
const C_SOURCE = `typedef struct { int refcnt; } jv;
|
||||
typedef struct jq_state jq_state;
|
||||
typedef struct { int length; jv elements[]; } jvp_array;
|
||||
struct jv_parser {
|
||||
jv* stack;
|
||||
jv next[2];
|
||||
jv value;
|
||||
int stackpos;
|
||||
};
|
||||
union cfunction {
|
||||
jv (*a1)(jq_state *, jv);
|
||||
jv (*a2)(jq_state *, jv, jv);
|
||||
};
|
||||
struct node {
|
||||
struct node *next;
|
||||
const jv *const *table;
|
||||
};
|
||||
`;
|
||||
|
||||
/** google/leveldb's shapes, plus base classes that must survive, one on a later line. */
|
||||
const CPP_SOURCE = `class Cache {};
|
||||
class DB {};
|
||||
class Status {};
|
||||
class Slice {};
|
||||
struct SharedState { int total; };
|
||||
struct ThreadState {
|
||||
SharedState* shared;
|
||||
const Cache& cache;
|
||||
Cache shards[4];
|
||||
Status (*hook)(int);
|
||||
};
|
||||
class Benchmark {
|
||||
public:
|
||||
Status Open(int k);
|
||||
virtual Status Put(const Slice& key) = 0;
|
||||
Slice operator[](int n) const;
|
||||
private:
|
||||
DB* db_;
|
||||
Cache* cache_;
|
||||
};
|
||||
class Base {};
|
||||
namespace ns { template <typename T> class Tpl {}; }
|
||||
class Derived : public Base, private ns::Tpl<int> {
|
||||
Base* parent;
|
||||
};
|
||||
struct Plain : Base { Base* next; };
|
||||
class Wrapped
|
||||
: public Base {
|
||||
Base* other;
|
||||
};
|
||||
`;
|
||||
|
||||
/** Go embedding is real inheritance-by-composition and keeps its edges. */
|
||||
const GO_SOURCE = `package store
|
||||
|
||||
type Engine struct{}
|
||||
|
||||
type Reader interface{ Read() }
|
||||
|
||||
type Store struct {
|
||||
*Engine
|
||||
Reader
|
||||
name string
|
||||
engine *Engine
|
||||
}
|
||||
`;
|
||||
|
||||
/** Objective-C reuses the C struct grammar; its superclass comes from @interface. */
|
||||
const OBJC_SOURCE = `#import <Foundation/Foundation.h>
|
||||
typedef struct { int width; } Size;
|
||||
struct Frame {
|
||||
Size *size;
|
||||
Size corners[4];
|
||||
Size origin;
|
||||
};
|
||||
@interface Image : NSObject <NSCopying> {
|
||||
Size *cached;
|
||||
}
|
||||
@end
|
||||
@implementation Image
|
||||
@end
|
||||
`;
|
||||
|
||||
const ENV_KEYS = ['CODEGRAPH_KERNEL', 'CODEGRAPH_KERNEL_LANGS'] as const;
|
||||
|
||||
/** `<source> <kind> <name>` for every extends/implements ref, sorted. */
|
||||
function supertypeRefs(result: ExtractionResult): string[] {
|
||||
const byId = new Map(result.nodes.map((n) => [n.id, n]));
|
||||
return result.unresolvedReferences
|
||||
.filter((r) => r.referenceKind === 'extends' || r.referenceKind === 'implements')
|
||||
.map((r) => `${byId.get(r.fromNodeId)?.name ?? r.fromNodeId} ${r.referenceKind} ${r.referenceName}`)
|
||||
.sort();
|
||||
}
|
||||
|
||||
describe('a C/C++ member is not a supertype of its struct', () => {
|
||||
let savedEnv: Record<string, string | undefined> = {};
|
||||
|
||||
beforeAll(async () => {
|
||||
await initGrammars();
|
||||
await loadGrammarsForLanguages(['c', 'cpp', 'go', 'objc']);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
savedEnv = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
|
||||
resetKernelForTests();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const k of ENV_KEYS) {
|
||||
if (savedEnv[k] === undefined) delete process.env[k];
|
||||
else process.env[k] = savedEnv[k];
|
||||
}
|
||||
resetKernelForTests();
|
||||
});
|
||||
|
||||
function extract(backend: 'kernel' | 'wasm', file: string, source: string, language: Language): ExtractionResult {
|
||||
if (backend === 'wasm') {
|
||||
process.env.CODEGRAPH_KERNEL = '0';
|
||||
return extractFromSource(file, source, language);
|
||||
}
|
||||
delete process.env.CODEGRAPH_KERNEL;
|
||||
process.env.CODEGRAPH_KERNEL_LANGS = 'all';
|
||||
const result = tryKernelExtract(file, source, language);
|
||||
expect(result, `kernel extraction of ${file}`).not.toBeNull();
|
||||
return result!;
|
||||
}
|
||||
|
||||
const backends = kernelAvailable ? (['kernel', 'wasm'] as const) : (['wasm'] as const);
|
||||
|
||||
for (const crlf of [false, true]) {
|
||||
const eol = (s: string) => (crlf ? s.replace(/\n/g, '\r\n') : s);
|
||||
const label = crlf ? ' (CRLF)' : '';
|
||||
|
||||
it.each(backends)(`C pointer, array and function-pointer members: %s${label}`, (backend) => {
|
||||
const result = extract(backend, 'src/jv.c', eol(C_SOURCE), 'c');
|
||||
expect(result.nodes.map((n) => n.name)).toEqual(
|
||||
expect.arrayContaining(['jvp_array', 'jv_parser', 'cfunction', 'node'])
|
||||
);
|
||||
expect(supertypeRefs(result)).toEqual([]);
|
||||
});
|
||||
|
||||
it.each(backends)(`C++ members, methods and operators; base classes kept: %s${label}`, (backend) => {
|
||||
const result = extract(backend, 'benchmarks/db_bench.cc', eol(CPP_SOURCE), 'cpp');
|
||||
expect(supertypeRefs(result)).toEqual([
|
||||
'Derived extends Base',
|
||||
'Derived extends ns::Tpl',
|
||||
'Plain extends Base',
|
||||
'Wrapped extends Base',
|
||||
]);
|
||||
});
|
||||
|
||||
it.each(backends)(`Go embedded fields still extend: %s${label}`, (backend) => {
|
||||
const result = extract(backend, 'store/store.go', eol(GO_SOURCE), 'go');
|
||||
expect(supertypeRefs(result)).toEqual(['Store extends Engine', 'Store extends Reader']);
|
||||
});
|
||||
|
||||
// Objective-C has no kernel walker; the wasm extractor is its only path.
|
||||
it(`Objective-C C-struct members; @interface superclass kept: wasm${label}`, () => {
|
||||
const result = extractFromSource('Image.m', eol(OBJC_SOURCE), 'objc');
|
||||
expect(supertypeRefs(result)).toEqual(['Image extends NSObject', 'Image implements NSCopying']);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('the type hierarchy of an indexed C/C++ project', () => {
|
||||
let root = '';
|
||||
let cg: CodeGraph | undefined;
|
||||
|
||||
afterEach(() => {
|
||||
cg?.destroy();
|
||||
cg = undefined;
|
||||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||||
root = '';
|
||||
});
|
||||
|
||||
it('holds only declared bases and Go embeddings', async () => {
|
||||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-member-supertypes-'));
|
||||
const files: Record<string, string> = {
|
||||
'include/db.h': 'class Cache {};\nclass DB {\n public:\n virtual ~DB();\n};\n',
|
||||
'benchmarks/db_bench.cc': [
|
||||
'#include "db.h"',
|
||||
'struct SharedState { int total; };',
|
||||
'struct ThreadState {',
|
||||
' SharedState* shared;',
|
||||
'};',
|
||||
'class Benchmark {',
|
||||
' Cache* cache_;',
|
||||
' DB* db_;',
|
||||
'};',
|
||||
'class MemDB : public DB {',
|
||||
' Cache* cache_;',
|
||||
'};',
|
||||
'',
|
||||
].join('\n'),
|
||||
'src/jv.c': C_SOURCE,
|
||||
'store/store.go': GO_SOURCE,
|
||||
};
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, rel), content);
|
||||
}
|
||||
cg = await CodeGraph.init(root, { index: true });
|
||||
const graph = cg;
|
||||
|
||||
const one = (name: string): Node => {
|
||||
const found = graph.getNodesByName(name).filter((n) => n.kind !== 'file' && n.kind !== 'import');
|
||||
expect(found, name).toHaveLength(1);
|
||||
return found[0]!;
|
||||
};
|
||||
|
||||
const ids = ['ThreadState', 'Benchmark', 'MemDB', 'jvp_array', 'jv_parser', 'cfunction', 'node', 'Store'].map(
|
||||
(name) => one(name).id
|
||||
);
|
||||
const supertypes = graph
|
||||
.getOutgoingEdgesFrom(ids, ['extends', 'implements'])
|
||||
.map((e) => `${graph.getNode(e.source)?.name} ${e.kind} ${graph.getNode(e.target)?.name}`)
|
||||
.sort();
|
||||
// An embedded interface resolves as `implements`.
|
||||
expect(supertypes).toEqual(['MemDB extends DB', 'Store extends Engine', 'Store implements Reader']);
|
||||
|
||||
// What the viewer's Type hierarchy and the "extends" chips are built from.
|
||||
expect(buildTypeHierarchy(graph, one('ThreadState'))).toBeNull();
|
||||
expect(buildTypeHierarchy(graph, one('SharedState'))).toBeNull();
|
||||
expect(buildTypeHierarchy(graph, one('jv'))).toBeNull();
|
||||
expect(buildTypeHierarchy(graph, one('DB'))?.descendants.map((d) => d.node.name)).toEqual(['MemDB']);
|
||||
expect(buildTypeHierarchy(graph, one('Cache'))).toBeNull();
|
||||
expect(buildTypeHierarchy(graph, one('Engine'))?.descendants.map((d) => d.node.name)).toEqual(['Store']);
|
||||
}, 60_000);
|
||||
});
|
||||
@@ -33,11 +33,6 @@
|
||||
//! (loses uninit scalars by design); cpp declarations instead take the TS
|
||||
//! GENERIC fallback (direct identifier children only → `int x;` extracts,
|
||||
//! `int x = 5;` does not — bug-for-bug).
|
||||
//! - inheritance quirk: extractInheritance recurses into
|
||||
//! field_declaration_list, where a field_declaration with no DIRECT
|
||||
//! field_identifier child (pointer/array/method members) but a direct
|
||||
//! type_identifier emits an `extends` ref to that type (the Go-embedding
|
||||
//! branch matching c/cpp shapes). Kept: the parity gate pins today's graph.
|
||||
//! - static-member/value-read pass (cpp only): `field_expression` is in
|
||||
//! MEMBER_ACCESS_TYPES (listed for Scala, same node kind in cpp), so
|
||||
//! `Capitalized.member` / `Capitalized->member` VALUE reads emit
|
||||
@@ -1866,8 +1861,10 @@ impl<'t> Walker<'t> {
|
||||
// --- inheritance ---------------------------------------------------------
|
||||
|
||||
/// extractInheritance — the branches whose node kinds occur in the c/cpp
|
||||
/// grammars: base_class_clause (#1043), the field_declaration Go-embedding
|
||||
/// shape, and the field_declaration_list recursion that reaches it.
|
||||
/// grammars: base_class_clause (#1043) and the field_declaration_list
|
||||
/// recursion. A member `field_declaration` is never a supertype: the TS
|
||||
/// Go-embedding check is gated to Go, since c/cpp members nest their
|
||||
/// field_identifier inside a pointer/array/function declarator.
|
||||
fn extract_inheritance(&mut self, node: Node<'t>, class_row: u32) {
|
||||
stack_guard!();
|
||||
let extends_kind = edge_kind_index("extends").unwrap();
|
||||
@@ -1886,20 +1883,6 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
}
|
||||
}
|
||||
"field_declaration" => {
|
||||
let has_field_identifier = (0..child.named_child_count())
|
||||
.filter_map(|j| child.named_child(j))
|
||||
.any(|c| c.kind() == "field_identifier");
|
||||
if !has_field_identifier {
|
||||
let type_id = (0..child.named_child_count())
|
||||
.filter_map(|j| child.named_child(j))
|
||||
.find(|c| c.kind() == "type_identifier");
|
||||
if let Some(type_id) = type_id {
|
||||
let name = self.text(type_id).to_string();
|
||||
self.push_ref_at(class_row, &name, extends_kind, type_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
"field_declaration_list" | "class_heritage" => {
|
||||
self.extract_inheritance(child, class_row);
|
||||
}
|
||||
|
||||
@@ -1061,9 +1061,9 @@ impl<'t> Walker<'t> {
|
||||
|
||||
/// extractInheritance — the rust-reachable cases: trait_bounds
|
||||
/// (supertraits; a scoped `fmt::Debug` bound matches NO case and is
|
||||
/// dropped), the Go embedding check on field_declaration (inert in rust —
|
||||
/// every field has a field_identifier), and the field_declaration_list
|
||||
/// recursion that reaches it.
|
||||
/// dropped) and the field_declaration_list recursion. The TS Go-embedding
|
||||
/// check on field_declaration is gated to Go (and was inert here anyway —
|
||||
/// every rust field has a field_identifier).
|
||||
fn extract_inheritance(&mut self, node: Node<'t>, class_row: u32) {
|
||||
stack_guard!();
|
||||
let extends_kind = edge_kind_index("extends").unwrap();
|
||||
@@ -1102,20 +1102,6 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
}
|
||||
}
|
||||
"field_declaration" => {
|
||||
let has_field_identifier = (0..child.named_child_count())
|
||||
.filter_map(|j| child.named_child(j))
|
||||
.any(|c| c.kind() == "field_identifier");
|
||||
if !has_field_identifier {
|
||||
let type_id = (0..child.named_child_count())
|
||||
.filter_map(|j| child.named_child(j))
|
||||
.find(|c| c.kind() == "type_identifier");
|
||||
if let Some(type_id) = type_id {
|
||||
let name = self.text(type_id).to_string();
|
||||
self.push_ref_at(class_row, &name, extends_kind, type_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
"field_declaration_list" | "class_heritage" => {
|
||||
self.extract_inheritance(child, class_row);
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ function_declarator → null, 12-hop guard).
|
||||
| 4545 | local fn-ptr call fan-out (#932-adjacent) | bare-identifier callee found in cppLocalFnPtrs[callerId] → emit one `calls` ref PER recorded target, suppress the local name |
|
||||
| 5173 | stack construction (#1035) | cpp `declaration` where `type` ∈ {type_identifier, template_type, qualified_identifier} and any init_declarator has `value` ∈ {argument_list, initializer_list} → extractInstantiation |
|
||||
| 5183 | fn-ptr binding recording | inside a body: `declaration>init_declarator` (identifier declarator) or `assignment_expression` (identifier left); value must be pointer_expression whose child(0) is `&`; target ∈ {identifier, template_function, qualified_identifier}, template-stripped; per-callerId map of per-local Sets (branch reassignments accumulate) |
|
||||
| 5408 | base_class_clause → extends (#1043) | per base: type_identifier / qualified_identifier / template_type, stripCppTemplateArgs'd; access-specifier keywords skipped |
|
||||
| 5408 | base_class_clause → extends (#1043) | per base: type_identifier / qualified_identifier / template_type, stripCppTemplateArgs'd; access-specifier keywords skipped. The only c/cpp supertype source: the Go struct-embedding branch (`field_declaration` with no direct `field_identifier`) runs for Go only, so a pointer/array/reference/function member (`Cache* cache_;`) is never an `extends` |
|
||||
| 4740 | static member refs — **cpp only** (c not in STATIC_MEMBER_LANGS, line 345) | `Foo::BAR` value reads → `references` edge; VERIFY the MEMBER_ACCESS_TYPES shapes for qualified_identifier + the call-callee skip during the port |
|
||||
| — | value-reference edges | **c: YES** (VALUE_REF_LANGS line 401), **cpp: NO**. Port the value-ref machinery for C only (crib go.rs / tsjs — shadow prune, scope stack, MAX_VALUE_REF_NODES cap, CODEGRAPH_VALUE_REFS=0 kill) |
|
||||
| — | fn-ref capture (#756) | function-ref.ts:376: `c: cFamilySpec()`, `cpp: cFamilySpec({ addressOfOnly: true })`; note line ~582: `&Cls::m` exemption from the bare-ids-are-free-functions rule — read cFamilySpec fully when porting |
|
||||
|
||||
@@ -317,10 +317,10 @@ trait_item — supertraits `trait Sub: Super + Display`). Per bound child:
|
||||
|
||||
Each yields an `extends` ref from the trait node at the bound's position.
|
||||
Struct/enum extraction also calls extractInheritance; rust struct_item children
|
||||
include `field_declaration_list` → the 5652 recursion descends, but rust
|
||||
`field_declaration` always carries a `field_identifier` name so the Go
|
||||
struct-embedding branch (5496) never fires. Verify with the torture fixture
|
||||
anyway.
|
||||
include `field_declaration_list` → the 5652 recursion descends and finds
|
||||
nothing: the Go struct-embedding branch (5496) runs for Go only (it was inert
|
||||
here anyway — rust `field_declaration` always carries a `field_identifier`
|
||||
name), so rustlang.rs has no `field_declaration` arm.
|
||||
|
||||
### impl Trait for Type — extractRustImplItem (5690)
|
||||
|
||||
|
||||
@@ -7001,8 +7001,12 @@ export class TreeSitterExtractor {
|
||||
}
|
||||
|
||||
// Go struct embedding: field_declaration without field_identifier
|
||||
// e.g. `type DB struct { *Head; Queryable }` — no field name means embedded type
|
||||
if (child.type === 'field_declaration') {
|
||||
// e.g. `type DB struct { *Head; Queryable }` — no field name means embedded type.
|
||||
// Go only: C, C++ and Objective-C members are field_declarations too, but
|
||||
// nest their name inside the declarator (`Cache* cache_;`, `jv elems[];`,
|
||||
// `Status Get(int);`), so this test read every such member's type as a
|
||||
// supertype. Their supertypes come from base_class_clause / superclass.
|
||||
if (this.language === 'go' && child.type === 'field_declaration') {
|
||||
const hasFieldIdentifier = child.namedChildren.some((c: SyntaxNode) => c.type === 'field_identifier');
|
||||
if (!hasFieldIdentifier) {
|
||||
const typeId = child.namedChildren.find((c: SyntaxNode) => c.type === 'type_identifier');
|
||||
|
||||
Reference in New Issue
Block a user