Skip to content

Commit a3be66b

Browse files
committed
fix(analyzer): correctly handle nullable generic templates
ref vimeo/psalm#10901 Signed-off-by: azjezz <[email protected]>
1 parent 98c9415 commit a3be66b

File tree

5 files changed

+69
-2
lines changed

5 files changed

+69
-2
lines changed

crates/analyzer/tests/cases/binary_throw_used.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<?php
22

3+
/**
4+
* @throws RuntimeException
5+
*/
36
function start(null|string $id): void
47
{
58
$id ?? throw new RuntimeException();

crates/analyzer/tests/cases/issue_583.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
*/
99
function x($data): void
1010
{
11-
$data['currency'] ?? throw new \InvalidArgumentException('Currency is required.'); // @mago-expect analysis: unused-statement
11+
$data['currency'] ?? throw new \InvalidArgumentException('Currency is required.');
1212
accept_string($data['currency']);
1313
}
1414

15-
function accept_string(string $_s): void {}
15+
function accept_string(string $_s): void
16+
{
17+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
interface TestInterface
4+
{
5+
public function test(): void;
6+
}
7+
8+
/**
9+
* @template T of TestInterface|null
10+
*/
11+
class Test
12+
{
13+
/**
14+
* @psalm-param T $value
15+
*/
16+
public function __construct(
17+
public TestInterface|null $value,
18+
) {}
19+
20+
public function getValue(): null|TestInterface
21+
{
22+
return $this->value;
23+
}
24+
25+
public function getValue2(): TestInterface
26+
{
27+
if ($this->value === null) {
28+
exit(1);
29+
}
30+
31+
return $this->value;
32+
}
33+
34+
public function getValue3(): TestInterface
35+
{
36+
return $this->value ?? exit(1);
37+
}
38+
}
39+
40+
/**
41+
* @template T of string|null
42+
*/
43+
class ScalarTest
44+
{
45+
/**
46+
* @param T $value
47+
*/
48+
public function __construct(
49+
public string|null $value,
50+
) {}
51+
52+
public function getValue(): string
53+
{
54+
if ($this->value === null) {
55+
return 'default';
56+
}
57+
58+
return $this->value;
59+
}
60+
}

crates/analyzer/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ test_case!(type_assert);
330330
test_case!(string_callable_template_inference);
331331
test_case!(clear_narrowed_prop_after_call);
332332
test_case!(binary_throw_used);
333+
test_case!(template_null_comparison);
333334

334335
// Github Issues
335336
test_case!(issue_659);

crates/codex/src/ttype/union.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,7 @@ impl TUnion {
796796
pub fn is_nullable(&self) -> bool {
797797
self.types.iter().any(|t| match t {
798798
TAtomic::Null => self.types.len() >= 2,
799+
TAtomic::GenericParameter(param) => param.constraint.is_nullable(),
799800
_ => false,
800801
})
801802
}

0 commit comments

Comments
 (0)