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.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit f11ed46

Please sign in to comment.