diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index ebd3553c36a612fa89cc105d58ce17febca4e591..f15512a558c3163dfbd5f0fb9229f53aa2010b7e 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -81,6 +81,11 @@ class ExtensionDiscovery {
    * The file cache object.
    *
    * @var \Drupal\Component\FileCache\FileCacheInterface
+   *
+   * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
+   *   direct replacement.
+   *
+   * @see https://www.drupal.org/node/3490431
    */
   protected $fileCache;
 
@@ -91,23 +96,36 @@ class ExtensionDiscovery {
    */
   protected $sitePath;
 
+  /**
+   * The info parser.
+   *
+   * Reads .info.yml files efficiently.
+   *
+   * @var \Drupal\Core\Extension\InfoParser|null
+   */
+  protected ?InfoParser $infoParser;
+
   /**
    * Constructs a new ExtensionDiscovery object.
    *
    * @param string $root
    *   The app root.
-   * @param bool $use_file_cache
-   *   Whether file cache should be used.
+   * @param bool $use_info_parser
+   *   Whether the info_parser should be used. Note this argument also
+   *   determines if the deprecated file cache property is set to maintain BC in
+   *   Drupal 11.
    * @param string[] $profile_directories
    *   The available profile directories
    * @param string $site_path
    *   The path to the site.
    */
-  public function __construct(string $root, $use_file_cache = TRUE, ?array $profile_directories = NULL, ?string $site_path = NULL) {
+  public function __construct(string $root, $use_info_parser = TRUE, ?array $profile_directories = NULL, ?string $site_path = NULL) {
     $this->root = $root;
-    $this->fileCache = $use_file_cache ? FileCacheFactory::get('extension_discovery') : NULL;
+    // @phpstan-ignore property.deprecated
+    $this->fileCache = $use_info_parser ? FileCacheFactory::get('extension_discovery') : NULL;
     $this->profileDirectories = $profile_directories;
     $this->sitePath = $site_path;
+    $this->infoParser = $use_info_parser ? new InfoParser($root) : NULL;
   }
 
   /**
@@ -453,12 +471,9 @@ protected function scanDirectory($dir, $include_tests) {
         continue;
       }
 
-      $extension_arguments = $this->fileCache ? $this->fileCache->get($fileinfo->getPathName()) : FALSE;
-      // Ensure $extension_arguments is an array. Previously, the Extension
-      // object was cached and now needs to be replaced with the array.
-      if (empty($extension_arguments) || !is_array($extension_arguments)) {
-        // Determine extension type from info file.
-        $type = FALSE;
+      // Determine the extension type from the info file.
+      $type = FALSE;
+      if ($this->infoParser === NULL) {
         $file = $fileinfo->openFile('r');
         while (!$type && !$file->eof()) {
           preg_match('@^type:\s*(\'|")?(\w+)\1?\s*(?:\#.*)?$@', $file->fgets(), $matches);
@@ -466,43 +481,37 @@ protected function scanDirectory($dir, $include_tests) {
             $type = $matches[2];
           }
         }
-        if (empty($type)) {
-          continue;
-        }
-        $name = $fileinfo->getBasename('.info.yml');
-        $pathname = $dir_prefix . $fileinfo->getSubPathname();
+      }
+      else {
+        $type = $this->infoParser->parse($fileinfo->getPathname())['type'] ?? FALSE;
+      }
+      if (empty($type)) {
+        continue;
+      }
 
-        // Determine whether the extension has a main extension file.
-        // For theme engines, the file extension is .engine.
-        if ($type == 'theme_engine') {
-          $filename = $name . '.engine';
-        }
-        // For profiles/modules/themes, it is the extension type.
-        else {
-          $filename = $name . '.' . $type;
-        }
-        if (!file_exists($this->root . '/' . dirname($pathname) . '/' . $filename)) {
-          $filename = NULL;
-        }
-        $extension_arguments = [
-          'type' => $type,
-          'pathname' => $pathname,
-          'filename' => $filename,
-          'subpath' => $fileinfo->getSubPath(),
-        ];
-
-        if ($this->fileCache) {
-          $this->fileCache->set($fileinfo->getPathName(), $extension_arguments);
-        }
+      $name = $fileinfo->getBasename('.info.yml');
+      $pathname = $dir_prefix . $fileinfo->getSubPathname();
+
+      // Determine whether the extension has a main extension file.
+      // For theme engines, the file extension is .engine.
+      if ($type == 'theme_engine') {
+        $filename = $name . '.engine';
+      }
+      // For profiles/modules/themes, it is the extension type.
+      else {
+        $filename = $name . '.' . $type;
+      }
+      if (!file_exists($this->root . '/' . dirname($pathname) . '/' . $filename)) {
+        $filename = NULL;
       }
 
-      $extension = new Extension($this->root, $extension_arguments['type'], $extension_arguments['pathname'], $extension_arguments['filename']);
+      $extension = new Extension($this->root, $type, $pathname, $filename);
 
       // Track the originating directory for sorting purposes.
-      $extension->subpath = $extension_arguments['subpath'];
+      $extension->subpath = $fileinfo->getSubPath();
       $extension->origin = $dir;
 
-      $files[$extension_arguments['type']][$key] = $extension;
+      $files[$type][$key] = $extension;
     }
     return $files;
   }
diff --git a/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php b/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php
index 1541a1f8bcb9332f14810d5068af4fc47de30247..96ebc050d530b852e566373ca8b86d867143702d 100644
--- a/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php
+++ b/core/modules/system/tests/src/Functional/Form/ModulesListFormWebTest.php
@@ -95,7 +95,7 @@ public function testModulesListFormStatusMessage(): void {
    * Tests the module form with a module with an invalid info.yml file.
    */
   public function testModulesListFormWithInvalidInfoFile(): void {
-    $path = \Drupal::getContainer()->getParameter('site.path') . "/modules/broken";
+    $path = $this->root . '/' . \Drupal::getContainer()->getParameter('site.path') . "/modules/broken";
     mkdir($path, 0777, TRUE);
     $file_path = "$path/broken.info.yml";
 
diff --git a/core/tests/Drupal/Tests/Core/Extension/ExtensionDiscoveryTest.php b/core/tests/Drupal/Tests/Core/Extension/ExtensionDiscoveryTest.php
index 7526cd575fdee15a0e8a3e12d652fa06fd2a6439..bc064e085ba90ab5b4de09c91b95deeb71908cea 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ExtensionDiscoveryTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ExtensionDiscoveryTest.php
@@ -4,7 +4,6 @@
 
 namespace Drupal\Tests\Core\Extension;
 
-use Drupal\Component\FileCache\FileCacheFactory;
 use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\Tests\UnitTestCase;
@@ -66,40 +65,6 @@ public function testExtensionDiscoveryVfs(): void {
     $this->assertEquals($extension_expected, $extensions_by_type['theme_engine']['twig'], 'twig');
   }
 
-  /**
-   * Tests changing extension discovery file cache objects to arrays.
-   *
-   * @covers ::scan
-   * @runInSeparateProcess
-   */
-  public function testExtensionDiscoveryCache(): void {
-    // Set up an extension object in the cache to mimic site prior to changing
-    // \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory() to cache an
-    // array instead of an object. Note we cannot use the VFS file system
-    // because FileCache does not support stream wrappers.
-    $extension = new Extension($this->root, 'module', 'core/modules/user/user.info.yml', 'user.module');
-    $extension->subpath = 'modules/user';
-    $extension->origin = 'core';
-    // Undo \Drupal\Tests\UnitTestCase::setUp() so FileCache works.
-    FileCacheFactory::setConfiguration([]);
-    $file_cache = FileCacheFactory::get('extension_discovery');
-    $file_cache->set($this->root . '/core/modules/user/user.info.yml', $extension);
-
-    // Create an ExtensionDiscovery object to test.
-    $extension_discovery = new ExtensionDiscovery($this->root, TRUE, [], 'sites/default');
-    $modules = $extension_discovery->scan('module', FALSE);
-    $this->assertArrayHasKey('user', $modules);
-    $this->assertEquals((array) $extension, (array) $modules['user']);
-    $this->assertNotSame($extension, $modules['user']);
-    // FileCache item should now be an array.
-    $this->assertSame([
-      'type' => 'module',
-      'pathname' => 'core/modules/user/user.info.yml',
-      'filename' => 'user.module',
-      'subpath' => 'modules/user',
-    ], $file_cache->get($this->root . '/core/modules/user/user.info.yml'));
-  }
-
   /**
    * Tests finding modules that have a trailing comment on the type property.
    *
diff --git a/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php b/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php
index ee6e500b9a797826baf733cce77b7bc399f6b51c..277c7fcefd8a939cb095e78d101283ba60e5f93b 100644
--- a/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php
+++ b/core/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php
@@ -61,6 +61,7 @@ protected function setupBasicExtensions(): void {
     $info_d = <<<'EOS'
 type: theme
 name: Theme D
+core_version_requirement: '*'
 EOS;
 
     $module_a = <<<'EOS'
@@ -637,6 +638,7 @@ public function testGetPendingCustomUpdateFunctions(): void {
     $info_d = <<<'EOS'
 type: theme
 name: Theme D
+core_version_requirement: '*'
 EOS;
 
     $module_a = <<<'EOS'