1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Allow opt-in to strict return type checking

This commit is contained in:
Matt Brown 2020-11-05 18:20:04 -05:00 committed by Daniil Gentili
parent 3483c59d9b
commit 1389dc6adf
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
4 changed files with 59 additions and 48 deletions

View File

@ -76,6 +76,7 @@
<xs:attribute name="allowInternalNamedArgumentCalls" type="xs:boolean" default="true" /> <xs:attribute name="allowInternalNamedArgumentCalls" type="xs:boolean" default="true" />
<xs:attribute name="allowNamedArgumentCalls" type="xs:boolean" default="true" /> <xs:attribute name="allowNamedArgumentCalls" type="xs:boolean" default="true" />
<xs:attribute name="reportInfo" type="xs:boolean" default="true" /> <xs:attribute name="reportInfo" type="xs:boolean" default="true" />
<xs:attribute name="restrictReturnTypes" type="xs:boolean" default="false" />
</xs:complexType> </xs:complexType>
<xs:complexType name="ProjectFilesType"> <xs:complexType name="ProjectFilesType">

View File

@ -433,6 +433,11 @@ class Config
*/ */
public $resolve_from_config_file = true; public $resolve_from_config_file = true;
/**
* @var bool
*/
public $restrict_return_types = false;
/** /**
* @var string[] * @var string[]
*/ */
@ -835,6 +840,7 @@ class Config
'allowNamedArgumentCalls' => 'allow_named_arg_calls', 'allowNamedArgumentCalls' => 'allow_named_arg_calls',
'findUnusedPsalmSuppress' => 'find_unused_psalm_suppress', 'findUnusedPsalmSuppress' => 'find_unused_psalm_suppress',
'reportInfo' => 'report_info', 'reportInfo' => 'report_info',
'restrictReturnTypes' => 'restrict_return_types',
]; ];
foreach ($booleanAttributes as $xmlName => $internalName) { foreach ($booleanAttributes as $xmlName => $internalName) {

View File

@ -525,63 +525,68 @@ class ReturnTypeAnalyzer
return false; return false;
} }
} }
} elseif ($codebase->alter_code } elseif (!$inferred_return_type->hasMixed()
&& isset($project_analyzer->getIssuesToFix()['LessSpecificReturnType']) && !UnionTypeComparator::isContainedBy(
&& !in_array('LessSpecificReturnType', $suppressed_issues)
&& !($function_like_storage instanceof MethodStorage && $function_like_storage->inheritdoc)
) {
if (!UnionTypeComparator::isContainedBy(
$codebase, $codebase,
$declared_return_type, $declared_return_type,
$inferred_return_type, $inferred_return_type,
false, false,
false false
)) { )
self::addOrUpdateReturnType(
$function,
$project_analyzer,
$inferred_return_type,
$source,
$compatible_method_ids
|| (($project_analyzer->only_replace_php_types_with_non_docblock_types
|| $unsafe_return_type)
&& $inferred_return_type->from_docblock),
$function_like_storage
);
return null;
}
} elseif (!$inferred_return_type->hasMixed()
&& ((!$inferred_return_type->isNullable() && $declared_return_type->isNullable())
|| (!$inferred_return_type->isFalsable() && $declared_return_type->isFalsable()))
) { ) {
if ($function instanceof Function_ if ($codebase->alter_code) {
|| $function instanceof Closure if (isset($project_analyzer->getIssuesToFix()['LessSpecificReturnType'])
|| $function instanceof ArrowFunction && !in_array('LessSpecificReturnType', $suppressed_issues)
|| $function->isPrivate() && !($function_like_storage instanceof MethodStorage && $function_like_storage->inheritdoc)
) { ) {
$check_for_less_specific_type = true; self::addOrUpdateReturnType(
} elseif ($source instanceof StatementsAnalyzer) { $function,
if ($function_like_storage instanceof MethodStorage) { $project_analyzer,
$check_for_less_specific_type = !$function_like_storage->overridden_somewhere; $inferred_return_type,
$source,
$compatible_method_ids
|| (($project_analyzer->only_replace_php_types_with_non_docblock_types
|| $unsafe_return_type)
&& $inferred_return_type->from_docblock),
$function_like_storage
);
}
} else {
if ($function instanceof Function_
|| $function instanceof Closure
|| $function instanceof ArrowFunction
|| $function->isPrivate()
) {
$check_for_less_specific_type = true;
} elseif ($source instanceof StatementsAnalyzer) {
if ($function_like_storage instanceof MethodStorage) {
$check_for_less_specific_type = !$function_like_storage->overridden_somewhere;
} else {
$check_for_less_specific_type = false;
}
} else { } else {
$check_for_less_specific_type = false; $check_for_less_specific_type = false;
} }
} else {
$check_for_less_specific_type = false;
}
if ($check_for_less_specific_type) { if ($check_for_less_specific_type
if (IssueBuffer::accepts( && (\Psalm\Config::getInstance()->restrict_return_types
new LessSpecificReturnType( || (!$inferred_return_type->isNullable() && $declared_return_type->isNullable())
'The inferred return type \'' . $inferred_return_type . '\' for ' . $cased_method_id . || (!$inferred_return_type->isFalsable() && $declared_return_type->isFalsable()))
' is more specific than the declared return type \'' . $declared_return_type . '\'', ) {
$return_type_location if (IssueBuffer::accepts(
), new LessSpecificReturnType(
$suppressed_issues, 'The inferred return type \''
!($function_like_storage instanceof MethodStorage && $function_like_storage->inheritdoc) . $inferred_return_type->getId()
)) { . '\' for ' . $cased_method_id
return false; . ' is more specific than the declared return type \''
. $declared_return_type->getId() . '\'',
$return_type_location
),
$suppressed_issues,
!($function_like_storage instanceof MethodStorage && $function_like_storage->inheritdoc)
)) {
return false;
}
} }
} }
} }

View File

@ -597,7 +597,6 @@ class ConditionalReturnTypeTest extends TestCase
* @template T of mixed|false|null * @template T of mixed|false|null
* @param T $i * @param T $i
* @return (T is false ? no-return : T is null ? no-return : T) * @return (T is false ? no-return : T is null ? no-return : T)
* @psalm-suppress LessSpecificReturnType
*/ */
function orThrow($i) { function orThrow($i) {
if ($i === false || $i === null) { if ($i === false || $i === null) {