|
| 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) |
0 commit comments