Skip to content

Commit

Permalink
Implemented property hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
kukulich committed Dec 2, 2024
1 parent 5151935 commit 600802a
Show file tree
Hide file tree
Showing 13 changed files with 913 additions and 27 deletions.
4 changes: 4 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ parameters:
- phar://%currentWorkingDirectory%/test/unit/Fixture/autoload.phar/vendor/autoload.php

ignoreErrors:
-
identifier: class.notFound
message: '#(unknown class|invalid type) PropertyHookType#'

-
identifier: missingType.generics
-
Expand Down
10 changes: 10 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,15 @@
<code><![CDATA[getEndLine]]></code>
<code><![CDATA[getEndLine]]></code>
<code><![CDATA[getEndLine]]></code>
<code><![CDATA[getEndLine]]></code>
<code><![CDATA[getReturnType]]></code>
<code><![CDATA[getStartLine]]></code>
<code><![CDATA[getStartLine]]></code>
<code><![CDATA[getStartLine]]></code>
<code><![CDATA[getStartLine]]></code>
<code><![CDATA[getStartLine]]></code>
<code><![CDATA[getStmts]]></code>
<code><![CDATA[getStmts]]></code>
<code><![CDATA[getSubNodeNames]]></code>
<code><![CDATA[traverse]]></code>
</ImpureMethodCall>
Expand Down Expand Up @@ -201,16 +205,22 @@
<ImpureMethodCall>
<code><![CDATA[__invoke]]></code>
<code><![CDATA[classExists]]></code>
<code><![CDATA[createFromNode]]></code>
<code><![CDATA[getEndLine]]></code>
<code><![CDATA[getStartLine]]></code>
<code><![CDATA[getStmts]]></code>
<code><![CDATA[getStmts]]></code>
<code><![CDATA[isPrivate]]></code>
<code><![CDATA[isPrivateSet]]></code>
<code><![CDATA[isProtected]]></code>
<code><![CDATA[isProtectedSet]]></code>
<code><![CDATA[isPublic]]></code>
<code><![CDATA[isPublic]]></code>
<code><![CDATA[isReadonly]]></code>
<code><![CDATA[isStatic]]></code>
<code><![CDATA[traitExists]]></code>
<code><![CDATA[traverse]]></code>
<code><![CDATA[traverse]]></code>
</ImpureMethodCall>
</file>
<file src="src/Reflection/ReflectionType.php">
Expand Down
44 changes: 34 additions & 10 deletions src/Reflection/Adapter/ReflectionNamedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,48 @@
/** @psalm-immutable */
final class ReflectionNamedType extends CoreReflectionNamedType
{
public function __construct(private BetterReflectionNamedType $betterReflectionType, private bool $allowsNull)
/** @var non-empty-string */
private string $nameType;

private bool $isBuiltin;

/** @var non-empty-string */
private string $toString;

/** @param \Roave\BetterReflection\Reflection\ReflectionNamedType|non-empty-string $type */
public function __construct(BetterReflectionNamedType|string $type, private bool $allowsNull = false)
{
if ($type instanceof BetterReflectionNamedType) {
$nameType = $type->getName();
$this->nameType = $nameType;
$this->isBuiltin = self::computeIsBuiltin($nameType, $type->isBuiltin());
$this->toString = $type->__toString();
} else {
$this->nameType = $type;
$this->isBuiltin = true;
$this->toString = $type;
}
}

public function getName(): string
{
return $this->betterReflectionType->getName();
return $this->nameType;
}

/** @return non-empty-string */
public function __toString(): string
{
$type = strtolower($this->betterReflectionType->getName());
$normalizedType = strtolower($this->nameType);

if (
! $this->allowsNull
|| $type === 'mixed'
|| $type === 'null'
|| $normalizedType === 'mixed'
|| $normalizedType === 'null'
) {
return $this->betterReflectionType->__toString();
return $this->toString;
}

return '?' . $this->betterReflectionType->__toString();
return '?' . $this->toString;
}

public function allowsNull(): bool
Expand All @@ -44,12 +63,17 @@ public function allowsNull(): bool

public function isBuiltin(): bool
{
$type = strtolower($this->betterReflectionType->getName());
return $this->isBuiltin;
}

private static function computeIsBuiltin(string $namedType, bool $isBuiltin): bool
{
$normalizedType = strtolower($namedType);

if ($type === 'self' || $type === 'parent' || $type === 'static') {
if ($normalizedType === 'self' || $normalizedType === 'parent' || $normalizedType === 'static') {
return false;
}

return $this->betterReflectionType->isBuiltin();
return $isBuiltin;
}
}
89 changes: 89 additions & 0 deletions src/Reflection/Adapter/ReflectionProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@

use ArgumentCountError;
use OutOfBoundsException;
use PropertyHookType;
use ReflectionException as CoreReflectionException;
use ReflectionMethod as CoreReflectionMethod;
use ReflectionProperty as CoreReflectionProperty;
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
use Roave\BetterReflection\Reflection\Exception\NotAnObject;
use Roave\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute;
use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod;
use Roave\BetterReflection\Reflection\ReflectionProperty as BetterReflectionProperty;
use Roave\BetterReflection\Reflection\ReflectionPropertyHookType as BetterReflectionPropertyHookType;
use Throwable;
use TypeError;
use ValueError;
Expand All @@ -30,6 +34,20 @@ final class ReflectionProperty extends CoreReflectionProperty
*/
public const IS_FINAL_COMPATIBILITY = 32;

/**
* @internal
*
* @see CoreReflectionProperty::IS_ABSTRACT
*/
public const IS_ABSTRACT_COMPATIBILITY = 64;

/**
* @internal
*
* @see CoreReflectionProperty::IS_VIRTUAL
*/
public const IS_VIRTUAL_COMPATIBILITY = 512;

/**
* @internal
*
Expand Down Expand Up @@ -156,6 +174,12 @@ public function isFinal(): bool
return $this->betterReflectionProperty->isFinal();
}

/** @psalm-mutation-free */
public function isAbstract(): bool
{
return $this->betterReflectionProperty->isAbstract();
}

/** @psalm-mutation-free */
public function isDefault(): bool
{
Expand Down Expand Up @@ -250,6 +274,71 @@ public function isReadOnly(): bool
return $this->betterReflectionProperty->isReadOnly();
}

/** @psalm-mutation-free */
public function isVirtual(): bool
{
return $this->betterReflectionProperty->isVirtual();
}

public function hasHooks(): bool
{
return $this->betterReflectionProperty->hasHooks();
}

/** @psalm-suppress UndefinedClass */
public function hasHook(PropertyHookType $hookType): bool
{
return $this->betterReflectionProperty->hasHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($hookType));
}

/** @psalm-suppress UndefinedClass */
public function getHook(PropertyHookType $hookType): ReflectionMethod|null
{
$hook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($hookType));
if ($hook === null) {
return null;
}

return new ReflectionMethod($hook);
}

/** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
public function getHooks(): array
{
return array_map(
static fn (BetterReflectionMethod $betterReflectionMethod): CoreReflectionMethod => new ReflectionMethod($betterReflectionMethod),
$this->betterReflectionProperty->getHooks(),
);
}

public function getSettableType(): ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null
{
$setHook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::Set);
if ($setHook !== null) {
return ReflectionType::fromTypeOrNull($setHook->getParameters()[0]->getType());
}

if ($this->isVirtual()) {
return new ReflectionNamedType('never');
}

return $this->getType();
}

public function getRawValue(object $object): mixed
{
throw new Exception\NotImplemented('Not implemented');
}

public function setRawValue(object $object, mixed $value): void
{
if ($this->hasHooks()) {
throw new Exception\NotImplemented('Not implemented');
}

$this->setValue($object, $value);
}

public function __get(string $name): mixed
{
if ($name === 'name') {
Expand Down
4 changes: 4 additions & 0 deletions src/Reflection/ReflectionClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ private function createImmediateMethods(ClassNode|InterfaceNode|TraitNode|EnumNo
$reflector,
$methodNode,
$this->locatedSource,
$methodNode->name->name,
$this->getNamespaceName(),
$this,
$this,
Expand All @@ -596,6 +597,8 @@ private function addEnumMethods(EnumNode $node, array $methods): array
{
$internalLocatedSource = new InternalLocatedSource('', $this->getName(), 'Core');
$createMethod = function (string $name, array $params, Node\Identifier|Node\NullableType $returnType) use ($internalLocatedSource): ReflectionMethod {
assert($name !== '');

/** @var array{flags: int, params: Node\Param[], returnType: Node\Identifier|Node\NullableType} $classMethodSubnodes */
$classMethodSubnodes = [
'flags' => Modifiers::PUBLIC | Modifiers::STATIC,
Expand All @@ -610,6 +613,7 @@ private function addEnumMethods(EnumNode $node, array $methods): array
$classMethodSubnodes,
),
$internalLocatedSource,
$name,
$this->getNamespaceName(),
$this,
$this,
Expand Down
8 changes: 4 additions & 4 deletions src/Reflection/ReflectionFunctionAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ abstract public function __toString(): string;
abstract public function getShortName(): string;

/** @psalm-external-mutation-free */
private function fillFromNode(MethodNode|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): void
private function fillFromNode(MethodNode|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): void
{
$this->parameters = $this->createParameters($node);
$this->returnsReference = $node->returnsByRef();
Expand Down Expand Up @@ -136,7 +136,7 @@ private function fillFromNode(MethodNode|Node\Stmt\Function_|Node\Expr\Closure|N
}

/** @return array<non-empty-string, ReflectionParameter> */
private function createParameters(Node\Stmt\ClassMethod|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): array
private function createParameters(Node\Stmt\ClassMethod|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): array
{
$parameters = [];

Expand Down Expand Up @@ -327,7 +327,7 @@ public function couldThrow(): bool
return $this->couldThrow;
}

private function computeCouldThrow(MethodNode|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): bool
private function computeCouldThrow(MethodNode|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): bool
{
$statements = $node->getStmts();

Expand Down Expand Up @@ -498,7 +498,7 @@ public function getTentativeReturnType(): ReflectionNamedType|ReflectionUnionTyp
return $this->returnType;
}

private function createReturnType(MethodNode|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null
private function createReturnType(MethodNode|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node): ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null
{
$returnType = $node->getReturnType();

Expand Down
15 changes: 10 additions & 5 deletions src/Reflection/ReflectionMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,37 +34,41 @@ class ReflectionMethod
private int $modifiers;

/**
* @param non-empty-string $name
* @param non-empty-string|null $aliasName
* @param non-empty-string|null $namespace
*/
private function __construct(
private Reflector $reflector,
MethodNode|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node,
MethodNode|Node\PropertyHook|Node\Stmt\Function_|Node\Expr\Closure|Node\Expr\ArrowFunction $node,
private LocatedSource $locatedSource,
string $name,
private string|null $namespace,
private ReflectionClass $declaringClass,
private ReflectionClass $implementingClass,
private ReflectionClass $currentClass,
private string|null $aliasName,
) {
assert($node instanceof MethodNode);
assert($node instanceof MethodNode || $node instanceof Node\PropertyHook);

$this->name = $node->name->name;
$this->modifiers = $this->computeModifiers($node);
$this->name = $name;
$this->modifiers = $node instanceof MethodNode ? $this->computeModifiers($node) : 0;

$this->fillFromNode($node);
}

/**
* @internal
*
* @param non-empty-string $name
* @param non-empty-string|null $aliasName
* @param non-empty-string|null $namespace
*/
public static function createFromNode(
Reflector $reflector,
MethodNode $node,
MethodNode|Node\PropertyHook $node,
LocatedSource $locatedSource,
string $name,
string|null $namespace,
ReflectionClass $declaringClass,
ReflectionClass $implementingClass,
Expand All @@ -75,6 +79,7 @@ public static function createFromNode(
$reflector,
$node,
$locatedSource,
$name,
$namespace,
$declaringClass,
$implementingClass,
Expand Down
Loading

0 comments on commit 600802a

Please sign in to comment.