|
17 | 17 | use PHPStan\Parser\CurlSetOptArgVisitor; |
18 | 18 | use PHPStan\Parser\CurlSetOptArrayArgVisitor; |
19 | 19 | use PHPStan\Parser\ImplodeArgVisitor; |
| 20 | +use PHPStan\Parser\PregReplaceCallbackArgVisitor; |
20 | 21 | use PHPStan\Reflection\Callables\CallableParametersAcceptor; |
21 | 22 | use PHPStan\Reflection\Native\NativeParameterReflection; |
22 | 23 | use PHPStan\Reflection\Php\DummyParameter; |
|
27 | 28 | use PHPStan\Type\ArrayType; |
28 | 29 | use PHPStan\Type\BooleanType; |
29 | 30 | use PHPStan\Type\CallableType; |
| 31 | +use PHPStan\Type\Constant\ConstantArrayType; |
30 | 32 | use PHPStan\Type\Constant\ConstantArrayTypeBuilder; |
31 | 33 | use PHPStan\Type\Constant\ConstantIntegerType; |
32 | 34 | use PHPStan\Type\Generic\TemplateTypeMap; |
33 | 35 | use PHPStan\Type\Generic\TemplateTypeVarianceMap; |
| 36 | +use PHPStan\Type\IntegerRangeType; |
34 | 37 | use PHPStan\Type\IntegerType; |
35 | 38 | use PHPStan\Type\IntersectionType; |
36 | 39 | use PHPStan\Type\MixedType; |
|
59 | 62 | use const ARRAY_FILTER_USE_KEY; |
60 | 63 | use const CURLOPT_SHARE; |
61 | 64 | use const CURLOPT_SSL_VERIFYHOST; |
| 65 | +use const PREG_OFFSET_CAPTURE; |
| 66 | +use const PREG_UNMATCHED_AS_NULL; |
62 | 67 |
|
63 | 68 | /** |
64 | 69 | * @api |
@@ -372,6 +377,82 @@ public static function selectFromArgs( |
372 | 377 | } |
373 | 378 | } |
374 | 379 |
|
| 380 | + foreach ([1, 0] as $pregArgIndex) { |
| 381 | + if (!isset($args[$pregArgIndex])) { |
| 382 | + continue; |
| 383 | + } |
| 384 | + |
| 385 | + $pregFlagsExpr = $args[$pregArgIndex]->getAttribute(PregReplaceCallbackArgVisitor::ATTRIBUTE_NAME); |
| 386 | + if (!$pregFlagsExpr instanceof Node\Expr) { |
| 387 | + continue; |
| 388 | + } |
| 389 | + |
| 390 | + $flagsType = $scope->getType($pregFlagsExpr); |
| 391 | + if (!$flagsType instanceof ConstantIntegerType) { |
| 392 | + break; |
| 393 | + } |
| 394 | + |
| 395 | + $flags = $flagsType->getValue(); |
| 396 | + $offsetCapture = ($flags & PREG_OFFSET_CAPTURE) !== 0; |
| 397 | + $unmatchedAsNull = ($flags & PREG_UNMATCHED_AS_NULL) !== 0; |
| 398 | + |
| 399 | + if (!$offsetCapture && !$unmatchedAsNull) { |
| 400 | + break; |
| 401 | + } |
| 402 | + |
| 403 | + $matchValueType = new StringType(); |
| 404 | + if ($unmatchedAsNull) { |
| 405 | + $matchValueType = TypeCombinator::addNull($matchValueType); |
| 406 | + } |
| 407 | + if ($offsetCapture) { |
| 408 | + $matchValueType = new ConstantArrayType( |
| 409 | + [new ConstantIntegerType(0), new ConstantIntegerType(1)], |
| 410 | + [$matchValueType, IntegerRangeType::fromInterval(-1, null)], |
| 411 | + [2], |
| 412 | + isList: TrinaryLogic::createYes(), |
| 413 | + ); |
| 414 | + } |
| 415 | + |
| 416 | + $callbackParameter = new DummyParameter( |
| 417 | + 'matches', |
| 418 | + new ArrayType(new UnionType([new IntegerType(), new StringType()]), $matchValueType), |
| 419 | + optional: false, |
| 420 | + passedByReference: PassedByReference::createNo(), |
| 421 | + variadic: false, |
| 422 | + defaultValue: null, |
| 423 | + ); |
| 424 | + $callbackType = new CallableType([$callbackParameter], new StringType(), false); |
| 425 | + |
| 426 | + $acceptor = $parametersAcceptors[0]; |
| 427 | + $parameters = $acceptor->getParameters(); |
| 428 | + if (isset($parameters[$pregArgIndex])) { |
| 429 | + $pregReplaceCallbackIsArray = $pregArgIndex === 0; |
| 430 | + $newParamType = $pregReplaceCallbackIsArray |
| 431 | + ? new ArrayType(new StringType(), $callbackType) |
| 432 | + : $callbackType; |
| 433 | + $parameters[$pregArgIndex] = new NativeParameterReflection( |
| 434 | + $parameters[$pregArgIndex]->getName(), |
| 435 | + $parameters[$pregArgIndex]->isOptional(), |
| 436 | + $newParamType, |
| 437 | + $parameters[$pregArgIndex]->passedByReference(), |
| 438 | + $parameters[$pregArgIndex]->isVariadic(), |
| 439 | + $parameters[$pregArgIndex]->getDefaultValue(), |
| 440 | + ); |
| 441 | + $parametersAcceptors = [ |
| 442 | + new FunctionVariant( |
| 443 | + $acceptor->getTemplateTypeMap(), |
| 444 | + $acceptor->getResolvedTemplateTypeMap(), |
| 445 | + $parameters, |
| 446 | + $acceptor->isVariadic(), |
| 447 | + $acceptor->getReturnType(), |
| 448 | + $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), |
| 449 | + ), |
| 450 | + ]; |
| 451 | + } |
| 452 | + |
| 453 | + break; |
| 454 | + } |
| 455 | + |
375 | 456 | $closureBindToVar = $args[0]->getAttribute(ClosureBindToVarVisitor::ATTRIBUTE_NAME); |
376 | 457 | if ( |
377 | 458 | $closureBindToVar instanceof Node\Expr\Variable |
|
0 commit comments