diff --git a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php index 518035fa1..38f5dc122 100644 --- a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php @@ -1518,10 +1518,10 @@ class ClassAnalyzer extends ClassLikeAnalyzer ), $storage->suppressed_issues + $this->getSuppressedIssues() )) { - return false; + // fall through } - return null; + return false; } else { if (!$codebase->traitHasCorrectCase($fq_trait_name)) { if (IssueBuffer::accepts( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 281893791..4cbd63dd9 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -58,6 +58,7 @@ class ArgumentAnalyzer ?FunctionLikeParameter $function_param, int $argument_offset, PhpParser\Node\Arg $arg, + ?Type\Union $arg_value_type, Context $context, array $class_generic_params, ?TemplateResult $template_result, @@ -66,8 +67,6 @@ class ArgumentAnalyzer ) { $codebase = $statements_analyzer->getCodebase(); - $arg_value_type = $statements_analyzer->node_data->getType($arg->value); - if (!$arg_value_type) { if ($function_param && !$function_param->by_ref) { if (!$context->collect_initializations diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index b33f66f4f..12095252b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -10,6 +10,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier; use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ArrayFetchAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Codebase\InternalCallMapHandler; +use Psalm\Internal\Stubs\Generator\StubsGenerator; use Psalm\Internal\Type\Comparator\UnionTypeComparator; use Psalm\Internal\MethodIdentifier; use Psalm\Internal\Type\TemplateResult; @@ -92,6 +93,8 @@ class ArgumentsAnalyzer $args = array_reverse($args, true); } + $codebase = $statements_analyzer->getCodebase(); + foreach ($args as $argument_offset => $arg) { if ($function_params === null) { if (self::evaluateAribitraryParam( @@ -155,8 +158,6 @@ class ArgumentsAnalyzer $toggled_class_exists = true; } - $codebase = $statements_analyzer->getCodebase(); - if (($arg->value instanceof PhpParser\Node\Expr\Closure || $arg->value instanceof PhpParser\Node\Expr\ArrowFunction) && $template_result @@ -569,6 +570,35 @@ class ArgumentsAnalyzer $function_param_count = count($function_params); + if (count($function_params) > count($args) && !$has_packed_var) { + for ($i = count($args); $i < count($function_params); $i++) { + if ($function_params[$i]->default_type + && $function_params[$i]->type + && $function_params[$i]->type->hasTemplate() + && $function_params[$i]->default_type->hasLiteralValue() + ) { + ArgumentAnalyzer::checkArgumentMatches( + $statements_analyzer, + $cased_method_id, + $self_fq_class_name, + $static_fq_class_name, + $code_location, + $function_params[$i], + $i, + new PhpParser\Node\Arg( + StubsGenerator::getExpressionFromType($function_params[$i]->default_type) + ), + $function_params[$i]->default_type, + $context, + $class_generic_params, + $template_result, + $function_storage ? $function_storage->specialize_call : true, + $in_call_map + ); + } + } + } + foreach ($args as $argument_offset => $arg) { $function_param = $function_param_count > $argument_offset ? $function_params[$argument_offset] @@ -594,8 +624,10 @@ class ArgumentsAnalyzer } } + $arg_value_type = $statements_analyzer->node_data->getType($arg->value); + if ($method_id === 'compact' - && ($arg_value_type = $statements_analyzer->node_data->getType($arg->value)) + && $arg_value_type && $arg_value_type->isSingleStringLiteral() ) { $literal = $arg_value_type->getSingleStringLiteral(); @@ -622,6 +654,7 @@ class ArgumentsAnalyzer $function_param, $argument_offset, $arg, + $arg_value_type, $context, $class_generic_params, $template_result, diff --git a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php index 23de525a0..d2af5a152 100644 --- a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php @@ -689,6 +689,13 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse $functionlike_storage = array_pop($this->functionlike_storages); + if ($functionlike_storage->docblock_issues + && (strpos($this->file_path, 'CoreGenericFunctions.phpstub') + || strpos($this->file_path, 'CoreGenericClasses.phpstub')) + ) { + throw new \UnexpectedValueException('Error with core stub file docblocks'); + } + if ($functionlike_storage->has_visitor_issues) { $this->file_storage->has_visitor_issues = true; } @@ -2646,6 +2653,8 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse new CodeLocation($this->file_scanner, $stmt, null, true) ); + + continue; } diff --git a/src/Psalm/Internal/Stubs/CoreGenericFunctions.phpstub b/src/Psalm/Internal/Stubs/CoreGenericFunctions.phpstub index bc428b890..e236efd91 100644 --- a/src/Psalm/Internal/Stubs/CoreGenericFunctions.phpstub +++ b/src/Psalm/Internal/Stubs/CoreGenericFunctions.phpstub @@ -634,6 +634,19 @@ function preg_replace($search, $replace, $subject, int $limit = -1, &$count = nu */ function preg_replace_callback($search, $replace, $subject, int $limit = -1, &$count = null) {} +/** + * @psalm-pure + * @template TFlags as int + * + * @param string $pattern + * @param string $subject + * @param mixed $matches + * @param TFlags $flags + * @param-out (TFlags is 256 ? list> : list>) $matches + * @return int + */ +function preg_match_all($pattern, $replace, &$matches, int $flags = 1) {} + /** * @psalm-pure * diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index 7a0260fc0..eefa0959a 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -1322,6 +1322,19 @@ class FunctionCallTest extends TestCase if ($one && array_values($one) === array_values($two)) {} }' ], + 'pregMatchAll' => [ + '> + */ + function extractUsernames(string $input): array { + preg_match_all(\'/@[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/\', $input, $matches); + + return $matches; + }' + ], ]; }