diff --git a/src/Context/FunctionContext.php b/src/Context/FunctionContext.php index 8d13ba29..1a90592c 100644 --- a/src/Context/FunctionContext.php +++ b/src/Context/FunctionContext.php @@ -68,6 +68,8 @@ class FunctionContext */ public array $stdContainers = []; public array $localVars = []; + /** @var array Locals explicitly created through std::int/float/bool. */ + public array $explicitNativeTypeVars = []; /** @var array C++ initializers folded into function-scope local declarations. */ public array $localVarInitializers = []; public array $staticVars = []; @@ -121,6 +123,7 @@ class FunctionContext public function __construct() { $this->localVars = []; + $this->explicitNativeTypeVars = []; $this->localVarInitializers = []; $this->staticVars = []; $this->arguments = []; diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 645fb4d4..304ac6c5 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -621,11 +621,15 @@ trait AssignOpTrait $finalVarType = $right->getAttribute('nativeType'); $this->addLocalVar($var, $finalVarType); } + $this->context->explicitNativeTypeVars[$var] = true; return $var . ' = ' . $valueExpr; } } } elseif ($this->isVarExpr($right)) { $rightVar = $this->parseIdentifier($right); + if (isset($this->context->explicitNativeTypeVars[$rightVar])) { + $this->context->explicitNativeTypeVars[$var] = true; + } $this->assertStdContainerDoesNotEscapeNativeObjects($right, $rightVar); $type = $this->isStdContainer($rightVar) ? Type::ARRAY : $this->getVarType($rightVar); $finalVarType = $this->getNormalAssignType($type); diff --git a/src/Parser/BinaryOpTrait.php b/src/Parser/BinaryOpTrait.php index 8933c68a..df1b73ed 100644 --- a/src/Parser/BinaryOpTrait.php +++ b/src/Parser/BinaryOpTrait.php @@ -167,6 +167,8 @@ trait BinaryOpTrait && $leftType === Type::INT && $rightType === Type::INT && in_array($op, ['+', '-', '*', '/'], true) + && !$this->isExplicitNativeArithmeticExpr($left) + && !$this->isExplicitNativeArithmeticExpr($right) && $this->evaluateConstantIntArithmetic($left, $right, $op) === null ) { // Keep the potentially widening result boxed, but pass the native @@ -178,6 +180,19 @@ trait BinaryOpTrait return '((' . $leftExpr . ') ' . $op . ' (' . $rightExpr . '))'; } + /** + * std::int/float/bool explicitly opt a value into native C++ arithmetic, + * independently of the file-wide `use native_types` declaration. + */ + protected function isExplicitNativeArithmeticExpr(NodeAbstract $expr): bool + { + if ($this->isVarExpr($expr) && is_string($expr->name)) { + return isset($this->context->explicitNativeTypeVars[$this->parseIdentifier($expr)]); + } + + return $expr->getAttribute('nativeType') !== null; + } + /** * Fold constant integer shifts to PHP semantics in non-native mode. * diff --git a/src/Parser/MethodCallTrait.php b/src/Parser/MethodCallTrait.php index 525211de..6d9387d5 100644 --- a/src/Parser/MethodCallTrait.php +++ b/src/Parser/MethodCallTrait.php @@ -312,6 +312,28 @@ trait MethodCallTrait return null; } + // A DynamicCall is also used for real methods inherited from an + // internal Zend class. Only attempt __call devirtualization when the + // TypePHP class hierarchy actually provides a compiled __call method; + // otherwise getNativeMethod() would continue into the internal parent + // and incorrectly diagnose the absent magic method. + $currentClass = $exactClass; + $hasCompiledMagicMethod = false; + while ($this->hasClass($currentClass)) { + $currentDef = $this->getClass($currentClass); + if ($currentDef->hasMethod('__call')) { + $hasCompiledMagicMethod = true; + break; + } + if ($currentDef->extends === '') { + break; + } + $currentClass = $currentDef->extends; + } + if (!$hasCompiledMagicMethod) { + return null; + } + $nativeFunc = $this->getNativeMethod($expr, $exactClass, '__call', false); if ($nativeFunc === false || !$this->hasFunction($nativeFunc)) { return null;