Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor $conditionalExpressions #1270

Merged
merged 2 commits into from May 2, 2022
Merged

Conversation

rajyan
Copy link
Contributor

@rajyan rajyan commented May 2, 2022

Working on phpstan/phpstan#3677 and half way done.

  • 218bd36
    If my understanding is correct, the logic in merge of conditionalExpressions in filterBySpecifiedTypes should be consistent with
    if ($mergedVariableHolders[$name]->getType()->equals($holder->getType())) {
    continue;
    }

    so continue 2 should be continue. I'm not sure enough with the fix yet, but the fixed test looks better to me.
    Also, this example (coalesce -> coalesce, coalesce -> ternary) is fixed with this change.
		if ($first || $second) {
			echo ($first ?? $second)->getValue();
			echo ($first ?? $second)->getValue();
		}
		if ($first || $second) {
			echo ($first ?? $second)->getValue();
			echo ($first ?: $second)->getValue();
		}

these cases are not fixed yet. (ternary -> ternary, ternary -> coalesce)

		if ($first || $second) {
			echo ($first ?: $second)->getValue();
			echo ($first ?? $second)->getValue();
		}
		if ($first || $second) {
			echo ($first ?: $second)->getValue();
			echo ($first ?: $second)->getValue();
		}

While investigating this issue, I noticed some inconsistencies in keys of $conditionalExpressions.
I fixed to set key from $holder->getKey() like in

$holder = new ConditionalExpressionHolder($filteredConditions, $conditionalExpression->getTypeHolder());
$newConditionalExpressions[$variableExprString][$holder->getKey()] = $holder;
, because it is used here
foreach (array_keys($holders) as $key) {
if (!array_key_exists($key, $otherHolders)) {
continue 2;
}
}

'Property PropertiesFromArrayIntoObject\Foo::$float_test (float) does not accept float|int|string.',
69,
],
[
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.',
69,
],
[
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept float|int|string.',
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes sense to me. The error must be same with line 54.

public function create_simple_1(): self {
$self = new self();
$data = $this->data();
foreach($data as $property => $value) {
$self->{$property} = $value;
}
return $self;
}
public function create_complex(): self {
$self = new self();
foreach($this->data() as $property => $value) {
if ($property === 'test') {
if ($self->{$property} === null) {
$self->{$property} = new \stdClass();
}
} else {
$self->{$property} = $value;
}

@rajyan rajyan marked this pull request as draft May 2, 2022 13:42
@rajyan rajyan marked this pull request as ready for review May 2, 2022 15:43
@rajyan
Copy link
Contributor Author

rajyan commented May 2, 2022

Checked for the failing tests and I think it's unrelated.

@ondrejmirtes ondrejmirtes merged commit 1e95392 into phpstan:1.6.x May 2, 2022
@ondrejmirtes
Copy link
Member

Thank you!

@ondrejmirtes
Copy link
Member

@rajyan I think there's opportunity for refactoring, most likely in multiple steps:

  1. Instead of MutatingScope::$variableTypes and MutatingScope::$moreSpecificTypes, there should be only one thing - MutatingScope::$expressionTypes. But of course variable-specific methods like getVariableType() should be preserved.
  2. I think there's a chance we don't need MutatingScope::$conditionalExpressions at all. Instead of ConditionalExpressionHolder, we could have something similar to PHPStan\Type\ConditionalType right inside MutatingScope::$expressionTypes that would resolve lazily when calling MutatingScope::getType(). One nice thing it'd solve is that right now ConditionalExpressionHolder is used only for variables on both sides - if $a is X, then $b is Y. But it could work for any expression.

There are also other issues in https://github.com/phpstan/phpstan/milestone/12 (potentially very hard to solve) that you might tackle (if you feel like it) after 1) and 2) :)

Thanks for considering!

@rajyan rajyan deleted the fix/issue-3677 branch May 2, 2022 16:14
@rajyan
Copy link
Contributor Author

rajyan commented May 2, 2022

Thank you for your pointers.
I'm getting to understand these concepts and I think I can look for improvements:+1:

@ondrejmirtes
Copy link
Member

FYI @rajyan I noticed there are some commented-out test cases in

/*function (): void {
$from = null;
$to = null;
if (rand(0, 1)) {
$from = new \stdClass();
$to = new \stdClass();
}
if (rand(0, 1)) {
$from = new \stdClass();
$to = new \stdClass();
}
if ($from !== null) {
assertType('stdClass', $to);
}
};
function (bool $b): void {
$from = null;
$to = null;
if ($b) {
$from = new \stdClass();
$to = new \stdClass();
}
if ($from !== null) {
assertType('true', $b);
assertType('stdClass', $from);
assertType('stdClass', $to);
}
};
function (bool $b): void {
$from = null;
$to = null;
if ($b) {
$from = new \stdClass();
$to = new \stdClass();
}
if ($b) {
assertType('true', $b);
assertType('stdClass', $from);
assertType('stdClass', $to);
}
};
function (bool $b, bool $c): void {
if ($b) {
if ($c) {
$foo = 'bla';
}
}
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
if ($b) {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
if ($c) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
} else {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}
} else {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
if (!$c) {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
} else {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}
}
if (!$b && !$c) {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}
};
function (bool $b, bool $c): void {
if ($b && $c) {
$foo = 'bla';
}
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
if ($b) {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
if ($c) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
} else {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}
} else {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
if (!$c) {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
} else {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}
}
if (!$b && !$c) {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}
};
function (bool $b, bool $c, bool $d): void {
if ($b) {
if ($c) {
$foo = 'bla';
}
}
if ($d) {
$foo = 'ble';
}
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'bla\'|\'ble\'', $foo);
if ($b) {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'bla\'|\'ble\'', $foo);
if ($c) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'bla\'|\'ble\'', $foo);
if (!$d) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'bla\'', $foo);
} else {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'ble\'', $foo);
}
}
} else {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'ble\'', $foo);
if (!$c) {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'ble\'', $foo);
if (!$d) {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
assertType('*ERROR*', $foo);
}
}
}
if (!$b && !$c) {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'ble\'', $foo);
if (!$d) {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
assertType('*ERROR*', $foo);
} else {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'ble\'', $foo);
}
}
if ($d) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'bla\'|\'ble\'', $foo);
} else {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'bla\'', $foo);
if (!$b && !$c) {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
assertType('*ERROR*', $foo);
}
}
};
function (bool $b, bool $c, bool $d): void {
if ($d) {
$foo = 'ble';
}
if ($b) {
if ($c) {
$foo = 'bla';
}
}
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'bla\'|\'ble\'', $foo);
if ($b) {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'bla\'|\'ble\'', $foo);
if ($c) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'bla\'', $foo);
if (!$d) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'bla\'', $foo);
} else {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'bla\'', $foo);
}
} else {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'ble\'', $foo);
if ($d) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'ble\'', $foo);
} else {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}
}
} else {
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
assertType('\'ble\'', $foo);
if ($d) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertType('\'ble\'', $foo);
} else {
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}
}
};*/
that we should make work too as part of this feature :)

@rajyan
Copy link
Contributor Author

rajyan commented May 5, 2022

Thank you! I look into it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants