diff --git a/src/Psalm/Codebase.php b/src/Psalm/Codebase.php index 8322b53ba..b6f3eaa4a 100644 --- a/src/Psalm/Codebase.php +++ b/src/Psalm/Codebase.php @@ -175,6 +175,11 @@ class Codebase */ public $diff_methods = false; + /** + * @var bool + */ + public $allow_backwards_incompatible_changes = true; + /** * @var int */ diff --git a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php index e1502e0e9..17e92a497 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php @@ -721,14 +721,31 @@ class ReturnTypeAnalyzer $function_like_analyzer->getMethodId(), $function ); + + $codebase = $project_analyzer->getCodebase(); + $is_final = true; + $fqcln = $source->getFQCLN(); + if ($fqcln !== null) { + $class_storage = $codebase->classlike_storage_provider->get($fqcln); + assert($function instanceof ClassMethod); + $is_final = $function->isFinal() || $class_storage->final; + } + + $allow_native_type = !$docblock_only + && $codebase->php_major_version >= 7 + && ( + $codebase->allow_backwards_incompatible_changes + || $is_final + ); + $manipulator->setReturnType( - !$docblock_only && $project_analyzer->getCodebase()->php_major_version >= 7 + $allow_native_type ? $inferred_return_type->toPhpString( $source->getNamespace(), $source->getAliasedClassesFlipped(), $source->getFQCLN(), - $project_analyzer->getCodebase()->php_major_version, - $project_analyzer->getCodebase()->php_minor_version + $codebase->php_major_version, + $codebase->php_minor_version ) : null, $inferred_return_type->toNamespacedString( $source->getNamespace(), diff --git a/src/psalter.php b/src/psalter.php index 7dc253a48..542c4e50d 100644 --- a/src/psalter.php +++ b/src/psalter.php @@ -21,6 +21,7 @@ $valid_long_options = [ 'help', 'debug', 'debug-by-line', 'config:', 'file:', 'root:', 'plugin:', 'issues:', 'php-version:', 'dry-run', 'safe-types', 'find-unused-code', 'threads:', 'codeowner:', + 'allow-backwards-incompatible-changes:', ]; // get options from command line @@ -117,6 +118,9 @@ Options: --codeowner=[codeowner] You can specify a GitHub code ownership group, and only that owner's code will be updated. + + --allow-backwards-incompatible-changes=BOOL + Allow Psalm modify method signatures that could break code outside the project. Defaults to true. HELP; exit; @@ -302,6 +306,20 @@ if (isset($options['codeowner'])) { } } +if (isset($options['allow-backwards-incompatible-changes'])) { + $allow_backwards_incompatible_changes = filter_var( + $options['allow-backwards-incompatible-changes'], + FILTER_VALIDATE_BOOLEAN, + ['flags' => FILTER_NULL_ON_FAILURE] + ); + + if ($allow_backwards_incompatible_changes === null) { + die('--allow-backwards-incompatible-changes expectes a boolean value [true|false|1|0]' . PHP_EOL); + } + + $project_analyzer->getCodebase()->allow_backwards_incompatible_changes = $allow_backwards_incompatible_changes; +} + $plugins = []; if (isset($options['plugin'])) { diff --git a/tests/FileManipulation/FileManipulationTest.php b/tests/FileManipulation/FileManipulationTest.php index 61c59c359..b5b53bb71 100644 --- a/tests/FileManipulation/FileManipulationTest.php +++ b/tests/FileManipulation/FileManipulationTest.php @@ -30,10 +30,11 @@ abstract class FileManipulationTest extends \Psalm\Tests\TestCase * @param string $php_version * @param string[] $issues_to_fix * @param bool $safe_types + * @param bool $allow_backwards_incompatible_changes * * @return void */ - public function testValidCode($input_code, $output_code, $php_version, array $issues_to_fix, $safe_types) + public function testValidCode($input_code, $output_code, $php_version, array $issues_to_fix, $safe_types, bool $allow_backwards_incompatible_changes = true) { $test_name = $this->getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { @@ -77,6 +78,7 @@ abstract class FileManipulationTest extends \Psalm\Tests\TestCase false, $safe_types ); + $this->project_analyzer->getCodebase()->allow_backwards_incompatible_changes = $allow_backwards_incompatible_changes; $this->project_analyzer->getCodebase()->reportUnusedCode(); diff --git a/tests/FileManipulation/ReturnTypeManipulationTest.php b/tests/FileManipulation/ReturnTypeManipulationTest.php index d88e051bf..c894e459e 100644 --- a/tests/FileManipulation/ReturnTypeManipulationTest.php +++ b/tests/FileManipulation/ReturnTypeManipulationTest.php @@ -1132,6 +1132,82 @@ class ReturnTypeManipulationTest extends FileManipulationTest ['InvalidReturnType'], false, ], + 'fixInvalidIntReturnTypeJustInPhpDocWhenDisallowingBackwardsIncompatibleChanges' => [ + ' [ + ' [ + ' [ + '