mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-29 07:13:44 +08:00
fix(graph): precise Struct label for Rust/Swift/D/Go structs (Option B)
Struct definitions were labelled 'Class', conflating a distinct kind. Emit the precise 'Struct' label for Rust (struct_item), Swift/D (struct_declaration) and Go (type_spec struct_type); C/C++/Obj-C struct_specifier stays 'Class'. To avoid regressing type resolution, a struct must still behave as a type everywhere a class does. Added a centralized predicate cbm_label_is_type_like() (Class/Struct/Interface/Enum/Type/Trait) and routed every type-like consumer through it: registry seeding (pass_definitions/pass_parallel/pipeline_incremental kept in sync), class-resolution (pass_semantic/pass_parallel resolve_as_class), Go interface satisfaction (collects Struct nodes), the cross-file LSP label map, and the Go/Rust LSP type registrars. Class-only sites (protobuf services, K8s kinds, the C-family LSPs) are left unchanged. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
@@ -598,4 +598,17 @@ void cbm_extract_unified(CBMExtractCtx *ctx);
|
||||
// K8s / Kustomize semantic extractor (called when language is CBM_LANG_K8S or CBM_LANG_KUSTOMIZE).
|
||||
void cbm_extract_k8s(CBMExtractCtx *ctx);
|
||||
|
||||
// --- Label predicates ---
|
||||
|
||||
// True when `label` names a TYPE-LIKE container definition — a node that can own
|
||||
// methods/fields, be a base/embedded type, satisfy/declare an interface, and be a
|
||||
// target of name→type resolution. The canonical set is:
|
||||
// Class, Struct, Interface, Enum, Type, Trait.
|
||||
// Single source of truth for every type-resolution / registry-seeding /
|
||||
// INHERITS·IMPLEMENTS / LSP-type-registrar consumer, so adding a new type-like
|
||||
// label (e.g. "Struct" for Rust/Go/Swift/D structs) updates them all at once
|
||||
// instead of scattering `|| strcmp(label,"Struct")==0` across the tree.
|
||||
// `label` may be NULL (returns false). Defined in helpers.c.
|
||||
bool cbm_label_is_type_like(const char *label);
|
||||
|
||||
#endif // CBM_H
|
||||
|
||||
@@ -3502,8 +3502,8 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
|
||||
// Sway/WGSL: label struct defs as "Struct" and Sway `abi` blocks as
|
||||
// "Interface". Scoped to these grammar-only languages so established
|
||||
// struct-as-"Class" labeling (Rust/C++/Go/Cap'n Proto …) and the
|
||||
// downstream type/IMPLEMENTS resolvers that depend on it are unaffected.
|
||||
// struct-as-"Class" labeling (C++/Cap'n Proto …) and the downstream
|
||||
// type/IMPLEMENTS resolvers that depend on it are unaffected.
|
||||
if (ctx->language == CBM_LANG_SWAY || ctx->language == CBM_LANG_WGSL) {
|
||||
if (strcmp(kind, "struct_item") == 0 || strcmp(kind, "struct_declaration") == 0) {
|
||||
label = "Struct";
|
||||
@@ -3511,6 +3511,20 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
label = "Interface";
|
||||
}
|
||||
}
|
||||
// Rust/Swift/D: a struct is a distinct kind from a class — emit the precise
|
||||
// "Struct" label rather than collapsing it to "Class". Scoped to these three
|
||||
// grammar/LSP languages whose struct node is `struct_item` (Rust) or
|
||||
// `struct_declaration` (Swift, D). C/C++/Obj-C keep `struct_specifier` →
|
||||
// "Class" (a C++ struct is class-like). "Struct" is a type-like container:
|
||||
// every type-resolution / registry / IMPLEMENTS / LSP-registrar consumer
|
||||
// routes through cbm_label_is_type_like(), so a struct still resolves as a
|
||||
// type for its methods, fields, inheritance and impls.
|
||||
if (ctx->language == CBM_LANG_RUST || ctx->language == CBM_LANG_SWIFT ||
|
||||
ctx->language == CBM_LANG_DLANG) {
|
||||
if (strcmp(kind, "struct_item") == 0 || strcmp(kind, "struct_declaration") == 0) {
|
||||
label = "Struct";
|
||||
}
|
||||
}
|
||||
// F#: a `type_definition` that has a primary constructor (`type Foo(...) =`)
|
||||
// or an `inherit` clause is an OOP class, not a plain type alias. Label it
|
||||
// "Class" so it is registered as a resolvable inheritance target (the graph
|
||||
@@ -3525,7 +3539,10 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
}
|
||||
}
|
||||
|
||||
// Go type_spec: check inner type for interface/struct
|
||||
// Go type_spec: check inner type for interface/struct. A Go `type T struct
|
||||
// {...}` is a struct → emit the precise "Struct" label (a type-like container;
|
||||
// its methods/fields/embedding resolve through cbm_label_is_type_like(), and
|
||||
// cbm_pipeline_implements_go() collects Struct nodes too).
|
||||
if (strcmp(kind, "type_spec") == 0) {
|
||||
TSNode type_inner = ts_node_child_by_field_name(node, TS_FIELD("type"));
|
||||
if (!ts_node_is_null(type_inner)) {
|
||||
@@ -3533,7 +3550,7 @@ static void extract_class_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
|
||||
if (strcmp(inner_kind, "interface_type") == 0) {
|
||||
label = "Interface";
|
||||
} else if (strcmp(inner_kind, "struct_type") == 0) {
|
||||
label = "Class";
|
||||
label = "Struct";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +155,18 @@ static const char *puppet_keywords[] = {
|
||||
"and", "or", "in", "node", "class", "define", "inherits", "default",
|
||||
"return", NULL};
|
||||
|
||||
// True when `label` names a type-like container definition (see cbm.h). Single
|
||||
// source of truth for the type-resolution / registry / IMPLEMENTS / LSP-type
|
||||
// consumers — adding a label here updates them all.
|
||||
bool cbm_label_is_type_like(const char *label) {
|
||||
if (!label) {
|
||||
return false;
|
||||
}
|
||||
return strcmp(label, "Class") == 0 || strcmp(label, "Struct") == 0 ||
|
||||
strcmp(label, "Interface") == 0 || strcmp(label, "Enum") == 0 ||
|
||||
strcmp(label, "Type") == 0 || strcmp(label, "Trait") == 0;
|
||||
}
|
||||
|
||||
bool cbm_is_keyword(const char *name, CBMLanguage lang) {
|
||||
if (!name || !name[0]) {
|
||||
return true;
|
||||
|
||||
@@ -1713,9 +1713,10 @@ void cbm_run_go_lsp(CBMArena* arena, CBMFileResult* result,
|
||||
CBMDefinition* d = &result->defs.items[i];
|
||||
if (!d->qualified_name || !d->name) continue;
|
||||
|
||||
// Register Class/Type nodes
|
||||
if (d->label && (strcmp(d->label, "Class") == 0 || strcmp(d->label, "Type") == 0 ||
|
||||
strcmp(d->label, "Interface") == 0)) {
|
||||
// Register every type-like container (Class/Struct/Type/Interface/Enum/
|
||||
// Trait). Struct included so a Go `type T struct {...}` (now labelled
|
||||
// "Struct") is registered as a type and its methods/embedding resolve.
|
||||
if (cbm_label_is_type_like(d->label)) {
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = d->qualified_name;
|
||||
@@ -2534,9 +2535,9 @@ void cbm_run_go_lsp_cross(
|
||||
|
||||
const char* def_mod = d->def_module_qn ? d->def_module_qn : module_qn;
|
||||
|
||||
// Type/Interface/Class
|
||||
if (strcmp(d->label, "Type") == 0 || strcmp(d->label, "Class") == 0 ||
|
||||
strcmp(d->label, "Interface") == 0) {
|
||||
// Every type-like container (Type/Class/Struct/Interface/Enum/Trait).
|
||||
// Struct included so Go structs (now labelled "Struct") register as types.
|
||||
if (cbm_label_is_type_like(d->label)) {
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = d->qualified_name; // borrowed
|
||||
@@ -2787,8 +2788,9 @@ CBMTypeRegistry* cbm_go_build_cross_registry(
|
||||
* fall back to — this registry is project-wide, not per-file. */
|
||||
const char* def_mod = d->def_module_qn ? d->def_module_qn : "";
|
||||
|
||||
if (strcmp(d->label, "Type") == 0 || strcmp(d->label, "Class") == 0 ||
|
||||
strcmp(d->label, "Interface") == 0) {
|
||||
// Every type-like container (Type/Class/Struct/Interface/Enum/Trait).
|
||||
// Struct included so Go structs (now labelled "Struct") register as types.
|
||||
if (cbm_label_is_type_like(d->label)) {
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = d->qualified_name; /* borrowed */
|
||||
|
||||
@@ -4530,8 +4530,10 @@ static void rust_build_registry_from_defs(CBMArena *arena, CBMTypeRegistry *reg,
|
||||
if (!d->qualified_name || !d->name)
|
||||
continue;
|
||||
|
||||
if (d->label && (strcmp(d->label, "Class") == 0 || strcmp(d->label, "Type") == 0 ||
|
||||
strcmp(d->label, "Interface") == 0 || strcmp(d->label, "Trait") == 0)) {
|
||||
// Every type-like container (Class/Struct/Type/Interface/Trait/Enum).
|
||||
// Struct included so a Rust `struct Foo` (now labelled "Struct") registers
|
||||
// as a type and its `impl Foo` methods/fields resolve.
|
||||
if (cbm_label_is_type_like(d->label)) {
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = d->qualified_name;
|
||||
@@ -4839,7 +4841,10 @@ static void rust_build_registry_from_defs(CBMArena *arena, CBMTypeRegistry *reg,
|
||||
CBMDefinition *d = &result->defs.items[i];
|
||||
if (!d->qualified_name || !d->name)
|
||||
continue;
|
||||
if (!d->label || (strcmp(d->label, "Class") != 0 && strcmp(d->label, "Type") != 0))
|
||||
/* `#[derive(...)]` rides on type-like defs — most often a struct or
|
||||
* enum (now labelled "Struct"/"Enum"), also type aliases. Accept the
|
||||
* whole type-like set so a derive on a struct is not dropped. */
|
||||
if (!cbm_label_is_type_like(d->label))
|
||||
continue;
|
||||
if (!d->decorators)
|
||||
continue;
|
||||
@@ -5151,8 +5156,9 @@ void cbm_run_rust_lsp_cross(CBMArena *arena, const char *source, int source_len,
|
||||
continue;
|
||||
const char *def_mod = d->def_module_qn ? d->def_module_qn : module_qn;
|
||||
|
||||
if (strcmp(d->label, "Type") == 0 || strcmp(d->label, "Class") == 0 ||
|
||||
strcmp(d->label, "Interface") == 0 || strcmp(d->label, "Trait") == 0) {
|
||||
// Every type-like container (Type/Class/Struct/Interface/Trait/Enum).
|
||||
// Struct included so Rust structs (now labelled "Struct") register here.
|
||||
if (cbm_label_is_type_like(d->label)) {
|
||||
CBMRegisteredType rt;
|
||||
memset(&rt, 0, sizeof(rt));
|
||||
rt.qualified_name = cbm_arena_strdup(arena, d->qualified_name);
|
||||
|
||||
@@ -105,7 +105,7 @@ static int collect_config_entries(const cbm_gbuf_node_t *const *vars, int var_co
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Collect code nodes (Function/Variable/Class) not from config files. */
|
||||
/* Collect code nodes (Function/Variable/Class/Struct) not from config files. */
|
||||
typedef struct {
|
||||
int64_t node_id;
|
||||
char normalized[CBM_SZ_256];
|
||||
@@ -113,7 +113,9 @@ typedef struct {
|
||||
|
||||
static int collect_code_entries(cbm_gbuf_t *gb, code_entry_t *out, int max_out) {
|
||||
int n = 0;
|
||||
static const char *labels[] = {"Function", "Variable", "Class", NULL};
|
||||
/* "Struct" alongside "Class": a config key may name a Go/Rust/Swift/D struct
|
||||
* type, which is now labelled "Struct" — keep it linkable. */
|
||||
static const char *labels[] = {"Function", "Variable", "Class", "Struct", NULL};
|
||||
|
||||
for (int li = 0; labels[li] && n < max_out; li++) {
|
||||
const cbm_gbuf_node_t **nodes = NULL;
|
||||
|
||||
@@ -295,15 +295,18 @@ static void process_def(cbm_pipeline_ctx_t *ctx, const CBMDefinition *def, const
|
||||
int64_t node_id = cbm_gbuf_upsert_node(
|
||||
ctx->gbuf, def->label ? def->label : "Function", def->name, def->qualified_name,
|
||||
def->file_path ? def->file_path : rel, (int)def->start_line, (int)def->end_line, props);
|
||||
/* Register callable symbols + Interface. Interface must be in the registry
|
||||
* so C#/Java `class Foo : IBar` / `class Foo implements IBar` can resolve
|
||||
* `IBar` to an INHERITS edge target during the enrichment phase.
|
||||
* Variable/Field defs are also registered so pass_usages.c can resolve
|
||||
* READS/WRITES accesses (rw->var_name) to a Variable/Field node QN. */
|
||||
/* Register callable symbols + every type-like container (Class/Struct/
|
||||
* Interface/Enum/Type/Trait). Type-like defs must be in the registry so
|
||||
* `class Foo : IBar` (INHERITS), `impl Trait for S` (IMPLEMENTS), and method/
|
||||
* field resolution can reach them — Struct included so Rust/Go/Swift/D structs
|
||||
* resolve as type targets just as a Class did. Variable/Field defs are also
|
||||
* registered so pass_usages.c can resolve READS/WRITES accesses (rw->var_name)
|
||||
* to a Variable/Field node QN.
|
||||
* KEEP IN SYNC with pass_parallel.c and pipeline_incremental.c's seed sets. */
|
||||
if (node_id > 0 && def->label &&
|
||||
(strcmp(def->label, "Function") == 0 || strcmp(def->label, "Method") == 0 ||
|
||||
strcmp(def->label, "Class") == 0 || strcmp(def->label, "Interface") == 0 ||
|
||||
strcmp(def->label, "Variable") == 0 || strcmp(def->label, "Field") == 0)) {
|
||||
cbm_label_is_type_like(def->label) || strcmp(def->label, "Variable") == 0 ||
|
||||
strcmp(def->label, "Field") == 0)) {
|
||||
cbm_registry_add(ctx->registry, def->name, def->qualified_name, def->label);
|
||||
}
|
||||
char *file_qn = cbm_pipeline_fqn_compute(ctx->project_name, rel, "__file__");
|
||||
|
||||
@@ -292,8 +292,11 @@ static void free_tagged_nodes(tagged_node_t *nodes, int count) {
|
||||
/* Phase 1: Collect decorated nodes and count word frequency. */
|
||||
static int collect_decorated_nodes(cbm_gbuf_t *gbuf, tagged_node_t **out_nodes,
|
||||
CBMHashTable *word_counts) {
|
||||
static const char *labels[] = {"Function", "Method", "Class"};
|
||||
static const int nlabels = 3;
|
||||
/* "Struct" alongside "Class" so Go/Rust/Swift/D struct names keep
|
||||
* contributing to / receiving auto-tags as they did when structs were
|
||||
* labelled "Class". */
|
||||
static const char *labels[] = {"Function", "Method", "Class", "Struct"};
|
||||
static const int nlabels = 4;
|
||||
tagged_node_t *nodes = NULL;
|
||||
int node_count = 0;
|
||||
int node_cap = 0;
|
||||
|
||||
@@ -82,16 +82,16 @@ static char *pxc_read_file(const char *path, int *out_len) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Map a CBMDefinition.label to a CBMLSPDef.label. Per-language LSP
|
||||
* registrars only care about Class/Interface/Trait/Enum/Type/Protocol/
|
||||
* Function/Method — variables, modules, decorators, etc. are skipped. */
|
||||
/* Map a CBMDefinition.label to a CBMLSPDef.label. Per-language LSP registrars
|
||||
* only care about type-like containers (Class/Struct/Interface/Trait/Enum/Type)
|
||||
* plus Protocol/Function/Method — variables, modules, decorators, etc. are
|
||||
* skipped. Struct passes through so Rust/Go struct type-registration via the
|
||||
* cross-file LSP path is not dropped. */
|
||||
static const char *pxc_map_label(const char *label) {
|
||||
if (!label)
|
||||
return NULL;
|
||||
if (strcmp(label, "Class") == 0 || strcmp(label, "Interface") == 0 ||
|
||||
strcmp(label, "Trait") == 0 || strcmp(label, "Enum") == 0 || strcmp(label, "Type") == 0 ||
|
||||
strcmp(label, "Protocol") == 0 || strcmp(label, "Function") == 0 ||
|
||||
strcmp(label, "Method") == 0) {
|
||||
if (cbm_label_is_type_like(label) || strcmp(label, "Protocol") == 0 ||
|
||||
strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0) {
|
||||
return label;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
@@ -410,12 +410,12 @@ static const char *resolve_as_class(const cbm_registry_t *reg, const char *name,
|
||||
if (!res.qualified_name || res.qualified_name[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
/* Accept any type-like container (Class/Struct/Interface/Enum/Type/Trait):
|
||||
* base classes, Rust `impl Trait for S` struct receivers, and Go struct
|
||||
* embedding all resolve through here. Struct included so the struct receiver
|
||||
* of an IMPLEMENTS edge is not dropped. */
|
||||
const char *label = cbm_registry_label_of(reg, res.qualified_name);
|
||||
if (!label) {
|
||||
return NULL;
|
||||
}
|
||||
if (strcmp(label, "Class") != 0 && strcmp(label, "Interface") != 0 &&
|
||||
strcmp(label, "Type") != 0 && strcmp(label, "Enum") != 0) {
|
||||
if (!cbm_label_is_type_like(label)) {
|
||||
return NULL;
|
||||
}
|
||||
return res.qualified_name;
|
||||
@@ -822,11 +822,14 @@ static int register_and_link_def(cbm_pipeline_ctx_t *ctx, const CBMDefinition *d
|
||||
if (!def->name || !def->qualified_name || !def->label) {
|
||||
return 0;
|
||||
}
|
||||
/* Register callable symbols + Interface — see pass_definitions.c for rationale.
|
||||
* Variable/Field defs are registered too so READS/WRITES can resolve. */
|
||||
/* Register callable symbols + every type-like container (Class/Struct/
|
||||
* Interface/Enum/Type/Trait) — see pass_definitions.c for rationale. Struct
|
||||
* included so Rust/Go/Swift/D structs resolve as type targets. Variable/Field
|
||||
* defs are registered too so READS/WRITES can resolve.
|
||||
* KEEP IN SYNC with pass_definitions.c and pipeline_incremental.c. */
|
||||
if (strcmp(def->label, "Function") == 0 || strcmp(def->label, "Method") == 0 ||
|
||||
strcmp(def->label, "Class") == 0 || strcmp(def->label, "Interface") == 0 ||
|
||||
strcmp(def->label, "Variable") == 0 || strcmp(def->label, "Field") == 0) {
|
||||
cbm_label_is_type_like(def->label) || strcmp(def->label, "Variable") == 0 ||
|
||||
strcmp(def->label, "Field") == 0) {
|
||||
cbm_registry_add(ctx->registry, def->name, def->qualified_name, def->label);
|
||||
(*reg_entries)++;
|
||||
}
|
||||
|
||||
@@ -167,13 +167,12 @@ static const char *resolve_as_class(const cbm_registry_t *reg, const char *name,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Verify it's a Class, Interface, or Type */
|
||||
/* Verify it's a type-like container (Class/Struct/Interface/Enum/Type/Trait):
|
||||
* a base/embedded type, impl receiver, or inheritance target must resolve to
|
||||
* one of these. Struct included so Rust/Go/Swift/D `impl Trait for S` and Go
|
||||
* struct embedding resolve. */
|
||||
const char *label = cbm_registry_label_of(reg, res.qualified_name);
|
||||
if (!label) {
|
||||
return NULL;
|
||||
}
|
||||
if (strcmp(label, "Class") != 0 && strcmp(label, "Interface") != 0 &&
|
||||
strcmp(label, "Type") != 0 && strcmp(label, "Enum") != 0) {
|
||||
if (!cbm_label_is_type_like(label)) {
|
||||
return NULL;
|
||||
}
|
||||
return res.qualified_name;
|
||||
@@ -301,11 +300,16 @@ int cbm_pipeline_implements_go(cbm_pipeline_ctx_t *ctx) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find all Class nodes */
|
||||
/* Find candidate concrete types. In Go the type that satisfies an interface
|
||||
* is a struct (now labelled "Struct") or a named type (labelled "Class"); both
|
||||
* sets are checked. Each call returns a borrowed internal array (no free). */
|
||||
const cbm_gbuf_node_t **classes = NULL;
|
||||
int class_count = 0;
|
||||
cbm_gbuf_find_by_label(ctx->gbuf, "Class", &classes, &class_count);
|
||||
if (class_count == 0) {
|
||||
const cbm_gbuf_node_t **structs = NULL;
|
||||
int struct_count = 0;
|
||||
cbm_gbuf_find_by_label(ctx->gbuf, "Struct", &structs, &struct_count);
|
||||
if (class_count == 0 && struct_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -337,7 +341,11 @@ int cbm_pipeline_implements_go(cbm_pipeline_ctx_t *ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check each Class node for method-set satisfaction */
|
||||
/* Check each concrete-type node (Struct + Class) for method-set
|
||||
* satisfaction. */
|
||||
for (int c = 0; c < struct_count; c++) {
|
||||
edge_count += check_go_class_implements(ctx, structs[c], iface, imethods, im_count);
|
||||
}
|
||||
for (int c = 0; c < class_count; c++) {
|
||||
edge_count += check_go_class_implements(ctx, classes[c], iface, imethods, im_count);
|
||||
}
|
||||
|
||||
@@ -509,9 +509,13 @@ static void persist_hashes(cbm_store_t *store, const char *project, cbm_file_inf
|
||||
* resolve to the same-named Module node instead of the Class node. Only
|
||||
* callable / declared symbols belong in the registry. */
|
||||
static bool incr_label_is_registry_symbol(const char *label) {
|
||||
/* Mirror pass_definitions.c / pass_parallel.c registry seeding EXACTLY:
|
||||
* callables + every type-like container (Class/Struct/Interface/Enum/Type/
|
||||
* Trait) + Variable/Field. Struct included so an incremental re-resolve seeds
|
||||
* the same struct type nodes a full reindex would. */
|
||||
return label && (strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0 ||
|
||||
strcmp(label, "Class") == 0 || strcmp(label, "Interface") == 0 ||
|
||||
strcmp(label, "Variable") == 0 || strcmp(label, "Field") == 0);
|
||||
cbm_label_is_type_like(label) || strcmp(label, "Variable") == 0 ||
|
||||
strcmp(label, "Field") == 0);
|
||||
}
|
||||
|
||||
/* Callback for cbm_gbuf_foreach_node: seed the registry with the existing
|
||||
|
||||
@@ -85,6 +85,8 @@ static float size_for_label(const char *label) {
|
||||
return 8.0f;
|
||||
if (strcmp(label, "Class") == 0)
|
||||
return 6.0f;
|
||||
if (strcmp(label, "Struct") == 0)
|
||||
return 6.0f;
|
||||
if (strcmp(label, "Interface") == 0)
|
||||
return 6.0f;
|
||||
if (strcmp(label, "Function") == 0)
|
||||
|
||||
Reference in New Issue
Block a user