Skip to content

Commit

Permalink
introduce more return type providers (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
azjezz authored Apr 9, 2021
1 parent 3ce1150 commit af8580e
Show file tree
Hide file tree
Showing 16 changed files with 669 additions and 33 deletions.
29 changes: 29 additions & 0 deletions src/Argument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Psl\Psalm;

use PhpParser;
use Psalm\StatementsSource;
use Psalm\Type;

final class Argument
{
/**
* @param list<PhpParser\Node\Arg> $arguments
* @param 0|positive-int $index
*/
public static function getType(
array $arguments,
StatementsSource $statements_source,
int $index
): ?Type\Union {
$argument = $arguments[$index] ?? null;
if (null === $argument) {
return null;
}

return $statements_source->getNodeTypeProvider()->getType($argument->value);
}
}
64 changes: 64 additions & 0 deletions src/EventHandler/Iter/Count/FunctionReturnTypeProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Psl\Psalm\EventHandler\Iter\Count;

use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
use Psl\Psalm\Argument;

use function count;

final class FunctionReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return non-empty-list<lowercase-string>
*/
public static function getFunctionIds(): array
{
return [
'psl\str\count',
];
}

public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union
{
$argument_type = Argument::getType($event->getCallArgs(), $event->getStatementsSource(), 0);
if (null === $argument_type) {
return Type::getInt();
}

$array_argument_type = $argument_type->getAtomicTypes()['array'] ?? null;
if (null === $array_argument_type) {
return Type::getInt();
}

// non-empty-array/non-empty-list -> positive-int / literal-int
if ($array_argument_type instanceof Type\Atomic\TNonEmptyList) {
$count = $array_argument_type->count;
if (null === $count) {
return Type::getPositiveInt();
}

return Type::getInt(false, $count);
}

if ($array_argument_type instanceof Type\Atomic\TNonEmptyArray) {
$count = $array_argument_type->count;
if (null === $count) {
return Type::getPositiveInt();
}

return Type::getInt(false, $count);
}

// array{foo: bar} -> literal-int(1)
if ($array_argument_type instanceof Type\Atomic\TKeyedArray) {
return Type::getInt(false, count($array_argument_type->properties));
}

return Type::getInt();
}
}
55 changes: 55 additions & 0 deletions src/EventHandler/Iter/First/FunctionReturnTypeProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Psl\Psalm\EventHandler\Iter\First;

use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
use Psl\Psalm\Argument;

final class FunctionReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return non-empty-list<lowercase-string>
*/
public static function getFunctionIds(): array
{
return [
'psl\iter\first',
];
}

public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union
{
$argument_type = Argument::getType($event->getCallArgs(), $event->getStatementsSource(), 0);
if (null === $argument_type) {
return null;
}

$array_argument_type = $argument_type->getAtomicTypes()['array'] ?? null;
if (null === $array_argument_type) {
return null;
}

if ($array_argument_type instanceof Type\Atomic\TNonEmptyArray) {
return clone $array_argument_type->type_params[1];
}

if ($array_argument_type instanceof Type\Atomic\TNonEmptyList) {
return clone $array_argument_type->type_param;
}

if ($array_argument_type instanceof Type\Atomic\TKeyedArray) {
// TODO(azjezz): add support for this once psalm starts enforcing the shape order ( if ever ).
//
// foreach ($properties as $property) {
// return clone $property;
// }
return clone $array_argument_type->getGenericValueType();
}

return null;
}
}
59 changes: 59 additions & 0 deletions src/EventHandler/Iter/Last/FunctionReturnTypeProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Psl\Psalm\EventHandler\Iter\Last;

use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
use Psl\Psalm\Argument;

final class FunctionReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return non-empty-list<lowercase-string>
*/
public static function getFunctionIds(): array
{
return [
'psl\iter\last',
];
}

public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union
{
$argument_type = Argument::getType($event->getCallArgs(), $event->getStatementsSource(), 0);
if (null === $argument_type) {
return null;
}

$array_argument_type = $argument_type->getAtomicTypes()['array'] ?? null;
if (null === $array_argument_type) {
return null;
}

if ($array_argument_type instanceof Type\Atomic\TNonEmptyArray) {
return clone $array_argument_type->type_params[1];
}

if ($array_argument_type instanceof Type\Atomic\TNonEmptyList) {
return clone $array_argument_type->type_param;
}

if ($array_argument_type instanceof Type\Atomic\TKeyedArray) {
// TODO(azjezz): add support for this once psalm starts enforcing the shape order ( if ever ).
//
// $properties = $array_argument_type->properties;
// $last_property = null;
// foreach ($properties as $property) {
// $last_property = $property;
// }
//
// return clone $last_property;
return clone $array_argument_type->getGenericValueType();
}

return null;
}
}
44 changes: 44 additions & 0 deletions src/EventHandler/Str/After/FunctionReturnTypeProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Psl\Psalm\EventHandler\Str\After;

use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
use Psl\Psalm\Argument;

final class FunctionReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return non-empty-list<lowercase-string>
*/
public static function getFunctionIds(): array
{
return [
'psl\str\after',
'psl\str\after_ci',
'psl\str\after_last',
'psl\str\after_last_ci',
'psl\str\byte\after',
'psl\str\byte\after_ci',
'psl\str\byte\after_last',
'psl\str\byte\after_last_ci',
'psl\str\grapheme\after',
'psl\str\grapheme\after_ci',
'psl\str\grapheme\after_last',
'psl\str\grapheme\after_last_ci',
];
}

public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union
{
$argument_type = Argument::getType($event->getCallArgs(), $event->getStatementsSource(), 0);
if ($argument_type === null || !$argument_type->hasLowercaseString()) {
return Type::combineUnionTypes(Type::getNull(), Type::getString());
}

return new Type\Union([new Type\Atomic\TLowercaseString(), new Type\Atomic\TNull()]);
}
}
44 changes: 44 additions & 0 deletions src/EventHandler/Str/Before/FunctionReturnTypeProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Psl\Psalm\EventHandler\Str\Before;

use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
use Psl\Psalm\Argument;

final class FunctionReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return non-empty-list<lowercase-string>
*/
public static function getFunctionIds(): array
{
return [
'psl\str\before',
'psl\str\before_ci',
'psl\str\before_last',
'psl\str\before_last_ci',
'psl\str\byte\before',
'psl\str\byte\before_ci',
'psl\str\byte\before_last',
'psl\str\byte\before_last_ci',
'psl\str\grapheme\before',
'psl\str\grapheme\before_ci',
'psl\str\grapheme\before_last',
'psl\str\grapheme\before_last_ci',
];
}

public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union
{
$argument_type = Argument::getType($event->getCallArgs(), $event->getStatementsSource(), 0);
if ($argument_type === null || !$argument_type->hasLowercaseString()) {
return Type::combineUnionTypes(Type::getNull(), Type::getString());
}

return new Type\Union([new Type\Atomic\TNull(), new Type\Atomic\TLowercaseString()]);
}
}
56 changes: 56 additions & 0 deletions src/EventHandler/Str/Chunk/FunctionReturnTypeProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Psl\Psalm\EventHandler\Str\Chunk;

use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
use Psl\Psalm\Argument;

final class FunctionReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return non-empty-list<lowercase-string>
*/
public static function getFunctionIds(): array
{
return [
'psl\str\chunk',
'psl\str\byte\chunk',
];
}

public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union
{
$argument_type = Argument::getType($event->getCallArgs(), $event->getStatementsSource(), 0);
if (null === $argument_type) {
// [unknown] -> list<string>
return new Type\Union([new Type\Atomic\TList(new Type\Union([new Type\Atomic\TString()]))]);
}

$string_argument_type = $argument_type->getAtomicTypes()['string'] ?? null;
if (null === $string_argument_type) {
// [unknown] -> list<string>
return new Type\Union([new Type\Atomic\TList(new Type\Union([new Type\Atomic\TString()]))]);
}

if ($string_argument_type instanceof Type\Atomic\TNonEmptyString) {
// non-empty-lowercase-string => non-empty-list<non-empty-lowercase-string>
if ($string_argument_type instanceof Type\Atomic\TNonEmptyLowercaseString) {
return new Type\Union([
new Type\Atomic\TNonEmptyList(new Type\Union([
new Type\Atomic\TNonEmptyLowercaseString()
]))
]);
}

// non-empty-string => non-empty-list<non-empty-string>
return new Type\Union([new Type\Atomic\TNonEmptyList(new Type\Union([new Type\Atomic\TNonEmptyString()]))]);
}

// string -> list<string>
return new Type\Union([new Type\Atomic\TList(new Type\Union([new Type\Atomic\TString()]))]);
}
}
39 changes: 39 additions & 0 deletions src/EventHandler/Str/Lowercase/FunctionReturnTypeProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Psl\Psalm\EventHandler\Str\Lowercase;

use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
use Psl\Psalm\Argument;

final class FunctionReturnTypeProvider implements FunctionReturnTypeProviderInterface
{
/**
* @return non-empty-list<lowercase-string>
*/
public static function getFunctionIds(): array
{
return [
'psl\str\lowercase',
'psl\str\byte\lowercase',
];
}

public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union
{
$argument_type = Argument::getType($event->getCallArgs(), $event->getStatementsSource(), 0);
if ($argument_type === null) {
return new Type\Union([new Type\Atomic\TLowercaseString()]);
}

$string_argument_type = $argument_type->getAtomicTypes()['string'] ?? null;
if ($string_argument_type instanceof Type\Atomic\TNonEmptyString) {
return new Type\Union([new Type\Atomic\TNonEmptyLowercaseString()]);
}

return new Type\Union([new Type\Atomic\TLowercaseString()]);
}
}
Loading

0 comments on commit af8580e

Please sign in to comment.