Skip to content

Commit

Permalink
Immutable assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
danog committed Oct 3, 2022
1 parent ef60a0c commit f11ed46
Show file tree
Hide file tree
Showing 47 changed files with 212 additions and 147 deletions.
Expand Up @@ -66,6 +66,7 @@
use Psalm\Storage\Assertion\NonEmptyCountable;
use Psalm\Storage\Assertion\NotNonEmptyCountable;
use Psalm\Storage\Assertion\Truthy;
use Psalm\Storage\Possibilities;
use Psalm\Storage\PropertyStorage;
use Psalm\Type;
use Psalm\Type\Atomic;
Expand Down Expand Up @@ -803,10 +804,7 @@ public static function processFunctionCall(
}
} elseif ($class_exists_check_type = self::hasClassExistsCheck($expr)) {
if ($first_var_name) {
$class_string_type = new TClassString();
if ($class_exists_check_type === 1) {
$class_string_type->is_loaded = true;
}
$class_string_type = new TClassString('object', null, $class_exists_check_type === 1);
$if_types[$first_var_name] = [[new IsType($class_string_type)]];
}
} elseif ($class_exists_check_type = self::hasTraitExistsCheck($expr)) {
Expand All @@ -819,14 +817,12 @@ public static function processFunctionCall(
}
} elseif (self::hasEnumExistsCheck($expr)) {
if ($first_var_name) {
$class_string = new TClassString();
$class_string->is_enum = true;
$class_string = new TClassString('object', null, false, false, true);
$if_types[$first_var_name] = [[new IsType($class_string)]];
}
} elseif (self::hasInterfaceExistsCheck($expr)) {
if ($first_var_name) {
$class_string = new TClassString();
$class_string->is_interface = true;
$class_string = new TClassString('object', null, false, true, false);
$if_types[$first_var_name] = [[new IsType($class_string)]];
}
} elseif (self::hasFunctionExistsCheck($expr)) {
Expand Down Expand Up @@ -960,15 +956,15 @@ protected static function processCustomAssertion(
foreach ($if_true_assertions as $assertion) {
$if_types = [];

$assertion = clone $assertion;
$newRules = [];

foreach ($assertion->rule as $i => $rule) {
foreach ($assertion->rule as $rule) {
$rule_type = $rule->getAtomicType();

if ($rule_type instanceof TClassConstant) {
$codebase = $source->getCodebase();

$assertion->rule[$i]->setAtomicType(
$newRules[] = $rule->setAtomicType(
TypeExpander::expandAtomic(
$codebase,
$rule_type,
Expand All @@ -977,9 +973,13 @@ protected static function processCustomAssertion(
null
)[0]
);
} else {
$newRules []= $rule;
}
}

$assertion = new Possibilities($assertion->var_id, $newRules);

if (is_int($assertion->var_id) && isset($expr->getArgs()[$assertion->var_id])) {
if ($assertion->var_id === 0) {
$var_name = $first_var_name;
Expand All @@ -992,7 +992,7 @@ protected static function processCustomAssertion(
}

if ($var_name) {
$if_types[$var_name] = [[clone $assertion->rule[0]]];
$if_types[$var_name] = [[$assertion->rule[0]]];
}
} elseif ($assertion->var_id === '$this') {
if (!$expr instanceof PhpParser\Node\Expr\MethodCall) {
Expand All @@ -1012,7 +1012,7 @@ protected static function processCustomAssertion(
);

if ($var_id) {
$if_types[$var_id] = [[clone $assertion->rule[0]]];
$if_types[$var_id] = [[$assertion->rule[0]]];
}
} elseif (is_string($assertion->var_id)) {
$is_function = substr($assertion->var_id, -2) === '()';
Expand Down Expand Up @@ -1081,7 +1081,7 @@ protected static function processCustomAssertion(
);
continue;
}
$if_types[$assertion_var_id] = [[clone $assertion->rule[0]]];
$if_types[$assertion_var_id] = [[$assertion->rule[0]]];
}

if ($if_types) {
Expand All @@ -1094,15 +1094,15 @@ protected static function processCustomAssertion(
foreach ($if_false_assertions as $assertion) {
$if_types = [];

$assertion = clone $assertion;
$newRules = [];

foreach ($assertion->rule as $i => $rule) {
foreach ($assertion->rule as $rule) {
$rule_type = $rule->getAtomicType();

if ($rule_type instanceof TClassConstant) {
$codebase = $source->getCodebase();

$assertion->rule[$i]->setAtomicType(
$newRules []= $rule->setAtomicType(
TypeExpander::expandAtomic(
$codebase,
$rule_type,
Expand All @@ -1111,9 +1111,13 @@ protected static function processCustomAssertion(
null
)[0]
);
} else {
$newRules []= $rule;
}
}

$assertion = new Possibilities($assertion->var_id, $newRules);

if (is_int($assertion->var_id) && isset($expr->getArgs()[$assertion->var_id])) {
if ($assertion->var_id === 0) {
$var_name = $first_var_name;
Expand All @@ -1126,7 +1130,7 @@ protected static function processCustomAssertion(
}

if ($var_name) {
$if_types[$var_name] = [[clone $assertion->rule[0]->getNegation()]];
$if_types[$var_name] = [[$assertion->rule[0]->getNegation()]];
}
} elseif ($assertion->var_id === '$this' && $expr instanceof PhpParser\Node\Expr\MethodCall) {
$var_id = ExpressionIdentifier::getExtendedVarId(
Expand All @@ -1136,7 +1140,7 @@ protected static function processCustomAssertion(
);

if ($var_id) {
$if_types[$var_id] = [[clone $assertion->rule[0]->getNegation()]];
$if_types[$var_id] = [[$assertion->rule[0]->getNegation()]];
}
} elseif (is_string($assertion->var_id)) {
$is_function = substr($assertion->var_id, -2) === '()';
Expand Down Expand Up @@ -1188,7 +1192,7 @@ protected static function processCustomAssertion(
}
}

$rule = clone $assertion->rule[0]->getNegation();
$rule = $assertion->rule[0]->getNegation();

$assertion_var_id = str_replace($var_id, $arg_var_id, $assertion->var_id);

Expand All @@ -1198,7 +1202,7 @@ protected static function processCustomAssertion(
if (strpos($var_id, 'self::') === 0) {
$var_id = $this_class_name.'::'.substr($var_id, 6);
}
$if_types[$var_id] = [[clone $assertion->rule[0]->getNegation()]];
$if_types[$var_id] = [[$assertion->rule[0]->getNegation()]];
} else {
IssueBuffer::maybeAdd(
new InvalidDocblock(
Expand Down Expand Up @@ -1243,10 +1247,10 @@ protected static function getInstanceOfAssertions(

if ($this_class_name
&& (in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true))) {
$named_object =new TNamedObject($this_class_name);
$is_static = $stmt->class->parts[0] === 'static';
$named_object = new TNamedObject($this_class_name, $is_static);

if ($stmt->class->parts[0] === 'static') {
$named_object->is_static = true;
if ($is_static) {
return [new IsIdentical($named_object)];
}

Expand Down Expand Up @@ -3337,7 +3341,7 @@ private static function getGetclassEqualityAssertions(
new IsIdentical(new TTemplateParam(
$type_part->param_name,
$type_part->as_type
? new Union([clone $type_part->as_type])
? new Union([$type_part->as_type])
: Type::getObject(),
$type_part->defining_class
))
Expand Down Expand Up @@ -3525,8 +3529,7 @@ private static function getIsaAssertions(

if ($class_node->parts === ['static']) {
if ($this_class_name) {
$object = new TNamedObject($this_class_name);
$object->is_static = true;
$object = new TNamedObject($this_class_name, true);

$if_types[$first_var_name] = [[new IsAClass($object, $third_arg_value === 'true')]];
}
Expand Down
Expand Up @@ -770,8 +770,7 @@ public static function applyAssertionsToContext(
continue;
}

$assertion_rule = clone $assertion_rule;
$assertion_rule->setAtomicType($atomic_type);
$assertion_rule = $assertion_rule->setAtomicType($atomic_type);
$orred_rules[] = $assertion_rule;
}
} elseif (isset($context->vars_in_scope[$assertion_var_id])) {
Expand Down
15 changes: 10 additions & 5 deletions src/Psalm/Storage/Assertion.php
Expand Up @@ -4,12 +4,15 @@

use Psalm\Type\Atomic;

/**
* @psalm-immutable
*/
abstract class Assertion
{
/** @psalm-mutation-free */
use ImmutableNonCloneableTrait;

abstract public function getNegation(): Assertion;

/** @psalm-mutation-free */
abstract public function isNegationOf(self $assertion): bool;

abstract public function __toString(): string;
Expand All @@ -19,19 +22,21 @@ public function isNegation(): bool
return false;
}

/** @psalm-mutation-free */
public function hasEquality(): bool
{
return false;
}

/** @psalm-mutation-free */
public function getAtomicType(): ?Atomic
{
return null;
}

public function setAtomicType(Atomic $type): void
/**
* @return static
*/
public function setAtomicType(Atomic $type): self
{
return $this;
}
}
5 changes: 3 additions & 2 deletions src/Psalm/Storage/Assertion/Any.php
Expand Up @@ -4,9 +4,11 @@

use Psalm\Storage\Assertion;

/**
* @psalm-immutable
*/
final class Any extends Assertion
{
/** @psalm-mutation-free */
public function getNegation(): Assertion
{
return $this;
Expand All @@ -17,7 +19,6 @@ public function __toString(): string
return 'mixed';
}

/** @psalm-mutation-free */
public function isNegationOf(Assertion $assertion): bool
{
return false;
Expand Down
5 changes: 3 additions & 2 deletions src/Psalm/Storage/Assertion/ArrayKeyDoesNotExist.php
Expand Up @@ -4,9 +4,11 @@

use Psalm\Storage\Assertion;

/**
* @psalm-immutable
*/
final class ArrayKeyDoesNotExist extends Assertion
{
/** @psalm-mutation-free */
public function getNegation(): Assertion
{
return new ArrayKeyExists();
Expand All @@ -22,7 +24,6 @@ public function __toString(): string
return '!array-key-exists';
}

/** @psalm-mutation-free */
public function isNegationOf(Assertion $assertion): bool
{
return $assertion instanceof ArrayKeyExists;
Expand Down
5 changes: 3 additions & 2 deletions src/Psalm/Storage/Assertion/ArrayKeyExists.php
Expand Up @@ -4,9 +4,11 @@

use Psalm\Storage\Assertion;

/**
* @psalm-immutable
*/
final class ArrayKeyExists extends Assertion
{
/** @psalm-mutation-free */
public function getNegation(): Assertion
{
return new ArrayKeyDoesNotExist();
Expand All @@ -17,7 +19,6 @@ public function __toString(): string
return 'array-key-exists';
}

/** @psalm-mutation-free */
public function isNegationOf(Assertion $assertion): bool
{
return $assertion instanceof ArrayKeyDoesNotExist;
Expand Down
6 changes: 3 additions & 3 deletions src/Psalm/Storage/Assertion/DoesNotHaveAtLeastCount.php
Expand Up @@ -4,6 +4,9 @@

use Psalm\Storage\Assertion;

/**
* @psalm-immutable
*/
final class DoesNotHaveAtLeastCount extends Assertion
{
/** @var positive-int */
Expand All @@ -15,13 +18,11 @@ public function __construct(int $count)
$this->count = $count;
}

/** @psalm-mutation-free */
public function getNegation(): Assertion
{
return new HasAtLeastCount($this->count);
}

/** @psalm-mutation-free */
public function isNegation(): bool
{
return true;
Expand All @@ -32,7 +33,6 @@ public function __toString(): string
return '!has-at-least-' . $this->count;
}

/** @psalm-mutation-free */
public function isNegationOf(Assertion $assertion): bool
{
return $assertion instanceof HasAtLeastCount && $this->count === $assertion->count;
Expand Down
5 changes: 3 additions & 2 deletions src/Psalm/Storage/Assertion/DoesNotHaveExactCount.php
Expand Up @@ -4,6 +4,9 @@

use Psalm\Storage\Assertion;

/**
* @psalm-immutable
*/
final class DoesNotHaveExactCount extends Assertion
{
/** @var positive-int */
Expand All @@ -20,7 +23,6 @@ public function isNegation(): bool
return true;
}

/** @psalm-mutation-free */
public function getNegation(): Assertion
{
return new HasExactCount($this->count);
Expand All @@ -31,7 +33,6 @@ public function __toString(): string
return '!has-exact-count-' . $this->count;
}

/** @psalm-mutation-free */
public function isNegationOf(Assertion $assertion): bool
{
return $assertion instanceof HasExactCount && $assertion->count === $this->count;
Expand Down
5 changes: 3 additions & 2 deletions src/Psalm/Storage/Assertion/DoesNotHaveMethod.php
Expand Up @@ -4,6 +4,9 @@

use Psalm\Storage\Assertion;

/**
* @psalm-immutable
*/
final class DoesNotHaveMethod extends Assertion
{
public string $method;
Expand All @@ -18,7 +21,6 @@ public function isNegation(): bool
return true;
}

/** @psalm-mutation-free */
public function getNegation(): Assertion
{
return new HasMethod($this->method);
Expand All @@ -29,7 +31,6 @@ public function __toString(): string
return '!method-exists-' . $this->method;
}

/** @psalm-mutation-free */
public function isNegationOf(Assertion $assertion): bool
{
return $assertion instanceof HasMethod && $assertion->method === $this->method;
Expand Down

0 comments on commit f11ed46

Please sign in to comment.