mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Reconcile string emptiness a bit better
This commit is contained in:
parent
c9b5e96b0f
commit
6d02aa86e8
@ -145,10 +145,6 @@ class FileFilter
|
||||
);
|
||||
}
|
||||
|
||||
if (!$directory_path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($ignore_type_stats && $filter instanceof ProjectFileFilter) {
|
||||
$filter->ignore_type_stats[$directory_path] = true;
|
||||
}
|
||||
|
@ -328,7 +328,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if ($codebase->store_node_types && $parent_fq_class_name) {
|
||||
if ($codebase->store_node_types) {
|
||||
$codebase->analyzer->addNodeReference(
|
||||
$this->getFilePath(),
|
||||
$class->extends,
|
||||
|
@ -95,7 +95,7 @@ class PropertyAssignmentAnalyzer
|
||||
$context
|
||||
);
|
||||
|
||||
if ($class_property_type && $context->self) {
|
||||
if ($class_property_type) {
|
||||
$class_storage = $codebase->classlike_storage_provider->get($context->self);
|
||||
|
||||
$class_property_type = ExpressionAnalyzer::fleshOutType(
|
||||
@ -755,7 +755,6 @@ class PropertyAssignmentAnalyzer
|
||||
|
||||
if ($var_id) {
|
||||
if ($context->collect_initializations
|
||||
&& $var_id
|
||||
&& $lhs_var_id === '$this'
|
||||
) {
|
||||
$assignment_value_type->initialized_class = $context->self;
|
||||
|
@ -621,7 +621,7 @@ class StaticCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
||||
$context->include_location = $old_context_include_location;
|
||||
$context->self = $old_self;
|
||||
|
||||
if (isset($context->vars_in_scope['$this']) && $old_self) {
|
||||
if (isset($context->vars_in_scope['$this'])) {
|
||||
$context->vars_in_scope['$this'] = Type::parseString($old_self);
|
||||
}
|
||||
}
|
||||
|
@ -1055,7 +1055,7 @@ class ExpressionAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
return $root_var_id && $offset !== null ? $root_var_id . '[' . $offset . ']' : null;
|
||||
return $offset !== null ? $root_var_id . '[' . $offset . ']' : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNever;
|
||||
use Psalm\Type\Atomic\TNonEmptyArray;
|
||||
use Psalm\Type\Atomic\TNonEmptyList;
|
||||
use Psalm\Type\Atomic\TNonEmptyString;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
@ -1153,6 +1154,7 @@ class TypeAnalyzer
|
||||
}
|
||||
|
||||
if ((get_class($container_type_part) === TString::class
|
||||
|| get_class($container_type_part) === TNonEmptyString::class
|
||||
|| get_class($container_type_part) === TSingleLetter::class)
|
||||
&& $input_type_part instanceof TLiteralString
|
||||
) {
|
||||
@ -1177,7 +1179,9 @@ class TypeAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((get_class($input_type_part) === TString::class || get_class($input_type_part) === TSingleLetter::class)
|
||||
if ((get_class($input_type_part) === TString::class
|
||||
|| get_class($input_type_part) === TSingleLetter::class
|
||||
|| get_class($input_type_part) === TNonEmptyString::class)
|
||||
&& $container_type_part instanceof TLiteralString
|
||||
) {
|
||||
if ($atomic_comparison_result) {
|
||||
@ -1248,7 +1252,10 @@ class TypeAnalyzer
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($container_type_part instanceof TTraitString && get_class($input_type_part) === TString::class) {
|
||||
if ($container_type_part instanceof TTraitString
|
||||
&& (get_class($input_type_part) === TString::class
|
||||
|| get_class($input_type_part) === TNonEmptyString::class)
|
||||
) {
|
||||
if ($atomic_comparison_result) {
|
||||
$atomic_comparison_result->type_coerced = true;
|
||||
}
|
||||
@ -1259,14 +1266,16 @@ class TypeAnalyzer
|
||||
if (($input_type_part instanceof TClassString
|
||||
|| $input_type_part instanceof TLiteralClassString)
|
||||
&& (get_class($container_type_part) === TString::class
|
||||
|| get_class($container_type_part) === TSingleLetter::class)
|
||||
|| get_class($container_type_part) === TSingleLetter::class
|
||||
|| get_class($container_type_part) === TNonEmptyString::class)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($input_type_part instanceof TCallableString
|
||||
&& (get_class($container_type_part) === TString::class
|
||||
|| get_class($container_type_part) === TSingleLetter::class)
|
||||
|| get_class($container_type_part) === TSingleLetter::class
|
||||
|| get_class($container_type_part) === TNonEmptyString::class)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
@ -2125,8 +2125,11 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
|
||||
|
||||
if ($existing_var_type->hasType('string')) {
|
||||
$existing_var_type->removeType('string');
|
||||
$existing_var_type->addType(new Type\Atomic\TLiteralString(''));
|
||||
$existing_var_type->addType(new Type\Atomic\TLiteralString('0'));
|
||||
|
||||
if (!$existing_var_atomic_types['string'] instanceof Type\Atomic\TNonEmptyString) {
|
||||
$existing_var_type->addType(new Type\Atomic\TLiteralString(''));
|
||||
$existing_var_type->addType(new Type\Atomic\TLiteralString('0'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -587,6 +587,12 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($existing_var_atomic_types['string'])) {
|
||||
$existing_var_type->removeType('string');
|
||||
|
||||
$existing_var_type->addType(new Type\Atomic\TNonEmptyString);
|
||||
}
|
||||
|
||||
self::removeFalsyNegatedLiteralTypes(
|
||||
$existing_var_type,
|
||||
$did_remove_type
|
||||
@ -685,6 +691,34 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($existing_var_atomic_types['string'])) {
|
||||
if (!$existing_var_atomic_types['string'] instanceof Type\Atomic\TNonEmptyString) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type->removeType('string');
|
||||
|
||||
$existing_var_type->addType(new Type\Atomic\TNonEmptyString);
|
||||
} elseif ($existing_var_type->isSingle() && !$is_equality) {
|
||||
if ($code_location
|
||||
&& $key
|
||||
&& IssueBuffer::accepts(
|
||||
new RedundantCondition(
|
||||
'Found a redundant condition when evaluating ' . $key
|
||||
. ' of type ' . $existing_var_type->getId()
|
||||
. ' and trying to reconcile it with a non-' . $assertion . ' assertion',
|
||||
$code_location
|
||||
),
|
||||
$suppressed_issues
|
||||
)
|
||||
) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
if ($existing_var_type->isSingle()) {
|
||||
return $existing_var_type;
|
||||
}
|
||||
}
|
||||
|
||||
if ($existing_var_type->hasType('null')) {
|
||||
$did_remove_type = true;
|
||||
$existing_var_type->removeType('null');
|
||||
|
@ -1028,7 +1028,7 @@ class TypeCombination
|
||||
}
|
||||
} elseif (get_class($combination->value_types['string']) !== TString::class) {
|
||||
if (get_class($type) === TString::class) {
|
||||
$combination->value_types[$type_key] = $type;
|
||||
$combination->value_types['string'] = $type;
|
||||
} elseif ($combination->value_types['string'] instanceof TTraitString
|
||||
&& $type instanceof TClassString
|
||||
) {
|
||||
@ -1037,7 +1037,7 @@ class TypeCombination
|
||||
|
||||
unset($combination->value_types['string']);
|
||||
} elseif (get_class($combination->value_types['string']) !== get_class($type)) {
|
||||
$combination->value_types[$type_key] = new TString();
|
||||
$combination->value_types['string'] = new TString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2451,59 +2451,57 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
);
|
||||
}
|
||||
|
||||
if ($docblock_return_type) {
|
||||
try {
|
||||
$fixed_type_tokens = Type::fixUpLocalType(
|
||||
$docblock_return_type,
|
||||
$this->aliases,
|
||||
$this->function_template_types + $this->class_template_types,
|
||||
$this->type_aliases,
|
||||
$class_storage && !$class_storage->is_trait ? $class_storage->name : null
|
||||
);
|
||||
try {
|
||||
$fixed_type_tokens = Type::fixUpLocalType(
|
||||
$docblock_return_type,
|
||||
$this->aliases,
|
||||
$this->function_template_types + $this->class_template_types,
|
||||
$this->type_aliases,
|
||||
$class_storage && !$class_storage->is_trait ? $class_storage->name : null
|
||||
);
|
||||
|
||||
$storage->return_type = Type::parseTokens(
|
||||
$fixed_type_tokens,
|
||||
null,
|
||||
$this->function_template_types + $this->class_template_types
|
||||
);
|
||||
$storage->return_type = Type::parseTokens(
|
||||
$fixed_type_tokens,
|
||||
null,
|
||||
$this->function_template_types + $this->class_template_types
|
||||
);
|
||||
|
||||
$storage->return_type->setFromDocblock();
|
||||
$storage->return_type->setFromDocblock();
|
||||
|
||||
if ($storage->signature_return_type) {
|
||||
$all_typehint_types_match = true;
|
||||
$signature_return_atomic_types = $storage->signature_return_type->getTypes();
|
||||
if ($storage->signature_return_type) {
|
||||
$all_typehint_types_match = true;
|
||||
$signature_return_atomic_types = $storage->signature_return_type->getTypes();
|
||||
|
||||
foreach ($storage->return_type->getTypes() as $key => $type) {
|
||||
if (isset($signature_return_atomic_types[$key])) {
|
||||
$type->from_docblock = false;
|
||||
} else {
|
||||
$all_typehint_types_match = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($all_typehint_types_match) {
|
||||
$storage->return_type->from_docblock = false;
|
||||
}
|
||||
|
||||
if ($storage->signature_return_type->isNullable()
|
||||
&& !$storage->return_type->isNullable()
|
||||
) {
|
||||
$storage->return_type->addType(new Type\Atomic\TNull());
|
||||
foreach ($storage->return_type->getTypes() as $key => $type) {
|
||||
if (isset($signature_return_atomic_types[$key])) {
|
||||
$type->from_docblock = false;
|
||||
} else {
|
||||
$all_typehint_types_match = false;
|
||||
}
|
||||
}
|
||||
|
||||
$storage->return_type->queueClassLikesForScanning($this->codebase, $this->file_storage);
|
||||
} catch (TypeParseTreeException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
$e->getMessage() . ' in docblock for ' . $cased_function_id,
|
||||
new CodeLocation($this->file_scanner, $stmt, null, true)
|
||||
)
|
||||
)) {
|
||||
if ($all_typehint_types_match) {
|
||||
$storage->return_type->from_docblock = false;
|
||||
}
|
||||
|
||||
$storage->has_docblock_issues = true;
|
||||
if ($storage->signature_return_type->isNullable()
|
||||
&& !$storage->return_type->isNullable()
|
||||
) {
|
||||
$storage->return_type->addType(new Type\Atomic\TNull());
|
||||
}
|
||||
}
|
||||
|
||||
$storage->return_type->queueClassLikesForScanning($this->codebase, $this->file_storage);
|
||||
} catch (TypeParseTreeException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
$e->getMessage() . ' in docblock for ' . $cased_function_id,
|
||||
new CodeLocation($this->file_scanner, $stmt, null, true)
|
||||
)
|
||||
)) {
|
||||
}
|
||||
|
||||
$storage->has_docblock_issues = true;
|
||||
}
|
||||
|
||||
if ($storage->return_type && $docblock_info->ignore_nullable_return) {
|
||||
|
@ -80,6 +80,7 @@ abstract class Type
|
||||
'callable' => true,
|
||||
'array' => true,
|
||||
'non-empty-array' => true,
|
||||
'non-empty-string' => true,
|
||||
'iterable' => true,
|
||||
'null' => true,
|
||||
'mixed' => true,
|
||||
|
@ -173,6 +173,9 @@ abstract class Atomic
|
||||
case 'non-empty-list':
|
||||
return new TNonEmptyList(Type::getMixed());
|
||||
|
||||
case 'non-empty-string':
|
||||
return new Type\Atomic\TNonEmptyString();
|
||||
|
||||
case 'resource':
|
||||
return $php_version !== null ? new TNamedObject($value) : new TResource();
|
||||
|
||||
|
16
src/Psalm/Type/Atomic/TNonEmptyString.php
Normal file
16
src/Psalm/Type/Atomic/TNonEmptyString.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
/**
|
||||
* Represents a non-empty array
|
||||
*/
|
||||
class TNonEmptyString extends TString
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'non-empty-string';
|
||||
}
|
||||
}
|
@ -182,11 +182,11 @@ class TypeAlgebraTest extends \Psalm\Tests\TestCase
|
||||
],
|
||||
'twoVarLogicNotNestedWithElseifCorrectlyNegatedInElseIf' => [
|
||||
'<?php
|
||||
function foo(?string $a, ?string $b): string {
|
||||
function foo(string $a, string $b): string {
|
||||
if ($a) {
|
||||
// do nothing here
|
||||
} elseif ($b) {
|
||||
$a = null;
|
||||
$a = "";
|
||||
} else {
|
||||
return "bad";
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user