sebastianbergmann/php-token-stream · error · OutOfBoundsException

No token at position

Error message

No token at position "%s"

What it means

PHP_Token_Stream implements ArrayAccess, so `$stream[$offset]` reads a token by its integer index in the tokenized source. offsetGet() first checks offsetExists() (isset on the internal tokens array); if the offset does not exist it throws OutOfBoundsException with the requested position. The library treats out-of-range access on a token stream as an out-of-bounds read, consistent with SeekableIterator semantics.

Solutions

  1. Check the offset before access: only read `$stream[$i]` when `$i >= 0 && $i < count($stream)` (or `isset($stream[$i])`).
  2. Fix off-by-one loop bounds (`<` instead of `<=`) against the stream's count().
  3. If the stream may be empty, handle that case explicitly before indexing.
  4. Re-instantiate PHP_Token_Stream if the underlying file changed after indices were computed, instead of reusing old indices.
  5. If the access is intentional optional probing, wrap in try/catch OutOfBoundsException or use isset() rather than offsetGet().

Example fix

// before
for ($i = 0; $i <= count($stream); $i++) {
    $token = $stream[$i]; // OutOfBoundsException on last iteration
}
// after
$count = count($stream);
for ($i = 0; $i < $count; $i++) {
    $token = $stream[$i];
}
Defensive patterns

Strategy: type-guard

Validate before calling

// PHP
function tokenAt(PHP_Token_Stream $stream, int $index): ?PHP_Token {
    return ($index >= 0 && $index < count($stream)) ? $stream[$index] : null;
}

Type guard

function hasTokenAt(PHP_Token_Stream $stream, int $index): bool {
    return $index >= 0 && $index < count($stream);
}

Try / catch

try {
    $token = $stream[$index];
} catch (\OutOfBoundsException $e) {
    // no token at this position; handle absence (skip/log/fallback)
    $token = null;
}

Prevention

When it happens

Trigger: Reading `$stream[$i]` (or offsetGet($i) directly) where $i < 0, $i >= count($stream), or where the stream was built from empty source so no token exists at that index. Also occurs when iterating past the end or when stale indices from a previous tokenization are reused after the file changed.

Common situations: Looping `for ($i = 0; $i <= count($stream); $i++)` with an off-by-one `<=`; caching token indices across edits to the analyzed file; assuming index 0 exists when the file is empty or contains only whitespace; code that mixes line numbers with token indices and reads the wrong array.


AI-assisted analysis of sebastianbergmann/php-token-stream@76fc056775 (2026-09-16). Data as JSON: /api/errors/51bd90e8395061d7. Report an issue: GitHub.

Appendix: source

Thrown at src/Stream.php:348

    /**
     * @param int $offset
     *
     * @return bool
     */
    public function offsetExists($offset)
    {
        return isset($this->tokens[$offset]);
    }

    /**
     * @param int $offset
     *
     * @throws OutOfBoundsException
     */
    public function offsetGet($offset)
    {
        if (!$this->offsetExists($offset)) {
            throw new OutOfBoundsException(
                \sprintf(
                    'No token at position "%s"',
                    $offset
                )
            );
        }

        return $this->tokens[$offset];
    }

    /**
     * @param int $offset
     */
    public function offsetSet($offset, $value)/*: void*/
    {
        $this->tokens[$offset] = $value;
    }

View on GitHub (pinned to 76fc056775)