forked from CuyZ/Valinor
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Previously, the method `TreeMapper::map` would allow mapping only to an object. It is now possible to map to any type handled by the library. It is for instance possible to map to an array of objects: ```php $objects = (new \CuyZ\Valinor\MapperBuilder())->mapper()->map( 'array<' . SomeClass::class . '>', [/* … */] ); ``` For simple use-cases, an array shape can be used: ```php $array = (new \CuyZ\Valinor\MapperBuilder())->mapper()->map( 'array{foo: string, bar: int}', [/* … */] ); echo strtolower($array['foo']); echo $array['bar'] * 2; ``` This new feature changes the possible behaviour of the mapper, meaning static analysis tools need help to understand the types correctly. An extension for PHPStan and a plugin for Psalm are now provided and can be included in a project to automatically increase the type coverage.
- Loading branch information
Showing
27 changed files
with
374 additions
and
89 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
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,62 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace CuyZ\Valinor\QA\PHPStan\Extension; | ||
|
||
use CuyZ\Valinor\Mapper\TreeMapper; | ||
use PhpParser\Node\Expr\MethodCall; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\PhpDoc\TypeStringResolver; | ||
use PHPStan\Reflection\MethodReflection; | ||
use PHPStan\Type\Constant\ConstantStringType; | ||
use PHPStan\Type\DynamicMethodReturnTypeExtension; | ||
use PHPStan\Type\Generic\GenericClassStringType; | ||
use PHPStan\Type\MixedType; | ||
use PHPStan\Type\Type; | ||
use PHPStan\Type\UnionType; | ||
|
||
final class TreeMapperPHPStanExtension implements DynamicMethodReturnTypeExtension | ||
{ | ||
private TypeStringResolver $resolver; | ||
|
||
public function __construct(TypeStringResolver $resolver) | ||
{ | ||
$this->resolver = $resolver; | ||
} | ||
|
||
public function getClass(): string | ||
{ | ||
return TreeMapper::class; | ||
} | ||
|
||
public function isMethodSupported(MethodReflection $methodReflection): bool | ||
{ | ||
return $methodReflection->getName() === 'map'; | ||
} | ||
|
||
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type | ||
{ | ||
$argument = $methodCall->getArgs()[0]->value; | ||
$type = $scope->getType($argument); | ||
|
||
if ($type instanceof UnionType) { | ||
return $type->traverse(fn (Type $type) => $this->type($type)); | ||
} | ||
|
||
return $this->type($type); | ||
} | ||
|
||
private function type(Type $type): Type | ||
{ | ||
if ($type instanceof GenericClassStringType) { | ||
return $type->getGenericType(); | ||
} | ||
|
||
if ($type instanceof ConstantStringType) { | ||
return $this->resolver->resolve($type->getValue()); | ||
} | ||
|
||
return new MixedType(); | ||
} | ||
} |
File renamed without changes.
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,14 @@ | ||
<?php | ||
|
||
use CuyZ\Valinor\QA\PHPStan\Extension\TreeMapperPHPStanExtension; | ||
|
||
require_once 'Extension/TreeMapperPHPStanExtension.php'; | ||
|
||
return [ | ||
'services' => [ | ||
[ | ||
'class' => TreeMapperPHPStanExtension::class, | ||
'tags' => ['phpstan.broker.dynamicMethodReturnTypeExtension'] | ||
] | ||
], | ||
]; |
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,68 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace CuyZ\Valinor\QA\Psalm\Plugin; | ||
|
||
use CuyZ\Valinor\Mapper\TreeMapper; | ||
use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent; | ||
use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface; | ||
use Psalm\Type; | ||
use Psalm\Type\Atomic; | ||
use Psalm\Type\Atomic\TClassString; | ||
use Psalm\Type\Atomic\TDependentGetClass; | ||
use Psalm\Type\Atomic\TLiteralString; | ||
use Psalm\Type\Union; | ||
|
||
final class TreeMapperPsalmPlugin implements MethodReturnTypeProviderInterface | ||
{ | ||
public static function getClassLikeNames(): array | ||
{ | ||
return [TreeMapper::class]; | ||
} | ||
|
||
public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Union | ||
{ | ||
if ($event->getMethodNameLowercase() !== 'map') { | ||
return null; | ||
} | ||
|
||
$type = $event->getSource()->getNodeTypeProvider()->getType($event->getCallArgs()[0]->value); | ||
|
||
if (! $type) { | ||
return null; | ||
} | ||
|
||
$types = []; | ||
|
||
foreach ($type->getChildNodes() as $node) { | ||
$inferred = self::type($node); | ||
|
||
if ($inferred === null) { | ||
return null; | ||
} | ||
|
||
$types[] = $inferred; | ||
} | ||
|
||
if (count($types) === 0) { | ||
return null; | ||
} | ||
|
||
return Type::combineUnionTypeArray($types, $event->getSource()->getCodebase()); | ||
} | ||
|
||
private static function type(Atomic $node): ?Union | ||
{ | ||
switch (true) { | ||
case $node instanceof TLiteralString: | ||
return Type::parseString($node->value); | ||
case $node instanceof TDependentGetClass: | ||
return $node->as_type; | ||
case $node instanceof TClassString && $node->as_type: | ||
return new Union([$node->as_type]); | ||
default: | ||
return null; | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
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.