diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 8f2203205a2..4159b2844a3 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -3330,7 +3330,7 @@ 'gettimeofday' => ['array'], 'gettimeofday\'1' => ['float', 'as_float='=>'true'], 'gettype' => ['string', 'value'=>'mixed'], -'glob' => ['list|false', 'pattern'=>'string', 'flags='=>'int<1, max>'], +'glob' => ['false|list{0?:string, ...}', 'pattern'=>'string', 'flags='=>'int-mask'], 'GlobIterator::__construct' => ['void', 'pattern'=>'string', 'flags='=>'int'], 'GlobIterator::count' => ['int'], 'GlobIterator::current' => ['FilesystemIterator|SplFileInfo|string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 031e013efcc..a719671831f 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -10728,7 +10728,7 @@ 'gettimeofday' => ['array'], 'gettimeofday\'1' => ['float', 'as_float='=>'true'], 'gettype' => ['string', 'value'=>'mixed'], - 'glob' => ['list|false', 'pattern'=>'string', 'flags='=>'int<1, max>'], + 'glob' => ['false|list{0?:string, ...}', 'pattern'=>'string', 'flags='=>'int-mask'], 'gmdate' => ['string', 'format'=>'string', 'timestamp='=>'int'], 'gmmktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], 'gmp_abs' => ['GMP', 'num'=>'GMP|string|int'], diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index aa253094fb5..30ac82c3c1f 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -1709,3 +1709,32 @@ function pg_escape_literal($string1, $string2 = null) {} * @psalm-flow ($string1, $string2) -> return */ function pg_escape_string($string1, $string2 = null) {} + +/** + * @psalm-template P of string + * @psalm-template F of int-mask + * @psalm-param P $pattern + * @psalm-param F $flags + * @psalm-return ( + * P is '' + * ? (F is int-mask + * ? false|list + * : (F is int-mask + * ? false|list{0:''} + * : false|list + * ) + * ) + * : (F is int-mask + * ? false|list + * : (F is int-mask + * ? false|list{0:non-empty-string, ...} + * : false|list + * ) + * ) + * ) + * @psalm-ignore-falsable-return + */ +function glob (string $pattern, int $flags = 0): array|false {} + + + diff --git a/tests/CoreStubsTest.php b/tests/CoreStubsTest.php index 647675b7274..d3d2e2faf22 100644 --- a/tests/CoreStubsTest.php +++ b/tests/CoreStubsTest.php @@ -293,6 +293,119 @@ function after_str_ends_with() echo str_contains($d, "psalm"); ', ]; + yield 'glob return types' => [ + 'code' => <<<'PHP' + */ + $maybeNocheckFlag = 0; + /** @var int-mask */ + $maybeOnlydirFlag = 0; + + /** @var string */ + $string = ''; + + $emptyPatternNoFlags = glob( '' ); + $emptyPatternWithoutNocheckFlag1 = glob( '', GLOB_MARK ); + $emptyPatternWithoutNocheckFlag2 = glob( '' , GLOB_NOSORT | GLOB_NOESCAPE); + $emptyPatternWithoutNocheckFlag3 = glob( '' , GLOB_MARK | GLOB_NOSORT | GLOB_NOESCAPE | GLOB_BRACE | GLOB_ONLYDIR | GLOB_ERR); + $emptyPatternWithNocheckFlag1 = glob( '' , GLOB_NOCHECK); + $emptyPatternWithNocheckFlag2 = glob( '' , GLOB_NOCHECK | GLOB_MARK); + $emptyPatternWithNocheckFlag3 = glob( '' , GLOB_NOCHECK | GLOB_MARK | GLOB_NOSORT | GLOB_NOESCAPE | GLOB_BRACE | GLOB_ERR); + $emptyPatternWithNocheckAndOnlydirFlag1 = glob( '' , GLOB_NOCHECK | GLOB_ONLYDIR); + $emptyPatternWithNocheckAndOnlydirFlag2 = glob( '' , GLOB_NOCHECK | GLOB_ONLYDIR | GLOB_MARK); + $emptyPatternWithNocheckAndOnlydirFlag3 = glob( '' , GLOB_NOCHECK | GLOB_ONLYDIR | GLOB_MARK | GLOB_NOSORT | GLOB_NOESCAPE | GLOB_BRACE | GLOB_ERR); + $emptyPatternWithNocheckFlagAndMaybeOnlydir = glob( '' , GLOB_NOCHECK | $maybeOnlydirFlag); + $emptyPatternMaybeWithNocheckFlag = glob( '' , $maybeNocheckFlag); + $emptyPatternMaybeWithNocheckFlagAndOnlydir = glob( '' , $maybeNocheckFlag | GLOB_ONLYDIR); + $emptyPatternMaybeWithNocheckFlagAndMaybeOnlydir = glob( '' , $maybeNocheckFlag | $maybeOnlydirFlag); + + $nonEmptyPatternNoFlags = glob( 'pattern' ); + $nonEmptyPatternWithoutNocheckFlag1 = glob( 'pattern', GLOB_MARK ); + $nonEmptyPatternWithoutNocheckFlag2 = glob( 'pattern' , GLOB_NOSORT | GLOB_NOESCAPE); + $nonEmptyPatternWithoutNocheckFlag3 = glob( 'pattern' , GLOB_MARK | GLOB_NOSORT | GLOB_NOESCAPE | GLOB_BRACE | GLOB_ONLYDIR | GLOB_ERR); + $nonEmptyPatternWithNocheckFlag1 = glob( 'pattern' , GLOB_NOCHECK); + $nonEmptyPatternWithNocheckFlag2 = glob( 'pattern' , GLOB_NOCHECK | GLOB_MARK); + $nonEmptyPatternWithNocheckFlag3 = glob( 'pattern' , GLOB_NOCHECK | GLOB_MARK | GLOB_NOSORT | GLOB_NOESCAPE | GLOB_BRACE | GLOB_ERR); + $nonEmptyPatternWithNocheckAndOnlydirFlag1 = glob( 'pattern' , GLOB_NOCHECK | GLOB_ONLYDIR); + $nonEmptyPatternWithNocheckAndOnlydirFlag2 = glob( 'pattern' , GLOB_NOCHECK | GLOB_ONLYDIR | GLOB_MARK); + $nonEmptyPatternWithNocheckAndOnlydirFlag3 = glob( 'pattern' , GLOB_NOCHECK | GLOB_ONLYDIR | GLOB_MARK | GLOB_NOSORT | GLOB_NOESCAPE | GLOB_BRACE | GLOB_ERR); + $nonEmptyPatternWithNocheckFlagAndMaybeOnlydir = glob( 'pattern' , GLOB_NOCHECK | $maybeOnlydirFlag); + $nonEmptyPatternMaybeWithNocheckFlag = glob( 'pattern' , $maybeNocheckFlag); + $nonEmptyPatternMaybeWithNocheckFlagAndOnlydir = glob( 'pattern' , $maybeNocheckFlag | GLOB_ONLYDIR); + $nonEmptyPatternMaybeWithNocheckFlagAndMaybeOnlydir = glob( 'pattern' , $maybeNocheckFlag | $maybeOnlydirFlag); + + $stringPatternNoFlags = glob( $string ); + $stringPatternWithoutNocheckFlag1 = glob( $string, GLOB_MARK ); + $stringPatternWithoutNocheckFlag2 = glob( $string , GLOB_NOSORT | GLOB_NOESCAPE); + $stringPatternWithoutNocheckFlag3 = glob( $string , GLOB_MARK | GLOB_NOSORT | GLOB_NOESCAPE | GLOB_BRACE | GLOB_ONLYDIR | GLOB_ERR); + $stringPatternWithNocheckFlag1 = glob( $string , GLOB_NOCHECK); + $stringPatternWithNocheckFlag2 = glob( $string , GLOB_NOCHECK | GLOB_MARK); + $stringPatternWithNocheckFlag3 = glob( $string , GLOB_NOCHECK | GLOB_MARK | GLOB_NOSORT | GLOB_NOESCAPE | GLOB_BRACE | GLOB_ERR); + $stringPatternWithNocheckAndOnlydirFlag1 = glob( $string , GLOB_NOCHECK | GLOB_ONLYDIR); + $stringPatternWithNocheckAndOnlydirFlag2 = glob( $string , GLOB_NOCHECK | GLOB_ONLYDIR | GLOB_MARK); + $stringPatternWithNocheckAndOnlydirFlag3 = glob( $string , GLOB_NOCHECK | GLOB_ONLYDIR | GLOB_MARK | GLOB_NOSORT | GLOB_NOESCAPE | GLOB_BRACE | GLOB_ERR); + $stringPatternWithNocheckFlagAndMaybeOnlydir = glob( $string , GLOB_NOCHECK | $maybeOnlydirFlag); + $stringPatternMaybeWithNocheckFlag = glob( $string , $maybeNocheckFlag); + $stringPatternMaybeWithNocheckFlagAndOnlydir = glob( $string , $maybeNocheckFlag | GLOB_ONLYDIR); + $stringPatternMaybeWithNocheckFlagAndMaybeOnlydir = glob( $string , $maybeNocheckFlag | $maybeOnlydirFlag); + PHP, + 'assertions' => [ + '$emptyPatternNoFlags===' => 'false|list', + '$emptyPatternWithoutNocheckFlag1===' => 'false|list', + '$emptyPatternWithoutNocheckFlag2===' => 'false|list', + '$emptyPatternWithoutNocheckFlag3===' => 'false|list', + '$emptyPatternWithNocheckFlag1===' => 'false|list{\'\'}', + '$emptyPatternWithNocheckFlag2===' => 'false|list{\'\'}', + '$emptyPatternWithNocheckFlag3===' => 'false|list{\'\'}', + '$emptyPatternWithNocheckAndOnlydirFlag1===' => 'false|list', + '$emptyPatternWithNocheckAndOnlydirFlag2===' => 'false|list', + '$emptyPatternWithNocheckAndOnlydirFlag3===' => 'false|list', + '$emptyPatternWithNocheckFlagAndMaybeOnlydir===' => 'false|list{0?: \'\', ...}', + '$emptyPatternMaybeWithNocheckFlag===' => 'false|list{0?: \'\', ...}', + '$emptyPatternMaybeWithNocheckFlagAndOnlydir===' => 'false|list', + '$emptyPatternMaybeWithNocheckFlagAndMaybeOnlydir===' => 'false|list{0?: \'\', ...}', + + '$nonEmptyPatternNoFlags===' => 'false|list', + '$nonEmptyPatternWithoutNocheckFlag1===' => 'false|list', + '$nonEmptyPatternWithoutNocheckFlag2===' => 'false|list', + '$nonEmptyPatternWithoutNocheckFlag3===' => 'false|list', + '$nonEmptyPatternWithNocheckFlag1===' => 'false|non-empty-list', + '$nonEmptyPatternWithNocheckFlag2===' => 'false|non-empty-list', + '$nonEmptyPatternWithNocheckFlag3===' => 'false|non-empty-list', + '$nonEmptyPatternWithNocheckAndOnlydirFlag1===' => 'false|list', + '$nonEmptyPatternWithNocheckAndOnlydirFlag2===' => 'false|list', + '$nonEmptyPatternWithNocheckAndOnlydirFlag3===' => 'false|list', + '$nonEmptyPatternWithNocheckFlagAndMaybeOnlydir===' => 'false|list', + '$nonEmptyPatternMaybeWithNocheckFlag===' => 'false|list', + '$nonEmptyPatternMaybeWithNocheckFlagAndOnlydir===' => 'false|list', + '$nonEmptyPatternMaybeWithNocheckFlagAndMaybeOnlydir===' => 'false|list', + + '$stringPatternNoFlags===' => 'false|list', + '$stringPatternWithoutNocheckFlag1===' => 'false|list', + '$stringPatternWithoutNocheckFlag2===' => 'false|list', + '$stringPatternWithoutNocheckFlag3===' => 'false|list', + '$stringPatternWithNocheckFlag1===' => 'false|list{string, ...}', + '$stringPatternWithNocheckFlag2===' => 'false|list{string, ...}', + '$stringPatternWithNocheckFlag3===' => 'false|list{string, ...}', + '$stringPatternWithNocheckAndOnlydirFlag1===' => 'false|list', + '$stringPatternWithNocheckAndOnlydirFlag2===' => 'false|list', + '$stringPatternWithNocheckAndOnlydirFlag3===' => 'false|list', + '$stringPatternWithNocheckFlagAndMaybeOnlydir===' => 'false|list{0?: string, ...}', + '$stringPatternMaybeWithNocheckFlag===' => 'false|list{0?: string, ...}', + '$stringPatternMaybeWithNocheckFlagAndOnlydir===' => 'false|list', + '$stringPatternMaybeWithNocheckFlagAndMaybeOnlydir===' => 'false|list{0?: string, ...}', + ], + ]; + yield 'glob return ignores false' => [ + 'code' => <<<'PHP' +