diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index a0af702..76a5b7b 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -700,10 +700,19 @@ protected function postProcessExtension(array &$cache, ActiveTheme $theme) {
       // of preprocess functions grouped by suggestion specificity if a matching
       // base hook is found.
       foreach ($grouped_functions[$first_prefix] as $candidate) {
-        if (preg_match("/^{$prefix}_preprocess_(((?:[^_]++|_(?!_))+)__.*)/", $candidate, $matches)) {
+        // Given a $candidate of 'test_preprocess_foo_bar__baz', $matches will
+        // contain the following values:
+        // 0 => 'test_preprocess_foo_bar__baz', the candidate name.
+        // 1 => 'foo_bar__baz', the theme hook name.
+        // 2 => 'foo_bar', the base hook name.
+        // 3 => 'baz', the suggestion name.
+        if (preg_match("/^{$prefix}_preprocess_(((?:[^_]++|_(?!_))+)__(.*))/", $candidate, $matches)) {
+          // If the base hook exists, this is a valid suggestion.
           if (isset($cache[$matches[2]])) {
             $level = substr_count($matches[1], '__');
-            $suggestion_level[$level][$candidate] = $matches[1];
+            // Use the suggestion name if it corresponds to a full theme hook,
+            // otherwise use the theme hook name.
+            $suggestion_level[$level][$candidate] = isset($cache[$matches[3]]) ? $matches[3] : $matches[1];
           }
         }
       }
@@ -718,6 +727,9 @@ protected function postProcessExtension(array &$cache, ActiveTheme $theme) {
     ksort($suggestion_level);
     foreach ($suggestion_level as $level => $item) {
       foreach ($item as $preprocessor => $hook) {
+        if (isset($cache[$hook]) && !isset($cache[$hook]['preprocess functions'])) {
+          $cache[$hook]['preprocess functions'] = [];
+        }
         if (isset($cache[$hook]['preprocess functions']) && !in_array($hook, $cache[$hook]['preprocess functions'])) {
           // Add missing preprocessor to existing hook.
           $cache[$hook]['preprocess functions'][] = $preprocessor;
diff --git a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
index f2cd22e..13b4919 100644
--- a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
+++ b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
@@ -334,7 +334,9 @@ public function providerTestPostProcessExtension() {
     // specify a base hook, such as is done in
     // \Drupal\Core\Layout\LayoutPluginManager::getThemeImplementations().
     $data['child_hook_without_magic_naming'] = [
-      'defined_functions' => [],
+      'defined_functions' => [
+        'test_preprocess_test_hook__child_hook',
+      ],
       'hooks' => [
         'test_hook' => [
           'preprocess functions' => ['explicit_preprocess_test_hook'],
@@ -355,6 +357,7 @@ public function providerTestPostProcessExtension() {
           'preprocess functions' => [
             'explicit_preprocess_test_hook',
             'explicit_preprocess_child_hook',
+            'test_preprocess_test_hook__child_hook',
           ],
           'base hook' => 'test_hook',
         ],
