Skip to content

TypeScript backend silently shadows duplicate signatures where the Java backend raises #419

Description

@rahlk

The Java and TypeScript backends handle the same condition — two declarations spelling one name — with opposite failure modes. Java refuses; TypeScript overwrites without a word.

Java: raises

cldk/analysis/java/codeanalyzer/codeanalyzer.py:347-353, mirrored identically in cldk/analysis/java/neo4j/neo4j_backend.py:657-663:

def _add_type(self, t: JType, path: str) -> None:
    name = t.qualified_name
    if name in self._types:
        raise CodeanalyzerExecutionException(duplicate_type_name(name))
    self._types[name] = t
    self._file_of[name] = path

duplicate_type_name (cldk/analysis/java/backend.py:515) states the rationale: the collision "would make a get_call_graph() node key and a get_class() key ambiguous, so it is surfaced rather than letting the second silently shadow the first."

TypeScript: shadows

cldk/analysis/typescript/codeanalyzer/codeanalyzer.py:298-300:

def _add_type(self, t, fp: str) -> None:
    self._file_of[t.signature] = fp
    self._id_index[t.id] = (t.signature, t.kind)

Plain assignment, no guard. _add_callable (302-305) does the same for self._callables[c.signature] and self._file_of[c.signature]. A second declaration spelling the same signature silently replaces the first, so _file_of reports one file for several declarations and every consumer of it — get_typescript_file, the path= on TSCallableOverview / TSClassOverview, resolve_callable's SliceNode.file — silently attributes code to the wrong file.

Why it matters now

Vendored duplicates are the realistic trigger: several service repos analyzed in one run, each carrying its own copy of a shared internal module. That is exactly the input the Java guard exists to reject. A polyglot monorepo therefore gets a loud, correct refusal on its Java half and a quiet wrong answer on its TypeScript half, from the same analysis.

The Java docstring's own reasoning applies unchanged to TypeScript, so the two should agree. Which direction they agree in is the open question:

  1. TypeScript raises too — parity with Java, and consistent with the stated rationale. Breaking for any caller currently relying on last-wins, though it is hard to argue that reliance is intentional.
  2. Both warn instead of raising, with the ambiguity recorded so a caller can decide. Softer, but changes documented Java behavior and needs somewhere for the warning to go.

Option 1 is the smaller change and matches the existing intent; worth confirming before implementing, since it turns a silent pass into a hard failure for existing TypeScript callers.

Related: codellm-devkit/codeanalyzer-java#269 — the analyzer that produces these duplicates neither reports nor resolves them correctly, binding every caller to the last source root.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions