diff --git a/src/DetectChanges/BCBreak/PropertyBased/PropertyDefaultValueChanged.php b/src/DetectChanges/BCBreak/PropertyBased/PropertyDefaultValueChanged.php index 670c1f36..61851631 100644 --- a/src/DetectChanges/BCBreak/PropertyBased/PropertyDefaultValueChanged.php +++ b/src/DetectChanges/BCBreak/PropertyBased/PropertyDefaultValueChanged.php @@ -4,10 +4,14 @@ namespace Roave\BackwardCompatibility\DetectChanges\BCBreak\PropertyBased; +use PhpParser\Node\Expr; +use PhpParser\PrettyPrinter\Standard; +use PhpParser\PrettyPrinterAbstract; use Psl\Str; use Roave\BackwardCompatibility\Change; use Roave\BackwardCompatibility\Changes; use Roave\BackwardCompatibility\Formatter\ReflectionPropertyName; +use Roave\BetterReflection\NodeCompiler\Exception\UnableToCompileNode; use Roave\BetterReflection\Reflection\ReflectionProperty; use function var_export; @@ -15,16 +19,33 @@ final class PropertyDefaultValueChanged implements PropertyBased { private ReflectionPropertyName $formatProperty; + private PrettyPrinterAbstract $prettyPrinter; public function __construct() { $this->formatProperty = new ReflectionPropertyName(); + $this->prettyPrinter = new Standard(); } public function __invoke(ReflectionProperty $fromProperty, ReflectionProperty $toProperty): Changes { - $fromPropertyDefaultValue = $fromProperty->getDefaultValue(); - $toPropertyDefaultValue = $toProperty->getDefaultValue(); + try { + $fromPropertyDefaultValue = $fromProperty->getDefaultValue(); + $toPropertyDefaultValue = $toProperty->getDefaultValue(); + } catch (UnableToCompileNode $unableToCompileNode) { + $fromPropertyDefaultExpression = $fromProperty->getDefaultValueExpression(); + $toPropertyDefaultExpression = $toProperty->getDefaultValueExpression(); + + if ( + $toPropertyDefaultExpression instanceof Expr && + $fromPropertyDefaultExpression instanceof Expr && + $this->prettyPrinter->prettyPrintExpr($toPropertyDefaultExpression) === $this->prettyPrinter->prettyPrintExpr($fromPropertyDefaultExpression) + ) { + return Changes::empty(); + } + + throw $unableToCompileNode; + } if ($fromPropertyDefaultValue === $toPropertyDefaultValue) { return Changes::empty(); diff --git a/test/unit/DetectChanges/BCBreak/PropertyBased/PropertyDefaultValueChangedTest.php b/test/unit/DetectChanges/BCBreak/PropertyBased/PropertyDefaultValueChangedTest.php index 36387c19..725424db 100644 --- a/test/unit/DetectChanges/BCBreak/PropertyBased/PropertyDefaultValueChangedTest.php +++ b/test/unit/DetectChanges/BCBreak/PropertyBased/PropertyDefaultValueChangedTest.php @@ -10,6 +10,7 @@ use Roave\BackwardCompatibility\Change; use Roave\BackwardCompatibility\DetectChanges\BCBreak\PropertyBased\PropertyDefaultValueChanged; use Roave\BetterReflection\BetterReflection; +use Roave\BetterReflection\NodeCompiler\Exception\UnableToCompileNode; use Roave\BetterReflection\Reflection\ReflectionProperty; use Roave\BetterReflection\Reflector\DefaultReflector; use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator; @@ -40,6 +41,93 @@ public function testDiffs( ); } + /** @param non-empty-string $property */ + #[DataProvider('propertiesWithUncompilableDefaultValueNotChanged')] + public function testUncompilableDefaultValueNotChanged(string $property): void + { + $source = <<<'PHP' +astLocator(); + $fromClass = (new DefaultReflector(new StringSourceLocator($source, $astLocator)))->reflectClass('TheClass'); + $toClass = (new DefaultReflector(new StringSourceLocator($source, $astLocator)))->reflectClass('TheClass'); + + $changes = (new PropertyDefaultValueChanged())( + TypeRestriction::object($fromClass->getProperty($property)), + TypeRestriction::object($toClass->getProperty($property)), + ); + + self::assertCount(0, $changes); + } + + /** @return array */ + public static function propertiesWithUncompilableDefaultValueNotChanged(): array + { + return [ + 'enum' => ['enum'], + 'promotedEnum' => ['promotedEnum'], + 'promotedNewInitializer' => ['promotedNewInitializer'], + ]; + } + + public function testUncompilableDefaultValueChangedCausesUnableToCompileNodeException(): void + { + $fromSource = <<<'PHP' +astLocator(); + $fromClass = (new DefaultReflector(new StringSourceLocator($fromSource, $astLocator)))->reflectClass('TheClass'); + $toClass = (new DefaultReflector(new StringSourceLocator($toSource, $astLocator)))->reflectClass('TheClass'); + + $this->expectException(UnableToCompileNode::class); + + (new PropertyDefaultValueChanged())( + TypeRestriction::object($fromClass->getProperty('promotedEnum')), + TypeRestriction::object($toClass->getProperty('promotedEnum')), + ); + } + /** * @return array>> * @psalm-return array}>