diff --git a/phpunit/code/re-assign-numeric-without-using-native-types.php b/phpunit/code/re-assign-numeric-without-using-native-types.php new file mode 100644 index 00000000..2eab54ae --- /dev/null +++ b/phpunit/code/re-assign-numeric-without-using-native-types.php @@ -0,0 +1,7 @@ +exec('Cannot re-assign `$obj` from `php::Str` to `php::Object`', 're-assign.php'); } + public function testReAssignNumericValueWithoutUsingNativeTypes() + { + $this->exec('Cannot re-assign `$x` from `php::Array` to `php::Int', "re-assign-numeric-without-using-native-types.php"); + } + public function testAssignClass() { $this->exec('Cannot re-assign typed object `$obj1` from `stdClass` to `ArrayObject`', 're-assign-2.php'); diff --git a/src/CompilerBase.php b/src/CompilerBase.php index ad87b290..41c5a4ee 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -1,4 +1,5 @@ toString(); } - if ($expr instanceof Node\Scalar\Int_ + if ( + $expr instanceof Node\Scalar\Int_ || $expr instanceof Node\Scalar\Float_ || $expr instanceof Node\Scalar\String_ ) { @@ -1976,8 +1978,10 @@ protected function resolveCalledFunctionDef(NodeAbstract $expr): ?FunctionDef } return $class === '' ? null : $this->findAotMethodFunctionDef($class, $expr->name->toString()); } - if ($expr instanceof Expr\StaticCall && $expr->class instanceof Node\Name - && $expr->name instanceof Node\Identifier) { + if ( + $expr instanceof Expr\StaticCall && $expr->class instanceof Node\Name + && $expr->name instanceof Node\Identifier + ) { $class = $this->parseIdentifier($expr->class); if ($class === 'self' || $class === 'static') { $class = $this->getFullClassName(); @@ -2059,7 +2063,7 @@ protected function detectClassOfExpr(NodeAbstract $expr): string } if ($expr instanceof Expr\Match_) { return $this->getCommonNativeObjectExpressionClass(array_map( - static fn (Node\MatchArm $arm): Expr => $arm->body, + static fn(Node\MatchArm $arm): Expr => $arm->body, $expr->arms, )); } @@ -2113,7 +2117,8 @@ protected function detectClassOfExpr(NodeAbstract $expr): string $receiverClass = $this->detectClassOfExpr($expr->var); if ($this->isNativeObjectClass($receiverClass)) { $property = $this->findNativeObjectProperty($receiverClass, $expr->name->toString()); - if ($property !== null + if ( + $property !== null && $property->type === Type::OBJECT && $this->isNativeObjectClass($property->class) ) { @@ -2230,7 +2235,8 @@ protected function isObjectClassStaticallyAssignableTo(string $class, string $ex return true; } - if (!$this->hasClass($class) + if ( + !$this->hasClass($class) && !$this->hasInterface($class) && !$this->isInternalClass($class) && !$this->isInternalInterface($class) @@ -2382,10 +2388,12 @@ protected function parseReturn(Node\Stmt\Return_ $v): string return 'return php::toReferenceExact(' . $this->parseExpr($v->expr) . ');'; } } - if (!$this->isVarExpr($v->expr) + if ( + !$this->isVarExpr($v->expr) && !$this->isPropertyFetch($v->expr) && !$this->isStaticPropertyFetch($v->expr) - && !$this->isArrayDimFetch($v->expr)) { + && !$this->isArrayDimFetch($v->expr) + ) { $this->fatalError($v, 'A function returning by reference must return a variable'); } if ($this->isVarExpr($v->expr)) { @@ -2419,7 +2427,8 @@ protected function parseReturn(Node\Stmt\Return_ $v): string return 'return ' . $this->parseChainedExpr($v->expr, self::OP_REFVAL) . ';'; } if ($v->expr === null) { - if (!$this->context->inClosure + if ( + !$this->context->inClosure && $this->getNativeObjectReturnType($this->functionDef) !== null ) { $this->fatalError( @@ -2540,7 +2549,8 @@ protected function parseReturn(Node\Stmt\Return_ $v): string 'return value' ); } - if (!$this->context->inClosure + if ( + !$this->context->inClosure && ($nativeReturnClass = $this->getReturnClass()) !== '' && $this->isNativeObjectClass($nativeReturnClass) ) { @@ -2551,7 +2561,8 @@ protected function parseReturn(Node\Stmt\Return_ $v): string return 'return nullptr;'; } $objectClass = $this->detectClassOfExpr($v->expr); - if ($objectClass === '' || !$this->isNativeObjectClass($objectClass) + if ( + $objectClass === '' || !$this->isNativeObjectClass($objectClass) || !$this->isObjectClassStaticallyAssignableTo($objectClass, $nativeReturnClass) ) { $this->fatalError( @@ -2564,7 +2575,7 @@ protected function parseReturn(Node\Stmt\Return_ $v): string $returnCode = $this->functionDef->returnNullable ? $returnExpr : 'php::nativeRequireObject(' . $returnExpr . ', "' - . addslashes($nativeReturnClass) . '")'; + . addslashes($nativeReturnClass) . '")'; if (count($this->context->afterStmtLines) === $afterStmtCount) { return 'return ' . $returnCode . ';'; @@ -2581,7 +2592,7 @@ protected function parseReturn(Node\Stmt\Return_ $v): string $finalReturn = $this->functionDef->returnNullable ? $tmpVar : 'php::nativeRequireObject(' . $tmpVar . ', "' - . addslashes($nativeReturnClass) . '")'; + . addslashes($nativeReturnClass) . '")'; $this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $finalReturn . ';'; return $tmpVar . ' = ' . $returnExpr . ';'; } @@ -2597,9 +2608,11 @@ protected function parseReturn(Node\Stmt\Return_ $v): string $returnType = Type::VAR; } - if (!$this->context->inClosure + if ( + !$this->context->inClosure && ($type === Type::VAR || $type === Type::REF) - && $this->isStrictScalarType($returnType)) { + && $this->isStrictScalarType($returnType) + ) { // Keep the zval type until the declared return boundary has been // checked. Converting first would silently coerce invalid values. $tmpVar = $this->addTmpVar(Type::VAR); @@ -2863,8 +2876,7 @@ protected function findNativeClassConst( string $class, string $const, ?string $accessingClass = null - ): string|false - { + ): string|false { if (!$this->hasClass($class)) { return false; } @@ -2911,12 +2923,14 @@ protected function findNativeClassConst( if ($constDef === null) { return false; } - if ($classDef instanceof ClassDef + if ( + $classDef instanceof ClassDef && !$this->checkAccessibleByClassName( $classDef->getNamespacedName(false), $constDef->flags, $accessingClass, - )) { + ) + ) { $this->fatalError($expr, 'Constant `' . $classDef->getNamespacedName() . '::' . $const . '` is not accessible'); } if ($constDef->type === Type::ARRAY) { @@ -3279,7 +3293,8 @@ protected function detectTypeOfExpr($expr): string } break; case 'Expr_ClassConstFetch': - if ($this->isIdExpr($expr->name) + if ( + $this->isIdExpr($expr->name) && strtolower($this->parseIdentifier($expr->name)) === 'class' ) { return Type::STR; @@ -3947,12 +3962,14 @@ protected function parseNew(Expr\New_ $expr): string $this->fatalError($expr, "abstract class `{$className}` cannot be instantiated"); } $constructor = $this->findConstructor($className); - if ($constructor !== null - && !$this->checkAccessibleByClassName($constructor['className'], $constructor['flags'])) { + if ( + $constructor !== null + && !$this->checkAccessibleByClassName($constructor['className'], $constructor['flags']) + ) { $this->fatalError( $expr, 'Cannot call ' . $this->visibilityLabel($constructor['flags']) . ' ' - . $constructor['className'] . '::__construct()' + . $constructor['className'] . '::__construct()' ); } if ($this->isNativeObjectClass($className)) { @@ -4060,7 +4077,8 @@ protected function parseInstanceof(Expr\Instanceof_ $expr): string $result = $valueIsNative && ($targetIsNative || $targetIsInterface) && $this->isObjectClassStaticallyAssignableTo($valueClass, $targetClass); - if (!$result + if ( + !$result && $valueIsNative && $targetIsNative && $this->isObjectClassStaticallyAssignableTo($targetClass, $valueClass) @@ -4393,7 +4411,8 @@ protected function parseChainedExpr(NodeAbstract $node, string $op, bool $getVal $this->assertNativeArrayAccessReferenceForbidden($node); $this->assertNativeObjectReferenceForbidden($node, $node); } - if ($node instanceof Expr\ArrayDimFetch + if ( + $node instanceof Expr\ArrayDimFetch && $this->isNativeObjectClass($this->detectClassOfExpr($node->var)) ) { return $this->parseNativeArrayAccessPresence($node, $op, $getValue); @@ -4404,7 +4423,8 @@ protected function parseChainedExpr(NodeAbstract $node, string $op, bool $getVal return $nativePresence; } } - if ($op === self::OP_ISSET + if ( + $op === self::OP_ISSET && $node instanceof Expr\ArrayDimFetch && $node->dim !== null && $node->var instanceof Expr\StaticPropertyFetch @@ -5040,8 +5060,7 @@ protected function checkAccessibleByClassName( string $declaringClass, int $flags, ?string $accessingClass = null - ): bool - { + ): bool { if ($accessingClass !== null) { $accessingClass = ltrim($accessingClass, '\\'); $scopeClassDef = $this->hasClass($accessingClass) @@ -5049,9 +5068,11 @@ protected function checkAccessibleByClassName( : null; } else { $scopeClassDef = $this->classDef; - if ($this->functionDef !== null + if ( + $this->functionDef !== null && $this->functionDef->attributeFactoryScope !== '' - && $this->hasClass($this->functionDef->attributeFactoryScope)) { + && $this->hasClass($this->functionDef->attributeFactoryScope) + ) { $scopeClassDef = $this->getClass($this->functionDef->attributeFactoryScope); } } @@ -5180,8 +5201,10 @@ protected function genLocalVarDecl(array $localVars): string $code .= 'php::Var ' . $name . ' = php::Var(' . $boxCtor . ');' . PHP_EOL; $code .= $this->getIndent() . 'auto &' . $name . '_ref = ' . $name . '.toBox<' . $containerType . '>()->container;'; } - if (!isset($info['boxExpr']) && $info['size'] !== null - && ($defaultValue = $this->getStdContainerDefaultValueExpr($info['type'])) !== null) { + if ( + !isset($info['boxExpr']) && $info['size'] !== null + && ($defaultValue = $this->getStdContainerDefaultValueExpr($info['type'])) !== null + ) { $code .= PHP_EOL . $this->getIndent() . 'php::initializeStdContainer(' . $name . '_ref, ' . $defaultValue . ');'; } } elseif ($type === Type::STD_MAP || $type === Type::STD_ORDERED_MAP) { @@ -5208,7 +5231,8 @@ protected function genLocalVarDecl(array $localVars): string $code .= ';'; } $code .= PHP_EOL; - if ($stdContainerInfo !== null + if ( + $stdContainerInfo !== null && isset($stdContainerInfo['class']) && $this->isNativeObjectClass($stdContainerInfo['class']) ) { @@ -5321,9 +5345,11 @@ protected function genReturnCode(): string if ($this->functionDef->returnTypeCheck && !$this->context->inClosure) { return $this->genUnionCheckedReturn(self::VALUE_NULL); } - if ($this->functionDef->returnType === Type::INT + if ( + $this->functionDef->returnType === Type::INT or $this->functionDef->returnType === Type::FLOAT - or $this->functionDef->returnType === Type::BOOL) { + or $this->functionDef->returnType === Type::BOOL + ) { return $this->getIndent() . 'return 0;'; } else { return $this->getIndent() . 'return ' . self::VALUE_NULL . ';'; diff --git a/src/NativeClass/NativeClassSupportTrait.php b/src/NativeClass/NativeClassSupportTrait.php index 89e34e8c..8001ff31 100644 --- a/src/NativeClass/NativeClassSupportTrait.php +++ b/src/NativeClass/NativeClassSupportTrait.php @@ -991,6 +991,7 @@ protected function assertNativeClassNotUsedWithReflection( } $target = $expr->args[0]->value; + $nativeClass = ''; if ($this->isScalarString($target)) { // Reflection string arguments are runtime names, not names relative diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 265d65f2..cef3d1b2 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -222,7 +222,6 @@ private function isHoistSafeDeclarationInitializer(Expr $expr): bool || ($expr instanceof Expr\ClassConstFetch && $this->isHoistSafeClassConstFetch($expr)) || (($expr instanceof Expr\UnaryPlus || $expr instanceof Expr\UnaryMinus) && ($expr->expr instanceof Node\Scalar\Int_ || $expr->expr instanceof Node\Scalar\Float_)); - return $literal && in_array( $this->detectTypeOfExpr($expr), [Type::INT, Type::FLOAT, Type::BOOL, Type::STR, Type::VAR], diff --git a/src/Parser/SwitchTrait.php b/src/Parser/SwitchTrait.php index b851b54a..86b85240 100644 --- a/src/Parser/SwitchTrait.php +++ b/src/Parser/SwitchTrait.php @@ -104,9 +104,9 @@ protected function parseSwitch(Node\Stmt\Switch_ $v): string ) { $this->fatalError($case, 'switch case must end with return/break/continue/exit/throw, ' . $lastExpr->getType() . ' given'); } - $target = count($caseGroups); + $count = count($caseGroups); if ($hasDefault) { - $defaultTarget = $target; + $defaultTarget = $count; } $caseGroups[] = [$caseConds, $hasDefault, $stmts]; $caseConds = []; diff --git a/src/Parser/TypeConversionTrait.php b/src/Parser/TypeConversionTrait.php index 5e7da7fc..b105920d 100644 --- a/src/Parser/TypeConversionTrait.php +++ b/src/Parser/TypeConversionTrait.php @@ -226,7 +226,7 @@ protected function getNativeType(string $type): string if ($type === Type::FLOAT && $this->decimalTypes) { return Type::DECIMAL; } - return $this->nativeTypes ? $type : Type::VAR; + return $type; } protected function convertExprFromType(string $type, string $expr): string diff --git a/src/Python/PythonModuleTrait.php b/src/Python/PythonModuleTrait.php index cd2a3dd6..02dc44eb 100644 --- a/src/Python/PythonModuleTrait.php +++ b/src/Python/PythonModuleTrait.php @@ -122,9 +122,11 @@ protected function parsePythonObjectCall(Expr\FuncCall $expr): ?string protected function parsePythonObjectPropertyFetch(Expr\PropertyFetch $expr): ?string { - if ($this->isPropertyFetchUpdate($expr) + if ( + $this->isPropertyFetchUpdate($expr) || !$this->isIdExpr($expr->name) - || !$this->isPythonObjectExpr($expr->var)) { + || !$this->isPythonObjectExpr($expr->var) + ) { return null; } @@ -402,7 +404,6 @@ protected function markPythonRuntimeUsed(): void return; } $this->pythonRuntimeUsed = true; - } protected function withPythonRuntimeConfigured(string $expression): string @@ -461,7 +462,6 @@ protected function parsePythonFunctionCall(Expr\FuncCall $expr): ?string if ($expr->isFirstClassCallable()) { $this->fatalError($expr, 'Python module callables do not support first-class callable syntax yet'); } - $target = $this->getPythonModuleExpression($moduleMember['module']); $member = $this->getLiteralString($moduleMember['member']); if ($expr->args === []) { @@ -493,7 +493,7 @@ protected function parsePythonFunctionCall(Expr\FuncCall $expr): ?string $call = $expr->args === [] ? 'php::python::construct(' . $constructor . ')' : 'php::python::construct(' . $constructor . ', ' - . $this->parseCallArgValue($expr->args[0]) . ')'; + . $this->parseCallArgValue($expr->args[0]) . ')'; return $this->withPythonRuntimeConfigured($call); } @@ -507,7 +507,7 @@ protected function parsePythonFunctionCall(Expr\FuncCall $expr): ?string } return $this->withPythonRuntimeConfigured( 'php::newObject(' . $classEntry . ', ' - . $this->parseCallArgs($expr->args, '__construct', $constructorClass) . ')' + . $this->parseCallArgs($expr->args, '__construct', $constructorClass) . ')' ); } @@ -523,7 +523,6 @@ protected function parsePythonFunctionCall(Expr\FuncCall $expr): ?string $this->genRuntimeFunctionCall($callable, $expr->args, $builtin, 'PyCore') ); } - $target = $this->getPythonModuleExpression('builtins'); $name = $this->getLiteralString($builtin); if ($expr->args === []) { @@ -571,7 +570,8 @@ protected function detectPythonExpressionReturnType(NodeAbstract $expr): ?string return Type::OBJECT; } if ($expr instanceof Expr\MethodCall && $this->isPythonObjectExpr($expr->var)) { - if (!$this->isIdExpr($expr->name) + if ( + !$this->isIdExpr($expr->name) || $this->isPythonDynamicMethodCall($expr->var, $this->parseIdentifier($expr->name)) ) { return Type::OBJECT; @@ -584,7 +584,8 @@ protected function detectPythonExpressionReturnType(NodeAbstract $expr): ?string if ($expr instanceof Expr\ArrayDimFetch && $this->isPythonObjectExpr($expr->var)) { return Type::OBJECT; } - if ($expr instanceof Expr\FuncCall + if ( + $expr instanceof Expr\FuncCall && $expr->name instanceof NodeAbstract && !$this->isNameExpr($expr->name) && $this->isPythonObjectExpr($expr->name) @@ -613,7 +614,8 @@ protected function detectPythonExpressionReturnClass(NodeAbstract $expr): ?strin return 'PyObject'; } if ($expr instanceof Expr\MethodCall && $this->isPythonObjectExpr($expr->var)) { - if (!$this->isIdExpr($expr->name) + if ( + !$this->isIdExpr($expr->name) || $this->isPythonDynamicMethodCall($expr->var, $this->parseIdentifier($expr->name)) ) { return 'PyObject'; @@ -626,7 +628,8 @@ protected function detectPythonExpressionReturnClass(NodeAbstract $expr): ?strin if ($expr instanceof Expr\ArrayDimFetch && $this->isPythonObjectExpr($expr->var)) { return 'PyObject'; } - if ($expr instanceof Expr\FuncCall + if ( + $expr instanceof Expr\FuncCall && $expr->name instanceof NodeAbstract && !$this->isNameExpr($expr->name) && $this->isPythonObjectExpr($expr->name) diff --git a/src/gen_stub.php b/src/gen_stub.php index d2d43239..10e7b4cc 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -860,6 +860,7 @@ public static function equals(?StubType $a, ?StubType $b): bool { if ($a === null || $b === null) { return $a === $b; } + if ($a->isIntersection !== $b->isIntersection || ($a->dnfClauses === null) !== ($b->dnfClauses === null) @@ -878,8 +879,8 @@ public static function equals(?StubType $a, ?StubType $b): bool { if (count($a->dnfClauses) !== count($b->dnfClauses)) { return false; } - foreach ($a->dnfClauses as $i => $clause) { - $otherClause = $b->dnfClauses[$i]; + foreach ($a->dnfClauses as $in => $clause) { + $otherClause = $b->dnfClauses[$in]; if (count($clause) !== count($otherClause)) { return false; }