Skip to content

Commit 288a30a

Browse files
committed
[generator] defer nullable/falsable handling
Rather than "always strip false/null from return types, and then add them back when the function is non-nullsy / non-falsy", we can reduce complexity by "leave return types alone, strip them only when needed" This results in no chanes to generated files, but makes future development simpler
1 parent 1fb13c3 commit 288a30a

File tree

2 files changed

+28
-39
lines changed

2 files changed

+28
-39
lines changed

generator/src/PhpStanFunctions/PhpStanType.php

+27-38
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ class PhpStanType
1919
'\OCI-Collection',
2020
];
2121

22-
private bool $nullable;
23-
24-
private bool $falsable;
25-
2622
/**
2723
* @var string[]
2824
*/
@@ -42,33 +38,17 @@ public function __construct(string|\SimpleXMLElement $data, bool $writeOnly = fa
4238
$data = $regs[1];
4339
}
4440

45-
//first we try to parse the type string to have a list as clean as possible.
46-
$nullable = false;
47-
$falsable = false;
4841
// Let's make the parameter nullable if it is by reference and is used only for writing.
4942
if ($writeOnly && $data !== 'resource' && $data !== 'mixed') {
5043
$data .= '|null';
5144
}
5245

5346
$returnTypes = $this->explodeTypes($data);
54-
//remove 'null' from the list to identify if the signature type should be nullable
55-
if (($nullablePosition = \array_search('null', $returnTypes, true)) !== false) {
56-
$nullable = true;
57-
\array_splice($returnTypes, (int) $nullablePosition, 1);
58-
}
59-
//remove 'false' from the list to identify if the function return false on error
60-
if (($falsablePosition = \array_search('false', $returnTypes, true)) !== false) {
61-
$falsable = true;
62-
\array_splice($returnTypes, (int) $falsablePosition, 1);
63-
}
64-
$count = \count($returnTypes);
65-
if ($count === 0) {
66-
$returnType = '';
67-
}
47+
$anyNullable = false;
6848
foreach ($returnTypes as &$returnType) {
6949
$returnType = \trim($returnType);
7050
if (str_contains($returnType, '?')) {
71-
$nullable = true;
51+
$anyNullable = true;
7252
$returnType = \str_replace('?', '', $returnType);
7353
}
7454
// remove the parenthesis only if we are not dealing with a callable
@@ -97,9 +77,10 @@ public function __construct(string|\SimpleXMLElement $data, bool $writeOnly = fa
9777

9878
$returnType = Type::toRootNamespace($returnType);
9979
}
80+
if ($anyNullable) {
81+
$returnTypes[] = 'null';
82+
}
10083
$this->types = array_unique($returnTypes);
101-
$this->nullable = $nullable;
102-
$this->falsable = $falsable;
10384
}
10485

10586
/**
@@ -136,27 +117,26 @@ public static function selectMostUsefulType(
136117
public function getDocBlockType(?ErrorType $errorType = null): string
137118
{
138119
$returnTypes = $this->types;
139-
//add back either null or false to the return types unless the target function return null or false on error (only relevant on return type)
140-
if ($this->falsable && $errorType !== ErrorType::FALSY) {
141-
$returnTypes[] = 'false';
142-
} elseif ($this->nullable && $errorType !== ErrorType::NULLSY) {
143-
$returnTypes[] = 'null';
120+
// If we're turning an error marker into an exception, remove
121+
// the error marker from the return types
122+
if (in_array('false', $returnTypes) && $errorType === ErrorType::FALSY) {
123+
$returnTypes = array_diff($returnTypes, ['false']);
124+
}
125+
if (in_array('null', $returnTypes) && $errorType === ErrorType::NULLSY) {
126+
$returnTypes = array_diff($returnTypes, ['null']);
144127
}
145128
sort($returnTypes);
146129
$type = join('|', $returnTypes);
147-
if ($type === 'bool' && !$this->nullable && $errorType === ErrorType::FALSY) {
148-
// If the function only returns a boolean, since false is for error, true is for success.
149-
// Let's replace this with a "void".
130+
// If the function only returns a boolean, since false is for error, true is for success.
131+
// Let's replace this with a "void".
132+
if ($type === 'bool' && $errorType === ErrorType::FALSY) {
150133
return 'void';
151134
}
152135
return $type;
153136
}
154137

155138
public function getSignatureType(?ErrorType $errorType = null): string
156139
{
157-
//We edit the return type depending of the "onErrorType" of the function. For example, a function that is both nullable and "nullsy" will created a non nullable safe function. Only relevant on return type.
158-
$nullable = $errorType === ErrorType::NULLSY ? false : $this->nullable;
159-
$falsable = $errorType === ErrorType::FALSY ? false : $this->falsable;
160140
$types = $this->types;
161141
//no typehint exists for those cases
162142
if (\array_intersect(self::NO_SIGNATURE_TYPES, $types) !== []) {
@@ -172,8 +152,6 @@ public function getSignatureType(?ErrorType $errorType = null): string
172152
$type = 'array'; //generics cannot be typehinted
173153
} elseif (str_contains($type, 'resource')) {
174154
$type = ''; // resource cant be typehinted
175-
} elseif (str_contains($type, 'null')) {
176-
$type = ''; // null is a real typehint
177155
} elseif (str_contains($type, 'true')) {
178156
$type = 'bool'; // php8.1 doesn't support "true" as a typehint
179157
} elseif (str_contains($type, 'non-falsy-string')) {
@@ -186,10 +164,21 @@ public function getSignatureType(?ErrorType $errorType = null): string
186164
$types = array_unique($types);
187165
sort($types);
188166

167+
// If we're turning false/null into exceptions, then
168+
// remove false/null from the return types
169+
if ($errorType === ErrorType::FALSY) {
170+
$types = array_diff($types, ['false']);
171+
}
172+
if ($errorType === ErrorType::NULLSY) {
173+
$types = array_diff($types, ['null']);
174+
}
175+
// remove "null" from the union so we can add "?" later
176+
$nullable = in_array('null', $types);
177+
$types = array_diff($types, ['null']);
189178
if (count($types) === 0) {
190179
return '';
191180
} elseif (count($types) === 1) {
192-
$finalType = $types[0];
181+
$finalType = array_values($types)[0];
193182
if ($finalType === 'bool' && !$nullable && $errorType === ErrorType::FALSY) {
194183
// If the function only returns a boolean, since false is for
195184
// error, true is for success. Let's replace this with a "void".

generator/tests/PhpStanFunctions/PhpStanTypeTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function testFalsable(): void
8484
{
8585
$param = new PhpStanType('string|false');
8686
$this->assertEquals('false|string', $param->getDocBlockType());
87-
$this->assertEquals('string', $param->getSignatureType());
87+
$this->assertEquals('', $param->getSignatureType());
8888
}
8989

9090
public function testResource(): void

0 commit comments

Comments
 (0)