Bug Tracker & Fixes

Track all reported bugs, their status, and resolutions for the Neutron programming language.

Neutron Bug Report Log

This page documents all reported bugs in the Neutron programming language, including their status, discovery details, and resolutions.

[NEUT-020] - Recursive Functions Return Function Objects Instead of Values

Fixed
Discovered On:

Sunday, November 3, 2025 — Linux — Neutron-1.2.1-beta

Fixed On:

Sunday, November 3, 2025 — Neutron-1.2.1

Root Cause:

Two-part issue in stack management during function returns:

  1. Regular Functions: OP_RETURN resized the stack to return_slot_offset which pointed to the first argument, leaving the callee (function object) on the stack. This caused recursive calls to return the function object instead of the computed value.
  2. Bound Methods in Loops: After fixing regular functions, bound method calls in loops crashed because they have a different stack layout (receiver acts as first argument at slot_offset position).
Description:

When a function called itself recursively, the return value was a function object (e.g., ) instead of the computed numeric value. This made all recursive algorithms impossible to implement. Example:

fun factorial(n) {
    if (n <= 1) {
        return 1;
    }
    var sub_result = factorial(n - 1);  // Returns  instead of number
    return n * sub_result;  // RuntimeError: Operands must be numbers
}
After the initial fix for regular functions, calling methods on objects within loops caused segfaults:
var i = 0;
while (i < 10) {
    var p = Person();
    p.initialize("Name");  // Segfault - stack corruption
    i = i + 1;
}

Resolution:

1. **Added isBoundMethod flag to CallFrame** (include/vm.h):

  • New boolean field to distinguish between regular function calls and bound method calls
  • Initialized to false by default in CallFrame constructor
2. **Mark bound method calls** (src/vm.cpp line ~227):
  • Set frame->isBoundMethod = true when calling bound methods in VM::callValue()
  • Regular function calls leave it as false
3. **Fixed OP_RETURN stack cleanup** (src/vm.cpp line ~549):
  • For **bound methods**: stack.resize(return_slot_offset) - keeps everything before receiver
  • For **regular functions**: stack.resize(return_slot_offset - 1) - removes callee + arguments
  • Added safety check for return_slot_offset == 0 case
**Stack Layout Analysis:**
Regular Function Call:
  Before: [outer_vars | callee | arg1 | arg2]
                        ^slot_offset points to arg1
  After:  [outer_vars | result]
          Resize to (slot_offset - 1) to remove callee
Bound Method Call:
  Before: [outer_vars | receiver | arg1 | arg2]  
                        ^slot_offset points to receiver (acts as arg0)
  After:  [outer_vars | result]
          Resize to (slot_offset) to keep outer_vars intact

Impact:
  • ✅ Recursive functions now work correctly (factorial, fibonacci, power, etc.)
  • ✅ Methods can be called on objects within loops without crashes
  • ✅ All 49 unit tests pass
  • ✅ Enabled 3 new benchmarks (recursion, dict_ops, nested_loops)
  • ✅ Benchmark win rate improved from 66.7% to 91.6% (11 out of 12 benchmarks)

[NEUT-019] - Runtime Errors Missing Line Numbers

Fixed
Discovered On:

Sunday, November 3, 2025 — Linux — Neutron-1.2.1-beta

Fixed On:

Sunday, November 3, 2025 — Neutron-1.2.1

Root Cause:

The compiler emitted bytecode with line number 0 (hardcoded placeholder) instead of actual line numbers from the source code. This was because Compiler::emitByte() used chunk->write(byte, 0) instead of tracking and passing the current line number.

Description:

Runtime errors did not display line numbers in error messages. When errors occurred, users saw messages like "RuntimeError in file.nt: Division by zero." without any indication of which line caused the problem. The error handler tried to display line numbers using frame->currentLine, but this was always -1 because the VM never updated it from the bytecode. The bytecode itself contained line 0 for all instructions because the compiler never tracked source line numbers.

Resolution:

1. Added int currentLine field to the Compiler class to track the current source line being compiled 2. Initialized currentLine = 1 in Compiler constructors 3. Updated Compiler::emitByte() to use chunk->write(byte, currentLine) instead of chunk->write(byte, 0) 4. Updated key visitor methods (visitBinaryExpr, visitUnaryExpr, visitVariableExpr, visitVarStmt) to extract line numbers from Token fields and update currentLine 5. Modified VM::run() to read line numbers from chunk->lines[] array before each instruction and update frame->currentLine 6. Fixed 43 runtime error calls to pass frame->currentLine instead of -1 All runtime errors now display accurate line numbers with source code context. Example output:

RuntimeError in /tmp/test.nt at line 8:
  Division by zero.
   8 | var z = x / y;  // Should show line 8 in error
Stack trace:
  at