1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 12:24:49 +01:00

Ensure getId() output can always be parsed as a type

Ref #5105
This commit is contained in:
Matt Brown 2021-01-25 23:41:42 -05:00 committed by Daniil Gentili
parent aaa912374f
commit 5da816f160
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
17 changed files with 60 additions and 44 deletions

View File

@ -1219,7 +1219,7 @@ class AssertionFinder
null,
null,
null
)->getId();
)->getAssertionString();
}
}
}
@ -1284,7 +1284,7 @@ class AssertionFinder
null,
null,
null
)->getId();
)->getAssertionString();
}
}
}
@ -3696,7 +3696,7 @@ class AssertionFinder
$literal_assertions = [];
foreach ($array_literal_types as $array_literal_type) {
$literal_assertions[] = '=' . $array_literal_type->getId();
$literal_assertions[] = '=' . $array_literal_type->getAssertionString();
}
if ($value_type->isFalsable()) {
@ -3766,11 +3766,11 @@ class AssertionFinder
if ($key_type->allStringLiterals() && !$key_type->possibly_undefined) {
foreach ($key_type->getLiteralStrings() as $array_literal_type) {
$literal_assertions[] = '=' . $array_literal_type->getId();
$literal_assertions[] = '=' . $array_literal_type->getAssertionString();
}
} elseif ($key_type->allIntLiterals() && !$key_type->possibly_undefined) {
foreach ($key_type->getLiteralInts() as $array_literal_type) {
$literal_assertions[] = '=' . $array_literal_type->getId();
$literal_assertions[] = '=' . $array_literal_type->getAssertionString();
}
}
}

View File

@ -557,6 +557,7 @@ class FunctionLikeDocblockScanner
foreach ($namespaced_type->getAtomicTypes() as $namespaced_type_part) {
if ($namespaced_type_part instanceof Type\Atomic\TAssertionFalsy
|| $namespaced_type_part instanceof Type\Atomic\TScalarClassConstant
|| ($namespaced_type_part instanceof Type\Atomic\TList
&& $namespaced_type_part->type_param->isMixed())
|| ($namespaced_type_part instanceof Type\Atomic\TArray

View File

@ -31,7 +31,13 @@ class TIntMask extends TInt
public function getId(bool $nested = false): string
{
return $this->getKey();
$s = '';
foreach ($this->values as $value) {
$s .= $value->getId() . ', ';
}
return 'int-mask<' . substr($s, 0, -2) . '>';
}
/**

View File

@ -20,6 +20,11 @@ class TLiteralInt extends TInt
}
public function getId(bool $nested = false): string
{
return (string) $this->value;
}
public function getAssertionString(bool $exact = false): string
{
return 'int(' . $this->value . ')';
}

View File

@ -20,7 +20,7 @@ class TLiteralString extends TString
public function getKey(bool $include_extra = true) : string
{
return $this->getId();
return 'string(' . $this->value . ')';
}
public function __toString(): string
@ -32,10 +32,15 @@ class TLiteralString extends TString
{
$no_newline_value = preg_replace("/\n/m", '\n', $this->value);
if (strlen($this->value) > 80) {
return 'string(' . substr($no_newline_value, 0, 80) . '...' . ')';
return '"' . substr($no_newline_value, 0, 80) . '...' . '"';
}
return 'string(' . $no_newline_value . ')';
return '"' . $no_newline_value . '"';
}
public function getAssertionString(bool $exact = false): string
{
return 'string(' . $this->value . ')';
}
/**

View File

@ -30,7 +30,12 @@ class TScalarClassConstant extends Scalar
public function getId(bool $nested = false): string
{
return $this->getKey();
return $this->fq_classlike_name . '::' . $this->const_name;
}
public function getAssertionString(bool $exact = false): string
{
return 'scalar-class-constant(' . $this->fq_classlike_name . '::' . $this->const_name . ')';
}
/**
@ -69,9 +74,4 @@ class TScalarClassConstant extends Scalar
. '::'
. $this->const_name;
}
public function getAssertionString(bool $exact = false): string
{
return 'mixed';
}
}

View File

@ -688,7 +688,7 @@ class ArrayFunctionCallTest extends TestCase
ARRAY_FILTER_USE_KEY
);',
'assertions' => [
'$foo' => 'array<string, pure-Closure():string(baz)>',
'$foo' => 'array<string, pure-Closure():"baz">',
],
],
'ignoreFalsableCurrent' => [

View File

@ -1244,7 +1244,7 @@ class AssertAnnotationTest extends TestCase
'convertConstStringType' => [
'<?php
class A {
const T1 = 1;
const T1 = 1;
const T2 = 2;
/**
@ -1260,7 +1260,6 @@ class AssertAnnotationTest extends TestCase
}
}
function takesA(int $a) : void {
if (A::isValid($a)) {
A::bar($a);

View File

@ -340,7 +340,7 @@ class ClosureTest extends TestCase
$a = function() : Closure { return function() : string { return "hello"; }; };
$b = $a()();',
'assertions' => [
'$a' => 'pure-Closure():pure-Closure():string(hello)',
'$a' => 'pure-Closure():pure-Closure():"hello"',
'$b' => 'string',
],
],

View File

@ -98,7 +98,7 @@ class JsonOutputTest extends TestCase
function fooFoo() {
return "hello";
}',
'message' => 'Method fooFoo does not have a return type, expecting string(hello)',
'message' => 'Method fooFoo does not have a return type, expecting "hello"',
'line' => 2,
'error' => 'fooFoo',
],
@ -110,7 +110,7 @@ class JsonOutputTest extends TestCase
function fooFoo() {
return "hello";
}',
'message' => "The inferred type 'string(hello)' does not match the declared return type 'int' for fooFoo",
'message' => "The inferred type '\"hello\"' does not match the declared return type 'int' for fooFoo",
'line' => 6,
'error' => '"hello"',
],

View File

@ -216,13 +216,13 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase
$this->assertNotNull($symbol_at_position);
$this->assertSame('213-214:int(1)', $symbol_at_position[0]);
$this->assertSame('213-214:1', $symbol_at_position[0]);
$symbol_at_position = $codebase->getReferenceAtPosition('somefile.php', new Position(17, 30));
$this->assertNotNull($symbol_at_position);
$this->assertSame('425-426:int(2)', $symbol_at_position[0]);
$this->assertSame('425-426:2', $symbol_at_position[0]);
}
public function testGetSymbolPositionMissingArg(): void

View File

@ -59,8 +59,8 @@ class Php56Test extends TestCase
$bitxor = C::BITXOR;',
'assertions' => [
'$c1' => 'int',
'$c2===' => 'int(2)',
'$c3===' => 'int(3)',
'$c2===' => '2',
'$c3===' => '3',
'$c1_3rd' => 'float|int',
'$c_sentence' => 'string',
'$cf' => 'int',

View File

@ -1288,7 +1288,7 @@ class ReturnTypeTest extends TestCase
return 1;
};
}',
'error_message' => 'InvalidReturnStatement - src' . DIRECTORY_SEPARATOR . 'somefile.php:9:28 - The inferred type \'pure-Closure(iterable<int, T:fn-map as mixed>):int(1)\' does not match the declared return type \'callable(iterable<int, T:fn-map as mixed>):iterable<int, U:fn-map as mixed>\' for map',
'error_message' => 'InvalidReturnStatement - src' . DIRECTORY_SEPARATOR . 'somefile.php:9:28 - The inferred type \'pure-Closure(iterable<int, T:fn-map as mixed>):1\' does not match the declared return type \'callable(iterable<int, T:fn-map as mixed>):iterable<int, U:fn-map as mixed>\' for map',
],
'cannotInferReturnClosureWithDifferentTypes' => [
'<?php

View File

@ -1045,7 +1045,7 @@ class ClassTemplateTest extends TestCase
$c = new C();',
'assertions' => [
'$c===' => 'C<string(hello)>',
'$c===' => 'C<"hello">',
],
],
'SKIPPED-templateDefaultConstant' => [
@ -3601,7 +3601,7 @@ class ClassTemplateTest extends TestCase
$mario = new CharacterRow(["id" => 5, "name" => "Mario", "height" => 3.5]);
$mario->ame = "Luigi";',
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects string(height)|string(id)|string(name), string(ame) provided',
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects "height"|"id"|"name", "ame" provided',
],
'specialiseTypeBeforeReturning' => [
'<?php

View File

@ -339,28 +339,28 @@ class TypeCombinationTest extends TestCase
],
],
'combineObjectTypeWithIntKeyedArray' => [
'array<int|string(a), int|string>',
'array<"a"|int, int|string>',
[
'array{a: int}',
'array<int, string>',
],
],
'combineNestedObjectTypeWithTKeyedArrayIntKeyedArray' => [
'array{a: array<int|string(a), int|string>}',
'array{a: array<"a"|int, int|string>}',
[
'array{a: array{a: int}}',
'array{a: array<int, string>}',
],
],
'combineIntKeyedObjectTypeWithNestedIntKeyedArray' => [
'array<int, array<int|string(a), int|string>>',
'array<int, array<"a"|int, int|string>>',
[
'array<int, array{a:int}>',
'array<int, array<int, string>>',
],
],
'combineNestedObjectTypeWithNestedIntKeyedArray' => [
'array<int|string(a), array<int|string(a), int|string>>',
'array<"a"|int, array<"a"|int, int|string>>',
[
'array{a: array{a: int}}',
'array<int, array<int, string>>',
@ -431,7 +431,7 @@ class TypeCombinationTest extends TestCase
],
],
'objectLikePlusArrayEqualsArray' => [
'array<string(a)|string(b)|string(c), int(1)|int(2)|int(3)>',
'array<"a"|"b"|"c", 1|2|3>',
[
'array<"a"|"b"|"c", 1|2|3>',
'array{a: 1|2, b: 2|3, c: 1|3}',
@ -571,14 +571,14 @@ class TypeCombinationTest extends TestCase
],
],
'combineZeroAndPositiveInt' => [
'int(0)|positive-int',
'0|positive-int',
[
'0',
'positive-int',
],
],
'combinePositiveIntAndZero' => [
'int(0)|positive-int',
'0|positive-int',
[
'positive-int',
'0',
@ -615,7 +615,7 @@ class TypeCombinationTest extends TestCase
],
],
'combineZeroOneAndPositiveInt' => [
'int(0)|positive-int',
'0|positive-int',
[
'0',
'1',
@ -623,7 +623,7 @@ class TypeCombinationTest extends TestCase
],
],
'combinePositiveIntOneAndZero' => [
'int(0)|positive-int',
'0|positive-int',
[
'positive-int',
'1',

View File

@ -717,7 +717,7 @@ class TypeParseTest extends TestCase
public function testCombineLiteralStringWithClassString(): void
{
$this->assertSame(
'class-string|string(array)',
'"array"|class-string',
Type::parseString('"array"|class-string')->getId()
);
}
@ -902,26 +902,26 @@ class TypeParseTest extends TestCase
{
$docblock_type = Type::parseString('int-mask<0, 1, 2, 4>');
$this->assertSame('int(0)|int(1)|int(2)|int(3)|int(4)|int(5)|int(6)|int(7)', $docblock_type->getId());
$this->assertSame('0|1|2|3|4|5|6|7', $docblock_type->getId());
$docblock_type = Type::parseString('int-mask<1, 2, 4>');
$this->assertSame('int(1)|int(2)|int(3)|int(4)|int(5)|int(6)|int(7)', $docblock_type->getId());
$this->assertSame('1|2|3|4|5|6|7', $docblock_type->getId());
$docblock_type = Type::parseString('int-mask<1, 4>');
$this->assertSame('int(1)|int(4)|int(5)', $docblock_type->getId());
$this->assertSame('1|4|5', $docblock_type->getId());
$docblock_type = Type::parseString('int-mask<PREG_PATTERN_ORDER, PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL>');
$this->assertSame('int(1)|int(256)|int(257)|int(512)|int(513)|int(768)|int(769)', $docblock_type->getId());
$this->assertSame('1|256|257|512|513|768|769', $docblock_type->getId());
}
public function testIntMaskWithClassConstant(): void
{
$docblock_type = Type::parseString('int-mask<0, A::FOO, A::BAR>');
$this->assertSame('int-mask<int(0), scalar-class-constant(A::FOO), scalar-class-constant(A::BAR)>', $docblock_type->getId());
$this->assertSame('int-mask<0, A::FOO, A::BAR>', $docblock_type->getId());
}
public function testIntMaskWithInvalidClassConstant(): void

View File

@ -101,7 +101,7 @@ class ReconcilerTest extends \Psalm\Tests\TestCase
'falsyWithMyObjectPipeBool' => ['false', 'falsy', 'MyObject|bool'],
'falsyWithMixed' => ['empty-mixed', 'falsy', 'mixed'],
'falsyWithBool' => ['false', 'falsy', 'bool'],
'falsyWithStringOrNull' => ['null|string()|string(0)', 'falsy', 'string|null'],
'falsyWithStringOrNull' => ['""|"0"|null', 'falsy', 'string|null'],
'falsyWithScalarOrNull' => ['empty-scalar', 'falsy', 'scalar'],
'notMyObjectWithMyObjectPipeBool' => ['bool', '!MyObject', 'MyObject|bool'],