Skip to content

Commit e0a9241

Browse files
committed
TASK: Infer types in arms of ternary
`nullableString ? nullableString : "fallback"` will be a string
1 parent 2ffe099 commit e0a9241

File tree

5 files changed

+116
-11
lines changed

5 files changed

+116
-11
lines changed

src/Target/Php/Transpiler/TypeReference/TypeReferenceTranspiler.php

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
use PackageFactory\ComponentEngine\TypeSystem\Type\BooleanType\BooleanType;
2828
use PackageFactory\ComponentEngine\TypeSystem\Type\ComponentType\ComponentType;
2929
use PackageFactory\ComponentEngine\TypeSystem\Type\EnumType\EnumType;
30-
use PackageFactory\ComponentEngine\TypeSystem\Type\NullType\NullType;
3130
use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType;
3231
use PackageFactory\ComponentEngine\TypeSystem\Type\SlotType\SlotType;
3332
use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType;
@@ -55,12 +54,8 @@ public function transpile(TypeReferenceNode $typeReferenceNode): string
5554

5655
private function transpileUnionType(UnionType $unionType, TypeReferenceNode $typeReferenceNode): string
5756
{
58-
if (count($unionType->members) === 2 && $otherMemberTypeIfOneMemberIsNullType = match (NullType::class) {
59-
$unionType->members[0]::class => $unionType->members[1],
60-
$unionType->members[1]::class => $unionType->members[0],
61-
default => null
62-
}) {
63-
return $this->transpileNullableType($otherMemberTypeIfOneMemberIsNullType, $typeReferenceNode);
57+
if (count($unionType->members) === 2 && $unionType->isNullable()) {
58+
return $this->transpileNullableType($unionType->withoutNullable(), $typeReferenceNode);
6459
}
6560

6661
throw new \Exception('@TODO Transpilation of complex union types is not implemented');

src/TypeSystem/Resolver/TernaryOperation/TernaryOperationTypeResolver.php

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
namespace PackageFactory\ComponentEngine\TypeSystem\Resolver\TernaryOperation;
2424

2525
use PackageFactory\ComponentEngine\Parser\Ast\BooleanLiteralNode;
26+
use PackageFactory\ComponentEngine\Parser\Ast\IdentifierNode;
2627
use PackageFactory\ComponentEngine\Parser\Ast\TernaryOperationNode;
2728
use PackageFactory\ComponentEngine\TypeSystem\Resolver\Expression\ExpressionTypeResolver;
29+
use PackageFactory\ComponentEngine\TypeSystem\Scope\ShallowScope\ShallowScope;
2830
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
31+
use PackageFactory\ComponentEngine\TypeSystem\Type\NullType\NullType;
2932
use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType;
3033
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
3134

@@ -41,14 +44,39 @@ public function resolveTypeOf(TernaryOperationNode $ternaryOperationNode): TypeI
4144
$expressionTypeResolver = new ExpressionTypeResolver(
4245
scope: $this->scope
4346
);
44-
$conditionNode = $ternaryOperationNode->condition->root;
47+
$conditionNode = $ternaryOperationNode->condition;
4548

46-
if ($conditionNode instanceof BooleanLiteralNode) {
47-
return $conditionNode->value
49+
$rootType = $expressionTypeResolver->resolveTypeOf($conditionNode);
50+
51+
if ($conditionNode->root instanceof BooleanLiteralNode) {
52+
return $conditionNode->root->value
4853
? $expressionTypeResolver->resolveTypeOf($ternaryOperationNode->true)
4954
: $expressionTypeResolver->resolveTypeOf($ternaryOperationNode->false);
5055
}
5156

57+
if ($conditionNode->root instanceof IdentifierNode && $rootType instanceof UnionType && $rootType->isNullable()) {
58+
$trueExpressionTypeResolver = new ExpressionTypeResolver(
59+
scope: new ShallowScope(
60+
$conditionNode->root->value,
61+
$rootType->withoutNullable(),
62+
$this->scope
63+
)
64+
);
65+
66+
$falseExpressionTypeResolver = new ExpressionTypeResolver(
67+
scope: new ShallowScope(
68+
$conditionNode->root->value,
69+
NullType::get(),
70+
$this->scope
71+
)
72+
);
73+
74+
return UnionType::of(
75+
$trueExpressionTypeResolver->resolveTypeOf($ternaryOperationNode->true),
76+
$falseExpressionTypeResolver->resolveTypeOf($ternaryOperationNode->false)
77+
);
78+
}
79+
5280
return UnionType::of(
5381
$expressionTypeResolver->resolveTypeOf($ternaryOperationNode->true),
5482
$expressionTypeResolver->resolveTypeOf($ternaryOperationNode->false)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/**
4+
* PackageFactory.ComponentEngine - Universal View Components for PHP
5+
* Copyright (C) 2022 Contributors of PackageFactory.ComponentEngine
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace PackageFactory\ComponentEngine\TypeSystem\Scope\ShallowScope;
24+
25+
use PackageFactory\ComponentEngine\Parser\Ast\TypeReferenceNode;
26+
use PackageFactory\ComponentEngine\TypeSystem\ScopeInterface;
27+
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
28+
29+
final class ShallowScope implements ScopeInterface
30+
{
31+
public function __construct(
32+
private readonly string $overriddenName,
33+
private readonly TypeInterface $overriddenType,
34+
private readonly ScopeInterface $parentScope
35+
) {
36+
}
37+
38+
public function lookupTypeFor(string $name): ?TypeInterface
39+
{
40+
if ($this->overriddenName === $name) {
41+
return $this->overriddenType;
42+
}
43+
44+
return $this->parentScope->lookupTypeFor($name);
45+
}
46+
47+
public function resolveTypeReference(TypeReferenceNode $typeReferenceNode): TypeInterface
48+
{
49+
return $this->parentScope->resolveTypeReference($typeReferenceNode);
50+
}
51+
}

src/TypeSystem/Type/UnionType/UnionType.php

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

2323
namespace PackageFactory\ComponentEngine\TypeSystem\Type\UnionType;
2424

25+
use PackageFactory\ComponentEngine\TypeSystem\Type\NullType\NullType;
2526
use PackageFactory\ComponentEngine\TypeSystem\TypeInterface;
2627

2728
final class UnionType implements TypeInterface
@@ -46,6 +47,28 @@ private function __construct(TypeInterface ...$members)
4647
$this->members = $uniqueMembers;
4748
}
4849

50+
public function isNullable(): bool
51+
{
52+
foreach ($this->members as $member) {
53+
if ($member->is(NullType::get())) {
54+
return true;
55+
}
56+
}
57+
return false;
58+
}
59+
60+
public function withoutNullable(): TypeInterface
61+
{
62+
$nonNullMembers = [];
63+
foreach ($this->members as $member) {
64+
if ($member->is(NullType::get())) {
65+
continue;
66+
}
67+
$nonNullMembers[] = $member;
68+
}
69+
return self::of(...$nonNullMembers);
70+
}
71+
4972
public static function of(TypeInterface ...$members): TypeInterface
5073
{
5174
$union = new self(...$members);

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use PackageFactory\ComponentEngine\Parser\Ast\TernaryOperationNode;
2727
use PackageFactory\ComponentEngine\Test\Unit\TypeSystem\Scope\Fixtures\DummyScope;
2828
use PackageFactory\ComponentEngine\TypeSystem\Resolver\TernaryOperation\TernaryOperationTypeResolver;
29+
use PackageFactory\ComponentEngine\TypeSystem\Type\NullType\NullType;
2930
use PackageFactory\ComponentEngine\TypeSystem\Type\NumberType\NumberType;
3031
use PackageFactory\ComponentEngine\TypeSystem\Type\StringType\StringType;
3132
use PackageFactory\ComponentEngine\TypeSystem\Type\UnionType\UnionType;
@@ -46,7 +47,13 @@ public function ternaryOperationExamples(): array
4647
'1 < 2 ? variableOfTypeString : variableOfTypeNumber' => [
4748
'1 < 2 ? variableOfTypeString : variableOfTypeNumber',
4849
UnionType::of(NumberType::get(), StringType::get())
49-
]
50+
],
51+
'nullableString ? nullableString : "fallback"' => [
52+
'nullableString ? nullableString : "fallback"', StringType::get()
53+
],
54+
'nullableString ? null : nullableString' => [
55+
'nullableString ? null : nullableString', NullType::get()
56+
],
5057
];
5158
}
5259

@@ -62,6 +69,7 @@ public function resolvesTernaryOperationToResultingType(string $ternaryExpressio
6269
$scope = new DummyScope([
6370
'variableOfTypeString' => StringType::get(),
6471
'variableOfTypeNumber' => NumberType::get(),
72+
'nullableString' => UnionType::of(StringType::get(), NullType::get())
6573
]);
6674
$ternaryOperationTypeResolver = new TernaryOperationTypeResolver(
6775
scope: $scope

0 commit comments

Comments
 (0)