Skip to content

Commit

Permalink
Implement typed properties
Browse files Browse the repository at this point in the history
RFC: https://wiki.php.net/rfc/typed_properties_v2

This is a squash of PR #3734, which is a squash of PR #3313.

Co-authored-by: Bob Weinand <bobwei9@hotmail.com>
Co-authored-by: Joe Watkins <krakjoe@php.net>
Co-authored-by: Dmitry Stogov <dmitry@zend.com>
  • Loading branch information
4 people committed Jan 11, 2019
1 parent fe8fdfa commit e219ec1
Show file tree
Hide file tree
Showing 210 changed files with 23,045 additions and 5,961 deletions.
14 changes: 13 additions & 1 deletion UPGRADING
Expand Up @@ -78,8 +78,20 @@ PHP 7.4 UPGRADE NOTES
2. New Features
========================================

- Core:
. Added support for typed properties. For example:

class User {
public int $id;
public string $name;
}

This will enforce that $user->id can only be assigned integer and
$user->name can only be assigned strings. For more information see the
RFC: https://wiki.php.net/rfc/typed_properties_v2

- PDO_OCI:
. PDOStatement::getColumnMeta is now available
. PDOStatement::getColumnMeta() is now available

- PDO_SQLite:
. PDOStatement::getAttribute(PDO::SQLITE_ATTR_READONLY_STATEMENT) allows to
Expand Down
13 changes: 13 additions & 0 deletions UPGRADING.INTERNALS
Expand Up @@ -13,6 +13,8 @@ PHP 7.4 INTERNALS UPGRADE NOTES
j. Removed add_get_assoc_*() and add_get_index_*()
k. Class declaration opcodes
l. HASH_FLAG_INITIALIZED
m. write_property return value
n. Assignments to references

2. Build system changes
a. Abstract
Expand Down Expand Up @@ -158,6 +160,17 @@ PHP 7.4 INTERNALS UPGRADE NOTES
Special HT_IS_INITIALIZED() and HT_INVALIDATE() macro were introduced
to hide implementation details.

m. The write_property() object handler now returns the assigned value (after
possible type coercions) rather than void. For extensions, it should
usually be sufficient to return whatever was passed as the argument.

n. Assignments to references now need to ensure that they respect property
types that affect the reference. This means that references should no
longer be directly assigned to, and instead a set of specialized macros
of the form ZEND_TRY_ASSIGN* needs to be used. You can find detailed
porting instructions as well as a compatibility shim in the wiki:
https://wiki.php.net/rfc/typed_properties_v2#assignments_to_references

========================
2. Build system changes
========================
Expand Down
44 changes: 44 additions & 0 deletions Zend/tests/type_declarations/typed_properties_001.phpt
@@ -0,0 +1,44 @@
--TEST--
Test typed properties basic operation
--FILE--
<?php
var_dump(new class(1, 2.2, true, ["four"], new stdClass) {
public int $int;
public float $float;
public bool $bool;
public array $array;
public stdClass $std;
public iterable $it;

public function __construct(int $int, float $float, bool $bool, array $array, stdClass $std) {
$this->int = $int;
$this->float = $float;
$this->bool = $bool;
$this->array = $array;
$this->std = $std;
$this->it = $array;
}
});
?>
--EXPECTF--
object(class@anonymous)#%d (6) {
["int"]=>
int(1)
["float"]=>
float(2.2)
["bool"]=>
bool(true)
["array"]=>
array(1) {
[0]=>
string(4) "four"
}
["std"]=>
object(stdClass)#%d (0) {
}
["it"]=>
array(1) {
[0]=>
string(4) "four"
}
}
15 changes: 15 additions & 0 deletions Zend/tests/type_declarations/typed_properties_002.phpt
@@ -0,0 +1,15 @@
--TEST--
Test typed properties error condition (read uninitialized)
--FILE--
<?php
$thing = new class() {
public int $int;
};

var_dump($thing->int);
?>
--EXPECTF--
Fatal error: Uncaught Error: Typed property class@anonymous::$int must not be accessed before initialization in %s:6
Stack trace:
#0 {main}
thrown in %s on line 6
15 changes: 15 additions & 0 deletions Zend/tests/type_declarations/typed_properties_003.phpt
@@ -0,0 +1,15 @@
--TEST--
Test typed properties error condition (fetch uninitialized by reference)
--FILE--
<?php
$thing = new class() {
public int $int;
};

$var = &$thing->int;
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot access uninitialized non-nullable property class@anonymous::$int by reference in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
18 changes: 18 additions & 0 deletions Zend/tests/type_declarations/typed_properties_004.phpt
@@ -0,0 +1,18 @@
--TEST--
Test typed properties error condition (type mismatch)
--FILE--
<?php
new class("PHP 7 is better than you, and it knows it ...") {
public int $int;

public function __construct(string $string) {
$this->int = $string;
}
};
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Typed property class@anonymous::$int must be int, string used in %s:6
Stack trace:
#0 %s(2): class@anonymous->__construct('PHP 7 is better...')
#1 {main}
thrown in %s on line 6
20 changes: 20 additions & 0 deletions Zend/tests/type_declarations/typed_properties_005.phpt
@@ -0,0 +1,20 @@
--TEST--
Test typed properties error condition (type mismatch object)
--FILE--
<?php
class Dummy {}

new class(new Dummy) {
public stdClass $std;

public function __construct(Dummy $dummy) {
$this->std = $dummy;
}
};
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Typed property class@anonymous::$std must be an instance of stdClass, Dummy used in %s:8
Stack trace:
#0 %s(4): class@anonymous->__construct(Object(Dummy))
#1 {main}
thrown in %s on line 8
14 changes: 14 additions & 0 deletions Zend/tests/type_declarations/typed_properties_006.phpt
@@ -0,0 +1,14 @@
--TEST--
Test typed properties inheritance (scalar)
--FILE--
<?php
class Foo {
public int $qux;
}

class Bar extends Foo {
public string $qux;
}
?>
--EXPECTF--
Fatal error: Type of Bar::$qux must be int (as in class Foo) in %s on line 8
17 changes: 17 additions & 0 deletions Zend/tests/type_declarations/typed_properties_007.phpt
@@ -0,0 +1,17 @@
--TEST--
Test typed properties inheritance
--FILE--
<?php
class Whatever {}
class Thing extends Whatever {}

class Foo {
public Whatever $qux;
}

class Bar extends Foo {
public Thing $qux;
}
?>
--EXPECTF--
Fatal error: Type of Bar::$qux must be Whatever (as in class Foo) in %s on line 11
14 changes: 14 additions & 0 deletions Zend/tests/type_declarations/typed_properties_008.phpt
@@ -0,0 +1,14 @@
--TEST--
Test typed properties inheritance (missing info)
--FILE--
<?php
class Foo {
public int $qux;
}

class Bar extends Foo {
public $qux;
}
?>
--EXPECTF--
Fatal error: Type of Bar::$qux must be int (as in class Foo) in %s on line 8
23 changes: 23 additions & 0 deletions Zend/tests/type_declarations/typed_properties_009.phpt
@@ -0,0 +1,23 @@
--TEST--
Test typed properties unset leaves properties in an uninitialized state
--FILE--
<?php
class Foo {
public int $bar;

public function __get($name) {
var_dump($name);
/* return value has to be compatible with int */
return 0;
}
}

$foo = new Foo();

unset($foo->bar);

var_dump($foo->bar);
?>
--EXPECT--
string(3) "bar"
int(0)
17 changes: 17 additions & 0 deletions Zend/tests/type_declarations/typed_properties_010.phpt
@@ -0,0 +1,17 @@
--TEST--
Test typed properties allow fetch reference
--FILE--
<?php
class Foo {
public int $bar = 1;
}

$cb = function(int &$bar) {
var_dump($bar);
};

$foo = new Foo();
$cb($foo->bar);
?>
--EXPECT--
int(1)
18 changes: 18 additions & 0 deletions Zend/tests/type_declarations/typed_properties_011.phpt
@@ -0,0 +1,18 @@
--TEST--
Test typed properties allow fetch reference for init array
--FILE--
<?php
class Foo {
public int $bar = 1;
}

$foo = new Foo();

$array = [&$foo->bar];
var_dump($array);
?>
--EXPECT--
array(1) {
[0]=>
&int(1)
}
19 changes: 19 additions & 0 deletions Zend/tests/type_declarations/typed_properties_012.phpt
@@ -0,0 +1,19 @@
--TEST--
Test typed properties allow fetch reference for foreach
--FILE--
<?php
class Foo {
public int $bar = 1;
}

$foo = new Foo();
foreach ($foo as &$prop) {
$prop++;
}
var_dump($foo);
?>
--EXPECT--
object(Foo)#1 (1) {
["bar"]=>
&int(2)
}
10 changes: 10 additions & 0 deletions Zend/tests/type_declarations/typed_properties_013.phpt
@@ -0,0 +1,10 @@
--TEST--
Test typed properties disallow incorrect type initial value (scalar)
--FILE--
<?php
class Foo {
public int $bar = "string";
}
?>
--EXPECTF--
Fatal error: Default value for property of type int can only be int in %s on line 3
10 changes: 10 additions & 0 deletions Zend/tests/type_declarations/typed_properties_014.phpt
@@ -0,0 +1,10 @@
--TEST--
Test typed properties disallow incorrect type initial value (array)
--FILE--
<?php
class Foo {
public array $bar = 32;
}
?>
--EXPECTF--
Fatal error: Default value for property of type array can only be an array in %s on line 3
10 changes: 10 additions & 0 deletions Zend/tests/type_declarations/typed_properties_015.phpt
@@ -0,0 +1,10 @@
--TEST--
Test typed properties disallow incorrect type initial value (object)
--FILE--
<?php
class Foo {
public stdClass $bar = null;
}
?>
--EXPECTF--
Fatal error: Default value for property of type stdClass may not be null. Use the nullable type ?stdClass to allow null default value in %s on line %d
16 changes: 16 additions & 0 deletions Zend/tests/type_declarations/typed_properties_016.phpt
@@ -0,0 +1,16 @@
--TEST--
Test typed properties initial values
--FILE--
<?php
class Foo {
public int $int = 1;
public float $flt = 2.2;
public float $flt2 = 2;
public array $arr = [];
public bool $bool = false;
public iterable $iter = [];
}
echo "ok\n";
?>
--EXPECT--
ok
12 changes: 12 additions & 0 deletions Zend/tests/type_declarations/typed_properties_017.phpt
@@ -0,0 +1,12 @@
--TEST--
Test typed properties disallow void
--FILE--
<?php
class Foo {
public void $int;
}

$foo = new Foo();
?>
--EXPECTF--
Fatal error: Property Foo::$int cannot have type void in %s on line 3
17 changes: 17 additions & 0 deletions Zend/tests/type_declarations/typed_properties_018.phpt
@@ -0,0 +1,17 @@
--TEST--
Test typed properties type applies to all props in group
--FILE--
<?php
class Foo {
public int $bar,
$qux;
}

$reflector = new ReflectionClass(Foo::class);

$prop = $reflector->getProperty("qux");

var_dump((string) $prop->getType());
?>
--EXPECT--
string(3) "int"
22 changes: 22 additions & 0 deletions Zend/tests/type_declarations/typed_properties_019.phpt
@@ -0,0 +1,22 @@
--TEST--
Test typed properties int must not be allowed to overflow
--FILE--
<?php
class Foo {
public int $bar = PHP_INT_MAX;

public function inc() {
return ++$this->bar;
}
}

$foo = new Foo();

try {
$foo->inc();
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Cannot increment property Foo::$bar of type int past its maximal value

0 comments on commit e219ec1

Please sign in to comment.