From 05426cc00d7a4126063a4444b11abc29f01cd3db Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Mon, 22 Aug 2022 17:57:12 -0400 Subject: [PATCH 01/12] Switch from ClassName:spl_object_hash to ClassName#id Use a much shorter object id using spl_object_id (available in PHP 7.2+) This will be shorter and easier to tell apart than hex character hashes of length 32. Closes #549 --- spec/Prophecy/Argument/ArgumentsWildcardSpec.php | 8 ++++---- .../Argument/Token/ExactValueTokenSpec.php | 10 +++++----- .../Argument/Token/IdenticalValueTokenSpec.php | 10 +++++----- spec/Prophecy/Util/StringUtilSpec.php | 14 +++++++------- src/Prophecy/Util/ExportUtil.php | 11 ++++++----- src/Prophecy/Util/StringUtil.php | 2 +- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/spec/Prophecy/Argument/ArgumentsWildcardSpec.php b/spec/Prophecy/Argument/ArgumentsWildcardSpec.php index 4daa0b0b3..6c9d52caf 100644 --- a/spec/Prophecy/Argument/ArgumentsWildcardSpec.php +++ b/spec/Prophecy/Argument/ArgumentsWildcardSpec.php @@ -12,12 +12,12 @@ function it_wraps_non_token_arguments_into_ExactValueToken(\stdClass $object) $this->beConstructedWith(array(42, 'zet', $object)); $class = get_class($object->getWrappedObject()); - $hash = spl_object_hash($object->getWrappedObject()); + $id = spl_object_id($object->getWrappedObject()); - $objHash = "exact(42), exact(\"zet\"), exact($class:$hash Object (\n 'objectProphecyClosure' => Closure:%s Object (\n 0 => Closure:%s Object\n )\n))"; + $objHash = "exact(42), exact(\"zet\"), exact($class#$id Object (\n 'objectProphecyClosure' => Closure#%s Object (\n 0 => Closure#%s Object\n )\n))"; - $hashRegexExpr = '[a-f0-9]{32}'; - $this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $hashRegexExpr, $hashRegexExpr))); + $idRegexExpr = '[0-9]+'; + $this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr))); } function it_generates_string_representation_from_all_tokens_imploded( diff --git a/spec/Prophecy/Argument/Token/ExactValueTokenSpec.php b/spec/Prophecy/Argument/Token/ExactValueTokenSpec.php index 6b8fb3695..d8ebc82c0 100644 --- a/spec/Prophecy/Argument/Token/ExactValueTokenSpec.php +++ b/spec/Prophecy/Argument/Token/ExactValueTokenSpec.php @@ -133,15 +133,15 @@ function it_generates_proper_string_representation_for_resource() function it_generates_proper_string_representation_for_object(\stdClass $object) { - $objHash = sprintf('exact(%s:%s', + $objHash = sprintf('exact(%s#%s', get_class($object->getWrappedObject()), - spl_object_hash($object->getWrappedObject()) - ) . " Object (\n 'objectProphecyClosure' => Closure:%s Object (\n 0 => Closure:%s Object\n )\n))"; + spl_object_id($object->getWrappedObject()) + ) . " Object (\n 'objectProphecyClosure' => Closure#%s Object (\n 0 => Closure#%s Object\n )\n))"; $this->beConstructedWith($object); - $hashRegexExpr = '[a-f0-9]{32}'; - $this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $hashRegexExpr, $hashRegexExpr))); + $idRegexExpr = '[0-9]+'; + $this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr))); } } diff --git a/spec/Prophecy/Argument/Token/IdenticalValueTokenSpec.php b/spec/Prophecy/Argument/Token/IdenticalValueTokenSpec.php index 42625b695..1c71c6d5d 100644 --- a/spec/Prophecy/Argument/Token/IdenticalValueTokenSpec.php +++ b/spec/Prophecy/Argument/Token/IdenticalValueTokenSpec.php @@ -141,14 +141,14 @@ function it_generates_proper_string_representation_for_resource() function it_generates_proper_string_representation_for_object($object) { - $objHash = sprintf('identical(%s:%s', + $objHash = sprintf('identical(%s#%s', get_class($object->getWrappedObject()), - spl_object_hash($object->getWrappedObject()) - ) . " Object (\n 'objectProphecyClosure' => Closure:%s Object (\n 0 => Closure:%s Object\n )\n))"; + spl_object_id($object->getWrappedObject()) + ) . " Object (\n 'objectProphecyClosure' => Closure#%s Object (\n 0 => Closure#%s Object\n )\n))"; $this->beConstructedWith($object); - $hashRegexExpr = '[a-f0-9]{32}'; - $this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $hashRegexExpr, $hashRegexExpr))); + $idRegexExpr = '[0-9]+'; + $this->__toString()->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr))); } } diff --git a/spec/Prophecy/Util/StringUtilSpec.php b/spec/Prophecy/Util/StringUtilSpec.php index 923f09af1..491cc1a54 100644 --- a/spec/Prophecy/Util/StringUtilSpec.php +++ b/spec/Prophecy/Util/StringUtilSpec.php @@ -71,20 +71,20 @@ function it_generates_proper_string_representation_for_resource() function it_generates_proper_string_representation_for_object(\stdClass $object) { - $objHash = sprintf('%s:%s', + $objHash = sprintf('%s#%s', get_class($object->getWrappedObject()), - spl_object_hash($object->getWrappedObject()) - ) . " Object (\n 'objectProphecyClosure' => Closure:%s Object (\n 0 => Closure:%s Object\n )\n)"; + spl_object_id($object->getWrappedObject()) + ) . " Object (\n 'objectProphecyClosure' => Closure#%s Object (\n 0 => Closure#%s Object\n )\n)"; - $hashRegexExpr = '[a-f0-9]{32}'; - $this->stringify($object)->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $hashRegexExpr, $hashRegexExpr))); + $idRegexExpr = '[0-9]+'; + $this->stringify($object)->shouldMatch(sprintf('/^%s$/', sprintf(preg_quote("$objHash"), $idRegexExpr, $idRegexExpr))); } function it_generates_proper_string_representation_for_object_without_exporting(\stdClass $object) { - $objHash = sprintf('%s:%s', + $objHash = sprintf('%s#%s', get_class($object->getWrappedObject()), - spl_object_hash($object->getWrappedObject()) + spl_object_id($object->getWrappedObject()) ); $this->stringify($object, false)->shouldReturn("$objHash"); diff --git a/src/Prophecy/Util/ExportUtil.php b/src/Prophecy/Util/ExportUtil.php index 1090a801e..0c66cba04 100644 --- a/src/Prophecy/Util/ExportUtil.php +++ b/src/Prophecy/Util/ExportUtil.php @@ -91,7 +91,8 @@ public static function toArray($value) } foreach ($value as $key => $val) { - $array[spl_object_hash($val)] = array( + // Use the same identifier that would be printed alongside the object's representation elsewhere. + $array[spl_object_id($val)] = array( 'obj' => $val, 'inf' => $value->getInfo(), ); @@ -181,11 +182,11 @@ protected static function recursiveExport(&$value, $indentation, $processed = nu if (is_object($value)) { $class = get_class($value); - if ($hash = $processed->contains($value)) { - return sprintf('%s:%s Object', $class, $hash); + if ($processed->contains($value)) { + return sprintf('%s#%d Object', $class, spl_object_id($value)); } - $hash = $processed->add($value); + $processed->add($value); $values = ''; $array = self::toArray($value); @@ -202,7 +203,7 @@ protected static function recursiveExport(&$value, $indentation, $processed = nu $values = "\n" . $values . $whitespace; } - return sprintf('%s:%s Object (%s)', $class, $hash, $values); + return sprintf('%s#%d Object (%s)', $class, spl_object_id($value), $values); } return var_export($value, true); diff --git a/src/Prophecy/Util/StringUtil.php b/src/Prophecy/Util/StringUtil.php index ba4faff57..b63181ecc 100644 --- a/src/Prophecy/Util/StringUtil.php +++ b/src/Prophecy/Util/StringUtil.php @@ -56,7 +56,7 @@ public function stringify($value, $exportObject = true) return get_resource_type($value).':'.$value; } if (is_object($value)) { - return $exportObject ? ExportUtil::export($value) : sprintf('%s:%s', get_class($value), spl_object_hash($value)); + return $exportObject ? ExportUtil::export($value) : sprintf('%s#%s', get_class($value), spl_object_id($value)); } if (true === $value || false === $value) { return $value ? 'true' : 'false'; From c1a2c7141596adee984b306f425f762fcd756488 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Wed, 24 Aug 2022 07:35:48 -0400 Subject: [PATCH 02/12] Remove workarounds for unsupported HHVM And clean up redundant test case `@requires`, composer.json requires php 7.2. See #561 --- .../ClassPatch/HhvmExceptionPatchSpec.php | 34 ---------- .../Doubler/ClassPatch/HhvmExceptionPatch.php | 63 ------------------- src/Prophecy/Prophet.php | 1 - src/Prophecy/Util/ExportUtil.php | 12 ---- tests/Doubler/Generator/ClassMirrorTest.php | 12 +--- 5 files changed, 1 insertion(+), 121 deletions(-) delete mode 100644 spec/Prophecy/Doubler/ClassPatch/HhvmExceptionPatchSpec.php delete mode 100644 src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php diff --git a/spec/Prophecy/Doubler/ClassPatch/HhvmExceptionPatchSpec.php b/spec/Prophecy/Doubler/ClassPatch/HhvmExceptionPatchSpec.php deleted file mode 100644 index 9d04421af..000000000 --- a/spec/Prophecy/Doubler/ClassPatch/HhvmExceptionPatchSpec.php +++ /dev/null @@ -1,34 +0,0 @@ -shouldBeAnInstanceOf('Prophecy\Doubler\ClassPatch\ClassPatchInterface'); - } - - function its_priority_is_minus_50() - { - $this->getPriority()->shouldReturn(-50); - } - - function it_uses_parent_code_for_setTraceOptions(ClassNode $node, MethodNode $method, MethodNode $getterMethod) - { - $node->hasMethod('setTraceOptions')->willReturn(true); - $node->getMethod('setTraceOptions')->willReturn($method); - $node->hasMethod('getTraceOptions')->willReturn(true); - $node->getMethod('getTraceOptions')->willReturn($getterMethod); - - $method->useParentCode()->shouldBeCalled(); - $getterMethod->useParentCode()->shouldBeCalled(); - - $this->apply($node); - } -} diff --git a/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php b/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php deleted file mode 100644 index fa38fc0d1..000000000 --- a/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php +++ /dev/null @@ -1,63 +0,0 @@ - - * Marcello Duarte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Prophecy\Doubler\ClassPatch; - -use Prophecy\Doubler\Generator\Node\ClassNode; - -/** - * Exception patch for HHVM to remove the stubs from special methods - * - * @author Christophe Coevoet - */ -class HhvmExceptionPatch implements ClassPatchInterface -{ - /** - * Supports exceptions on HHVM. - * - * @param ClassNode $node - * - * @return bool - */ - public function supports(ClassNode $node) - { - if (!defined('HHVM_VERSION')) { - return false; - } - - return 'Exception' === $node->getParentClass() || is_subclass_of($node->getParentClass(), 'Exception'); - } - - /** - * Removes special exception static methods from the doubled methods. - * - * @param ClassNode $node - * - * @return void - */ - public function apply(ClassNode $node) - { - if ($node->hasMethod('setTraceOptions')) { - $node->getMethod('setTraceOptions')->useParentCode(); - } - if ($node->hasMethod('getTraceOptions')) { - $node->getMethod('getTraceOptions')->useParentCode(); - } - } - - /** - * {@inheritdoc} - */ - public function getPriority() - { - return -50; - } -} diff --git a/src/Prophecy/Prophet.php b/src/Prophecy/Prophet.php index d37c92a34..15a4969dc 100644 --- a/src/Prophecy/Prophet.php +++ b/src/Prophecy/Prophet.php @@ -59,7 +59,6 @@ public function __construct( $doubler->registerClassPatch(new ClassPatch\DisableConstructorPatch); $doubler->registerClassPatch(new ClassPatch\ProphecySubjectPatch); $doubler->registerClassPatch(new ClassPatch\ReflectionClassNewInstancePatch); - $doubler->registerClassPatch(new ClassPatch\HhvmExceptionPatch()); $doubler->registerClassPatch(new ClassPatch\MagicCallPatch); $doubler->registerClassPatch(new ClassPatch\KeywordPatch); } diff --git a/src/Prophecy/Util/ExportUtil.php b/src/Prophecy/Util/ExportUtil.php index 0c66cba04..e3a02c031 100644 --- a/src/Prophecy/Util/ExportUtil.php +++ b/src/Prophecy/Util/ExportUtil.php @@ -78,18 +78,6 @@ public static function toArray($value) // above (fast) mechanism nor with reflection in Zend. // Format the output similarly to print_r() in this case if ($value instanceof \SplObjectStorage) { - // However, the fast method does work in HHVM, and exposes the - // internal implementation. Hide it again. - if (property_exists('\SplObjectStorage', '__storage')) { - unset($array['__storage']); - } elseif (property_exists('\SplObjectStorage', 'storage')) { - unset($array['storage']); - } - - if (property_exists('\SplObjectStorage', '__key')) { - unset($array['__key']); - } - foreach ($value as $key => $val) { // Use the same identifier that would be printed alongside the object's representation elsewhere. $array[spl_object_id($val)] = array( diff --git a/tests/Doubler/Generator/ClassMirrorTest.php b/tests/Doubler/Generator/ClassMirrorTest.php index 03f5964ec..18c966837 100644 --- a/tests/Doubler/Generator/ClassMirrorTest.php +++ b/tests/Doubler/Generator/ClassMirrorTest.php @@ -123,7 +123,6 @@ public function it_properly_reads_methods_arguments_with_types() /** * @test - * @requires PHP 5.4 */ public function it_properly_reads_methods_arguments_with_callable_types() { @@ -153,7 +152,6 @@ public function it_properly_reads_methods_arguments_with_callable_types() /** * @test - * @requires PHP 5.6 */ public function it_properly_reads_methods_variadic_arguments() { @@ -176,14 +174,9 @@ public function it_properly_reads_methods_variadic_arguments() /** * @test - * @requires PHP 5.6 */ public function it_properly_reads_methods_typehinted_variadic_arguments() { - if (defined('HHVM_VERSION_ID')) { - $this->markTestSkipped('HHVM does not support typehints on variadic arguments.'); - } - $class = new \ReflectionClass('Fixtures\Prophecy\WithTypehintedVariadicArgument'); $mirror = new ClassMirror(); @@ -337,7 +330,6 @@ public function it_does_not_throw_exception_for_virtually_private_finals() /** * @test - * @requires PHP 7 */ public function it_reflects_return_typehints() { @@ -418,7 +410,6 @@ public function it_doesnt_fail_to_typehint_nonexistent_FQCN() /** * @test - * @requires PHP 7.1 */ public function it_doesnt_fail_on_array_nullable_parameter_with_not_null_default_value() { @@ -445,7 +436,6 @@ public function it_doesnt_fail_to_typehint_nonexistent_RQCN() /** * @test - * @requires PHP 7.2 */ function it_doesnt_fail_when_method_is_extended_with_more_params() { @@ -614,7 +604,7 @@ public function it_can_not_double_an_enum() } $this->expectException(ClassMirrorException::class); - + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\Enum'), []); } From 48333044faa315cba92a24ca44ca67fe778c3cad Mon Sep 17 00:00:00 2001 From: kschatzle Date: Fri, 9 Sep 2022 13:47:16 -0500 Subject: [PATCH 03/12] Initial compatability support for PHP8.2 for disjunctive normal form arguments and return types. DNF arguments and return types will throw ClassMirrorException's. --- fixtures/DnfArgumentType.php | 11 ++++++++ fixtures/DnfReturnType.php | 11 ++++++++ .../Doubler/Generator/ClassMirror.php | 7 +++++ tests/Doubler/Generator/ClassMirrorTest.php | 28 +++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 fixtures/DnfArgumentType.php create mode 100644 fixtures/DnfReturnType.php diff --git a/fixtures/DnfArgumentType.php b/fixtures/DnfArgumentType.php new file mode 100644 index 000000000..494b39295 --- /dev/null +++ b/fixtures/DnfArgumentType.php @@ -0,0 +1,11 @@ +getTypes(); + if (\PHP_VERSION_ID >= 80200) { + foreach ($types as $reflectionType) { + if ($reflectionType instanceof ReflectionIntersectionType) { + throw new ClassMirrorException('Doubling intersection types is not supported', $class); + } + } + } } elseif ($type instanceof ReflectionIntersectionType) { throw new ClassMirrorException('Doubling intersection types is not supported', $class); diff --git a/tests/Doubler/Generator/ClassMirrorTest.php b/tests/Doubler/Generator/ClassMirrorTest.php index 03f5964ec..fc19b9bca 100644 --- a/tests/Doubler/Generator/ClassMirrorTest.php +++ b/tests/Doubler/Generator/ClassMirrorTest.php @@ -645,4 +645,32 @@ public function it_can_not_double_intersection_argument_types() $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\IntersectionArgumentType'), []); } + + /** + * @test + */ + public function it_can_not_double_dnf_intersection_argument_types() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('DNF intersection types are not supported in this PHP version'); + } + + $this->expectException(ClassMirrorException::class); + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\DnfArgumentType'), []); + } + + /** + * @test + */ + public function it_can_not_double_dnf_intersection_return_types() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('DNF intersection types are not supported in this PHP version'); + } + + $this->expectException(ClassMirrorException::class); + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\DnfReturnType'), []); + } } From a5a13843ef72f7ae6513af1e9ded5d8b8b386712 Mon Sep 17 00:00:00 2001 From: kschatzle Date: Sun, 11 Sep 2022 13:22:06 -0500 Subject: [PATCH 04/12] Support standalone and nullable types for true, false, and null. --- fixtures/NullableParameterTypeFalse.php | 11 ++ fixtures/NullableParameterTypeTrue.php | 11 ++ fixtures/NullableReturnTypeFalse.php | 11 ++ fixtures/NullableReturnTypeTrue.php | 11 ++ fixtures/StandaloneParameterTypeFalse.php | 11 ++ fixtures/StandaloneParameterTypeNull.php | 11 ++ fixtures/StandaloneParameterTypeTrue.php | 11 ++ fixtures/StandaloneReturnTypeFalse.php | 11 ++ fixtures/StandaloneReturnTypeNull.php | 11 ++ fixtures/StandaloneReturnTypeTrue.php | 11 ++ fixtures/UnionArgumentTypeFalse.php | 11 ++ fixtures/UnionReturnTypeFalse.php | 11 ++ .../Generator/Node/ArgumentTypeNodeSpec.php | 71 +++++-- .../Generator/Node/ReturnTypeNodeSpec.php | 71 +++++-- .../Generator/Node/TypeNodeAbstract.php | 31 ++- tests/Doubler/Generator/ClassMirrorTest.php | 185 ++++++++++++++++++ 16 files changed, 458 insertions(+), 32 deletions(-) create mode 100644 fixtures/NullableParameterTypeFalse.php create mode 100644 fixtures/NullableParameterTypeTrue.php create mode 100644 fixtures/NullableReturnTypeFalse.php create mode 100644 fixtures/NullableReturnTypeTrue.php create mode 100644 fixtures/StandaloneParameterTypeFalse.php create mode 100644 fixtures/StandaloneParameterTypeNull.php create mode 100644 fixtures/StandaloneParameterTypeTrue.php create mode 100644 fixtures/StandaloneReturnTypeFalse.php create mode 100644 fixtures/StandaloneReturnTypeNull.php create mode 100644 fixtures/StandaloneReturnTypeTrue.php create mode 100644 fixtures/UnionArgumentTypeFalse.php create mode 100644 fixtures/UnionReturnTypeFalse.php diff --git a/fixtures/NullableParameterTypeFalse.php b/fixtures/NullableParameterTypeFalse.php new file mode 100644 index 000000000..8327f5536 --- /dev/null +++ b/fixtures/NullableParameterTypeFalse.php @@ -0,0 +1,11 @@ +getNonNullTypes()->shouldReturn(['int']); } - function it_does_not_allow_standalone_null() - { - $this->beConstructedWith('null'); - - $this->shouldThrow(DoubleException::class)->duringInstantiation(); - } - function it_does_not_allow_union_mixed() { $this->beConstructedWith('mixed', 'int'); @@ -92,21 +85,77 @@ function it_does_not_prefix_false() $this->getTypes()->shouldReturn(['false', 'array']); } - function it_does_not_allow_standalone_false() + function it_allows_standalone_false() { $this->beConstructedWith('false'); - if (PHP_VERSION_ID >=80000) { + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { $this->shouldThrow(DoubleException::class)->duringInstantiation(); } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['false']); + } } - function it_does_not_allow_nullable_false() + function it_allows_standalone_null() + { + $this->beConstructedWith('null'); + + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { + $this->shouldThrow(DoubleException::class)->duringInstantiation(); + } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['null']); + } + } + + function it_allows_standalone_true() + { + $this->beConstructedWith('true'); + + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { + $this->shouldThrow(DoubleException::class)->duringInstantiation(); + } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['true']); + } + } + + function it_allows_nullable_false() { $this->beConstructedWith('null', 'false'); - if (PHP_VERSION_ID >=80000) { + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { + $this->shouldThrow(DoubleException::class)->duringInstantiation(); + } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['null', 'false']); + } + } + + function it_allows_nullable_true() + { + $this->beConstructedWith('null', 'true'); + + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { $this->shouldThrow(DoubleException::class)->duringInstantiation(); } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['null', 'true']); + } + } + + function it_allows_union_with_false() + { + $this->beConstructedWith('false', 'Foo'); + + if (PHP_VERSION_ID >= 80000) { + $this->getTypes()->shouldReturn(['false', '\\Foo']); + } } } diff --git a/spec/Prophecy/Doubler/Generator/Node/ReturnTypeNodeSpec.php b/spec/Prophecy/Doubler/Generator/Node/ReturnTypeNodeSpec.php index 7670339db..f5d8c6b3e 100644 --- a/spec/Prophecy/Doubler/Generator/Node/ReturnTypeNodeSpec.php +++ b/spec/Prophecy/Doubler/Generator/Node/ReturnTypeNodeSpec.php @@ -82,13 +82,6 @@ function it_can_return_non_null_types() $this->getNonNullTypes()->shouldReturn(['int']); } - function it_does_not_allow_standalone_null() - { - $this->beConstructedWith('null'); - - $this->shouldThrow(DoubleException::class)->duringInstantiation(); - } - function it_does_not_allow_union_void() { $this->beConstructedWith('void', 'int'); @@ -112,22 +105,78 @@ function it_does_not_prefix_false() $this->getTypes()->shouldReturn(['false', 'array']); } - function it_does_not_allow_standalone_false() + function it_allows_standalone_false() { $this->beConstructedWith('false'); - if (PHP_VERSION_ID >=80000) { + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { + $this->shouldThrow(DoubleException::class)->duringInstantiation(); + } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['false']); + } + } + + function it_allows_standalone_null() + { + $this->beConstructedWith('null'); + + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { + $this->shouldThrow(DoubleException::class)->duringInstantiation(); + } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['null']); + } + } + + function it_allows_standalone_true() + { + $this->beConstructedWith('true'); + + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { $this->shouldThrow(DoubleException::class)->duringInstantiation(); } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['true']); + } } - function it_does_not_allow_nullable_false() + function it_allows_nullable_false() { $this->beConstructedWith('null', 'false'); - if (PHP_VERSION_ID >=80000) { + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { $this->shouldThrow(DoubleException::class)->duringInstantiation(); } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['null', 'false']); + } + } + + function it_allows_nullable_true() + { + $this->beConstructedWith('null', 'true'); + + if (PHP_VERSION_ID >=80000 && PHP_VERSION_ID < 80200) { + $this->shouldThrow(DoubleException::class)->duringInstantiation(); + } + + if (PHP_VERSION_ID >= 80200) { + $this->getTypes()->shouldReturn(['null', 'true']); + } + } + + function it_allows_union_with_false() + { + $this->beConstructedWith('false', 'Foo'); + + if (PHP_VERSION_ID >= 80000) { + $this->getTypes()->shouldReturn(['false', '\\Foo']); + } } function it_does_not_prefix_never() diff --git a/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php b/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php index 97fc54978..16013ff73 100644 --- a/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php +++ b/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php @@ -61,6 +61,7 @@ protected function getRealType(string $type): string case 'callable': case 'bool': case 'false': + case 'true': case 'float': case 'int': case 'string': @@ -78,16 +79,26 @@ protected function getRealType(string $type): string protected function guardIsValidType() { - if ($this->types == ['null' => 'null']) { - throw new DoubleException('Type cannot be standalone null'); - } - - if ($this->types == ['false' => 'false']) { - throw new DoubleException('Type cannot be standalone false'); - } - - if ($this->types == ['false' => 'false', 'null' => 'null']) { - throw new DoubleException('Type cannot be nullable false'); + if (\PHP_VERSION_ID < 80200) { + if ($this->types == ['null' => 'null']) { + throw new DoubleException('Type cannot be standalone null'); + } + + if ($this->types == ['false' => 'false']) { + throw new DoubleException('Type cannot be standalone false'); + } + + if ($this->types == ['false' => 'false', 'null' => 'null']) { + throw new DoubleException('Type cannot be nullable false'); + } + + if ($this->types == ['true' => 'true']) { + throw new DoubleException('Type cannot be standalone true'); + } + + if ($this->types == ['true' => 'true', 'null' => 'null']) { + throw new DoubleException('Type cannot be nullable true'); + } } if (\PHP_VERSION_ID >= 80000 && isset($this->types['mixed']) && count($this->types) !== 1) { diff --git a/tests/Doubler/Generator/ClassMirrorTest.php b/tests/Doubler/Generator/ClassMirrorTest.php index 03f5964ec..3f2077986 100644 --- a/tests/Doubler/Generator/ClassMirrorTest.php +++ b/tests/Doubler/Generator/ClassMirrorTest.php @@ -536,6 +536,21 @@ public function it_can_double_a_class_with_union_return_types() $this->assertSame(['\stdClass', 'bool'], $methodNode->getReturnTypeNode()->getTypes()); } + /** + * @test + */ + public function it_can_double_a_class_with_union_return_type_with_false() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Union types with false are not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\UnionReturnTypeFalse'), []); + $methodNode = $classNode->getMethods()['method']; + + $this->assertSame(['\stdClass', 'false'], $methodNode->getReturnTypeNode()->getTypes()); + } + /** @test */ public function it_can_double_a_class_with_union_argument_types() { @@ -549,6 +564,21 @@ public function it_can_double_a_class_with_union_argument_types() $this->assertEquals(new ArgumentTypeNode('bool', '\\stdClass'), $methodNode->getArguments()[0]->getTypeNode()); } + /** + * @test + */ + public function it_can_double_a_class_with_union_argument_type_with_false() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Union types with false are not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\UnionArgumentTypeFalse'), []); + $methodNode = $classNode->getMethods()['method']; + + $this->assertEquals(new ArgumentTypeNode('false', '\stdClass'), $methodNode->getArguments()[0]->getTypeNode()); + } + /** @test */ public function it_can_double_a_class_with_mixed_types() { @@ -645,4 +675,159 @@ public function it_can_not_double_intersection_argument_types() $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\IntersectionArgumentType'), []); } + + /** + * @test + */ + public function it_can_double_a_standalone_return_type_of_false() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Standalone return type of false is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\StandaloneReturnTypeFalse'), []); + $methodNode = $classNode->getMethods()['method']; + + $this->assertEquals(new ReturnTypeNode('false'), $methodNode->getReturnTypeNode()); + } + + /** + * @test + */ + public function it_can_double_a_standalone_parameter_type_of_false() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Standalone parameter type of false is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\StandaloneParameterTypeFalse'), []); + $method = $classNode->getMethod('method'); + $arguments = $method->getArguments(); + + $this->assertEquals(new ArgumentTypeNode('false'), $arguments[0]->getTypeNode()); + } + + /** + * @test + */ + public function it_can_double_a_nullable_return_type_of_false() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Nullable return type of false is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\NullableReturnTypeFalse'), []); + $methodNode = $classNode->getMethods()['method']; + + $this->assertEquals(new ReturnTypeNode('null', 'false'), $methodNode->getReturnTypeNode()); + } + + /** + * @test + */ + public function it_can_double_a_nullable_parameter_type_of_false() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Nullable parameter type of false is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\NullableParameterTypeFalse'), []); + $method = $classNode->getMethod('method'); + $arguments = $method->getArguments(); + + $this->assertEquals(new ArgumentTypeNode('null', 'false'), $arguments[0]->getTypeNode()); + } + + /** + * @test + */ + public function it_can_double_a_standalone_return_type_of_true() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Standalone return type of true is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\StandaloneReturnTypeTrue'), []); + $methodNode = $classNode->getMethods()['method']; + + $this->assertEquals(new ReturnTypeNode('true'), $methodNode->getReturnTypeNode()); + } + + /** + * @test + */ + public function it_can_double_a_standalone_parameter_type_of_true() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Standalone parameter type of true is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\StandaloneParameterTypeTrue'), []); + $method = $classNode->getMethod('method'); + $arguments = $method->getArguments(); + + $this->assertEquals(new ArgumentTypeNode('true'), $arguments[0]->getTypeNode()); + } + + /** + * @test + */ + public function it_can_double_a_nullable_return_type_of_true() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Nullable return type of true is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\NullableReturnTypeTrue'), []); + $methodNode = $classNode->getMethods()['method']; + + $this->assertEquals(new ReturnTypeNode('null', 'true'), $methodNode->getReturnTypeNode()); + } + + /** + * @test + */ + public function it_can_double_a_nullable_parameter_type_of_true() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Nullable parameter type of true is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\NullableParameterTypeTrue'), []); + $method = $classNode->getMethod('method'); + $arguments = $method->getArguments(); + + $this->assertEquals(new ArgumentTypeNode('null', 'true'), $arguments[0]->getTypeNode()); + } + + /** + * @test + */ + public function it_can_double_a_standalone_return_type_of_null() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Standalone return type of null is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\StandaloneReturnTypeNull'), []); + $methodNode = $classNode->getMethods()['method']; + + $this->assertEquals(new ReturnTypeNode('null'), $methodNode->getReturnTypeNode()); + } + + /** + * @test + */ + public function it_can_double_a_standalone_parameter_type_of_null() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Standalone parameter type of null is not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\StandaloneParameterTypeNull'), []); + $method = $classNode->getMethod('method'); + $arguments = $method->getArguments(); + + $this->assertEquals(new ArgumentTypeNode('null'), $arguments[0]->getTypeNode()); + } } From 3f007e262f8f6f010abe08aedea5d94975a99ff3 Mon Sep 17 00:00:00 2001 From: kschatzle Date: Mon, 26 Sep 2022 08:28:07 -0500 Subject: [PATCH 05/12] Support standalone and nullable types for true, false, and null. Make true type declarations version checked. --- src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php b/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php index 16013ff73..f63bb38aa 100644 --- a/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php +++ b/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php @@ -61,7 +61,6 @@ protected function getRealType(string $type): string case 'callable': case 'bool': case 'false': - case 'true': case 'float': case 'int': case 'string': @@ -71,6 +70,8 @@ protected function getRealType(string $type): string return $type; case 'mixed': return \PHP_VERSION_ID < 80000 ? $this->prefixWithNsSeparator($type) : $type; + case 'true': + return \PHP_VERSION_ID < 80200 ? $this->prefixWithNsSeparator($type) : $type; default: return $this->prefixWithNsSeparator($type); From b577c4a8a8ba8078dd01e01ca405da6c966f5fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gildas=20Qu=C3=A9m=C3=A9ner?= Date: Fri, 30 Sep 2022 22:21:41 +0200 Subject: [PATCH 06/12] Support PHP 8.2 --- .github/workflows/build.yml | 4 +--- composer.json | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d08954553..fddd89cea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,15 +15,13 @@ jobs: strategy: fail-fast: false matrix: - php: [7.2, 7.3, 7.4, 8.0] + php: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2"] composer-flags: [ "" ] experimental: [false] include: - php: 7.2 composer-flags: "--prefer-lowest" experimental: false - - php: 8.1 - experimental: true steps: - uses: actions/checkout@v2 diff --git a/composer.json b/composer.json index 129a4cf6c..551da0c6b 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ ], "require": { - "php": "^7.2 || ~8.0, <8.2", + "php": "^7.2 || 8.0.* || 8.1.* || 8.2.*", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "doctrine/instantiator": "^1.2", From e5e3759bc9a61c747ab317b115d267c21ca07be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gildas=20Qu=C3=A9m=C3=A9ner?= Date: Sat, 1 Oct 2022 13:26:51 +0200 Subject: [PATCH 07/12] Support doubling readonly class --- fixtures/ReadOnlyClass.php | 7 ++++ .../Generator/ClassCodeGeneratorSpec.php | 28 ++++++++++++++++ .../Doubler/Generator/Node/ClassNodeSpec.php | 12 +++++++ .../Doubler/Generator/ClassCodeGenerator.php | 7 ++-- .../Doubler/Generator/ClassMirror.php | 4 +++ .../Doubler/Generator/Node/ClassNode.php | 17 ++++++++++ tests/Doubler/Generator/ClassMirrorTest.php | 33 +++++++++++++++++++ 7 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 fixtures/ReadOnlyClass.php diff --git a/fixtures/ReadOnlyClass.php b/fixtures/ReadOnlyClass.php new file mode 100644 index 000000000..dce31f4a6 --- /dev/null +++ b/fixtures/ReadOnlyClass.php @@ -0,0 +1,7 @@ +getProperties()->willReturn(array('name' => 'public', 'email' => 'private')); $class->getMethods()->willReturn(array($method1, $method2, $method3, $method4, $method5)); + $class->isReadOnly()->willReturn(false); $method1->getName()->willReturn('getName'); $method1->getVisibility()->willReturn('public'); @@ -156,6 +157,7 @@ function it_generates_proper_php_code_for_variadics( $class->getMethods()->willReturn(array( $method1, $method2, $method3, $method4 )); + $class->isReadOnly()->willReturn(false); $method1->getName()->willReturn('variadic'); $method1->getVisibility()->willReturn('public'); @@ -248,6 +250,7 @@ function it_overrides_properly_methods_with_args_passed_by_reference( $class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface')); $class->getProperties()->willReturn(array()); $class->getMethods()->willReturn(array($method)); + $class->isReadOnly()->willReturn(false); $method->getName()->willReturn('getName'); $method->getVisibility()->willReturn('public'); @@ -290,6 +293,7 @@ function it_generates_proper_code_for_union_return_types $class->getInterfaces()->willReturn([]); $class->getProperties()->willReturn([]); $class->getMethods()->willReturn(array($method)); + $class->isReadOnly()->willReturn(false); $method->getName()->willReturn('foo'); $method->getVisibility()->willReturn('public'); @@ -328,6 +332,7 @@ function it_generates_proper_code_for_union_argument_types $class->getInterfaces()->willReturn([]); $class->getProperties()->willReturn([]); $class->getMethods()->willReturn(array($method)); + $class->isReadOnly()->willReturn(false); $method->getName()->willReturn('foo'); $method->getVisibility()->willReturn('public'); @@ -367,6 +372,7 @@ function it_generates_empty_class_for_empty_ClassNode(ClassNode $class) $class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface')); $class->getProperties()->willReturn(array()); $class->getMethods()->willReturn(array()); + $class->isReadOnly()->willReturn(false); $code = $this->generate('CustomClass', $class); $expected =<<<'PHP' @@ -387,6 +393,7 @@ function it_wraps_class_in_namespace_if_it_is_namespaced(ClassNode $class) $class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface')); $class->getProperties()->willReturn(array()); $class->getMethods()->willReturn(array()); + $class->isReadOnly()->willReturn(false); $code = $this->generate('My\Awesome\CustomClass', $class); $expected =<<<'PHP' @@ -394,6 +401,27 @@ function it_wraps_class_in_namespace_if_it_is_namespaced(ClassNode $class) class CustomClass extends \stdClass implements \Prophecy\Doubler\Generator\MirroredInterface { +} +} +PHP; + $expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n")); + $code->shouldBe($expected); + } + + function it_generates_read_only_class_if_parent_class_is_read_only(ClassNode $class) + { + $class->getParentClass()->willReturn('ReadOnlyClass'); + $class->getInterfaces()->willReturn(array('Prophecy\Doubler\Generator\MirroredInterface')); + $class->getProperties()->willReturn(array()); + $class->getMethods()->willReturn(array()); + $class->isReadOnly()->willReturn(true); + + $code = $this->generate('CustomClass', $class); + $expected =<<<'PHP' +namespace { +readonly class CustomClass extends \ReadOnlyClass implements \Prophecy\Doubler\Generator\MirroredInterface { + + } } PHP; diff --git a/spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php b/spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php index 16fc498b4..d1b73ea28 100644 --- a/spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php +++ b/spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php @@ -182,4 +182,16 @@ function it_throws_an_exception_when_adding_a_method_that_isnt_extendable(Method ); $this->shouldThrow($expectedException)->duringAddMethod($method); } + + function it_is_non_read_only_by_default() + { + $this->isReadOnly()->shouldReturn(false); + } + + function its_read_only_is_mutable() + { + $this->setReadOnly(true); + + $this->isReadOnly()->shouldReturn(true); + } } diff --git a/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php b/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php index 52e5e0455..932308d91 100644 --- a/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php +++ b/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php @@ -40,8 +40,11 @@ public function generate($classname, Node\ClassNode $class) $classname = array_pop($parts); $namespace = implode('\\', $parts); - $code = sprintf("class %s extends \%s implements %s {\n", - $classname, $class->getParentClass(), implode(', ', + $code = sprintf("%sclass %s extends \%s implements %s {\n", + $class->isReadOnly() ? 'readonly ': '', + $classname, + $class->getParentClass(), + implode(', ', array_map(function ($interface) {return '\\'.$interface;}, $class->getInterfaces()) ) ); diff --git a/src/Prophecy/Doubler/Generator/ClassMirror.php b/src/Prophecy/Doubler/Generator/ClassMirror.php index 5d9cd2d20..35a4f8f62 100644 --- a/src/Prophecy/Doubler/Generator/ClassMirror.php +++ b/src/Prophecy/Doubler/Generator/ClassMirror.php @@ -98,6 +98,10 @@ private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node ), $class); } + if (method_exists(ReflectionClass::class, 'isReadOnly')) { + $node->setReadOnly($class->isReadOnly()); + } + $node->setParentClass($class->getName()); foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) { diff --git a/src/Prophecy/Doubler/Generator/Node/ClassNode.php b/src/Prophecy/Doubler/Generator/Node/ClassNode.php index f7bd2857a..c62b4f593 100644 --- a/src/Prophecy/Doubler/Generator/Node/ClassNode.php +++ b/src/Prophecy/Doubler/Generator/Node/ClassNode.php @@ -25,6 +25,7 @@ class ClassNode private $interfaces = array(); private $properties = array(); private $unextendableMethods = array(); + private $readOnly = false; /** * @var MethodNode[] @@ -166,4 +167,20 @@ public function isExtendable($method) { return !in_array($method, $this->unextendableMethods); } + + /** + * @return bool + */ + public function isReadOnly() + { + return $this->readOnly; + } + + /** + * @param bool $readOnly + */ + public function setReadOnly($readOnly) + { + $this->readOnly = $readOnly; + } } diff --git a/tests/Doubler/Generator/ClassMirrorTest.php b/tests/Doubler/Generator/ClassMirrorTest.php index 18c966837..e4aad8077 100644 --- a/tests/Doubler/Generator/ClassMirrorTest.php +++ b/tests/Doubler/Generator/ClassMirrorTest.php @@ -472,6 +472,9 @@ function it_changes_argument_names_if_they_are_varying() $method = $this->prophesize('ReflectionMethod'); $parameter = $this->prophesize('ReflectionParameter'); + if (PHP_VERSION_ID >= 80200) { + $class->isReadOnly()->willReturn(false); + } $class->getName()->willReturn('Custom\ClassName'); $class->isInterface()->willReturn(false); $class->isFinal()->willReturn(false); @@ -635,4 +638,34 @@ public function it_can_not_double_intersection_argument_types() $classNode = (new ClassMirror())->reflect(new \ReflectionClass('Fixtures\Prophecy\IntersectionArgumentType'), []); } + + /** + * @test + */ + public function it_reflects_non_read_only_class() + { + $classNode = (new ClassMirror())->reflect( + new \ReflectionClass('Fixtures\Prophecy\EmptyClass'), + [] + ); + + $this->assertFalse($classNode->isReadOnly()); + } + + /** + * @test + */ + public function it_reflects_read_only_class() + { + if (PHP_VERSION_ID < 80200) { + $this->markTestSkipped('Read only classes are not supported in this PHP version'); + } + + $classNode = (new ClassMirror())->reflect( + new \ReflectionClass('Fixtures\Prophecy\ReadOnlyClass'), + [] + ); + + $this->assertTrue($classNode->isReadOnly()); + } } From 79e9b287f157592009878496dd487b0decd52db9 Mon Sep 17 00:00:00 2001 From: Ciaran McNulty Date: Tue, 29 Nov 2022 14:34:33 +0000 Subject: [PATCH 08/12] Fix standalone true handling --- src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php b/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php index f63bb38aa..16013ff73 100644 --- a/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php +++ b/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php @@ -61,6 +61,7 @@ protected function getRealType(string $type): string case 'callable': case 'bool': case 'false': + case 'true': case 'float': case 'int': case 'string': @@ -70,8 +71,6 @@ protected function getRealType(string $type): string return $type; case 'mixed': return \PHP_VERSION_ID < 80000 ? $this->prefixWithNsSeparator($type) : $type; - case 'true': - return \PHP_VERSION_ID < 80200 ? $this->prefixWithNsSeparator($type) : $type; default: return $this->prefixWithNsSeparator($type); From bf271b40adfe6b2d0e62cc50ee6e64ed7f277458 Mon Sep 17 00:00:00 2001 From: Ciaran McNulty Date: Tue, 29 Nov 2022 14:44:11 +0000 Subject: [PATCH 09/12] Force install on PHP 8.2 --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fddd89cea..d21a086ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,9 @@ jobs: - php: 7.2 composer-flags: "--prefer-lowest" experimental: false + - php: 8.2 + composer-flags: "--ignore-platform-req=php" + experimental: false steps: - uses: actions/checkout@v2 From ad2cc169ed142870a7d4e3fd8cea7c82be40fd45 Mon Sep 17 00:00:00 2001 From: Ciaran McNulty Date: Tue, 29 Nov 2022 14:46:09 +0000 Subject: [PATCH 10/12] Remove non-forced 8.2 install --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d21a086ff..60baef215 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,6 @@ jobs: experimental: false - php: 8.2 composer-flags: "--ignore-platform-req=php" - experimental: false steps: - uses: actions/checkout@v2 From 1252e66c080676d3ff331e38dc02c66914113c21 Mon Sep 17 00:00:00 2001 From: Ciaran McNulty Date: Tue, 29 Nov 2022 14:53:43 +0000 Subject: [PATCH 11/12] Actually remove non-forced 8.2 install --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60baef215..dbd4128ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - php: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2"] + php: ["7.2", "7.3", "7.4", "8.0", "8.1"] composer-flags: [ "" ] experimental: [false] include: @@ -24,6 +24,7 @@ jobs: experimental: false - php: 8.2 composer-flags: "--ignore-platform-req=php" + experimental: false steps: - uses: actions/checkout@v2 From be8cac52a0827776ff9ccda8c381ac5b71aeb359 Mon Sep 17 00:00:00 2001 From: Ciaran McNulty Date: Tue, 29 Nov 2022 15:06:56 +0000 Subject: [PATCH 12/12] Update changelog --- CHANGES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c383a0880..0f76d68cb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,14 @@ +1.16.0 / 2022/11/29 +=================== + +* [added] Allow install with PHP 8.2 [@gquemener] +* [added] Use shorter object IDs for object comparison [@TysonAndre] +* [added] Support standalone false,true and null types [@kschatzle] +* [added] Support doubling readonly classes [@gquemener] +* [fixed] Remove workarounds for unsupported HHVM [@TysonAndre] +* [fixed] Clear error message when doubling DNF types [@kschatzle] + + 1.15.0 / 2021/12/08 ===================