Commit 2d359a40b74 for php.net

commit 2d359a40b748308403100caec4891c09b9262aa7
Author: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com>
Date:   Mon Jul 27 17:59:25 2026 +0200

    [PFA 3/n] Support PFA in const exprs (#22785)

    * Use a pointer to the declaring opline->lineno as unique addr, instead of the declaring opline itself

    zend_partial_create() takes a 'zend_op* declaring_opline' parameter for the
    purpose of building a cache key: the opline address is unique to this PFA as
    long as it's in SHM.

    However the declaring opline is not available when evaluating a PFA AST. Replace
    the parameter by 'uint32_t* declaring_lineno'. Callers can pass
    '&zend_op->lineno' or '&ast->lineno': Both are unique to the related PFA.

    * zend_partial_create(): Remove the dependency on the declaring op_array

    The declaring op_array is used for multiple purposes:
     * Generating a PFA name
     * Finding the filename
     * Finding whether op_array is cached, and therefore whether the PFA should be
       cached too

    However it's not available when evaluating a PFA AST.

    Remove the declaring_op_array argument, and pass the relevant information
    directly:
     * Generate the PFA name at compile time, pass it as argument to zend_partial_create()
     * Pass the filename and a cacheable flag as argument to zend_partial_create()

    * Support for PFA in constant expressions

diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c
index a4ecb19c85e..d603fe159e2 100644
--- a/Zend/Optimizer/compact_literals.c
+++ b/Zend/Optimizer/compact_literals.c
@@ -26,6 +26,7 @@
 #include "zend_execute.h"
 #include "zend_vm.h"
 #include "zend_extensions.h"
+#include "zend_partial.h"

 #define DEBUG_COMPACT_LITERALS 0

@@ -747,7 +748,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
 					}
 					break;
 				case ZEND_CALLABLE_CONVERT_PARTIAL:
-					opline->op1.num = cache_size;
+					opline->extended_value = cache_size | (opline->extended_value & ZEND_PARTIAL_FLAGS);
 					cache_size += 2 * sizeof(void *);
 					break;
 			}
diff --git a/Zend/tests/partial_application/constexpr_001.phpt b/Zend/tests/partial_application/constexpr_001.phpt
new file mode 100644
index 00000000000..8ab5fc881af
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_001.phpt
@@ -0,0 +1,54 @@
+--TEST--
+PFA in constexpr 001
+--FILE--
+<?php
+
+function f($a = g("foo", ?)) {
+    return $a(1);
+}
+
+function g(string $a, int $b) {
+    return [$a, $b];
+}
+
+class C {
+    static function f($a = self::g("foo", ?)) {
+        return $a(1);
+    }
+
+    static function g(string $a, int $b) {
+        return [$a, $b];
+    }
+}
+
+var_dump(f());
+var_dump(f());
+var_dump(C::f());
+var_dump(C::f());
+
+?>
+--EXPECT--
+array(2) {
+  [0]=>
+  string(3) "foo"
+  [1]=>
+  int(1)
+}
+array(2) {
+  [0]=>
+  string(3) "foo"
+  [1]=>
+  int(1)
+}
+array(2) {
+  [0]=>
+  string(3) "foo"
+  [1]=>
+  int(1)
+}
+array(2) {
+  [0]=>
+  string(3) "foo"
+  [1]=>
+  int(1)
+}
diff --git a/Zend/tests/partial_application/constexpr_002.phpt b/Zend/tests/partial_application/constexpr_002.phpt
new file mode 100644
index 00000000000..05f48b5eb67
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_002.phpt
@@ -0,0 +1,22 @@
+--TEST--
+PFA in constexpr: non-constexpr arg
+--FILE--
+<?php
+
+function f($a = g(new stdClass, $x, ?)) {
+    return $a(1);
+}
+
+function g(string $a, int $b) {
+    return [$a, $b];
+}
+
+try {
+    var_dump(f());
+} catch (Error $e) {
+    echo $e::class, ": ", $e->getMessage(), " on line ", $e->getLine();
+}
+
+?>
+--EXPECTF--
+Fatal error: Constant expression contains invalid operations in %s on line %d
diff --git a/Zend/tests/partial_application/constexpr_003.phpt b/Zend/tests/partial_application/constexpr_003.phpt
new file mode 100644
index 00000000000..4e7b7a17d0b
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_003.phpt
@@ -0,0 +1,25 @@
+--TEST--
+PFA in constexpr: named args
+--FILE--
+<?php
+
+function f($a = g(c: ?, ...)) {
+    return $a(1.5, b: 3);
+}
+
+function g(string $a = 'hello', int $b = 2, float $c = 0) {
+    return [$a, $b, $c];
+}
+
+var_dump(f());
+
+?>
+--EXPECT--
+array(3) {
+  [0]=>
+  string(5) "hello"
+  [1]=>
+  int(3)
+  [2]=>
+  float(1.5)
+}
diff --git a/Zend/tests/partial_application/constexpr_004.phpt b/Zend/tests/partial_application/constexpr_004.phpt
new file mode 100644
index 00000000000..b5fee160a03
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_004.phpt
@@ -0,0 +1,28 @@
+--TEST--
+PFA in constexpr: extra named args
+--FILE--
+<?php
+
+function f($a = g(?, foo: 'bar', ...)) {
+    return $a(1, bar: 'baz');
+}
+
+function g(...$args) {
+    return [$args];
+}
+
+var_dump(f());
+
+?>
+--EXPECT--
+array(1) {
+  [0]=>
+  array(3) {
+    [0]=>
+    int(1)
+    ["foo"]=>
+    string(3) "bar"
+    ["bar"]=>
+    string(3) "baz"
+  }
+}
diff --git a/Zend/tests/partial_application/constexpr_005.phpt b/Zend/tests/partial_application/constexpr_005.phpt
new file mode 100644
index 00000000000..48c830a0b83
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_005.phpt
@@ -0,0 +1,13 @@
+--TEST--
+PFA in constexpr: variadic placeholder must be after positional params
+--FILE--
+<?php
+
+function f($a = g(..., 1)) {
+}
+
+f();
+
+?>
+--EXPECTF--
+Fatal error: Variadic placeholder must be last in %s on line %d
diff --git a/Zend/tests/partial_application/constexpr_006.phpt b/Zend/tests/partial_application/constexpr_006.phpt
new file mode 100644
index 00000000000..873c0b155fe
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_006.phpt
@@ -0,0 +1,13 @@
+--TEST--
+PFA in constexpr: variadic placeholder must be after named params
+--FILE--
+<?php
+
+function f($a = g(..., foo: 1)) {
+}
+
+f();
+
+?>
+--EXPECTF--
+Fatal error: Variadic placeholder must be last in %s on line %d
diff --git a/Zend/tests/partial_application/constexpr_007.phpt b/Zend/tests/partial_application/constexpr_007.phpt
new file mode 100644
index 00000000000..e932812ddf7
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_007.phpt
@@ -0,0 +1,13 @@
+--TEST--
+PFA in constexpr: variadic placeholder may only appear once
+--FILE--
+<?php
+
+function f($a = g(..., ...)) {
+}
+
+f();
+
+?>
+--EXPECTF--
+Fatal error: Variadic placeholder may only appear once in %s on line %d
diff --git a/Zend/tests/partial_application/constexpr_008.phpt b/Zend/tests/partial_application/constexpr_008.phpt
new file mode 100644
index 00000000000..c8f5b36ccb6
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_008.phpt
@@ -0,0 +1,13 @@
+--TEST--
+PFA in constexpr: variadic placeholder not allowed to be named
+--FILE--
+<?php
+
+function f($a = g(foo: ...)) {
+}
+
+f();
+
+?>
+--EXPECTF--
+Parse error: syntax error, unexpected token "..." in %s on line %d
diff --git a/Zend/tests/partial_application/constexpr_010.phpt b/Zend/tests/partial_application/constexpr_010.phpt
new file mode 100644
index 00000000000..e4e05cc30a8
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_010.phpt
@@ -0,0 +1,20 @@
+--TEST--
+PFA in constexpr: error during partial creation
+--FILE--
+<?php
+
+function f($a = g(?)) {
+}
+
+function g($a, $b) {
+}
+
+try {
+    f();
+} catch (Error $e) {
+    echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ArgumentCountError: Partial application of g() expects exactly 2 arguments, 1 given
diff --git a/Zend/tests/partial_application/constexpr_011.phpt b/Zend/tests/partial_application/constexpr_011.phpt
new file mode 100644
index 00000000000..a6480f21479
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_011.phpt
@@ -0,0 +1,43 @@
+--TEST--
+PFA in constexpr: error during arg list evaluation
+--FILE--
+<?php
+
+function f($a = g(strlen(?), e: strlen(?), a: strlen(?), ...)) {
+}
+
+function g($a, $b, $c, $d, $e) {
+}
+
+function h($a = i(strlen(?), a: strlen(?), b: invalid(?), ...)) {
+}
+
+function i(...$args) {
+}
+
+function j($a = g(c: new stdClass, ...), $b = g(c: MISSING_CONST + 1, ...)) {
+}
+
+try {
+    f();
+} catch (Error $e) {
+    echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+try {
+    h();
+} catch (Error $e) {
+    echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+try {
+    j();
+} catch (Error $e) {
+    echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Error: Named parameter $a overwrites previous argument
+Error: Call to undefined function invalid()
+Error: Undefined constant "MISSING_CONST"
diff --git a/Zend/tests/partial_application/constexpr_012.phpt b/Zend/tests/partial_application/constexpr_012.phpt
new file mode 100644
index 00000000000..3c941ed6e13
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_012.phpt
@@ -0,0 +1,38 @@
+--TEST--
+PFA in constexpr: nested const expr
+--FILE--
+<?php
+
+function f($a = g(new stdClass, g(...), ...)) {
+    return $a(1);
+}
+
+function g($a, $b, $c) {
+    return [$a, $b, $c];
+}
+
+var_dump(f());
+
+?>
+--EXPECTF--
+array(3) {
+  [0]=>
+  object(stdClass)#%d (0) {
+  }
+  [1]=>
+  object(Closure)#%d (2) {
+    ["function"]=>
+    string(1) "g"
+    ["parameter"]=>
+    array(3) {
+      ["$a"]=>
+      string(10) "<required>"
+      ["$b"]=>
+      string(10) "<required>"
+      ["$c"]=>
+      string(10) "<required>"
+    }
+  }
+  [2]=>
+  int(1)
+}
diff --git a/Zend/tests/partial_application/constexpr_014.phpt b/Zend/tests/partial_application/constexpr_014.phpt
new file mode 100644
index 00000000000..c75fdb60716
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_014.phpt
@@ -0,0 +1,45 @@
+--TEST--
+PFA in constexpr: sites
+--FILE--
+<?php
+
+#[Attribute]
+class A {
+    function __construct(public mixed $fn) {}
+}
+
+#[A(strlen(?))]
+class C {
+    const CC = strlen(?);
+
+    #[A(strlen(?))]
+    function f($arg = strlen(?)) {
+        return $arg;
+    }
+}
+
+const CC = strlen(?);
+
+echo "# Class const\n";
+var_dump((C::CC)('hello'));
+echo "# Const\n";
+var_dump((CC)('hello'));
+echo "# Class attr\n";
+var_dump((new ReflectionClass(C::class)->getAttributes(A::class)[0]->newInstance()->fn)('hello'));
+echo "# Method attr\n";
+var_dump((new ReflectionMethod(C::class, 'f')->getAttributes(A::class)[0]->newInstance()->fn)('hello'));
+echo "# Method default value\n";
+var_dump((new C()->f())('hello'));
+
+?>
+--EXPECT--
+# Class const
+int(5)
+# Const
+int(5)
+# Class attr
+int(5)
+# Method attr
+int(5)
+# Method default value
+int(5)
diff --git a/Zend/tests/partial_application/constexpr_015.inc b/Zend/tests/partial_application/constexpr_015.inc
new file mode 100644
index 00000000000..bd9dd1674f8
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_015.inc
@@ -0,0 +1,20 @@
+<?php
+
+#[Attribute]
+class A {
+    function __construct(public mixed $fn) {}
+}
+
+#[A(strlen(?))]
+class C {
+    const CC = strlen(?);
+
+    #[A(strlen(?))]
+    function f($arg = strlen(?)) {
+        return $arg;
+    }
+}
+
+const CC = strlen(?);
+
+?>
diff --git a/Zend/tests/partial_application/constexpr_015.phpt b/Zend/tests/partial_application/constexpr_015.phpt
new file mode 100644
index 00000000000..2449adb9e4b
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_015.phpt
@@ -0,0 +1,33 @@
+--TEST--
+PFA in constexpr: preloading
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.preload={PWD}/constexpr_015.inc
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
+?>
+--FILE--
+<?php
+
+echo "# Class const\n";
+var_dump((C::CC)('hello'));
+echo "# Class attr\n";
+var_dump((new ReflectionClass(C::class)->getAttributes(A::class)[0]->newInstance()->fn)('hello'));
+echo "# Method attr\n";
+var_dump((new ReflectionMethod(C::class, 'f')->getAttributes(A::class)[0]->newInstance()->fn)('hello'));
+echo "# Method default value\n";
+var_dump((new C()->f())('hello'));
+
+?>
+--EXPECT--
+# Class const
+int(5)
+# Class attr
+int(5)
+# Method attr
+int(5)
+# Method default value
+int(5)
diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt
index addee498d81..194f08a9c14 100644
--- a/Zend/tests/partial_application/pipe_optimization_004.phpt
+++ b/Zend/tests/partial_application/pipe_optimization_004.phpt
@@ -37,7 +37,7 @@ function foo($a, $b) {
 0005 INIT_FCALL_BY_NAME 2 string("foo")
 0006 SEND_PLACEHOLDER 1
 0007 SEND_PLACEHOLDER 2
-0008 T1 = CALLABLE_CONVERT_PARTIAL %d
+0008 T1 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}")
 0009 INIT_DYNAMIC_CALL 1 T1
 0010 SEND_VAL_EX int(2) 1
 0011 DO_FCALL
diff --git a/Zend/tests/partial_application/pipe_optimization_007.phpt b/Zend/tests/partial_application/pipe_optimization_007.phpt
index 09c3c765d23..be8d773c44c 100644
--- a/Zend/tests/partial_application/pipe_optimization_007.phpt
+++ b/Zend/tests/partial_application/pipe_optimization_007.phpt
@@ -37,7 +37,7 @@ function foo($a, $b) {
 0005 INIT_FCALL_BY_NAME 0 string("foo")
 0006 SEND_PLACEHOLDER string("a")
 0007 SEND_PLACEHOLDER string("b")
-0008 T1 = CALLABLE_CONVERT_PARTIAL %d array(...)
+0008 T1 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") array(...)
 0009 INIT_DYNAMIC_CALL 1 T1
 0010 SEND_VAL_EX int(2) 1
 0011 DO_FCALL
diff --git a/Zend/tests/partial_application/pipe_optimization_008.phpt b/Zend/tests/partial_application/pipe_optimization_008.phpt
index 070074632c7..96f8d88815c 100644
--- a/Zend/tests/partial_application/pipe_optimization_008.phpt
+++ b/Zend/tests/partial_application/pipe_optimization_008.phpt
@@ -36,7 +36,7 @@ function foo($a, $b) {
 0004 DECLARE_FUNCTION string("foo") 0
 0005 INIT_FCALL_BY_NAME 0 string("foo")
 0006 SEND_VAL_EX int(1) string("a")
-0007 T1 = CALLABLE_CONVERT_PARTIAL %d
+0007 T1 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}")
 0008 INIT_DYNAMIC_CALL 1 T1
 0009 SEND_VAL_EX int(2) 1
 0010 DO_FCALL
diff --git a/Zend/tests/partial_application/pipe_optimization_013.phpt b/Zend/tests/partial_application/pipe_optimization_013.phpt
index 7d1a48b2f2e..6c2be6728e7 100644
--- a/Zend/tests/partial_application/pipe_optimization_013.phpt
+++ b/Zend/tests/partial_application/pipe_optimization_013.phpt
@@ -32,7 +32,7 @@ function foo($a, $b) {
 0004 DECLARE_FUNCTION string("foo") 0
 0005 INIT_FCALL_BY_NAME 0 string("foo")
 0006 SEND_VAL_EX int(2) string("b")
-0007 T0 = CALLABLE_CONVERT_PARTIAL 3
+0007 T0 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}")
 0008 INIT_DYNAMIC_CALL 1 T0
 0009 SEND_VAL_EX int(1) 1
 0010 DO_FCALL
diff --git a/Zend/tests/partial_application/rfc_examples_const_expr.phpt b/Zend/tests/partial_application/rfc_examples_const_expr.phpt
index e2fc5744025..23350f6116c 100644
--- a/Zend/tests/partial_application/rfc_examples_const_expr.phpt
+++ b/Zend/tests/partial_application/rfc_examples_const_expr.phpt
@@ -1,7 +1,5 @@
 --TEST--
 PFA RFC examples: "Constant expressions" section
---XFAIL--
-PFA in constant expressions not implemented yet
 --FILE--
 <?php

diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index 1b293c832c9..8e735c6aba7 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -25,6 +25,7 @@
 #include "zend_closures.h"
 #include "zend_constants.h"
 #include "zend_enum.h"
+#include "zend_partial.h"

 ZEND_API zend_ast_process_t zend_ast_process = NULL;

@@ -60,6 +61,8 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(zend_ast *args) {
 	ast->kind = ZEND_AST_CALLABLE_CONVERT;
 	ast->attr = 0;
 	ast->lineno = CG(zend_lineno);
+	ast->filename = NULL;
+	ast->name = NULL;
 	ast->args = args;
 	ZEND_MAP_PTR_INIT(ast->fptr, NULL);

@@ -671,6 +674,82 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex(
 	return r;
 }

+static zend_execute_data *zend_ast_evaluate_arg_list(
+	zend_function *func,
+	zend_ast_list *args_ast,
+	zend_class_entry *scope,
+	void *object_or_called_scope,
+	bool *short_circuited_ptr,
+	zend_ast_evaluate_ctx *ctx,
+	zend_array **named_positions_ptr,
+	bool *uses_variadic_placeholder
+) {
+	zend_execute_data *frame = zend_vm_stack_push_call_frame_ex(
+			zend_vm_calc_used_stack(args_ast->children, func),
+			0, func, 0, object_or_called_scope);
+
+	for (uint32_t i = 0; i < args_ast->children; i++) {
+		zend_ast *arg_ast = args_ast->child[i];
+		uint32_t arg_num = i + 1;
+		zval *arg;
+		zend_string *arg_name = NULL;
+
+		if (arg_ast->kind == ZEND_AST_NAMED_ARG) {
+			void *cache_slot[2] = {0};
+			arg_name = zend_ast_get_str(arg_ast->child[0]);
+			arg = zend_handle_named_arg(&frame, arg_name, &arg_num, (void**)&cache_slot);
+			if (!arg) {
+				goto fail;
+			}
+			if (named_positions_ptr) {
+				if (!*named_positions_ptr) {
+					*named_positions_ptr = zend_new_array(0);
+				}
+				zval tmp;
+				ZVAL_LONG(&tmp, zend_hash_num_elements(*named_positions_ptr));
+				zend_hash_add(*named_positions_ptr, arg_name, &tmp);
+			}
+			arg_ast = arg_ast->child[1];
+		} else {
+			arg = ZEND_CALL_VAR_NUM(frame, ZEND_CALL_NUM_ARGS(frame));
+		}
+
+		if (arg_ast->kind == ZEND_AST_PLACEHOLDER_ARG) {
+			if (arg_ast->attr == ZEND_PLACEHOLDER_VARIADIC) {
+				if (uses_variadic_placeholder) {
+					*uses_variadic_placeholder = true;
+				}
+				continue;
+			} else {
+				Z_TYPE_INFO_P(arg) = _IS_PLACEHOLDER;
+			}
+		} else {
+			if (zend_ast_evaluate_ex(arg, arg_ast, scope, short_circuited_ptr, ctx) == FAILURE) {
+				ZVAL_UNDEF(arg);
+				goto fail;
+			}
+		}
+		if (!arg_name) {
+			ZEND_CALL_NUM_ARGS(frame)++;
+		}
+	}
+
+	return frame;
+
+fail:
+	for (uint32_t i = 0, num_args = ZEND_CALL_NUM_ARGS(frame); i < num_args; i++) {
+		zval_ptr_dtor(ZEND_CALL_VAR_NUM(frame, i));
+	}
+	if (ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
+		zend_array_destroy(frame->extra_named_params);
+	}
+	zend_vm_stack_free_call_frame(frame);
+	if (named_positions_ptr && *named_positions_ptr) {
+		zend_array_destroy(*named_positions_ptr);
+	}
+	return NULL;
+}
+
 static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
 	zval *result,
 	zend_ast *ast,
@@ -1155,10 +1234,6 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(

 			zend_ast_list *args = zend_ast_get_list(fcc_ast->args);
 			ZEND_ASSERT(args->children > 0);
-			if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) {
-				/* TODO: PFAs */
-				zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations");
-			}

 			switch (ast->kind) {
 				case ZEND_AST_CALL: {
@@ -1248,9 +1323,46 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
 				default: ZEND_UNREACHABLE();
 			}

-			zend_create_fake_closure(result, fptr, fptr->common.scope, called_scope, NULL);
+			bool is_fcc = args->children == 1 && args->child[0]->attr == ZEND_PLACEHOLDER_VARIADIC;

-			return SUCCESS;
+			if (is_fcc) {
+				zend_create_fake_closure(result, fptr, fptr->common.scope, called_scope, NULL);
+				return SUCCESS;
+			}
+
+			zend_array *named_positions = NULL;
+			bool uses_variadic_placeholder = false;
+			zend_execute_data *frame = zend_ast_evaluate_arg_list(
+					fptr, args, scope, called_scope, short_circuited_ptr, ctx,
+					&named_positions, &uses_variadic_placeholder);
+			if (!frame) {
+				ZEND_ASSERT(EG(exception));
+				return FAILURE;
+			}
+
+			void *cache_slot[2] = {0};
+			zend_array *extra_named_params = ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS
+						? frame->extra_named_params
+						: NULL;
+			uint32_t flags = (fcc_ast->attr & ZEND_PARTIAL_CACHEABLE_IN_SHM);
+			if (uses_variadic_placeholder) {
+				flags |= ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER;
+			}
+			zend_partial_create(result, &frame->This, fptr,
+					ZEND_CALL_NUM_ARGS(frame), ZEND_CALL_ARG(frame, 1),
+					extra_named_params, named_positions,
+					fcc_ast->filename, &ast->lineno,
+					(void**)cache_slot, fcc_ast->name, flags);
+
+			if (named_positions) {
+				zend_array_release(named_positions);
+			}
+			if (extra_named_params) {
+				zend_array_release(extra_named_params);
+			}
+			zend_vm_stack_free_call_frame(frame);
+
+			return EG(exception) ? FAILURE : SUCCESS;
 		}
 		case ZEND_AST_OP_ARRAY:
 		{
@@ -1425,6 +1537,16 @@ static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf)
 		new->kind = old->kind;
 		new->attr = old->attr;
 		new->lineno = old->lineno;
+		if (old->filename) {
+			new->filename = zend_string_copy(old->filename);
+		} else {
+			new->filename = NULL;
+		}
+		if (old->name) {
+			new->name = zend_string_copy(old->name);
+		} else {
+			new->name = NULL;
+		}
 		ZEND_MAP_PTR_INIT(new->fptr, ZEND_MAP_PTR(old->fptr));
 		buf = (void*)((char*)buf + sizeof(zend_ast_fcc));
 		new->args = buf;
@@ -1525,6 +1647,12 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast)
 	} else if (EXPECTED(ast->kind == ZEND_AST_CALLABLE_CONVERT)) {
 		zend_ast_fcc *fcc_ast = (zend_ast_fcc*) ast;

+		if (fcc_ast->filename) {
+			zend_string_release_ex(fcc_ast->filename, 0);
+		}
+		if (fcc_ast->name) {
+			zend_string_release_ex(fcc_ast->name, 0);
+		}
 		ast = fcc_ast->args;
 		goto tail_call;
 	}
@@ -1543,6 +1671,9 @@ ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *contex
 		for (i = 0; i < list->children; ++i) {
 			fn(&list->child[i], context);
 		}
+	} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
+		zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast;
+		fn(&fcc_ast->args, context);
 	} else if (zend_ast_is_decl(ast)) {
 		/* Not implemented. */
 		ZEND_UNREACHABLE();
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index 26f54102a14..5d646f428c1 100644
--- a/Zend/zend_ast.h
+++ b/Zend/zend_ast.h
@@ -233,6 +233,8 @@ typedef struct _zend_ast_fcc {
 	zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */
 	zend_ast_attr attr; /* Additional attribute, use depending on node type */
 	uint32_t lineno;    /* Line number */
+	zend_string *filename;
+	zend_string *name;
 	zend_ast *args;
 	ZEND_MAP_PTR_DEF(zend_function *, fptr);
 } zend_ast_fcc;
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 2466710f9ce..420a7cc0865 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -40,6 +40,7 @@
 #include "zend_call_stack.h"
 #include "zend_frameless_function.h"
 #include "zend_property_hooks.h"
+#include "zend_partial.h"

 #define SET_NODE(target, src) do { \
 		target ## _type = (src)->op_type; \
@@ -4088,7 +4089,50 @@ ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, const zend_function *f
 }
 /* }}} */

-static void zend_compile_call_partial(znode *result, uint32_t arg_count,
+static zend_string *zend_compile_partial_name(const zend_op_array *declaring_op_array,
+		const uint32_t declaring_lineno)
+{
+	/* We attempt to generate a name that hints at where the PFA was created,
+	 * similarly to Closures (GH-13550).
+	 * We do not attempt to make the name unique. */
+
+	zend_string *filename = declaring_op_array->filename;
+	uint32_t start_lineno = declaring_lineno;
+
+	zend_string *class = zend_empty_string;
+	zend_string *separator = zend_empty_string;
+	zend_string *function = filename;
+	const char *parens = "";
+
+	if (declaring_op_array->function_name) {
+		function = declaring_op_array->function_name;
+		if (declaring_op_array->fn_flags & ZEND_ACC_CLOSURE) {
+			/* If the parent function is a closure, don't redundantly
+			 * add the classname and parentheses. */
+		} else {
+			parens = "()";
+
+			if (declaring_op_array->scope && declaring_op_array->scope->name) {
+				class = declaring_op_array->scope->name;
+				separator = ZSTR_KNOWN(ZEND_STR_PAAMAYIM_NEKUDOTAYIM);
+			}
+		}
+	}
+
+	zend_string *name = zend_strpprintf_unchecked(
+		0,
+		"{closure:pfa:%S%S%S%s:%" PRIu32 "}",
+		class,
+		separator,
+		function,
+		parens,
+		start_lineno
+	);
+
+	return name;
+}
+
+static void zend_compile_call_partial(znode *result, zend_ast_fcc *fcc_ast, uint32_t arg_count,
 		bool may_have_extra_named_args, bool uses_variadic_placeholder,
 		zval *named_positions, uint32_t opnum_init, const zend_function *fbc) {

@@ -4105,15 +4149,16 @@ static void zend_compile_call_partial(znode *result, uint32_t arg_count,
 	zend_op *opline = zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT_PARTIAL,
 				NULL, NULL);

-	opline->op1.num = zend_alloc_cache_slots(2);
+	opline->extended_value = zend_alloc_cache_slots(2);

-	if (may_have_extra_named_args) {
-		opline->extended_value = ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS;
-	}
 	if (uses_variadic_placeholder) {
-		opline->extended_value |= ZEND_FCALL_USES_VARIADIC_PLACEHOLDER;
+		opline->extended_value |= ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER;
 	}

+	zend_string *name = zend_compile_partial_name(CG(active_op_array), opline->lineno);
+	opline->op1.constant = zend_add_literal_string(&name);
+	opline->op1_type = IS_CONST;
+
 	if (!Z_ISUNDEF_P(named_positions)) {
 		opline->op2.constant = zend_add_literal(named_positions);
 		opline->op2_type = IS_CONST;
@@ -4156,7 +4201,8 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze
 			return true;
 		}

-		args_ast = ((zend_ast_fcc*)args_ast)->args;
+		zend_ast_fcc *fcc_ast = (zend_ast_fcc*)args_ast;
+		args_ast = fcc_ast->args;

 		bool may_have_extra_named_args;
 		bool uses_variadic_placeholder;
@@ -4168,7 +4214,7 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze
 				&may_have_extra_named_args, true, &uses_variadic_placeholder,
 				&named_positions);

-		zend_compile_call_partial(result, arg_count,
+		zend_compile_call_partial(result, fcc_ast, arg_count,
 				may_have_extra_named_args, uses_variadic_placeholder,
 				&named_positions, opnum_init, fbc);

@@ -11856,7 +11902,8 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
 		|| kind == ZEND_AST_NAMED_ARG
 		|| kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP
 		|| kind == ZEND_AST_CLOSURE
-		|| kind == ZEND_AST_CALL || kind == ZEND_AST_STATIC_CALL || kind == ZEND_AST_CALLABLE_CONVERT;
+		|| kind == ZEND_AST_CALL || kind == ZEND_AST_STATIC_CALL || kind == ZEND_AST_CALLABLE_CONVERT
+		|| kind == ZEND_AST_PLACEHOLDER_ARG;
 }
 /* }}} */

@@ -12021,24 +12068,20 @@ static void zend_compile_const_expr_closure(zend_ast **ast_ptr)

 static void zend_compile_const_expr_fcc(zend_ast **ast_ptr)
 {
-	zend_ast **args_ast;
-	switch ((*ast_ptr)->kind) {
-		case ZEND_AST_CALL:
-			args_ast = &(*ast_ptr)->child[1];
-			break;
-		case ZEND_AST_STATIC_CALL:
-			args_ast = &(*ast_ptr)->child[2];
-			break;
-		default: ZEND_UNREACHABLE();
-	}
-	if ((*args_ast)->kind != ZEND_AST_CALLABLE_CONVERT) {
+	zend_ast *args_ast = zend_ast_call_get_args(*ast_ptr);
+
+	if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT) {
 		zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations");
 	}

-	zend_ast_list *args = zend_ast_get_list(((zend_ast_fcc*)*args_ast)->args);
-	if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) {
-		// TODO: PFAs
-		zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations");
+	zend_ast_fcc *fcc = (zend_ast_fcc*)args_ast;
+
+	zend_ast_list *args = zend_ast_get_list(fcc->args);
+	bool is_fcc = args->children == 1 && args->child[0]->attr == ZEND_PLACEHOLDER_VARIADIC;
+
+	if (!is_fcc) {
+		fcc->filename = zend_string_copy(CG(active_op_array)->filename);
+		fcc->name = zend_compile_partial_name(CG(active_op_array), fcc->lineno);
 	}

 	switch ((*ast_ptr)->kind) {
@@ -12080,6 +12123,7 @@ static void zend_compile_const_expr_args(zend_ast **ast_ptr)
 {
 	zend_ast_list *list = zend_ast_get_list(*ast_ptr);
 	bool uses_named_args = false;
+	bool uses_variadic_placeholder = false;
 	for (uint32_t i = 0; i < list->children; i++) {
 		const zend_ast *arg = list->child[i];
 		if (arg->kind == ZEND_AST_UNPACK) {
@@ -12088,11 +12132,31 @@ static void zend_compile_const_expr_args(zend_ast **ast_ptr)
 		}
 		if (arg->kind == ZEND_AST_NAMED_ARG) {
 			uses_named_args = true;
-		} else if (uses_named_args) {
-			zend_error_noreturn(E_COMPILE_ERROR,
-				"Cannot use positional argument after named argument");
+			if (uses_variadic_placeholder) {
+				zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder must be last");
+			}
+		} else {
+			bool is_variadic_placeholder = arg->kind == ZEND_AST_PLACEHOLDER_ARG
+				&& arg->attr == ZEND_PLACEHOLDER_VARIADIC;
+
+			if (uses_named_args && !is_variadic_placeholder) {
+				zend_error_noreturn(E_COMPILE_ERROR, "Cannot use positional argument after named argument");
+			}
+
+			if (uses_variadic_placeholder) {
+				if (is_variadic_placeholder) {
+					zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder may only appear once");
+				} else {
+					zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder must be last");
+				}
+			}
+
+			if (is_variadic_placeholder) {
+				uses_variadic_placeholder = true;
+			}
 		}
 	}
+
 	if (uses_named_args) {
 		list->attr = 1;
 	}
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 69d7aeb2f37..3d4e6f3c3f9 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -1121,7 +1121,6 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
 #define ZEND_THROW_IS_EXPR 1u

 #define ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS (1<<0)
-#define ZEND_FCALL_USES_VARIADIC_PLACEHOLDER   (1<<1)

 /* The send mode, the is_variadic, the is_promoted, and the is_tentative flags are stored as part of zend_type */
 #define _ZEND_SEND_MODE_SHIFT _ZEND_TYPE_EXTRA_FLAGS_SHIFT
diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c
index e7a3e1c92b5..a78db3f51ef 100644
--- a/Zend/zend_partial.c
+++ b/Zend/zend_partial.c
@@ -51,6 +51,7 @@
 #include "zend_closures.h"
 #include "zend_attributes.h"
 #include "zend_exceptions.h"
+#include "zend_partial.h"
 #include "ext/opcache/ZendAccelerator.h"

 static zend_always_inline bool Z_IS_PLACEHOLDER_P(const zval *p) {
@@ -502,49 +503,6 @@ static zend_ast *zp_param_attributes_to_ast(zend_function *function,
 	return attributes_ast;
 }

-static zend_string *zp_pfa_name(const zend_op_array *declaring_op_array,
-		const zend_op *declaring_opline)
-{
-	/* We attempt to generate a name that hints at where the PFA was created,
-	 * similarly to Closures (GH-13550).
-	 * We do not attempt to make the name unique. */
-
-	zend_string *filename = declaring_op_array->filename;
-	uint32_t start_lineno = declaring_opline->lineno;
-
-	zend_string *class = zend_empty_string;
-	zend_string *separator = zend_empty_string;
-	zend_string *function = filename;
-	const char *parens = "";
-
-	if (declaring_op_array->function_name) {
-		function = declaring_op_array->function_name;
-		if (declaring_op_array->fn_flags & ZEND_ACC_CLOSURE) {
-			/* If the parent function is a closure, don't redundantly
-			 * add the classname and parentheses. */
-		} else {
-			parens = "()";
-
-			if (declaring_op_array->scope && declaring_op_array->scope->name) {
-				class = declaring_op_array->scope->name;
-				separator = ZSTR_KNOWN(ZEND_STR_PAAMAYIM_NEKUDOTAYIM);
-			}
-		}
-	}
-
-	zend_string *name = zend_strpprintf_unchecked(
-		0,
-		"{closure:pfa:%S%S%S%s:%" PRIu32 "}",
-		class,
-		separator,
-		function,
-		parens,
-		start_lineno
-	);
-
-	return name;
-}
-
 /* Generate the AST for calling the actual function */
 static zend_ast *zp_compile_forwarding_call(
 	zval *this_ptr, zend_function *function,
@@ -701,9 +659,9 @@ static uint32_t zp_compute_num_required(const zend_function *function,
 static zend_op_array *zp_compile(zval *this_ptr, zend_function *function,
 		uint32_t argc, zval *argv, zend_array *extra_named_params,
 		const zend_array *named_positions,
-		const zend_op_array *declaring_op_array,
-		const zend_op *declaring_opline, void **cache_slot,
-		bool uses_variadic_placeholder) {
+		zend_string *declaring_filename,
+		const uint32_t *declaring_lineno_ptr, void **cache_slot,
+		zend_string *pfa_name, uint32_t flags) {

 	zend_op_array *op_array = NULL;

@@ -713,6 +671,8 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function,
 		return NULL;
 	}

+	bool uses_variadic_placeholder = flags & ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER;
+
 	if (UNEXPECTED(zp_args_check(function, argc, argv, extra_named_params, uses_variadic_placeholder) != SUCCESS)) {
 		ZEND_ASSERT(EG(exception));
 		return NULL;
@@ -1012,10 +972,8 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function,
 	}
 #endif

-	zend_string *pfa_name = zp_pfa_name(declaring_op_array, declaring_opline);
-
-	op_array = zend_accel_compile_pfa(closure_ast, declaring_op_array,
-			declaring_opline, function, pfa_name);
+	op_array = zend_accel_compile_pfa(closure_ast, declaring_filename,
+			declaring_lineno_ptr, function, pfa_name, flags & ZEND_PARTIAL_CACHEABLE_IN_SHM);

 	zend_ast_destroy(closure_ast);

@@ -1040,9 +998,9 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function,
 static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function,
 		uint32_t argc, zval *argv, zend_array *extra_named_params,
 		const zend_array *named_positions,
-		const zend_op_array *declaring_op_array,
-		const zend_op *declaring_opline, void **cache_slot,
-		bool uses_variadic_placeholder) {
+		zend_string *declaring_filename,
+		const uint32_t *declaring_lineno_ptr, void **cache_slot,
+		zend_string *pfa_name, uint32_t flags) {

 	if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION
 					? cache_slot[0] == function
@@ -1051,13 +1009,13 @@ static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *funct
 		return cache_slot[1];
 	}

-	const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_op_array,
-			declaring_opline, function);
+	const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_lineno_ptr, function,
+			flags & ZEND_PARTIAL_CACHEABLE_IN_SHM);

 	if (UNEXPECTED(!op_array)) {
 		op_array = zp_compile(this_ptr, function, argc, argv,
-			extra_named_params, named_positions, declaring_op_array, declaring_opline,
-			cache_slot, uses_variadic_placeholder);
+			extra_named_params, named_positions, declaring_filename, declaring_lineno_ptr,
+			cache_slot, pfa_name, flags);
 	}

 	if (EXPECTED(op_array) && !(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)) {
@@ -1135,14 +1093,16 @@ static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval *
 void zend_partial_create(zval *result, zval *this_ptr, zend_function *function,
 		uint32_t argc, zval *argv, zend_array *extra_named_params,
 		const zend_array *named_positions,
-		const zend_op_array *declaring_op_array,
-		const zend_op *declaring_opline, void **cache_slot,
-		bool uses_variadic_placeholder) {
+		zend_string *declaring_filename,
+		const uint32_t *declaring_lineno_ptr, void **cache_slot,
+		zend_string *pfa_name, uint32_t flags) {
+
+	ZEND_ASSERT(pfa_name);

 	const zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv,
 			extra_named_params, named_positions,
-			declaring_op_array, declaring_opline,
-			cache_slot, uses_variadic_placeholder);
+			declaring_filename, declaring_lineno_ptr,
+			cache_slot, pfa_name, flags);

 	if (UNEXPECTED(!op_array)) {
 		ZEND_ASSERT(EG(exception));
diff --git a/Zend/zend_partial.h b/Zend/zend_partial.h
index 3c47305ff54..386823261e2 100644
--- a/Zend/zend_partial.h
+++ b/Zend/zend_partial.h
@@ -21,12 +21,22 @@

 BEGIN_EXTERN_C()

+#define ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER (1<<0)
+/* Whether the PFA is cacheable in SHM. This flag is set only when the op_array that declares the PFA is itself in SHM,
+ * for two reasons: Avoids SHM churn, and ensures that the PFA cache key is unique. */
+#define ZEND_PARTIAL_CACHEABLE_IN_SHM          (1<<1)
+#define ZEND_PARTIAL_FLAGS                     (ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER|ZEND_PARTIAL_CACHEABLE_IN_SHM)
+
+/* Create a partial application of 'function'
+ * 'declaring_lineno_ptr' should be a pointer the zend_op.lineno or
+ * zend_ast.lineno that declares the PFA. The address is used to build a cache
+ * key. */
 void zend_partial_create(zval *result, zval *this_ptr, zend_function *function,
 		uint32_t argc, zval *argv, zend_array *extra_named_params,
 		const zend_array *named_positions,
-		const zend_op_array *declaring_op_array,
-		const zend_op *declaring_opline, void **cache_slot,
-		bool uses_variadic_placeholder);
+		zend_string *declaring_filename,
+		const uint32_t *declaring_lineno_ptr, void **cache_slot,
+		zend_string *pfa_name, uint32_t flags);

 void zend_partial_op_array_dtor(zval *pDest);

diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index cd74145799b..3466bc68d69 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -9866,14 +9866,15 @@ ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED, NUM|CACHE_SLOT)
 	ZEND_VM_NEXT_OPCODE();
 }

-ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NUM)
+ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CONST, CONST|UNUSED, NUM)
 {
 	USE_OPLINE
 	SAVE_OPLINE();

 	zend_execute_data *call = EX(call);
-	void **cache_slot = CACHE_ADDR(opline->op1.num);
+	void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS);
 	zval *named_positions = GET_OP2_ZVAL_PTR();
+	zend_string *pfa_name = Z_STR_P(GET_OP1_ZVAL_PTR());

 	zend_partial_create(EX_VAR(opline->result.var),
 		&call->This, call->func,
@@ -9881,8 +9882,8 @@ ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NU
 		(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ?
 			call->extra_named_params : NULL,
 		OP2_TYPE == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL,
-		&EX(func)->op_array, opline, cache_slot,
-		opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER);
+		EX(func)->op_array.filename, &opline->lineno, cache_slot,
+		pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS);

 	if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
 		zend_array_release(call->extra_named_params);
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 8d2e0670ee3..c0a23e49d02 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h
index 7ffe2c220a0..503a6d25634 100644
Binary files a/Zend/zend_vm_handlers.h and b/Zend/zend_vm_handlers.h differ
diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c
index 9846e36037b..f9b30edb5e9 100644
Binary files a/Zend/zend_vm_opcodes.c and b/Zend/zend_vm_opcodes.c differ
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index 6f7cc8a6552..8685fb56415 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -2024,7 +2024,7 @@ static char *zend_accel_uintptr_hex(char *dest, uintptr_t n)
  * a scheme, except file:// and phar://. */
 #define PFA_KEY_PREFIX "pfa://"

-static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline,
+static zend_string *zend_accel_pfa_key(const uint32_t *declaring_lineno_ptr,
 		const zend_function *called_function)
 {
 	const size_t max_key_len = strlen(PFA_KEY_PREFIX) + (sizeof(uintptr_t)*2) + strlen(":") + (sizeof(uintptr_t)*2);
@@ -2032,7 +2032,7 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline,

 	char *dest = ZSTR_VAL(key);
 	dest = zend_mempcpy(dest, PFA_KEY_PREFIX, strlen(PFA_KEY_PREFIX));
-	dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_opline);
+	dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_lineno_ptr);
 	*dest++ = ':';

 	const void *ptr;
@@ -2054,17 +2054,17 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline,
 	return key;
 }

-const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array,
-		const zend_op *declaring_opline, const zend_function *called_function)
+const zend_op_array *zend_accel_pfa_cache_get(
+		const uint32_t *declaring_lineno_ptr, const zend_function *called_function, bool cacheable_in_shm)
 {
-	zend_string *key = zend_accel_pfa_key(declaring_opline, called_function);
+	zend_string *key = zend_accel_pfa_key(declaring_lineno_ptr, called_function);
 	zend_op_array *op_array = NULL;

-	/* A PFA is SHM-cacheable if the declaring_op_array and called_function are
+	/* A PFA is SHM-cacheable if the declaring op_array and called_function are
 	 * cached. */
 	if (ZCG(accelerator_enabled)
 			&& !file_cache_only
-			&& !declaring_op_array->refcount
+			&& cacheable_in_shm /* declaring op_array is cached */
 			&& (called_function->type != ZEND_USER_FUNCTION || !called_function->op_array.refcount)) {
 		zend_persistent_script *persistent_script = zend_accel_hash_find(&ZCSG(hash), key);
 		if (persistent_script) {
@@ -2084,11 +2084,13 @@ const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_
 }

 zend_op_array *zend_accel_compile_pfa(zend_ast *ast,
-		const zend_op_array *declaring_op_array,
-		const zend_op *declaring_opline,
+		zend_string *declaring_filename,
+		const uint32_t *declaring_lineno_ptr,
 		const zend_function *called_function,
-		zend_string *pfa_func_name)
+		zend_string *pfa_func_name, bool cacheable_in_shm)
 {
+	ZEND_ASSERT(zend_accel_in_shm((void*)declaring_lineno_ptr) || !cacheable_in_shm);
+
 	zend_begin_record_errors();
 	zend_op_array *op_array;

@@ -2106,7 +2108,7 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast,
 		CG(compiler_options) |= ZEND_COMPILE_IGNORE_INTERNAL_CLASSES;
 #endif

-		op_array = zend_compile_ast(ast, ZEND_USER_FUNCTION, declaring_op_array->filename);
+		op_array = zend_compile_ast(ast, ZEND_USER_FUNCTION, declaring_filename);

 		CG(compiler_options) = orig_compiler_options;
 	} zend_catch {
@@ -2119,21 +2121,21 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast,
 	ZEND_ASSERT(op_array->num_dynamic_func_defs == 1);

 	zend_string_release(op_array->dynamic_func_defs[0]->function_name);
-	op_array->dynamic_func_defs[0]->function_name = pfa_func_name;
+	op_array->dynamic_func_defs[0]->function_name = zend_string_copy(pfa_func_name);

-	zend_string *key = zend_accel_pfa_key(declaring_opline, called_function);
+	zend_string *key = zend_accel_pfa_key(declaring_lineno_ptr, called_function);

 	/* Cache op_array only if the declaring op_array and the called function
 	 * are cached */
 	if (!ZCG(accelerator_enabled)
 			|| file_cache_only
-			|| declaring_op_array->refcount
+			|| !cacheable_in_shm /* declaring op_array is not in SHM */
 			|| (called_function->type == ZEND_USER_FUNCTION && called_function->op_array.refcount)
 			|| (ZCSG(restart_in_progress) && accel_restart_is_active())
 			|| (!ZCG(counted) && accel_activate_add() == FAILURE)) {
 		zend_op_array *script_op_array = op_array;
 		zend_op_array *op_array = script_op_array->dynamic_func_defs[0];
-		GC_ADDREF(op_array->function_name);
+		GC_TRY_ADDREF(op_array->function_name);
 		(*op_array->refcount)++;
 		destroy_op_array(script_op_array);
 		efree(script_op_array);
diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h
index ef7eea433c0..b76e5d1d4be 100644
--- a/ext/opcache/ZendAccelerator.h
+++ b/ext/opcache/ZendAccelerator.h
@@ -334,15 +334,14 @@ zend_string* ZEND_FASTCALL accel_new_interned_string(zend_string *str);

 uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name);

-const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array,
-		const zend_op *declaring_opline,
-		const zend_function *called_function);
+const zend_op_array *zend_accel_pfa_cache_get(
+		const uint32_t *declaring_lineno_ptr, const zend_function *called_function, bool cacheable_in_shm);

 zend_op_array *zend_accel_compile_pfa(zend_ast *ast,
-		const zend_op_array *declaring_op_array,
-		const zend_op *declaring_opline,
+		zend_string *declaring_filename,
+		const uint32_t *declaring_lineno_ptr,
 		const zend_function *called_function,
-		zend_string *pfa_func_name);
+		zend_string *pfa_func_name, bool cacheable_in_shm);

 END_EXTERN_C()

diff --git a/ext/opcache/tests/array_map_foreach_optimization_008.phpt b/ext/opcache/tests/array_map_foreach_optimization_008.phpt
index 8a406ef11ca..36c80b460e8 100644
--- a/ext/opcache/tests/array_map_foreach_optimization_008.phpt
+++ b/ext/opcache/tests/array_map_foreach_optimization_008.phpt
@@ -33,7 +33,7 @@ function plusn($x, $n) {
 0005 INIT_FCALL 2 %d string("array_map")
 0006 INIT_FCALL 0 %d string("plusn")
 0007 SEND_VAL int(2) string("n")
-0008 T2 = CALLABLE_CONVERT_PARTIAL 2
+0008 T2 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}")
 0009 SEND_VAL T2 1
 0010 SEND_VAR CV0($array) 2
 0011 T2 = DO_ICALL
diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c
index 265d5de4147..0b6cabe4201 100644
--- a/ext/opcache/zend_file_cache.c
+++ b/ext/opcache/zend_file_cache.c
@@ -22,6 +22,7 @@
 #include "zend_attributes.h"
 #include "zend_system_id.h"
 #include "zend_enum.h"
+#include "zend_partial.h"

 #include "php.h"
 #ifdef ZEND_WIN32
@@ -382,6 +383,10 @@ static void zend_file_cache_serialize_ast(zend_ast                 *ast,
 	} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
 		zend_ast_fcc *fcc = (zend_ast_fcc*)ast;
 		ZEND_MAP_PTR_INIT(fcc->fptr, NULL);
+		/* Will be reset if needed during unserialize */
+		fcc->attr |= ZEND_PARTIAL_CACHEABLE_IN_SHM;
+		SERIALIZE_STR(fcc->filename);
+		SERIALIZE_STR(fcc->name);
 		if (!IS_SERIALIZED(fcc->args)) {
 			SERIALIZE_PTR(fcc->args);
 			tmp = fcc->args;
@@ -636,6 +641,12 @@ static void zend_file_cache_serialize_op_array(zend_op_array            *op_arra
 					break;
 			}
 #endif
+
+			if (opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) {
+				/* Will be reset if needed during unserialize */
+				opline->extended_value |= ZEND_PARTIAL_CACHEABLE_IN_SHM;
+			}
+
 			zend_serialize_opcode_handler(opline);
 			opline++;
 		}
@@ -1308,9 +1319,13 @@ static void zend_file_cache_unserialize_ast(zend_ast                *ast,
 		zend_ast_get_op_array(ast)->op_array = Z_PTR(z);
 	} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
 		zend_ast_fcc *fcc = (zend_ast_fcc*)ast;
-		if (!script->corrupted) {
+		if (script->corrupted) {
+			fcc->attr &= ~ZEND_PARTIAL_CACHEABLE_IN_SHM;
+		} else {
 			ZEND_MAP_PTR_NEW(fcc->fptr);
 		}
+		UNSERIALIZE_STR(fcc->filename);
+		UNSERIALIZE_STR(fcc->name);
 		if (!IS_UNSERIALIZED(fcc->args)) {
 			UNSERIALIZE_PTR(fcc->args);
 			zend_file_cache_unserialize_ast(fcc->args, script, buf);
@@ -1541,6 +1556,13 @@ static void zend_file_cache_unserialize_op_array(zend_op_array           *op_arr
 				zval *literal = RT_CONSTANT(opline, opline->op1);
 				UNSERIALIZE_ATTRIBUTES(Z_PTR_P(literal));
 			}
+
+			if (opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) {
+				if (script->corrupted) {
+					opline->extended_value &= ~ZEND_PARTIAL_CACHEABLE_IN_SHM;
+				}
+			}
+
 			zend_deserialize_opcode_handler(opline);
 			opline++;
 		}
diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c
index d3e719dbed7..134551781ba 100644
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@ -27,6 +27,7 @@
 #include "zend_operators.h"
 #include "zend_interfaces.h"
 #include "zend_attributes.h"
+#include "zend_partial.h"

 #ifdef HAVE_JIT
 # include "Optimizer/zend_func_info.h"
@@ -197,6 +198,13 @@ static zend_ast *zend_persist_ast(zend_ast *ast)
 		zend_ast_fcc *copy = zend_shared_memdup(ast, sizeof(zend_ast_fcc));
 		if (!ZCG(current_persistent_script)->corrupted) {
 			ZEND_MAP_PTR_NEW(copy->fptr);
+			copy->attr |= ZEND_PARTIAL_CACHEABLE_IN_SHM;
+		}
+		if (copy->filename) {
+			zend_accel_store_interned_string(copy->filename);
+		}
+		if (copy->name) {
+			zend_accel_store_interned_string(copy->name);
 		}
 		copy->args = zend_persist_ast(copy->args);
 		node = (zend_ast *) copy;
@@ -624,6 +632,12 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
 				attributes = zend_persist_attributes(attributes);
 				ZVAL_PTR(literal, attributes);
 			}
+
+			if (opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) {
+				if (!ZCG(current_persistent_script)->corrupted) {
+					opline->extended_value |= ZEND_PARTIAL_CACHEABLE_IN_SHM;
+				}
+			}
 		}

 		efree(op_array->opcodes);
diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c
index 9ff37079193..71398024e7c 100644
--- a/ext/opcache/zend_persist_calc.c
+++ b/ext/opcache/zend_persist_calc.c
@@ -92,6 +92,12 @@ static void zend_persist_ast_calc(zend_ast *ast)
 	} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
 		zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast;
 		ADD_SIZE(sizeof(zend_ast_fcc));
+		if (fcc_ast->filename) {
+			ADD_INTERNED_STRING(fcc_ast->filename);
+		}
+		if (fcc_ast->name) {
+			ADD_INTERNED_STRING(fcc_ast->name);
+		}
 		zend_persist_ast_calc(fcc_ast->args);
 	} else if (zend_ast_is_decl(ast)) {
 		/* Not implemented. */