From 1d1784013dca13833cbf75f650816365fff64c26 Mon Sep 17 00:00:00 2001 From: Anton Vasiliev Date: Mon, 20 Sep 2021 14:13:45 +0100 Subject: [PATCH] #2276 - Add support for `mixed` type in parameters --- Library/Backends/ZendEngine3/Backend.php | 18 ++-- Library/ClassMethod.php | 4 +- Library/ClassMethodParameters.php | 1 + Library/Expression/NativeArray.php | 1 + Library/Statements/ReturnStatement.php | 4 + Library/SymbolTable.php | 8 +- ext/stub/types/mixedtype.zep.c | 119 ++++++++++++++++++++++- ext/stub/types/mixedtype.zep.h | 31 ++++++ stub/types/mixedtype.zep | 32 ++++++ tests/Extension/Types/MixedTest.php | 45 --------- tests/Extension/Types/MixedTypeTest.php | 86 ++++++++++++++++ 11 files changed, 291 insertions(+), 58 deletions(-) delete mode 100644 tests/Extension/Types/MixedTest.php create mode 100644 tests/Extension/Types/MixedTypeTest.php diff --git a/Library/Backends/ZendEngine3/Backend.php b/Library/Backends/ZendEngine3/Backend.php index de6fa8db5f..b8bf043c32 100644 --- a/Library/Backends/ZendEngine3/Backend.php +++ b/Library/Backends/ZendEngine3/Backend.php @@ -105,7 +105,7 @@ public function getStringsManager() /** * {@inheritdoc} */ - public function getTypeDefinition($type) + public function getTypeDefinition($type): array { switch ($type) { case 'zend_ulong': @@ -154,6 +154,7 @@ public function getTypeDefinition($type) case 'variable': case 'array': case 'null': + case 'mixed': $pointer = '*'; $code = 'zval'; break; @@ -316,7 +317,7 @@ public function onPostCompile(ClassMethod $method, CompilationContext $context) public function generateInitCode(&$groupVariables, $type, $pointer, Variable $variable) { - $isComplex = \in_array($type, ['variable', 'string', 'array', 'resource', 'callable', 'object'], true); + $isComplex = \in_array($type, ['variable', 'string', 'array', 'resource', 'callable', 'object', 'mixed'], true); if ($isComplex && !$variable->isDoublePointer()) { /* && $variable->mustInitNull() */ $groupVariables[] = $variable->getName(); @@ -365,6 +366,7 @@ public function generateInitCode(&$groupVariables, $type, $pointer, Variable $va case 'resource': case 'callable': case 'object': + case 'mixed': $groupVariables[] = $pointer.$variable->getName(); break; @@ -594,7 +596,7 @@ public function addArrayEntry(Variable $variable, $key, $value, CompilationConte $keyType = 'append'; } elseif ($key instanceof CompiledExpression) { $typeKey = $key->getType(); - if ('variable' == $typeKey) { + if ('variable' === $typeKey || 'mixed' === $typeKey) { $var = $context->symbolTable->getVariableForRead($key->getCode(), $context); $typeKey = $var->getType(); } @@ -629,6 +631,7 @@ public function addArrayEntry(Variable $variable, $key, $value, CompilationConte case 'variable': case 'array': + case 'mixed': $type = 'zval'; break; } @@ -846,20 +849,21 @@ public function resolveValue($value, CompilationContext $context, $usePointer = $tempVariable = new Variable('variable', $varName, $context->branchManager->getCurrentBranch()); $context->symbolTable->addRawVariable($tempVariable); } + $tempVariable = $context->symbolTable->getVariableForWrite($varName, $context); $tempVariable->increaseUses(); - $tempVariable->setUsed(true, null); + $tempVariable->setUsed(true); + if ('null' == $value) { $tempVariable->setDynamicTypes('null'); } else { $tempVariable->setDynamicTypes('bool'); } + $value = $this->getVariableCode($tempVariable); } else { if ($value instanceof CompiledExpression) { - if ('array' == $value->getType()) { - $value = $context->symbolTable->getVariableForWrite($value->getCode(), $context, null); - } elseif ('variable' == $value->getType()) { + if (in_array($value->getType(), ['array', 'variable', 'mixed'])) { $value = $context->symbolTable->getVariableForWrite($value->getCode(), $context); } else { return $value->getCode(); diff --git a/Library/ClassMethod.php b/Library/ClassMethod.php index 1fb7583e07..003fe71f63 100644 --- a/Library/ClassMethod.php +++ b/Library/ClassMethod.php @@ -1447,7 +1447,7 @@ public function assignZvalValue(array $parameter, CompilationContext $compilatio { $dataType = $this->getParamDataType($parameter); - if (in_array($dataType, ['variable', 'callable', 'object', 'resource'])) { + if (in_array($dataType, ['variable', 'callable', 'object', 'resource', 'mixed'])) { return ""; } @@ -1652,6 +1652,7 @@ public function compile(CompilationContext $compilationContext): void case 'callable': case 'resource': case 'variable': + case 'mixed': $symbol = $symbolTable->addVariable($parameter['data-type'], $parameter['name'], $compilationContext); /* TODO: Move this to the respective backend, which requires refactoring how this works */ if ($compilationContext->backend->isZE3()) { @@ -1878,6 +1879,7 @@ public function compile(CompilationContext $compilationContext): void case 'callable': case 'resource': case 'variable': + case 'mixed': $name = $parameter['name']; break; diff --git a/Library/ClassMethodParameters.php b/Library/ClassMethodParameters.php index 18de1afad3..13e54efb5d 100644 --- a/Library/ClassMethodParameters.php +++ b/Library/ClassMethodParameters.php @@ -115,6 +115,7 @@ public function fetchParameters(bool $isMethodInternal): array case 'callable': case 'resource': case 'variable': + case 'mixed': $parameters[] = $isMethodInternal ? $name : '&'.$name; break; diff --git a/Library/Expression/NativeArray.php b/Library/Expression/NativeArray.php index 23382881cb..8e61539da7 100644 --- a/Library/Expression/NativeArray.php +++ b/Library/Expression/NativeArray.php @@ -152,6 +152,7 @@ public function getArrayValue(CompiledExpression $exprCompiled, CompilationConte case 'string': case 'variable': case 'array': + case 'mixed': return $itemVariable; default: diff --git a/Library/Statements/ReturnStatement.php b/Library/Statements/ReturnStatement.php index 65877d5d32..5ffc637dec 100644 --- a/Library/Statements/ReturnStatement.php +++ b/Library/Statements/ReturnStatement.php @@ -138,6 +138,7 @@ public function compile(CompilationContext $compilationContext): void break; case Types::T_VARIABLE: + case Types::T_MIXED: $symbolVariable = $compilationContext->symbolTable->getVariableForRead( $resolvedExpr->getCode(), $compilationContext, @@ -176,6 +177,7 @@ public function compile(CompilationContext $compilationContext): void break; case Types::T_VARIABLE: + case Types::T_MIXED: break; } break; @@ -221,6 +223,7 @@ public function compile(CompilationContext $compilationContext): void break; case Types::T_VARIABLE: + case Types::T_MIXED: if (!isset($symbolVariable)) { $symbolVariable = $compilationContext->symbolTable->getVariableForRead( $resolvedExpr->getCode(), @@ -256,6 +259,7 @@ public function compile(CompilationContext $compilationContext): void break; case Types::T_VARIABLE: + case Types::T_MIXED: if ('this_ptr' == $symbolVariable->getName()) { $codePrinter->output('RETURN_THIS();'); } else { diff --git a/Library/SymbolTable.php b/Library/SymbolTable.php index b451929cf0..7fe44386e9 100644 --- a/Library/SymbolTable.php +++ b/Library/SymbolTable.php @@ -253,7 +253,7 @@ public function getVariablesByBranch($branchId) */ public function getVariableForRead($name, CompilationContext $compilationContext = null, array $statement = null) { - /* + /** * Validate that 'this' cannot be used in a static function */ if ('this' == $name || 'this_ptr' == $name) { @@ -262,7 +262,7 @@ public function getVariableForRead($name, CompilationContext $compilationContext } } - /* + /** * Create superglobals just in time */ if ($this->globalsManager->isSuperGlobal($name)) { @@ -295,7 +295,7 @@ public function getVariableForRead($name, CompilationContext $compilationContext $variable->increaseUses(); - /* + /** * Analise branches to detect possible initialization of variables in conditional branches */ if (!$variable->isTemporal() && !$variable->getSkipVariant()) { @@ -306,6 +306,7 @@ public function getVariableForRead($name, CompilationContext $compilationContext case 'variable': case 'string': case 'array': + case 'mixed': if (!$variable->isLocalOnly()) { $variable->setMustInitNull(true); } @@ -380,7 +381,6 @@ public function getVariableForRead($name, CompilationContext $compilationContext foreach ($branches as $branch) { $graph->addLeaf($branch); } - //echo $graph->getRoot()->show(); } else { /* * Variable is assigned just once and it's assigned in a conditional branch diff --git a/ext/stub/types/mixedtype.zep.c b/ext/stub/types/mixedtype.zep.c index 3659d7286b..7ec1a3d8ee 100644 --- a/ext/stub/types/mixedtype.zep.c +++ b/ext/stub/types/mixedtype.zep.c @@ -15,6 +15,7 @@ #include "kernel/object.h" #include "kernel/operators.h" #include "kernel/memory.h" +#include "kernel/array.h" ZEPHIR_INIT_CLASS(Stub_Types_MixedType) @@ -24,6 +25,9 @@ ZEPHIR_INIT_CLASS(Stub_Types_MixedType) return SUCCESS; } +/** + * Mixed only as return type methods + */ PHP_METHOD(Stub_Types_MixedType, returnMixedObject) { zval *this_ptr = getThis(); @@ -125,6 +129,119 @@ PHP_METHOD(Stub_Types_MixedType, returnMultiButAlwaysMixed) - RETURN_STRING("IS_MIXED in header"); + RETURN_STRING("ZEND_BEGIN_ARG_INFO_EX"); +} + +/** + * Mixed only as parameter in method + */ +PHP_METHOD(Stub_Types_MixedType, paramMixed) +{ + zval val_sub; + zval *val; + zval *this_ptr = getThis(); + + ZVAL_UNDEF(&val_sub); +#if PHP_VERSION_ID >= 80000 + bool is_null_true = 1; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(val) + ZEND_PARSE_PARAMETERS_END(); +#endif + + + zephir_fetch_params_without_memory_grow(1, 0, &val); + + + RETVAL_ZVAL(val, 1, 0); + return; +} + +PHP_METHOD(Stub_Types_MixedType, paramMixedTwo) +{ + zval val1_sub, val2_sub; + zval *val1, *val2; + zval *this_ptr = getThis(); + + ZVAL_UNDEF(&val1_sub); + ZVAL_UNDEF(&val2_sub); +#if PHP_VERSION_ID >= 80000 + bool is_null_true = 1; + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(val1) + Z_PARAM_ZVAL(val2) + ZEND_PARSE_PARAMETERS_END(); +#endif + + + zephir_fetch_params_without_memory_grow(2, 0, &val1, &val2); + + + zephir_create_array(return_value, 2, 0); + zephir_array_fast_append(return_value, val1); + zephir_array_fast_append(return_value, val2); + return; +} + +PHP_METHOD(Stub_Types_MixedType, paramMixedWithMulti) +{ + zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL; + zval *mixedVal; + zval stringVal; + zval *intVal_param = NULL, *stringVal_param = NULL, mixedVal_sub, _0; + zend_long intVal; + zval *this_ptr = getThis(); + + ZVAL_UNDEF(&mixedVal_sub); + ZVAL_UNDEF(&_0); + ZVAL_UNDEF(&stringVal); +#if PHP_VERSION_ID >= 80000 + bool is_null_true = 1; + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_LONG(intVal) + Z_PARAM_STR(stringVal) + Z_PARAM_ZVAL(mixedVal) + ZEND_PARSE_PARAMETERS_END(); +#endif + + + ZEPHIR_MM_GROW(); + zephir_fetch_params(1, 3, 0, &intVal_param, &stringVal_param, &mixedVal); + intVal = zephir_get_intval(intVal_param); + zephir_get_strval(&stringVal, stringVal_param); + + + zephir_create_array(return_value, 3, 0); + ZEPHIR_INIT_VAR(&_0); + ZVAL_LONG(&_0, intVal); + zephir_array_fast_append(return_value, &_0); + zephir_array_fast_append(return_value, &stringVal); + zephir_array_fast_append(return_value, mixedVal); + RETURN_MM(); +} + +/** + * Mixed as as parameter and return type in method + */ +PHP_METHOD(Stub_Types_MixedType, paramAndReturnMixed) +{ + zval val_sub; + zval *val; + zval *this_ptr = getThis(); + + ZVAL_UNDEF(&val_sub); +#if PHP_VERSION_ID >= 80000 + bool is_null_true = 1; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(val) + ZEND_PARSE_PARAMETERS_END(); +#endif + + + zephir_fetch_params_without_memory_grow(1, 0, &val); + + + RETVAL_ZVAL(val, 1, 0); + return; } diff --git a/ext/stub/types/mixedtype.zep.h b/ext/stub/types/mixedtype.zep.h index 929cb05366..641da1d00f 100644 --- a/ext/stub/types/mixedtype.zep.h +++ b/ext/stub/types/mixedtype.zep.h @@ -12,6 +12,10 @@ PHP_METHOD(Stub_Types_MixedType, returnMixedBool); PHP_METHOD(Stub_Types_MixedType, returnMixedNull); PHP_METHOD(Stub_Types_MixedType, returnMixed74); PHP_METHOD(Stub_Types_MixedType, returnMultiButAlwaysMixed); +PHP_METHOD(Stub_Types_MixedType, paramMixed); +PHP_METHOD(Stub_Types_MixedType, paramMixedTwo); +PHP_METHOD(Stub_Types_MixedType, paramMixedWithMulti); +PHP_METHOD(Stub_Types_MixedType, paramAndReturnMixed); #if PHP_VERSION_ID >= 80000 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_stub_types_mixedtype_returnmixedobject, 0, 0, IS_MIXED, 0) @@ -73,6 +77,29 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_stub_types_mixedtype_returnmultibutalwaysmixed, 0, 0, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_stub_types_mixedtype_parammixed, 0, 0, 1) + ZEND_ARG_INFO(0, val) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_stub_types_mixedtype_parammixedtwo, 0, 0, 2) + ZEND_ARG_INFO(0, val1) + ZEND_ARG_INFO(0, val2) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_stub_types_mixedtype_parammixedwithmulti, 0, 0, 3) + ZEND_ARG_TYPE_INFO(0, intVal, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, stringVal, IS_STRING, 0) + ZEND_ARG_INFO(0, mixedVal) +ZEND_END_ARG_INFO() + +#if PHP_VERSION_ID >= 80000 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_stub_types_mixedtype_paramandreturnmixed, 0, 1, IS_MIXED, 0) +#else +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_stub_types_mixedtype_paramandreturnmixed, 0, 1, IS_NULL, 0) +#endif + ZEND_ARG_INFO(0, val) +ZEND_END_ARG_INFO() + ZEPHIR_INIT_FUNCS(stub_types_mixedtype_method_entry) { PHP_ME(Stub_Types_MixedType, returnMixedObject, arginfo_stub_types_mixedtype_returnmixedobject, ZEND_ACC_PUBLIC) PHP_ME(Stub_Types_MixedType, returnMixedArray, arginfo_stub_types_mixedtype_returnmixedarray, ZEND_ACC_PUBLIC) @@ -87,5 +114,9 @@ ZEPHIR_INIT_FUNCS(stub_types_mixedtype_method_entry) { #else PHP_ME(Stub_Types_MixedType, returnMultiButAlwaysMixed, NULL, ZEND_ACC_PUBLIC) #endif + PHP_ME(Stub_Types_MixedType, paramMixed, arginfo_stub_types_mixedtype_parammixed, ZEND_ACC_PUBLIC) + PHP_ME(Stub_Types_MixedType, paramMixedTwo, arginfo_stub_types_mixedtype_parammixedtwo, ZEND_ACC_PUBLIC) + PHP_ME(Stub_Types_MixedType, paramMixedWithMulti, arginfo_stub_types_mixedtype_parammixedwithmulti, ZEND_ACC_PUBLIC) + PHP_ME(Stub_Types_MixedType, paramAndReturnMixed, arginfo_stub_types_mixedtype_paramandreturnmixed, ZEND_ACC_PUBLIC) PHP_FE_END }; diff --git a/stub/types/mixedtype.zep b/stub/types/mixedtype.zep index 6553360440..182e3d8f41 100644 --- a/stub/types/mixedtype.zep +++ b/stub/types/mixedtype.zep @@ -3,6 +3,10 @@ namespace Stub\Types; class MixedType { + /** + * Mixed only as return type methods + */ + public function returnMixedObject() -> mixed { return new \stdClass(); @@ -52,4 +56,32 @@ class MixedType // Maybe in future make sense to generate with `IS_MIXED`. return "ZEND_BEGIN_ARG_INFO_EX"; } + + /** + * Mixed only as parameter in method + */ + + public function paramMixed(mixed val) + { + return val; + } + + public function paramMixedTwo(mixed val1, mixed val2) + { + return [val1, val2]; + } + + public function paramMixedWithMulti(int intVal, string stringVal, mixed mixedVal) + { + return [intVal, stringVal, mixedVal]; + } + + /** + * Mixed as as parameter and return type in method + */ + + public function paramAndReturnMixed(mixed val) -> mixed + { + return val; + } } diff --git a/tests/Extension/Types/MixedTest.php b/tests/Extension/Types/MixedTest.php deleted file mode 100644 index cf87d94b30..0000000000 --- a/tests/Extension/Types/MixedTest.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Extension\Types; - -use PHPUnit\Framework\TestCase; -use Stub\Types\MixedType; - -final class MixedTest extends TestCase -{ - public function testReturnsOfMixedType(): void - { - $returns = new MixedType(); - - $this->assertEquals((new \stdClass()), $returns->returnMixedObject()); - $this->assertSame([], $returns->returnMixedArray()); - $this->assertSame('mixed string', $returns->returnMixedString()); - $this->assertSame(1, $returns->returnMixedInt()); - $this->assertSame(3.14, $returns->returnMixedFloat()); - $this->assertSame(true, $returns->returnMixedBool()); - $this->assertSame(null, $returns->returnMixedNull()); - - $this->assertNotNull($returns->returnMixedObject()); - $this->assertNotNull($returns->returnMixedArray()); - $this->assertNotNull($returns->returnMixedString()); - $this->assertNotNull($returns->returnMixedInt()); - $this->assertNotNull($returns->returnMixedFloat()); - $this->assertNotNull($returns->returnMixedBool()); - $this->assertNull($returns->returnMixedNull()); - - $this->assertEquals((new \stdClass()), $returns->returnMixed74()); - $this->assertSame('string', $returns->returnMixed74(true)); - $this->assertEquals((new \stdClass()), $returns->returnMixed74(false)); - } -} diff --git a/tests/Extension/Types/MixedTypeTest.php b/tests/Extension/Types/MixedTypeTest.php new file mode 100644 index 0000000000..58ddb11c82 --- /dev/null +++ b/tests/Extension/Types/MixedTypeTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Extension\Types; + +use PHPUnit\Framework\TestCase; +use Stub\Types\MixedType; + +final class MixedTypeTest extends TestCase +{ + public function testReturnsOfMixedType(): void + { + $returns = new MixedType(); + + $this->assertEquals((new \stdClass()), $returns->returnMixedObject()); + $this->assertSame([], $returns->returnMixedArray()); + $this->assertSame('mixed string', $returns->returnMixedString()); + $this->assertSame(1, $returns->returnMixedInt()); + $this->assertSame(3.14, $returns->returnMixedFloat()); + $this->assertSame(true, $returns->returnMixedBool()); + $this->assertSame(null, $returns->returnMixedNull()); + + $this->assertNotNull($returns->returnMixedObject()); + $this->assertNotNull($returns->returnMixedArray()); + $this->assertNotNull($returns->returnMixedString()); + $this->assertNotNull($returns->returnMixedInt()); + $this->assertNotNull($returns->returnMixedFloat()); + $this->assertNotNull($returns->returnMixedBool()); + $this->assertNull($returns->returnMixedNull()); + + $this->assertEquals((new \stdClass()), $returns->returnMixed74()); + $this->assertSame('string', $returns->returnMixed74(true)); + $this->assertEquals((new \stdClass()), $returns->returnMixed74(false)); + } + + public function testParamsOfMixedType(): void + { + $returns = new MixedType(); + + $this->assertEquals((new \stdClass()), $returns->paramMixed((new \stdClass()))); + $this->assertSame([], $returns->paramMixed([])); + $this->assertSame('mixed string', $returns->paramMixed('mixed string')); + $this->assertSame(1, $returns->paramMixed(1)); + $this->assertSame(3.14, $returns->paramMixed(3.14)); + $this->assertSame(true, $returns->paramMixed(true)); + $this->assertSame(null, $returns->paramMixed(null)); + + $this->assertEquals([(new \stdClass()), []], $returns->paramMixedTwo((new \stdClass()), [])); + $this->assertSame([[], 'mixed string'], $returns->paramMixedTwo([], 'mixed string')); + $this->assertSame([1, 3.14], $returns->paramMixedTwo(1, 3.14)); + $this->assertSame([3.14, true], $returns->paramMixedTwo(3.14, true)); + $this->assertSame([true, null], $returns->paramMixedTwo(true, null)); + $this->assertSame([null, null], $returns->paramMixedTwo(null, null)); + + $this->assertEquals([1337, 'object', (new \stdClass())], $returns->paramMixedWithMulti(1337, 'object', (new \stdClass()))); + $this->assertSame([1337, 'array', []], $returns->paramMixedWithMulti(1337, 'array', [])); + $this->assertSame([1337, 'string', 'mixed string'], $returns->paramMixedWithMulti(1337, 'string', 'mixed string')); + $this->assertSame([1337, 'int', 123], $returns->paramMixedWithMulti(1337, 'int', 123)); + $this->assertSame([1337, 'float', 2.44], $returns->paramMixedWithMulti(1337, 'float', 2.44)); + $this->assertSame([1337, 'bool', false], $returns->paramMixedWithMulti(1337, 'bool', false)); + $this->assertSame([1337, 'null', null], $returns->paramMixedWithMulti(1337, 'null', null)); + } + + public function testParamsAndReturnsOfMixedType(): void + { + $returns = new MixedType(); + + $this->assertEquals((new \stdClass()), $returns->paramAndReturnMixed((new \stdClass()))); + $this->assertSame([], $returns->paramAndReturnMixed([])); + $this->assertSame('mixed string', $returns->paramAndReturnMixed('mixed string')); + $this->assertSame(1, $returns->paramAndReturnMixed(1)); + $this->assertSame(3.14, $returns->paramAndReturnMixed(3.14)); + $this->assertSame(true, $returns->paramAndReturnMixed(true)); + $this->assertSame(null, $returns->paramAndReturnMixed(null)); + } +}