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
- Check the offset before access: only read `$stream[$i]` when `$i >= 0 && $i < count($stream)` (or `isset($stream[$i])`).
- Fix off-by-one loop bounds (`<` instead of `<=`) against the stream's count().
- If the stream may be empty, handle that case explicitly before indexing.
- Re-instantiate PHP_Token_Stream if the underlying file changed after indices were computed, instead of reusing old indices.
- 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
- Always bound loops with `count($stream)` and strict `<`, never `<=`.
- Prefer `isset($stream[$i])` for optional probing instead of direct access.
- Never reuse token indices after the source file changes; re-tokenize.
- Distinguish token indices from line numbers and from raw token_get_all() offsets.
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)