diff --git a/core/core.services.yml b/core/core.services.yml
index 6e499d1..d00b9d2 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -208,8 +208,27 @@ services:
     class: Drupal\Core\TypedData\TypedDataManager
     arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler']
     calls:
+      - [setContainer, ['@service_container']]
       - [setValidationConstraintManager, ['@validation.constraint']]
+  typed_data_subscriber:
+    class: Drupal\Core\EventSubscriber\TypedDataSubscriber
+    arguments: ['@typed_data_resolver_manager']
+    tags:
+      - { name: event_subscriber }
+  typed_data_resolver_manager:
+    class: Drupal\Core\TypedData\Resolver\ResolverManager
+  typed_data_resolver.reflection:
+    class: Drupal\Core\TypedData\Resolver\ReflectionResolver
+    arguments: ['@typed_data']
+    calls:
       - [setContainer, ['@service_container']]
+    tags:
+      - { name: typed_data_resolver }
+  typed_data_resolver.entity:
+    class: Drupal\Core\TypedData\Resolver\EntityResolver
+    arguments: ['@typed_data']
+    tags:
+      - { name: typed_data_resolver }
   validation.constraint:
     class: Drupal\Core\Validation\ConstraintManager
     arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler']
@@ -302,11 +321,11 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@paramconverter_manager']
-  paramconverter.entity:
-    class: Drupal\Core\ParamConverter\EntityConverter
+  paramconverter.typeddata:
+    class: Drupal\Core\TypedData\TypedDataConverter
     tags:
-      - { name: paramconverter }
-    arguments: ['@plugin.manager.entity']
+      - { name: paramconverter, priority: -10 }
+    arguments: ['@typed_data']
   reverse_proxy_subscriber:
     class: Drupal\Core\EventSubscriber\ReverseProxySubscriber
     tags:
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index c590122..ae27686 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core;
 
 use Drupal\Core\Cache\ListCacheBinsPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterTypedDataResolversPass;
 use Drupal\Core\DependencyInjection\ServiceProviderInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
@@ -58,6 +59,7 @@ public function register(ContainerBuilder $container) {
     $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
     $container->addCompilerPass(new RegisterAccessChecksPass());
     // Add a compiler pass for upcasting route parameters.
+    $container->addCompilerPass(new RegisterTypedDataResolversPass());
     $container->addCompilerPass(new RegisterParamConvertersPass());
     $container->addCompilerPass(new RegisterRouteEnhancersPass());
     // Add a compiler pass for registering services needing destruction.
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterTypedDataResolversPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterTypedDataResolversPass.php
new file mode 100644
index 0000000..8939648
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterTypedDataResolversPass.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\DependencyInjection\Compiler\RegisterTypedDataResolversPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Registers typed data resolver services with the typed data resolver manager.
+ */
+class RegisterTypedDataResolversPass implements CompilerPassInterface {
+
+  /**
+   * Register typed data resolver services with the typed data resolver manager.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   The container to process.
+   */
+  public function process(ContainerBuilder $container) {
+    if (!$container->hasDefinition('typed_data_resolver_manager')) {
+      return;
+    }
+
+    $manager = $container->getDefinition('typed_data_resolver_manager');
+    foreach ($container->findTaggedServiceIds('typed_data_resolver') as $id => $attributes) {
+      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
+      $manager->addMethodCall('addTypedDataResolver', array(new Reference($id), $priority));
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/TypedDataSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/TypedDataSubscriber.php
new file mode 100644
index 0000000..9b05d6b
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/TypedDataSubscriber.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\EventSubscriber\TypedDataSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\TypedData\Resolver\ResolverManager;
+use Drupal\Core\TypedData\TypedDataDetector;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Drupal\Core\Routing\RoutingEvents;
+use Drupal\Core\Routing\RouteBuildEvent;
+
+/**
+ * Typed data subscriber for route build events.
+ */
+class TypedDataSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The typed data resolver.
+   *
+   * @var \Drupal\Core\TypedData\Resolver\ResolverManager
+   */
+  protected $resolverManager;
+
+  /**
+   * Constructs a TypedDataSubscriber object.
+   */
+  public function __construct(ResolverManager $resolver_manager) {
+    $this->resolverManager = $resolver_manager;
+  }
+
+  /**
+   * Generates typed data definition for route arguments.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The event to process.
+   */
+  public function onRoutingRouteAlterSetAccessCheck(RouteBuildEvent $event) {
+    $this->resolverManager->resolveParameterTypes($event->getRouteCollection());
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    // Setting very low priority to ensure typed data discovery runs after
+    $events[RoutingEvents::ALTER][] = array('onRoutingRouteAlterSetAccessCheck', 20);
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
deleted file mode 100644
index 91e365b..0000000
--- a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\ParamConverter\EntityConverter.
- */
-
-namespace Drupal\Core\ParamConverter;
-
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\Routing\Route;
-use Drupal\Core\Entity\EntityManager;
-
-/**
- * Parameter converter for upcasting entity ids to full objects.
- */
-class EntityConverter implements ParamConverterInterface {
-
-  /**
-   * Entity manager which performs the upcasting in the end.
-   *
-   * @var \Drupal\Core\Entity\EntityManager
-   */
-  protected $entityManager;
-
-  /**
-   * Constructs a new EntityConverter.
-   *
-   * @param \Drupal\Core\Entity\EntityManager $entityManager
-   *   The entity manager.
-   */
-  public function __construct(EntityManager $entity_manager) {
-    $this->entityManager = $entity_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function convert($definition, $name, array $defaults, Request $request) {
-    // Only continue if there is a value for the given parameter.
-    if (!isset($defaults[$name])) {
-      return;
-    }
-
-    $entity_type = substr($definition['type'], strlen('entity:'));
-    if ($storage = $this->entityManager->getStorageController($entity_type)) {
-      return $storage->load($defaults[$name]);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function applies($definition, $name, Route $route) {
-    if (!empty($definition['type']) && strpos($definition['type'], 'entity:') === 0) {
-      $entity_type = substr($definition['type'], strlen('entity:'));
-      return $this->entityManager->getDefinition($entity_type);
-    }
-    return FALSE;
-  }
-
-}
diff --git a/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php b/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php
index 6e1b25d..5d5b0b4 100644
--- a/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php
+++ b/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php
@@ -7,18 +7,21 @@
 
 namespace Drupal\Core\ParamConverter;
 
-use Symfony\Component\DependencyInjection\ContainerAware;
 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\DependencyInjection\ContainerAware;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\Routing\RouteCollection;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
- * Manages converter services for converting request parameters to full objects.
+ * Provides a service which allows to enhance (say alter) the arguments coming
+ * from the URL.
+ *
+ * A typical use case for this would be upcasting a node id to a node entity.
  *
- * A typical use case for this would be upcasting (converting) a node id to a
- * node entity.
+ * This class will not enhance any of the arguments itself, but allow other
+ * services to register to do so.
  */
 class ParamConverterManager extends ContainerAware implements RouteEnhancerInterface {
 
@@ -55,6 +58,7 @@ class ParamConverterManager extends ContainerAware implements RouteEnhancerInter
    *   The called object for chaining.
    */
   public function addConverter($converter, $priority = 0) {
+    $converter = substr($converter, strlen('paramconverter.'));
     if (empty($this->converterIds[$priority])) {
       $this->converterIds[$priority] = array();
     }
@@ -81,28 +85,6 @@ public function getConverterIds() {
   }
 
   /**
-   * Lazy-loads converter services.
-   *
-   * @param string $converter
-   *   The service id of converter service to load.
-   *
-   * @return \Drupal\Core\ParamConverter\ParamConverterInterface
-   *   The loaded converter service identified by the given service id.
-   *
-   * @throws \InvalidArgumentException
-   *   If the given service id is not a registered converter.
-   */
-  public function getConverter($converter) {
-    if (isset($this->converters[$converter])) {
-      return $this->converters[$converter];
-    }
-    if (!in_array($converter, $this->getConverterIds())) {
-      throw new \InvalidArgumentException(sprintf('No converter has been registered for %s', $converter));
-    }
-    return $this->converters[$converter] = $this->container->get($converter);
-  }
-
-  /**
    * For each route, saves a list of applicable converters to the route.
    *
    * @param \Symfony\Component\Routing\RouteCollection $routes
@@ -181,5 +163,27 @@ public function enhance(array $defaults, Request $request) {
     return $defaults;
   }
 
+  /**
+   * Lazy-loads converter services.
+   *
+   * @param string $converter
+   *   The service id of converter service to load.
+   *
+   * @return \Drupal\Core\ParamConverter\ParamConverterInterface
+   *   The loaded converter service identified by the given service id.
+   *
+   * @throws \InvalidArgumentException
+   *   If the given service id is not a registered converter.
+   */
+  protected function getConverter($converter) {
+    if (isset($this->converters[$converter])) {
+      return $this->converters[$converter];
+    }
+    if (!in_array($converter, $this->getConverterIds())) {
+      throw new \InvalidArgumentException(sprintf('No converter has been registered for %s', $converter));
+    }
+    return $this->converters[$converter] = $this->container->get("paramconverter.$converter");
+  }
+
 }
 
diff --git a/core/lib/Drupal/Core/TypedData/Resolver/EntityResolver.php b/core/lib/Drupal/Core/TypedData/Resolver/EntityResolver.php
new file mode 100644
index 0000000..eb72e37
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Resolver/EntityResolver.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\TypedData\Resolver\EntityResolver.
+ */
+
+namespace Drupal\Core\TypedData\Resolver;
+
+use Drupal\Core\TypedData\TypedDataManager;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Generates typed data definitions for specific entity routes.
+ */
+class EntityResolver implements ResolverInterface {
+
+  /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager;
+   */
+  protected $typedDataManager;
+
+  /**
+   * Constructs a EntityResolver object.
+   *
+   * @param TypedDataManager $typed_data_manager
+   *   The typed data manager to use.
+   */
+  public function __construct(TypedDataManager $typed_data_manager) {
+    $this->typedDataManager = $typed_data_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resolveParameterTypes(Route $route) {
+    $defaults = $route->getDefaults();
+    if (!empty($defaults['_controller'])) {
+      return;
+    }
+
+    // Either the '_entity_form' or '_entity_list' have to be set.
+    if (empty($defaults['_entity_form']) && empty($defaults['_entity_list'])) {
+      return;
+    }
+
+    // This typed data resolver is only capable of generating typed data
+    // definitions for routes that have either '_entity_form' or '_entity_list'
+    // in the route defaults array.
+    $entity_type = NULL;
+    if (!empty($defaults['_entity_form'])) {
+      list ($entity_type) = explode('.', $defaults['_entity_form']);
+    }
+    elseif (!empty($defaults['_entity_list'])) {
+      $entity_type = $defaults['_entity_list'];
+    }
+
+    // Check that the given entity type is not either a variable or has a
+    // default value set for it.
+    if (!in_array($entity_type, $route->compile()->getVariables()) && !isset($defaults[$entity_type])) {
+      return;
+    }
+
+    // Return the typed data definition for the given entity type if it exists
+    // and the parameter has not been defined manually already.
+    $predefined = $route->getOption('parameters') ?: array();
+    if (!isset($predefined[$entity_type]) && $this->typedDataManager->getDefinition("entity:$entity_type")) {
+      return array($entity_type => array(
+        'type' => "entity:$entity_type",
+      ));
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/TypedData/Resolver/ReflectionResolver.php b/core/lib/Drupal/Core/TypedData/Resolver/ReflectionResolver.php
new file mode 100644
index 0000000..801b0df
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Resolver/ReflectionResolver.php
@@ -0,0 +1,246 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\TypedData\Resolver\ReflectionResolver.
+ */
+
+namespace Drupal\Core\TypedData\Resolver;
+
+use Drupal\Core\TypedData\TypedDataManager;
+use Symfony\Component\DependencyInjection\ContainerAware;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Generates typed data definitions based on a controller's type hints.
+ */
+class ReflectionResolver extends ContainerAware implements ResolverInterface {
+
+  /**
+   * A static cache of type hinted typed data types.
+   *
+   * Stores a keyed list of typed data types with the key being the class or
+   * interface type hint and the value being the typed data type that belongs to
+   * that class or interface.
+   *
+   * @var array
+   */
+  protected $cache = array();
+
+  /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager;
+   */
+  protected $typedDataManager;
+
+  /**
+   * Constructs a ReflectionResolver object.
+   *
+   * @param TypedDataManager $typed_data_manager
+   *   The typed data manager to use.
+   */
+  public function __construct(TypedDataManager $typed_data_manager) {
+    $this->typedDataManager = $typed_data_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resolveParameterTypes(Route $route) {
+    $defaults = $route->getDefaults();
+    $predefined = $route->getOption('parameters') ?: array();
+    $variables = $route->compile()->getVariables();
+
+    // Resolve type hints from the '_form' controller, if set.
+    $controllers = array();
+    if (isset($defaults['_form'])) {
+      $controllers['_form'] = $this->getFormController($defaults['_form']);
+    }
+
+    // Resolve type hints from '_content' or '_controller' controllers.
+    foreach (array('_content', '_controller') as $controller) {
+      if (isset($defaults[$controller]) && $callable = $this->getController($defaults[$controller])) {
+        $controllers[$controller] = $callable;
+      }
+    }
+
+    // Retrieve the typed data types of as many parameters as possible by
+    // reflecting on the type hints on a controller. However, for performance
+    // reasons, make sure we do not try to reflect the same parameter twice.
+    $definitions = $predefined;
+    foreach ($controllers as $callable) {
+      if ($parameters = $this->getParameterTypes($callable, $variables, $definitions)) {
+        $definitions = array_merge($parameters, $definitions);
+      }
+    }
+
+    return array_diff_key($definitions, $predefined);
+  }
+
+  /**
+   * Returns a callable for the given form controller.
+   *
+   * @param string $controller
+   *   The controller string from the route defaults.
+   *
+   * @return array
+   *   A callable, composed of the controller class and method.
+   */
+  protected function getFormController($controller) {
+    // If the class does not exist, the form controller is a service.
+    if (!class_exists($controller)) {
+      $controller = $this->container->get($controller);
+    }
+    return array($controller, 'buildForm');
+  }
+
+  /**
+   * Returns a callable for the given controller.
+   *
+   * @param string $controller
+   *   The controller string from the route defaults.
+   *
+   * @throws \InvalidArgumentException
+   *   If the resolved class name is not a valid class.
+   *
+   * @return array|null
+   *   A callable, composed of the controller class and method or NULL if the
+   *   given string could not be turned to a valid callable.
+   */
+  protected function getController($controller) {
+    // Controller in the service:method notation.
+    $count = substr_count($controller, ':');
+    if ($count == 1) {
+      list($service, $method) = explode(':', $controller, 2);
+      return array($this->container->get($service), $method);
+    }
+
+    // Controller in the class::method notation.
+    if (strpos($controller, '::') !== FALSE) {
+      list($class, $method) = explode('::', $controller, 2);
+      if (!class_exists($class)) {
+        throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
+      }
+      return array($class, $method);
+    }
+  }
+
+  /**
+   * Tries to determine the typed data types via the type hints on a method.
+   *
+   * @param array $callable
+   *   The name of a method on the given class.
+   * @param array $variables
+   *   The route variables array for the current route.
+   * @param array $predefined
+   *   A keyed array of pre-defined parameter definitions.
+   *
+   * @return array
+   *   An array of typed data definitions keyed by parameter name.
+   */
+  protected function getParameterTypes($callable, array $variables, array $predefined) {
+    $reflection = new \ReflectionMethod($callable[0], $callable[1]);
+    $definitions = array();
+    foreach ($reflection->getParameters() as $parameter) {
+      $name = $parameter->getName();
+      // Do not try to resolve parameters that have been manually set.
+      if (isset($predefined[$name]) || isset($definitions[$name])) {
+        continue;
+      }
+
+      // Do not resolve parameters that are not part of the route variables and
+      // instead provide a default value.
+      if (!in_array($name, $variables) && $parameter->isDefaultValueAvailable()) {
+        continue;
+      }
+
+      if ($type = $this->getParameterType($parameter)) {
+        $definitions[$name] = array(
+          'type' => $type,
+        );
+      }
+    }
+    return $definitions;
+  }
+
+  /**
+   * Tries to determine the typed data type of a given parameter.
+   *
+   * @param \ReflectionParameter $parameter
+   *   The parameter to generate a typed data definition for.
+   *
+   * @return array
+   *   The typed data definition for the given parameter.
+   */
+  protected function getParameterType(\ReflectionParameter $parameter) {
+    if (!$class = $parameter->getClass()) {
+      // The parameter does not have a proper type hint.
+      return;
+    }
+
+    $name = $class->getName();
+    if (isset($this->cache[$name])) {
+      return $this->cache[$name];
+    }
+
+    // Ignore request object type hints.
+    $request = 'Symfony\Component\HttpFoundation\Request';
+    if ($name == $request || $class->isSubclassOf($request)) {
+      return;
+    }
+
+    // Try to find a typed data type that matches the type hint.
+    $this->cache[$name] = $class->isInterface() ? $this->getParameterTypeFromInterface($name) : $this->getParameterTypeFromClass($name);
+
+    return $this->cache[$name];
+  }
+
+  /**
+   * Tries to determine the typed data type that matches a given interface.
+   *
+   * @param string $name
+   *   An interface name for which to find the matching typed data type.
+   *
+   * @return array|null
+   *   The typed data definition that matches the given interface or NULL if it
+   *   it is ambiguous or does not match any.
+   */
+  protected function getParameterTypeFromInterface($name) {
+    $match = NULL;
+    foreach ($this->typedDataManager->getDefinitions() as $type => $definition) {
+      // Check if the defined typed data class implements the given interface.
+      if (is_subclass_of($definition['class'], $name)) {
+        // Make sure that the type hint is not ambiguous.
+        if (isset($match)) {
+          return;
+        }
+
+        $match = $type;
+      }
+    }
+
+    // Return the matched typed data type if any.
+    return $match ?: NULL;
+  }
+
+  /**
+   * Tries to determine the typed data type that matches a given class.
+   *
+   * @param string $name
+   *   A class name for which to find the matching typed data type.
+   *
+   * @return array|null
+   *   The typed data definition that matches the given interface or NULL if it
+   *   did not match any.
+   */
+  protected function getParameterTypeFromClass($name) {
+    foreach ($this->typedDataManager->getDefinitions() as $type => $definition) {
+      if ($definition['class'] === $name) {
+        // Return the first match.
+        return $type;
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/TypedData/Resolver/ResolverInterface.php b/core/lib/Drupal/Core/TypedData/Resolver/ResolverInterface.php
new file mode 100644
index 0000000..9b63eb6
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Resolver/ResolverInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Resolver\ResolverInterface.
+ */
+
+namespace Drupal\Core\TypedData\Resolver;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Interface for typed data definition resolvers.
+ */
+interface ResolverInterface {
+
+  /**
+   * Tries to generate typed data definitions for the parameters of a route.
+   *
+   * @param Route $route
+   *   The route object for which to generate typed data definitions.
+   *
+   * @return array
+   *   An array of typed data definitions keyed by parameter name.
+   */
+  public function resolveParameterTypes(Route $route);
+
+}
diff --git a/core/lib/Drupal/Core/TypedData/Resolver/ResolverManager.php b/core/lib/Drupal/Core/TypedData/Resolver/ResolverManager.php
new file mode 100644
index 0000000..7860b39
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Resolver/ResolverManager.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\TypedData\Resolver\ResolverManager.
+ */
+
+namespace Drupal\Core\TypedData\Resolver;
+
+use Drupal\Core\TypedData\TypedDataManager;
+use ReflectionMethod;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Generates typed data definitions for route parameters.
+ *
+ * By defining the typed data type of a route argument, other systems (like
+ * page manager) get the chance to manipulate the behavior of a given route
+ * while being fully aware of the available contexts.
+ *
+ * Typed data resolver services can employ different strategies to automatically
+ * discover and return the typed data type of certain route parameters e.g.
+ * by reflecting the type hints on the assigned controller or by parsing other
+ * metadata in a route definition that clearly identifies the type of a given
+ * parameter.
+ *
+ * This greatly increases the developer experience when defining new routes as
+ * it removes the need to repeat the type definition in the route options even
+ * though it may already be obvious e.g. due to a type hint or a special,
+ * generic controller like '_entity_form' or '_entity_list'.
+ */
+class ResolverManager {
+
+  /**
+   * An array of registered typed data resolvers.
+   *
+   * @var array
+   */
+  protected $resolvers = array();
+
+  /**
+   * Array of registered typed resolvers sorted by their priority.
+   *
+   * @var array
+   */
+  protected $sortedResolvers;
+
+  /**
+   * Registers a typed data resolver with the manager.
+   *
+   * @param \Drupal\Core\TypedData\Resolver\ResolverInterface $resolver
+   *   The typed data resolver to register.
+   * @param int $priority
+   *   (optional) The priority of the typed data resolver. Defaults to 0.
+   *
+   * @return \Drupal\Core\TypedData\Resolver\ResolverManager
+   *   The called object for chaining.
+   */
+  public function addTypedDataResolver(ResolverInterface $resolver, $priority = 0) {
+    if (empty($this->resolvers[$priority])) {
+      $this->resolvers[$priority] = array();
+    }
+    $this->resolvers[$priority][] = $resolver;
+    unset($this->sortedResolvers);
+    return $this;
+  }
+
+  /**
+   * Sorts the resolvers and flattens them.
+   *
+   * @return \Drupal\Core\TypedData\Resolver\ResolverInterface[]
+   *   The sorted typed data resolvers.
+   */
+  public function getTypedDataResolvers() {
+    if (!isset($this->sortedResolvers)) {
+      krsort($this->resolvers);
+      $this->sortedResolvers = array();
+      foreach ($this->resolvers as $resolvers) {
+        $this->sortedResolvers = array_merge($this->sortedResolvers, $resolvers);
+      }
+    }
+    return $this->sortedResolvers;
+  }
+
+  /**
+   * Tries to generate typed data definitions for each route in a collection.
+   *
+   * @param RouteCollection $routes
+   *   A route collection.
+   */
+  public function resolveParameterTypes(RouteCollection $routes) {
+    $resolvers = $this->getTypedDataResolvers();
+    foreach ($routes->all() as $route) {
+      $definitions = array();
+      $before = $route->getOption('parameters') ?: array();
+      foreach ($resolvers as $resolver) {
+        if ($parameters = $resolver->resolveParameterTypes($route)) {
+          $definitions = array_merge($parameters, $definitions);
+        }
+      }
+
+      // Merge the resolved typed data definitions into the route options.
+      if (!empty($definitions)) {
+        // Do not override manually defined typed data definitions.
+        $definitions = array_merge($definitions, $before);
+        $route->setOption('parameters', $definitions);
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataConverter.php b/core/lib/Drupal/Core/TypedData/TypedDataConverter.php
new file mode 100644
index 0000000..83a5b5b
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/TypedDataConverter.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\ParamConverter\TypedDataConverter.
+ */
+
+namespace Drupal\Core\TypedData;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Component\Utility\String;
+use Drupal\Core\ParamConverter\ParamConverterInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Upcasts request attributes to typed data objects.
+ */
+class TypedDataConverter implements ParamConverterInterface {
+
+  /**
+   * The typed data manager.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager
+   */
+  protected $typedDataManager;
+
+  /**
+   * Constructs a new TypedDataConverter object.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager
+   *   The typed data manager.
+   */
+  public function __construct(TypedDataManager $typed_data_manager) {
+    $this->typedDataManager = $typed_data_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert($definition, $name, array $defaults, Request $request) {
+    // Only continue if there is a value for the given parameter.
+    if (!isset($defaults[$name])) {
+      return;
+    }
+
+    // Remove keys that do not belong to the typed data definition.
+    unset($definition['converter']);
+
+    // Allow per-data definition overrides of the used classes, i.e. take over
+    // classes specified in the data definition.
+    $original = $this->typedDataManager->getDefinition($definition['type']);
+    $key = empty($definition['list']) ? 'class' : 'list class';
+    if (isset($definition[$key])) {
+      $class = $definition[$key];
+    }
+    elseif (isset($original[$key])) {
+      $class = $original[$key];
+    }
+
+    if (!isset($class)) {
+      throw new PluginException(String::format('The plugin %plugin did not specify an instance class.', array('%plugin' => $definition['type'])));
+    }
+
+    // If the given typed data type is not loadable, just create an instance.
+    if (!is_subclass_of($class, 'Drupal\Core\TypedData\LoadableInterface')) {
+      return $this->typedDataManager->create($definition, $defaults[$name]);
+    }
+
+    // Try to load the typed data object via the typed data manager.
+    return $this->typedDataManager->load($defaults[$name], $definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies($definition, $name, Route $route) {
+    return !empty($definition['type']) && $this->typedDataManager->getDefinition($definition['type']);
+  }
+
+}
diff --git a/core/modules/action/action.routing.yml b/core/modules/action/action.routing.yml
index 1a134e5..19e1703 100644
diff --git a/core/modules/aggregator/aggregator.routing.yml b/core/modules/aggregator/aggregator.routing.yml
index 96ba963..2c69045 100644
diff --git a/core/modules/block/block.routing.yml b/core/modules/block/block.routing.yml
index a25b667..cabd4cb 100644
diff --git a/core/modules/block/custom_block/custom_block.routing.yml b/core/modules/block/custom_block/custom_block.routing.yml
index 2702b86..2178d91 100644
diff --git a/core/modules/comment/comment.routing.yml b/core/modules/comment/comment.routing.yml
index 0c86816..b62c118 100644
diff --git a/core/modules/config/tests/config_test/config_test.routing.yml b/core/modules/config/tests/config_test/config_test.routing.yml
index ec72b7c..c26a438 100644
diff --git a/core/modules/contact/contact.routing.yml b/core/modules/contact/contact.routing.yml
index dfe6195..ba47a6a 100644
diff --git a/core/modules/entity/entity.routing.yml b/core/modules/entity/entity.routing.yml
index f1b5646..05d7092 100644
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
index 0810530..e994202 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Routing/RouteSubscriber.php
@@ -56,30 +56,21 @@ public function routes(RouteBuildEvent $event) {
         $route = new Route(
           "$path/fields/{field_instance}",
           array('_form' => '\Drupal\field_ui\Form\FieldInstanceEditForm'),
-          array('_permission' => 'administer ' . $entity_type . ' fields'),
-          array('parameters' => array(
-            'field_instance' => array('type' => 'entity:field_instance'),
-          ))
+          array('_permission' => 'administer ' . $entity_type . ' fields')
         );
         $collection->add("field_ui.instance_edit.$entity_type", $route);
 
         $route = new Route(
           "$path/fields/{field_instance}/field",
           array('_form' => '\Drupal\field_ui\Form\FieldEditForm'),
-          array('_permission' => 'administer ' . $entity_type . ' fields'),
-          array('parameters' => array(
-            'field_instance' => array('type' => 'entity:field_instance'),
-          ))
+          array('_permission' => 'administer ' . $entity_type . ' fields')
         );
         $collection->add("field_ui.field_edit.$entity_type", $route);
 
         $route = new Route(
           "$path/fields/{field_instance}/delete",
           array('_entity_form' => 'field_instance.delete'),
-          array('_permission' => 'administer ' . $entity_type . ' fields'),
-          array('parameters' => array(
-            'field_instance' => array('type' => 'entity:field_instance'),
-          ))
+          array('_permission' => 'administer ' . $entity_type . ' fields')
         );
         $collection->add("field_ui.delete.$entity_type", $route);
 
diff --git a/core/modules/filter/filter.routing.yml b/core/modules/filter/filter.routing.yml
index 194182d..5442fb0 100644
diff --git a/core/modules/forum/forum.routing.yml b/core/modules/forum/forum.routing.yml
index 980d830..f7d90f8 100644
diff --git a/core/modules/image/image.routing.yml b/core/modules/image/image.routing.yml
index dc49706..b178d33 100644
diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php
index 50bf2bb..ba6367d 100644
--- a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php
+++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php
@@ -8,6 +8,7 @@
 namespace Drupal\image\Form;
 
 use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\image\ImageStyleInterface;
 use Drupal\image\Plugin\Core\Entity\ImageStyle;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -61,7 +62,7 @@ public function getFormID() {
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, array &$form_state, $image_style = NULL, $image_effect = NULL, Request $request = NULL) {
+  public function buildForm(array $form, array &$form_state, ImageStyleInterface $image_style = NULL, $image_effect = NULL, Request $request = NULL) {
     $this->imageStyle = $image_style;
     $this->imageEffect = image_effect_load($image_effect, $this->imageStyle->id());
 
diff --git a/core/modules/menu/menu.routing.yml b/core/modules/menu/menu.routing.yml
index c938647..62c54f2 100644
diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml
index 266e0ab..dfdc0a6 100644
diff --git a/core/modules/picture/picture.routing.yml b/core/modules/picture/picture.routing.yml
index e9f636a..c953029 100644
diff --git a/core/modules/shortcut/shortcut.routing.yml b/core/modules/shortcut/shortcut.routing.yml
index 403da9b..e6bb718 100644
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 725718d..144c8f9 100644
diff --git a/core/modules/taxonomy/taxonomy.routing.yml b/core/modules/taxonomy/taxonomy.routing.yml
index 8b15837..389ca7c 100644
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index 2235bca..10ec95d 100644
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ParamConverter/ViewUIConverter.php b/core/modules/views_ui/lib/Drupal/views_ui/ParamConverter/ViewUIConverter.php
deleted file mode 100644
index 06148be..0000000
--- a/core/modules/views_ui/lib/Drupal/views_ui/ParamConverter/ViewUIConverter.php
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\views_ui\ParamConverter\ViewUIConverter.
- */
-
-namespace Drupal\views_ui\ParamConverter;
-
-use Drupal\Core\Entity\EntityManager;
-use Drupal\Core\ParamConverter\EntityConverter;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\Routing\Route;
-use Drupal\Core\ParamConverter\ParamConverterInterface;
-use Drupal\user\TempStoreFactory;
-use Drupal\views_ui\ViewUI;
-
-/**
- * Provides upcasting for a view entity to be used in the Views UI.
- *
- * Example:
- *
- * pattern: '/some/{view}/and/{bar}'
- * options:
- *   parameters:
- *     view:
- *       type: 'entity:view'
- *       tempstore: TRUE
- *
- * The value for {view} will be converted to a view entity prepared for the
- * Views UI and loaded from the views temp store, but it will not touch the
- * value for {bar}.
- */
-class ViewUIConverter extends EntityConverter implements ParamConverterInterface {
-
-  /**
-   * Stores the tempstore factory.
-   *
-   * @var \Drupal\user\TempStoreFactory
-   */
-  protected $tempStore;
-
-  /**
-   * Constructs a new ViewUIConverter.
-   *
-   * @param \Drupal\user\TempStoreFactory $temp_store_factory
-   *   The factory for the temp store object.
-   */
-  public function __construct(EntityManager $entity_manager, TempStoreFactory $temp_store_factory) {
-    parent::__construct($entity_manager);
-
-    $this->tempStoreFactory = $temp_store_factory;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function convert($definition, $name, array $defaults, Request $request) {
-    if (!$entity = parent::convert($definition, $name, $defaults, $request)) {
-      return;
-    }
-
-    // Get the temp store for this variable if it needs one. Attempt to load the
-    // view from the temp store, synchronize its status with the existing view,
-    // and store the lock metadata.
-    $store = $this->tempStoreFactory->get('views');
-    if ($view = $store->get($defaults[$name])) {
-      if ($entity->status()) {
-        $view->enable();
-      }
-      else {
-        $view->disable();
-      }
-      $view->lock = $store->getMetadata($defaults[$name]);
-    }
-    // Otherwise, decorate the existing view for use in the UI.
-    else {
-      $view = new ViewUI($entity);
-    }
-
-    return $view;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function applies($definition, $name, Route $route) {
-    if (parent::applies($definition, $name, $route)) {
-      return !empty($definition['tempstore']) && $definition['type'] === 'entity:view';
-    }
-    return FALSE;
-  }
-
-}
diff --git a/core/modules/views_ui/views_ui.routing.yml b/core/modules/views_ui/views_ui.routing.yml
index b117fd7..64ac85b 100644
diff --git a/core/modules/views_ui/views_ui.services.yml b/core/modules/views_ui/views_ui.services.yml
deleted file mode 100644
index 3120b87..0000000
--- a/core/modules/views_ui/views_ui.services.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-services:
-  paramconverter.views_ui:
-    class: Drupal\views_ui\ParamConverter\ViewUIConverter
-    arguments: ['@plugin.manager.entity', '@user.tempstore']
-    tags:
-      - { name: paramconverter, priority: 10 }
diff --git a/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php b/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php
deleted file mode 100644
index 6e9a018..0000000
--- a/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php
+++ /dev/null
@@ -1,145 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Tests\Core\ParamConverter\ParamConverterManagerTest.
- */
-
-namespace Drupal\Tests\Core\ParamConverter;
-
-use Drupal\Core\ParamConverter\ParamConverterManager;
-use Drupal\Tests\UnitTestCase;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-
-/**
- * Tests the typed data resolver manager.
- */
-class ParamConverterManagerTest extends UnitTestCase {
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Parameter converter manager',
-      'description' => 'Tests the parameter converter manager.',
-      'group' => 'Routing',
-    );
-  }
-
-  public function setUp() {
-    parent::setUp();
-
-    $this->container = new ContainerBuilder();
-    $this->manager = new ParamConverterManager();
-    $this->manager->setContainer($this->container);
-  }
-
-  /**
-   * Tests \Drupal\Core\ParamConverter\ParamConverterManager::addConverter().
-   *
-   * @dataProvider providerTestAddConverter
-   *
-   * @see ParamConverterManagerTest::providerTestAddConverter().
-   */
-  public function testAddConverter($unsorted, $sorted) {
-    foreach ($unsorted as $data) {
-      $this->manager->addConverter($data['name'], $data['priority']);
-    }
-
-    // Test that ResolverManager::getTypedDataResolvers() returns the resolvers
-    // in the expected order.
-    foreach ($this->manager->getConverterIds() as $key => $converter) {
-      $this->assertEquals($sorted[$key], $converter);
-    }
-  }
-
-  /**
-   * Tests \Drupal\Core\ParamConverter\ParamConverterManager::getConverter().
-   *
-   * @dataProvider providerTestGetConverter
-   *
-   * @see ParamConverterManagerTest::providerTestGetConverter().
-   */
-  public function testGetConverter($name, $priority, $class) {
-    $converter = $this->getMockBuilder('Drupal\Core\ParamConverter\ParamConverterInterface')
-      ->setMockClassName($class)
-      ->getMock();
-
-    $this->manager->addConverter($name, $priority);
-    $this->container->set($name, $converter);
-
-    $this->assertInstanceOf($class, $this->manager->getConverter($name));
-  }
-
-  /**
-   * Tests \Drupal\Core\ParamConverter\ParamConverterManager::getConverter().
-   *
-   * @expectedException InvalidArgumentException
-   */
-  public function testGetConverterException() {
-    $this->manager->getConverter('undefined.converter');
-  }
-
-  /**
-   * Provide data for parameter converter manager tests.
-   *
-   * @return array
-   *   An array of arrays, each containing the input parameters for
-   *   providerTestResolvers::testAddConverter().
-   *
-   * @see ParamConverterManagerTest::testAddConverter().
-   */
-  public function providerTestAddConverter() {
-    $converters[0]['unsorted'] = array(
-      array('name' => 'raspberry', 'priority' => 10),
-      array('name' => 'pear', 'priority' => 5),
-      array('name' => 'strawberry', 'priority' => 20),
-      array('name' => 'pineapple', 'priority' => 0),
-      array('name' => 'banana', 'priority' => -10),
-      array('name' => 'apple', 'priority' => -10),
-      array('name' => 'peach', 'priority' => 5),
-    );
-
-    $converters[0]['sorted'] = array(
-      'strawberry', 'raspberry', 'pear', 'peach',
-      'pineapple', 'banana', 'apple'
-    );
-
-    $converters[1]['unsorted'] = array(
-      array('name' => 'ape', 'priority' => 0),
-      array('name' => 'cat', 'priority' => -5),
-      array('name' => 'puppy', 'priority' => -10),
-      array('name' => 'llama', 'priority' => -15),
-      array('name' => 'giraffe', 'priority' => 10),
-      array('name' => 'zebra', 'priority' => 10),
-      array('name' => 'eagle', 'priority' => 5),
-    );
-
-    $converters[1]['sorted'] = array(
-      'giraffe', 'zebra', 'eagle', 'ape',
-      'cat', 'puppy', 'llama'
-    );
-
-    return $converters;
-  }
-
-  /**
-   * Provide data for parameter converter manager tests.
-   *
-   * @return array
-   *   An array of arrays, each containing the input parameters for
-   *   providerTestResolvers::testGetConverter().
-   *
-   * @see ParamConverterManagerTest::testGetConverter().
-   */
-  public function providerTestGetConverter() {
-    return array(
-      array('ape', 0, 'ApeConverterClass'),
-      array('cat', -5, 'CatConverterClass'),
-      array('puppy', -10, 'PuppyConverterClass'),
-      array('llama', -15, 'LlamaConverterClass'),
-      array('giraffe', 10, 'GiraffeConverterClass'),
-      array('zebra', 10, 'ZebraConverterClass'),
-      array('eagle', 5, 'EagleConverterClass'),
-    );
-  }
-
-}
diff --git a/core/tests/Drupal/Tests/Core/TypedData/EntityResolverTest.php b/core/tests/Drupal/Tests/Core/TypedData/EntityResolverTest.php
new file mode 100644
index 0000000..91e76e7
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/TypedData/EntityResolverTest.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\TypedData\EntityResolverTest.
+ */
+
+namespace Drupal\Tests\Core\TypedData;
+
+use Drupal\Core\TypedData\Resolver\EntityResolver;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Tests the typed data entity type resolver.
+ */
+class EntityResolverTest extends UnitTestCase {
+
+  /**
+   * The entity type resolver.
+   *
+   * @var \Drupal\Core\TypedData\Resolver\EntityResolver
+   */
+  protected $entityResolver;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity type resolver',
+      'description' => 'Tests the typed data entity type resolver.',
+      'group' => 'Typed Data API',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    $this->setupEntityResolver();
+  }
+
+  /**
+   * Tests \Drupal\Core\TypedData\Resolver\ResolverManager::resolveParamaterTypes().
+   *
+   * @dataProvider providerTestParameters
+   *
+   * @see \Drupal\Tests\Core\TypedData\EntityResolverTest::providerTestParameters()
+   */
+  public function testResolveParameterTypes(Route $route, $expected) {
+    $this->assertEquals($expected, $this->entityResolver->resolveParameterTypes($route));
+  }
+
+  /**
+   * Provide data for reflection resolver tests.
+   *
+   * @return array
+   *   An array of arrays, each containing the input parameters for
+   *   ResolverManagerTest::testResolveParameterTypes().
+   *
+   * @see \Drupal\Tests\Core\TypedData\EntityResolverTest::testResolveParameterTypes().
+   */
+  public function providerTestParameters() {
+    return array(
+      // Test that the entity resolver automatically finds the entity:node type
+      // if _entity_form is set to 'node.foo'.
+      array(new Route('/test-1/{node}', array(
+        '_entity_form' => 'node.edit',
+      )), array(
+        'node' => array('type' => 'entity:node'),
+      )),
+      // Test that the entity resolver automatically finds the entity:user type
+      // if _entity_list is set to 'user'.
+      array(new Route('/test-2/{user}', array(
+        '_entity_list' => 'user',
+      )), array(
+        'user' => array('type' => 'entity:user'),
+      )),
+      // Test that the entity resolver doesn't do anything if neither
+      // _entity_form nor _entity_list are defined.
+      array(new Route('/test-3/{user}/{node}'), NULL),
+    );
+  }
+
+  /**
+   * Sets up a mocked reflection resolver for the test.
+   */
+  protected function setupEntityResolver() {
+    $manager = $this
+      ->getMockBuilder('Drupal\Core\TypedData\TypedDataManager')
+      ->disableOriginalConstructor()
+      ->setMethods(array('getDefinitions'))
+      ->getMock();
+
+    $definitions = array(
+      'entity:node' => array('id' => 'entity:node'),
+      'entity:user' => array('id' => 'entity:user'),
+    );
+
+    $property = new \ReflectionProperty('Drupal\Core\TypedData\TypedDataManager', 'definitions');
+    $property->setAccessible(TRUE);
+    $property->setValue($manager, $definitions);
+
+    $this->entityResolver = new EntityResolver($manager);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/TypedData/ReflectionResolverTest.php b/core/tests/Drupal/Tests/Core/TypedData/ReflectionResolverTest.php
new file mode 100644
index 0000000..d908d15
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/TypedData/ReflectionResolverTest.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\TypedData\ReflectionResolverTest.
+ */
+
+namespace Drupal\Tests\Core\TypedData;
+
+use Drupal\Core\TypedData\Resolver\ReflectionResolver;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Tests the typed data reflection resolver.
+ */
+class ReflectionResolverTest extends UnitTestCase {
+
+  /**
+   * The typed data reflection resolver.
+   *
+   * @var \Drupal\Core\TypedData\Resolver\ReflectionResolver
+   */
+  protected $reflectionResolver;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Reflection resolver',
+      'description' => 'Tests the typed data reflection resolver.',
+      'group' => 'Typed Data API',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    $this->setupReflectionResolver();
+  }
+
+  /**
+   * Tests \Drupal\Core\TypedData\Resolver\ResolverManager::resolveParamaterTypes().
+   *
+   * @dataProvider providerTestParameters
+   */
+  public function testResolveParameterTypes(Route $route, $expected) {
+    $actual = $this->reflectionResolver->resolveParameterTypes($route);
+    krsort($expected);
+    krsort($actual);
+    $this->assertEquals($expected, $actual);
+  }
+
+  /**
+   * Provide data for reflection resolver tests.
+   *
+   * @return array
+   *   An array of arrays, each containing the input parameters for
+   *   ResolverManagerTest::testResolveParameterTypes().
+   *
+   * @see ResolverManagerTest::testResolveParameterTypes().
+   */
+  public function providerTestParameters() {
+    return array(
+      // Test class type hint reflection of a single _controller method
+      // argument.
+      array(new Route('/test-1/{node}', array(
+        '_controller' => 'Drupal\Tests\Core\TypedData\ReflectionTestController::nodeController',
+      )), array(
+        'node' => array('type' => 'entity:node'),
+      )),
+      // Test class type hint reflection of multiple _controller method
+      // arguments.
+      array(new Route('/test-2/{user}/{node}', array(
+        '_controller' => 'Drupal\Tests\Core\TypedData\ReflectionTestController::nodeUserController',
+      )), array(
+        'user' => array('type' => 'entity:user'),
+        'node' => array('type' => 'entity:node'),
+      )),
+      // Test interface type hint reflection of a _controller method argument.
+      array(new Route('/test-3/{block}', array(
+        '_controller' => 'Drupal\Tests\Core\TypedData\ReflectionTestController::blockController',
+      )), array(
+        'block' => array('type' => 'entity:block'),
+      )),
+      // Test interface and class type hint reflection together.
+      array(new Route('/test-4/{block}/{node}', array(
+        '_controller' => 'Drupal\Tests\Core\TypedData\ReflectionTestController::blockNodeController',
+      )), array(
+        'block' => array('type' => 'entity:block'),
+        'node' => array('type' => 'entity:node'),
+      )),
+      // Test _form controller reflection (different because _form controllers
+      // do not manually specify the controller method).
+      array(new Route('/test-5/{node}/{block}', array(
+        '_form' => 'Drupal\Tests\Core\TypedData\ReflectionTestController',
+      )), array(
+        'node' => array('type' => 'entity:node'),
+        'block' => array('type' => 'entity:block'),
+      )),
+    );
+  }
+
+  /**
+   * Sets up a mocked reflection resolver for the test.
+   */
+  protected function setupReflectionResolver() {
+    $manager = $this
+      ->getMockBuilder('Drupal\Core\TypedData\TypedDataManager')
+      ->disableOriginalConstructor()
+      ->setMethods(NULL)
+      ->getMock();
+
+    $definitions = array(
+      'entity:node' => array(
+        'id' => 'entity:node',
+        'class' => 'Drupal\node\Plugin\Core\Entity\Node',
+      ),
+      'entity:user' => array(
+        'id' => 'entity:user',
+        'class' => 'Drupal\user\Plugin\Core\Entity\User',
+      ),
+      'entity:block' => array(
+        'id' => 'entity:block',
+        'class' => 'Drupal\block\Plugin\Core\Entity\Block',
+      ),
+    );
+
+    $property = new \ReflectionProperty('Drupal\Core\TypedData\TypedDataManager', 'definitions');
+    $property->setAccessible(TRUE);
+    $property->setValue($manager, $definitions);
+
+    $this->reflectionResolver = new ReflectionResolver($manager);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/TypedData/ReflectionTestController.php b/core/tests/Drupal/Tests/Core/TypedData/ReflectionTestController.php
new file mode 100644
index 0000000..7fd0a07
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/TypedData/ReflectionTestController.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Contains \Drupal\Tests\Core\TypedData\ReflectionTestController.
+ */
+
+namespace Drupal\Tests\Core\TypedData;
+
+use Drupal\block\BlockInterface;
+use Drupal\node\Plugin\Core\Entity\Node;
+use Drupal\user\Plugin\Core\Entity\User;
+
+/**
+ * Hypothetical controller for testing reflection based type resolving.
+ */
+class ReflectionTestController {
+
+  /**
+   * Controller with a single node entity type hint.
+   */
+  public function nodeController(Node $node) {
+  }
+
+  /**
+   * Controller with a node and a user entity type hint.
+   */
+  public function nodeUserController(Node $node, User $user) {
+  }
+
+  /**
+   * Controller with a block interface type hint.
+   */
+  public function blockController(BlockInterface $block) {
+  }
+
+  /**
+   * Controller with a block interface and a node class type hint.
+   */
+  public function blockNodeController(Node $node, BlockInterface $block) {
+  }
+
+  /**
+   * Form controller method with a block interface and a node class type hint.
+   */
+  public function buildForm(array $form, array &$form_state, BlockInterface $block = NULL, Node $node = NULL) {
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/TypedData/ResolverManagerTest.php b/core/tests/Drupal/Tests/Core/TypedData/ResolverManagerTest.php
new file mode 100644
index 0000000..e71d036
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/TypedData/ResolverManagerTest.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\TypedData\ResolverManagerTest.
+ */
+
+namespace Drupal\Tests\Core\TypedData;
+
+use Drupal\Core\TypedData\Resolver\ResolverManager;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Tests the typed data resolver manager.
+ */
+class ResolverManagerTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Typed data resolver manager',
+      'description' => 'Tests the typed data resolver manager.',
+      'group' => 'Typed Data API',
+    );
+  }
+
+  /**
+   * Tests \Drupal\Core\TypedData\Resolver\ResolverManager::resolveParamaterTypes().
+   */
+  public function testResolveParameterTypes() {
+    $collection = new RouteCollection();
+    // Add a route for which we want to have automatically generate parameter
+    // definitions.
+    $collection->add('magic', new Route('magic'));
+
+    // Manually pre-define a parameter set for testing that manually defined
+    // parameters do not get replaced by automatically resolved definitions.
+    $collection->add('manual', new Route('manual', array(), array(), array(
+      'parameters' => array(
+        'magic' => 'no',
+      ),
+    )));
+
+    // Mock a resolver that tries to resolve 'magic' to 'yes'. This should not
+    // override manually defined parameters.
+    $resolver = $this->getMock('Drupal\Core\TypedData\Resolver\ResolverInterface');
+    $resolver->expects($this->exactly($collection->count()))
+      ->method('resolveParameterTypes')
+      ->with($this->isInstanceOf('Symfony\Component\Routing\Route'))
+      ->will($this->returnValue(array('magic' => 'yes')));
+
+    $manager = $this->getMockBuilder('Drupal\Core\TypedData\Resolver\ResolverManager')
+      ->setMethods(array('getTypedDataResolvers'))
+      ->getMock();
+
+    $manager->expects($this->once())
+      ->method('getTypedDataResolvers')
+      ->will($this->returnValue(array($resolver)));
+
+    $manager->resolveParameterTypes($collection);
+
+    // Assert that the route options are correct.
+    $this->assertEquals(array('magic' => 'yes'), $collection->get('magic')->getOption('parameters'));
+    $this->assertEquals(array('magic' => 'no'), $collection->get('manual')->getOption('parameters'));
+  }
+
+  /**
+   * Tests \Drupal\Core\TypedData\Resolver\ResolverManager::addTypedDataResolver().
+   *
+   * @dataProvider providerTestResolvers
+   */
+  public function testAddTypedDataResolver($unsorted, $sorted) {
+    $manager = new ResolverManager();
+    foreach ($unsorted as $data) {
+      $resolver = $this->getMockBuilder('Drupal\Core\TypedData\Resolver\ResolverInterface')
+        ->setMockClassName($data['name'])
+        ->getMock();
+
+      $manager->addTypedDataResolver($resolver, $data['priority']);
+    }
+
+    // Test that ResolverManager::getTypedDataResolvers() returns the resolvers
+    // in the expected order.
+    foreach ($manager->getTypedDataResolvers() as $key => $resolver) {
+      $this->assertInstanceOf($sorted[$key], $resolver);
+    }
+  }
+
+  /**
+   * Provide data for typed data resolver tests.
+   *
+   * @return array
+   *   An array of arrays, each containing the input parameters for
+   *   ResolverManagerTest::testAddTypedDataResolver().
+   *
+   * @see ResolverManagerTest::testAddTypedDataResolver().
+   */
+  public function providerTestResolvers() {
+    $resolvers[0]['unsorted'] = array(
+      array('name' => 'RaspberryResolver', 'priority' => 10),
+      array('name' => 'PearResolver', 'priority' => 5),
+      array('name' => 'StrawberryResolver', 'priority' => 20),
+      array('name' => 'PineappleResolver', 'priority' => 0),
+      array('name' => 'BananaResolver', 'priority' => -10),
+      array('name' => 'AppleResolver', 'priority' => -10),
+      array('name' => 'PeachFooResolver', 'priority' => 5),
+    );
+
+    $resolvers[0]['sorted'] = array(
+      'StrawberryResolver', 'RaspberryResolver', 'PearResolver', 'PeachFooResolver',
+      'PineappleResolver', 'BananaResolver', 'AppleResolver'
+    );
+
+    $resolvers[1]['unsorted'] = array(
+      array('name' => 'ApeResolver', 'priority' => 0),
+      array('name' => 'CatResolver', 'priority' => -5),
+      array('name' => 'PuppyResolver', 'priority' => -10),
+      array('name' => 'LlamaResolver', 'priority' => -15),
+      array('name' => 'GiraffeResolver', 'priority' => 10),
+      array('name' => 'ZebraResolver', 'priority' => 10),
+      array('name' => 'EagleResolver', 'priority' => 5),
+    );
+
+    $resolvers[1]['sorted'] = array(
+      'GiraffeResolver', 'ZebraResolver', 'EagleResolver', 'ApeResolver',
+      'CatResolver', 'PuppyResolver', 'LlamaResolver'
+    );
+
+    return $resolvers;
+  }
+
+}
