- Add normalizeCallableClass helper for dynamic callback normalization - Introduce FunctionContext::needsUserCodeCallableScope for user-code scope tracking - Replace MethodDef::needsUnpackedCallbackScope with per-context scope management - Update callback descriptors to include handling strategies (callable, dynamic, map) - Implement proper callable scope guard generation for closures and fibers - Add propertyNameToStr method for correct property name conversion - Refactor scope guard generation to use CallableScope with lexical scope - Update documentation for enhanced scope management system - Add tests for complex scoped callback scenarios - Fix parent class name normalization in property access resolver - Remove deprecated unpacked callback scope fallback mechanismpull/48/head
parent
8a3b5ae9a4
commit
e07b97d6cf
20 changed files with 328 additions and 81 deletions
@ -0,0 +1,44 @@ |
||||
<?php |
||||
|
||||
namespace TypePhp\Tests\Resolver; |
||||
|
||||
use PhpParser\NodeAbstract; |
||||
use PHPUnit\Framework\TestCase; |
||||
use TypePhp\Entity\ClassDef; |
||||
use TypePhp\Resolver\PropertyAccessContext; |
||||
use TypePhp\Resolver\PropertyAccessResolver; |
||||
|
||||
final class PropertyAccessResolverTest extends TestCase |
||||
{ |
||||
public function testSubclassLookupNormalizesEveryParentName(): void |
||||
{ |
||||
$parents = [ |
||||
'app\\childexception' => 'RuntimeException', |
||||
]; |
||||
$context = new class($parents) implements PropertyAccessContext { |
||||
public function __construct(private array $parents) |
||||
{ |
||||
} |
||||
|
||||
public function getClassDef(string $name): ?ClassDef |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
public function getParentClass(string $class): string |
||||
{ |
||||
return $this->parents[strtolower(ltrim($class, '\\'))] ?? ''; |
||||
} |
||||
|
||||
public function fatalError(NodeAbstract $node, string $msg): never |
||||
{ |
||||
throw new \LogicException($msg); |
||||
} |
||||
}; |
||||
|
||||
$resolver = new PropertyAccessResolver($context); |
||||
|
||||
$this->assertTrue($resolver->isSameOrSubclassOf('App\\ChildException', 'runtimeexception')); |
||||
$this->assertTrue($resolver->canAccessProtectedProperty('App\\ChildException', 'RuntimeException')); |
||||
} |
||||
} |
||||
@ -0,0 +1,128 @@ |
||||
--TEST-- |
||||
Scoped callbacks preserve relative, inherited, delayed and nested call contexts |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class ComplexScopeBase |
||||
{ |
||||
private static function privateMap(int $value): string |
||||
{ |
||||
return static::class . ':private:' . $value; |
||||
} |
||||
|
||||
protected static function protectedMap(int $value): string |
||||
{ |
||||
return static::class . ':protected:' . $value; |
||||
} |
||||
|
||||
public function dynamicRelativeCallbacks(): void |
||||
{ |
||||
$self = 'self'; |
||||
$static = 'static'; |
||||
var_dump(array_map([$self, 'privateMap'], [1])); |
||||
var_dump(array_map([$static, 'protectedMap'], [2])); |
||||
} |
||||
|
||||
public function delayedUnpackedCallback(): Closure |
||||
{ |
||||
return function (int $value): string { |
||||
$arguments = [['self', 'privateMap'], $value]; |
||||
return call_user_func(...$arguments); |
||||
}; |
||||
} |
||||
|
||||
public function unpackedCallbackArray(int $value): string |
||||
{ |
||||
$static = 'static'; |
||||
$arguments = [[$static, 'protectedMap'], [$value]]; |
||||
return call_user_func_array(...$arguments); |
||||
} |
||||
|
||||
public function nestedDynamicClosure(int $value): string |
||||
{ |
||||
$callback = function (int $innerValue): string { |
||||
$arguments = [['self', 'privateMap'], $innerValue]; |
||||
return call_user_func(...$arguments); |
||||
}; |
||||
return call_user_func($callback, $value); |
||||
} |
||||
} |
||||
|
||||
class ComplexScopeChild extends ComplexScopeBase |
||||
{ |
||||
public function parentCallback(): void |
||||
{ |
||||
$parent = 'parent'; |
||||
var_dump(array_map([$parent, 'protectedMap'], [3])); |
||||
} |
||||
} |
||||
|
||||
class NestedScopeOuter |
||||
{ |
||||
private function invokeInner(int $value): string |
||||
{ |
||||
return (new NestedScopeInner())->run($value) . ':outer'; |
||||
} |
||||
|
||||
public function callback(): array |
||||
{ |
||||
return [$this, 'invokeInner']; |
||||
} |
||||
|
||||
public function run(int $value): string |
||||
{ |
||||
$arguments = [[$this, 'invokeInner'], $value]; |
||||
return call_user_func(...$arguments); |
||||
} |
||||
} |
||||
|
||||
class NestedScopeInner |
||||
{ |
||||
private function value(int $value): string |
||||
{ |
||||
return 'inner:' . $value; |
||||
} |
||||
|
||||
public function run(int $value): string |
||||
{ |
||||
$arguments = [[$this, 'value'], $value]; |
||||
return call_user_func(...$arguments); |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$object = new ComplexScopeChild(); |
||||
$object->dynamicRelativeCallbacks(); |
||||
$object->parentCallback(); |
||||
|
||||
$delayed = $object->delayedUnpackedCallback(); |
||||
var_dump($delayed(4)); |
||||
var_dump($object->unpackedCallbackArray(5)); |
||||
var_dump($object->nestedDynamicClosure(6)); |
||||
|
||||
$nested = new NestedScopeOuter(); |
||||
$privateCallback = $nested->callback(); |
||||
var_dump($nested->run(7)); |
||||
var_dump(is_callable($privateCallback)); |
||||
} |
||||
|
||||
?> |
||||
--EXPECT-- |
||||
array(1) { |
||||
[0]=> |
||||
string(27) "ComplexScopeChild:private:1" |
||||
} |
||||
array(1) { |
||||
[0]=> |
||||
string(29) "ComplexScopeChild:protected:2" |
||||
} |
||||
array(1) { |
||||
[0]=> |
||||
string(29) "ComplexScopeChild:protected:3" |
||||
} |
||||
string(26) "ComplexScopeBase:private:4" |
||||
string(29) "ComplexScopeChild:protected:5" |
||||
string(26) "ComplexScopeBase:private:6" |
||||
string(13) "inner:7:outer" |
||||
bool(false) |
||||
@ -0,0 +1,28 @@ |
||||
--TEST-- |
||||
An instance property named static is not treated as late static binding |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class StaticPropertyValue |
||||
{ |
||||
public string $static = 'value'; |
||||
} |
||||
|
||||
class StaticPropertyReader |
||||
{ |
||||
public function read(?StaticPropertyValue $value): void |
||||
{ |
||||
var_dump($value->static); |
||||
var_dump($value?->static); |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
(new StaticPropertyReader())->read(new StaticPropertyValue()); |
||||
} |
||||
|
||||
?> |
||||
--EXPECT-- |
||||
string(5) "value" |
||||
string(5) "value" |
||||
Loading…
Reference in new issue