-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
741 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
|
||
$finder = Finder::create() | ||
->in([ | ||
__DIR__ . '/dev', | ||
__DIR__ . '/src', | ||
__DIR__ . '/tests', | ||
]) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\Type\TypeMatcherGenerator; | ||
|
||
final class Param | ||
{ | ||
public function __construct( | ||
public readonly string $name, | ||
public readonly string $nativeType, | ||
public readonly ?string $phpDocType, | ||
) {} | ||
|
||
public function arg(): string | ||
{ | ||
return '$' . $this->name; | ||
} | ||
|
||
public function native(): string | ||
{ | ||
return sprintf('%s $%s', $this->nativeType, $this->name); | ||
} | ||
|
||
public function phpDoc(): string | ||
{ | ||
return sprintf('%s', $this->phpDocType ?? $this->nativeType); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\Type\TypeMatcherGenerator; | ||
|
||
final class Type | ||
{ | ||
/** | ||
* @param list<Param> $params | ||
*/ | ||
public function __construct( | ||
public readonly string $name, | ||
public readonly array $params, | ||
) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\Type\TypeMatcherGenerator; | ||
|
||
final class TypeMatcherGenerator | ||
{ | ||
/** | ||
* @param list<Type> $types | ||
*/ | ||
public function generate(array $types): string | ||
{ | ||
return <<<PHP | ||
<?php | ||
declare(strict_types=1); | ||
namespace Typhoon\\Type; | ||
/** | ||
* @api | ||
* @template-covariant TReturn | ||
* @implements TypeVisitor<TReturn> | ||
*/ | ||
final class TypeMatcher implements TypeVisitor | ||
{ | ||
{$this->properties($types)} | ||
/** | ||
{$this->constructorPhpDocParams($types)} | ||
* @param ?\\Closure(): TReturn \$default | ||
*/ | ||
public function __construct( | ||
{$this->constructorParams($types)} | ||
?\\Closure \$default = null, | ||
) { | ||
{$this->constructorPropertyAssignments($types)} | ||
} | ||
{$this->methods($types)} | ||
} | ||
PHP; | ||
} | ||
|
||
/** | ||
* @param list<Type> $types | ||
*/ | ||
private function constructorParams(array $types): string | ||
{ | ||
return implode("\n", array_map( | ||
static fn(Type $type): string => " ?\\Closure \${$type->name} = null,", | ||
$types, | ||
)); | ||
} | ||
|
||
/** | ||
* @param list<Type> $types | ||
*/ | ||
private function constructorPhpDocParams(array $types): string | ||
{ | ||
return implode("\n", array_map( | ||
fn(Type $type): string => " * @param ?{$this->phpDocClosure($type)} \${$type->name}", | ||
$types, | ||
)); | ||
} | ||
|
||
/** | ||
* @param list<Type> $types | ||
*/ | ||
private function constructorPropertyAssignments(array $types): string | ||
{ | ||
return implode("\n", array_map( | ||
static fn(Type $type): string => " \$this->{$type->name} = \${$type->name} ?? \$default ?? throw new \\InvalidArgumentException('Either \${$type->name} or \$default must be provided.');", | ||
$types, | ||
)); | ||
} | ||
|
||
/** | ||
* @param list<Type> $types | ||
*/ | ||
private function methods(array $types): string | ||
{ | ||
return implode("\n\n", array_map( | ||
static function (Type $type): string { | ||
$params = implode(', ', array_map( | ||
static fn(Param $param): string => $param->native(), | ||
$type->params, | ||
)); | ||
$args = implode(', ', array_map( | ||
static fn(Param $param): string => $param->arg(), | ||
$type->params, | ||
)); | ||
|
||
return <<<PHP | ||
public function {$type->name}({$params}): mixed | ||
{ | ||
return \$this->{$type->name}->__invoke({$args}, \$this); | ||
} | ||
PHP; | ||
}, | ||
$types, | ||
)); | ||
} | ||
|
||
private function phpDocClosure(Type $type): string | ||
{ | ||
return sprintf('\Closure(%s, TypeVisitor): TReturn', implode(', ', array_map( | ||
static fn(Param $param): string => $param->phpDoc(), | ||
$type->params, | ||
))); | ||
} | ||
|
||
/** | ||
* @param list<Type> $types | ||
*/ | ||
private function properties(array $types): string | ||
{ | ||
return implode("\n\n", array_map( | ||
fn(Type $type): string => <<<PHP | ||
/** @var {$this->phpDocClosure($type)} */ | ||
private readonly \\Closure \${$type->name}; | ||
PHP, | ||
$types, | ||
)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\Type\TypeMatcherGenerator; | ||
|
||
use Typhoon\Type\TypeVisitor; | ||
|
||
final class TypeVisitorParser | ||
{ | ||
/** | ||
* @return list<Type> | ||
*/ | ||
public function parse(): array | ||
{ | ||
$types = []; | ||
$class = new \ReflectionClass(TypeVisitor::class); | ||
|
||
foreach ($class->getMethods() as $method) { | ||
$phpDoc = $method->getDocComment() === false ? '' : $method->getDocComment(); | ||
$types[] = new Type($method->name, array_map( | ||
fn(\ReflectionParameter $parameter): Param => new Param( | ||
$parameter->name, | ||
$this->nativeType($parameter->getType() ?? throw new \ReflectionException()), | ||
$this->phpDocParamType($phpDoc, $parameter->name), | ||
), | ||
$method->getParameters(), | ||
)); | ||
} | ||
|
||
return $types; | ||
} | ||
|
||
private function nativeType(\ReflectionType $type): string | ||
{ | ||
if ($type instanceof \ReflectionUnionType) { | ||
return implode('|', array_map($this->nativeType(...), $type->getTypes())); | ||
} | ||
|
||
if ($type instanceof \ReflectionIntersectionType) { | ||
return implode('&', array_map($this->nativeType(...), $type->getTypes())); | ||
} | ||
|
||
if (!$type instanceof \ReflectionNamedType) { | ||
throw new \RuntimeException(); | ||
} | ||
|
||
$name = $type->getName(); | ||
$lastSlash = strrpos($name, '\\'); | ||
$name = substr($name, $lastSlash === false ? 0 : $lastSlash + 1); | ||
|
||
if ($type->allowsNull() && $name !== 'null' && $name !== 'mixed') { | ||
$name = '?' . $name; | ||
} | ||
|
||
return $name; | ||
} | ||
|
||
private function phpDocParamType(string $phpDoc, string $name): ?string | ||
{ | ||
preg_match("/@param (.+) \\\${$name}\\s/", $phpDoc, $matches); | ||
|
||
return $matches[1] ?? null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\Type\TypeMatcherGenerator; | ||
|
||
require_once __DIR__ . '/../../vendor/autoload.php'; | ||
|
||
$types = (new TypeVisitorParser())->parse(); | ||
$code = (new TypeMatcherGenerator())->generate($types); | ||
|
||
file_put_contents(__DIR__ . '/../../src/TypeMatcher.php', $code); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Typhoon\Type\TypeMatcherGenerator; | ||
|
||
require_once __DIR__ . '/../../vendor/autoload.php'; | ||
|
||
$types = (new TypeVisitorParser())->parse(); | ||
$code = (new TypeMatcherGenerator())->generate($types); | ||
|
||
/** @psalm-suppress ForbiddenCode */ | ||
exit(file_get_contents(__DIR__ . '/../../src/TypeMatcher.php') === $code ? 0 : 1); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.