Skip to content

Commit

Permalink
Helpers::improveException() improved 'invalid callable errors'
Browse files Browse the repository at this point in the history
related to php/php-src#8039
  • Loading branch information
dg committed Jan 3, 2024
1 parent bb82fbd commit 74471ad
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/Tracy/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,25 @@ public static function improveException(\Throwable $e): void
|| strpos($e->getMessage(), 'did you mean')
) {
// do nothing
} elseif (preg_match('~Argument #(\d+) \(\$\w+\) must be of type callable, (.+ given)~', $message, $m)) {
$arg = $e->getTrace()[0]['args'][$m[1] - 1] ?? null;
if (is_string($arg) && str_contains($arg, '::')) {
$arg = explode('::', $arg, 2);
}
if (!is_callable($arg, syntax_only: true)) {
// do nothing
} elseif (is_array($arg) && is_string($arg[0]) && !class_exists($arg[0]) && !trait_exists($arg[0])) {
$message = str_replace($m[2], "but class '$arg[0]' does not exist", $message);
} elseif (is_array($arg) && !method_exists($arg[0], $arg[1])) {
$hint = self::getSuggestion(get_class_methods($arg[0]) ?: [], $arg[1]);
$class = is_object($arg[0]) ? get_class($arg[0]) : $arg[0];
$message = str_replace($m[2], "but method $class::$arg[1]() does not exist" . ($hint ? " (did you mean $hint?)" : ''), $message);
} elseif (is_string($arg) && !function_exists($arg)) {
$funcs = array_merge(get_defined_functions()['internal'], get_defined_functions()['user']);
$hint = self::getSuggestion($funcs, $arg);
$message = str_replace($m[2], "but function $arg() does not exist" . ($hint ? " (did you mean $hint?)" : ''), $message);
}

} elseif (preg_match('#^Call to undefined function (\S+\\\\)?(\w+)\(#', $message, $m)) {
$funcs = array_merge(get_defined_functions()['internal'], get_defined_functions()['user']);
if ($hint = self::getSuggestion($funcs, $m[1] . $m[2]) ?: self::getSuggestion($funcs, $m[2])) {
Expand Down
61 changes: 61 additions & 0 deletions tests/Tracy/Helpers.improveException.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,64 @@ test('do not suggest anything when accessing anonymous class', function () {
Assert::same('Undefined property: class@anonymous::$property', $e->getMessage());
Assert::false(isset($e->tracyAction));
});


test('callable error: ignore syntax mismatch', function () {
try {
(fn(callable $a) => null)(false);
} catch (Error $e) {
}

Helpers::improveException($e);
Assert::match('{closure}(): Argument #1 ($a) must be of type callable, bool given, called in %a%', $e->getMessage());
});

test('callable error: typo in class name', function () {
try {
(fn(callable $a) => null)([PhpTokn::class, 'tokenize']);
} catch (Error $e) {
}

Helpers::improveException($e);
Assert::match("{closure}(): Argument #1 (\$a) must be of type callable, but class 'PhpTokn' does not exist, called in %a%", $e->getMessage());
});

test('callable error: typo in class name', function () {
try {
(fn(callable $a) => null)('PhpTokn::tokenize');
} catch (Error $e) {
}

Helpers::improveException($e);
Assert::match("{closure}(): Argument #1 (\$a) must be of type callable, but class 'PhpTokn' does not exist, called in %a%", $e->getMessage());
});

test('callable error: typo in method name', function () {
try {
(fn(callable $a) => null)([PhpToken::class, 'tokenze']);
} catch (Error $e) {
}

Helpers::improveException($e);
Assert::match('{closure}(): Argument #1 ($a) must be of type callable, but method PhpToken::tokenze() does not exist (did you mean tokenize?), called in %a%', $e->getMessage());
});

test('callable error: typo in method name', function () {
try {
(fn(callable $a) => null)('PhpToken::tokenze');
} catch (Error $e) {
}

Helpers::improveException($e);
Assert::match('{closure}(): Argument #1 ($a) must be of type callable, but method PhpToken::tokenze() does not exist (did you mean tokenize?), called in %a%', $e->getMessage());
});

test('callable error: typo in function name', function () {
try {
(fn(callable $a) => null)('trm');
} catch (Error $e) {
}

Helpers::improveException($e);
Assert::match('{closure}(): Argument #1 ($a) must be of type callable, but function trm() does not exist (did you mean trim?), called in %a%', $e->getMessage());
});

0 comments on commit 74471ad

Please sign in to comment.