Skip to content

Commit 6c18b30

Browse files
committed
RFC examples
1 parent 3e13275 commit 6c18b30

10 files changed

+453
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
--TEST--
2+
Partial application RFC examples: equivalence
3+
--FILE--
4+
<?php
5+
6+
function stuff(int $i, string $s, float $f, Point $p, int $m = 0): array {
7+
return [$i, $s, $f, $p, $m];
8+
}
9+
10+
class Point {
11+
}
12+
13+
$point = new Point();
14+
15+
$tests = [
16+
'Manually specify the first two values, and pull the rest "as is"' => [
17+
stuff(?, ?, ?, ?, ?),
18+
fn(int $i, string $s, float $f, Point $p, int $m = 0): array => stuff($i, $s, $f, $p, $m),
19+
],
20+
'Manually specify the first two values, and pull the rest "as is" (2)' => [
21+
stuff(?, ?, ...),
22+
fn(int $i, string $s, float $f, Point $p, int $m = 0): array => stuff($i, $s, $f, $p, $m),
23+
],
24+
'The degenerate "first class callables" case. (Supported since 8.1)' => [
25+
stuff(...),
26+
fn(int $i, string $s, float $f, Point $p, int $m = 0): array => stuff($i, $s, $f, $p, $m),
27+
],
28+
'Provide some values, require the rest to be provided later' => [
29+
stuff(1, 'hi', ?, ?, ?),
30+
fn(float $f, Point $p, int $m = 0): array => stuff(1, 'hi', $f, $p, $m),
31+
],
32+
'Provide some values, require the rest to be provided later (2)' => [
33+
stuff(1, 'hi', ...),
34+
fn(float $f, Point $p, int $m = 0): array => stuff(1, 'hi', $f, $p, $m),
35+
],
36+
'Provided some values, but not just from the left' => [
37+
stuff(1, ?, 3.5, ?, ?),
38+
fn(string $s, Point $p, int $m = 0): array => stuff(1, $s, 3.5, $p, $m),
39+
],
40+
'Provided some values, but not just from the left (2)' => [
41+
stuff(1, ?, 3.5, ...),
42+
fn(string $s, Point $p, int $m = 0): array => stuff(1, $s, 3.5, $p, $m),
43+
],
44+
'Provide just the last value' => [
45+
stuff(?, ?, ?, ?, 5),
46+
fn(int $i, string $s, float $f, Point $p): array => stuff($i, $s, $f, $p, 5),
47+
],
48+
'Not accounting for an optional argument means it will always get its default value' => [
49+
stuff(?, ?, ?, ?),
50+
fn(int $i, string $s, float $f, Point $p): array => stuff($i, $s, $f, $p),
51+
],
52+
'Named arguments can be pulled "out of order", and still work' => [
53+
stuff(?, ?, f: 3.5, p: $point),
54+
fn(int $i, string $s): array => stuff($i, $s, 3.5, $point),
55+
],
56+
'Named arguments can be pulled "out of order", and still work (2)' => [
57+
stuff(?, ?, p: $point, f: 3.5),
58+
fn(int $i, string $s): array => stuff($i, $s, 3.5, $point),
59+
],
60+
'The ... "everything else" placeholder respects named arguments' => [
61+
stuff(?, ?, ..., f: 3.5, p: $point),
62+
fn(int $i, string $s, int $m = 0): array => stuff($i, $s, 3.5, $point, $m),
63+
],
64+
'Prefill all parameters, making a "delayed call" or "thunk"' => [
65+
stuff(1, 'hi', 3.4, $point, 5, ...),
66+
fn(): array => stuff(1, 'hi', 3.4, $point, 5),
67+
],
68+
'Placeholders may be named, too. Their order doesn\'t matter as long as they come after the ..., if any' => [
69+
stuff(?, p: $point, f: ?, s: ?, m: 4),
70+
fn(int $i, string $s, float $f): array => stuff($i, $s, $f, $point, 4),
71+
],
72+
'Placeholders may be named, too. Their order doesn\'t matter as long as they come after the ..., if any (2)' => [
73+
stuff(..., m: 4, p: $point, i: ?),
74+
fn(int $i, string $s, float $f): array => stuff($i, $s, $f, $point, 4),
75+
],
76+
];
77+
78+
foreach ($tests as $test => [$pfa, $closure]) {
79+
echo "# ", $test, "\n";
80+
$pfaReflector = new ReflectionFunction($pfa);
81+
$closureReflector = new ReflectionFunction($closure);
82+
83+
try {
84+
if (count($pfaReflector->getParameters()) !== count($closureReflector->getParameters())) {
85+
throw new Exception("Arity does not match");
86+
}
87+
88+
$it = new MultipleIterator();
89+
$it->attachIterator(new ArrayIterator($pfaReflector->getParameters()));
90+
$it->attachIterator(new ArrayIterator($closureReflector->getParameters()));
91+
foreach ($it as $i => [$pfaParam, $closureParam]) {
92+
[$i] = $i;
93+
if ($pfaParam->getName() !== $closureParam->getName()) {
94+
throw new Exception(sprintf("Name of param %d does not match: %s vs %s",
95+
$i,
96+
$pfaParam->getName(),
97+
$closureParam->getName(),
98+
));
99+
}
100+
if ((string)$pfaParam->getType() !== (string)$closureParam->getType()) {
101+
throw new Exception(sprintf("Type of param %d does not match: %s vs %s",
102+
$i,
103+
$pfaParam->getType(),
104+
$closureParam->getType(),
105+
));
106+
}
107+
if ($pfaParam->isOptional() !== $closureParam->isOptional()) {
108+
throw new Exception(sprintf("Optionalness of param %d does not match: %d vs %d",
109+
$i,
110+
$pfaParam->isOptional(),
111+
$closureParam->isOptional(),
112+
));
113+
}
114+
}
115+
} catch (Exception $e) {
116+
echo $e->getMessage(), "\n";
117+
echo $pfaReflector;
118+
echo $closureReflector;
119+
}
120+
121+
$args = [];
122+
foreach ($pfaReflector->getParameters() as $i => $p) {
123+
$args[] = match ((string) $p->getType()) {
124+
'int' => 100 + $i,
125+
'float' => 100.5 + $i,
126+
'string' => (string) (100 + $i),
127+
'Point' => new Point,
128+
};
129+
}
130+
131+
if ($pfa(...$args) !== $closure(...$args)) {
132+
echo "PFA is not equivalent to closure\n";
133+
}
134+
}
135+
--EXPECT--
136+
# Manually specify the first two values, and pull the rest "as is"
137+
# Manually specify the first two values, and pull the rest "as is" (2)
138+
# The degenerate "first class callables" case. (Supported since 8.1)
139+
# Provide some values, require the rest to be provided later
140+
# Provide some values, require the rest to be provided later (2)
141+
# Provided some values, but not just from the left
142+
# Provided some values, but not just from the left (2)
143+
# Provide just the last value
144+
# Not accounting for an optional argument means it will always get its default value
145+
# Named arguments can be pulled "out of order", and still work
146+
# Named arguments can be pulled "out of order", and still work (2)
147+
# The ... "everything else" placeholder respects named arguments
148+
# Prefill all parameters, making a "delayed call" or "thunk"
149+
# Placeholders may be named, too. Their order doesn't matter as long as they come after the ..., if any
150+
# Placeholders may be named, too. Their order doesn't matter as long as they come after the ..., if any (2)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
--TEST--
2+
Partial application RFC examples: variadics equivalence
3+
--XFAIL--
4+
Name of params for positional placeholders that run into the variadic portion is wrong
5+
--FILE--
6+
<?php
7+
8+
function things(int $i, ?float $f = null, Point ...$points): array {
9+
return [$i, $f, $points];
10+
}
11+
12+
class Point {
13+
}
14+
15+
$point = new Point();
16+
17+
$tests = [
18+
'FCC equivalent. The signature is unchanged' => [
19+
things(...),
20+
fn(int $i, ?float $f = null, Point ...$points): array => things(...[$i, $f, ...$points]),
21+
],
22+
'Provide some values, but allow the variadic to remain variadic' => [
23+
things(1, 3.14, ...),
24+
fn(Point ...$points): array => things(...[1, 3.14, ...$points]),
25+
],
26+
'In this version, the partial requires precisely four arguments, the last two of which will get received by things() in the variadic parameter. Note too that $f becomes required in this case.' => [
27+
things(?, ?, ?, ?),
28+
fn(int $i, ?float $f, Point $p1, Point $p2): array => things($i, $f, $p1, $p2),
29+
],
30+
];
31+
32+
foreach ($tests as $test => [$pfa, $closure]) {
33+
echo "# ", $test, "\n";
34+
$pfaReflector = new ReflectionFunction($pfa);
35+
$closureReflector = new ReflectionFunction($closure);
36+
37+
try {
38+
if (count($pfaReflector->getParameters()) !== count($closureReflector->getParameters())) {
39+
throw new Exception("Arity does not match");
40+
}
41+
42+
$it = new MultipleIterator();
43+
$it->attachIterator(new ArrayIterator($pfaReflector->getParameters()));
44+
$it->attachIterator(new ArrayIterator($closureReflector->getParameters()));
45+
foreach ($it as $i => [$pfaParam, $closureParam]) {
46+
[$i] = $i;
47+
if ($pfaParam->getName() !== $closureParam->getName()) {
48+
throw new Exception(sprintf("Name of param %d does not match: %s vs %s",
49+
$i,
50+
$pfaParam->getName(),
51+
$closureParam->getName(),
52+
));
53+
}
54+
if ((string)$pfaParam->getType() !== (string)$closureParam->getType()) {
55+
throw new Exception(sprintf("Type of param %d does not match: %s vs %s",
56+
$i,
57+
$pfaParam->getType(),
58+
$closureParam->getType(),
59+
));
60+
}
61+
if ($pfaParam->isOptional() !== $closureParam->isOptional()) {
62+
throw new Exception(sprintf("Optionalness of param %d does not match: %d vs %d",
63+
$i,
64+
$pfaParam->isOptional(),
65+
$closureParam->isOptional(),
66+
));
67+
}
68+
}
69+
} catch (Exception $e) {
70+
echo $e->getMessage(), "\n";
71+
echo $pfaReflector;
72+
echo $closureReflector;
73+
}
74+
75+
$args = [];
76+
foreach ($pfaReflector->getParameters() as $i => $p) {
77+
$args[] = match ((string) $p->getType()) {
78+
'int' => 100 + $i,
79+
'float' => 100.5 + $i,
80+
'?float' => 100.5 + $i,
81+
'string' => (string) (100 + $i),
82+
'Point' => new Point,
83+
};
84+
}
85+
86+
if ($pfa(...$args) !== $closure(...$args)) {
87+
echo "PFA is not equivalent to closure\n";
88+
}
89+
}
90+
--EXPECTF--
91+
# FCC equivalent. The signature is unchanged
92+
# Provide some values, but allow the variadic to remain variadic
93+
# In this version, the partial requires precisely four arguments, the last two of which will get received by things() in the variadic parameter. Note too that $f becomes required in this case.
94+
Name of param 2 does not match: points vs p1
95+
Partial [ <user> function things ] {
96+
@@ %srfc_examples_002.php 22 - 22
97+
98+
- Parameters [4] {
99+
Parameter #0 [ <required> int $i ]
100+
Parameter #1 [ <required> ?float $f ]
101+
Parameter #2 [ <required> Point $points ]
102+
Parameter #3 [ <required> Point $points ]
103+
}
104+
- Return [ array ]
105+
}
106+
Closure [ <user> function {closure:%s:%d} ] {
107+
@@ %srfc_examples_002.php 23 - 23
108+
109+
- Parameters [4] {
110+
Parameter #0 [ <required> int $i ]
111+
Parameter #1 [ <required> ?float $f ]
112+
Parameter #2 [ <required> Point $p1 ]
113+
Parameter #3 [ <required> Point $p2 ]
114+
}
115+
- Return [ array ]
116+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
Partial application RFC examples: errors
3+
--FILE--
4+
<?php
5+
6+
if (time() > 0) {
7+
function stuff(int $i, string $s, float $f, Point $p, int $m = 0) {}
8+
}
9+
10+
try {
11+
stuff(?);
12+
} catch (Error $e) {
13+
echo $e->getMessage(), "\n";
14+
}
15+
16+
try {
17+
stuff(?, ?, ?, ?, ?, ?);
18+
} catch (Error $e) {
19+
echo $e->getMessage(), "\n";
20+
}
21+
22+
try {
23+
stuff(?, ?, 3.5, null, i: 5);
24+
} catch (Error $e) {
25+
echo $e->getMessage(), "\n";
26+
}
27+
28+
--EXPECTF--
29+
not enough arguments or placeholders for application of stuff, 1 given and at least 4 expected, declared in %s on line %d
30+
too many arguments or placeholders for application of stuff, 6 given and a maximum of 5 expected, declared in %s on line %d
31+
Named parameter $i overwrites previous placeholder
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Partial application RFC examples: errors
3+
--FILE--
4+
<?php
5+
6+
if (time() > 0) {
7+
function stuff(int $i, string $s, float $f, Point $p, int $m = 0) {}
8+
}
9+
10+
stuff(i:1, ?, ?, ?, ?);
11+
12+
--EXPECTF--
13+
Fatal error: Cannot use positional argument after named argument in %s on line %d
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Partial application RFC examples: errors
3+
--FILE--
4+
<?php
5+
6+
if (time() > 0) {
7+
function stuff(int $i, string $s, float $f, Point $p, int $m = 0) {}
8+
}
9+
10+
stuff(?, ?, ?, p: $point, ?);
11+
12+
--EXPECTF--
13+
Fatal error: Cannot use positional argument after named argument in %s on line %d
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Partial application RFC examples: func_get_args()
3+
--FILE--
4+
<?php
5+
6+
function f($a = 0, $b = 0, $c = 3, $d = 4) {
7+
echo func_num_args() . PHP_EOL;
8+
9+
var_dump($a, $b, $c, $d);
10+
}
11+
12+
f(1, 2);
13+
14+
$f = f(?, ?);
15+
16+
$f(1, 2);
17+
18+
--EXPECT--
19+
2
20+
int(1)
21+
int(2)
22+
int(3)
23+
int(4)
24+
2
25+
int(1)
26+
int(2)
27+
int(3)
28+
int(4)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Partial application RFC examples: evaluation order
3+
--FILE--
4+
<?php
5+
6+
function getArg() {
7+
print __FUNCTION__ . PHP_EOL;
8+
return 'hi';
9+
}
10+
11+
function speak(string $who, string $msg) {
12+
printf("%s: %s\n", $who, $msg);
13+
}
14+
15+
$arrow = fn($who) => speak($who, getArg());
16+
print "Arnaud\n";
17+
$arrow('Larry');
18+
19+
$partial = speak(?, getArg());
20+
print "Arnaud\n";
21+
$partial('Larry');
22+
23+
--EXPECT--
24+
Arnaud
25+
getArg
26+
Larry: hi
27+
getArg
28+
Arnaud
29+
Larry: hi

0 commit comments

Comments
 (0)