TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
7.3 KiB
198 lines
7.3 KiB
<?php
|
|
/**
|
|
* This file is part of TypePHP.
|
|
*
|
|
* Evaluates static relations for union, intersection, nullable, and literal types.
|
|
*/
|
|
|
|
namespace TypePhp\TypeSystem;
|
|
|
|
use TypePhp\Type;
|
|
|
|
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) === 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, Type::INT),
|
|
// PHP permits int -> float widening. It is compatible but still
|
|
// needs conversion, so retain the runtime normalization path.
|
|
'isFloat' => $type === Type::INT
|
|
? self::COMPOSITE_TYPE_UNKNOWN
|
|
: $this->exactCompositeTypeRelation($type, Type::FLOAT),
|
|
'isBool' => $this->exactCompositeTypeRelation($type, Type::BOOL),
|
|
'isString' => $this->exactCompositeTypeRelation($type, Type::STR),
|
|
'isArray' => $this->exactCompositeTypeRelation($type, Type::ARRAY),
|
|
'isObject' => $this->exactCompositeTypeRelation($type, Type::OBJECT),
|
|
'isTrue' => $this->compositeLiteralBoolRelation($value, true),
|
|
'isFalse' => $this->compositeLiteralBoolRelation($value, false),
|
|
'isResource' => $this->exactCompositeTypeRelation($type, 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) === Type::BOOL
|
|
? self::COMPOSITE_TYPE_UNKNOWN
|
|
: self::COMPOSITE_TYPE_MISMATCH;
|
|
}
|
|
|
|
protected function compositeCallableRelation(NodeAbstract $value, string $type): int
|
|
{
|
|
if ($type === Type::STR || $type === Type::ARRAY || $type === Type::OBJECT) {
|
|
return self::COMPOSITE_TYPE_UNKNOWN;
|
|
}
|
|
return self::COMPOSITE_TYPE_MISMATCH;
|
|
}
|
|
|
|
protected function compositeIterableRelation(NodeAbstract $value, string $type): int
|
|
{
|
|
if ($type === Type::ARRAY) {
|
|
return self::COMPOSITE_TYPE_MATCH;
|
|
}
|
|
if ($type !== Type::OBJECT) {
|
|
return self::COMPOSITE_TYPE_MISMATCH;
|
|
}
|
|
return $this->compositeObjectTypeRelation($value, 'Traversable');
|
|
}
|
|
|
|
protected function compositeObjectEntryRelation(NodeAbstract $value, array $entry): int
|
|
{
|
|
if ($this->detectTypeOfExpr($value) !== 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) {
|
|
Type::INT => 'int',
|
|
Type::FLOAT => 'float',
|
|
Type::BOOL => 'bool',
|
|
Type::STR => 'string',
|
|
Type::ARRAY => 'array',
|
|
Type::OBJECT => 'object',
|
|
default => 'mixed',
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
|