Skip to content

bug(indexer): parser audit roundup — context attribution, wrong Module names, missing declarations and duplicate call records across 12 languages #1538

Description

@Shashankss1205

Remaining findings from a parser audit of tools/languages/, below the threshold I filed individually. Grouped so they are not lost; each is independent and locally fixable. All reproduced by running the parsers unless noted.

Nesting / context attribution

Call-site class_context is structurally always null in four languages. Downstream, resolution/calls.py:1394-1396 uses class_context[0] to resolve super.* calls and :804 uses it to disambiguate same-named methods — both dead for these languages.

  • java.py:954_get_parent_context (:273-291) takes no types argument and returns the first enclosing method or class. For a call inside a method it always returns the method, so the ctx_type in ("class_declaration", ...) test never passes → (None, None).
  • csharp.py:409-410 — same root cause; 'class' in 'method_declaration' is always false.
  • swift.py:567 — hardcoded "class_context": (None, None). Line 564 also hardcodes "args": [] for every call, and :571-572 swallows all exceptions with a bare continue.
  • php.py:551 — the new X() branch emits a (None, None) tuple while every other PHP call (:502) emits a plain string, so the shape varies within a single list.

cpp.py:583 does this correctly by passing types=("class_specifier",) — a good template.

Nested functions get no CONTAINS edge outside Python.

  • javascript.py:292-311context / context_type are computed at 292 and then never stored; func_data keeps only class_context.
  • TypeScript does emit context_type: "function_declaration", but graph_builder.py:311 and writer.py:358 gate the nested-function batch on the literal Python node type 'function_definition', which only python.py emits. This is a parser-output/indexer-contract mismatch affecting every non-Python language that populates context_type.

rust.py:152-162 — Rust impl methods carry only module_context, no class_context. impl blocks are read for inheritance (struct Point correctly gets bases: ["Draw"]), but graph_builder.py:302 needs class_context, so Point::new and Point::helper end up orphan functions never linked to Point.

Wrong values written to the graph

  • rust.py:304 — braced use lists keep only the last name: use std::collections::{HashMap, HashSet, BTreeMap}; → a single import named BTreeMap.
  • rust.py:295full_import_name is raw source text including keyword and semicolon: "use std::collections::HashMap;". The cleaned_path computed at :303 is used only for name. writer.py:557-565 writes this verbatim as the Module node name, and graph_builder.py:668 / calls.py:1510 do full_import_name.replace('.', '/') in path, which can never match.
  • cpp.py:320#include "local.h" keeps its quotes (.strip('<>') misses them; c.py:606 correctly uses .strip('"<>')). Yields a Module named '"function_chain.h"', so local-header imports match neither the header file nor the C parser's unquoted form for the same header.
  • csharp.py:293 — base lists split on a naive ,, shredding multi-arg generics: class Map : Dictionary<string, int>bases: ['Dictionary<string', 'int>'], creating INHERITS edges to two nonexistent types.
  • python.py:447from . import X yields full_import_name: "..sibling" (and from .. import x"...x"), one package level too high. No consumer strips leading dots.
  • dart.py:499import 'dart:math' as math; loses the alias (prefix has no identifier field and there is no fallback, unlike dart.py:402/:619), so a math.max(...) call cannot be mapped back to its import.

Missing declarations

  • scala.py:9-13 — the query makes parameters mandatory, so parameterless defs (getters, abstract members, def name: String) are dropped: in object Utils { def simple = 42; def withParens(): Int = 43 } only withParens is returned.
  • ruby.py:9-12 (and pre_scan_ruby :548-550) — def self.x singleton methods are never captured (node type is singleton_method, not method). Because the pre-scan omits them too, cross-file resolution cannot find them either.
  • php.py:21-33, :148 — PHP 8.1 enum is in neither the classes query nor _get_parent_context's owner list, so enums are not indexed and their methods become orphans.
  • c.py:124-128, cpp.py:95-99 — function-like macros (#define SQUARE(x) ...) are preproc_function_def, not preproc_def, so they are never extracted. The parameter-extraction code at c.py:729-734, commented "Extract parameters for function-like macros", is unreachable.
  • cpp.py:497, :100-118 — variable type is None for every initialized variable (node.parent is the init_declarator; type lives on the enclosing declaration), and uninitialized declarations (int count;) are never captured at all — c.py:114-122 has the forms cpp.py lacks.
  • cpp.py:183-217 — inline methods defined in a class body get no class_context (only out-of-line Foo::bar does), so writer.py:1374-1377's C++ post-pass creates no Class-[:CONTAINS]->Function edge for header-defined methods.

Duplicate / spurious records

  • elixir.py:450-466 — every def/defp head is emitted as a self-recursive call. A definition is a call node whose arguments contain the signature as a nested call; ELIXIR_KEYWORDS filters def itself, but the recursion at :468-469 descends into its arguments and records the signature as an invocation. Every Elixir function gets a false CALLS edge to itself.
  • ruby.py:428-448 — nested calls steal each other's receiver and args: receiver/args captures are assigned to every containing call node with no field check and no break (the name branch at :438 does verify). logger.info(formatter.render(payload)) records info with full_name: "formatter.info".
  • csharp.py:398-401 — the name_line dedup key drops distinct calls to the same function on one line: return Math.Max(Clamp(a), Clamp(b)); records one Clamp.
  • csharp.py:217 — the attribute relationship is inverted (attribute_list is a child of method_declaration, never its parent), so attributes is always [] and line_number points at the attribute line.
  • c.py:505-517, :498-531 — two enum-member bugs: the * quantifier makes only the first enumerator of each enum match (enum Color { RED, GREEN, BLUE }[RED]), and _find_enum_members re-runs the whole-file query inside its per-enum loop with a global seen_names, so the first enum claims every member (OK from enum Status is emitted with enum_name: 'Color').
  • haskell.py _find_calls — duplicate call records: print (addTwo 1 2) emits addTwo twice at the same line.
  • ruby.py:487-507 — variable value is always None. node.parent returns a fresh Python wrapper each access, so id(current) differs for the same underlying node and the @name/@value captures land in different buckets. (n1 == n2 is True while id(n1) == id(n2) is False.)

Minor

  • c.py:741, cpp.py:426 — macro end_line off by one; a preproc_def node's end_point includes the trailing newline, so +1 double-counts.
  • kotlin.py:1223/:1342, php.py:280/:213line_number points at the annotation/attribute line rather than the declaration. python.py:274-277 deliberately handles this via decorated_definition, so the declaration line is the established convention.
  • ruby.py:38-47, :516-522@@class_vars and $globals are never captured; separately the startswith("@") test at :517 precedes startswith("@@") at :518, making the "class" branch dead even if the query were fixed.
  • go.py:93curr.child_by_field_name('type_spec'); type_declaration has no such field and there is no fallback, so _get_parent_context returns (None, 'type_declaration', line).
  • solidity.py:238 — an unreadable file returns a normal empty result dict rather than the {"path":..., "error":...} shape every other parser returns, so an I/O failure is indexed as "file with no symbols" rather than flagged.

Checked and clean

Kotlin imports/aliases and nested/data/sealed class context, extension receiver_type/return_type, parameter and variable scoping, call receiver inference. Ruby require/require_relative and module includebases merging. PHP class/interface/trait extends/implements and $this->/self:: calls. Java bases extraction (:453 and :472 reference nonexistent fields but both have explicit node-type fallbacks). Lua and Perl overall. Every open() in the directory already passes errors=, so the meta-test is satisfied; no parser crashed on any of the 85 fixture files with index_source=True.


Environment
Commit c0e0bed (main)
Python 3.12.3, Linux

From a parser audit of tools/languages/. Reproduced by running the parsers directly unless noted otherwise. Higher-impact findings from the same audit are filed separately (#1520-#1527).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: indexerParsing, discovery, resolution, persistencebugSomething isn't workingseverity: mediumIncorrect results, misleading output, or a broken contract

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions