refactor(compiler): move composite type checker and property access methods to traits

- Extract composite type checking logic to new CompositeTypeCheckerTrait
- Move dynamic property access methods from CompilerBase to PropertyAccessTrait
- Add use statement for CompositeTypeCheckerTrait in CompilerBase
- Remove duplicate method implementations from CompilerBase
- Maintain all existing functionality through trait-based code organization
pull/17/head
韩天峰 2 months ago
parent 7d34cad400
commit 223c9b22b4
  1. 360
      src/CompilerBase.php
  2. 179
      src/Parser/PropertyAccessTrait.php
  3. 196
      src/TypeSystem/CompositeTypeCheckerTrait.php

@ -60,6 +60,7 @@ use TypePhp\Resolver\PropertyAssignTypeInfo;
use TypePhp\Resolver\PropertyWriteTarget;
use TypePhp\Resolver\StaticPropertyFetchResolution;
use TypePhp\Resolver\StaticPropertyFetchTarget;
use TypePhp\TypeSystem\CompositeTypeCheckerTrait;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\Node\ArrayItem;
@ -82,6 +83,7 @@ use PhpParser\PrettyPrinter;
class CompilerBase implements PropertyAccessContext
{
public const string DEFAULT_PHP_VERSION = '8.5';
use CompositeTypeCheckerTrait;
use AstNodeType;
use FuncCallOptimizer;
use AnonClassGenerator;
@ -5100,184 +5102,6 @@ class CompilerBase implements PropertyAccessContext
}
}
protected function emitDynamicPropertyRead(string $object, string $property): string
{
return "{$object}.getProperty({$property})";
}
protected function emitDynamicPropertyWrite(string $object, string $property, string $value): string
{
$scope = $this->class ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr';
return 'typephp_write_property_scoped('
. $object . ', ' . $property . ', ' . $value . ', ' . $scope . ')';
}
protected function emitDynamicPropertyTargetRead(PropertyWriteTarget $target): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyRead($target->getDynamicObjectExpr(), $target->getDynamicPropertyExpr());
}
protected function emitDynamicPropertyTargetWrite(PropertyWriteTarget $target, string $value): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyWrite($target->getDynamicObjectExpr(), $target->getDynamicPropertyExpr(), $value);
}
protected function emitDynamicPropertyTargetUnset(PropertyWriteTarget $target): string
{
$this->assertDynamicPropertyTarget($target);
return $target->getDynamicObjectExpr() . '.unsetProperty(' . $target->getDynamicPropertyExpr() . ')';
}
protected function emitDynamicPropertyTargetRef(PropertyWriteTarget $target): string
{
$this->assertDynamicPropertyTarget($target);
return $target->getDynamicObjectExpr() . '.attrRef(' . $target->getDynamicPropertyExpr() . ')';
}
protected function emitDynamicPropertyTargetAppendArray(PropertyWriteTarget $target, string $value): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyAppendArray(
$target->getDynamicObjectExpr(),
$target->getDynamicPropertyExpr(),
$value
);
}
protected function emitDynamicPropertyTargetUpdateArray(PropertyWriteTarget $target, string $dim, string $value): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyUpdateArray(
$target->getDynamicObjectExpr(),
$target->getDynamicPropertyExpr(),
$dim,
$value
);
}
protected function canEmitDynamicPropertyTarget(?PropertyWriteTarget $target): bool
{
return $target !== null && $target->isDynamicObjectProperty();
}
protected function emitDynamicPropertyFetchRead(Expr\PropertyFetch $expr, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetRead($target);
}
return $this->emitDynamicPropertyRead(
$this->parseIdentifier($expr->var),
$this->identifierToStr($expr->name, literal: true)
);
}
protected function emitDynamicPropertyFetchWrite(Expr\PropertyFetch $expr, string $value, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetWrite($target, $value);
}
return $this->emitDynamicPropertyWrite(
$this->parseIdentifier($expr->var),
$this->identifierToStr($expr->name, literal: true),
$value
);
}
protected function getDynamicPropertyFetchObjectExpr(Expr\PropertyFetch $expr, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $target->getDynamicObjectExpr();
}
return $this->parseIdentifier($expr->var);
}
protected function emitDynamicPropertyFetchUnset(Expr\PropertyFetch $expr, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetUnset($target);
}
return $this->parseIdentifier($expr->var) . '.unsetProperty(' . $this->identifierToStr($expr->name, literal: true) . ')';
}
protected function emitDynamicPropertyFetchAppendArray(Expr\PropertyFetch $expr, string $value, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetAppendArray($target, $value);
}
return $this->emitDynamicPropertyAppendArray(
$this->parseIdentifier($expr->var),
$this->identifierToStr($expr->name, literal: true),
$value
);
}
protected function emitDynamicPropertyFetchUpdateArray(Expr\PropertyFetch $expr, string $dim, string $value, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetUpdateArray($target, $dim, $value);
}
return $this->emitDynamicPropertyUpdateArray(
$this->parseIdentifier($expr->var),
$this->identifierToStr($expr->name, literal: true),
$dim,
$value
);
}
protected function emitDynamicPropertyAppendArray(string $object, string $property, string $value): string
{
return "{$object}.attr({$property}, true).newItem() = {$value}";
}
protected function emitDynamicPropertyUpdateArray(string $object, string $property, string $dim, string $value): string
{
return "{$object}.attr({$property}, true).item({$dim}, true) = {$value}";
}
protected function assertDynamicPropertyTarget(PropertyWriteTarget $target): void
{
if (!$target->isDynamicObjectProperty()) {
$this->fatalError($target->node, 'Internal error: property write target is not a dynamic object property');
}
}
protected function emitDynamicPropertyFetchRef(Expr\PropertyFetch $expr, NodeAbstract $errorNode): string
{
$target = $this->preparePropertyWriteTarget($expr);
if ($this->canEmitDynamicPropertyTarget($target)) {
$objectExpr = $target->getDynamicObjectExpr();
if (!$this->hasVar($objectExpr)) {
$this->fatalError($errorNode, 'Undefined variable `$' . $objectExpr . '`');
}
return $this->emitDynamicPropertyTargetRef($target);
}
if (!$this->isVarExpr($expr->var)) {
return $this->parseExpr($expr->var) . '.attrRef(' . $this->identifierToStr($expr->name) . ')';
}
$objectExpr = $this->parseIdentifier($expr->var);
if (!$this->hasVar($objectExpr)) {
$this->fatalError($errorNode, 'Undefined variable `$' . $objectExpr . '`');
}
return $objectExpr . '.attrRef(' . $this->identifierToStr($expr->name) . ')';
}
protected function getChainedFunc(string $op): string
{
return match ($op) {
@ -5778,186 +5602,6 @@ class CompilerBase implements PropertyAccessContext
* The outer descriptor list is a union (OR); an allOf entry represents an
* intersection (AND). Nullable is represented by an isNull union member.
*/
protected function checkCompositeTypeAssignment(
NodeAbstract $errorNode,
array $typeCheck,
string $typeStr,
NodeAbstract $value,
string $context
): int {
$relation = $this->compositeTypeRelation($value, $typeCheck);
if ($relation !== self::COMPOSITE_TYPE_MISMATCH) {
return $relation;
}
$valueType = $this->staticTypeNameOfExpr($value);
$this->fatalError($errorNode, "Cannot assign {$valueType} to {$context} of type `{$typeStr}`");
}
protected function compositeTypeRelation(NodeAbstract $value, array $clauses): int
{
// TYPE_VAR means that the expression is dynamic or its result cannot
// be represented by the current scalar type system. It must retain the
// runtime type check.
if ($this->detectTypeOfExpr($value) === self::TYPE_VAR && !$this->isNullExpr($value)) {
return self::COMPOSITE_TYPE_UNKNOWN;
}
$hasUnknown = false;
foreach ($clauses as $clause) {
$relation = $this->compositeTypeClauseRelation($value, $clause);
if ($relation === self::COMPOSITE_TYPE_MATCH) {
return self::COMPOSITE_TYPE_MATCH;
}
if ($relation === self::COMPOSITE_TYPE_UNKNOWN) {
$hasUnknown = true;
}
}
return $hasUnknown ? self::COMPOSITE_TYPE_UNKNOWN : self::COMPOSITE_TYPE_MISMATCH;
}
protected function compositeTypeClauseRelation(NodeAbstract $value, array $clause): int
{
if (($clause['kind'] ?? '') === 'allOf') {
$hasUnknown = false;
foreach ($clause['types'] ?? [] as $entry) {
$relation = $this->compositeTypeEntryRelation($value, $entry);
if ($relation === self::COMPOSITE_TYPE_MISMATCH) {
return self::COMPOSITE_TYPE_MISMATCH;
}
if ($relation === self::COMPOSITE_TYPE_UNKNOWN) {
$hasUnknown = true;
}
}
return $hasUnknown ? self::COMPOSITE_TYPE_UNKNOWN : self::COMPOSITE_TYPE_MATCH;
}
return $this->compositeTypeEntryRelation($value, $clause);
}
protected function compositeTypeEntryRelation(NodeAbstract $value, array $entry): int
{
$kind = $entry['kind'] ?? '';
if ($kind === 'isNull') {
return $this->isNullExpr($value) ? self::COMPOSITE_TYPE_MATCH : self::COMPOSITE_TYPE_MISMATCH;
}
$type = $this->detectTypeOfExpr($value);
return match ($kind) {
'isInt' => $this->exactCompositeTypeRelation($type, self::TYPE_INT),
// PHP permits int -> float widening. It is compatible but still
// needs conversion, so retain the runtime normalization path.
'isFloat' => $type === self::TYPE_INT
? self::COMPOSITE_TYPE_UNKNOWN
: $this->exactCompositeTypeRelation($type, self::TYPE_FLOAT),
'isBool' => $this->exactCompositeTypeRelation($type, self::TYPE_BOOL),
'isString' => $this->exactCompositeTypeRelation($type, self::TYPE_STR),
'isArray' => $this->exactCompositeTypeRelation($type, self::TYPE_ARRAY),
'isObject' => $this->exactCompositeTypeRelation($type, self::TYPE_OBJECT),
'isTrue' => $this->compositeLiteralBoolRelation($value, true),
'isFalse' => $this->compositeLiteralBoolRelation($value, false),
'isResource' => $this->exactCompositeTypeRelation($type, self::TYPE_RESOURCE),
'callable' => $this->compositeCallableRelation($value, $type),
'iterable' => $this->compositeIterableRelation($value, $type),
'instanceof' => $this->compositeObjectEntryRelation($value, $entry),
default => self::COMPOSITE_TYPE_UNKNOWN,
};
}
protected function exactCompositeTypeRelation(string $actual, string $expected): int
{
return $actual === $expected ? self::COMPOSITE_TYPE_MATCH : self::COMPOSITE_TYPE_MISMATCH;
}
protected function compositeLiteralBoolRelation(NodeAbstract $value, bool $expected): int
{
if ($this->isScalarBool($value)) {
$actual = strcasecmp($value->name->toString(), 'true') === 0;
return $actual === $expected ? self::COMPOSITE_TYPE_MATCH : self::COMPOSITE_TYPE_MISMATCH;
}
return $this->detectTypeOfExpr($value) === self::TYPE_BOOL
? self::COMPOSITE_TYPE_UNKNOWN
: self::COMPOSITE_TYPE_MISMATCH;
}
protected function compositeCallableRelation(NodeAbstract $value, string $type): int
{
if ($type === self::TYPE_STR || $type === self::TYPE_ARRAY || $type === self::TYPE_OBJECT) {
return self::COMPOSITE_TYPE_UNKNOWN;
}
return self::COMPOSITE_TYPE_MISMATCH;
}
protected function compositeIterableRelation(NodeAbstract $value, string $type): int
{
if ($type === self::TYPE_ARRAY) {
return self::COMPOSITE_TYPE_MATCH;
}
if ($type !== self::TYPE_OBJECT) {
return self::COMPOSITE_TYPE_MISMATCH;
}
return $this->compositeObjectTypeRelation($value, 'Traversable');
}
protected function compositeObjectEntryRelation(NodeAbstract $value, array $entry): int
{
if ($this->detectTypeOfExpr($value) !== self::TYPE_OBJECT) {
return self::COMPOSITE_TYPE_MISMATCH;
}
return $this->compositeObjectTypeRelation($value, $entry['class'] ?? '');
}
protected function compositeObjectTypeRelation(NodeAbstract $value, string $expected): int
{
$class = $this->detectDeclaredClassOfExpr($value);
if ($class === '') {
return self::COMPOSITE_TYPE_UNKNOWN;
}
if ($expected === '' || $expected === 'static') {
return self::COMPOSITE_TYPE_UNKNOWN;
}
$actualKnown = $this->hasClass($class)
|| $this->hasInterface($class)
|| $this->isInternalClass($class)
|| $this->isInternalInterface($class);
$expectedKnown = $this->hasClass($expected)
|| $this->hasInterface($expected)
|| $this->isInternalClass($expected)
|| $this->isInternalInterface($expected);
if (!$actualKnown || !$expectedKnown) {
return self::COMPOSITE_TYPE_UNKNOWN;
}
return $this->isObjectClassStaticallyAssignableTo($class, $expected)
? self::COMPOSITE_TYPE_MATCH
: self::COMPOSITE_TYPE_MISMATCH;
}
protected function isNullExpr(NodeAbstract $expr): bool
{
return $expr instanceof Expr\ConstFetch
&& strcasecmp($this->parseIdentifier($expr->name), 'null') === 0;
}
protected function staticTypeNameOfExpr(NodeAbstract $expr): string
{
if ($this->isNullExpr($expr)) {
return 'null';
}
$type = $this->detectTypeOfExpr($expr);
return match ($type) {
self::TYPE_INT => 'int',
self::TYPE_FLOAT => 'float',
self::TYPE_BOOL => 'bool',
self::TYPE_STR => 'string',
self::TYPE_ARRAY => 'array',
self::TYPE_OBJECT => 'object',
default => 'mixed',
};
}
protected function mustNoCall(NodeAbstract $node): void
{
$nodeFinder = new NodeFinder();

@ -21,6 +21,185 @@ use TypePhp\Symbol;
trait PropertyAccessTrait
{
protected function emitDynamicPropertyRead(string $object, string $property): string
{
return "{$object}.getProperty({$property})";
}
protected function emitDynamicPropertyWrite(string $object, string $property, string $value): string
{
$scope = $this->class ? $this->getClassEntryPtr($this->getFullClassName()) : 'nullptr';
return 'typephp_write_property_scoped('
. $object . ', ' . $property . ', ' . $value . ', ' . $scope . ')';
}
protected function emitDynamicPropertyTargetRead(PropertyWriteTarget $target): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyRead($target->getDynamicObjectExpr(), $target->getDynamicPropertyExpr());
}
protected function emitDynamicPropertyTargetWrite(PropertyWriteTarget $target, string $value): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyWrite($target->getDynamicObjectExpr(), $target->getDynamicPropertyExpr(), $value);
}
protected function emitDynamicPropertyTargetUnset(PropertyWriteTarget $target): string
{
$this->assertDynamicPropertyTarget($target);
return $target->getDynamicObjectExpr() . '.unsetProperty(' . $target->getDynamicPropertyExpr() . ')';
}
protected function emitDynamicPropertyTargetRef(PropertyWriteTarget $target): string
{
$this->assertDynamicPropertyTarget($target);
return $target->getDynamicObjectExpr() . '.attrRef(' . $target->getDynamicPropertyExpr() . ')';
}
protected function emitDynamicPropertyTargetAppendArray(PropertyWriteTarget $target, string $value): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyAppendArray(
$target->getDynamicObjectExpr(),
$target->getDynamicPropertyExpr(),
$value
);
}
protected function emitDynamicPropertyTargetUpdateArray(PropertyWriteTarget $target, string $dim, string $value): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyUpdateArray(
$target->getDynamicObjectExpr(),
$target->getDynamicPropertyExpr(),
$dim,
$value
);
}
protected function canEmitDynamicPropertyTarget(?PropertyWriteTarget $target): bool
{
return $target !== null && $target->isDynamicObjectProperty();
}
protected function emitDynamicPropertyFetchRead(Expr\PropertyFetch $expr, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetRead($target);
}
return $this->emitDynamicPropertyRead(
$this->parseIdentifier($expr->var),
$this->identifierToStr($expr->name, literal: true)
);
}
protected function emitDynamicPropertyFetchWrite(Expr\PropertyFetch $expr, string $value, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetWrite($target, $value);
}
return $this->emitDynamicPropertyWrite(
$this->parseIdentifier($expr->var),
$this->identifierToStr($expr->name, literal: true),
$value
);
}
protected function getDynamicPropertyFetchObjectExpr(Expr\PropertyFetch $expr, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $target->getDynamicObjectExpr();
}
return $this->parseIdentifier($expr->var);
}
protected function emitDynamicPropertyFetchUnset(Expr\PropertyFetch $expr, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetUnset($target);
}
return $this->parseIdentifier($expr->var) . '.unsetProperty(' . $this->identifierToStr($expr->name, literal: true) . ')';
}
protected function emitDynamicPropertyFetchAppendArray(Expr\PropertyFetch $expr, string $value, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetAppendArray($target, $value);
}
return $this->emitDynamicPropertyAppendArray(
$this->parseIdentifier($expr->var),
$this->identifierToStr($expr->name, literal: true),
$value
);
}
protected function emitDynamicPropertyFetchUpdateArray(Expr\PropertyFetch $expr, string $dim, string $value, ?PropertyWriteTarget $target = null): string
{
if ($this->canEmitDynamicPropertyTarget($target)) {
return $this->emitDynamicPropertyTargetUpdateArray($target, $dim, $value);
}
return $this->emitDynamicPropertyUpdateArray(
$this->parseIdentifier($expr->var),
$this->identifierToStr($expr->name, literal: true),
$dim,
$value
);
}
protected function emitDynamicPropertyAppendArray(string $object, string $property, string $value): string
{
return "{$object}.attr({$property}, true).newItem() = {$value}";
}
protected function emitDynamicPropertyUpdateArray(string $object, string $property, string $dim, string $value): string
{
return "{$object}.attr({$property}, true).item({$dim}, true) = {$value}";
}
protected function assertDynamicPropertyTarget(PropertyWriteTarget $target): void
{
if (!$target->isDynamicObjectProperty()) {
$this->fatalError($target->node, 'Internal error: property write target is not a dynamic object property');
}
}
protected function emitDynamicPropertyFetchRef(Expr\PropertyFetch $expr, NodeAbstract $errorNode): string
{
$target = $this->preparePropertyWriteTarget($expr);
if ($this->canEmitDynamicPropertyTarget($target)) {
$objectExpr = $target->getDynamicObjectExpr();
if (!$this->hasVar($objectExpr)) {
$this->fatalError($errorNode, 'Undefined variable `$' . $objectExpr . '`');
}
return $this->emitDynamicPropertyTargetRef($target);
}
if (!$this->isVarExpr($expr->var)) {
return $this->parseExpr($expr->var) . '.attrRef(' . $this->identifierToStr($expr->name) . ')';
}
$objectExpr = $this->parseIdentifier($expr->var);
if (!$this->hasVar($objectExpr)) {
$this->fatalError($errorNode, 'Undefined variable `$' . $objectExpr . '`');
}
return $objectExpr . '.attrRef(' . $this->identifierToStr($expr->name) . ')';
}
protected function resolveNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): ?StaticPropertyFetchResolution
{
$target = $this->resolveStaticPropertyFetchTarget($expr);

@ -0,0 +1,196 @@
<?php
/**
* This file is part of TypePHP.
*
* Evaluates static relations for union, intersection, nullable, and literal types.
*/
namespace TypePhp\TypeSystem;
use PhpParser\Node\Expr;
use PhpParser\NodeAbstract;
trait CompositeTypeCheckerTrait
{
protected function checkCompositeTypeAssignment(
NodeAbstract $errorNode,
array $typeCheck,
string $typeStr,
NodeAbstract $value,
string $context
): int {
$relation = $this->compositeTypeRelation($value, $typeCheck);
if ($relation !== self::COMPOSITE_TYPE_MISMATCH) {
return $relation;
}
$valueType = $this->staticTypeNameOfExpr($value);
$this->fatalError($errorNode, "Cannot assign {$valueType} to {$context} of type `{$typeStr}`");
}
protected function compositeTypeRelation(NodeAbstract $value, array $clauses): int
{
// TYPE_VAR means that the expression is dynamic or its result cannot
// be represented by the current scalar type system. It must retain the
// runtime type check.
if ($this->detectTypeOfExpr($value) === self::TYPE_VAR && !$this->isNullExpr($value)) {
return self::COMPOSITE_TYPE_UNKNOWN;
}
$hasUnknown = false;
foreach ($clauses as $clause) {
$relation = $this->compositeTypeClauseRelation($value, $clause);
if ($relation === self::COMPOSITE_TYPE_MATCH) {
return self::COMPOSITE_TYPE_MATCH;
}
if ($relation === self::COMPOSITE_TYPE_UNKNOWN) {
$hasUnknown = true;
}
}
return $hasUnknown ? self::COMPOSITE_TYPE_UNKNOWN : self::COMPOSITE_TYPE_MISMATCH;
}
protected function compositeTypeClauseRelation(NodeAbstract $value, array $clause): int
{
if (($clause['kind'] ?? '') === 'allOf') {
$hasUnknown = false;
foreach ($clause['types'] ?? [] as $entry) {
$relation = $this->compositeTypeEntryRelation($value, $entry);
if ($relation === self::COMPOSITE_TYPE_MISMATCH) {
return self::COMPOSITE_TYPE_MISMATCH;
}
if ($relation === self::COMPOSITE_TYPE_UNKNOWN) {
$hasUnknown = true;
}
}
return $hasUnknown ? self::COMPOSITE_TYPE_UNKNOWN : self::COMPOSITE_TYPE_MATCH;
}
return $this->compositeTypeEntryRelation($value, $clause);
}
protected function compositeTypeEntryRelation(NodeAbstract $value, array $entry): int
{
$kind = $entry['kind'] ?? '';
if ($kind === 'isNull') {
return $this->isNullExpr($value) ? self::COMPOSITE_TYPE_MATCH : self::COMPOSITE_TYPE_MISMATCH;
}
$type = $this->detectTypeOfExpr($value);
return match ($kind) {
'isInt' => $this->exactCompositeTypeRelation($type, self::TYPE_INT),
// PHP permits int -> float widening. It is compatible but still
// needs conversion, so retain the runtime normalization path.
'isFloat' => $type === self::TYPE_INT
? self::COMPOSITE_TYPE_UNKNOWN
: $this->exactCompositeTypeRelation($type, self::TYPE_FLOAT),
'isBool' => $this->exactCompositeTypeRelation($type, self::TYPE_BOOL),
'isString' => $this->exactCompositeTypeRelation($type, self::TYPE_STR),
'isArray' => $this->exactCompositeTypeRelation($type, self::TYPE_ARRAY),
'isObject' => $this->exactCompositeTypeRelation($type, self::TYPE_OBJECT),
'isTrue' => $this->compositeLiteralBoolRelation($value, true),
'isFalse' => $this->compositeLiteralBoolRelation($value, false),
'isResource' => $this->exactCompositeTypeRelation($type, self::TYPE_RESOURCE),
'callable' => $this->compositeCallableRelation($value, $type),
'iterable' => $this->compositeIterableRelation($value, $type),
'instanceof' => $this->compositeObjectEntryRelation($value, $entry),
default => self::COMPOSITE_TYPE_UNKNOWN,
};
}
protected function exactCompositeTypeRelation(string $actual, string $expected): int
{
return $actual === $expected ? self::COMPOSITE_TYPE_MATCH : self::COMPOSITE_TYPE_MISMATCH;
}
protected function compositeLiteralBoolRelation(NodeAbstract $value, bool $expected): int
{
if ($this->isScalarBool($value)) {
$actual = strcasecmp($value->name->toString(), 'true') === 0;
return $actual === $expected ? self::COMPOSITE_TYPE_MATCH : self::COMPOSITE_TYPE_MISMATCH;
}
return $this->detectTypeOfExpr($value) === self::TYPE_BOOL
? self::COMPOSITE_TYPE_UNKNOWN
: self::COMPOSITE_TYPE_MISMATCH;
}
protected function compositeCallableRelation(NodeAbstract $value, string $type): int
{
if ($type === self::TYPE_STR || $type === self::TYPE_ARRAY || $type === self::TYPE_OBJECT) {
return self::COMPOSITE_TYPE_UNKNOWN;
}
return self::COMPOSITE_TYPE_MISMATCH;
}
protected function compositeIterableRelation(NodeAbstract $value, string $type): int
{
if ($type === self::TYPE_ARRAY) {
return self::COMPOSITE_TYPE_MATCH;
}
if ($type !== self::TYPE_OBJECT) {
return self::COMPOSITE_TYPE_MISMATCH;
}
return $this->compositeObjectTypeRelation($value, 'Traversable');
}
protected function compositeObjectEntryRelation(NodeAbstract $value, array $entry): int
{
if ($this->detectTypeOfExpr($value) !== self::TYPE_OBJECT) {
return self::COMPOSITE_TYPE_MISMATCH;
}
return $this->compositeObjectTypeRelation($value, $entry['class'] ?? '');
}
protected function compositeObjectTypeRelation(NodeAbstract $value, string $expected): int
{
$class = $this->detectDeclaredClassOfExpr($value);
if ($class === '') {
return self::COMPOSITE_TYPE_UNKNOWN;
}
if ($expected === '' || $expected === 'static') {
return self::COMPOSITE_TYPE_UNKNOWN;
}
$actualKnown = $this->hasClass($class)
|| $this->hasInterface($class)
|| $this->isInternalClass($class)
|| $this->isInternalInterface($class);
$expectedKnown = $this->hasClass($expected)
|| $this->hasInterface($expected)
|| $this->isInternalClass($expected)
|| $this->isInternalInterface($expected);
if (!$actualKnown || !$expectedKnown) {
return self::COMPOSITE_TYPE_UNKNOWN;
}
return $this->isObjectClassStaticallyAssignableTo($class, $expected)
? self::COMPOSITE_TYPE_MATCH
: self::COMPOSITE_TYPE_MISMATCH;
}
protected function isNullExpr(NodeAbstract $expr): bool
{
return $expr instanceof Expr\ConstFetch
&& strcasecmp($this->parseIdentifier($expr->name), 'null') === 0;
}
protected function staticTypeNameOfExpr(NodeAbstract $expr): string
{
if ($this->isNullExpr($expr)) {
return 'null';
}
$type = $this->detectTypeOfExpr($expr);
return match ($type) {
self::TYPE_INT => 'int',
self::TYPE_FLOAT => 'float',
self::TYPE_BOOL => 'bool',
self::TYPE_STR => 'string',
self::TYPE_ARRAY => 'array',
self::TYPE_OBJECT => 'object',
default => 'mixed',
};
}
}
Loading…
Cancel
Save