fix(compiler): enforce AST type boundaries

master
韩天峰 23 hours ago
parent 1d1a50c3d3
commit eccf5a0ef1
  1. 2
      composer.json
  2. 15
      phpstan-bootstrap.php
  3. 17
      phpstan.neon
  4. 385
      src/CompilerBase.php
  5. 7
      src/Diagnostics/CompilerDiagnosticTrait.php
  6. 2
      src/Installer/LinuxPackageManager.php
  7. 2
      src/NativeClass/NativeClassSupportTrait.php
  8. 2
      src/Parser/AssignOpTrait.php
  9. 91
      src/Parser/AstNodeType.php
  10. 1
      src/Parser/StdContainerTrait.php
  11. 114
      src/Preprocessor.php
  12. 92
      src/Translator.php

@ -37,7 +37,7 @@
},
"scripts": {
"cs-fix": "php-cs-fixer fix $1",
"analyse": "phpstan analyse --memory-limit 512M -l 5 -c phpstan.neon",
"analyse": "phpstan analyse --memory-limit 1G -c phpstan.neon",
"rector": "rector process --clear-cache"
},
"suggest": {

@ -0,0 +1,15 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
if (!defined('TYPEPHP_ROOT_PATH')) {
define('TYPEPHP_ROOT_PATH', __DIR__);
}
if (!defined('TYPEPHP_DEBUG')) {
define('TYPEPHP_DEBUG', true);
}

@ -0,0 +1,17 @@
parameters:
level: 5
paths:
- src
- bin
# Define CLI constants without executing bin/bootstrap.php inside PHPStan's
# process, where loading Composer's autoloader a second time is unsafe.
bootstrapFiles:
- phpstan-bootstrap.php
# gen_stub.php is maintained as a namespaced copy of php-src's generator.
# Analyze TypePHP-owned compiler code independently from upstream diagnostics.
excludePaths:
analyse:
- src/gen_stub.php

@ -239,6 +239,11 @@ class CompilerBase implements PropertyAccessContext
public const string VALUE_ZERO = 'php::zero';
public const string VALUE_FALSE = 'php::false_';
public const string VALUE_TRUE = 'php::true_';
protected function getBoolValue(Expr\ConstFetch $expr): string
{
return strcasecmp($expr->name->toString(), 'true') === 0 ? self::VALUE_TRUE : self::VALUE_FALSE;
}
public const string LITERAL_STRINGS = '_literal_strings';
public const string LITERAL_STRING_GETTER = 'get_str';
public const string ANON_CLASS = '_anon_class_';
@ -576,7 +581,7 @@ class CompilerBase implements PropertyAccessContext
return $this->lang;
}
protected function unsupportedSyntax(NodeAbstract $node): never
protected function unsupportedSyntax(Node $node): never
{
$message = 'Error: Unsupported ' . $this->getLang() . ' Syntax,';
$message .= ' Line: ' . $this->getLine($node) . ', Type: ' . $this->getType($node) . PHP_EOL;
@ -731,7 +736,7 @@ class CompilerBase implements PropertyAccessContext
return '';
}
public function parseExpr(NodeAbstract $expr): string
public function parseExpr(Node $expr): string
{
if ($expr->hasAttribute('replace')) {
return $expr->getAttribute('replace');
@ -744,217 +749,114 @@ class CompilerBase implements PropertyAccessContext
if ($expr instanceof Node\Expr\BinaryOp) {
$this->assertNativeObjectBinaryOperatorSupported($expr);
}
switch ($type) {
case 'Expr_Isset':
return $this->parseIsset($expr);
case 'Expr_Empty':
return $this->parseEmpty($expr);
case 'Expr_Assign':
return $this->parseAssign($expr);
case 'Expr_AssignRef':
return $this->parseAssignRef($expr);
case 'Expr_Print':
return $this->parsePrint($expr);
case 'Expr_BinaryOp_Equal':
return $this->parseBinaryOpEqual($expr);
case 'Expr_BinaryOp_NotEqual':
return $this->parseBinaryOpNotEqual($expr);
case 'Expr_BinaryOp_Identical':
return $this->parseBinaryOpIdentical($expr);
case 'Expr_BinaryOp_NotIdentical':
return $this->parseBinaryOpNotIdentical($expr);
case 'Expr_BooleanNot':
return $this->parseBooleanNot($expr);
case 'Expr_BinaryOp_Plus':
return $this->parseBinaryOpPlus($expr);
case 'Expr_BinaryOp_Div':
return $this->parseBinaryOpDiv($expr);
case 'Expr_BinaryOp_Smaller':
return $this->parseBinaryOpSmaller($expr);
case 'Expr_BinaryOp_SmallerOrEqual':
return $this->parseBinaryOpSmallerOrEqual($expr);
case 'Expr_BinaryOp_GreaterOrEqual':
return $this->parseBinaryOpGreaterOrEqual($expr);
case 'Expr_BinaryOp_Spaceship':
return $this->parseBinaryOpSpaceship($expr);
case 'Expr_BinaryOp_Coalesce':
return $this->parseBinaryOpCoalesce($expr);
case 'Expr_PreInc':
return $this->parsePreInc($expr);
case 'Expr_PostInc':
return $this->parsePostInc($expr);
case 'Expr_PreDec':
return $this->parsePreDec($expr);
case 'Expr_PostDec':
return $this->parsePostDec($expr);
case 'Expr_AssignOp_Plus':
return $this->parseAssignOpPlus($expr);
case 'Expr_AssignOp_Minus':
return $this->parseAssignOpMinus($expr);
case 'Expr_AssignOp_Mul':
return $this->parseAssignOpMul($expr);
case 'Expr_AssignOp_Div':
return $this->parseAssignOpDiv($expr);
case 'Expr_AssignOp_Mod':
return $this->parseAssignOpMod($expr);
case 'Expr_AssignOp_Concat':
return $this->parseAssignOpConcat($expr);
case 'Expr_AssignOp_ShiftLeft':
return $this->parseAssignOpShiftLeft($expr);
case 'Expr_AssignOp_ShiftRight':
return $this->parseAssignOpShiftRight($expr);
case 'Expr_AssignOp_BitwiseAnd':
return $this->parseAssignOpBitwiseAnd($expr);
case 'Expr_AssignOp_BitwiseOr':
return $this->parseAssignOpBitwiseOr($expr);
case 'Expr_AssignOp_BitwiseXor':
return $this->parseAssignOpBitwiseXor($expr);
case 'Expr_AssignOp_Pow':
return $this->parseAssignOpPow($expr);
case 'Expr_AssignOp_Coalesce':
return $this->parseAssignOpCoalesce($expr);
case 'Expr_BinaryOp_Mul':
return $this->parseBinaryOpMul($expr);
case 'Expr_BinaryOp_Concat':
return $this->parseBinaryOpConcat($expr);
case 'Expr_BinaryOp_Greater':
return $this->parseBinaryOpGreater($expr);
case 'Expr_BinaryOp_LogicalAnd':
case 'Expr_BinaryOp_BooleanAnd':
return $this->parseBinaryOpLogicalAnd($expr);
case 'Expr_BinaryOp_LogicalOr':
case 'Expr_BinaryOp_BooleanOr':
return $this->parseBinaryOpLogicalOr($expr);
case 'Expr_BinaryOp_LogicalXor':
return $this->parseBinaryOpLogicalXor($expr);
case 'Expr_BinaryOp_Minus':
return $this->parseBinaryOpMinus($expr);
case 'Expr_Array':
return $this->parseArray($expr);
case 'Expr_ArrayDimFetch':
return $this->parseArrayDimFetch($expr);
case 'Expr_PropertyFetch':
return $this->parsePropertyFetch($expr);
case 'Expr_NullsafePropertyFetch':
return $this->parseNullsafePropertyFetch($expr);
case 'Expr_NullsafeMethodCall':
return $this->parseNullsafeMethodCall($expr);
case 'Expr_BinaryOp_ShiftLeft':
return $this->parseBinaryOpShiftLeft($expr);
case 'Expr_BinaryOp_ShiftRight':
return $this->parseBinaryOpShiftRight($expr);
case 'Expr_BinaryOp_BitwiseAnd':
return $this->parseBinaryOpBitwiseAnd($expr);
case 'Expr_BinaryOp_BitwiseOr':
return $this->parseBinaryOpBitwiseOr($expr);
case 'Expr_BinaryOp_BitwiseXor':
return $this->parseBinaryOpBitwiseXor($expr);
case 'Expr_BinaryOp_Pipe':
return $this->parsePipeOperator($expr);
case 'Expr_BitwiseNot':
return $this->parseBitwiseNot($expr);
case 'Expr_BinaryOp_Mod':
return $this->parseBinaryOpMod($expr);
case 'Expr_BinaryOp_Pow':
return $this->parseBinaryOpPow($expr);
case 'Expr_Ternary':
return $this->parseTernary($expr);
case 'Expr_Match':
return $this->parseMatch($expr);
case 'Expr_FuncCall':
return $this->parseFuncCall($expr);
case 'Expr_MethodCall':
return $this->parseMethodCall($expr);
case 'Expr_StaticCall':
return $this->parseStaticCall($expr);
case 'Expr_StaticPropertyFetch':
return $this->parseStaticPropertyFetch($expr);
case 'Expr_ClassConstFetch':
return $this->parseClassConstFetch($expr);
case 'Expr_Include':
return $this->parseInclude($expr);
case 'Expr_Eval':
return $this->parseEval($expr);
case 'Expr_New':
return $this->parseNew($expr);
case 'Expr_Clone':
return $this->parseClone($expr);
case 'Expr_Instanceof':
return $this->parseInstanceof($expr);
case 'Expr_Throw':
return $this->parseThrow($expr);
case 'Expr_ShellExec':
return $this->parseShellExec($expr);
case 'Expr_Closure':
return $this->parseClosure($expr);
case 'Expr_ArrowFunction':
return $this->parseArrowFunction($expr);
case 'Name_FullyQualified':
return $this->parseFullyQualifiedName($expr);
case 'Scalar_Int':
case 'Scalar_Float':
case 'Scalar_String':
return $this->parseIdentifier($expr);
case 'Expr_Variable':
$varName = $this->parseIdentifier($expr);
$this->requireVar($expr, $varName);
if ($this->isStdContainer($varName)) {
return $varName . '_ref';
}
// $GLOBALS is an INDIRECT to &EG(symbol_table),
// whose refcount MUST NOT be directly manipulated.
// Use php::globalsArray() to create a separated copy.
if ($varName === 'GLOBALS') {
return 'php::globalsArray()';
}
return $varName;
case 'Scalar_MagicConst_File':
case 'Scalar_MagicConst_Dir':
case 'Scalar_MagicConst_Line':
case 'Scalar_MagicConst_Function':
case 'Scalar_MagicConst_Method':
case 'Scalar_MagicConst_Class':
case 'Scalar_MagicConst_Trait':
case 'Scalar_MagicConst_Namespace':
case 'Scalar_MagicConst_Property':
return $this->parseMagicConst($expr);
case 'Scalar_InterpolatedString':
return $this->parseInterpolatedString($expr);
case 'Expr_Cast_Int':
return $this->parseCastInt($expr);
case 'Expr_Cast_Double':
return $this->parseCastDouble($expr);
case 'Expr_Cast_Bool':
return $this->parseCastBool($expr);
case 'Expr_Cast_String':
return $this->parseCastString($expr);
case 'Expr_Cast_Array':
return $this->parseCastArray($expr);
case 'Expr_Cast_Object':
return $this->parseCastObject($expr);
case 'Expr_Cast_Void':
return $this->parseCastVoid($expr);
case 'Expr_ConstFetch':
return $this->parseConstFetch($expr);
case 'Expr_UnaryMinus':
return $this->parseUnaryMinus($expr);
case 'Expr_UnaryPlus':
return $this->parseUnaryPlus($expr);
case 'InterpolatedStringPart':
return $this->parseInterpolatedStringPart($expr);
case 'Expr_ErrorSuppress':
return $this->parseErrorSuppress($expr);
case 'Expr_Exit':
return $this->parseExit($expr);
case 'Expr_Yield':
return $this->parseYieldExpr($expr);
case 'Expr_YieldFrom':
return $this->parseYieldFromExpr($expr);
default:
$this->unsupportedSyntax($expr);
break;
}
return '';
if ($expr instanceof Expr\Variable) {
$varName = $this->parseIdentifier($expr);
$this->requireVar($expr, $varName);
if ($this->isStdContainer($varName)) {
return $varName . '_ref';
}
// $GLOBALS is an INDIRECT to &EG(symbol_table), whose refcount
// MUST NOT be directly manipulated. Create a separated copy.
return $varName === 'GLOBALS' ? 'php::globalsArray()' : $varName;
}
return match (true) {
$expr instanceof Expr\Isset_ => $this->parseIsset($expr),
$expr instanceof Expr\Empty_ => $this->parseEmpty($expr),
$expr instanceof Expr\Assign => $this->parseAssign($expr),
$expr instanceof Expr\AssignRef => $this->parseAssignRef($expr),
$expr instanceof Expr\Print_ => $this->parsePrint($expr),
$expr instanceof Expr\BinaryOp\Equal => $this->parseBinaryOpEqual($expr),
$expr instanceof Expr\BinaryOp\NotEqual => $this->parseBinaryOpNotEqual($expr),
$expr instanceof Expr\BinaryOp\Identical => $this->parseBinaryOpIdentical($expr),
$expr instanceof Expr\BinaryOp\NotIdentical => $this->parseBinaryOpNotIdentical($expr),
$expr instanceof Expr\BooleanNot => $this->parseBooleanNot($expr),
$expr instanceof Expr\BinaryOp\Plus => $this->parseBinaryOpPlus($expr),
$expr instanceof Expr\BinaryOp\Div => $this->parseBinaryOpDiv($expr),
$expr instanceof Expr\BinaryOp\Smaller => $this->parseBinaryOpSmaller($expr),
$expr instanceof Expr\BinaryOp\SmallerOrEqual => $this->parseBinaryOpSmallerOrEqual($expr),
$expr instanceof Expr\BinaryOp\GreaterOrEqual => $this->parseBinaryOpGreaterOrEqual($expr),
$expr instanceof Expr\BinaryOp\Spaceship => $this->parseBinaryOpSpaceship($expr),
$expr instanceof Expr\BinaryOp\Coalesce => $this->parseBinaryOpCoalesce($expr),
$expr instanceof Expr\PreInc => $this->parsePreInc($expr),
$expr instanceof Expr\PostInc => $this->parsePostInc($expr),
$expr instanceof Expr\PreDec => $this->parsePreDec($expr),
$expr instanceof Expr\PostDec => $this->parsePostDec($expr),
$expr instanceof Expr\AssignOp\Plus => $this->parseAssignOpPlus($expr),
$expr instanceof Expr\AssignOp\Minus => $this->parseAssignOpMinus($expr),
$expr instanceof Expr\AssignOp\Mul => $this->parseAssignOpMul($expr),
$expr instanceof Expr\AssignOp\Div => $this->parseAssignOpDiv($expr),
$expr instanceof Expr\AssignOp\Mod => $this->parseAssignOpMod($expr),
$expr instanceof Expr\AssignOp\Concat => $this->parseAssignOpConcat($expr),
$expr instanceof Expr\AssignOp\ShiftLeft => $this->parseAssignOpShiftLeft($expr),
$expr instanceof Expr\AssignOp\ShiftRight => $this->parseAssignOpShiftRight($expr),
$expr instanceof Expr\AssignOp\BitwiseAnd => $this->parseAssignOpBitwiseAnd($expr),
$expr instanceof Expr\AssignOp\BitwiseOr => $this->parseAssignOpBitwiseOr($expr),
$expr instanceof Expr\AssignOp\BitwiseXor => $this->parseAssignOpBitwiseXor($expr),
$expr instanceof Expr\AssignOp\Pow => $this->parseAssignOpPow($expr),
$expr instanceof Expr\AssignOp\Coalesce => $this->parseAssignOpCoalesce($expr),
$expr instanceof Expr\BinaryOp\Mul => $this->parseBinaryOpMul($expr),
$expr instanceof Expr\BinaryOp\Concat => $this->parseBinaryOpConcat($expr),
$expr instanceof Expr\BinaryOp\Greater => $this->parseBinaryOpGreater($expr),
$expr instanceof Expr\BinaryOp\LogicalAnd,
$expr instanceof Expr\BinaryOp\BooleanAnd => $this->parseBinaryOpLogicalAnd($expr),
$expr instanceof Expr\BinaryOp\LogicalOr,
$expr instanceof Expr\BinaryOp\BooleanOr => $this->parseBinaryOpLogicalOr($expr),
$expr instanceof Expr\BinaryOp\LogicalXor => $this->parseBinaryOpLogicalXor($expr),
$expr instanceof Expr\BinaryOp\Minus => $this->parseBinaryOpMinus($expr),
$expr instanceof Expr\Array_ => $this->parseArray($expr),
$expr instanceof Expr\ArrayDimFetch => $this->parseArrayDimFetch($expr),
$expr instanceof Expr\PropertyFetch => $this->parsePropertyFetch($expr),
$expr instanceof Expr\NullsafePropertyFetch => $this->parseNullsafePropertyFetch($expr),
$expr instanceof Expr\NullsafeMethodCall => $this->parseNullsafeMethodCall($expr),
$expr instanceof Expr\BinaryOp\ShiftLeft => $this->parseBinaryOpShiftLeft($expr),
$expr instanceof Expr\BinaryOp\ShiftRight => $this->parseBinaryOpShiftRight($expr),
$expr instanceof Expr\BinaryOp\BitwiseAnd => $this->parseBinaryOpBitwiseAnd($expr),
$expr instanceof Expr\BinaryOp\BitwiseOr => $this->parseBinaryOpBitwiseOr($expr),
$expr instanceof Expr\BinaryOp\BitwiseXor => $this->parseBinaryOpBitwiseXor($expr),
$expr instanceof Expr\BinaryOp\Pipe => $this->parsePipeOperator($expr),
$expr instanceof Expr\BitwiseNot => $this->parseBitwiseNot($expr),
$expr instanceof Expr\BinaryOp\Mod => $this->parseBinaryOpMod($expr),
$expr instanceof Expr\BinaryOp\Pow => $this->parseBinaryOpPow($expr),
$expr instanceof Expr\Ternary => $this->parseTernary($expr),
$expr instanceof Expr\Match_ => $this->parseMatch($expr),
$expr instanceof Expr\FuncCall => $this->parseFuncCall($expr),
$expr instanceof Expr\MethodCall => $this->parseMethodCall($expr),
$expr instanceof Expr\StaticCall => $this->parseStaticCall($expr),
$expr instanceof Expr\StaticPropertyFetch => $this->parseStaticPropertyFetch($expr),
$expr instanceof Expr\ClassConstFetch => $this->parseClassConstFetch($expr),
$expr instanceof Expr\Include_ => $this->parseInclude($expr),
$expr instanceof Expr\Eval_ => $this->parseEval($expr),
$expr instanceof Expr\New_ => $this->parseNew($expr),
$expr instanceof Expr\Clone_ => $this->parseClone($expr),
$expr instanceof Expr\Instanceof_ => $this->parseInstanceof($expr),
$expr instanceof Expr\Throw_ => $this->parseThrow($expr),
$expr instanceof Expr\ShellExec => $this->parseShellExec($expr),
$expr instanceof Expr\Closure => $this->parseClosure($expr),
$expr instanceof Expr\ArrowFunction => $this->parseArrowFunction($expr),
$expr instanceof Node\Name\FullyQualified => $this->parseFullyQualifiedName($expr),
$expr instanceof Node\Scalar\Int_,
$expr instanceof Node\Scalar\Float_,
$expr instanceof Node\Scalar\String_ => $this->parseIdentifier($expr),
$expr instanceof Node\Scalar\MagicConst => $this->parseMagicConst($expr),
$expr instanceof Node\Scalar\InterpolatedString => $this->parseInterpolatedString($expr),
$expr instanceof Expr\Cast\Int_ => $this->parseCastInt($expr),
$expr instanceof Expr\Cast\Double => $this->parseCastDouble($expr),
$expr instanceof Expr\Cast\Bool_ => $this->parseCastBool($expr),
$expr instanceof Expr\Cast\String_ => $this->parseCastString($expr),
$expr instanceof Expr\Cast\Array_ => $this->parseCastArray($expr),
$expr instanceof Expr\Cast\Object_ => $this->parseCastObject($expr),
$expr instanceof Expr\Cast\Void_ => $this->parseCastVoid($expr),
$expr instanceof Expr\ConstFetch => $this->parseConstFetch($expr),
$expr instanceof Expr\UnaryMinus => $this->parseUnaryMinus($expr),
$expr instanceof Expr\UnaryPlus => $this->parseUnaryPlus($expr),
$expr instanceof Node\InterpolatedStringPart => $this->parseInterpolatedStringPart($expr),
$expr instanceof Expr\ErrorSuppress => $this->parseErrorSuppress($expr),
$expr instanceof Expr\Exit_ => $this->parseExit($expr),
$expr instanceof Expr\Yield_ => $this->parseYieldExpr($expr),
$expr instanceof Expr\YieldFrom => $this->parseYieldFromExpr($expr),
default => $this->unsupportedSyntax($expr),
};
}
public function stop(string $string): never
@ -1195,12 +1097,12 @@ class CompilerBase implements PropertyAccessContext
// evaluated for side effects and then coerced from null.
}
protected function isVoidValueExpr(NodeAbstract $expr): bool
protected function isVoidValueExpr(Node $expr): bool
{
return $this->detectTypeOfExpr($expr) === Type::VOID;
}
protected function wrapVoidExprAsNull(NodeAbstract $expr, string $exprCode): string
protected function wrapVoidExprAsNull(Node $expr, string $exprCode): string
{
if (!$this->isVoidValueExpr($expr)) {
return $exprCode;
@ -1209,7 +1111,7 @@ class CompilerBase implements PropertyAccessContext
return '((void) (' . $exprCode . '), ' . self::VALUE_NULL . ')';
}
protected function parseExprAsValue(NodeAbstract $expr): string
protected function parseExprAsValue(Node $expr): string
{
if ($expr instanceof Expr\Cast\Void_) {
return $this->parseExpr($expr);
@ -1614,33 +1516,32 @@ class CompilerBase implements PropertyAccessContext
return false;
}
protected function parseIdentifier(NodeAbstract $expr): string
protected function parseIdentifier(Node $expr): string
{
$type = $expr->getType();
switch ($type) {
case 'Expr_Variable':
return $this->parseVariable($expr);
case 'Name_FullyQualified':
return '\\' . $expr->name;
case 'Name':
case 'VarLikeIdentifier':
case 'Identifier':
return $expr->name;
case 'Scalar_Int':
case 'Scalar_Float':
case 'Scalar_String':
return $this->parseScalar($expr);
case 'Expr_ConstFetch':
return $this->parseConstFetch($expr);
case 'Expr_Assign':
case 'Expr_AssignRef':
if (!$this->isVarExpr($expr->var) && !$this->isPropertyFetch($expr->var) && !$this->isArrayDimFetch($expr->var)) {
$this->fatalError($expr, 'When an assignment expression serves as an rvalue, it must be an assignment of a variable, property, or array element');
}
return $this->parseExprAsValue($expr);
default:
return $this->parseExprAsValue($expr);
if ($expr instanceof Variable) {
return $this->parseVariable($expr);
}
if ($expr instanceof Node\Name\FullyQualified) {
return '\\' . $expr->toString();
}
if ($expr instanceof Node\Name || $expr instanceof Node\VarLikeIdentifier || $expr instanceof Node\Identifier) {
return $expr->toString();
}
if ($expr instanceof Node\Scalar\Int_
|| $expr instanceof Node\Scalar\Float_
|| $expr instanceof Node\Scalar\String_
) {
return $this->parseScalar($expr);
}
if ($expr instanceof Expr\ConstFetch) {
return $this->parseConstFetch($expr);
}
if ($expr instanceof Expr\Assign || $expr instanceof Expr\AssignRef) {
if (!$this->isVarExpr($expr->var) && !$this->isPropertyFetch($expr->var) && !$this->isArrayDimFetch($expr->var)) {
$this->fatalError($expr, 'When an assignment expression serves as an rvalue, it must be an assignment of a variable, property, or array element');
}
}
return $this->parseExprAsValue($expr);
}
protected function parseParamDefaultValue(?NodeAbstract $default): ?string
@ -4634,7 +4535,7 @@ class CompilerBase implements PropertyAccessContext
return array_key_exists($name, $this->context->staticVars);
}
protected function parseCastDouble(mixed $expr): string
protected function parseCastDouble(Expr\Cast\Double $expr): string
{
$this->assertExprCanBeUsedAsValue($expr->expr, 'cast operand');
$native = $this->parseNativeObjectExplicitConversion($expr->expr, 'toFloat');

@ -10,7 +10,6 @@ namespace TypePhp\Diagnostics;
use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use PhpParser\NodeAbstract;
trait CompilerDiagnosticTrait
{
@ -22,7 +21,7 @@ trait CompilerDiagnosticTrait
$this->getDiagnosticReporter()->fatal($msg);
}
public function fatalError(NodeAbstract $node, string $msg): never
public function fatalError(Node $node, string $msg): never
{
$this->error("{$msg} in {$this->file}:{$node->getStartLine()}");
}
@ -56,12 +55,12 @@ trait CompilerDiagnosticTrait
$this->fatalError($node, "The variable `\${$node->name}` is undefined");
}
protected function warningUndefinedBehavior(NodeAbstract $expr): void
protected function warningUndefinedBehavior(Node $expr): void
{
$this->warning($expr, 'Use this expression carefully, which may be inconsistent with the dynamic execution behavior');
}
protected function dump(NodeAbstract $node): void
protected function dump(Node $node): void
{
if ($this->debugLine == $node->getStartLine()) {
var_dump($node);

@ -68,6 +68,7 @@ final readonly class LinuxPackageManager
'apt-get' => ['build-essential', 'cmake', 'pkg-config'],
'dnf' => ['gcc', 'gcc-c++', 'make', 'cmake', 'pkgconf-pkg-config'],
'yum' => ['gcc', 'gcc-c++', 'make', 'cmake', 'pkgconfig'],
default => throw new \LogicException("Unsupported package manager: {$this->command}"),
};
}
@ -78,6 +79,7 @@ final readonly class LinuxPackageManager
'apt-get' => ['apt-get', 'install', '-y'],
'dnf' => ['dnf', 'install', '-y'],
'yum' => ['yum', 'install', '-y'],
default => throw new \LogicException("Unsupported package manager: {$this->command}"),
};
return [...$prefix, ...$args, ...$packages];
}

@ -1168,7 +1168,7 @@ trait NativeClassSupportTrait
* the reference itself, while assignments, returns, arguments and
* comparisons need the typed pointer.
*/
protected function normalizeNativeObjectValueExpr(NodeAbstract $expr, string $value): string
protected function normalizeNativeObjectValueExpr(Node $expr, string $value): string
{
if ($this->classDef?->nativeObject
&& $this->isVarExpr($expr)

@ -1151,6 +1151,7 @@ trait AssignOpTrait
Type::BIGINT => ['BigInt', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod', '&' => 'bitAnd', '|' => 'bitOr', '^' => 'bitXor', '<<' => 'bitShiftLeft', '>>' => 'bitShiftRight']],
Type::DECIMAL => ['Decimal', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod']],
Type::BIGFLOAT => ['BigFloat', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div']],
default => $this->fatalError($errorNode, "Unsupported compound assignment type '{$leftType}'"),
};
$method = $opMap[$binaryOp] ?? null;
@ -1164,6 +1165,7 @@ trait AssignOpTrait
Type::BIGINT => $isShift ? $rightExpr : $this->convertBigIntExpr($rightExpr, $rightType),
Type::DECIMAL => $this->convertDecimalExpr($rightExpr, $rightType, $rightNode),
Type::BIGFLOAT => $this->convertBigFloatExpr($rightExpr, $rightType),
default => $this->fatalError($errorNode, "Unsupported compound assignment type '{$leftType}'"),
};
return 'php::' . $class . '::' . $method . '(' . $leftExpr . ', ' . $convertedRight . ')';

@ -11,143 +11,162 @@ namespace TypePhp\Parser;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\VariadicPlaceholder;
use PhpParser\NodeAbstract;
trait AstNodeType
{
protected function isArrayDimFetch(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\ArrayDimFetch $expr */
protected function isArrayDimFetch(Node $expr): bool
{
return $expr instanceof Expr\ArrayDimFetch;
}
protected function isVarExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\Variable $expr */
protected function isVarExpr(Node $expr): bool
{
return $expr instanceof Expr\Variable;
}
protected function isIdExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Identifier $expr */
protected function isIdExpr(Node $expr): bool
{
return $expr instanceof Node\Identifier;
}
protected function isPropertyFetch(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\PropertyFetch $expr */
protected function isPropertyFetch(Node $expr): bool
{
return $expr instanceof Expr\PropertyFetch;
}
protected function isStaticPropertyFetch(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\StaticPropertyFetch $expr */
protected function isStaticPropertyFetch(Node $expr): bool
{
return $expr instanceof Expr\StaticPropertyFetch;
}
protected function isClassConstFetch(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\ClassConstFetch $expr */
protected function isClassConstFetch(Node $expr): bool
{
return $expr instanceof Expr\ClassConstFetch;
}
protected function isNewExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\New_ $expr */
protected function isNewExpr(Node $expr): bool
{
return $expr instanceof Expr\New_;
}
protected function isNameExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Name $expr */
protected function isNameExpr(Node $expr): bool
{
return $expr instanceof Node\Name;
}
protected function isFullNameExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Name\FullyQualified $expr */
protected function isFullNameExpr(Node $expr): bool
{
return $expr instanceof Node\Name\FullyQualified;
}
protected function isNamedMethod(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Identifier $expr */
protected function isNamedMethod(Node $expr): bool
{
return $this->isIdExpr($expr);
}
protected function isScalarString(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Scalar\String_ $expr */
protected function isScalarString(Node $expr): bool
{
return $expr instanceof Node\Scalar\String_;
}
protected function isFuncCallExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\FuncCall $expr */
protected function isFuncCallExpr(Node $expr): bool
{
return $expr instanceof Expr\FuncCall;
}
protected function isRefvalCall(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\FuncCall $expr */
protected function isRefvalCall(Node $expr): bool
{
return $this->isFuncCallExpr($expr) and $this->isNameExpr($expr->name) and $expr->name->toString() === 'refval';
}
protected function isMethodCall(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\MethodCall $expr */
protected function isMethodCall(Node $expr): bool
{
return $expr instanceof Expr\MethodCall;
}
protected function isStaticCall(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\StaticCall $expr */
protected function isStaticCall(Node $expr): bool
{
return $expr instanceof Expr\StaticCall;
}
protected function isScalar(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Scalar $expr */
protected function isScalar(Node $expr): bool
{
return $expr instanceof Node\Scalar;
}
protected function isScalarInt(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Scalar\Int_ $expr */
protected function isScalarInt(Node $expr): bool
{
return $expr instanceof Node\Scalar\Int_;
}
protected function isScalarBool(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\ConstFetch $expr */
protected function isScalarBool(Node $expr): bool
{
return $expr instanceof Expr\ConstFetch and in_array(strtolower($expr->name->toString()), ['true', 'false']);
}
protected function getBoolValue(Expr\ConstFetch $expr): string
{
return strcasecmp($expr->name->toString(), 'true') === 0 ? self::VALUE_TRUE : self::VALUE_FALSE;
}
protected function isMatchExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\Match_ $expr */
protected function isMatchExpr(Node $expr): bool
{
return $expr instanceof Expr\Match_;
}
protected function isAssignExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\Assign $expr */
protected function isAssignExpr(Node $expr): bool
{
return $expr instanceof Expr\Assign;
}
protected function isCallExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\FuncCall|Expr\MethodCall|Expr\StaticCall $expr */
protected function isCallExpr(Node $expr): bool
{
return $expr instanceof Expr\FuncCall
or $expr instanceof Expr\MethodCall
or $expr instanceof Expr\StaticCall;
}
protected function isPlaceholderExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true VariadicPlaceholder $expr */
protected function isPlaceholderExpr(Node $expr): bool
{
return $expr instanceof VariadicPlaceholder;
}
protected function isReturnExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Stmt\Return_ $expr */
protected function isReturnExpr(Node $expr): bool
{
return $expr instanceof Node\Stmt\Return_;
}
protected function isBreakExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Stmt\Break_ $expr */
protected function isBreakExpr(Node $expr): bool
{
return $expr instanceof Node\Stmt\Break_;
}
protected function isContinueExpr(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Node\Stmt\Continue_ $expr */
protected function isContinueExpr(Node $expr): bool
{
return $expr instanceof Node\Stmt\Continue_;
}
protected function isThrowExpr(NodeAbstract $expr): bool
protected function isThrowExpr(Node $expr): bool
{
if ($expr instanceof Node\Stmt\Expression) {
$expr = $expr->expr;
@ -155,7 +174,7 @@ trait AstNodeType
return $expr instanceof Expr\Throw_;
}
protected function isExitExpr(NodeAbstract $expr): bool
protected function isExitExpr(Node $expr): bool
{
if ($expr instanceof Node\Stmt\Expression) {
$expr = $expr->expr;
@ -163,12 +182,14 @@ trait AstNodeType
return $expr instanceof Expr\Exit_;
}
protected function isEmptyArray(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\Array_ $expr */
protected function isEmptyArray(Node $expr): bool
{
return $expr instanceof Expr\Array_ && count($expr->items) === 0;
}
protected function isNull(NodeAbstract $expr): bool
/** @phpstan-assert-if-true Expr\ConstFetch $expr */
protected function isNull(Node $expr): bool
{
return $expr instanceof Expr\ConstFetch && strcasecmp($expr->name->toString(), 'null') === 0;
}

@ -811,6 +811,7 @@ trait StdContainerTrait
'toStdVector' => 'vector',
'toStdMap' => 'map',
'toStdOrderedMap' => 'ordered_map',
default => $this->fatalError($expr, "Unknown std conversion method: {$methodName}"),
};
if (!$this->isVarExpr($expr->var)) {

@ -90,7 +90,7 @@ class Preprocessor extends CompilerBase
return str_ends_with(strtolower($file), '.php');
}
/** @param array<Node\Stmt> $ast */
/** @param list<Node> $ast */
private function discoverNativeClassDeclarationsInAst(array $ast): void
{
$finder = new NodeFinder();
@ -302,7 +302,7 @@ class Preprocessor extends CompilerBase
));
$traverser->addVisitor(new ConstantExpressionValidationVisitor($this->phpVersion));
$traverser->addVisitor(new RuntimeAttributeFactoryLowering($this->file));
$stmts = $traverser->traverse($ast);
$stmts = $this->requireStatementList($traverser->traverse($ast));
// Keep the resolved declaration AST until convert. Defaults and
// constants are validated here, but their C++ expressions are not
// generated until the complete symbol table is available.
@ -314,40 +314,24 @@ class Preprocessor extends CompilerBase
$this->discoverNativeClassDeclarationsInAst($stmts);
foreach ($stmts as $v) {
$type = $v->getType();
switch ($type) {
case 'Stmt_Namespace':
$this->prepareNamespace($v);
break;
case 'Stmt_Enum':
case 'Stmt_Class':
case 'Stmt_Trait':
$this->prepareClass($v);
break;
case 'Stmt_Interface':
$this->parseInterface($v);
break;
case 'Stmt_Function':
$this->prepareFunction($v);
break;
case 'Stmt_Use':
$this->parseUse($v);
break;
case 'Stmt_GroupUse':
$this->parseGroupUse($v);
break;
case 'Stmt_Declare':
case 'Stmt_Nop':
break;
case 'Stmt_Const':
$this->parseConstDef($v);
break;
case 'Stmt_Expression':
$this->foundStrayCode($v);
break;
default:
$this->fatalError($v, 'Unsupported statement: ' . $type);
break;
if ($v instanceof Node\Stmt\Namespace_) {
$this->prepareNamespace($v);
} elseif ($v instanceof Node\Stmt\Class_ || $v instanceof Node\Stmt\Enum_ || $v instanceof Node\Stmt\Trait_) {
$this->prepareClass($v);
} elseif ($v instanceof Node\Stmt\Interface_) {
$this->parseInterface($v);
} elseif ($v instanceof Node\Stmt\Function_) {
$this->prepareFunction($v);
} elseif ($v instanceof Node\Stmt\Use_) {
$this->parseUse($v);
} elseif ($v instanceof Node\Stmt\GroupUse) {
$this->parseGroupUse($v);
} elseif ($v instanceof Node\Stmt\Const_) {
$this->parseConstDef($v);
} elseif ($v instanceof Node\Stmt\Expression) {
$this->foundStrayCode($v);
} elseif (!$v instanceof Node\Stmt\Declare_ && !$v instanceof Node\Stmt\Nop) {
$this->fatalError($v, 'Unsupported statement: ' . $v->getType());
}
}
} finally {
@ -355,6 +339,23 @@ class Preprocessor extends CompilerBase
}
}
/**
* Root parser output must remain a statement list after declaration
* visitors have run. Validate that invariant before storing the AST.
*
* @param list<Node> $nodes
* @return list<Node\Stmt>
*/
private function requireStatementList(array $nodes): array
{
foreach ($nodes as $node) {
if (!$node instanceof Node\Stmt) {
throw new \LogicException('Root AST traversal produced a non-statement node: ' . $node->getType());
}
}
return $nodes;
}
/**
* Lower declaration-only constant expressions after every symbol is known.
*
@ -735,33 +736,20 @@ class Preprocessor extends CompilerBase
$this->namespace = $node->name ? $this->parseIdentifier($node->name) : '';
foreach ($node->stmts as $v2) {
$type2 = $v2->getType();
switch ($type2) {
case 'Stmt_Class':
case 'Stmt_Enum':
case 'Stmt_Trait':
$this->prepareClass($v2);
break;
case 'Stmt_Function':
$this->prepareFunction($v2);
break;
case 'Stmt_Use':
$this->parseUse($v2);
break;
case 'Stmt_GroupUse':
$this->parseGroupUse($v2);
break;
case 'Stmt_Const':
$this->parseConstDef($v2);
break;
case 'Stmt_Interface':
$this->parseInterface($v2);
break;
case 'Stmt_Nop':
break;
default:
$this->foundStrayCode($v2);
break;
if ($v2 instanceof Node\Stmt\Class_ || $v2 instanceof Node\Stmt\Enum_ || $v2 instanceof Node\Stmt\Trait_) {
$this->prepareClass($v2);
} elseif ($v2 instanceof Node\Stmt\Function_) {
$this->prepareFunction($v2);
} elseif ($v2 instanceof Node\Stmt\Use_) {
$this->parseUse($v2);
} elseif ($v2 instanceof Node\Stmt\GroupUse) {
$this->parseGroupUse($v2);
} elseif ($v2 instanceof Node\Stmt\Const_) {
$this->parseConstDef($v2);
} elseif ($v2 instanceof Node\Stmt\Interface_) {
$this->parseInterface($v2);
} elseif (!$v2 instanceof Node\Stmt\Nop) {
$this->foundStrayCode($v2);
}
}
}

@ -2795,39 +2795,24 @@ CODE;
$cppCode = '';
foreach ($stmts as $v) {
$type = $v->getType();
switch ($type) {
case 'Stmt_Declare':
$this->parseDeclare($v);
break;
case 'Stmt_Namespace':
$cppCode .= $this->parseNamespace($v);
break;
case 'Stmt_Class':
case 'Stmt_Trait':
case 'Stmt_Enum':
$cppCode .= $this->parseClass($v);
break;
case 'Stmt_Use':
$this->parseUse($v);
break;
case 'Stmt_GroupUse':
$this->parseGroupUse($v);
break;
case 'Stmt_Function':
$cppCode .= $this->parseFunction($v) . PHP_EOL;
break;
case 'Stmt_Const':
$this->parseConstDef($v);
break;
case 'Stmt_Interface':
$this->validateInterfaceOverrideAttributes($v);
break;
case 'Stmt_Nop':
break;
default:
$this->unsupportedSyntax($v);
break;
if ($v instanceof Node\Stmt\Declare_) {
$this->parseDeclare($v);
} elseif ($v instanceof Node\Stmt\Namespace_) {
$cppCode .= $this->parseNamespace($v);
} elseif ($v instanceof Node\Stmt\Class_ || $v instanceof Node\Stmt\Trait_ || $v instanceof Node\Stmt\Enum_) {
$cppCode .= $this->parseClass($v);
} elseif ($v instanceof Node\Stmt\Use_) {
$this->parseUse($v);
} elseif ($v instanceof Node\Stmt\GroupUse) {
$this->parseGroupUse($v);
} elseif ($v instanceof Node\Stmt\Function_) {
$cppCode .= $this->parseFunction($v) . PHP_EOL;
} elseif ($v instanceof Node\Stmt\Const_) {
$this->parseConstDef($v);
} elseif ($v instanceof Node\Stmt\Interface_) {
$this->validateInterfaceOverrideAttributes($v);
} elseif (!$v instanceof Node\Stmt\Nop) {
$this->unsupportedSyntax($v);
}
}
@ -2988,33 +2973,20 @@ CODE;
$ns_end = '';
foreach ($node->stmts as $v2) {
$type2 = $v2->getType();
switch ($type2) {
case 'Stmt_Class':
case 'Stmt_Trait':
case 'Stmt_Enum':
$code .= $this->parseClass($v2);
break;
case 'Stmt_Const':
$this->parseConstDef($v2);
break;
case 'Stmt_Function':
$code .= $this->parseFunction($v2) . PHP_EOL;
break;
case 'Stmt_Use':
$this->parseUse($v2);
break;
case 'Stmt_GroupUse':
$this->parseGroupUse($v2);
break;
case 'Stmt_Interface':
$this->validateInterfaceOverrideAttributes($v2);
break;
case 'Stmt_Nop':
break;
default:
$this->unsupportedSyntax($v2);
break;
if ($v2 instanceof Node\Stmt\Class_ || $v2 instanceof Node\Stmt\Trait_ || $v2 instanceof Node\Stmt\Enum_) {
$code .= $this->parseClass($v2);
} elseif ($v2 instanceof Node\Stmt\Const_) {
$this->parseConstDef($v2);
} elseif ($v2 instanceof Node\Stmt\Function_) {
$code .= $this->parseFunction($v2) . PHP_EOL;
} elseif ($v2 instanceof Node\Stmt\Use_) {
$this->parseUse($v2);
} elseif ($v2 instanceof Node\Stmt\GroupUse) {
$this->parseGroupUse($v2);
} elseif ($v2 instanceof Node\Stmt\Interface_) {
$this->validateInterfaceOverrideAttributes($v2);
} elseif (!$v2 instanceof Node\Stmt\Nop) {
$this->unsupportedSyntax($v2);
}
}
$code .= $ns_end;

Loading…
Cancel
Save