1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Fix #479 - allow PhpStorm generic syntax behind a config flag

This commit is contained in:
Matthew Brown 2018-02-01 01:10:27 -05:00
parent fb300baa6d
commit 8d2baf584e
4 changed files with 180 additions and 0 deletions

View File

@ -28,6 +28,7 @@
<xs:attribute name="cacheFileContentHashes" type="xs:string" />
<xs:attribute name="rememberPropertyAssignmentsAfterCall" type="xs:string" />
<xs:attribute name="serializer" type="xs:string" />
<xs:attribute name="allowPhpStormGenerics" type="xs:string" />
</xs:complexType>
<xs:complexType name="ProjectFilesType">

View File

@ -383,6 +383,42 @@ class Codebase
$this->populateFileStorage($file_storage);
}
if ($this->config->allow_phpstorm_generics) {
foreach ($this->classlike_storage_provider->getAll() as $class_storage) {
foreach ($class_storage->properties as $property_storage) {
if ($property_storage->type) {
$this->convertPhpStormGenericToPsalmGeneric($property_storage->type, true);
}
}
foreach ($class_storage->methods as $method_storage) {
if ($method_storage->return_type) {
$this->convertPhpStormGenericToPsalmGeneric($method_storage->return_type);
}
foreach ($method_storage->params as $param_storage) {
if ($param_storage->type) {
$this->convertPhpStormGenericToPsalmGeneric($param_storage->type);
}
}
}
}
foreach ($all_file_storage as $file_storage) {
foreach ($file_storage->functions as $function_storage) {
if ($function_storage->return_type) {
$this->convertPhpStormGenericToPsalmGeneric($function_storage->return_type);
}
foreach ($function_storage->params as $param_storage) {
if ($param_storage->type) {
$this->convertPhpStormGenericToPsalmGeneric($param_storage->type);
}
}
}
}
}
if ($this->debug_output) {
echo 'FileStorage is populated' . PHP_EOL;
}
@ -629,6 +665,49 @@ class Codebase
$storage->populated = true;
}
/**
* @param Type\Union $candidate
* @param bool $is_property
*
* @return void
*/
private function convertPhpStormGenericToPsalmGeneric(Type\Union $candidate, $is_property = false)
{
$atomic_types = $candidate->getTypes();
if (isset($atomic_types['array']) && count($atomic_types) > 1) {
$iterator_name = null;
$generic_params = null;
foreach ($atomic_types as $type) {
if ($type instanceof Type\Atomic\TNamedObject
&& (!$type->from_docblock || $is_property)
&& (
strtolower($type->value) === 'traversable'
|| $this->interfaceExtends(
$type->value,
'Traversable'
)
|| $this->classImplements(
$type->value,
'Traversable'
)
)
) {
$iterator_name = $type->value;
} elseif ($type instanceof Type\Atomic\TArray) {
$generic_params = $type->type_params;
}
}
if ($iterator_name && $generic_params) {
$generic_iterator = new Type\Atomic\TGenericObject($iterator_name, $generic_params);
$candidate->removeType('array');
$candidate->addType($generic_iterator);
}
}
}
/**
* @param ClassLikeStorage $storage
* @param ClassLikeStorage $parent_storage

View File

@ -172,6 +172,11 @@ class Config
/** @var bool */
public $use_igbinary = false;
/**
* @var bool
*/
public $allow_phpstorm_generics = false;
/**
* Psalm plugins
*
@ -382,6 +387,11 @@ class Config
$config->use_igbinary = $attribute_text === 'igbinary';
}
if (isset($config_xml['allowPhpStormGenerics'])) {
$attribute_text = (string) $config_xml['allowPhpStormGenerics'];
$config->allow_phpstorm_generics = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml->projectFiles)) {
$config->project_files = ProjectFileFilter::loadFromXMLElement($config_xml->projectFiles, $base_dir, true);
}

View File

@ -1,11 +1,87 @@
<?php
namespace Psalm\Tests;
use Psalm\Config;
use Psalm\Context;
class AnnotationTest extends TestCase
{
use Traits\FileCheckerInvalidCodeParseTestTrait;
use Traits\FileCheckerValidCodeParseTestTrait;
/**
* @return void
*/
public function testPhpStormGenericsWithValidArgument()
{
Config::getInstance()->allow_phpstorm_generics = true;
$this->addFile(
'somefile.php',
'<?php
function takesString(string $s): void {}
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString(ArrayIterator $i): void {
$s = $i->offsetGet("a");
takesString($s);
foreach ($i as $s2) {
takesString($s2);
}
}'
);
$this->analyzeFile('somefile.php', new Context());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage InvalidScalarArgument
*
* @return void
*/
public function testPhpStormGenericsInvalidArgument()
{
Config::getInstance()->allow_phpstorm_generics = true;
$this->addFile(
'somefile.php',
'<?php
function takesInt(int $s): void {}
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString(ArrayIterator $i): void {
$s = $i->offsetGet("a");
takesInt($s);
}'
);
$this->analyzeFile('somefile.php', new Context());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage PossiblyInvalidMethodCall
*
* @return void
*/
public function testPhpStormGenericsNoTypehint()
{
Config::getInstance()->allow_phpstorm_generics = true;
$this->addFile(
'somefile.php',
'<?php
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString($i): void {
$s = $i->offsetGet("a");
}'
);
$this->analyzeFile('somefile.php', new Context());
}
/**
* @return array
*/
@ -733,6 +809,20 @@ class AnnotationTest extends TestCase
function bar(array $arr): void {}',
'error_message' => 'InvalidDocblock',
],
'noPhpStormAnnotationsThankYou' => [
'<?php
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString(ArrayIterator $i): void {}',
'error_message' => 'MismatchingDocblockParamType',
],
'noPhpStormAnnotationsPossiblyInvalid' => [
'<?php
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString($i): void {
$s = $i->offsetGet("a");
}',
'error_message' => 'PossiblyInvalidMethodCall',
],
];
}
}