Skip to content

bug(core): Nested functions are silently dropped from extraction #1041

Description

@squid-protocol

Problem

Two of the four function-slicing strategies in gitgalaxy/core/detector.py contain identical guards that cause nested/inner functions to be silently dropped:

Mode B (brace languages)_slice_by_braces at line 1564:

start_idx = match.start()
if start_idx < last_end_idx:
    continue

Mode C (indentation languages)_slice_by_indentation at line 1693:

start_idx = match.start()
if start_idx < last_end_idx:
    continue

last_end_idx tracks the end index of the previously accepted function match (set at line 1640 for braces, line 1733 for indentation).

Bug

A nested/inner function declaration (e.g. a Python function defined inside another function, or a JavaScript function expression assigned inside another function's body) necessarily starts before its enclosing function's end index. This guard causes it to always be skipped — it never becomes its own FunctionNode.

Consequence:

  • The source text of the nested function is still included as part of the parent function's block (so its complexity signals bleed into the parent's aggregate hit_vector/branch_count/etc.)
  • But it is never counted, named, or reported as a distinct function
  • function_count is undercounted for any file using nested functions/closures
  • Per-function complexity for the true innermost logic is invisible (merged into the outer function's numbers instead)
  • Any future per-method analysis (e.g. LCOM within a class whose methods contain nested helper functions) would misattribute the nested function's behavior to the outer method

Example

def outer():
    def inner():      # SILENTLY DROPPED — never becomes its own FunctionNode
        return 42
    return inner()

Detector sees:

  • 1 function total (outer)
  • Inner's branch/logic complexity merged into outer's hit_vector

Should see:

  • 2 functions (outer, inner)
  • Inner's metrics separate from outer

Fix Location

  • Mode B (braces): gitgalaxy/core/detector.py line 1564 guard + line 1640 last_end_idx update
  • Mode C (indentation): gitgalaxy/core/detector.py line 1693 guard + line 1733 last_end_idx update
  • Mode A (labels/assembly) at line 1775+ does NOT have this guard and is not affected the same way

Fix Direction

The fix needs a proper nesting-depth-aware or stack-based scope tracker rather than the current flat "skip if inside previous match's span" approach. The guard exists specifically to prevent double-counting, but has the side effect of dropping real nested definitions entirely instead of counting them as their own (nested) function.

A correct approach would:

  1. Track the full nesting depth of each function's scope boundaries (not just flat "did it start after the previous one ended")
  2. Allow nested functions to be counted as distinct FunctionNodes with their own metrics
  3. Preserve the flat-scanning property (regex-based, no AST) while adding depth awareness

Affects

Mode B (C-family/brace languages): C, C++, C#, Java, JavaScript, TypeScript, Kotlin, Scala, Go, Rust, Swift, Objective-C, Groovy, etc.

Mode C (Python/YAML indentation languages): Python, Python embedded, YAML, etc.

These are the two most common parsing modes by volume of covered languages.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugUnintended behavior or logic failure in the enginecore-engineModifications to the central physics and parsing engine

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions