Move declare() to the top of the file only, and allow int->float generalization

This commit is contained in:
Anthony Ferrara 2015-02-18 10:26:29 -05:00
parent d6bea5bb1e
commit 00b3e77ead
7 changed files with 303 additions and 89 deletions

View File

@ -1,13 +0,0 @@
--TEST--
RFC example: expected class int and returned integer
--FILE--
<?php
function answer(): int {
return 42;
}
answer();
--EXPECTF--
Catchable fatal error: Return value of answer() must be an instance of int, integer returned in %s on line %d

View File

@ -0,0 +1,16 @@
--TEST--
Scalar type with namespaces proper ordering
--FILE--
<?php
declare(strict_types=1);
namespace Foo {
function add1(int $arg): int {
return $arg + 1;
}
}
namespace {
var_dump(Foo\add1(123));
}
--EXPECT--
int(124)

View File

@ -0,0 +1,268 @@
--TEST--
Scalar type hint strict mode
--FILE--
<?php
declare(strict_types=1);
$errnames = [
E_NOTICE => 'E_NOTICE',
E_WARNING => 'E_WARNING',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR'
];
set_error_handler(function (int $errno, string $errmsg, string $file, int $line) use ($errnames) {
echo "$errnames[$errno]: $errmsg on line $line\n";
return true;
});
$functions = [
'int' => function (int $i) { return $i; },
'float' => function (float $f) { return $f; },
'string' => function (string $s) { return $s; },
'bool' => function (bool $b) { return $b; }
];
class Stringable {
public function __toString() {
return "foobar";
}
}
$values = [
1,
"1",
1.0,
1.5,
"1a",
"a",
"",
PHP_INT_MAX,
NAN,
TRUE,
FALSE,
NULL,
[],
new StdClass,
new Stringable,
fopen("data:text/plain,foobar", "r")
];
foreach ($functions as $type => $function) {
echo PHP_EOL, "Testing '$type' typehint:", PHP_EOL;
foreach ($values as $value) {
echo "*** Trying ";
var_dump($value);
var_dump($function($value));
}
}
--EXPECTF--
Testing 'int' typehint:
*** Trying int(1)
int(1)
*** Trying string(1) "1"
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d and defined on line %d
string(1) "1"
*** Trying float(1)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d and defined on line %d
float(1)
*** Trying float(1.5)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d and defined on line %d
float(1.5)
*** Trying string(2) "1a"
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d and defined on line %d
string(2) "1a"
*** Trying string(1) "a"
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d and defined on line %d
string(1) "a"
*** Trying string(0) ""
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d and defined on line %d
string(0) ""
*** Trying int(9223372036854775807)
int(9223372036854775807)
*** Trying float(NAN)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d and defined on line %d
float(NAN)
*** Trying bool(true)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, boolean given, called in %s on line %d and defined on line %d
bool(true)
*** Trying bool(false)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, boolean given, called in %s on line %d and defined on line %d
bool(false)
*** Trying NULL
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, null given, called in %s on line %d and defined on line %d
NULL
*** Trying array(0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, array given, called in %s on line %d and defined on line %d
array(0) {
}
*** Trying object(stdClass)#6 (0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, object given, called in %s on line %d and defined on line %d
object(stdClass)#6 (0) {
}
*** Trying object(Stringable)#7 (0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, object given, called in %s on line %d and defined on line %d
object(Stringable)#7 (0) {
}
*** Trying resource(5) of type (stream)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type integer, resource given, called in %s on line %d and defined on line %d
resource(5) of type (stream)
Testing 'float' typehint:
*** Trying int(1)
float(1)
*** Trying string(1) "1"
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d and defined on line %d
string(1) "1"
*** Trying float(1)
float(1)
*** Trying float(1.5)
float(1.5)
*** Trying string(2) "1a"
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d and defined on line %d
string(2) "1a"
*** Trying string(1) "a"
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d and defined on line %d
string(1) "a"
*** Trying string(0) ""
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d and defined on line %d
string(0) ""
*** Trying int(9223372036854775807)
float(9.2233720368548E+18)
*** Trying float(NAN)
float(NAN)
*** Trying bool(true)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, boolean given, called in %s on line %d and defined on line %d
bool(true)
*** Trying bool(false)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, boolean given, called in %s on line %d and defined on line %d
bool(false)
*** Trying NULL
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, null given, called in %s on line %d and defined on line %d
NULL
*** Trying array(0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, array given, called in %s on line %d and defined on line %d
array(0) {
}
*** Trying object(stdClass)#6 (0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, object given, called in %s on line %d and defined on line %d
object(stdClass)#6 (0) {
}
*** Trying object(Stringable)#7 (0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, object given, called in %s on line %d and defined on line %d
object(Stringable)#7 (0) {
}
*** Trying resource(5) of type (stream)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, resource given, called in %s on line %d and defined on line %d
resource(5) of type (stream)
Testing 'string' typehint:
*** Trying int(1)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, integer given, called in %s on line %d and defined on line %d
int(1)
*** Trying string(1) "1"
string(1) "1"
*** Trying float(1)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d and defined on line %d
float(1)
*** Trying float(1.5)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d and defined on line %d
float(1.5)
*** Trying string(2) "1a"
string(2) "1a"
*** Trying string(1) "a"
string(1) "a"
*** Trying string(0) ""
string(0) ""
*** Trying int(9223372036854775807)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, integer given, called in %s on line %d and defined on line %d
int(9223372036854775807)
*** Trying float(NAN)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d and defined on line %d
float(NAN)
*** Trying bool(true)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, boolean given, called in %s on line %d and defined on line %d
bool(true)
*** Trying bool(false)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, boolean given, called in %s on line %d and defined on line %d
bool(false)
*** Trying NULL
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, null given, called in %s on line %d and defined on line %d
NULL
*** Trying array(0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, array given, called in %s on line %d and defined on line %d
array(0) {
}
*** Trying object(stdClass)#6 (0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, object given, called in %s on line %d and defined on line %d
object(stdClass)#6 (0) {
}
*** Trying object(Stringable)#7 (0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, object given, called in %s on line %d and defined on line %d
object(Stringable)#7 (0) {
}
*** Trying resource(5) of type (stream)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type string, resource given, called in %s on line %d and defined on line %d
resource(5) of type (stream)
Testing 'bool' typehint:
*** Trying int(1)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, integer given, called in %s on line %d and defined on line %d
int(1)
*** Trying string(1) "1"
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d and defined on line %d
string(1) "1"
*** Trying float(1)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d and defined on line %d
float(1)
*** Trying float(1.5)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d and defined on line %d
float(1.5)
*** Trying string(2) "1a"
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d and defined on line %d
string(2) "1a"
*** Trying string(1) "a"
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d and defined on line %d
string(1) "a"
*** Trying string(0) ""
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d and defined on line %d
string(0) ""
*** Trying int(9223372036854775807)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, integer given, called in %s on line %d and defined on line %d
int(9223372036854775807)
*** Trying float(NAN)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d and defined on line %d
float(NAN)
*** Trying bool(true)
bool(true)
*** Trying bool(false)
bool(false)
*** Trying NULL
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, null given, called in %s on line %d and defined on line %d
NULL
*** Trying array(0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, array given, called in %s on line %d and defined on line %d
array(0) {
}
*** Trying object(stdClass)#6 (0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, object given, called in %s on line %d and defined on line %d
object(stdClass)#6 (0) {
}
*** Trying object(Stringable)#7 (0) {
}
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, object given, called in %s on line %d and defined on line %d
object(Stringable)#7 (0) {
}
*** Trying resource(5) of type (stream)
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type boolean, resource given, called in %s on line %d and defined on line %d
resource(5) of type (stream)

View File

@ -83,7 +83,7 @@ E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type intege
Testing 'float' typehint:
*** Trying integer value
E_RECOVERABLE_ERROR: Argument 1 passed to {closure}() must be of the type float, integer given, called in %s on line 53 and defined on line 19
float(1)
*** Trying float value
float(1)
*** Trying string value

View File

@ -3,17 +3,6 @@ Test nested function calls in strict_types=0 and strict_types=1 modes
--FILE--
<?php
$errored = FALSE;
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use (&$errored) {
// skipping errors make testing more practical
if ($errno !== E_RECOVERABLE_ERROR) die("Wrong errno");
echo "Catchable fatal error: $errstr in $errfile on line $errline\n";
$errored = TRUE;
return TRUE;
});
function takes_int(int $x) {
global $errored;
if ($errored) {
@ -48,68 +37,7 @@ declare(strict_types=0) {
}
}
// implicit weak mode code
function weak_calls_takes_int() {
takes_int(1.0); // should succeed, weak mode
}
class WeakTakesIntCaller {
public function call() {
takes_int(1.0); // should succeed, weak mode
}
}
// Now for the calling!
// implicit weak mode code
strict_calls_takes_int(); // should cause an error: our call to func is weak, but it was declared in strict mode so calls it makes are strict
weak_calls_takes_int(); // should succeed
explicit_weak_calls_takes_int(); // should succeed
(new StrictTakesIntCaller)->call(); // should cause an error
(new WeakTakesIntCaller)->call(); // should succeed
(new ExplicitWeakTakesIntCaller)->call(); // should succeed
declare(strict_types=0) {
strict_calls_takes_int(); // should cause an error: our call to func is weak, but it was declared in strict mode so calls it makes are strict
weak_calls_takes_int(); // should succeed
explicit_weak_calls_takes_int(); // should succeed
(new StrictTakesIntCaller)->call(); // should cause an error
(new WeakTakesIntCaller)->call(); // should succeed
(new ExplicitWeakTakesIntCaller)->call(); // should succeed
}
declare(strict_types=1) {
strict_calls_takes_int(); // should cause an error
weak_calls_takes_int(); // should succeed: our call to func is strict, but it was declared in weak mode so calls it makes are weak
explicit_weak_calls_takes_int(); // should succeed
(new StrictTakesIntCaller)->call(); // should cause an error
(new WeakTakesIntCaller)->call(); // should succeed
(new ExplicitWeakTakesIntCaller)->call(); // should succeed
}
?>
--EXPECTF--
Catchable fatal error: Argument 1 passed to takes_int() must be of the type integer, float given, called in %sstrict_nested.php on line 26 and defined in %sstrict_nested.php on line 14
Failure!
Success!
Success!
Catchable fatal error: Argument 1 passed to takes_int() must be of the type integer, float given, called in %sstrict_nested.php on line 31 and defined in %sstrict_nested.php on line 14
Failure!
Success!
Success!
Catchable fatal error: Argument 1 passed to takes_int() must be of the type integer, float given, called in %sstrict_nested.php on line 26 and defined in %sstrict_nested.php on line 14
Failure!
Success!
Success!
Catchable fatal error: Argument 1 passed to takes_int() must be of the type integer, float given, called in %sstrict_nested.php on line 31 and defined in %sstrict_nested.php on line 14
Failure!
Success!
Success!
Catchable fatal error: Argument 1 passed to takes_int() must be of the type integer, float given, called in %sstrict_nested.php on line 26 and defined in %sstrict_nested.php on line 14
Failure!
Success!
Success!
Catchable fatal error: Argument 1 passed to takes_int() must be of the type integer, float given, called in %sstrict_nested.php on line 31 and defined in %sstrict_nested.php on line 14
Failure!
Success!
Success!
Fatal error: strict_types declaration must be the very first statement in the script in %s on line %d

View File

@ -1145,7 +1145,7 @@ static zend_always_inline int zend_parse_arg_double(zval *arg, double *dest, zen
*is_null = 0;
}
if (UNEXPECTED(strict && Z_TYPE_P(arg) != IS_DOUBLE && !(check_null && Z_TYPE_P(arg) == IS_NULL))) {
if (UNEXPECTED(strict && Z_TYPE_P(arg) != IS_DOUBLE && Z_TYPE_P(arg) != IS_LONG && !(check_null && Z_TYPE_P(arg) == IS_NULL))) {
return 0;
}

View File

@ -3870,7 +3870,22 @@ void zend_compile_declare(zend_ast *ast) /* {{{ */
}
} else if (zend_string_equals_literal_ci(name, "strict_types")) {
zval value_zv;
zend_const_expr_to_zval(&value_zv, value_ast);
/* Strict Hint declaration was already handled during parsing. Here we
* only check that it is the first statement in the file. */
uint32_t num = CG(active_op_array)->last;
while (num > 0 &&
(CG(active_op_array)->opcodes[num-1].opcode == ZEND_EXT_STMT ||
CG(active_op_array)->opcodes[num-1].opcode == ZEND_TICKS)) {
--num;
}
if (num > 0) {
zend_error_noreturn(E_COMPILE_ERROR, "strict_types declaration must be "
"the very first statement in the script");
}
zend_const_expr_to_zval(&value_zv, value_ast);
if (Z_TYPE(value_zv) != IS_LONG || (Z_LVAL(value_zv) != 0 && Z_LVAL(value_zv) != 1)) {
zend_error_noreturn(E_COMPILE_ERROR, "strict_types declaration must have 0 or 1 as its value");