Skip to content

Commit 7e7f05a

Browse files
committed
Unite the types WIP
1 parent f0c39a8 commit 7e7f05a

File tree

6 files changed

+164
-1
lines changed

6 files changed

+164
-1
lines changed

app/bootstrap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use PhpSchool\PHP8Appreciate\Exercise\HaveTheLastSay;
2525
use PhpSchool\PHP8Appreciate\Exercise\PhpPromotion;
2626
use PhpSchool\PHP8Appreciate\Exercise\LordOfTheStrings;
27+
use PhpSchool\PHP8Appreciate\Exercise\UniteTheTypes;
2728
use PhpSchool\PhpWorkshop\Application;
2829

2930
$app = new Application('PHP8 Appreciate', __DIR__ . '/config.php');
@@ -33,6 +34,7 @@
3334
$app->addExercise(PhpPromotion::class);
3435
$app->addExercise(CautionWithCatches::class);
3536
$app->addExercise(LordOfTheStrings::class);
37+
$app->addExercise(UniteTheTypes::class);
3638

3739
$art = <<<ART
3840
_ __ _

app/config.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
use PhpSchool\PHP8Appreciate\Exercise\HaveTheLastSay;
66
use PhpSchool\PHP8Appreciate\Exercise\PhpPromotion;
77
use PhpSchool\PHP8Appreciate\Exercise\LordOfTheStrings;
8+
use PhpSchool\PHP8Appreciate\Exercise\UniteTheTypes;
89
use Psr\Container\ContainerInterface;
910

1011
use function DI\create;
1112
use function DI\factory;
12-
use function DI\object;
1313

1414
return [
1515
'basePath' => __DIR__ . '/../',
@@ -30,4 +30,7 @@
3030
LordOfTheStrings::class => function (ContainerInterface $c) {
3131
return new LordOfTheStrings($c->get(\Faker\Generator::class));
3232
},
33+
UniteTheTypes::class => function (ContainerInterface $c) {
34+
return new UniteTheTypes($c->get(PhpParser\Parser::class), $c->get(\Faker\Generator::class));
35+
},
3336
];

exercises/unite-the-types/problem/problem.md

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
function adder(string|float|int ...$numbers)
4+
{
5+
return array_sum($numbers);
6+
}
7+
8+
$nums = $argv;
9+
array_shift($nums);
10+
11+
echo adder(...$nums) . "\n";
12+

solution.php

Whitespace-only changes.

src/Exercise/UniteTheTypes.php

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
3+
4+
namespace PhpSchool\PHP8Appreciate\Exercise;
5+
6+
use PhpParser\Node\Identifier;
7+
use PhpParser\Node\Stmt;
8+
use PhpParser\Node\Stmt\Function_;
9+
use PhpParser\Node\UnionType;
10+
use PhpParser\NodeFinder;
11+
use PhpParser\Parser;
12+
use PhpSchool\PhpWorkshop\CodeInsertion;
13+
use PhpSchool\PhpWorkshop\Exercise\AbstractExercise;
14+
use PhpSchool\PhpWorkshop\Exercise\CliExercise;
15+
use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface;
16+
use PhpSchool\PhpWorkshop\Exercise\ExerciseType;
17+
use PhpSchool\PhpWorkshop\Exercise\SubmissionPatchable;
18+
use PhpSchool\PhpWorkshop\ExerciseCheck\SelfCheck;
19+
use PhpSchool\PhpWorkshop\Input\Input;
20+
use PhpSchool\PhpWorkshop\Patch;
21+
use PhpSchool\PhpWorkshop\Result\Failure;
22+
use PhpSchool\PhpWorkshop\Result\ResultInterface;
23+
use PhpSchool\PhpWorkshop\Result\Success;
24+
use Faker\Generator as FakerGenerator;
25+
26+
class UniteTheTypes extends AbstractExercise implements
27+
ExerciseInterface,
28+
CliExercise,
29+
SelfCheck,
30+
SubmissionPatchable
31+
{
32+
33+
private float $total;
34+
35+
public function __construct(private Parser $parser, private FakerGenerator $faker)
36+
{
37+
}
38+
39+
public function getName(): string
40+
{
41+
return 'Unite The Types';
42+
}
43+
44+
public function getDescription(): string
45+
{
46+
return 'PHP 8\'s union types';
47+
}
48+
49+
public function getType(): ExerciseType
50+
{
51+
return ExerciseType::CLI();
52+
}
53+
54+
public function getArgs(): array
55+
{
56+
$numbers = array_map(
57+
function (): string {
58+
if ($this->faker->boolean) {
59+
return $this->faker->numberBetween(0, 50);
60+
}
61+
return $this->faker->randomFloat(3, 0, 50);
62+
},
63+
range(0, random_int(5, 15))
64+
);
65+
66+
$this->total = array_sum($numbers);
67+
68+
return [$numbers];
69+
}
70+
71+
public function getPatch(): Patch
72+
{
73+
$code = <<<'CODE'
74+
declare(strict_types=1);
75+
76+
$argv = array_map(function ($value) {
77+
return match (true) {
78+
(int) $value != (float) $value => (float) $value,
79+
(bool) random_int(0, 1) => (int) $value,
80+
default => (string) $value
81+
};
82+
}, $argv);
83+
CODE;
84+
85+
$casterInsertion = new CodeInsertion(CodeInsertion::TYPE_BEFORE, $code);
86+
87+
return (new Patch())->withInsertion($casterInsertion);
88+
}
89+
90+
public function check(Input $input): ResultInterface
91+
{
92+
/** @var array<Stmt> $statements */
93+
$statements = $this->parser->parse((string) file_get_contents($input->getRequiredArgument('program')));
94+
95+
/** @var Function_|null $adder */
96+
$adder = (new NodeFinder())->findFirst($statements, function (\PhpParser\Node $node) {
97+
return $node instanceof Function_ && $node->name->toString() === 'adder';
98+
});
99+
100+
if (null === $adder) {
101+
return Failure::fromNameAndReason($this->getName(), 'No function named adder was found');
102+
}
103+
104+
if (!isset($adder->params[0])) {
105+
return Failure::fromNameAndReason($this->getName(), 'Function adder has no parameters');
106+
}
107+
108+
/** @var \PhpParser\Node\Param $firstParam */
109+
$firstParam = $adder->params[0];
110+
111+
if (!$firstParam->type instanceof UnionType) {
112+
return Failure::fromNameAndReason(
113+
$this->getName(),
114+
'Function adder does not use a union type for it\'s first param'
115+
);
116+
}
117+
118+
$incorrectTypes = array_filter(
119+
$firstParam->type->types,
120+
fn ($type) => !$type instanceof Identifier
121+
);
122+
123+
if (count($incorrectTypes)) {
124+
return Failure::fromNameAndReason(
125+
$this->getName(),
126+
'Union type is incorrect, it should only accept the required types'
127+
);
128+
}
129+
130+
$types = array_map(
131+
fn (Identifier $type) => $type->__toString(),
132+
$firstParam->type->types
133+
);
134+
135+
sort($types);
136+
137+
if ($types !== ['float', 'int', 'string']) {
138+
return Failure::fromNameAndReason(
139+
$this->getName(),
140+
'Union type is incorrect, it should only accept the required types'
141+
);
142+
}
143+
144+
return new Success('Union type for adder is correct');
145+
}
146+
}

0 commit comments

Comments
 (0)