file_analyzer = new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php'); $this->file_analyzer->context = new Context(); $this->statements_analyzer = new StatementsAnalyzer( $this->file_analyzer, new \Psalm\Internal\Provider\NodeDataProvider() ); } /** * @dataProvider providerTestReconcilation * * @param string $expected * @param string $type * @param string $string * * @return void */ public function testReconcilation($expected, $type, $string) { $reconciled = \Psalm\Internal\Type\AssertionReconciler::reconcile( $type, Type::parseString($string), null, $this->statements_analyzer, false, [] ); $this->assertSame( $expected, $reconciled->getId() ); if (is_array($reconciled->getTypes())) { $this->assertContainsOnlyInstancesOf('Psalm\Type\Atomic', $reconciled->getTypes()); } } /** * @dataProvider providerTestTypeIsContainedBy * * @param string $input * @param string $container * * @return void */ public function testTypeIsContainedBy($input, $container) { $this->assertTrue( TypeAnalyzer::isContainedBy( $this->project_analyzer->getCodebase(), Type::parseString($input), Type::parseString($container) ) ); } /** * @return array */ public function providerTestReconcilation() { return [ 'notNullWithObject' => ['MyObject', '!null', 'MyObject'], 'notNullWithObjectPipeNull' => ['MyObject', '!null', 'MyObject|null'], 'notNullWithMyObjectPipeFalse' => ['MyObject|false', '!null', 'MyObject|false'], 'notNullWithMixed' => ['mixed', '!null', 'mixed'], 'notEmptyWithMyObject' => ['MyObject', '!falsy', 'MyObject'], 'notEmptyWithMyObjectPipeNull' => ['MyObject', '!falsy', 'MyObject|null'], 'notEmptyWithMyObjectPipeFalse' => ['MyObject', '!falsy', 'MyObject|false'], 'notEmptyWithMixed' => ['non-empty-mixed', '!falsy', 'mixed'], // @todo in the future this should also work //'notEmptyWithMyObjectFalseTrue' => ['MyObject|true', '!falsy', 'MyObject|bool'], 'nullWithMyObjectPipeNull' => ['null', 'null', 'MyObject|null'], 'nullWithMixed' => ['null', 'null', 'mixed'], 'falsyWithMyObject' => ['mixed', 'falsy', 'MyObject'], 'falsyWithMyObjectPipeFalse' => ['false', 'falsy', 'MyObject|false'], 'falsyWithMyObjectPipeBool' => ['false', 'falsy', 'MyObject|bool'], 'falsyWithMixed' => ['empty-mixed', 'falsy', 'mixed'], 'falsyWithBool' => ['false', 'falsy', 'bool'], 'falsyWithStringOrNull' => ['null|string()|string(0)', 'falsy', 'string|null'], 'falsyWithScalarOrNull' => ['empty-scalar', 'falsy', 'scalar'], 'notMyObjectWithMyObjectPipeBool' => ['bool', '!MyObject', 'MyObject|bool'], 'notMyObjectWithMyObjectPipeNull' => ['null', '!MyObject', 'MyObject|null'], 'notMyObjectWithMyObjectAPipeMyObjectB' => ['MyObjectB', '!MyObjectA', 'MyObjectA|MyObjectB'], 'myObjectWithMyObjectPipeBool' => ['MyObject', 'MyObject', 'MyObject|bool'], 'myObjectWithMyObjectAPipeMyObjectB' => ['MyObjectA', 'MyObjectA', 'MyObjectA|MyObjectB'], 'array' => ['array', 'array', 'array|null'], '2dArray' => ['array>', 'array', 'array>|null'], 'numeric' => ['numeric-string', 'numeric', 'string'], 'nullableClassString' => ['null', 'falsy', '?class-string'], 'mixedOrNullNotFalsy' => ['non-empty-mixed', '!falsy', 'mixed|null'], 'mixedOrNullFalsy' => ['empty-mixed|null', 'falsy', 'mixed|null'], 'nullableClassStringFalsy' => ['null', 'falsy', 'class-string|null'], 'nullableClassStringEqualsNull' => ['null', '=null', 'class-string|null'], 'nullableClassStringTruthy' => ['class-string', '!falsy', 'class-string|null'], 'iterableToArray' => ['array', 'array', 'iterable'], 'iterableToTraversable' => ['Traversable', 'Traversable', 'iterable'], 'callableToCallableArray' => ['callable-array{0: class-string|object, 1: string}', 'array', 'callable'], 'callableOrArrayToCallableArray' => ['array|callable-array{0: class-string|object, 1: string}', 'array', 'callable|array'], 'traversableToIntersection' => ['Countable&Traversable', 'Traversable', 'Countable'], 'iterableWithoutParamsToTraversableWithoutParams' => ['Traversable', '!array', 'iterable'], 'iterableWithParamsToTraversableWithParams' => ['Traversable', '!array', 'iterable'], ]; } /** * @return array */ public function providerTestTypeIsContainedBy() { return [ 'arrayContainsWithArrayOfStrings' => ['array', 'array'], 'arrayContainsWithArrayOfExceptions' => ['array', 'array'], 'unionContainsWithstring' => ['string', 'string|false'], 'unionContainsWithFalse' => ['false', 'string|false'], 'objectLikeTypeWithPossiblyUndefinedToGeneric' => [ 'array{0: array{a: string}, 1: array{c: string, e: string}}', 'array>', ], 'objectLikeTypeWithPossiblyUndefinedToEmpty' => [ 'array', 'array{a?: string, b?: string}', ], ]; } }