Skip to content

Commit 1dd40c0

Browse files
authored
Merge branch refs/heads/1.12.x into 2.0.x
2 parents a477323 + de74813 commit 1dd40c0

File tree

2 files changed

+158
-52
lines changed

2 files changed

+158
-52
lines changed

src/Type/Regex/RegexGroupParser.php

+37-52
Original file line numberDiff line numberDiff line change
@@ -304,35 +304,25 @@ private function createGroupType(TreeNode $group, bool $maybeConstant, string $p
304304
return TypeCombinator::union(...$types);
305305
}
306306

307-
$isNonEmpty = TrinaryLogic::createMaybe();
308-
$isNonFalsy = TrinaryLogic::createMaybe();
309-
$isNumeric = TrinaryLogic::createMaybe();
310-
$inOptionalQuantification = false;
311-
$onlyLiterals = [];
312-
313-
$this->walkGroupAst(
307+
$walkResult = $this->walkGroupAst(
314308
$group,
315309
false,
316-
$isNonEmpty,
317-
$isNonFalsy,
318-
$isNumeric,
319-
$inOptionalQuantification,
320-
$onlyLiterals,
321310
false,
322311
$patternModifiers,
312+
RegexGroupWalkResult::createEmpty(),
323313
);
324314

325-
if ($maybeConstant && $onlyLiterals !== null && $onlyLiterals !== []) {
315+
if ($maybeConstant && $walkResult->getOnlyLiterals() !== null && $walkResult->getOnlyLiterals() !== []) {
326316
$result = [];
327-
foreach ($onlyLiterals as $literal) {
317+
foreach ($walkResult->getOnlyLiterals() as $literal) {
328318
$result[] = new ConstantStringType($literal);
329319

330320
}
331321
return TypeCombinator::union(...$result);
332322
}
333323

334-
if ($isNumeric->yes()) {
335-
if ($isNonFalsy->yes()) {
324+
if ($walkResult->isNumeric()->yes()) {
325+
if ($walkResult->isNonFalsy()->yes()) {
336326
return new IntersectionType([
337327
new StringType(),
338328
new AccessoryNumericStringType(),
@@ -341,13 +331,13 @@ private function createGroupType(TreeNode $group, bool $maybeConstant, string $p
341331
}
342332

343333
$result = new IntersectionType([new StringType(), new AccessoryNumericStringType()]);
344-
if (!$isNonEmpty->yes()) {
334+
if (!$walkResult->isNonEmpty()->yes()) {
345335
return TypeCombinator::union(new ConstantStringType(''), $result);
346336
}
347337
return $result;
348-
} elseif ($isNonFalsy->yes()) {
338+
} elseif ($walkResult->isNonFalsy()->yes()) {
349339
return new IntersectionType([new StringType(), new AccessoryNonFalsyStringType()]);
350-
} elseif ($isNonEmpty->yes()) {
340+
} elseif ($walkResult->isNonEmpty()->yes()) {
351341
return new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]);
352342
}
353343

@@ -376,20 +366,13 @@ private function getRootAlternation(TreeNode $group): ?TreeNode
376366
return null;
377367
}
378368

379-
/**
380-
* @param array<string>|null $onlyLiterals
381-
*/
382369
private function walkGroupAst(
383370
TreeNode $ast,
384371
bool $inAlternation,
385-
TrinaryLogic &$isNonEmpty,
386-
TrinaryLogic &$isNonFalsy,
387-
TrinaryLogic &$isNumeric,
388-
bool &$inOptionalQuantification,
389-
?array &$onlyLiterals,
390372
bool $inClass,
391373
string $patternModifiers,
392-
): void
374+
RegexGroupWalkResult $walkResult,
375+
): RegexGroupWalkResult
393376
{
394377
$children = $ast->getChildren();
395378

@@ -411,61 +394,65 @@ private function walkGroupAst(
411394
}
412395

413396
// a single token non-falsy on its own
414-
$isNonFalsy = TrinaryLogic::createYes();
397+
$walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes());
415398
break;
416399
}
417400

418401
if ($meaningfulTokens > 0) {
419-
$isNonEmpty = TrinaryLogic::createYes();
402+
$walkResult = $walkResult->nonEmpty(TrinaryLogic::createYes());
420403

421404
// two non-empty tokens concatenated results in a non-falsy string
422405
if ($meaningfulTokens > 1 && !$inAlternation) {
423-
$isNonFalsy = TrinaryLogic::createYes();
406+
$walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes());
424407
}
425408
}
426409
} elseif ($ast->getId() === '#quantification') {
427410
[$min] = $this->getQuantificationRange($ast);
428411

429412
if ($min === 0) {
430-
$inOptionalQuantification = true;
413+
$walkResult = $walkResult->inOptionalQuantification(true);
431414
}
432415
if ($min >= 1) {
433-
$isNonEmpty = TrinaryLogic::createYes();
434-
$inOptionalQuantification = false;
416+
$walkResult = $walkResult
417+
->nonEmpty(TrinaryLogic::createYes())
418+
->inOptionalQuantification(false);
435419
}
436420
if ($min >= 2 && !$inAlternation) {
437-
$isNonFalsy = TrinaryLogic::createYes();
421+
$walkResult = $walkResult->nonFalsy(TrinaryLogic::createYes());
438422
}
439423

440-
$onlyLiterals = null;
441-
} elseif ($ast->getId() === '#class' && $onlyLiterals !== null) {
424+
$walkResult = $walkResult->onlyLiterals(null);
425+
} elseif ($ast->getId() === '#class' && $walkResult->getOnlyLiterals() !== null) {
442426
$inClass = true;
443427

444428
$newLiterals = [];
445429
foreach ($children as $child) {
446-
$oldLiterals = $onlyLiterals;
430+
$oldLiterals = $walkResult->getOnlyLiterals();
447431

448432
$this->getLiteralValue($child, $oldLiterals, true, $patternModifiers, true);
449433
foreach ($oldLiterals ?? [] as $oldLiteral) {
450434
$newLiterals[] = $oldLiteral;
451435
}
452436
}
453-
$onlyLiterals = $newLiterals;
437+
$walkResult = $walkResult->onlyLiterals($newLiterals);
454438
} elseif ($ast->getId() === 'token') {
439+
$onlyLiterals = $walkResult->getOnlyLiterals();
455440
$literalValue = $this->getLiteralValue($ast, $onlyLiterals, !$inClass, $patternModifiers, false);
441+
$walkResult = $walkResult->onlyLiterals($onlyLiterals);
442+
456443
if ($literalValue !== null) {
457444
if (Strings::match($literalValue, '/^\d+$/') === null) {
458-
$isNumeric = TrinaryLogic::createNo();
459-
} elseif ($isNumeric->maybe()) {
460-
$isNumeric = TrinaryLogic::createYes();
445+
$walkResult = $walkResult->numeric(TrinaryLogic::createNo());
446+
} elseif ($walkResult->isNumeric()->maybe()) {
447+
$walkResult = $walkResult->numeric(TrinaryLogic::createYes());
461448
}
462449

463-
if (!$inOptionalQuantification && $literalValue !== '') {
464-
$isNonEmpty = TrinaryLogic::createYes();
450+
if (!$walkResult->isInOptionalQuantification() && $literalValue !== '') {
451+
$walkResult = $walkResult->nonEmpty(TrinaryLogic::createYes());
465452
}
466453
}
467454
} elseif (!in_array($ast->getId(), ['#capturing', '#namedcapturing', '#alternation'], true)) {
468-
$onlyLiterals = null;
455+
$walkResult = $walkResult->onlyLiterals(null);
469456
}
470457

471458
if ($ast->getId() === '#alternation') {
@@ -476,22 +463,20 @@ private function walkGroupAst(
476463
// doable but really silly compared to just \d so we can safely assume the string is not numeric
477464
// for negative classes
478465
if ($ast->getId() === '#negativeclass') {
479-
$isNumeric = TrinaryLogic::createNo();
466+
$walkResult = $walkResult->numeric(TrinaryLogic::createNo());
480467
}
481468

482469
foreach ($children as $child) {
483-
$this->walkGroupAst(
470+
$walkResult = $this->walkGroupAst(
484471
$child,
485472
$inAlternation,
486-
$isNonEmpty,
487-
$isNonFalsy,
488-
$isNumeric,
489-
$inOptionalQuantification,
490-
$onlyLiterals,
491473
$inClass,
492474
$patternModifiers,
475+
$walkResult,
493476
);
494477
}
478+
479+
return $walkResult;
495480
}
496481

497482
private function isMaybeEmptyNode(TreeNode $node, string $patternModifiers, bool &$isNonFalsy): bool
+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Regex;
4+
5+
use PHPStan\TrinaryLogic;
6+
7+
/** @immutable */
8+
final class RegexGroupWalkResult
9+
{
10+
11+
/**
12+
* @param array<string>|null $onlyLiterals
13+
*/
14+
public function __construct(
15+
private bool $inOptionalQuantification,
16+
private ?array $onlyLiterals,
17+
private TrinaryLogic $isNonEmpty,
18+
private TrinaryLogic $isNonFalsy,
19+
private TrinaryLogic $isNumeric,
20+
)
21+
{
22+
}
23+
24+
public static function createEmpty(): self
25+
{
26+
return new self(
27+
false,
28+
[],
29+
TrinaryLogic::createMaybe(),
30+
TrinaryLogic::createMaybe(),
31+
TrinaryLogic::createMaybe(),
32+
);
33+
}
34+
35+
public function inOptionalQuantification(bool $inOptionalQuantification): self
36+
{
37+
return new self(
38+
$inOptionalQuantification,
39+
$this->onlyLiterals,
40+
$this->isNonEmpty,
41+
$this->isNonFalsy,
42+
$this->isNumeric,
43+
);
44+
}
45+
46+
/**
47+
* @param array<string>|null $onlyLiterals
48+
*/
49+
public function onlyLiterals(?array $onlyLiterals): self
50+
{
51+
return new self(
52+
$this->inOptionalQuantification,
53+
$onlyLiterals,
54+
$this->isNonEmpty,
55+
$this->isNonFalsy,
56+
$this->isNumeric,
57+
);
58+
}
59+
60+
public function nonEmpty(TrinaryLogic $nonEmpty): self
61+
{
62+
return new self(
63+
$this->inOptionalQuantification,
64+
$this->onlyLiterals,
65+
$nonEmpty,
66+
$this->isNonFalsy,
67+
$this->isNumeric,
68+
);
69+
}
70+
71+
public function nonFalsy(TrinaryLogic $nonFalsy): self
72+
{
73+
return new self(
74+
$this->inOptionalQuantification,
75+
$this->onlyLiterals,
76+
$this->isNonEmpty,
77+
$nonFalsy,
78+
$this->isNumeric,
79+
);
80+
}
81+
82+
public function numeric(TrinaryLogic $numeric): self
83+
{
84+
return new self(
85+
$this->inOptionalQuantification,
86+
$this->onlyLiterals,
87+
$this->isNonEmpty,
88+
$this->isNonFalsy,
89+
$numeric,
90+
);
91+
}
92+
93+
public function isInOptionalQuantification(): bool
94+
{
95+
return $this->inOptionalQuantification;
96+
}
97+
98+
/**
99+
* @return array<string>|null
100+
*/
101+
public function getOnlyLiterals(): ?array
102+
{
103+
return $this->onlyLiterals;
104+
}
105+
106+
public function isNonEmpty(): TrinaryLogic
107+
{
108+
return $this->isNonEmpty;
109+
}
110+
111+
public function isNonFalsy(): TrinaryLogic
112+
{
113+
return $this->isNonFalsy;
114+
}
115+
116+
public function isNumeric(): TrinaryLogic
117+
{
118+
return $this->isNumeric;
119+
}
120+
121+
}

0 commit comments

Comments
 (0)