Skip to content
Permalink
Browse files

Reconcile string emptiness a bit better

  • Loading branch information
muglug committed Dec 13, 2019
1 parent c9b5e96 commit 6d02aa86e8c67083f676a111eced8e9a08ac26a8
@@ -145,10 +145,6 @@ public static function loadFromXMLElement(
);
}

if (!$directory_path) {
continue;
}

if ($ignore_type_stats && $filter instanceof ProjectFileFilter) {
$filter->ignore_type_stats[$directory_path] = true;
}
@@ -328,7 +328,7 @@ public function analyze(
}
}

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 @@ public static function analyzeInstance(
$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 @@ public static function analyzeInstance(

if ($var_id) {
if ($context->collect_initializations
&& $var_id
&& $lhs_var_id === '$this'
) {
$assignment_value_type->initialized_class = $context->self;
@@ -621,7 +621,7 @@ function (PhpParser\Node\Arg $arg) {
$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 @@ public static function getArrayVarId(
}
}

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\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 @@ public static function isAtomicContainedBy(
}

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 @@ public static function isAtomicContainedBy(
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 @@ public static function isAtomicContainedBy(
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 @@ public static function isAtomicContainedBy(
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 @@ private static function reconcileFalsyOrEmpty(

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 @@ private static function reconcileFalsyOrEmpty(
}
}

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 @@ private static function reconcileFalsyOrEmpty(
}
}

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 @@ private static function scrapeTypeProperties(
}
} 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 @@ private static function scrapeTypeProperties(

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 @@ function (FunctionLikeParameter $p) {
);
}

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

$storage->return_type = Type::parseTokens(
$fixed_type_tokens,
null,
$this->function_template_types + $this->class_template_types
);
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->setFromDocblock();
$storage->return_type = Type::parseTokens(
$fixed_type_tokens,
null,
$this->function_template_types + $this->class_template_types
);

if ($storage->signature_return_type) {
$all_typehint_types_match = true;
$signature_return_atomic_types = $storage->signature_return_type->getTypes();
$storage->return_type->setFromDocblock();

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 ($storage->signature_return_type) {
$all_typehint_types_match = true;
$signature_return_atomic_types = $storage->signature_return_type->getTypes();

if ($all_typehint_types_match) {
$storage->return_type->from_docblock = false;
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 ($storage->signature_return_type->isNullable()
&& !$storage->return_type->isNullable()
) {
$storage->return_type->addType(new Type\Atomic\TNull());
}
if ($all_typehint_types_match) {
$storage->return_type->from_docblock = 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 ($storage->signature_return_type->isNullable()
&& !$storage->return_type->isNullable()
) {
$storage->return_type->addType(new Type\Atomic\TNull());
}
}

$storage->has_docblock_issues = true;
$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 @@ public static function create(
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();

@@ -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 @@ function foo(?string $a, ?string $b, ?string $c): string {
],
'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";
}

0 comments on commit 6d02aa8

Please sign in to comment.
You can’t perform that action at this time.