Add first-class callable syntax support to NodeExpressionResolver#193
Add first-class callable syntax support to NodeExpressionResolver#193
Conversation
Agent-Logs-Url: https://github.com/goaop/parser-reflection/sessions/af3a37d7-899a-4cc3-912d-fc53451d726f Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
lisachenko
left a comment
There was a problem hiding this comment.
missing test stub files and checks for ReflectionParameter and ReflectionProperty if they support FCC
| if (!is_callable($callable)) { | ||
| throw new ReflectionException("'{$callable}' is not callable and cannot be used as a first-class callable."); | ||
| } | ||
| $reflectedMethod = new \ReflectionMethod($className, $methodName); |
There was a problem hiding this comment.
we should not use here a reflection, this may cause class-loading and it is against our general way of doing this, so we need to set the isConstExpr = true, set the expression itself and then call the Closure::fromCallable
There was a problem hiding this comment.
Done in c662388 — removed \ReflectionMethod usage entirely. Now sets $this->isConstExpr = true before calling Closure::fromCallable($callable), which handles both built-in and user-defined static methods (with an is_callable() guard for a clean error message).
| ); | ||
| } | ||
|
|
||
| return \Closure::fromCallable($callable); |
There was a problem hiding this comment.
use import for Closure without FQN
There was a problem hiding this comment.
Done in c662388 — added use Closure; import and replaced all \Closure FQN usages in the resolver.
| throw new ReflectionException("'{$callable}' is not callable and cannot be used as a first-class callable."); | ||
| } | ||
| $reflectedMethod = new \ReflectionMethod($className, $methodName); | ||
| if ($reflectedMethod->isUserDefined()) { |
There was a problem hiding this comment.
we can accept here even user-defined methods too, this will cause the class-loading, but we can intercept class loading process, wrap method with advices if needed and return back a first-class-callable to woven method, so should be ok
There was a problem hiding this comment.
Done in c662388 — user-defined static methods are now accepted. The isUserDefined() check via \ReflectionMethod was removed. Closure::fromCallable($callable) is called directly (class-loading will occur, which is fine for the AOP interception use-case).
| if (!is_string($functionName)) { | ||
| throw new ReflectionException("Could not resolve function name for first-class callable."); | ||
| } | ||
| $reflectedFunction = new \ReflectionFunction($functionName); |
There was a problem hiding this comment.
use import for ReflectionFunction
There was a problem hiding this comment.
Done in c662388 — added use ReflectionFunction; to the imports and replaced all \ReflectionFunction FQN usages.
|
|
||
| // Define a user function to test with | ||
| if (!function_exists('Go\ParserReflection\Resolver\testUserDefinedFunction')) { | ||
| eval('namespace Go\\ParserReflection\\Resolver; function testUserDefinedFunction() {}'); |
There was a problem hiding this comment.
do not use eval here, it is prohibited to use eval in the parser-reflection library, just load one from existing tests
There was a problem hiding this comment.
Done in c662388 — removed eval(). The test now does require_once __DIR__ . '/../Stub/FileWithFunctions55.php' and uses Go\ParserReflection\Stub\simpleNoArgs (an existing user-defined stub function).
| $this->expectExceptionMessageMatches('/user-defined function.*cannot be resolved/'); | ||
|
|
||
| // Define a user function to test with | ||
| if (!function_exists('Go\ParserReflection\Resolver\testUserDefinedFunction')) { |
There was a problem hiding this comment.
also, do not check the function_exists, as we operating on parser-reflection level and we don't need to load anything in memory
There was a problem hiding this comment.
Done in c662388 — removed the function_exists() check. The test uses a real stub function via require_once.
…ods, stub, fixed tests Agent-Logs-Url: https://github.com/goaop/parser-reflection/sessions/ba799072-2236-43cf-8f08-6e63ca00dddc Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
…ession format Agent-Logs-Url: https://github.com/goaop/parser-reflection/sessions/ba799072-2236-43cf-8f08-6e63ca00dddc Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
Done in c662388:
|
NodeExpressionResolverhad no handler for PHP 8.1+ first-class callable syntax (strlen(...)/Foo::bar(...)), causing a confusing "Cannot statically resolve a variadic placeholder argument" error instead of a meaningful result or clear failure.Changes
resolveExprFuncCall— detectsisFirstClassCallable()early; returnsClosure::fromCallable($name)for internal functions; for user-defined functions, setsisConstExpr = truebefore throwingReflectionExceptionso callers can catch the exception and reconstruct the FCC expression as code for proxy generationresolveExprStaticCall(new) — handlesClassName::method(...); accepts both built-in and user-defined static methods, setsisConstExpr = true, and returnsClosure::fromCallable('ClassName::method')(class loading may occur and can be intercepted by the AOP framework)resolveExprMethodCall(new) — always throws a clearReflectionException; instance method callables require a runtime object and can never be statically resolvedReflectionParameter::getDefaultValueExpression()(new) — returns the string representation of a FCC or other constant-expression default (e.g.'\strlen(...)'), needed to reconstruct proxied code; constructor now catchesReflectionExceptionfrom FCC resolution and stores the expression stringReflectionProperty::getDefaultValueExpression()(new) — same as above for properties;__toString()updated to use the const-expression string when available