2016-12-06 19:49:57 +01:00
< ? php
namespace Psalm\Tests ;
2017-04-25 05:45:02 +02:00
class SwitchTypeTest extends TestCase
2016-12-06 19:49:57 +01:00
{
2018-11-06 03:57:36 +01:00
use Traits\InvalidCodeAnalysisTestTrait ;
use Traits\ValidCodeAnalysisTestTrait ;
2016-12-06 19:49:57 +01:00
/**
2019-03-01 21:55:20 +01:00
* @ return iterable < string , array { string , assertions ? : array < string , string > , error_levels ? : string []} >
2016-12-06 19:49:57 +01:00
*/
2018-11-06 03:57:36 +01:00
public function providerValidCodeParse ()
2016-12-06 19:49:57 +01:00
{
2017-04-25 05:45:02 +02:00
return [
2017-10-26 21:07:36 +02:00
'getClassConstArg' => [
' < ? php
class A {
/**
* @ return void
*/
public function fooFoo () {
}
}
class B {
/**
* @ return void
*/
public function barBar () {
}
}
2018-01-11 21:50:45 +01:00
$a = rand ( 0 , 10 ) ? new A () : new B ();
2017-10-26 21:07:36 +02:00
switch ( get_class ( $a )) {
case A :: class :
$a -> fooFoo ();
break ;
case B :: class :
$a -> barBar ();
break ;
} ' ,
],
2017-07-25 22:11:02 +02:00
'getClassExteriorArgClassConsts' => [
' < ? php
/** @return void */
function foo ( Exception $e ) {
switch ( get_class ( $e )) {
case InvalidArgumentException :: class :
$e -> getMessage ();
break ;
case LogicException :: class :
$e -> getMessage ();
break ;
}
}
' ,
],
2017-12-13 21:56:05 +01:00
'switchGetClassVar' => [
' < ? php
class A {}
class B extends A {
2018-01-11 21:50:45 +01:00
public function foo () : void {}
2017-07-25 22:11:02 +02:00
}
2018-01-11 21:50:45 +01:00
function takesA ( A $a ) : void {
2017-12-13 21:56:05 +01:00
$class = get_class ( $a );
switch ( $class ) {
case B :: class :
$a -> foo ();
break ;
}
} ' ,
2017-07-25 22:11:02 +02:00
],
2017-04-25 05:45:02 +02:00
'getTypeArg' => [
' < ? php
2018-01-11 21:50:45 +01:00
function testInt ( int $var ) : void {
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
}
2017-07-25 22:11:02 +02:00
2018-01-11 21:50:45 +01:00
function testString ( string $var ) : void {
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
}
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
$a = rand ( 0 , 10 ) ? 1 : " two " ;
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
switch ( gettype ( $a )) {
case " string " :
testString ( $a );
break ;
2017-07-25 22:11:02 +02:00
2017-09-11 17:52:34 +02:00
case " integer " :
2017-04-25 05:45:02 +02:00
testInt ( $a );
break ;
2017-05-27 02:05:57 +02:00
} ' ,
],
2017-10-23 01:53:53 +02:00
'switchTruthy' => [
' < ? php
class A {
/**
* @ var ? string
*/
public $a = null ;
/**
* @ var ? string
*/
public $b = null ;
}
function f ( A $obj ) : string {
switch ( true ) {
case $obj -> a !== null :
return $obj -> a ; // definitely not null
case ! is_null ( $obj -> b ) :
return $obj -> b ; // definitely not null
default :
throw new \InvalidArgumentException ( " $obj->a or $obj->b must be set " );
}
} ' ,
],
'switchMoTruthy' => [
' < ? php
class A {
/**
* @ var ? string
*/
public $a = null ;
/**
* @ var ? string
*/
public $b = null ;
}
function f ( A $obj ) : string {
switch ( true ) {
case $obj -> a :
return $obj -> a ; // definitely not null
case $obj -> b :
return $obj -> b ; // definitely not null
default :
throw new \InvalidArgumentException ( " $obj->a or $obj->b must be set " );
}
} ' ,
],
2017-11-28 06:25:21 +01:00
'switchWithBadBreak' => [
' < ? php
class A {}
function foo () : A {
switch ( rand ( 0 , 1 )) {
case true :
return new A ;
break ;
default :
return new A ;
}
} ' ,
],
'switchCaseExpression' => [
' < ? php
switch ( true ) {
case preg_match ( " /(d)ata/ " , " some data in subject string " , $matches ) :
return $matches [ 1 ];
default :
throw new RuntimeException ( " none found " );
} ' ,
],
2017-11-29 04:33:37 +01:00
'switchBools' => [
' < ? php
$x = false ;
$y = false ;
foreach ([ 1 , 2 , 3 ] as $v ) {
switch ( $v ) {
case 3 :
$y = true ;
break ;
case 2 :
$x = true ;
break ;
default :
break ;
}
} ' ,
'assertions' => [
'$x' => 'bool' ,
'$y' => 'bool' ,
],
],
2018-01-24 06:01:08 +01:00
'continueIsBreak' => [
' < ? php
switch ( 2 ) {
case 2 :
echo " two \n " ;
continue ;
} ' ,
],
2018-01-24 22:15:53 +01:00
'defaultAboveCase' => [
' < ? php
function foo ( string $a ) : string {
switch ( $a ) {
case " a " :
return " hello " ;
default :
case " b " :
return " goodbye " ;
}
} ' ,
],
2018-05-09 15:30:23 +02:00
'dontResolveTypesBadly' => [
' < ? php
$a = new A ;
switch ( rand ( 0 , 1 )) {
case 0 :
case 1 :
$dt = $a -> maybeReturnsDT ();
if ( ! is_null ( $dt )) {
$dt = $dt -> format ( \DateTime :: ISO8601 );
}
break ;
}
class A {
public function maybeReturnsDT () : ? \DateTimeInterface {
return rand ( 0 , 1 ) ? new \DateTime ( " now " ) : null ;
}
} ' ,
],
2018-05-12 18:55:24 +02:00
'issetInFallthrough' => [
' < ? php
function foo () : void {
switch ( rand () % 4 ) {
case 0 :
echo " here " ;
break ;
case 1 :
$x = rand () % 4 ;
case 2 :
if ( isset ( $x ) && $x > 2 ) {
echo " $x is large " ;
}
break ;
}
} ' ,
],
'switchManyGetClass' => [
' < ? php
class A {}
class B extends A {}
class C extends A {}
class D extends A {}
function foo ( A $a ) : void {
switch ( get_class ( $a )) {
case B :: class :
case C :: class :
case D :: class :
echo " goodbye " ;
}
} ' ,
],
2018-05-13 00:46:47 +02:00
'switchManyStrings' => [
' < ? php
function foo ( string $s ) : void {
switch ( $s ) {
case " a " :
case " b " :
case " c " :
echo " goodbye " ;
}
} ' ,
],
'allSwitchesMet' => [
' < ? php
$a = rand ( 0 , 1 ) ? " a " : " b " ;
switch ( $a ) {
case " a " :
$foo = " hello " ;
break ;
case " b " :
$foo = " goodbye " ;
break ;
}
echo $foo ; ' ,
],
'impossibleCaseDefaultWithThrow' => [
' < ? php
$a = rand ( 0 , 1 ) ? " a " : " b " ;
switch ( $a ) {
case " a " :
break ;
case " b " :
break ;
default :
throw new \Exception ( " should never happen " );
} ' ,
],
2018-05-13 02:08:22 +02:00
'switchOnUnknownInts' => [
' < ? php
function foo ( int $a , int $b , int $c ) : void {
switch ( $a ) {
case $b :
break ;
case $c :
break ;
}
} ' ,
],
2018-05-13 06:54:12 +02:00
'switchNullable1' => [
' < ? php
function foo ( ? string $s ) : void {
switch ( $s ) {
case " hello " :
case " goodbye " :
echo " cool " ;
break ;
case " hello again " :
echo " cool " ;
break ;
}
} ' ,
],
'switchNullable2' => [
' < ? php
function foo ( ? string $s ) : void {
switch ( $s ) {
case " hello " :
echo " cool " ;
case " goodbye " :
echo " cooler " ;
break ;
case " hello again " :
echo " cool " ;
break ;
}
} ' ,
],
'switchNullable3' => [
' < ? php
function foo ( ? string $s ) : void {
switch ( $s ) {
case " hello " :
echo " cool " ;
break ;
case " goodbye " :
echo " cool " ;
break ;
case " hello again " :
echo " cool " ;
break ;
}
} ' ,
],
2018-05-13 07:27:45 +02:00
'switchNullable4' => [
' < ? php
function foo ( ? string $s , string $a , string $b ) : void {
switch ( $s ) {
case $a :
case $b :
break ;
}
} ' ,
],
2018-05-14 22:29:51 +02:00
'removeChangedVarsFromReasonableClauses' => [
' < ? php
function r () : bool {
return ( bool ) rand ( 0 , 1 );
}
function foo ( string $s ) : void {
if (( $s === " a " || $s === " b " )
&& ( $s === " a " || r ())
&& ( $s === " b " || r ())
&& ( r () || r ())
) {
// do something
} else {
return ;
}
switch ( $s ) {
case " a " :
break ;
case " b " :
break ;
}
} '
],
'preventBadClausesFromBleeding' => [
' < ? php
function foo ( string $s ) : void {
if ( $s === " a " && rand ( 0 , 1 )) {
} elseif ( $s === " b " && rand ( 0 , 1 )) {
} else {
return ;
}
switch ( $s ) {
case " a " :
echo " hello " ;
break ;
case " b " :
echo " goodbye " ;
break ;
}
} ' ,
],
2018-05-24 20:26:29 +02:00
'alwaysReturns' => [
' < ? php
/**
* @ param " a " | " b " $s
*/
function foo ( string $s ) : string {
switch ( $s ) {
case " a " :
return " hello " ;
case " b " :
return " goodbye " ;
}
} ' ,
],
2018-11-11 02:34:53 +01:00
'switchVarConditionalAssignment' => [
' < ? php
switch ( rand ( 0 , 4 )) {
case 0 :
2018-11-12 04:03:08 +01:00
$b = 2 ;
2018-11-11 02:34:53 +01:00
if ( rand ( 0 , 1 )) {
$a = false ;
break ;
}
default :
$a = true ;
$b = 1 ;
} ' ,
'assertions' => [
'$a' => 'bool' ,
'$b' => 'int' ,
],
],
'switchVarConditionalReAssignment' => [
' < ? php
$a = false ;
switch ( rand ( 0 , 4 )) {
case 0 :
$b = 1 ;
if ( rand ( 0 , 1 )) {
$a = false ;
break ;
}
default :
$a = true ;
} ' ,
'assertions' => [
'$a' => 'bool' ,
],
],
2018-11-28 21:12:08 +01:00
'moreThan30Cases' => [
' < ? php
function f ( string $a ) : void {
switch ( $a ) {
case " a " :
case " b " :
case " c " :
case " d " :
case " e " :
case " f " :
case " g " :
case " h " :
case " i " :
case " j " :
case " k " :
case " l " :
case " m " :
case " n " :
case " o " :
case " p " :
case " q " :
case " r " :
case " s " :
case " t " :
case " u " :
case " v " :
case " w " :
case " x " :
case " y " :
case " z " :
case " A " :
case " B " :
case " C " :
case " D " :
case " E " :
return ;
}
} ' ,
],
2017-04-25 05:45:02 +02:00
];
2016-12-06 19:49:57 +01:00
}
2017-01-13 20:07:23 +01:00
/**
2019-03-01 21:55:20 +01:00
* @ return iterable < string , array { string , error_message : string , 2 ? : string [], 3 ? : bool , 4 ? : string } >
2017-01-13 20:07:23 +01:00
*/
2018-11-06 03:57:36 +01:00
public function providerInvalidCodeParse ()
2016-12-06 19:49:57 +01:00
{
2017-04-25 05:45:02 +02:00
return [
2017-11-28 06:25:21 +01:00
'switchReturnTypeWithFallthroughAndBreak' => [
' < ? php
class A {
/** @return bool */
public function fooFoo () {
switch ( rand ( 0 , 10 )) {
case 1 :
break ;
default :
return true ;
}
}
} ' ,
2018-01-07 23:17:18 +01:00
'error_message' => 'InvalidNullableReturnType' ,
2017-11-28 06:25:21 +01:00
],
'switchReturnTypeWithFallthroughAndConditionalBreak' => [
' < ? php
class A {
/** @return bool */
public function fooFoo () {
switch ( rand ( 0 , 10 )) {
case 1 :
if ( rand ( 0 , 10 ) === 5 ) {
break ;
}
default :
return true ;
}
}
} ' ,
2018-01-07 23:17:18 +01:00
'error_message' => 'InvalidNullableReturnType' ,
2017-11-28 06:25:21 +01:00
],
'switchReturnTypeWithNoDefault' => [
' < ? php
class A {
/** @return bool */
public function fooFoo () {
switch ( rand ( 0 , 10 )) {
case 1 :
case 2 :
return true ;
}
}
} ' ,
2018-01-07 23:17:18 +01:00
'error_message' => 'InvalidNullableReturnType' ,
2017-11-28 06:25:21 +01:00
],
2017-04-25 05:45:02 +02:00
'getClassArgWrongClass' => [
' < ? php
class A {
/** @return void */
public function fooFoo () {
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
}
}
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
class B {
/** @return void */
public function barBar () {
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
}
}
2017-07-25 22:11:02 +02:00
2018-01-11 21:50:45 +01:00
$a = rand ( 0 , 10 ) ? new A () : new B ();
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
switch ( get_class ( $a )) {
2018-11-29 05:59:43 +01:00
case A :: class :
2017-04-25 05:45:02 +02:00
$a -> barBar ();
break ;
} ' ,
2017-05-27 02:05:57 +02:00
'error_message' => 'UndefinedMethod' ,
2017-04-25 05:45:02 +02:00
],
2017-09-11 17:52:34 +02:00
'getClassMissingClass' => [
' < ? php
class A {}
class B {}
2018-01-11 21:50:45 +01:00
$a = rand ( 0 , 10 ) ? new A () : new B ();
2017-09-11 17:52:34 +02:00
switch ( get_class ( $a )) {
2018-11-29 05:59:43 +01:00
case C :: class :
2017-09-11 17:52:34 +02:00
break ;
} ' ,
'error_message' => 'UndefinedClass' ,
],
'getTypeNotAType' => [
' < ? php
$a = rand ( 0 , 10 ) ? 1 : " two " ;
switch ( gettype ( $a )) {
case " int " :
break ;
} ' ,
'error_message' => 'UnevaluatedCode' ,
],
2017-04-25 05:45:02 +02:00
'getTypeArgWrongArgs' => [
' < ? php
2018-01-11 21:50:45 +01:00
function testInt ( int $var ) : void {
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
}
2017-07-25 22:11:02 +02:00
2018-01-11 21:50:45 +01:00
function testString ( string $var ) : void {
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
}
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
$a = rand ( 0 , 10 ) ? 1 : " two " ;
2017-07-25 22:11:02 +02:00
2017-04-25 05:45:02 +02:00
switch ( gettype ( $a )) {
case " string " :
testInt ( $a );
2017-07-25 22:11:02 +02:00
2017-09-11 17:52:34 +02:00
case " integer " :
2017-04-25 05:45:02 +02:00
testString ( $a );
} ' ,
2017-05-27 02:05:57 +02:00
'error_message' => 'InvalidScalarArgument' ,
],
2017-11-28 06:25:21 +01:00
'switchBadMethodCallInCase' => [
' < ? php
2018-01-11 21:50:45 +01:00
function f ( string $p ) : void { }
2017-11-28 06:25:21 +01:00
switch ( true ) {
case $q = ( bool ) rand ( 0 , 1 ) :
f ( $q ); // this type problem is not detected
break ;
} ' ,
'error_message' => 'InvalidScalarArgument' ,
],
2018-01-24 06:01:08 +01:00
'continueIsNotBreak' => [
' < ? php
switch ( 2 ) {
case 2 :
echo " two \n " ;
continue 2 ;
} ' ,
'error_message' => 'ContinueOutsideLoop' ,
],
2018-01-24 22:15:53 +01:00
'defaultAboveCaseThatBreaks' => [
' < ? php
function foo ( string $a ) : string {
switch ( $a ) {
case " a " :
return " hello " ;
default :
case " b " :
break ;
case " c " :
return " goodbye " ;
}
} ' ,
'error_message' => 'InvalidReturnType' ,
],
2018-05-18 17:02:50 +02:00
'SKIPPED-switchManyGetClassWithRepetitionWithProperLineNumber' => [
2018-05-12 18:55:24 +02:00
' < ? php
class A {}
class B extends A {}
class C extends A {}
class D extends A {}
function foo ( A $a ) : void {
switch ( get_class ( $a )) {
case B :: class :
case C :: class :
case B :: class :
case C :: class :
case D :: class :
echo " goodbye " ;
}
} ' ,
'error_message' => 'RedundantCondition - src/somefile.php:10' ,
'error_levels' => [ 'ParadoxicalCondition' ],
],
2018-05-13 00:46:47 +02:00
'repeatedCaseValue' => [
' < ? php
$a = rand ( 0 , 1 );
switch ( $a ) {
case 0 :
break ;
case 0 :
echo " I never get here " ;
} ' ,
2019-02-06 20:19:29 +01:00
'error_message' => 'ParadoxicalCondition - src' . DIRECTORY_SEPARATOR . 'somefile.php:7' ,
2018-05-13 00:46:47 +02:00
],
'impossibleCaseValue' => [
' < ? php
$a = rand ( 0 , 1 ) ? " a " : " b " ;
switch ( $a ) {
case " a " :
break ;
case " b " :
break ;
case " c " :
echo " impossible " ;
} ' ,
2019-02-06 20:19:29 +01:00
'error_message' => 'TypeDoesNotContainType - src' . DIRECTORY_SEPARATOR . 'somefile.php:11' ,
2018-05-13 00:46:47 +02:00
],
'impossibleCaseDefault' => [
' < ? php
$a = rand ( 0 , 1 ) ? " a " : " b " ;
switch ( $a ) {
case " a " :
break ;
case " b " :
break ;
default :
echo " impossible " ;
} ' ,
2019-02-06 20:19:29 +01:00
'error_message' => 'ParadoxicalCondition - src' . DIRECTORY_SEPARATOR . 'somefile.php:11' ,
2018-05-13 00:46:47 +02:00
],
2018-11-12 04:03:08 +01:00
'breakWithoutSettingVar' => [
' < ? php
function foo ( int $i ) : void {
switch ( $i ) {
case 0 :
if ( rand ( 0 , 1 )) {
break ;
}
default :
$a = true ;
}
if ( $a ) {}
} ' ,
'error_message' => 'PossiblyUndefinedVariable'
],
2018-11-29 05:59:43 +01:00
'getClassExteriorArgStringType' => [
' < ? php
/** @return void */
function foo ( Exception $e ) {
switch ( get_class ( $e )) {
case " InvalidArgumentException " :
$e -> getMessage ();
break ;
}
} ' ,
2019-02-27 22:00:44 +01:00
'error_message' => 'TypeDoesNotContainType - src' . DIRECTORY_SEPARATOR . 'somefile.php:5:34 - string(InvalidArgumentException) cannot be identical to class-string' ,
2018-11-29 05:59:43 +01:00
],
2017-04-25 05:45:02 +02:00
];
2016-12-06 19:49:57 +01:00
}
}