Commit f74c740c6ca for php.net

commit f74c740c6ca6e5e1a8ebd1d275c49ec66214fd70
Author: Louis-Arnaud <la.catoire@gmail.com>
Date:   Mon Aug 31 14:21:14 2026 +0200

    ext/filter: Narrow the return type of filter_var_array() to array|false (#23403)

    The stub declares array|false|null, but null is unreachable. The function
    has two exits: RETURN_FALSE for an unknown filter id, and the array handler,
    which establishes an array on both of its branches before doing anything
    else. Its remaining exits throw.

    The sibling filter_input_array() is declared identically and does return
    null, deliberately, because its source superglobal may not exist. That case
    has no equivalent here, where the source is a required array parameter.

    null remains an ordinary element value in the returned array, which is
    likely where the wider union came from; that is the value type, not the
    return type.

diff --git a/UPGRADING b/UPGRADING
index 5a5cafc0234..b6bf7cd0249 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -639,6 +639,12 @@ PHP 8.6 UPGRADE NOTES
 5. Changed Functions
 ========================================

+- Filter:
+  . filter_var_array() return type has been narrowed from array|false|null to
+    array|false. The function always establishes an array before filtering, so
+    null was never returned. filter_input_array() is unaffected: it still
+    returns null when the requested superglobal does not exist.
+
 - GMP:
   . gmp_fact() now throws a ValueError if $num does not fit into an unsigned
     long.
diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h
index cfe758db796..b14de7888a9 100644
--- a/Zend/Optimizer/zend_func_infos.h
+++ b/Zend/Optimizer/zend_func_infos.h
@@ -95,7 +95,7 @@ static const func_info_t func_infos[] = {
 	F1("finfo_buffer", MAY_BE_STRING|MAY_BE_FALSE),
 	F1("mime_content_type", MAY_BE_STRING|MAY_BE_FALSE),
 	F1("filter_input_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_FALSE|MAY_BE_NULL),
-	F1("filter_var_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_FALSE|MAY_BE_NULL),
+	F1("filter_var_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_FALSE),
 	F1("filter_list", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
 	F1("ftp_raw", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_NULL),
 	F1("ftp_nlist", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE),
diff --git a/ext/filter/filter.stub.php b/ext/filter/filter.stub.php
index 4332f9261e9..16a6fe5c940 100644
--- a/ext/filter/filter.stub.php
+++ b/ext/filter/filter.stub.php
@@ -310,7 +310,7 @@ function filter_var(mixed $value, int $filter = FILTER_DEFAULT, array|int $optio
 function filter_input_array(int $type, array|int $options = FILTER_DEFAULT, bool $add_empty = true): array|false|null {}

 /** @refcount 1 */
-function filter_var_array(array $array, array|int $options = FILTER_DEFAULT, bool $add_empty = true): array|false|null {}
+function filter_var_array(array $array, array|int $options = FILTER_DEFAULT, bool $add_empty = true): array|false {}

 /**
  * @return array<int, string>
diff --git a/ext/filter/filter_arginfo.h b/ext/filter/filter_arginfo.h
index 4e24ede41a6..89164736299 100644
Binary files a/ext/filter/filter_arginfo.h and b/ext/filter/filter_arginfo.h differ