1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +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, null,
null null
)->getId(); )->getAssertionString();
} }
} }
} }
@ -1284,7 +1284,7 @@ class AssertionFinder
null, null,
null, null,
null null
)->getId(); )->getAssertionString();
} }
} }
} }
@ -3696,7 +3696,7 @@ class AssertionFinder
$literal_assertions = []; $literal_assertions = [];
foreach ($array_literal_types as $array_literal_type) { foreach ($array_literal_types as $array_literal_type) {
$literal_assertions[] = '=' . $array_literal_type->getId(); $literal_assertions[] = '=' . $array_literal_type->getAssertionString();
} }
if ($value_type->isFalsable()) { if ($value_type->isFalsable()) {
@ -3766,11 +3766,11 @@ class AssertionFinder
if ($key_type->allStringLiterals() && !$key_type->possibly_undefined) { if ($key_type->allStringLiterals() && !$key_type->possibly_undefined) {
foreach ($key_type->getLiteralStrings() as $array_literal_type) { 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) { } elseif ($key_type->allIntLiterals() && !$key_type->possibly_undefined) {
foreach ($key_type->getLiteralInts() as $array_literal_type) { 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) { foreach ($namespaced_type->getAtomicTypes() as $namespaced_type_part) {
if ($namespaced_type_part instanceof Type\Atomic\TAssertionFalsy 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 instanceof Type\Atomic\TList
&& $namespaced_type_part->type_param->isMixed()) && $namespaced_type_part->type_param->isMixed())
|| ($namespaced_type_part instanceof Type\Atomic\TArray || ($namespaced_type_part instanceof Type\Atomic\TArray

View File

@ -31,7 +31,13 @@ class TIntMask extends TInt
public function getId(bool $nested = false): string 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 public function getId(bool $nested = false): string
{
return (string) $this->value;
}
public function getAssertionString(bool $exact = false): string
{ {
return 'int(' . $this->value . ')'; return 'int(' . $this->value . ')';
} }

View File

@ -20,7 +20,7 @@ class TLiteralString extends TString
public function getKey(bool $include_extra = true) : string public function getKey(bool $include_extra = true) : string
{ {
return $this->getId(); return 'string(' . $this->value . ')';
} }
public function __toString(): string public function __toString(): string
@ -32,10 +32,15 @@ class TLiteralString extends TString
{ {
$no_newline_value = preg_replace("/\n/m", '\n', $this->value); $no_newline_value = preg_replace("/\n/m", '\n', $this->value);
if (strlen($this->value) > 80) { 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 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; . $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 ARRAY_FILTER_USE_KEY
);', );',
'assertions' => [ 'assertions' => [
'$foo' => 'array<string, pure-Closure():string(baz)>', '$foo' => 'array<string, pure-Closure():"baz">',
], ],
], ],
'ignoreFalsableCurrent' => [ 'ignoreFalsableCurrent' => [

View File

@ -1260,7 +1260,6 @@ class AssertAnnotationTest extends TestCase
} }
} }
function takesA(int $a) : void { function takesA(int $a) : void {
if (A::isValid($a)) { if (A::isValid($a)) {
A::bar($a); A::bar($a);

View File

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

View File

@ -98,7 +98,7 @@ class JsonOutputTest extends TestCase
function fooFoo() { function fooFoo() {
return "hello"; 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, 'line' => 2,
'error' => 'fooFoo', 'error' => 'fooFoo',
], ],
@ -110,7 +110,7 @@ class JsonOutputTest extends TestCase
function fooFoo() { function fooFoo() {
return "hello"; 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, 'line' => 6,
'error' => '"hello"', 'error' => '"hello"',
], ],

View File

@ -216,13 +216,13 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase
$this->assertNotNull($symbol_at_position); $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)); $symbol_at_position = $codebase->getReferenceAtPosition('somefile.php', new Position(17, 30));
$this->assertNotNull($symbol_at_position); $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 public function testGetSymbolPositionMissingArg(): void

View File

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

View File

@ -1288,7 +1288,7 @@ class ReturnTypeTest extends TestCase
return 1; 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' => [ 'cannotInferReturnClosureWithDifferentTypes' => [
'<?php '<?php

View File

@ -1045,7 +1045,7 @@ class ClassTemplateTest extends TestCase
$c = new C();', $c = new C();',
'assertions' => [ 'assertions' => [
'$c===' => 'C<string(hello)>', '$c===' => 'C<"hello">',
], ],
], ],
'SKIPPED-templateDefaultConstant' => [ 'SKIPPED-templateDefaultConstant' => [
@ -3601,7 +3601,7 @@ class ClassTemplateTest extends TestCase
$mario = new CharacterRow(["id" => 5, "name" => "Mario", "height" => 3.5]); $mario = new CharacterRow(["id" => 5, "name" => "Mario", "height" => 3.5]);
$mario->ame = "Luigi";', $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' => [ 'specialiseTypeBeforeReturning' => [
'<?php '<?php

View File

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

View File

@ -717,7 +717,7 @@ class TypeParseTest extends TestCase
public function testCombineLiteralStringWithClassString(): void public function testCombineLiteralStringWithClassString(): void
{ {
$this->assertSame( $this->assertSame(
'class-string|string(array)', '"array"|class-string',
Type::parseString('"array"|class-string')->getId() Type::parseString('"array"|class-string')->getId()
); );
} }
@ -902,26 +902,26 @@ class TypeParseTest extends TestCase
{ {
$docblock_type = Type::parseString('int-mask<0, 1, 2, 4>'); $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>'); $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>'); $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>'); $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 public function testIntMaskWithClassConstant(): void
{ {
$docblock_type = Type::parseString('int-mask<0, A::FOO, A::BAR>'); $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 public function testIntMaskWithInvalidClassConstant(): void

View File

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