Skip to content

Commit

Permalink
Add ConstantReflection (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
vudaltsov authored Aug 6, 2024
1 parent c7687b2 commit a199205
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 14 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.3] 2024-08-06

### Added

- Add `ConstantReflection` and `TyphoonReflector::reflectConstant()`.

## [0.4.2] 2024-08-05

### Added
Expand Down
106 changes: 106 additions & 0 deletions ConstantReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection;

use Typhoon\ChangeDetector\ChangeDetector;
use Typhoon\DeclarationId\ConstantId;
use Typhoon\Reflection\Internal\Data;
use Typhoon\Reflection\Internal\Misc\NonSerializable;
use Typhoon\Type\Type;
use Typhoon\TypedMap\TypedMap;

/**
* @api
*/
final class ConstantReflection
{
use NonSerializable;

public readonly ConstantId $id;

/**
* This internal property is public for testing purposes.
* It will likely be available as part of the API in the near future.
*
* @internal
* @psalm-internal Typhoon
*/
public readonly TypedMap $data;

/**
* @internal
* @psalm-internal Typhoon\Reflection
*/
public function __construct(
ConstantId $id,
TypedMap $data,
private readonly TyphoonReflector $reflector,
) {
$this->id = $id;
$this->data = $data;
}

/**
* @return ?non-empty-string
*/
public function extension(): ?string
{
return $this->data[Data::PhpExtension];
}

public function namespace(): string
{
return $this->data[Data::Namespace];
}

public function changeDetector(): ChangeDetector
{
return $this->data[Data::ChangeDetector];
}

public function location(): ?Location
{
return $this->data[Data::Location];
}

public function isInternallyDefined(): bool
{
return $this->data[Data::InternallyDefined];
}

/**
* @return ?non-empty-string
*/
public function phpDoc(): ?string
{
return $this->data[Data::PhpDoc]?->getText();
}

/**
* This method returns the actual class constant's value and thus might trigger autoloading or throw errors.
*/
public function evaluate(): mixed
{
return $this->data[Data::ValueExpression]->evaluate($this->reflector);
}

/**
* @return ($kind is TypeKind::Resolved ? Type : ?Type)
*/
public function type(TypeKind $kind = TypeKind::Resolved): ?Type
{
return $this->data[Data::Type]->get($kind);
}

public function isDeprecated(): bool
{
return $this->data[Data::Deprecation] !== null;
}

public function deprecation(): ?Deprecation
{
return $this->data[Data::Deprecation];
}
}
58 changes: 53 additions & 5 deletions Internal/ConstantExpression/ConstantFetch.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Typhoon\Reflection\Internal\ConstantExpression;

use Typhoon\Reflection\Exception\DeclarationNotFound;
use Typhoon\Reflection\TyphoonReflector;

/**
Expand All @@ -25,13 +26,35 @@ public function __construct(
/**
* @return non-empty-string
*/
public function name(?TyphoonReflector $_reflector = null): string
public function name(?TyphoonReflector $reflector = null): string
{
if ($this->globalName === null || \defined($this->namespacedName)) {
if (\defined($this->namespacedName)) {
return $this->namespacedName;
}

return $this->globalName;
if ($reflector !== null) {
try {
return $reflector->reflectConstant($this->namespacedName)->id->name;
} catch (DeclarationNotFound) {
}
}

if ($this->globalName === null) {
throw new \LogicException(\sprintf('Constant %s is not defined', $this->namespacedName));
}

if (\defined($this->globalName)) {
return $this->globalName;
}

if ($reflector !== null) {
try {
return $reflector->reflectConstant($this->globalName)->id->name;
} catch (DeclarationNotFound) {
}
}

throw new \LogicException(\sprintf('Constants %s and %s are not defined', $this->namespacedName, $this->globalName));
}

public function recompile(CompilationContext $context): Expression
Expand All @@ -41,7 +64,32 @@ public function recompile(CompilationContext $context): Expression

public function evaluate(?TyphoonReflector $reflector = null): mixed
{
// todo via reflection
return \constant($this->name($reflector));
if (\defined($this->namespacedName)) {
return \constant($this->namespacedName);
}

if ($reflector !== null) {
try {
return $reflector->reflectConstant($this->namespacedName);
} catch (DeclarationNotFound) {
}
}

if ($this->globalName === null) {
throw new \LogicException(\sprintf('Constant %s is not defined', $this->namespacedName));
}

if (\defined($this->globalName)) {
return \constant($this->globalName);
}

if ($reflector !== null) {
try {
return $reflector->reflectConstant($this->globalName);
} catch (DeclarationNotFound) {
}
}

throw new \LogicException(\sprintf('Constants %s and %s are not defined', $this->namespacedName, $this->globalName));
}
}
52 changes: 52 additions & 0 deletions Internal/NativeReflector/NativeReflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Typhoon\ChangeDetector\ChangeDetector;
use Typhoon\ChangeDetector\PhpExtensionVersionChangeDetector;
use Typhoon\ChangeDetector\PhpVersionChangeDetector;
use Typhoon\DeclarationId\ConstantId;
use Typhoon\DeclarationId\Id;
use Typhoon\DeclarationId\NamedClassId;
use Typhoon\DeclarationId\NamedFunctionId;
Expand All @@ -23,6 +24,7 @@
use Typhoon\Type\types;
use Typhoon\TypedMap\TypedMap;
use function Typhoon\Reflection\Internal\class_like_exists;
use function Typhoon\Reflection\Internal\get_namespace;

/**
* @internal
Expand All @@ -32,6 +34,23 @@ final class NativeReflector
{
private const CORE_EXTENSION = 'Core';

public function reflectConstant(ConstantId $id): ?TypedMap
{
if (!\defined($id->name)) {
return null;
}

$value = \constant($id->name);
$extension = $this->constantExtensions()[$id->name] ?? null;

return (new TypedMap())
->with(Data::Type, new TypeData(inferred: types::value(\constant($id->name))))
->with(Data::Namespace, get_namespace($id->name))
->with(Data::ValueExpression, Value::from($value))
->with(Data::PhpExtension, $extension)
->with(Data::InternallyDefined, $extension !== null);
}

public function reflectNamedFunction(NamedFunctionId $id): ?TypedMap
{
if (!\function_exists($id->name)) {
Expand Down Expand Up @@ -425,4 +444,37 @@ private function reflectNameAsType(string $name, ?\ReflectionClass $static, ?\Re
default => types::object($name),
};
}

/**
* @var ?array<non-empty-string, non-empty-string>
*/
private ?array $constantExtensions = null;

/**
* @return array<non-empty-string, non-empty-string>
*/
private function constantExtensions(): array
{
if ($this->constantExtensions !== null) {
return $this->constantExtensions;
}

$this->constantExtensions = [];

foreach (get_defined_constants(categorize: true) as $category => $constants) {
if ($category === 'user') {
continue;
}

foreach ($constants as $name => $_value) {
/**
* @var non-empty-string $name
* @var non-empty-string $category
*/
$this->constantExtensions[$name] = $category;
}
}

return $this->constantExtensions;
}
}
15 changes: 14 additions & 1 deletion Internal/PhpDoc/PhpDocReflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use Typhoon\DeclarationId\AnonymousClassId;
use Typhoon\DeclarationId\AnonymousFunctionId;
use Typhoon\DeclarationId\ConstantId;
use Typhoon\DeclarationId\NamedClassId;
use Typhoon\DeclarationId\NamedFunctionId;
use Typhoon\Reflection\Annotated\CustomTypeResolver;
Expand All @@ -32,6 +33,7 @@
use Typhoon\Reflection\Internal\Data\TypeData;
use Typhoon\Reflection\Internal\Data\Visibility;
use Typhoon\Reflection\Internal\Hook\ClassHook;
use Typhoon\Reflection\Internal\Hook\ConstantHook;
use Typhoon\Reflection\Internal\Hook\FunctionHook;
use Typhoon\Reflection\Location;
use Typhoon\Reflection\TyphoonReflector;
Expand All @@ -45,7 +47,7 @@
* @internal
* @psalm-internal Typhoon\Reflection
*/
final class PhpDocReflector implements AnnotatedDeclarationsDiscoverer, FunctionHook, ClassHook
final class PhpDocReflector implements AnnotatedDeclarationsDiscoverer, ConstantHook, FunctionHook, ClassHook
{
public function __construct(
private readonly CustomTypeResolver $customTypeResolver = new NullCustomTypeResolver(),
Expand Down Expand Up @@ -86,6 +88,17 @@ public function priority(): int
return 500;
}

public function processConstant(ConstantId $id, TypedMap $data, TyphoonReflector $reflector): TypedMap
{
$phpDoc = $this->parsePhpDoc($data[Data::PhpDoc]);

if ($phpDoc !== null) {
$data = $data->with(Data::Type, $this->addAnnotatedType($data[Data::Context], $data[Data::Type], $phpDoc->varType()));
}

return $data;
}

public function processFunction(NamedFunctionId|AnonymousFunctionId $id, TypedMap $data, TyphoonReflector $reflector): TypedMap
{
return $this->reflectFunctionLike($data);
Expand Down
3 changes: 2 additions & 1 deletion Internal/PhpParser/CodeReflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\Parser;
use Typhoon\DeclarationId\AnonymousClassId;
use Typhoon\DeclarationId\ConstantId;
use Typhoon\DeclarationId\Internal\IdMap;
use Typhoon\DeclarationId\NamedClassId;
use Typhoon\DeclarationId\NamedFunctionId;
Expand All @@ -29,7 +30,7 @@ public function __construct(

/**
* @param ?non-empty-string $file
* @return IdMap<NamedFunctionId|NamedClassId|AnonymousClassId, \Closure(): TypedMap>
* @return IdMap<ConstantId|NamedFunctionId|NamedClassId|AnonymousClassId, \Closure(): TypedMap>
*/
public function reflectCode(string $code, ?string $file = null): IdMap
{
Expand Down
Loading

0 comments on commit a199205

Please sign in to comment.