parent
821c686ab0
commit
b3898c329d
17 changed files with 324 additions and 9 deletions
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
#[Native] |
||||||
|
class NativeUint64Property |
||||||
|
{ |
||||||
|
public uint64 $value; |
||||||
|
} |
||||||
@ -0,0 +1,119 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP. |
||||||
|
* |
||||||
|
* @link https://www.swoole.com/ |
||||||
|
* @contact service@swoole.com |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace TypePhp\Transform; |
||||||
|
|
||||||
|
use PhpParser\Node; |
||||||
|
use PhpParser\Node\Stmt; |
||||||
|
use PhpParser\NodeAbstract; |
||||||
|
use TypePhp\Exception\SyntaxError; |
||||||
|
|
||||||
|
/** |
||||||
|
* Preserve PHP's scalar type system while selecting a narrower C++ field for |
||||||
|
* explicitly sized Native Class properties. |
||||||
|
*/ |
||||||
|
final class NativePropertyTypeLowering |
||||||
|
{ |
||||||
|
public const string STORAGE_TYPE_ATTRIBUTE = 'typephpNativePropertyStorageType'; |
||||||
|
|
||||||
|
/** @var array<string, array{php: string, cpp: string}> */ |
||||||
|
private const array TYPES = [ |
||||||
|
'int8' => ['php' => 'int', 'cpp' => 'int8_t'], |
||||||
|
'int16' => ['php' => 'int', 'cpp' => 'int16_t'], |
||||||
|
'int32' => ['php' => 'int', 'cpp' => 'int32_t'], |
||||||
|
'uint8' => ['php' => 'int', 'cpp' => 'uint8_t'], |
||||||
|
'uint16' => ['php' => 'int', 'cpp' => 'uint16_t'], |
||||||
|
'uint32' => ['php' => 'int', 'cpp' => 'uint32_t'], |
||||||
|
'float32' => ['php' => 'float', 'cpp' => 'float'], |
||||||
|
]; |
||||||
|
|
||||||
|
private const array UNSUPPORTED_TYPES = [ |
||||||
|
'uint64' => 'it cannot be represented by php::Int', |
||||||
|
]; |
||||||
|
|
||||||
|
public static function lowerClass(Stmt\Class_ $class): void |
||||||
|
{ |
||||||
|
foreach ($class->stmts as $stmt) { |
||||||
|
if ($stmt instanceof Stmt\Property) { |
||||||
|
$stmt->type = self::lowerDeclaration($stmt, $stmt->type); |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (!$stmt instanceof Stmt\ClassMethod || $stmt->name->toLowerString() !== '__construct') { |
||||||
|
continue; |
||||||
|
} |
||||||
|
foreach ($stmt->params as $param) { |
||||||
|
if ($param->isPromoted()) { |
||||||
|
$param->type = self::lowerDeclaration($param, $param->type); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static function getStorageType(Node $declaration): string |
||||||
|
{ |
||||||
|
$type = $declaration->getAttribute(self::STORAGE_TYPE_ATTRIBUTE, ''); |
||||||
|
return is_string($type) ? $type : ''; |
||||||
|
} |
||||||
|
|
||||||
|
private static function lowerDeclaration( |
||||||
|
Node $declaration, |
||||||
|
Node\ComplexType|Node\Identifier|Node\Name|null $type, |
||||||
|
): Node\ComplexType|Node\Identifier|Node\Name|null { |
||||||
|
$unsupportedType = self::findType($type, self::UNSUPPORTED_TYPES); |
||||||
|
if ($unsupportedType !== null) { |
||||||
|
throw new SyntaxError( |
||||||
|
"Native property type `{$unsupportedType}` is not supported because " |
||||||
|
. self::UNSUPPORTED_TYPES[$unsupportedType], |
||||||
|
); |
||||||
|
} |
||||||
|
$fixedType = self::findFixedType($type); |
||||||
|
if ($fixedType === null) { |
||||||
|
return $type; |
||||||
|
} |
||||||
|
if (!$type instanceof Node\Name && !$type instanceof Node\Identifier) { |
||||||
|
throw new SyntaxError( |
||||||
|
"Native property type `{$fixedType}` must be declared directly and cannot be nullable or composite", |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
$mapping = self::TYPES[$fixedType]; |
||||||
|
$declaration->setAttribute(self::STORAGE_TYPE_ATTRIBUTE, $mapping['cpp']); |
||||||
|
return new Node\Identifier($mapping['php'], $type->getAttributes()); |
||||||
|
} |
||||||
|
|
||||||
|
private static function findFixedType(?NodeAbstract $type): ?string |
||||||
|
{ |
||||||
|
return self::findType($type, self::TYPES); |
||||||
|
} |
||||||
|
|
||||||
|
/** @param array<string, mixed> $types */ |
||||||
|
private static function findType(?NodeAbstract $type, array $types): ?string |
||||||
|
{ |
||||||
|
if ($type === null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
if ($type instanceof Node\NullableType) { |
||||||
|
return self::findType($type->type, $types); |
||||||
|
} |
||||||
|
if ($type instanceof Node\UnionType || $type instanceof Node\IntersectionType) { |
||||||
|
foreach ($type->types as $member) { |
||||||
|
$matchedType = self::findType($member, $types); |
||||||
|
if ($matchedType !== null) { |
||||||
|
return $matchedType; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (!$type instanceof Node\Name && !$type instanceof Node\Identifier) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
$name = strtolower($type->toString()); |
||||||
|
return isset($types[$name]) ? $name : null; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,99 @@ |
|||||||
|
--TEST-- |
||||||
|
Native class: fixed-width scalar fields retain PHP scalar expression types |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
#[Native] |
||||||
|
final class CompactScalars |
||||||
|
{ |
||||||
|
public int8 $i8 = -8; |
||||||
|
public int16 $i16 = -1600; |
||||||
|
public int32 $i32 = -320000; |
||||||
|
public uint8 $u8 = 8; |
||||||
|
public uint16 $u16 = 1600; |
||||||
|
public uint32 $u32 = 4294967295; |
||||||
|
public float32 $f32 = 16777216.0; |
||||||
|
public float32 $f32unit = 1.0; |
||||||
|
public int32 $negative = -1; |
||||||
|
public int32 $one = 1; |
||||||
|
|
||||||
|
public function update(): void |
||||||
|
{ |
||||||
|
$this->i8 += 2; |
||||||
|
$this->u16 *= 2; |
||||||
|
} |
||||||
|
|
||||||
|
public function intSum(): int |
||||||
|
{ |
||||||
|
return $this->u32 + $this->one; |
||||||
|
} |
||||||
|
|
||||||
|
public function intCompare(): bool |
||||||
|
{ |
||||||
|
return $this->u32 > $this->negative; |
||||||
|
} |
||||||
|
|
||||||
|
public function floatSum(): float |
||||||
|
{ |
||||||
|
return $this->f32 + $this->f32unit; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[Native] |
||||||
|
final class MixedScalars |
||||||
|
{ |
||||||
|
public int $integer = 3; |
||||||
|
public float $floating = 0.5; |
||||||
|
public int8 $smallInteger = 2; |
||||||
|
public float32 $smallFloat = 0.25; |
||||||
|
|
||||||
|
public function nativeSum(): float |
||||||
|
{ |
||||||
|
return $this->integer + $this->floating; |
||||||
|
} |
||||||
|
|
||||||
|
public function fixedIntSum(): float |
||||||
|
{ |
||||||
|
return $this->smallInteger + $this->floating; |
||||||
|
} |
||||||
|
|
||||||
|
public function fixedFloatSum(): float |
||||||
|
{ |
||||||
|
return $this->integer + $this->smallFloat; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$value = new CompactScalars(); |
||||||
|
$value->update(); |
||||||
|
|
||||||
|
var_dump( |
||||||
|
$value->i8, |
||||||
|
$value->i16, |
||||||
|
$value->i32, |
||||||
|
$value->u8, |
||||||
|
$value->u16, |
||||||
|
$value->u32, |
||||||
|
$value->intSum(), |
||||||
|
$value->intCompare(), |
||||||
|
$value->floatSum(), |
||||||
|
); |
||||||
|
|
||||||
|
$mixed = new MixedScalars(); |
||||||
|
var_dump($mixed->nativeSum(), $mixed->fixedIntSum(), $mixed->fixedFloatSum()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(-6) |
||||||
|
int(-1600) |
||||||
|
int(-320000) |
||||||
|
int(8) |
||||||
|
int(3200) |
||||||
|
int(4294967295) |
||||||
|
int(4294967296) |
||||||
|
bool(true) |
||||||
|
float(16777217) |
||||||
|
float(3.5) |
||||||
|
float(2.5) |
||||||
|
float(3.25) |
||||||
Loading…
Reference in new issue