Skip to content

Commit aaf6c49

Browse files
committed
TASK: Narrower null comparison against any expression that resolves to null
1 parent e0a913a commit aaf6c49

File tree

3 files changed

+44
-39
lines changed

3 files changed

+44
-39
lines changed

src/TypeSystem/Narrower/ExpressionTypeNarrower.php

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222

2323
namespace PackageFactory\ComponentEngine\TypeSystem\Narrower;
2424

25-
use PackageFactory\ComponentEngine\Definition\BinaryOperator;
2625
use PackageFactory\ComponentEngine\Parser\Ast\BinaryOperationNode;
2726
use PackageFactory\ComponentEngine\Parser\Ast\BooleanLiteralNode;
2827
use PackageFactory\ComponentEngine\Parser\Ast\ExpressionNode;
2928
use PackageFactory\ComponentEngine\Parser\Ast\IdentifierNode;
30-
use PackageFactory\ComponentEngine\Parser\Ast\NullLiteralNode;
29+
use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver;
3130
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
31+
use PackageFactory\ComponentEngine\TypeSystem\Type\NullType\NullType;
3232

3333
/**
3434
* This class handles the analysis of identifier types that are used in a condition
@@ -68,23 +68,13 @@ public function narrowTypesOfSymbolsIn(ExpressionNode $expressionNode, TypeNarro
6868
$second = $binaryOperationNode->operands->rest[0];
6969

7070
if (
71-
($first->root instanceof BooleanLiteralNode
72-
&& ($boolean = $first->root) instanceof BooleanLiteralNode
73-
// @phpstan-ignore-next-line
74-
&& $other = $second
75-
) || ($second->root instanceof BooleanLiteralNode
76-
&& ($boolean = $second->root) instanceof BooleanLiteralNode
77-
// @phpstan-ignore-next-line
78-
&& $other = $first
71+
(($boolean = $first->root) instanceof BooleanLiteralNode
72+
&& $other = $second // @phpstan-ignore-line
73+
) || (($boolean = $second->root) instanceof BooleanLiteralNode
74+
&& $other = $first // @phpstan-ignore-line
7975
)
8076
) {
81-
$contextBasedOnOperator = match ($binaryOperationNode->operator) {
82-
BinaryOperator::EQUAL => $context,
83-
BinaryOperator::NOT_EQUAL => $context->negate(),
84-
default => null,
85-
};
86-
87-
if (!$contextBasedOnOperator) {
77+
if (!$contextBasedOnOperator = $context->basedOnBinaryOperator($binaryOperationNode->operator)) {
8878
return NarrowedTypes::empty();
8979
}
9080

@@ -94,30 +84,30 @@ public function narrowTypesOfSymbolsIn(ExpressionNode $expressionNode, TypeNarro
9484
);
9585
}
9686

97-
// cases
98-
// `nullableString === null ? "nullableString is null" : "nullableString is not null"`
99-
// `nullableString !== null ? "nullableString is not null" : "nullableString is null"`
100-
$comparedIdentifierValueToNull = match (true) {
101-
// case `nullableString === null`
102-
$first->root instanceof IdentifierNode && $second->root instanceof NullLiteralNode => $first->root->value,
103-
// yodas case `null === nullableString`
104-
$first->root instanceof NullLiteralNode && $second->root instanceof IdentifierNode => $second->root->value,
105-
default => null
106-
};
87+
$expressionTypeResolver = (new ExpressionTypeResolver($this->scope));
88+
if (
89+
($expressionTypeResolver->resolveTypeOf($first)->is(NullType::get())
90+
&& $other = $second // @phpstan-ignore-line
91+
) || ($expressionTypeResolver->resolveTypeOf($second)->is(NullType::get())
92+
&& $other = $first // @phpstan-ignore-line
93+
)
94+
) {
95+
if (!$other->root instanceof IdentifierNode) {
96+
return NarrowedTypes::empty();
97+
}
98+
$type = $this->scope->lookupTypeFor($other->root->value);
99+
if (!$type) {
100+
return NarrowedTypes::empty();
101+
}
107102

108-
if ($comparedIdentifierValueToNull === null) {
109-
return NarrowedTypes::empty();
110-
}
111-
$type = $this->scope->lookupTypeFor($comparedIdentifierValueToNull);
112-
if (!$type) {
113-
return NarrowedTypes::empty();
114-
}
103+
if (!$contextBasedOnOperator = $context->basedOnBinaryOperator($binaryOperationNode->operator)) {
104+
return NarrowedTypes::empty();
105+
}
115106

116-
if ($binaryOperationNode->operator === BinaryOperator::EQUAL) {
117-
return NarrowedTypes::fromEntry($comparedIdentifierValueToNull, $context->negate()->narrowType($type));
118-
}
119-
if ($binaryOperationNode->operator === BinaryOperator::NOT_EQUAL) {
120-
return NarrowedTypes::fromEntry($comparedIdentifierValueToNull, $context->narrowType($type));
107+
return NarrowedTypes::fromEntry(
108+
$other->root->value,
109+
$contextBasedOnOperator->negate()->narrowType($type)
110+
);
121111
}
122112
}
123113

src/TypeSystem/Narrower/TypeNarrowerContext.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
namespace PackageFactory\ComponentEngine\TypeSystem\Narrower;
2424

25+
use PackageFactory\ComponentEngine\Definition\BinaryOperator;
2526
use PackageFactory\ComponentEngine\TypeSystem\Type\NullType\NullType;
2627
use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType;
2728
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
@@ -40,6 +41,15 @@ public function negate(): self
4041
};
4142
}
4243

44+
public function basedOnBinaryOperator(BinaryOperator $operator): ?self
45+
{
46+
return match ($operator) {
47+
BinaryOperator::EQUAL => $this,
48+
BinaryOperator::NOT_EQUAL => $this->negate(),
49+
default => null,
50+
};
51+
}
52+
4353
public function narrowType(TypeInterface $type): TypeInterface
4454
{
4555
if (!$type instanceof UnionType || !$type->containsNull()) {

test/Unit/TypeSystem/Resolver/TernaryOperation/TernaryOperationTypeResolverTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ public function ternaryOperationExamples(): array
8484
'true !== (nullableString === null) ? nullableString : ""' => [
8585
'true !== (nullableString === null) ? nullableString : ""', StringType::get()
8686
],
87+
88+
'nullableString === variableOfTypeNull ? "" : nullableString' => [
89+
'nullableString === variableOfTypeNull ? "" : nullableString', StringType::get()
90+
],
8791
];
8892
}
8993

@@ -99,6 +103,7 @@ public function resolvesTernaryOperationToResultingType(string $ternaryExpressio
99103
$scope = new DummyScope([
100104
'variableOfTypeString' => StringType::get(),
101105
'variableOfTypeNumber' => NumberType::get(),
106+
'variableOfTypeNull' => NullType::get(),
102107
'nullableString' => UnionType::of(StringType::get(), NullType::get())
103108
]);
104109
$ternaryOperationTypeResolver = new TernaryOperationTypeResolver(

0 commit comments

Comments
 (0)