Skip to content

Commit 248ba6d

Browse files
committed
Generated types
1 parent 9a103ae commit 248ba6d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1858
-8
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/.github/ export-ignore
2+
/spec/ export-ignore
23
/stubs/ export-ignore
34
/tests/ export-ignore
45
/.gitattributes export-ignore

.php-cs-fixer.dist.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
$config = (new Config())
1111
->setFinder(
1212
Finder::create()
13+
->in(__DIR__ . '/spec')
1314
->in(__DIR__ . '/src')
1415
->in(__DIR__ . '/tests')
1516
->append([

composer.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
},
2424
"autoload": {
2525
"psr-4": {
26-
"Typhoon\\Type\\": "src/",
27-
"Typhoon\\TypeSpec\\": "spec/"
28-
}
26+
"Typhoon\\TypeSpec\\": "spec/",
27+
"Typhoon\\Type\\": "src/"
28+
},
29+
"files": [
30+
"src/types.php"
31+
]
2932
},
3033
"autoload-dev": {
3134
"psr-4": {

psalm.xml.dist

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
xsi:schemaLocation="https://getpsalm.org/schema/config tools/psalm/vendor/vimeo/psalm/config.xsd"
1919
>
2020
<projectFiles>
21+
<directory name="spec"/>
2122
<directory name="src"/>
2223
<directory name="tests"/>
2324
<ignoreFiles>
@@ -41,14 +42,10 @@
4142
<issueHandlers>
4243
<PluginIssue name="UnspecifiedVisibility">
4344
<errorLevel type="suppress">
45+
<directory name="spec"/>
4446
<directory name="tests"/>
4547
</errorLevel>
4648
</PluginIssue>
47-
<MissingThrowsDocblock>
48-
<errorLevel type="suppress">
49-
<directory name="tests"/>
50-
</errorLevel>
51-
</MissingThrowsDocblock>
5249
</issueHandlers>
5350

5451
<forbiddenFunctions>

spec/AtomicType.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Typhoon\TypeSpec;
6+
7+
final class AtomicType extends Type
8+
{
9+
public function typeCode(): string
10+
{
11+
return <<<PHP
12+
<?php
13+
14+
declare(strict_types=1);
15+
16+
namespace Typhoon\\Type;
17+
18+
/**
19+
* This code is generated, do not modify it directly.
20+
*
21+
* @api
22+
* @implements Type<{$this->name}>
23+
*/
24+
enum {$this->shortClassName()} implements Type
25+
{
26+
case Type;
27+
28+
public function accept(TypeVisitor \$visitor): mixed
29+
{
30+
return \$visitor->{$this->name}(\$this);
31+
}
32+
}
33+
34+
PHP;
35+
}
36+
}

spec/ComplexType.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Typhoon\TypeSpec;
6+
7+
final class ComplexType extends Type
8+
{
9+
/**
10+
* @param non-empty-string $name
11+
* @param list<Template> $templates
12+
* @param non-empty-list<Parameter> $parameters
13+
* @param non-empty-string $TType
14+
* @param list<class-string> $imports
15+
*/
16+
public function __construct(
17+
string $name,
18+
private string $TType,
19+
private array $parameters,
20+
private array $templates = [],
21+
private array $imports = [],
22+
) {
23+
parent::__construct($name);
24+
}
25+
26+
public function typeCode(): string
27+
{
28+
$imports = implode(
29+
'',
30+
array_map(
31+
static fn (string $class): string => "\nuse {$class};",
32+
$this->imports,
33+
)
34+
);
35+
$imports = $imports === '' ? '' : $imports."\n";
36+
$templates = implode(
37+
'',
38+
array_map(
39+
static fn(Template $template): string => $template->code(),
40+
$this->templates,
41+
),
42+
);
43+
$properties = implode(
44+
"\n",
45+
array_map(
46+
static fn(Parameter $parameter): string => $parameter->propertyCode(),
47+
$this->parameters,
48+
),
49+
);
50+
$phpDocParams = implode(
51+
'',
52+
array_filter(
53+
array_map(
54+
static fn(Parameter $parameter): ?string => $parameter->paramPhpDocCode(),
55+
$this->parameters,
56+
),
57+
),
58+
);
59+
$params = implode(
60+
"\n",
61+
array_map(
62+
static fn(Parameter $parameter): string => $parameter->paramCode(),
63+
$this->parameters,
64+
),
65+
);
66+
$assignments = implode(
67+
"\n",
68+
array_map(
69+
static fn(Parameter $parameter): string => $parameter->propertyAssignCode(),
70+
$this->parameters,
71+
),
72+
);
73+
74+
return <<<PHP
75+
<?php
76+
77+
declare(strict_types=1);
78+
79+
namespace Typhoon\\Type;
80+
{$imports}
81+
/**
82+
* This code is generated, do not modify it directly.
83+
*
84+
* @api{$templates}
85+
* @implements Type<{$this->TType}>
86+
*/
87+
final class {$this->shortClassName()} implements Type
88+
{
89+
{$properties}
90+
/**
91+
* @internal
92+
* @psalm-internal Typhoon\\Type{$phpDocParams}
93+
*/
94+
public function __construct(
95+
{$params}
96+
) {
97+
{$assignments}
98+
}
99+
100+
public function accept(TypeVisitor \$visitor): mixed
101+
{
102+
return \$visitor->{$this->name}(\$this);
103+
}
104+
}
105+
106+
PHP;
107+
}
108+
}

spec/Parameter.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Typhoon\TypeSpec;
6+
7+
final class Parameter
8+
{
9+
/**
10+
* @param non-empty-string $name
11+
* @param non-empty-string $nativeType
12+
* @param ?non-empty-string $phpDocType
13+
*/
14+
public function __construct(
15+
public readonly string $name,
16+
public readonly string $nativeType,
17+
public readonly ?string $phpDocType = null,
18+
) {}
19+
20+
/**
21+
* @return non-empty-string
22+
*/
23+
public function propertyCode(): string
24+
{
25+
$code = '';
26+
27+
if ($this->phpDocType !== null) {
28+
$code .= <<<PHP
29+
/**
30+
* @var {$this->phpDocType}
31+
*/
32+
33+
PHP;
34+
}
35+
36+
return $code . \sprintf(" public readonly %s \$%s;\n", $this->nativeType, $this->name);
37+
}
38+
39+
/**
40+
* @return ?non-empty-string
41+
*/
42+
public function paramPhpDocCode(): ?string
43+
{
44+
if ($this->phpDocType === null) {
45+
return null;
46+
}
47+
48+
return "\n * @param {$this->phpDocType} \${$this->name}";
49+
}
50+
51+
/**
52+
* @return non-empty-string
53+
*/
54+
public function paramCode(): string
55+
{
56+
return " {$this->nativeType} \${$this->name},";
57+
}
58+
59+
/**
60+
* @return non-empty-string
61+
*/
62+
public function propertyAssignCode(): string
63+
{
64+
return " \$this->{$this->name} = \${$this->name};";
65+
}
66+
}

spec/Template.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Typhoon\TypeSpec;
6+
7+
final class Template
8+
{
9+
/**
10+
* @param non-empty-string $name
11+
* @param ?non-empty-string $of
12+
* @param ?non-empty-string $default
13+
*/
14+
public function __construct(
15+
public readonly string $name,
16+
public readonly ?string $of = null,
17+
public readonly ?string $default = null,
18+
) {}
19+
20+
/**
21+
* @return non-empty-string
22+
*/
23+
public function code(): string
24+
{
25+
$code = \sprintf("\n * @template %s", $this->name);
26+
27+
if ($this->of !== null) {
28+
$code .= ' of ' . $this->of;
29+
}
30+
31+
if ($this->default !== null) {
32+
$code .= ' = ' . $this->default;
33+
}
34+
35+
return $code;
36+
}
37+
}

spec/Type.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Typhoon\TypeSpec;
6+
7+
abstract class Type
8+
{
9+
/**
10+
* @param non-empty-string $name
11+
*/
12+
public function __construct(
13+
public readonly string $name,
14+
) {}
15+
16+
/**
17+
* @return non-empty-string
18+
*/
19+
final public function shortClassName(): string
20+
{
21+
return ucfirst($this->name) . 'Type';
22+
}
23+
24+
/**
25+
* @psalm-suppress PossiblyUnusedMethod
26+
* @return non-empty-string
27+
*/
28+
final public function className(): string
29+
{
30+
return 'Typhoon\Type\\' . $this->shortClassName();
31+
}
32+
33+
/**
34+
* @return non-empty-string
35+
*/
36+
final public function file(): string
37+
{
38+
return __DIR__ . '/../src/' . $this->shortClassName() . '.php';
39+
}
40+
41+
/**
42+
* @return non-empty-string
43+
*/
44+
abstract public function typeCode(): string;
45+
46+
/**
47+
* @return non-empty-string
48+
*/
49+
final public function visitorCode(): string
50+
{
51+
return <<<PHP
52+
/** @return TResult */
53+
public function {$this->name}({$this->shortClassName()} \$type): mixed;
54+
PHP;
55+
}
56+
}

spec/generate.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Typhoon\TypeSpec;
6+
7+
require_once __DIR__ . '/../vendor/autoload.php';
8+
9+
$visitorCode = <<<'PHP'
10+
<?php
11+
12+
declare(strict_types=1);
13+
14+
namespace Typhoon\Type;
15+
16+
/**
17+
* @api
18+
* @template-covariant TResult
19+
*/
20+
interface TypeVisitor
21+
{
22+
PHP;
23+
24+
25+
foreach (require_once __DIR__ . '/types.php' as $type) {
26+
\assert($type instanceof Type);
27+
28+
file_put_contents($type->file(), $type->typeCode());
29+
$visitorCode .= "\n" . $type->visitorCode() . "\n";
30+
}
31+
32+
file_put_contents(__DIR__ . '/../src/TypeVisitor.php', $visitorCode . "}\n");

0 commit comments

Comments
 (0)