diff --git a/lib/Compiler.php b/lib/Compiler.php index c58e0474b83..a9eaa01b470 100755 --- a/lib/Compiler.php +++ b/lib/Compiler.php @@ -2046,6 +2046,11 @@ protected function getOpCodeTypeFromUnaryOp(Op\Expr $expr): int { } protected function compileExpr(Op\Expr $expr, Block $block): array { + if ($expr instanceof Op\Expr\BinaryOp\Coalesce) { + $this->compileCoalesce($expr, $block); + + return []; + } if ($expr instanceof Op\Expr\BinaryOp) { $opcode = new OpCode( $this->getOpCodeTypeFromBinaryOp($expr), diff --git a/patches/php-types-generic-null-tail.patch b/patches/php-types-generic-null-tail.patch new file mode 100644 index 00000000000..183665b9e78 --- /dev/null +++ b/patches/php-types-generic-null-tail.patch @@ -0,0 +1,15 @@ +diff --git vendor/ircmaxell/php-types/lib/PHPTypes/Type.php vendor/ircmaxell/php-types/lib/PHPTypes/Type.php +--- vendor/ircmaxell/php-types/lib/PHPTypes/Type.php ++++ vendor/ircmaxell/php-types/lib/PHPTypes/Type.php +@@ -372,6 +372,11 @@ + } + $decl = self::stripTrailingDocText($decl); + $trimmedDecl = trim($decl); ++ // list union splits may pass a trailing "null>" fragment (#2276). ++ if (str_ends_with($trimmedDecl, '>') && !str_contains($trimmedDecl, '<')) { ++ $trimmedDecl = rtrim(substr($trimmedDecl, 0, -1)); ++ $decl = $trimmedDecl; ++ } + if ('' === $trimmedDecl || '*' === $trimmedDecl || '*/' === $trimmedDecl + || str_starts_with($trimmedDecl, '*/')) { + return self::mixed(); diff --git a/prelinked/bootstrap-vendor/sources/ircmaxell/php-types/lib/PHPTypes/Type.php b/prelinked/bootstrap-vendor/sources/ircmaxell/php-types/lib/PHPTypes/Type.php index 9243b56412b..1bdb911b242 100755 --- a/prelinked/bootstrap-vendor/sources/ircmaxell/php-types/lib/PHPTypes/Type.php +++ b/prelinked/bootstrap-vendor/sources/ircmaxell/php-types/lib/PHPTypes/Type.php @@ -372,6 +372,11 @@ public static function fromDecl($decl): self } $decl = self::stripTrailingDocText($decl); $trimmedDecl = trim($decl); + // list union splits may pass a trailing "null>" fragment (#2276). + if (str_ends_with($trimmedDecl, '>') && !str_contains($trimmedDecl, '<')) { + $trimmedDecl = rtrim(substr($trimmedDecl, 0, -1)); + $decl = $trimmedDecl; + } if ('' === $trimmedDecl || '*' === $trimmedDecl || '*/' === $trimmedDecl || str_starts_with($trimmedDecl, '*/')) { return self::mixed(); diff --git a/script/apply-patches.sh b/script/apply-patches.sh index 05e9c7658e6..5a176d265e1 100755 --- a/script/apply-patches.sh +++ b/script/apply-patches.sh @@ -75,6 +75,9 @@ patch_already_applied() { php-types-generics-list-array.patch) grep -qF "preg_match('/^(list|array)" "$ROOT/vendor/ircmaxell/php-types/lib/PHPTypes/Type.php" 2>/dev/null ;; + php-types-generic-null-tail.patch) + grep -q 'list union splits' "$ROOT/vendor/ircmaxell/php-types/lib/PHPTypes/Type.php" 2>/dev/null + ;; php-types-docblock-trailing-text.patch) grep -q "stripTrailingDocText" "$ROOT/vendor/ircmaxell/php-types/lib/PHPTypes/Type.php" 2>/dev/null ;; @@ -1922,6 +1925,7 @@ if [[ -d "$ROOT/vendor/ircmaxell/php-types" ]]; then apply_patch "$PATCH_DIR/php-types-array-shape.patch" apply_patch "$PATCH_DIR/php-types-generics-fallback.patch" apply_patch "$PATCH_DIR/php-types-generics-list-array.patch" + apply_patch "$PATCH_DIR/php-types-generic-null-tail.patch" apply_patch "$PATCH_DIR/php-types-docblock-trailing-text.patch" apply_patch "$PATCH_DIR/php-types-fromdecl-junk-fragments.patch" apply_patch "$PATCH_DIR/php-types-ns-func-call.patch" diff --git a/test/unit/CoalesceVmTest.php b/test/unit/CoalesceVmTest.php index ed935195cb1..ac6cad9f5c7 100644 --- a/test/unit/CoalesceVmTest.php +++ b/test/unit/CoalesceVmTest.php @@ -83,6 +83,18 @@ public function testNullCoalesceAssignEchoInline(): void } /** Issue #3462: ($x ?? throw new Ex()) — RHS only when LHS is null. */ + public function testNullCoalesceNestedInCast(): void + { + $this->assertVmOutput( + ' 0644]; +echo (int) ($stat["mode"] ?? 0), "\n"; +echo (int) ($stat["missing"] ?? 0), "\n"; +', + "420\n0\n" + ); + } + public function testNullCoalesceThrow(): void { $this->assertVmOutput( diff --git a/test/unit/PhpcLintBootstrapInventoryTest.php b/test/unit/PhpcLintBootstrapInventoryTest.php index 498f7f37feb..ffd869f63f9 100644 --- a/test/unit/PhpcLintBootstrapInventoryTest.php +++ b/test/unit/PhpcLintBootstrapInventoryTest.php @@ -44,6 +44,14 @@ public function testBootstrapInventoryFixturePathsLintClean(): void } } + /** Issue #2276 — nested ?? in casts must not report Expr_BinaryOp_Coalesce. */ + public function testBootstrapSpineVmFsLintClean(): void + { + $repoRoot = dirname(__DIR__, 2); + $exit = $this->runLint([$repoRoot.'/ext/standard/VmFs.php']); + $this->assertSame(0, $exit['code'], $exit['stderr']."\n".$exit['stdout']); + } + public function testPhpcDelegatesBootstrapInventory(): void { $repoRoot = dirname(__DIR__, 2);