1
0
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:
Brown 2019-12-13 15:51:43 -05:00
parent c9b5e96b0f
commit 6d02aa86e8
14 changed files with 121 additions and 62 deletions

View File

@ -145,10 +145,6 @@ class FileFilter
);
}
if (!$directory_path) {
continue;
}
if ($ignore_type_stats && $filter instanceof ProjectFileFilter) {
$filter->ignore_type_stats[$directory_path] = true;
}

View File

@ -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,

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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'));
}
}
}
}

View File

@ -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');

View File

@ -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();
}
}

View File

@ -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) {

View File

@ -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,

View File

@ -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();

View 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';
}
}

View File

@ -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";
}