Skip to content

Commit 1dab96c

Browse files
hikari-no-yumenikic
authored andcommitted
Show "or null" in TypeErrors for nullable arg_infos
1 parent b33e965 commit 1dab96c

File tree

9 files changed

+399
-57
lines changed

9 files changed

+399
-57
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ PHP NEWS
55
- Core:
66
. Fixed bug #72767 (PHP Segfaults when trying to expand an infinite operator).
77
(Nikita)
8+
. TypeError messages for arg_info type checks will now say "must be ...
9+
or null" where the parameter or return type accepts null. (Andrea)
810

911
- EXIF:
1012
. Fixed bug #72735 (Samsung picture thumb not read (zero size)). (Kalle, Remi)

UPGRADING

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ PHP 7.1 UPGRADE NOTES
127127
. Closure::fromCallable (RFC: https://wiki.php.net/rfc/closurefromcallable)
128128
. Added support for class constant visibility modifiers.
129129
(RFC: https://wiki.php.net/rfc/class_const_visibility)
130+
. TypeError messages for arg_info type checks will now say "must be ...
131+
or null", or "must ... or be null" where the parameter or return type
132+
accepts null. arg_info type checks are used by all userland functions with
133+
type declarations, and some internal functions. Both nullable type
134+
declarations (?int) and parameters with default values of null
135+
(int $foo = NULL) are considered to "accept null" for this purpose.
130136

131137
========================================
132138
3. Changes in SAPI modules

Zend/tests/ns_071.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ new bar(new \stdclass);
1818
--EXPECTF--
1919
NULL
2020

21-
Fatal error: Uncaught TypeError: Argument 1 passed to foo\bar::__construct() must be of the type array, object given, called in %s on line %d and defined in %s:%d
21+
Fatal error: Uncaught TypeError: Argument 1 passed to foo\bar::__construct() must be of the type array or null, object given, called in %s on line %d and defined in %s:%d
2222
Stack trace:
2323
#0 %s(%d): foo\bar->__construct(Object(stdClass))
2424
#1 {main}

Zend/tests/ns_072.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ object(foo\test)#%d (0) {
3030
}
3131
NULL
3232

33-
Fatal error: Uncaught TypeError: Argument 1 passed to foo\bar::__construct() must implement interface foo\foo, instance of stdClass given, called in %s on line %d and defined in %s:%d
33+
Fatal error: Uncaught TypeError: Argument 1 passed to foo\bar::__construct() must implement interface foo\foo or be null, instance of stdClass given, called in %s on line %d and defined in %s:%d
3434
Stack trace:
3535
#0 %s(%d): foo\bar->__construct(Object(stdClass))
3636
#1 {main}

Zend/tests/return_types/030.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ foo(0);
1616
ok
1717
ok
1818

19-
Fatal error: Uncaught TypeError: Return value of foo() must be of the type array, integer returned in %s030.php:3
19+
Fatal error: Uncaught TypeError: Return value of foo() must be of the type array or null, integer returned in %s030.php:3
2020
Stack trace:
2121
#0 %s030.php(10): foo(0)
2222
#1 {main}

Zend/tests/typehints/or_null.phpt

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
--TEST--
2+
Test "or null"/"or be null" in type-checking errors for userland functions
3+
--FILE--
4+
<?php
5+
6+
// This should test every branch in zend_execute.c's `zend_verify_arg_type`, `zend_verify_return_type` and `zend_verify_missing_return_type` functions which produces an "or null"/"or be null" part in its error message
7+
8+
function unloadedClass(?I\Dont\Exist $param) {}
9+
10+
try {
11+
unloadedClass(new \StdClass);
12+
} catch (\TypeError $e) {
13+
echo $e, PHP_EOL;
14+
}
15+
16+
class RealClass {}
17+
interface RealInterface {}
18+
19+
function loadedClass(?RealClass $param) {}
20+
function loadedInterface(?RealInterface $param) {}
21+
22+
try {
23+
loadedClass(new \StdClass);
24+
} catch (\TypeError $e) {
25+
echo $e, PHP_EOL;
26+
}
27+
28+
try {
29+
loadedInterface(new \StdClass);
30+
} catch (\TypeError $e) {
31+
echo $e, PHP_EOL;
32+
}
33+
34+
try {
35+
unloadedClass(1);
36+
} catch (\TypeError $e) {
37+
echo $e, PHP_EOL;
38+
}
39+
40+
try {
41+
loadedClass(1);
42+
} catch (\TypeError $e) {
43+
echo $e, PHP_EOL;
44+
}
45+
46+
try {
47+
loadedInterface(1);
48+
} catch (\TypeError $e) {
49+
echo $e, PHP_EOL;
50+
}
51+
52+
function callableF(?callable $param) {}
53+
54+
try {
55+
callableF(1);
56+
} catch (\TypeError $e) {
57+
echo $e, PHP_EOL;
58+
}
59+
60+
function iterableF(?iterable $param) {}
61+
62+
try {
63+
iterableF(1);
64+
} catch (\TypeError $e) {
65+
echo $e, PHP_EOL;
66+
}
67+
68+
function intF(?int $param) {}
69+
70+
try {
71+
intF(new StdClass);
72+
} catch (\TypeError $e) {
73+
echo $e, PHP_EOL;
74+
}
75+
76+
function returnUnloadedClass(): ?I\Dont\Exist {
77+
return new \StdClass;
78+
}
79+
80+
try {
81+
returnUnloadedClass();
82+
} catch (\TypeError $e) {
83+
echo $e, PHP_EOL;
84+
}
85+
86+
function returnLoadedClass(): ?RealClass {
87+
return new \StdClass;
88+
}
89+
90+
try {
91+
returnLoadedClass();
92+
} catch (\TypeError $e) {
93+
echo $e, PHP_EOL;
94+
}
95+
96+
function returnLoadedInterface(): ?RealInterface {
97+
return new \StdClass;
98+
}
99+
100+
try {
101+
returnLoadedInterface();
102+
} catch (\TypeError $e) {
103+
echo $e, PHP_EOL;
104+
}
105+
106+
function returnUnloadedClassScalar(): ?I\Dont\Exist {
107+
return 1;
108+
}
109+
110+
try {
111+
returnUnloadedClassScalar();
112+
} catch (\TypeError $e) {
113+
echo $e, PHP_EOL;
114+
}
115+
116+
function returnLoadedClassScalar(): ?RealClass {
117+
return 1;
118+
}
119+
120+
try {
121+
returnLoadedClassScalar();
122+
} catch (\TypeError $e) {
123+
echo $e, PHP_EOL;
124+
}
125+
126+
function returnLoadedInterfaceScalar(): ?RealInterface {
127+
return 1;
128+
}
129+
130+
try {
131+
returnLoadedInterfaceScalar();
132+
} catch (\TypeError $e) {
133+
echo $e, PHP_EOL;
134+
}
135+
136+
function returnCallable(): ?callable {
137+
return 1;
138+
}
139+
140+
try {
141+
returnCallable();
142+
} catch (\TypeError $e) {
143+
echo $e, PHP_EOL;
144+
}
145+
146+
function returnIterable(): ?iterable {
147+
return 1;
148+
}
149+
150+
try {
151+
returnIterable();
152+
} catch (\TypeError $e) {
153+
echo $e, PHP_EOL;
154+
}
155+
156+
function returnInt(): ?int {
157+
return new \StdClass;
158+
}
159+
160+
try {
161+
returnInt();
162+
} catch (\TypeError $e) {
163+
echo $e, PHP_EOL;
164+
}
165+
166+
function returnMissingUnloadedClass(): ?I\Dont\Exist {
167+
}
168+
169+
try {
170+
returnMissingUnloadedClass();
171+
} catch (\TypeError $e) {
172+
echo $e, PHP_EOL;
173+
}
174+
175+
function returnMissingLoadedClass(): ?RealClass {
176+
}
177+
178+
try {
179+
returnMissingLoadedClass();
180+
} catch (\TypeError $e) {
181+
echo $e, PHP_EOL;
182+
}
183+
184+
function returnMissingLoadedInterface(): ?RealInterface {
185+
}
186+
187+
try {
188+
returnMissingLoadedInterface();
189+
} catch (\TypeError $e) {
190+
echo $e, PHP_EOL;
191+
}
192+
193+
function returnMissingCallable(): ?callable {
194+
}
195+
196+
try {
197+
returnMissingCallable();
198+
} catch (\TypeError $e) {
199+
echo $e, PHP_EOL;
200+
}
201+
202+
function returnMissingIterable(): ?iterable {
203+
}
204+
205+
try {
206+
returnMissingIterable();
207+
} catch (\TypeError $e) {
208+
echo $e, PHP_EOL;
209+
}
210+
211+
function returnMissingInt(): ?int {
212+
}
213+
214+
try {
215+
returnMissingInt();
216+
} catch (\TypeError $e) {
217+
echo $e, PHP_EOL;
218+
}
219+
220+
?>
221+
--EXPECTF--
222+
TypeError: Argument 1 passed to unloadedClass() must be an instance of I\Dont\Exist or null, instance of stdClass given, called in %s on line 8 and defined in %s:5
223+
Stack trace:
224+
#0 %s(8): unloadedClass(Object(stdClass))
225+
#1 {main}
226+
TypeError: Argument 1 passed to loadedClass() must be an instance of RealClass or null, instance of stdClass given, called in %s on line 20 and defined in %s:16
227+
Stack trace:
228+
#0 %s(20): loadedClass(Object(stdClass))
229+
#1 {main}
230+
TypeError: Argument 1 passed to loadedInterface() must implement interface RealInterface or be null, instance of stdClass given, called in %s on line 26 and defined in %s:17
231+
Stack trace:
232+
#0 %s(26): loadedInterface(Object(stdClass))
233+
#1 {main}
234+
TypeError: Argument 1 passed to unloadedClass() must be an instance of I\Dont\Exist or null, integer given, called in %s on line 32 and defined in %s:5
235+
Stack trace:
236+
#0 %s(32): unloadedClass(1)
237+
#1 {main}
238+
TypeError: Argument 1 passed to loadedClass() must be an instance of RealClass or null, integer given, called in %s on line 38 and defined in %s:16
239+
Stack trace:
240+
#0 %s(38): loadedClass(1)
241+
#1 {main}
242+
TypeError: Argument 1 passed to loadedInterface() must implement interface RealInterface or be null, integer given, called in %s on line 44 and defined in %s:17
243+
Stack trace:
244+
#0 %s(44): loadedInterface(1)
245+
#1 {main}
246+
TypeError: Argument 1 passed to callableF() must be callable or null, integer given, called in %s on line 52 and defined in %s:49
247+
Stack trace:
248+
#0 %s(52): callableF(1)
249+
#1 {main}
250+
TypeError: Argument 1 passed to iterableF() must be iterable or null, integer given, called in %s on line 60 and defined in %s:57
251+
Stack trace:
252+
#0 %s(60): iterableF(1)
253+
#1 {main}
254+
TypeError: Argument 1 passed to intF() must be of the type integer or null, object given, called in %s on line 68 and defined in %s:65
255+
Stack trace:
256+
#0 %s(68): intF(Object(stdClass))
257+
#1 {main}
258+
TypeError: Return value of returnUnloadedClass() must be an instance of I\Dont\Exist or null, instance of stdClass returned in %s:74
259+
Stack trace:
260+
#0 %s(78): returnUnloadedClass()
261+
#1 {main}
262+
TypeError: Return value of returnLoadedClass() must be an instance of RealClass or null, instance of stdClass returned in %s:84
263+
Stack trace:
264+
#0 %s(88): returnLoadedClass()
265+
#1 {main}
266+
TypeError: Return value of returnLoadedInterface() must implement interface RealInterface or be null, instance of stdClass returned in %s:94
267+
Stack trace:
268+
#0 %s(98): returnLoadedInterface()
269+
#1 {main}
270+
TypeError: Return value of returnUnloadedClassScalar() must be an instance of I\Dont\Exist or null, integer returned in %s:104
271+
Stack trace:
272+
#0 %s(108): returnUnloadedClassScalar()
273+
#1 {main}
274+
TypeError: Return value of returnLoadedClassScalar() must be an instance of RealClass or null, integer returned in %s:114
275+
Stack trace:
276+
#0 %s(118): returnLoadedClassScalar()
277+
#1 {main}
278+
TypeError: Return value of returnLoadedInterfaceScalar() must implement interface RealInterface or be null, integer returned in %s:124
279+
Stack trace:
280+
#0 %s(128): returnLoadedInterfaceScalar()
281+
#1 {main}
282+
TypeError: Return value of returnCallable() must be callable or null, integer returned in %s:134
283+
Stack trace:
284+
#0 %s(138): returnCallable()
285+
#1 {main}
286+
TypeError: Return value of returnIterable() must be iterable or null, integer returned in %s:144
287+
Stack trace:
288+
#0 %s(148): returnIterable()
289+
#1 {main}
290+
TypeError: Return value of returnInt() must be of the type integer or null, object returned in %s:154
291+
Stack trace:
292+
#0 %s(158): returnInt()
293+
#1 {main}
294+
TypeError: Return value of returnMissingUnloadedClass() must be an instance of I\Dont\Exist or null, none returned in %s:164
295+
Stack trace:
296+
#0 %s(167): returnMissingUnloadedClass()
297+
#1 {main}
298+
TypeError: Return value of returnMissingLoadedClass() must be an instance of RealClass or null, none returned in %s:173
299+
Stack trace:
300+
#0 %s(176): returnMissingLoadedClass()
301+
#1 {main}
302+
TypeError: Return value of returnMissingLoadedInterface() must implement interface RealInterface or be null, none returned in %s:182
303+
Stack trace:
304+
#0 %s(185): returnMissingLoadedInterface()
305+
#1 {main}
306+
TypeError: Return value of returnMissingCallable() must be callable or null, none returned in %s:191
307+
Stack trace:
308+
#0 %s(194): returnMissingCallable()
309+
#1 {main}
310+
TypeError: Return value of returnMissingIterable() must be iterable or null, none returned in %s:200
311+
Stack trace:
312+
#0 %s(203): returnMissingIterable()
313+
#1 {main}
314+
TypeError: Return value of returnMissingInt() must be of the type integer or null, none returned in %s:209
315+
Stack trace:
316+
#0 %s(212): returnMissingInt()
317+
#1 {main}

0 commit comments

Comments
 (0)