Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Zend/tests/primary_ctor/attributes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Primary constructors: attributes apply to promoted parameters/properties
--FILE--
<?php
#[Attribute(Attribute::TARGET_PARAMETER | Attribute::TARGET_PROPERTY)]
class Col {
public function __construct(public string $name) {}
}

/** @param int $id */
class Row(
#[Col('id')] public int $id,
) {}

$class = new ReflectionClass(Row::class);
var_dump($class->getDocComment());
var_dump($class->getConstructor()->getDocComment());

$param = $class->getConstructor()->getParameters()[0];
var_dump($param->getAttributes()[0]->getName());
var_dump($param->getAttributes()[0]->newInstance()->name);

$prop = (new ReflectionProperty(Row::class, 'id'))->getAttributes()[0];
var_dump($prop->newInstance()->name);
?>
--EXPECT--
string(21) "/** @param int $id */"
string(21) "/** @param int $id */"
string(3) "Col"
string(2) "id"
string(2) "id"
25 changes: 25 additions & 0 deletions Zend/tests/primary_ctor/basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Primary constructors: basic promotion, defaults, named args
--FILE--
<?php
class Point(public int $x, public int $y = 0) {
public function sum(): int { return $this->x + $this->y; }
}

$p = new Point(3);
var_dump($p->x, $p->y, $p->sum());

$p2 = new Point(x: 10, y: 20);
var_dump($p2->sum());

$r = new ReflectionMethod(Point::class, '__construct');
var_dump($r->getNumberOfParameters());
var_dump($r->isPublic());
?>
--EXPECT--
int(3)
int(0)
int(3)
int(30)
int(2)
bool(true)
36 changes: 36 additions & 0 deletions Zend/tests/primary_ctor/docblock.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
Primary constructors: a class doc comment also applies to the synthesized constructor
--FILE--
<?php
/** Doc for C */
class C(public int $x) {}

$rc = new ReflectionClass(C::class);
var_dump($rc->getDocComment());
var_dump($rc->getConstructor()->getDocComment());

class D(public int $y) {}

$rd = new ReflectionClass(D::class);
var_dump($rd->getDocComment());
var_dump($rd->getConstructor()->getDocComment());

class E(
/** Doc for z */ public int $z,
) {}

$re = new ReflectionClass(E::class);
var_dump($re->getDocComment());
var_dump($re->getConstructor()->getDocComment());
var_dump($re->getProperty('z')->getDocComment());
?>
--EXPECT--
string(16) "/** Doc for C */"
string(16) "/** Doc for C */"
bool(false)
bool(false)
bool(false)
bool(false)
string(16) "/** Doc for z */"
--CREDITS--
Robert Landers
9 changes: 9 additions & 0 deletions Zend/tests/primary_ctor/error_anon_parent_args.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Primary constructors: anonymous classes cannot forward arguments to the parent
--FILE--
<?php
class Base { public function __construct(public int $id) {} }
$o = new class extends Base(5) {};
?>
--EXPECTF--
Fatal error: Cannot pass arguments to the parent constructor without a primary constructor in %s on line %d
18 changes: 18 additions & 0 deletions Zend/tests/primary_ctor/error_enum_trait.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
Primary constructors: not allowed on enums or traits
--FILE--
<?php
function tryCompile(string $code): void {
try {
eval($code);
} catch (\ParseError $e) {
echo $e->getMessage(), "\n";
}
}

tryCompile('enum E(public int $x) { case A; }');
tryCompile('trait T(public int $x) {}');
?>
--EXPECT--
syntax error, unexpected token "(", expecting "{"
syntax error, unexpected token "(", expecting "{"
8 changes: 8 additions & 0 deletions Zend/tests/primary_ctor/error_interface.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
Primary constructors: not allowed on interfaces
--FILE--
<?php
interface I(public int $x) {}
?>
--EXPECTF--
Parse error: syntax error, unexpected token "(", expecting "{" in %s on line %d
9 changes: 9 additions & 0 deletions Zend/tests/primary_ctor/error_parent_args_no_primary.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Primary constructors: parent constructor arguments require a primary constructor
--FILE--
<?php
class Base { public function __construct(public int $id) {} }
class Sub extends Base(5) {}
?>
--EXPECTF--
Fatal error: Cannot pass arguments to the parent constructor without a primary constructor in %s on line %d
9 changes: 9 additions & 0 deletions Zend/tests/primary_ctor/error_parent_empty_no_primary.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Primary constructors: parent constructor call requires a primary constructor
--FILE--
<?php
class Base { public function __construct() {} }
class Sub extends Base() {}
?>
--EXPECTF--
Fatal error: Cannot call the parent constructor without a primary constructor in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/primary_ctor/error_readonly_hook.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Primary constructors: hooked promoted properties cannot be readonly
--FILE--
<?php
readonly class Temperature(
public float $celsius {
set { $this->celsius = $value; }
}
) {}
?>
--EXPECTF--
Fatal error: Hooked properties cannot be readonly in %s on line %d
10 changes: 10 additions & 0 deletions Zend/tests/primary_ctor/error_redeclare_construct.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Primary constructors: cannot also declare an explicit __construct()
--FILE--
<?php
class Bad(public int $x) {
public function __construct(public int $x) {}
}
?>
--EXPECTF--
Fatal error: Cannot redeclare Bad::__construct() in %s on line %d
24 changes: 24 additions & 0 deletions Zend/tests/primary_ctor/hook_cross_parameter_order_edge.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
Primary constructors: promoted property hooks cannot read later promoted properties before initialization
--FILE--
<?php
class Range(
public int $start {
set {
var_dump(isset($this->end));
var_dump($this->end);
$this->start = $value;
}
},
public int $end,
) {}

try {
new Range(1, 3);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
bool(false)
Typed property Range::$end must not be accessed before initialization
33 changes: 33 additions & 0 deletions Zend/tests/primary_ctor/hook_cross_parameter_validation.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
Primary constructors: later promoted property hooks can validate against earlier promoted properties
--FILE--
<?php
class Range(
public int $start,
public int $end {
set {
if ($this->start > $value) {
throw new ValueError('start must precede end');
}
$this->end = $value;
}
}
) {}

$ok = new Range(1, 3);
var_dump($ok);

try {
new Range(3, 1);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
object(Range)#1 (2) {
["start"]=>
int(1)
["end"]=>
int(3)
}
start must precede end
38 changes: 38 additions & 0 deletions Zend/tests/primary_ctor/hook_derived_state.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
Primary constructors: a later promoted property hook can initialize derived state
--FILE--
<?php
class Range(
public int $start,
public int $end {
set {
if ($this->start > $value) {
throw new ValueError('start must precede end');
}
$this->end = $value;
$this->length = $value - $this->start;
}
}
) {
public readonly int $length;
}

$range = new Range(2, 5);
var_dump($range);

try {
$range->length = 99;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
object(Range)#1 (3) {
["start"]=>
int(2)
["end"]=>
int(5)
["length"]=>
int(3)
}
Cannot modify readonly property Range::$length
26 changes: 26 additions & 0 deletions Zend/tests/primary_ctor/hook_validation.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Primary constructors: a set hook on a promoted parameter validates during construction
--FILE--
<?php
class Temperature(
public float $celsius {
set {
if ($value < -273.15) {
throw new ValueError('below absolute zero');
}
$this->celsius = $value;
}
}
) {}

var_dump((new Temperature(20.0))->celsius);

try {
new Temperature(-300.0);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
float(20)
below absolute zero
23 changes: 23 additions & 0 deletions Zend/tests/primary_ctor/parent_call_variants.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Primary constructors: extends Base() calls parent, extends Base (no parens) does not
--FILE--
<?php
class WithCtor {
public int $v = 0;
public function __construct() { echo "WithCtor::__construct\n"; $this->v = 7; }
}

class A(public int $x) extends WithCtor() {}
$a = new A(1);
var_dump($a->x, $a->v);

class B(public int $x) extends WithCtor {}
$b = new B(2);
var_dump($b->x, $b->v);
?>
--EXPECT--
WithCtor::__construct
int(1)
int(7)
int(2)
int(0)
31 changes: 31 additions & 0 deletions Zend/tests/primary_ctor/parent_forwarding.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Primary constructors: forward arguments to the parent constructor via extends
--FILE--
<?php
abstract class Shape(protected string $name) {
abstract public function area(): float;
public function name(): string { return $this->name; }
}

final class Circle(
public readonly float $radius,
string $name = 'circle',
) extends Shape($name) {
public function area(): float { return $this->radius ** 2 * 3; }
}

$c = new Circle(2.0);
printf("%s %.1f\n", $c->name(), $c->area());

$c2 = new Circle(1.0, 'unit');
echo $c2->name(), "\n";

$rp = (new ReflectionMethod(Circle::class, '__construct'))->getParameters();
printf("radius promoted=%s, name promoted=%s\n",
$rp[0]->isPromoted() ? 'yes' : 'no',
$rp[1]->isPromoted() ? 'yes' : 'no');
?>
--EXPECT--
circle 12.0
unit
radius promoted=yes, name promoted=no
31 changes: 31 additions & 0 deletions Zend/tests/primary_ctor/promotion_and_bare.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Primary constructors: explicit modifier promotes, bare parameter does not
--FILE--
<?php
class Foo(
int $x,
public int $y,
private int $z = 9,
) {
public function z(): int { return $this->z; }
}

$f = new Foo(1, 2);
var_dump(property_exists($f, 'x'));
var_dump(property_exists($f, 'y'));
var_dump(property_exists($f, 'z'));
var_dump($f->y, $f->z());

foreach ((new ReflectionMethod(Foo::class, '__construct'))->getParameters() as $p) {
printf("%s promoted=%s\n", $p->getName(), $p->isPromoted() ? 'yes' : 'no');
}
?>
--EXPECT--
bool(false)
bool(true)
bool(true)
int(2)
int(9)
x promoted=no
y promoted=yes
z promoted=yes
Loading
Loading