diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php
index a0b305ee2649275aa99ad3aa88d94a0c3444e5cf..99f48104f6a7e5a30d9679804f6499f31966a696 100644
--- a/core/lib/Drupal/Core/Config/Schema/Mapping.php
+++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php
@@ -119,8 +119,8 @@ protected function getDefinedKeys(): array {
    *   - the corresponding value an array of the additional mapping keys that
    *     are supported for this resolved type
    *
-   * @see \Drupal\Core\Config\TypedConfigManager::replaceName()
-   * @see \Drupal\Core\Config\TypedConfigManager::replaceVariable()
+   * @see \Drupal\Core\Config\TypedConfigManager::resolveDynamicTypeName()
+   * @see \Drupal\Core\Config\TypedConfigManager::resolveExpression()
    * @see https://www.drupal.org/files/ConfigSchemaCheatSheet2.0.pdf
    */
   public function getDynamicallyValidKeys(): array {
diff --git a/core/lib/Drupal/Core/Config/TypedConfigManager.php b/core/lib/Drupal/Core/Config/TypedConfigManager.php
index 054247d06a3bd7c88e381618358647536e51270a..7d1981c76d40cd18a21c9b408fc7e82f8afc688c 100644
--- a/core/lib/Drupal/Core/Config/TypedConfigManager.php
+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -104,7 +104,7 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
       if (isset($name)) {
         $replace['%key'] = $name;
       }
-      $type = $this->replaceName($type, $replace);
+      $type = $this->resolveDynamicTypeName($type, $replace);
       // Remove the type from the definition so that it is replaced with the
       // concrete type from schema definitions.
       unset($definition['type']);
@@ -150,7 +150,7 @@ protected function determineType($base_plugin_id, array $definitions) {
   }
 
   /**
-   * Gets a schema definition with replacements for dynamic names.
+   * Gets a schema definition with replacements for dynamic type names.
    *
    * @param string $base_plugin_id
    *   A plugin ID.
@@ -177,7 +177,7 @@ protected function getDefinitionWithReplacements($base_plugin_id, array $replace
 
       // Replace dynamic portions of the definition type.
       if (!empty($replacements) && strpos($definition['type'], ']')) {
-        $sub_type = $this->determineType($this->replaceName($definition['type'], $replacements), $definitions);
+        $sub_type = $this->determineType($this->resolveDynamicTypeName($definition['type'], $replacements), $definitions);
         $sub_definition = $definitions[$sub_type];
         if (isset($definitions[$sub_type]['type'])) {
           $sub_merge = $this->getDefinition($definitions[$sub_type]['type'], $exception_on_invalid);
@@ -282,39 +282,69 @@ protected function getFallbackName($name) {
   }
 
   /**
-   * Replaces variables in configuration name.
+   * Replaces dynamic type expressions in configuration type.
    *
-   * The configuration name may contain one or more variables to be replaced,
-   * enclosed in square brackets like '[name]' and will follow the replacement
-   * rules defined by the replaceVariable() method.
+   * The configuration type name may contain one or more expressions to be
+   * replaced, enclosed in square brackets like '[name]' or '[%parent.id]' and
+   * will follow the replacement rules defined by the resolveExpression()
+   * method.
    *
-   * @param string $name
-   *   Configuration name with variables in square brackets.
-   * @param mixed $data
+   * @param string $type
+   *   Configuration type, potentially with expressions in square brackets.
+   * @param array $data
    *   Configuration data for the element.
    *
    * @return string
-   *   Configuration name with variables replaced.
+   *   Configuration type name with all expressions resolved.
    */
-  protected function replaceName($name, $data) {
-    if (preg_match_all("/\[(.*)\]/U", $name, $matches)) {
+  protected function resolveDynamicTypeName(string $type, array $data): string {
+    // Parse the expressions in the dynamic type, if any.
+    if (preg_match_all("/\[(.*)\]/U", $type, $matches)) {
       // Build our list of '[value]' => replacement.
       $replace = [];
       foreach (array_combine($matches[0], $matches[1]) as $key => $value) {
-        $replace[$key] = $this->replaceVariable($value, $data);
+        $replace[$key] = $this->resolveExpression($value, $data);
       }
-      return strtr($name, $replace);
+      return strtr($type, $replace);
     }
     else {
-      return $name;
+      // No expressions: nothing to resolve.
+      return $type;
     }
   }
 
   /**
-   * Replaces variable values in included names with configuration data.
+   * Replaces dynamic type expressions in configuration type.
+   *
+   * The configuration type name may contain one or more expressions to be
+   * replaced, enclosed in square brackets like '[name]' or '[%parent.id]' and
+   * will follow the replacement rules defined by the resolveExpression()
+   * method.
+   *
+   * @param string $name
+   *   Configuration type, potentially with expressions in square brackets.
+   * @param array $data
+   *   Configuration data for the element.
+   *
+   * @return string
+   *   Configuration type name with all expressions resolved.
+   *
+   * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
+   *   ::resolveDynamicTypeName() instead.
+   *
+   * @see https://www.drupal.org/node/3408266
+   */
+  protected function replaceName($name, $data) {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use ::resolveDynamicTypeName() instead. See https://www.drupal.org/node/3408266', E_USER_DEPRECATED);
+    return $this->resolveDynamicTypeName($name, $data);
+  }
+
+  /**
+   * Resolves a dynamic type expression using configuration data.
    *
-   * Variable values are nested configuration keys that will be replaced by
-   * their value or some of these special strings:
+   * Dynamic type names are nested configuration keys containing expressions to
+   * be replaced by the value at the property path that the expression is
+   * pointing at. The expression may contain the following special strings:
    * - '%key', will be replaced by the element's key.
    * - '%parent', to reference the parent element.
    * - '%type', to reference the schema definition type. Can only be used in
@@ -324,7 +354,7 @@ protected function replaceName($name, $data) {
    * patterns like '%parent.name' which references the 'name' value of the
    * parent element.
    *
-   * Example patterns:
+   * Example expressions:
    * - 'name.subkey', indicates a nested value of the current element.
    * - '%parent.name', will be replaced by the 'name' value of the parent.
    * - '%parent.%key', will be replaced by the parent element's key.
@@ -332,29 +362,33 @@ protected function replaceName($name, $data) {
    * - '%parent.%parent.%type', will be replaced by the schema type of the
    *   parent's parent.
    *
-   * @param string $value
-   *   Variable value to be replaced.
-   * @param mixed $data
+   * @param string $expression
+   *   Expression to be resolved.
+   * @param array $data
    *   Configuration data for the element.
    *
    * @return string
-   *   The replaced value if a replacement found or the original value if not.
+   *   The value the expression resolves to, or the given expression if it
+   *   cannot be resolved.
+   *
+   * @todo Validate the expression in https://www.drupal.org/project/drupal/issues/3392903
    */
-  protected function replaceVariable($value, $data) {
-    $parts = explode('.', $value);
+  protected function resolveExpression(string $expression, array $data): string {
+    assert(!str_contains($expression, '[') && !str_contains($expression, ']'));
+    $parts = explode('.', $expression);
     // Process each value part, one at a time.
     while ($name = array_shift($parts)) {
       if (!is_array($data) || !isset($data[$name])) {
         // Key not found, return original value
-        return $value;
+        return $expression;
       }
       elseif (!$parts) {
-        $value = $data[$name];
-        if (is_bool($value)) {
-          $value = (int) $value;
+        $expression = $data[$name];
+        if (is_bool($expression)) {
+          $expression = (int) $expression;
         }
         // If no more parts left, this is the final property.
-        return (string) $value;
+        return (string) $expression;
       }
       else {
         // Get nested value and continue processing.
@@ -375,6 +409,51 @@ protected function replaceVariable($value, $data) {
         }
       }
     }
+
+    // Satisfy PHPStan, which cannot interpret the loop.
+    return $expression;
+  }
+
+  /**
+   * Resolves a dynamic type expression using configuration data.
+   *
+   * Dynamic type names are nested configuration keys containing expressions to
+   * be replaced by the value at the property path that the expression is
+   * pointing at. The expression may contain the following special strings:
+   * - '%key', will be replaced by the element's key.
+   * - '%parent', to reference the parent element.
+   * - '%type', to reference the schema definition type. Can only be used in
+   *   combination with %parent.
+   *
+   * There may be nested configuration keys separated by dots or more complex
+   * patterns like '%parent.name' which references the 'name' value of the
+   * parent element.
+   *
+   * Example expressions:
+   * - 'name.subkey', indicates a nested value of the current element.
+   * - '%parent.name', will be replaced by the 'name' value of the parent.
+   * - '%parent.%key', will be replaced by the parent element's key.
+   * - '%parent.%type', will be replaced by the schema type of the parent.
+   * - '%parent.%parent.%type', will be replaced by the schema type of the
+   *   parent's parent.
+   *
+   * @param string $value
+   *   Expression to be resolved.
+   * @param array $data
+   *   Configuration data for the element.
+   *
+   * @return string
+   *   The value the expression resolves to, or the given expression if it
+   *   cannot be resolved.
+   *
+   * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use
+   *   ::resolveExpression() instead.
+   *
+   * @see https://www.drupal.org/node/3408266
+   */
+  protected function replaceVariable($value, $data) {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use ::resolveExpression() instead. See https://www.drupal.org/node/3408266', E_USER_DEPRECATED);
+    return $this->resolveExpression($value, $data);
   }
 
   /**
diff --git a/core/phpstan-baseline.neon b/core/phpstan-baseline.neon
index ba653f410f80dbb3b1a49596149eb8f9e9db114a..a37bb883d88123ea30b6b183104d84cc328c5f27 100644
--- a/core/phpstan-baseline.neon
+++ b/core/phpstan-baseline.neon
@@ -344,11 +344,6 @@ parameters:
 			count: 1
 			path: lib/Drupal/Core/Config/ExtensionInstallStorage.php
 
-		-
-			message: "#^Method Drupal\\\\Core\\\\Config\\\\TypedConfigManager\\:\\:replaceVariable\\(\\) should return string but return statement is missing\\.$#"
-			count: 1
-			path: lib/Drupal/Core/Config/TypedConfigManager.php
-
 		-
 			message: """
 				#^Fetching deprecated class constant RETURN_AFFECTED of class Drupal\\\\Core\\\\Database\\\\Database\\: