diff --git a/core/core.api.php b/core/core.api.php
index de34440..52984a0 100644
--- a/core/core.api.php
+++ b/core/core.api.php
@@ -1221,7 +1221,7 @@
  * - Choose a namespace subdirectory for your plugin. For example, search page
  *   plugins go in directory Plugin/Search under the module namespace.
  * - Define an annotation class for your plugin type. This class should extend
- *   \Drupal\Component\Annotation\Plugin, and for most plugin types, it should
+ *   \Drupal\Core\Annotation\Plugin, and for most plugin types, it should
  *   contain member variables corresponding to the annotations plugins will
  *   need to provide. All plugins have at least $id: a unique string
  *   identifier.
diff --git a/core/lib/Drupal/Component/Annotation/Plugin.php b/core/lib/Drupal/Component/Annotation/Plugin.php
index b3699bf..23166ad 100644
--- a/core/lib/Drupal/Component/Annotation/Plugin.php
+++ b/core/lib/Drupal/Component/Annotation/Plugin.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Component\Annotation;
 
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Component\Utility\NestedArray;
 
 /**
@@ -74,9 +75,11 @@ protected function parse(array $values) {
 
   /**
    * {@inheritdoc}
+   *
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface
    */
   public function get() {
-    return $this->definition;
+    return new ArrayPluginDefinition($this->definition);
   }
 
   /**
diff --git a/core/lib/Drupal/Component/Annotation/PluginID.php b/core/lib/Drupal/Component/Annotation/PluginID.php
index 2bdcb43..8acc059 100644
--- a/core/lib/Drupal/Component/Annotation/PluginID.php
+++ b/core/lib/Drupal/Component/Annotation/PluginID.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Component\Annotation;
 
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinition;
+
 /**
  * Defines a Plugin annotation object that just contains an ID.
  *
@@ -27,11 +29,10 @@ class PluginID extends AnnotationBase {
    * {@inheritdoc}
    */
   public function get() {
-    return array(
+    return new ArrayPluginDefinition(array(
       'id' => $this->value,
       'class' => $this->class,
-      'provider' => $this->provider,
-    );
+    ));
   }
 
   /**
diff --git a/core/lib/Drupal/Component/Plugin/CategorizingPluginManagerInterface.php b/core/lib/Drupal/Component/Plugin/CategorizingPluginManagerInterface.php
index 9bb7d2b..2f4068a 100644
--- a/core/lib/Drupal/Component/Plugin/CategorizingPluginManagerInterface.php
+++ b/core/lib/Drupal/Component/Plugin/CategorizingPluginManagerInterface.php
@@ -23,7 +23,7 @@ public function getCategories();
   /**
    * Gets sorted plugin definitions.
    *
-   * @param array[]|null $definitions
+   * @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]|null $definitions
    *   (optional) The plugin definitions to sort. If omitted, all plugin
    *   definitions are used.
    *
@@ -38,7 +38,7 @@ public function getSortedDefinitions(array $definitions = NULL);
    * In addition to grouping, both categories and its entries are sorted,
    * whereas plugin definitions are sorted by label.
    *
-   * @param array[]|null $definitions
+   * @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]|null $definitions
    *   (optional) The plugin definitions to group. If omitted, all plugin
    *   definitions are used.
    *
diff --git a/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinition.php b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinition.php
new file mode 100644
index 0000000..f01d68a
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinition.php
@@ -0,0 +1,264 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\Definition\ArrayPluginDefinitionBase.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
+use Drupal\Component\Utility\NestedArray;
+
+/**
+ * Provides a plugin definition based on an array.
+ *
+ * @ingroup Plugin
+ */
+class ArrayPluginDefinition implements ArrayPluginDefinitionInterface, PluginContextDefinitionInterface, PluginDeriverDefinitionInterface, PluginLabelDefinitionInterface {
+
+  use MergeablePluginDefinitionTrait;
+
+  /**
+   * The array definition.
+   *
+   * @var mixed[]
+   */
+  protected $arrayDefinition = [];
+
+  /**
+   * Constructs a new instance.
+   *
+   * @param array $array_definition
+   *   The array definition.
+   */
+  public function __construct(array $array_definition = []) {
+    if (isset($array_definition['class'])) {
+      PluginDefinitionValidator::validateClass($array_definition['class']);
+    }
+    if (isset($array_definition['deriver'])) {
+      PluginDefinitionValidator::validateDeriverClass($array_definition['deriver']);
+    }
+    if (isset($array_definition['context'])) {
+      PluginDefinitionValidator::validateContextDefinitions($array_definition['context']);
+    }
+    $this->arrayDefinition = $array_definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getArrayDefinition() {
+    return $this->arrayDefinition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mergeDefaultArrayDefinition(array $other_definition) {
+    $this->arrayDefinition = NestedArray::mergeDeepArray([$other_definition, $this->arrayDefinition], TRUE);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mergeOverrideArrayDefinition(array $other_definition) {
+    $this->arrayDefinition = NestedArray::mergeDeepArray([$this->arrayDefinition, $other_definition], TRUE);
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setId($id) {
+    $this->arrayDefinition['id'] = $id;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return isset($this->arrayDefinition['id']) ? $this->arrayDefinition['id'] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setClass($class) {
+    PluginDefinitionValidator::validateClass($class);
+
+    $this->arrayDefinition['class'] = $class;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClass() {
+    return isset($this->arrayDefinition['class']) ? $this->arrayDefinition['class'] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLabel($label) {
+    $this->arrayDefinition['label'] = $label;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return isset($this->arrayDefinition['label']) ? $this->arrayDefinition['label'] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDeriverClass($class) {
+    PluginDefinitionValidator::validateDeriverClass($class);
+
+    $this->arrayDefinition['deriver'] = $class;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDeriverClass() {
+    return isset($this->arrayDefinition['deriver']) ? $this->arrayDefinition['deriver'] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContextDefinitions(array $context_definitions) {
+    PluginDefinitionValidator::validateContextDefinitions($context_definitions);
+
+    $this->arrayDefinition['context'] = $context_definitions;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextDefinitions() {
+    return isset($this->arrayDefinition['context']) ? $this->arrayDefinition['context'] : [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContextDefinition($name, ContextDefinitionInterface $context_definition) {
+    $this->arrayDefinition['context'][$name] = $context_definition;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextDefinition($name) {
+    if (!$this->hasContextDefinition($name)) {
+      throw new \InvalidArgumentException(sprintf('Context %s does not exist.', $name));
+    }
+
+    return $this->arrayDefinition['context'][$name];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasContextDefinition($name) {
+    return isset($this->arrayDefinition['context'][$name]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function doMergeDefaultDefinition(PluginDefinitionInterface $other_definition) {
+    /** @var \Drupal\Component\Plugin\Definition\ArrayPluginDefinitionInterface $other_definition */
+    $this->mergeDefaultArrayDefinition($other_definition->getArrayDefinition());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function doMergeOverrideDefinition(PluginDefinitionInterface $other_definition) {
+    /** @var \Drupal\Component\Plugin\Definition\ArrayPluginDefinitionInterface $other_definition */
+    $this->mergeOverrideArrayDefinition($other_definition->getArrayDefinition());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isDefinitionCompatible(PluginDefinitionInterface $other_definition) {
+    return $other_definition instanceof $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetExists($offset) {
+    return isset($this->arrayDefinition[$offset]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &offsetGet($offset) {
+    return $this->arrayDefinition[$offset];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {
+    switch ($offset) {
+      case 'class':
+        $this->setClass($value);
+        break;
+      case 'deriver':
+        $this->setDeriverClass($value);
+        break;
+      case 'context':
+        $this->setContextDefinitions($value);
+        break;
+      default:
+        $this->arrayDefinition[$offset] = $value;
+        break;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($offset) {
+    unset($this->arrayDefinition[$offset]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count() {
+    return count($this->arrayDefinition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->arrayDefinition);
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinitionInterface.php
new file mode 100644
index 0000000..93291df
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinitionInterface.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a plugin definition.
+ *
+ * For backwards compatibility with array-based plugin definitions, this
+ * interface implements \ArrayAccess. The required array keys and their
+ * corresponding setters and getters are:
+ * - id: static::setId() and static::getId()
+ * - class: static::setClass() and static::getClass()
+ * - label: static::setLabel() and static::getLabel()
+ * - deriver: static::setDeriverClass() and static::getDeriverClass()
+ * - context: static::setContextDefinitions() and static::getContextDefinitions()
+ *
+ * @ingroup Plugin
+ *
+ * @deprecated Deprecated as of 8.0.0. Do not rely on array plugin
+ *   definitions.
+ */
+interface ArrayPluginDefinitionInterface extends PluginDefinitionInterface, \ArrayAccess, \IteratorAggregate, \Countable {
+
+  /**
+   * Gets the array definition.
+   *
+   * @return mixed[]
+   *   The array definition.
+   */
+  public function getArrayDefinition();
+
+  /**
+   * Merges another array definition into this one, using the other for defaults.
+   *
+   * @param array $other_definition
+   *   The other array definition to merge into self::arrayDefinition.
+   *
+   * @return $this
+   */
+  public function mergeDefaultArrayDefinition(array $other_definition);
+
+  /**
+   * Merges another array definition into this one, using the other for overrides.
+   *
+   * @param array $other_definition
+   *   The other array definition to merge into self::arrayDefinition.
+   *
+   * @return $this
+   */
+  public function mergeOverrideArrayDefinition(array $other_definition);
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/MergeablePluginDefinitionTrait.php b/core/lib/Drupal/Component/Plugin/Definition/MergeablePluginDefinitionTrait.php
new file mode 100644
index 0000000..8e42579
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/MergeablePluginDefinitionTrait.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\MergeablePluginDefinitionTrait.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Implements the plugin merger parts of \Drupal\Component\Plugin\Definition\PluginDefinitionInterface.
+ *
+ * @ingroup Plugin
+ */
+trait MergeablePluginDefinitionTrait {
+
+  /**
+   * Implements \Drupal\Component\Plugin\Definition\PluginDefinitionInterface::mergeDefaultDefinition().
+   */
+  public function mergeDefaultDefinition(PluginDefinitionInterface $other_definition) {
+    $this->validateMergeDefinition($other_definition);
+    $this->doMergeDefaultDefinition($other_definition);
+
+    return $this;
+  }
+
+  /**
+   * Merges another definition into this one, using the other for defaults.
+   *
+   * @param static $other_definition
+   *   The other definition to merge into $this. It will not override $this, but
+   *   be used to extract default values from instead.
+   */
+  protected function doMergeDefaultDefinition(PluginDefinitionInterface $other_definition) {
+    // Child classes can override this to perform an actual merge.
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\Definition\PluginDefinitionInterface::mergeOverrideDefinition().
+   */
+  public function mergeOverrideDefinition(PluginDefinitionInterface $other_definition) {
+    $this->validateMergeDefinition($other_definition);
+    $this->doMergeOverrideDefinition($other_definition);
+
+    return $this;
+  }
+
+  /**
+   * Merges another definition into this one, using the other for overrides.
+   *
+   * @param static $other_definition
+   *   The other definition to merge into $this. It will override any values
+   *   already set in $this.
+   */
+  protected function doMergeOverrideDefinition(PluginDefinitionInterface $other_definition) {
+    // Child classes can override this to perform an actual merge.
+  }
+
+  /**
+   * Validates whether another definition is compatible with this one.
+   *
+   * @throws \InvalidArgumentException
+   */
+  protected function validateMergeDefinition(PluginDefinitionInterface $other_definition) {
+    if (!$this->isDefinitionCompatible($other_definition)) {
+      throw new \InvalidArgumentException(sprintf('$other_definition must be an instance of %s, but %s was given.', get_class($this), get_class($other_definition)));
+    }
+  }
+
+  /**
+   * Returns whether another definition is compatible with this one.
+   *
+   * @return bool
+   *   Whether or not the definition is compatible with $this.
+   */
+  abstract protected function isDefinitionCompatible(PluginDefinitionInterface $other_definition);
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginContextDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginContextDefinitionInterface.php
new file mode 100644
index 0000000..8de9086
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginContextDefinitionInterface.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginContextDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
+
+/**
+ * Defines a plugin definition that includes contexts.
+ *
+ * @ingroup Plugin
+ */
+interface PluginContextDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the context definitions.
+   *
+   * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface[] $context_definitions
+   *   The array of context definitions, keyed by context name.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if the definitions are invalid.
+   */
+  public function setContextDefinitions(array $context_definitions);
+
+  /**
+   * Gets the context definitions.
+   *
+   * @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface[]
+   *   The array of context definitions, keyed by context name.
+   */
+  public function getContextDefinitions();
+
+  /**
+   * Sets a specific context definition.
+   *
+   * @param string $name
+   *   The name of the context in the plugin definition.
+   * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $context_definition
+   *   The context definition to set.
+   *
+   * @return $this
+   */
+  public function setContextDefinition($name, ContextDefinitionInterface $context_definition);
+
+  /**
+   * Gets a specific context definition.
+   *
+   * @param string $name
+   *   The name of the context in the plugin definition.
+   *
+   * @throws \InvalidArgumentException
+   *   If the requested context does not exist.
+   *
+   * @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface
+   *
+   * @see self::hasContextDefinition()
+   */
+  public function getContextDefinition($name);
+
+  /**
+   * Checks if a specific context definition exists.
+   *
+   * @param string $name
+   *   The name of the context in the plugin definition.
+   *
+   * @return bool
+   *   Whether the context definition exists.
+   */
+  public function hasContextDefinition($name);
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginContextDefinitionTrait.php b/core/lib/Drupal/Component/Plugin/Definition/PluginContextDefinitionTrait.php
new file mode 100644
index 0000000..813cc27
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginContextDefinitionTrait.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginContextDefinitionTrait.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
+
+/**
+ * Implements \Drupal\Component\Plugin\PluginContextDefinitionInterface.
+ *
+ * @ingroup Plugin
+ */
+trait PluginContextDefinitionTrait {
+
+  /**
+   * The context definitions.
+   *
+   * @var \Drupal\Component\Plugin\Context\ContextDefinitionInterface[]
+   */
+  protected $contextDefinitions = [];
+
+  /**
+   * Implements \Drupal\Component\Plugin\PluginContextDefinitionInterface::setContextDefinitions().
+   */
+  public function setContextDefinitions(array $context_definitions) {
+    PluginDefinitionValidator::validateContextDefinitions($context_definitions);
+
+    $this->contextDefinitions = $context_definitions;
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\PluginContextDefinitionInterface::getContextDefinitions().
+   */
+  public function getContextDefinitions() {
+    return $this->contextDefinitions;
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\PluginContextDefinitionInterface::setContextDefinition().
+   */
+  public function setContextDefinition($name, ContextDefinitionInterface $context_definition) {
+    $this->contextDefinitions[$name] = $context_definition;
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\PluginContextDefinitionInterface::getContextDefinition().
+   */
+  public function getContextDefinition($name) {
+    if (!$this->hasContextDefinition($name)) {
+      throw new \InvalidArgumentException(sprintf('Context %s does not exist.', $name));
+    }
+
+    return $this->contextDefinitions[$name];
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\PluginContextDefinitionInterface::hasContextDefinition().
+   */
+  public function hasContextDefinition($name) {
+    return isset($this->contextDefinitions[$name]);
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDefinition.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinition.php
new file mode 100644
index 0000000..418fe20
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinition.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginDefinition.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Provides a plugin definition.
+ *
+ * @ingroup Plugin
+ */
+abstract class PluginDefinition implements PluginDefinitionInterface {
+
+  use MergeablePluginDefinitionTrait;
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The class.
+   *
+   * @var string
+   *   A fully qualified class name.
+   */
+  protected $class;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setId($id) {
+    $this->id = $id;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setClass($class) {
+    PluginDefinitionValidator::validateClass($class);
+
+    $this->class = $class;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClass() {
+    return $this->class;
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php
new file mode 100644
index 0000000..43ad3c0
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a plugin definition.
+ *
+ * @ingroup Plugin
+ */
+interface PluginDefinitionInterface {
+
+  /**
+   * Sets the plugin ID.
+   *
+   * @param string $id
+   *   The plugin ID.
+   *
+   * @return $this
+   */
+  public function setId($id);
+
+  /**
+   * Gets the plugin ID.
+   *
+   * @return string
+   *   The plugin ID.
+   */
+  public function getId();
+
+  /**
+   * Sets the class.
+   *
+   * @param string $class
+   *   A fully qualified class name.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function setClass($class);
+
+  /**
+   * Gets the class.
+   *
+   * @return string
+   *   A fully qualified class name.
+   */
+  public function getClass();
+
+  /**
+   * Merges another definition into this one, using the other for defaults.
+   *
+   * @param static $other_definition
+   *   The other definition to merge into $this. It will not override $this, but
+   *   be used to extract default values from instead.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if $other_definition is no instance of $this.
+   */
+  public function mergeDefaultDefinition(PluginDefinitionInterface $other_definition);
+
+  /**
+   * Merges another definition into this one, using the other for overrides.
+   *
+   * @param static $other_definition
+   *   The other definition to merge into $this. It will override any values
+   *   already set in $this.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if $other_definition is no instance of $this.
+   */
+  public function mergeOverrideDefinition(PluginDefinitionInterface $other_definition);
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionValidator.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionValidator.php
new file mode 100644
index 0000000..edc3d3b
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionValidator.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginDefinitionValidator.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
+
+/**
+ * Provides plugin definition validation.
+ *
+ * @ingroup Plugin
+ */
+class PluginDefinitionValidator {
+
+  /**
+   * Validates a plugin class.
+   *
+   * @param string $class
+   *   A fully qualified class name.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the class is invalid.
+   */
+  public static function validateClass($class) {
+    if (!class_exists($class)) {
+      throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
+    }
+  }
+
+  /**
+   * Validates a plugin deriver class.
+   *
+   * @param string $class
+   *   A fully qualified class name.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the class is invalid.
+   */
+  public static function validateDeriverClass($class) {
+    static::validateClass($class);
+
+    if (!is_subclass_of($class, 'Drupal\Component\Plugin\Derivative\DeriverInterface')) {
+      throw new \InvalidArgumentException('Plugin deriver classes must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.');
+    }
+  }
+
+  /**
+   * Validates plugin context definitions.
+   *
+   * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface[] $context_definitions
+   *   The array of context definitions, keyed by context name.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the class is invalid.
+   */
+  public static function validateContextDefinitions(array $context_definitions) {
+    foreach ($context_definitions as $name => $context_definition) {
+      if (!($context_definition instanceof ContextDefinitionInterface)) {
+        throw new \InvalidArgumentException(sprintf('$context_definition[%s] does not implement \Drupal\Component\Plugin\Context\ContextDefinitionInterface', $name));
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionInterface.php
new file mode 100644
index 0000000..2c442da
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginDeriverDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that includes a deriver.
+ *
+ * @ingroup Plugin
+ */
+interface PluginDeriverDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the deriver class.
+   *
+   * @param string $class
+   *   The fully qualified name of a class that implements
+   *   \Drupal\Component\Plugin\Derivative\DeriverInterface.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function setDeriverClass($class);
+
+  /**
+   * Gets the deriver class.
+   *
+   * @return string|null
+   *   The fully qualified name of a class that implements
+   *   \Drupal\Component\Plugin\Derivative\DeriverInterface or null.
+   */
+  public function getDeriverClass();
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionTrait.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionTrait.php
new file mode 100644
index 0000000..97cf223
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionTrait.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginDeriverDefinitionTrait.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Implements \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface.
+ *
+ * @ingroup Plugin
+ */
+trait PluginDeriverDefinitionTrait {
+
+  /**
+   * The deriver class.
+   *
+   * @var string
+   *   The fully qualified name of a class that implements
+   *   \Drupal\Component\Plugin\Derivative\DeriverInterface.
+   */
+  protected $deriverClass;
+
+  /**
+   * Implements \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface::setDeriverClass().
+   */
+  public function setDeriverClass($class) {
+    PluginDefinitionValidator::validateClass($class);
+
+    $this->deriverClass = $class;
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface::getDeriverClass().
+   */
+  public function getDeriverClass() {
+    return $this->deriverClass;
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionInterface.php
new file mode 100644
index 0000000..877520d
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginLabelDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that includes a label.
+ *
+ * @ingroup Plugin
+ */
+interface PluginLabelDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the human-readable plugin label.
+   *
+   * @param string $label
+   *   The label.
+   *
+   * @return $this
+   */
+  public function setLabel($label);
+
+  /**
+   * Gets the human-readable plugin label.
+   *
+   * @return string|null
+   *   The label or NULL if there is none.
+   */
+  public function getLabel();
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionTrait.php b/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionTrait.php
new file mode 100644
index 0000000..f501c9f
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionTrait.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginLabelDefinitionTrait.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Implements \Drupal\Component\Plugin\PluginLabelDefinitionInterface.
+ *
+ * @ingroup Plugin
+ */
+trait PluginLabelDefinitionTrait {
+
+  /**
+   * The human-readable label.
+   *
+   * @var string|null
+   */
+  protected $label;
+
+  /**
+   * Implements \Drupal\Component\Plugin\PluginLabelDefinitionInterface::setLabel().
+   */
+  public function setLabel($label) {
+    $this->label = $label;
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\PluginLabelDefinitionInterface::getLabel().
+   */
+  public function getLabel() {
+    return $this->label;
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Derivative/DeriverBase.php b/core/lib/Drupal/Component/Plugin/Derivative/DeriverBase.php
index f989bf6..3a7a7af 100644
--- a/core/lib/Drupal/Component/Plugin/Derivative/DeriverBase.php
+++ b/core/lib/Drupal/Component/Plugin/Derivative/DeriverBase.php
@@ -6,6 +6,7 @@
  */
 
 namespace Drupal\Component\Plugin\Derivative;
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * Provides a basic deriver.
@@ -15,14 +16,14 @@
   /**
    * List of derivative definitions.
    *
-   * @var array
+   * @var \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]
    */
   protected $derivatives = array();
 
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
       return $this->derivatives[$derivative_id];
     }
@@ -33,7 +34,7 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     return $this->derivatives;
   }
 }
diff --git a/core/lib/Drupal/Component/Plugin/Derivative/DeriverInterface.php b/core/lib/Drupal/Component/Plugin/Derivative/DeriverInterface.php
index 43b9c07..fef9857 100644
--- a/core/lib/Drupal/Component/Plugin/Derivative/DeriverInterface.php
+++ b/core/lib/Drupal/Component/Plugin/Derivative/DeriverInterface.php
@@ -6,6 +6,7 @@
  */
 
 namespace Drupal\Component\Plugin\Derivative;
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * Provides additional plugin definitions based on an existing definition.
@@ -20,28 +21,28 @@
    * @param string $derivative_id
    *   The derivative id. The id must uniquely identify the derivative within a
    *   given base plugin, but derivative ids can be reused across base plugins.
-   * @param mixed $base_plugin_definition
+   * @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface $base_plugin_definition
    *   The definition of the base plugin from which the derivative plugin
-   *   is derived. It is maybe an entire object or just some array, depending
-   *   on the discovery mechanism.
+   *   is derived.
    *
-   * @return array
-   *   The full definition array of the derivative plugin, typically a merge of
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface|null
+   *   The full definition of the derivative plugin, typically a merge of
    *   $base_plugin_definition with extra derivative-specific information. NULL
    *   if the derivative doesn't exist.
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition);
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition);
 
   /**
    * Gets the definition of all derivatives of a base plugin.
    *
-   * @param array $base_plugin_definition
-   *   The definition array of the base plugin.
-   * @return array
+   * @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface $base_plugin_definition
+   *   The definition of the base plugin.
+   *
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]
    *   An array of full derivative definitions keyed on derivative id.
    *
    * @see getDerivativeDefinition()
    */
-  public function getDerivativeDefinitions($base_plugin_definition);
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition);
 
 }
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php b/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
index 04e360f..22f33b6 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
@@ -7,7 +7,8 @@
 
 namespace Drupal\Component\Plugin\Discovery;
 
-use Drupal\Component\Plugin\Exception\InvalidDeriverException;
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
+use Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface;
 
 /**
  * Base class providing the tools for a plugin discovery to be derivative aware.
@@ -47,10 +48,6 @@ public function __construct(DiscoveryInterface $decorated) {
 
   /**
    * {@inheritdoc}
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
-   *   Thrown if the 'deriver' class specified in the plugin definition
-   *   does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
    */
   public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
     // This check is only for derivative plugins that have explicitly provided
@@ -58,7 +55,6 @@ public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
     // out of the thrown exception, which will be handled when checking the
     // $base_plugin_id.
     $plugin_definition = $this->decorated->getDefinition($plugin_id, FALSE);
-
     list($base_plugin_id, $derivative_id) = $this->decodePluginId($plugin_id);
     $base_plugin_definition = $this->decorated->getDefinition($base_plugin_id, $exception_on_invalid);
     if ($base_plugin_definition) {
@@ -68,11 +64,14 @@ public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
         // If a plugin defined itself as a derivative, merge in possible
         // defaults from the derivative.
         if ($derivative_id && isset($plugin_definition)) {
-          $plugin_definition = $this->mergeDerivativeDefinition($plugin_definition, $derivative_plugin_definition);
+          $plugin_definition = $derivative_plugin_definition->mergeOverrideDefinition($plugin_definition);
         }
         else {
           $plugin_definition = $derivative_plugin_definition;
         }
+        if ($plugin_definition) {
+          $plugin_definition->setId($plugin_id);
+        }
       }
     }
 
@@ -81,10 +80,6 @@ public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
 
   /**
    * {@inheritdoc}
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
-   *   Thrown if the 'deriver' class specified in the plugin definition
-   *   does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
    */
   public function getDefinitions() {
     $plugin_definitions = $this->decorated->getDefinitions();
@@ -96,6 +91,10 @@ public function getDefinitions() {
    *
    * This should be called by the class extending this in
    * DiscoveryInterface::getDefinitions().
+   *
+   * @param \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface[] $base_plugin_definitions
+   *
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]
    */
   protected function getDerivatives(array $base_plugin_definitions) {
     $plugin_definitions = array();
@@ -108,8 +107,9 @@ protected function getDerivatives(array $base_plugin_definitions) {
           // Use this definition as defaults if a plugin already defined
           // itself as this derivative.
           if ($derivative_id && isset($base_plugin_definitions[$plugin_id])) {
-            $derivative_definition = $this->mergeDerivativeDefinition($base_plugin_definitions[$plugin_id], $derivative_definition);
+            $derivative_definition->mergeOverrideDefinition($base_plugin_definitions[$plugin_id]);
           }
+          $derivative_definition->setId($plugin_id);
           $plugin_definitions[$plugin_id] = $derivative_definition;
         }
       }
@@ -170,20 +170,16 @@ protected function encodePluginId($base_plugin_id, $derivative_id) {
    *
    * @param string $base_plugin_id
    *   The base plugin id of the plugin.
-   * @param mixed $base_definition
+   * @param \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface $base_definition
    *   The base plugin definition to build derivatives.
    *
    * @return \Drupal\Component\Plugin\Derivative\DeriverInterface|null
    *   A DerivativeInterface or NULL if none exists for the plugin.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
-   *   Thrown if the 'deriver' class specified in the plugin definition
-   *   does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
    */
-  protected function getDeriver($base_plugin_id, $base_definition) {
+  protected function getDeriver($base_plugin_id, PluginDeriverDefinitionInterface $base_definition) {
     if (!isset($this->derivers[$base_plugin_id])) {
       $this->derivers[$base_plugin_id] = FALSE;
-      $class = $this->getDeriverClass($base_definition);
+      $class = $base_definition->getDeriverClass();
       if ($class) {
         $this->derivers[$base_plugin_id] = new $class($base_plugin_id);
       }
@@ -192,54 +188,6 @@ protected function getDeriver($base_plugin_id, $base_definition) {
   }
 
   /**
-   * Gets the deriver class name from the base plugin definition.
-   *
-   * @param array $base_definition
-   *   The base plugin definition to build derivatives.
-   *
-   * @return string|null
-   *   The name of a class implementing
-   *   \Drupal\Component\Plugin\Derivative\DeriverInterface.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
-   *   Thrown if the 'deriver' class specified in the plugin definition
-   *   does not implement
-   *   \Drupal\Component\Plugin\Derivative\DerivativeInterface.
-   */
-  protected function getDeriverClass($base_definition) {
-    $class = NULL;
-    if ((is_array($base_definition) || ($base_definition = (array) $base_definition)) && (isset($base_definition['deriver']) && $class = $base_definition['deriver'])) {
-      if (!class_exists($class)) {
-        throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $base_definition['id'], $class));
-      }
-      if (!is_subclass_of($class, '\Drupal\Component\Plugin\Derivative\DeriverInterface')) {
-        throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.', $base_definition['id'], $class));
-      }
-    }
-    return $class;
-  }
-
-  /**
-   * Merges a base and derivative definition, taking into account empty values.
-   *
-   * @param array $base_plugin_definition
-   *   The base plugin definition.
-   * @param array $derivative_definition
-   *   The derivative plugin definition.
-   *
-   * @return array
-   *   The merged definition.
-   */
-  protected function mergeDerivativeDefinition($base_plugin_definition, $derivative_definition) {
-    // Use this definition as defaults if a plugin already defined itself as
-    // this derivative, but filter out empty values first.
-    $filtered_base = array_filter($base_plugin_definition);
-    $derivative_definition = $filtered_base + ($derivative_definition ?: array());
-    // Add back any empty keys that the derivative didn't have.
-    return $derivative_definition + $base_plugin_definition;
-  }
-
-  /**
    * Passes through all unknown calls onto the decorated object.
    */
   public function __call($method, $args) {
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php
index 75e692f..382d829 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryInterface.php
@@ -23,7 +23,7 @@
    * @param bool $exception_on_invalid
    *   (optional) If TRUE, an invalid plugin ID will throw an exception.
    *
-   * @return mixed
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface|null
    *   A plugin definition, or NULL if the plugin ID is invalid and
    *   $exception_on_invalid is FALSE.
    *
@@ -35,7 +35,7 @@ public function getDefinition($plugin_id, $exception_on_invalid = TRUE);
   /**
    * Gets the definition of all plugins for this type.
    *
-   * @return mixed[]
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]
    *   An array of plugin definitions (empty array if no definitions were
    *   found). Keys are plugin IDs.
    */
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php
index 20007d1..1f96dae 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php
@@ -30,7 +30,7 @@ public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
   /**
    * Gets a specific plugin definition.
    *
-   * @param array $definitions
+   * @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[] $definitions
    *   An array of the available plugin definitions.
    * @param string $plugin_id
    *   A plugin id.
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/StaticDiscovery.php b/core/lib/Drupal/Component/Plugin/Discovery/StaticDiscovery.php
index 0837bbb..4145e71 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/StaticDiscovery.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/StaticDiscovery.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Component\Plugin\Discovery;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
+
 /**
  * A discovery mechanism that allows plugin definitions to be manually
  * registered rather than actively discovered.
@@ -28,8 +30,8 @@ public function getDefinitions() {
   /**
    * Sets a plugin definition.
    */
-  public function setDefinition($plugin, $definition) {
-    $this->definitions[$plugin] = $definition;
+  public function setDefinition($plugin, PluginDefinitionInterface $definition) {
+    $this->definitions[$plugin] = $definition->setId($plugin);
   }
 
   /**
diff --git a/core/lib/Drupal/Component/Plugin/Exception/InvalidDeriverException.php b/core/lib/Drupal/Component/Plugin/Exception/InvalidDeriverException.php
deleted file mode 100644
index b94a9a8..0000000
--- a/core/lib/Drupal/Component/Plugin/Exception/InvalidDeriverException.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-/**
- * @file
- * Contains \Drupal\Component\Plugin\Exception\InvalidDeriverException.
- */
-
-namespace Drupal\Component\Plugin\Exception;
-
-/**
- * Exception to be thrown if a plugin tries to use an invalid deriver.
- */
-class InvalidDeriverException extends PluginException { }
diff --git a/core/lib/Drupal/Component/Plugin/PluginBase.php b/core/lib/Drupal/Component/Plugin/PluginBase.php
index 8e4f1d6..69c0157 100644
--- a/core/lib/Drupal/Component/Plugin/PluginBase.php
+++ b/core/lib/Drupal/Component/Plugin/PluginBase.php
@@ -26,7 +26,7 @@
   /**
    * The plugin implementation definition.
    *
-   * @var array
+   * @var \Drupal\Component\Plugin\Definition\PluginDefinitionInterface
    */
   protected $pluginDefinition;
 
@@ -51,7 +51,7 @@
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin_id for the plugin instance.
-   * @param mixed $plugin_definition
+   * @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    */
   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
diff --git a/core/lib/Drupal/Component/Plugin/PluginInspectionInterface.php b/core/lib/Drupal/Component/Plugin/PluginInspectionInterface.php
index 735e5a0..c8b7070 100644
--- a/core/lib/Drupal/Component/Plugin/PluginInspectionInterface.php
+++ b/core/lib/Drupal/Component/Plugin/PluginInspectionInterface.php
@@ -27,7 +27,7 @@ public function getPluginId();
   /**
    * Gets the definition of the plugin implementation.
    *
-   * @return array
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface
    *   The plugin definition, as returned by the discovery object used by the
    *   plugin manager.
    */
diff --git a/core/lib/Drupal/Component/Utility/SortArray.php b/core/lib/Drupal/Component/Utility/SortArray.php
index 0ed5bbc..d4b8674 100644
--- a/core/lib/Drupal/Component/Utility/SortArray.php
+++ b/core/lib/Drupal/Component/Utility/SortArray.php
@@ -22,17 +22,17 @@ class SortArray {
    *
    * Callback for uasort().
    *
-   * @param array $a
+   * @param array|\ArrayAccess $a
    *   First item for comparison. The compared items should be associative
    *   arrays that optionally include a 'weight' element. For items without a
    *   'weight' element, a default value of 0 will be used.
-   * @param array $b
+   * @param array|\ArrayAccess $b
    *   Second item for comparison.
    *
    * @return int
    *   The comparison result for uasort().
    */
-  public static function sortByWeightElement(array $a, array $b) {
+  public static function sortByWeightElement($a, $b) {
     return static::sortByKeyInt($a, $b, 'weight');
   }
 
@@ -113,9 +113,9 @@ public static function sortByKeyString($a, $b, $key) {
   /**
    * Sorts an integer array item by an arbitrary key.
    *
-   * @param array $a
+   * @param array|\ArrayAccess $a
    *   First item for comparison.
-   * @param array $b
+   * @param array|\ArrayAccess $b
    *   Second item for comparison.
    * @param string $key
    *   The key to use in the comparison.
@@ -124,8 +124,8 @@ public static function sortByKeyString($a, $b, $key) {
    *   The comparison result for uasort().
    */
   public static function sortByKeyInt($a, $b, $key) {
-    $a_weight = (is_array($a) && isset($a[$key])) ? $a[$key] : 0;
-    $b_weight = (is_array($b) && isset($b[$key])) ? $b[$key] : 0;
+    $a_weight = ((is_array($a) || $a instanceof \ArrayAccess) && isset($a[$key])) ? $a[$key] : 0;
+    $b_weight = ((is_array($b) || $a instanceof \ArrayAccess) && isset($b[$key])) ? $b[$key] : 0;
 
     if ($a_weight == $b_weight) {
       return 0;
diff --git a/core/lib/Drupal/Core/Annotation/Action.php b/core/lib/Drupal/Core/Annotation/Action.php
index b35f70d..64dda2b 100644
--- a/core/lib/Drupal/Core/Annotation/Action.php
+++ b/core/lib/Drupal/Core/Annotation/Action.php
@@ -7,8 +7,6 @@
 
 namespace Drupal\Core\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
-
 /**
  * Defines an Action annotation object.
  *
diff --git a/core/lib/Drupal/Core/Annotation/ContextDefinition.php b/core/lib/Drupal/Core/Annotation/ContextDefinition.php
index e820f0c..1be78fc 100644
--- a/core/lib/Drupal/Core/Annotation/ContextDefinition.php
+++ b/core/lib/Drupal/Core/Annotation/ContextDefinition.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Component\Annotation\AnnotationBase;
 use Drupal\Core\StringTranslation\TranslationWrapper;
 
 /**
@@ -76,7 +76,7 @@
  *
  * @ingroup plugin_context
  */
-class ContextDefinition extends Plugin {
+class ContextDefinition extends AnnotationBase {
 
   /**
    * The ContextDefinitionInterface object.
diff --git a/core/lib/Drupal/Core/Annotation/Mail.php b/core/lib/Drupal/Core/Annotation/Mail.php
index 4f0a11d..63d42e3 100644
--- a/core/lib/Drupal/Core/Annotation/Mail.php
+++ b/core/lib/Drupal/Core/Annotation/Mail.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a Mail annotation object.
diff --git a/core/lib/Drupal/Core/Annotation/Plugin.php b/core/lib/Drupal/Core/Annotation/Plugin.php
new file mode 100644
index 0000000..c24f8f6
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/Plugin.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Annotation\Plugin.
+ */
+
+namespace Drupal\Core\Annotation;
+
+use Drupal\Component\Annotation\Plugin as ComponentPlugin;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+
+/**
+ * Defines a Plugin annotation object.
+ *
+ * Annotations in plugin classes can use this class in order to pass various
+ * metadata about the plugin through the parser to
+ * DiscoveryInterface::getDefinitions() calls. This allows the metadata
+ * of a class to be located with the class itself, rather than in module-based
+ * info hooks.
+ *
+ * @ingroup plugin_api
+ *
+ * @Annotation
+ */
+class Plugin extends ComponentPlugin {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\Core\Plugin\Definition\PluginDefinitionInterface
+   */
+  public function get() {
+    return new ArrayPluginDefinition($this->definition);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Annotation/PluginID.php b/core/lib/Drupal/Core/Annotation/PluginID.php
new file mode 100644
index 0000000..d0055f9
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/PluginID.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Component\Annotation\PluginID.
+ */
+
+namespace Drupal\Core\Annotation;
+
+use Drupal\Component\Annotation\PluginID as ComponentPluginID;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+
+/**
+ * Defines a Plugin annotation object that just contains an ID.
+ *
+ * @Annotation
+ */
+class PluginID extends ComponentPluginId {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get() {
+    return new ArrayPluginDefinition(array(
+      'id' => $this->value,
+      'class' => $this->class,
+      'provider' => $this->provider,
+    ));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Annotation/QueueWorker.php b/core/lib/Drupal/Core/Annotation/QueueWorker.php
index 85a2296..419683c 100644
--- a/core/lib/Drupal/Core/Annotation/QueueWorker.php
+++ b/core/lib/Drupal/Core/Annotation/QueueWorker.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Declare queue workers that need to be run periodically.
diff --git a/core/lib/Drupal/Core/Archiver/Annotation/Archiver.php b/core/lib/Drupal/Core/Archiver/Annotation/Archiver.php
index 9aff246..f095add 100644
--- a/core/lib/Drupal/Core/Archiver/Annotation/Archiver.php
+++ b/core/lib/Drupal/Core/Archiver/Annotation/Archiver.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Archiver\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines an archiver annotation object.
diff --git a/core/lib/Drupal/Core/Block/Annotation/Block.php b/core/lib/Drupal/Core/Block/Annotation/Block.php
index e29f80d..dccdca1 100644
--- a/core/lib/Drupal/Core/Block/Annotation/Block.php
+++ b/core/lib/Drupal/Core/Block/Annotation/Block.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Block\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a Block annotation object.
diff --git a/core/lib/Drupal/Core/Block/BlockManager.php b/core/lib/Drupal/Core/Block/BlockManager.php
index 086016d..c58dced 100644
--- a/core/lib/Drupal/Core/Block/BlockManager.php
+++ b/core/lib/Drupal/Core/Block/BlockManager.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
 use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * Manages discovery and instantiation of block plugins.
@@ -50,7 +51,7 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
     $this->processDefinitionCategory($definition);
   }
diff --git a/core/lib/Drupal/Core/Condition/Annotation/Condition.php b/core/lib/Drupal/Core/Condition/Annotation/Condition.php
index d796865..d9b167c 100644
--- a/core/lib/Drupal/Core/Condition/Annotation/Condition.php
+++ b/core/lib/Drupal/Core/Condition/Annotation/Condition.php
@@ -6,7 +6,7 @@
 
 namespace Drupal\Core\Condition\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a condition plugin annotation object.
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php
index a762de9..7288b41 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigDependencyManager.php
@@ -70,7 +70,8 @@
  * Classes for configurable plugins are a special case. They can either declare
  * their configuration dependencies using the calculateDependencies() method
  * described in the paragraph above, or if they have only static dependencies,
- * these can be declared using the 'config_dependencies' annotation key.
+ * these can be declared by making their definitions implement
+ * \Drupal\Core\Plugin\Definition\PluginConfigDependenciesDefinitionInterface.
  *
  * If an extension author wants a configuration entity to depend on something
  * that is not calculable then they can add these dependencies to the enforced
diff --git a/core/lib/Drupal/Core/Config/Schema/ArrayElement.php b/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
index 35e932a..42f07d0 100644
--- a/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
+++ b/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
@@ -9,6 +9,8 @@
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Config\TypedConfigManagerInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
+use Drupal\Core\TypedData\DataDefinitionInterface;
 use Drupal\Core\TypedData\TypedData;
 
 /**
@@ -152,7 +154,7 @@ public function getIterator() {
    *
    * @return \Drupal\Core\TypedData\TypedDataInterface
    */
-  protected function createElement($definition, $value, $key) {
+  protected function createElement(DataDefinitionInterface $definition, $value, $key) {
     return $this->typedConfig->create($definition, $value, $key, $this);
   }
 
@@ -160,9 +162,8 @@ protected function createElement($definition, $value, $key) {
    * Creates a new data definition object from a type definition array and
    * actual configuration data.
    *
-   * @param array $definition
-   *   The base type definition array, for which a data definition should be
-   *   created.
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $definition
+   *   The base type definition, for which a data definition should be created.
    * @param $value
    *   The value of the configuration element.
    * @param string $key
@@ -170,7 +171,7 @@ protected function createElement($definition, $value, $key) {
    *
    * @return \Drupal\Core\TypedData\DataDefinitionInterface
    */
-  protected function buildDataDefinition($definition, $value, $key) {
+  protected function buildDataDefinition(PluginDefinitionInterface $definition, $value, $key) {
     return $this->typedConfig->buildDataDefinition($definition, $value, $key, $this);
   }
 
diff --git a/core/lib/Drupal/Core/Config/Schema/ConfigSchemaDiscovery.php b/core/lib/Drupal/Core/Config/Schema/ConfigSchemaDiscovery.php
index 53330ec..ff09d9e 100644
--- a/core/lib/Drupal/Core/Config/Schema/ConfigSchemaDiscovery.php
+++ b/core/lib/Drupal/Core/Config/Schema/ConfigSchemaDiscovery.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
 use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Allows YAML files to define config schema types.
@@ -42,7 +43,7 @@ public function getDefinitions() {
     $definitions = array();
     foreach ($this->schemaStorage->readMultiple($this->schemaStorage->listAll()) as $schema) {
       foreach ($schema as $type => $definition) {
-        $definitions[$type] = $definition;
+        $definitions[$type] = new ArrayPluginDefinition($definition);
       }
     }
     return $definitions;
diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php
index cf02dd4..fb0a927 100644
--- a/core/lib/Drupal/Core/Config/Schema/Mapping.php
+++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Config\Schema;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+
 /**
  * Defines a mapping configuration element.
  *
@@ -27,8 +29,8 @@ class Mapping extends ArrayElement {
    */
   protected function getElementDefinition($key) {
     $value = isset($this->value[$key]) ? $this->value[$key] : NULL;
-    $definition = isset($this->definition['mapping'][$key]) ? $this->definition['mapping'][$key] : array();
-    return $this->buildDataDefinition($definition, $value, $key);
+    $definition = isset($this->definition['mapping'][$key]) ? $this->definition['mapping'][$key] : [];
+    return $this->buildDataDefinition(new ArrayPluginDefinition($definition), $value, $key);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Config/Schema/Sequence.php b/core/lib/Drupal/Core/Config/Schema/Sequence.php
index 0c0900a..3840b20 100644
--- a/core/lib/Drupal/Core/Config/Schema/Sequence.php
+++ b/core/lib/Drupal/Core/Config/Schema/Sequence.php
@@ -6,6 +6,7 @@
  */
 
 namespace Drupal\Core\Config\Schema;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Defines a configuration element of type Sequence.
@@ -31,7 +32,7 @@ protected function getElementDefinition($key) {
     elseif ($this->definition['sequence']) {
       $definition = $this->definition['sequence'];
     }
-    return $this->buildDataDefinition($definition, $value, $key);
+    return $this->buildDataDefinition(new ArrayPluginDefinition($definition), $value, $key);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Config/TypedConfigManager.php b/core/lib/Drupal/Core/Config/TypedConfigManager.php
index 6bd33cc..487b057 100644
--- a/core/lib/Drupal/Core/Config/TypedConfigManager.php
+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -14,6 +14,8 @@
 use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
 use Drupal\Core\Config\Schema\ConfigSchemaDiscovery;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\TypedData\TypedDataManager;
 
 /**
@@ -90,9 +92,12 @@ public function get($name) {
   /**
    * {@inheritdoc}
    */
-  public function buildDataDefinition(array $definition, $value, $name = NULL, $parent = NULL) {
+  public function buildDataDefinition(PluginDefinitionInterface $definition, $value, $name = NULL, $parent = NULL) {
+    /** @var \Drupal\Core\Plugin\Definition\ArrayPluginDefinition $definition */
     // Add default values for data type and replace variables.
-    $definition += array('type' => 'undefined');
+    if (!isset($definition['type'])) {
+      $definition['type'] = 'undefined';
+    }
 
     $type = $definition['type'];
     if (strpos($type, ']')) {
@@ -110,7 +115,7 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
       unset($definition['type']);
     }
     // Add default values from type definition.
-    $definition += $this->getDefinition($type);
+    $definition->mergeDefaultDefinition($this->getDefinition($type));
 
     $data_definition = $this->createDataDefinition($definition['type']);
 
@@ -141,20 +146,20 @@ public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
     }
     $definition = $definitions[$type];
     // Check whether this type is an extension of another one and compile it.
-    if (isset($definition['type'])) {
+    // Use a special public class property to keep track of the merge so we
+    // don't accidentally collide the real definition properties.
+    if (isset($definition['type']) && !isset($definition->_merged)) {
+      $definition->_merged = TRUE;
       $merge = $this->getDefinition($definition['type'], $exception_on_invalid);
-      // Preserve integer keys on merge, so sequence item types can override
-      // parent settings as opposed to adding unused second, third, etc. items.
-      $definition = NestedArray::mergeDeepArray(array($merge, $definition), TRUE);
+      $definition->mergeDefaultDefinition($merge);
       // Unset type so we try the merge only once per type.
       unset($definition['type']);
-      $this->definitions[$type] = $definition;
     }
     // Add type and default definition class.
-    $definition += array(
+    $definition->mergeDefaultArrayDefinition([
       'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
       'type' => $type,
-    );
+    ]);
     return $definition;
   }
 
@@ -313,7 +318,7 @@ protected function replaceVariable($value, $data) {
   public function hasConfigSchema($name) {
     // The schema system falls back on the Undefined class for unknown types.
     $definition = $this->getDefinition($name);
-    return is_array($definition) && ($definition['class'] != '\Drupal\Core\Config\Schema\Undefined');
+    return !is_null($definition) && ($definition['class'] != '\Drupal\Core\Config\Schema\Undefined');
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php b/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
index 11c3751..b2fed41 100644
--- a/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
+++ b/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
 use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\TypedData\DataDefinitionInterface;
 
 /**
@@ -79,9 +80,8 @@ public function create(DataDefinitionInterface $definition, $value, $name = NULL
    * actual configuration data. Since type definitions may contain variables
    * to be replaced, we need the configuration value to create it.
    *
-   * @param array $definition
-   *   The base type definition array, for which a data definition should be
-   *   created.
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $definition
+   *   The base type definition, for which a data definition should be created.
    * @param $value
    *   Optional value of the configuration element.
    * @param string $name
@@ -92,7 +92,7 @@ public function create(DataDefinitionInterface $definition, $value, $name = NULL
    * @return \Drupal\Core\TypedData\DataDefinitionInterface
    *   A data definition for the given data type.
    */
-  public function buildDataDefinition(array $definition, $value, $name = NULL, $parent = NULL);
+  public function buildDataDefinition(PluginDefinitionInterface $definition, $value, $name = NULL, $parent = NULL);
 
   /**
    * Checks if the configuration schema with the given config name exists.
@@ -114,8 +114,8 @@ public function hasConfigSchema($name);
    *   Ignored with TypedConfigManagerInterface. Kept for compatibility with
    *   DiscoveryInterface.
    *
-   * @return array
-   *   A plugin definition array. If the given plugin id does not have typed
+   * @return \Drupal\Core\Plugin\Definition\PluginDefinitionInterface
+   *   A plugin definition. If the given plugin id does not have typed
    *   configuration definition assigned, the definition of an undefined
    *   element type is returned.
    */
diff --git a/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php b/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php
index 3bf7492..b010bff 100644
--- a/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php
+++ b/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Display\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a display variant annotation object.
diff --git a/core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php b/core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php
index 4720b35..0377ede 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php
@@ -36,10 +36,8 @@ class ConfigEntityType extends EntityType {
   /**
    * {@inheritdoc}
    */
-  public function get() {
+  protected function preprocessArrayDefinition(array &$definition) {
     $this->definition['group_label'] = new TranslationWrapper('Configuration', array(), array('context' => 'Entity type group'));
-
-    return parent::get();
   }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php b/core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php
index 34363a5..8ed6426 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php
@@ -36,10 +36,8 @@ class ContentEntityType extends EntityType {
   /**
    * {@inheritdoc}
    */
-  public function get() {
-    $this->definition['group_label'] = new TranslationWrapper('Content', array(), array('context' => 'Entity type group'));
-
-    return parent::get();
+  protected function preprocessArrayDefinition(array &$definition) {
+    $definition['group_label'] = new TranslationWrapper('Content', array(), array('context' => 'Entity type group'));
   }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php b/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php
index 96e2283..98ef03e 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Entity\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines an EntityReferenceSelection plugin annotation object.
diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
index d40e8fe..095f92b 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Entity\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
@@ -54,13 +54,21 @@ class EntityType extends Plugin {
    * {@inheritdoc}
    */
   public function get() {
-    $values = $this->definition;
-
     // Use the specified entity type class, and remove it before instantiating.
-    $class = $values['entity_type_class'];
-    unset($values['entity_type_class']);
+    $class = $this->definition['entity_type_class'];
+    unset($this->definition['entity_type_class']);
+
+    $this->preprocessArrayDefinition($this->definition);
+
+    return new $class($this->definition);
+  }
 
-    return new $class($values);
+  /**
+   * Preprocesses an array definition.
+   *
+   * @param array $definition
+   */
+  protected function preprocessArrayDefinition(array &$definition) {
   }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 2d03dff..66ae17e 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -29,6 +29,7 @@
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -230,7 +231,7 @@ public function clearCachedDefinitions() {
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     /** @var \Drupal\Core\Entity\EntityTypeInterface $definition */
     parent::processDefinition($definition, $plugin_id);
 
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index e121d75..ba9fb8b 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -7,9 +7,14 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
+use Drupal\Component\Plugin\Definition\PluginDefinitionValidator;
+use Drupal\Component\Plugin\Definition\PluginLabelDefinitionTrait;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Entity\Exception\EntityTypeIdLengthException;
+use Drupal\Core\Plugin\Definition\PluginConfigDependenciesDefinitionTrait;
+use Drupal\Core\Plugin\Definition\PluginDefinition;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
@@ -17,8 +22,10 @@
  *
  * @ingroup entity_api
  */
-class EntityType implements EntityTypeInterface {
+class EntityType extends PluginDefinition implements EntityTypeInterface {
 
+  use PluginConfigDependenciesDefinitionTrait;
+  use PluginLabelDefinitionTrait;
   use StringTranslationTrait;
 
   /**
@@ -50,27 +57,6 @@ class EntityType implements EntityTypeInterface {
   protected $entity_keys = array();
 
   /**
-   * The unique identifier of this entity type.
-   *
-   * @var string
-   */
-  protected $id;
-
-  /**
-   * The name of the provider of this entity type.
-   *
-   * @var string
-   */
-  protected $provider;
-
-  /**
-   * The name of the entity type class.
-   *
-   * @var string
-   */
-  protected $class;
-
-  /**
    * The name of the original entity type class.
    *
    * This is only set if the class name is changed.
@@ -172,13 +158,6 @@ class EntityType implements EntityTypeInterface {
   protected $translatable = FALSE;
 
   /**
-   * The human-readable name of the type.
-   *
-   * @var string
-   */
-  protected $label = '';
-
-  /**
    * A callable that can be used to provide the entity URI.
    *
    * @var callable|null
@@ -242,7 +221,7 @@ class EntityType implements EntityTypeInterface {
    * @throws \Drupal\Core\Entity\Exception\EntityTypeIdLengthException
    *   Thrown when attempting to instantiate an entity type with too long ID.
    */
-  public function __construct($definition) {
+  public function __construct(array $definition) {
     // Throw an exception if the entity type ID is longer than 32 characters.
     if (Unicode::strlen($definition['id']) > static::ID_MAX_LENGTH) {
       throw new EntityTypeIdLengthException(SafeMarkup::format(
@@ -284,6 +263,13 @@ public function __construct($definition) {
   /**
    * {@inheritdoc}
    */
+  public function isDefinitionCompatible(PluginDefinitionInterface $other_definition) {
+    return $other_definition instanceof EntityType;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function get($property) {
     return isset($this->{$property}) ? $this->{$property} : NULL;
   }
@@ -350,28 +336,26 @@ public function id() {
   /**
    * {@inheritdoc}
    */
-  public function getProvider() {
-    return $this->provider;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getClass() {
-    return $this->class;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getOriginalClass() {
     return $this->originalClass ?: $this->class;
   }
 
   /**
-   * {@inheritdoc}
+   * Sets the class.
+   *
+   * @param string $class
+   *   A class that implements \Drupal\Core\Entity\EntityInterface.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
    */
   public function setClass($class) {
+    PluginDefinitionValidator::validateClass($class);
+    if (!is_subclass_of($class, 'Drupal\Core\Entity\EntityInterface')) {
+      throw new \InvalidArgumentException('Entity classes must implement \Drupal\Core\Entity\EntityInterface.');
+    }
+
     if (!$this->originalClass && $this->class) {
       // If the original class is currently not set, set it to the current
       // class, assume that is the original class name.
@@ -675,13 +659,6 @@ public function getDataTable() {
   /**
    * {@inheritdoc}
    */
-  public function getLabel() {
-    return (string) $this->label;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getLowercaseLabel() {
     return Unicode::strtolower($this->getLabel());
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index 46c2058..4f2b6aa 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
+
 /**
  * Provides an interface for an entity type and its metadata.
  *
@@ -15,7 +17,7 @@
  * implemented to alter existing data and fill-in defaults. Module-specific
  * properties should be documented in the hook implementations defining them.
  */
-interface EntityTypeInterface {
+interface EntityTypeInterface extends PluginDefinitionInterface {
 
   /**
    * The maximum length of ID, in characters.
@@ -59,22 +61,6 @@ public function set($property, $value);
   public function id();
 
   /**
-   * Gets the name of the provider of this entity type.
-   *
-   * @return string
-   *   The name of the provider of this entity type.
-   */
-  public function getProvider();
-
-  /**
-   * Gets the name of the entity type class.
-   *
-   * @return string
-   *   The name of the entity type class.
-   */
-  public function getClass();
-
-  /**
    * Gets the name of the original entity type class.
    *
    * In case the class name was changed with setClass(), this will return
@@ -171,16 +157,6 @@ public function isRenderCacheable();
   public function isPersistentlyCacheable();
 
   /**
-   * Sets the name of the entity type class.
-   *
-   * @param string $class
-   *   The name of the entity type class.
-   *
-   * @return $this
-   */
-  public function setClass($class);
-
-  /**
    * Determines if there is a handler for a given type.
    *
    * @param string $handler_type
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
index 76decd9..ad3f129 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\Core\Entity\Plugin\DataType\Deriver;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -19,7 +21,7 @@ class EntityDeriver implements ContainerDeriverInterface {
   /**
    * List of derivative definitions.
    *
-   * @var array
+   * @var \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]
    */
   protected $derivatives = array();
 
@@ -63,7 +65,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
       return $this->derivatives[$derivative_id];
     }
@@ -76,23 +78,25 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     // Also keep the 'entity' defined as is.
     $this->derivatives[''] = $base_plugin_definition;
     // Add definitions for each entity type and bundle.
     foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
-      $this->derivatives[$entity_type_id] = array(
+      $this->derivatives[$entity_type_id] = new ArrayPluginDefinition([
         'label' => $entity_type->getLabel(),
         'constraints' => $entity_type->getConstraints(),
-      ) + $base_plugin_definition;
+      ]);
+      $this->derivatives[$entity_type_id]->mergeDefaultDefinition($base_plugin_definition);
 
       // Incorporate the bundles as entity:$entity_type:$bundle, if any.
       foreach (entity_get_bundles($entity_type_id) as $bundle => $bundle_info) {
         if ($bundle !== $entity_type_id) {
-          $this->derivatives[$entity_type_id . ':' . $bundle] = array(
+          $this->derivatives[$entity_type_id . ':' . $bundle] = new ArrayPluginDefinition([
             'label' => $bundle_info['label'],
             'constraints' => $this->derivatives[$entity_type_id]['constraints']
-          ) + $base_plugin_definition;
+          ]);
+          $this->derivatives[$entity_type_id . ':' . $bundle]->mergeDefaultDefinition($base_plugin_definition);
         }
       }
     }
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Derivative/SelectionBase.php b/core/lib/Drupal/Core/Entity/Plugin/Derivative/SelectionBase.php
index 0752494..e6217fe 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Derivative/SelectionBase.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Derivative/SelectionBase.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\Core\Entity\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -52,12 +54,16 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
-      $this->derivatives[$entity_type_id] = $base_plugin_definition;
-      $this->derivatives[$entity_type_id]['entity_types'] = array($entity_type_id);
-      $this->derivatives[$entity_type_id]['label'] = t('@entity_type selection', array('@entity_type' => $entity_type->getLabel()));
-      $this->derivatives[$entity_type_id]['base_plugin_label'] = (string) $base_plugin_definition['label'];
+      $this->derivatives[$entity_type_id] = new ArrayPluginDefinition([
+        'entity_types' => [$entity_type_id],
+        'label' => t('@entity_type selection', [
+          '@entity_type' => $entity_type->getLabel(),
+        ]),
+        'base_plugin_label' => (string) $base_plugin_definition->getLabel(),
+      ]);
+      $this->derivatives[$entity_type_id]->mergeDefaultDefinition($base_plugin_definition);
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
   }
diff --git a/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php b/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php
index 80d3025..539e0aa 100644
--- a/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php
+++ b/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Field\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a FieldFormatter annotation object.
diff --git a/core/lib/Drupal/Core/Field/Annotation/FieldWidget.php b/core/lib/Drupal/Core/Field/Annotation/FieldWidget.php
index 5ca352e..1c7acbe 100644
--- a/core/lib/Drupal/Core/Field/Annotation/FieldWidget.php
+++ b/core/lib/Drupal/Core/Field/Annotation/FieldWidget.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Field\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a FieldWidget annotation object.
diff --git a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php
index 000e3fe..2fe8da5 100644
--- a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php
+++ b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php
@@ -13,6 +13,8 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\TypedData\TypedDataManager;
 
 /**
@@ -90,7 +92,7 @@ public function createFieldItem(FieldItemListInterface $items, $index, $values =
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
     if (!isset($definition['list_class'])) {
       $definition['list_class'] = '\Drupal\Core\Field\FieldItemList';
@@ -141,9 +143,9 @@ public function getUiDefinitions() {
     foreach ($definitions as $id => $definition) {
       if (is_subclass_of($definition['class'], '\Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface')) {
         foreach ($definition['class']::getPreconfiguredOptions() as $key => $option) {
-          $definitions['field_ui:' . $id . ':' . $key] = [
+          $definitions['field_ui:' . $id . ':' . $key] = (new ArrayPluginDefinition([
             'label' => $option['label'],
-          ] + $definition;
+          ]))->mergeDefaultDefinition($definition);
 
           if (isset($option['category'])) {
             $definitions['field_ui:' . $id . ':' . $key]['category'] = $option['category'];
diff --git a/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php b/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php
index c4cfeb1..e489e11 100644
--- a/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php
+++ b/core/lib/Drupal/Core/Field/Plugin/DataType/Deriver/FieldItemDeriver.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\Core\Field\Plugin\DataType\Deriver;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -19,7 +21,7 @@ class FieldItemDeriver implements ContainerDeriverInterface {
   /**
    * List of derivative definitions.
    *
-   * @var array
+   * @var \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]
    */
   protected $derivatives = array();
 
@@ -63,7 +65,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     if (!isset($this->derivatives)) {
       $this->getDerivativeDefinitions($base_plugin_definition);
     }
@@ -75,12 +77,14 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     foreach ($this->fieldTypePluginManager->getDefinitions() as $plugin_id => $definition) {
-      $definition['definition_class'] = '\Drupal\Core\Field\TypedData\FieldItemDataDefinition';
-      $definition['list_definition_class'] = '\Drupal\Core\Field\BaseFieldDefinition';
-      $definition['unwrap_for_canonical_representation'] = FALSE;
-      $this->derivatives[$plugin_id] = $definition;
+      $this->derivatives[$plugin_id] = new ArrayPluginDefinition([
+        'definition_class' => '\Drupal\Core\Field\TypedData\FieldItemDataDefinition',
+        'list_definition_class' => '\Drupal\Core\Field\BaseFieldDefinition',
+        'unwrap_for_canonical_representation' => FALSE,
+      ]);
+      $this->derivatives[$plugin_id]->mergeDefaultDefinition($definition);
     }
     return $this->derivatives;
   }
diff --git a/core/lib/Drupal/Core/ImageToolkit/Annotation/ImageToolkit.php b/core/lib/Drupal/Core/ImageToolkit/Annotation/ImageToolkit.php
index a73e5e2..424339d 100644
--- a/core/lib/Drupal/Core/ImageToolkit/Annotation/ImageToolkit.php
+++ b/core/lib/Drupal/Core/ImageToolkit/Annotation/ImageToolkit.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\ImageToolkit\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a Plugin annotation object for the image toolkit plugin.
diff --git a/core/lib/Drupal/Core/ImageToolkit/Annotation/ImageToolkitOperation.php b/core/lib/Drupal/Core/ImageToolkit/Annotation/ImageToolkitOperation.php
index 0e0dbb2..26c7bc2 100644
--- a/core/lib/Drupal/Core/ImageToolkit/Annotation/ImageToolkitOperation.php
+++ b/core/lib/Drupal/Core/ImageToolkit/Annotation/ImageToolkitOperation.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\ImageToolkit\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a Plugin annotation object for the image toolkit operation plugin.
diff --git a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php
index f5d8544..36b0081 100644
--- a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php
+++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitBase.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Image\ImageInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Psr\Log\LoggerInterface;
 
@@ -60,7 +61,7 @@
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin_id for the plugin instance.
-   * @param array $plugin_definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    * @param \Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface $operation_manager
    *   The toolkit operation manager.
@@ -69,7 +70,7 @@
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The config factory.
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImageToolkitOperationManagerInterface $operation_manager, LoggerInterface $logger, ConfigFactoryInterface $config_factory) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, ImageToolkitOperationManagerInterface $operation_manager, LoggerInterface $logger, ConfigFactoryInterface $config_factory) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->operationManager = $operation_manager;
     $this->logger = $logger;
diff --git a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php
index 4fd6bdc..4e2e2a5 100644
--- a/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php
+++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationBase.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Psr\Log\LoggerInterface;
 
@@ -43,14 +44,14 @@
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin_id for the plugin instance.
-   * @param array $plugin_definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    * @param \Drupal\Core\ImageToolkit\ImageToolkitInterface $toolkit
    *   The image toolkit.
    * @param \Psr\Log\LoggerInterface $logger
    *   A logger instance.
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImageToolkitInterface $toolkit, LoggerInterface $logger) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, ImageToolkitInterface $toolkit, LoggerInterface $logger) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->toolkit = $toolkit;
     $this->logger = $logger;
diff --git a/core/lib/Drupal/Core/Menu/ContextualLinkManager.php b/core/lib/Drupal/Core/Menu/ContextualLinkManager.php
index 25421d7..28a4d00 100644
--- a/core/lib/Drupal/Core/Menu/ContextualLinkManager.php
+++ b/core/lib/Drupal/Core/Menu/ContextualLinkManager.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\Core\Plugin\Discovery\YamlDiscovery;
 use Drupal\Core\Plugin\Factory\ContainerFactory;
@@ -128,7 +129,7 @@ protected function getDiscovery() {
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
 
      // If there is no route name, this is a broken definition.
diff --git a/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
index 1062b2b..be86321 100644
--- a/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
+++ b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Menu\MenuLinkManagerInterface;
 use Drupal\Core\Menu\MenuParentFormSelectorInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
diff --git a/core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php b/core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php
index 075e356..c76888e 100644
--- a/core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php
+++ b/core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php
@@ -9,6 +9,8 @@
 
 use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * A menu link plugin for wrapping another menu link, in sensitive situations.
@@ -32,11 +34,11 @@ class InaccessibleMenuLink extends MenuLinkBase {
    */
   public function __construct(MenuLinkInterface $wrapped_link) {
     $this->wrappedLink = $wrapped_link;
-    $plugin_definition = [
+    $plugin_definition = (new ArrayPluginDefinition([
       'route_name' => '<front>',
       'route_parameters' => [],
       'url' => NULL,
-    ] + $this->wrappedLink->getPluginDefinition();
+    ]))->mergeDefaultDefinition($this->wrappedLink->getPluginDefinition());
     parent::__construct([], $this->wrappedLink->getPluginId(), $plugin_definition);
   }
 
diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
index 5b12ac8..3fb211f 100644
--- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php
+++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\Core\Plugin\Discovery\YamlDiscovery;
 use Drupal\Core\Plugin\Factory\ContainerFactory;
@@ -152,7 +153,7 @@ protected function getDiscovery() {
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
      // If there is no route name, this is a broken definition.
     if (empty($definition['route_name'])) {
@@ -177,7 +178,7 @@ public function getDefinitions() {
     $definitions =  parent::getDefinitions();
 
     $count = 0;
-    foreach ($definitions as &$definition) {
+    foreach ($definitions as $definition) {
       if (isset($definition['weight'])) {
         // Add some micro weight.
         $definition['weight'] += ($count++) * 1e-6;
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkDefault.php b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
index c224142..36c4a16 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
@@ -8,6 +8,8 @@
 namespace Drupal\Core\Menu;
 
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -40,12 +42,12 @@ class MenuLinkDefault extends MenuLinkBase implements ContainerFactoryPluginInte
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin_id for the plugin instance.
-   * @param mixed $plugin_definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    * @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $static_override
    *   The static override storage.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, StaticMenuLinkOverridesInterface $static_override) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, StaticMenuLinkOverridesInterface $static_override) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->staticOverride = $static_override;
@@ -104,7 +106,7 @@ public function updateLink(array $new_definition_values, $persist) {
     // Filter the list of updates to only those that are allowed.
     $overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
     // Update the definition.
-    $this->pluginDefinition = $overrides + $this->getPluginDefinition();
+    $this->pluginDefinition = (new ArrayPluginDefinition($overrides))->mergeDefaultDefinition($this->pluginDefinition);
     if ($persist) {
       // Always save the menu name as an override to avoid defaulting to tools.
       $overrides['menu_name'] = $this->pluginDefinition['menu_name'];
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkInterface.php b/core/lib/Drupal/Core/Menu/MenuLinkInterface.php
index da5ca39..5387ed1 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkInterface.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkInterface.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Component\Plugin\DerivativeInspectionInterface;
 use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * Defines an interface for classes providing a type of menu link.
@@ -168,7 +169,7 @@ public function getMetaData();
    *   TRUE to have the link persist the changed values to any additional
    *   storage.
    *
-   * @return array
+   * @return \Drupal\Core\Plugin\Definition\PluginDefinitionInterface
    *   The plugin definition incorporating any allowed changes.
    */
   public function updateLink(array $new_definition_values, $persist);
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkManager.php b/core/lib/Drupal/Core/Menu/MenuLinkManager.php
index b4c7760..41ae885 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkManager.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkManager.php
@@ -12,6 +12,8 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\Core\Plugin\Discovery\YamlDiscovery;
 use Drupal\Core\Plugin\Factory\ContainerFactory;
@@ -128,13 +130,13 @@ public function __construct(MenuTreeStorageInterface $tree_storage, StaticMenuLi
    * additional processing logic, the logic can be added by replacing or
    * extending this method.
    *
-   * @param array $definition
-   *   The definition to be processed and modified by reference.
+   * @param \Drupal\Core\Plugin\Definition\ArrayPluginDefinition $definition
+   *   The definition to be processed and modified.
    * @param $plugin_id
    *   The ID of the plugin this definition is being used for.
    */
-  protected function processDefinition(array &$definition, $plugin_id) {
-    $definition = NestedArray::mergeDeep($this->defaults, $definition);
+  protected function processDefinition(ArrayPluginDefinition $definition, $plugin_id) {
+    $definition->mergeDefaultArrayDefinition($this->defaults);
     // Typecast so NULL, no parent, will be an empty string since the parent ID
     // should be a string.
     $definition['parent'] = (string) $definition['parent'];
@@ -172,10 +174,9 @@ protected function getFactory() {
   public function getDefinitions() {
     // Since this function is called rarely, instantiate the discovery here.
     $definitions = $this->getDiscovery()->getDefinitions();
-
     $this->moduleHandler->alter('menu_links_discovered', $definitions);
 
-    foreach ($definitions as $plugin_id => &$definition) {
+    foreach ($definitions as $plugin_id => $definition) {
       $definition['id'] = $plugin_id;
       $this->processDefinition($definition, $plugin_id);
     }
@@ -201,7 +202,7 @@ public function rebuild() {
     $overrides = $this->overrides->loadMultipleOverrides(array_keys($definitions));
     foreach ($overrides as $id => $changes) {
       if (!empty($definitions[$id])) {
-        $definitions[$id] = $changes + $definitions[$id];
+        $definitions[$id]->mergeOverrideArrayDefinition($changes);
       }
     }
     $this->treeStorage->rebuild($definitions);
@@ -353,7 +354,7 @@ public function loadLinksByRoute($route_name, array $route_parameters = array(),
   /**
    * {@inheritdoc}
    */
-  public function addDefinition($id, array $definition) {
+  public function addDefinition($id, PluginDefinitionInterface $definition) {
     if ($this->treeStorage->load($id) || $id === '') {
       throw new PluginException(SafeMarkup::format('The ID @id already exists as a plugin definition or is not valid', array('@id' => $id)));
     }
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkManagerInterface.php b/core/lib/Drupal/Core/Menu/MenuLinkManagerInterface.php
index 3507a92..0ca2a41 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkManagerInterface.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkManagerInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Menu;
 
 use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * Defines an interface for managing menu links and storing their definitions.
@@ -87,7 +88,7 @@ public function loadLinksByRoute($route_name, array $route_parameters = array(),
    *
    * @param string $id
    *   The plugin ID for the new menu link definition that is being added.
-   * @param array $definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $definition
    *   The values of the link definition.
    *
    * @return \Drupal\Core\Menu\MenuLinkInterface
@@ -96,7 +97,7 @@ public function loadLinksByRoute($route_name, array $route_parameters = array(),
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    *   Thrown when the $id is not valid or is an already existing plugin ID.
    */
-  public function addDefinition($id, array $definition);
+  public function addDefinition($id, PluginDefinitionInterface $definition);
 
   /**
    * Updates the values for a menu link definition in the menu tree storage.
diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
index 659d6be..dc7ba04 100644
--- a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
+++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
@@ -17,6 +17,8 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\Core\Database\SchemaObjectExistsException;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * Provides a menu tree storage using the database.
@@ -66,9 +68,9 @@ class MenuTreeStorage implements MenuTreeStorageInterface {
   /**
    * Stores definitions that have already been loaded for better performance.
    *
-   * An array of plugin definition arrays, keyed by plugin ID.
+   * An array of plugin definitions, keyed by plugin ID.
    *
-   * @var array
+   * @var \Drupal\Core\Plugin\Definition\PluginDefinitionInterface
    */
   protected $definitions = array();
 
@@ -249,7 +251,7 @@ protected function safeExecuteSelect(SelectInterface $query) {
   /**
    * {@inheritdoc}
    */
-  public function save(array $link) {
+  public function save(PluginDefinitionInterface $link) {
     $affected_menus = $this->doSave($link);
     $this->resetDefinitions();
     $cache_tags = Cache::buildTags('config:system.menu', $affected_menus, '.');
@@ -260,7 +262,7 @@ public function save(array $link) {
   /**
    * Saves a link without clearing caches.
    *
-   * @param array $link
+   * @param \Drupal\Core\Plugin\Definition\ArrayPluginDefinition $link
    *   A definition, according to $definitionFields, for a
    *   \Drupal\Core\Menu\MenuLinkInterface plugin.
    *
@@ -276,7 +278,7 @@ public function save(array $link) {
    *   would cause the links children to be moved to greater than the maximum
    *   depth.
    */
-  protected function doSave(array $link) {
+  protected function doSave(ArrayPluginDefinition $link) {
     $original = $this->loadFull($link['id']);
     // @todo Should we just return here if the link values match the original
     //   values completely?
@@ -319,9 +321,9 @@ protected function doSave(array $link) {
   /**
    * Fills in all the fields the database save needs, using the link definition.
    *
-   * @param array $link
+   * @param \Drupal\Core\Plugin\Definition\ArrayPluginDefinition $link
    *   The link definition to be updated.
-   * @param array $original
+   * @param \Drupal\Core\Plugin\Definition\ArrayPluginDefinition|null $original
    *   The link definition before the changes. May be empty if not found.
    *
    * @return array
@@ -330,7 +332,7 @@ protected function doSave(array $link) {
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    *   Thrown when the specific depth exceeds the maximum.
    */
-  protected function preSave(array &$link, array $original) {
+  protected function preSave(ArrayPluginDefinition $link, ArrayPluginDefinition $original = NULL) {
     static $schema_fields, $schema_defaults;
     if (empty($schema_fields)) {
       $schema = static::schemaDefinition();
@@ -359,7 +361,7 @@ protected function preSave(array &$link, array $original) {
         $link[$name] = $default;
       }
     }
-    $fields = array_intersect_key($link, $schema_fields);
+    $fields = array_intersect_key($link->getArrayDefinition(), $schema_fields);
     // Sort the route parameters so that the query string will be the same.
     asort($fields['route_parameters']);
     // Since this will be urlencoded, it's safe to store and match against a
@@ -420,13 +422,13 @@ public function getSubtreeHeight($id) {
   /**
    * Finds the relative depth of this link's deepest child.
    *
-   * @param array $original
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $original
    *   The parent definition used to find the depth.
    *
    * @return int
    *   Returns the relative depth.
    */
-  protected function doFindChildrenRelativeDepth(array $original) {
+  protected function doFindChildrenRelativeDepth(PluginDefinitionInterface $original) {
     $query = $this->connection->select($this->table, $this->options);
     $query->addField($this->table, 'depth');
     $query->condition('menu_name', $original['menu_name']);
@@ -447,12 +449,12 @@ protected function doFindChildrenRelativeDepth(array $original) {
    *
    * @param array $fields
    *   The menu link.
-   * @param array|false $parent
+   * @param \Drupal\Core\Plugin\Definition\ArrayPluginDefinition|null $parent
    *   The parent menu link.
-   * @param array $original
+   * @param \Drupal\Core\Plugin\Definition\ArrayPluginDefinition|null $original
    *   The original menu link.
    */
-  protected function setParents(array &$fields, $parent, array $original) {
+  protected function setParents(array &$fields, ArrayPluginDefinition $parent = NULL, ArrayPluginDefinition $original = NULL) {
     // Directly fill parents for top-level links.
     if (empty($fields['parent'])) {
       $fields['p1'] = $fields['mlid'];
@@ -499,10 +501,10 @@ protected function setParents(array &$fields, $parent, array $original) {
    *
    * @param array $fields
    *   The changed menu link.
-   * @param array $original
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $original
    *   The original menu link.
    */
-  protected function moveChildren($fields, $original) {
+  protected function moveChildren($fields, PluginDefinitionInterface $original) {
     $query = $this->connection->update($this->table, $this->options);
 
     $query->fields(array('menu_name' => $fields['menu_name']));
@@ -544,17 +546,17 @@ protected function moveChildren($fields, $original) {
   /**
    * Loads the parent definition if it exists.
    *
-   * @param array $link
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $link
    *   The link definition to find the parent of.
-   * @param array|false $original
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface|null $original
    *   The original link that might be used to find the parent if the parent
    *   is not set on the $link, or FALSE if the original could not be loaded.
    *
-   * @return array|false
-   *   Returns a definition array, or FALSE if no parent was found.
+   * @return array|null
+   *   Returns a definition array, or NULL if no parent was found.
    */
-  protected function findParent($link, $original) {
-    $parent = FALSE;
+  protected function findParent(PluginDefinitionInterface $link, PluginDefinitionInterface $original = NULL) {
+    $parent = NULL;
 
     // This item is explicitly top-level, skip the rest of the parenting.
     if (isset($link['parent']) && empty($link['parent'])) {
@@ -583,10 +585,10 @@ protected function findParent($link, $original) {
   /**
    * Sets has_children for the link's parent if it has visible children.
    *
-   * @param array $link
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $link
    *   The link to get a parent ID from.
    */
-  protected function updateParentalStatus(array $link) {
+  protected function updateParentalStatus(PluginDefinitionInterface $link) {
     // If parent is empty, there is nothing to update.
     if (!empty($link['parent'])) {
       // Check if at least one visible child exists in the table.
@@ -614,7 +616,7 @@ protected function updateParentalStatus(array $link) {
    * @param bool $intersect
    *   If TRUE, filter out values that are not part of the actual definition.
    *
-   * @return array
+   * @return \Drupal\Core\Plugin\Definition\PluginDefinitionInterface
    *   The prepared link data.
    */
   protected function prepareLink(array $link, $intersect = FALSE) {
@@ -624,8 +626,8 @@ protected function prepareLink(array $link, $intersect = FALSE) {
     if ($intersect) {
       $link = array_intersect_key($link, array_flip($this->definitionFields()));
     }
-    $this->definitions[$link['id']] = $link;
-    return $link;
+    $this->definitions[$link['id']] = new ArrayPluginDefinition($link);
+    return $this->definitions[$link['id']];
   }
 
   /**
@@ -703,7 +705,7 @@ public function load($id) {
       return $this->definitions[$id];
     }
     $loaded = $this->loadMultiple(array($id));
-    return isset($loaded[$id]) ? $loaded[$id] : FALSE;
+    return isset($loaded[$id]) ? $loaded[$id] : NULL;
   }
 
   /**
@@ -712,12 +714,12 @@ public function load($id) {
    * @param string $id
    *   The menu link ID.
    *
-   * @return array
-   *   The loaded menu link definition or an empty array if not be found.
+   * @return \Drupal\Core\Plugin\Definition\PluginDefinitionInterface|null
+   *   The loaded menu link definition or NULL if the link was not found.
    */
   protected function loadFull($id) {
     $loaded = $this->loadFullMultiple(array($id));
-    return isset($loaded[$id]) ? $loaded[$id] : array();
+    return isset($loaded[$id]) ? $loaded[$id] : NULL;
   }
 
   /**
@@ -726,18 +728,19 @@ protected function loadFull($id) {
    * @param array $ids
    *   The IDs to load.
    *
-   * @return array
+   * @return \Drupal\Core\Plugin\Definition\PluginDefinitionInterface[]
    *   The loaded menu link definitions.
    */
   protected function loadFullMultiple(array $ids) {
     $query = $this->connection->select($this->table, $this->options);
     $query->fields($this->table);
     $query->condition('id', $ids, 'IN');
-    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
-    foreach ($loaded as &$link) {
+    $loaded = [];
+    foreach ($this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC) as $id => $link) {
       foreach ($this->serializedFields() as $name) {
         $link[$name] = unserialize($link[$name]);
       }
+      $loaded[$id] = new ArrayPluginDefinition($link);
     }
     return $loaded;
   }
@@ -794,7 +797,7 @@ public function getExpanded($menu_name, array $parents) {
    *   The definition ID.
    * @param array $children
    *   An array of IDs of child links collected by parent ID.
-   * @param array $links
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $links
    *   An array of all definitions keyed by ID.
    */
   protected function saveRecursive($id, &$children, &$links) {
@@ -949,13 +952,13 @@ protected function loadLinks($menu_name, MenuTreeParameters $parameters) {
    *
    * @param array $tree
    *   The menu tree you wish to operate on.
-   * @param array $definitions
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface[] $definitions
    *   An array to accumulate definitions by reference.
    *
    * @return array
    *   Array of route names, with all values being unique.
    */
-  protected function collectRoutesAndDefinitions(array $tree, array &$definitions) {
+  protected function collectRoutesAndDefinitions(array $tree, array $definitions) {
     return array_values($this->doCollectRoutesAndDefinitions($tree, $definitions));
   }
 
@@ -964,13 +967,13 @@ protected function collectRoutesAndDefinitions(array $tree, array &$definitions)
    *
    * @param array $tree
    *   A menu link tree from MenuTreeStorage::doBuildTreeData()
-   * @param array $definitions
-   *   The collected definitions which are populated by reference.
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface[] $definitions
+   *   The collected definitions.
    *
    * @return array
    *   The collected route names.
    */
-  protected function doCollectRoutesAndDefinitions(array $tree, array &$definitions) {
+  protected function doCollectRoutesAndDefinitions(array $tree, array $definitions) {
     $route_names = array();
     foreach (array_keys($tree) as $id) {
       $definitions[$id] = $this->definitions[$id];
@@ -1440,7 +1443,7 @@ protected static function schemaDefinition() {
   /**
    * Find any previously discovered menu links that no longer exist.
    *
-   * @param array $definitions
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface[] $definitions
    *   The new menu link definitions.
    * @return array
    *   A list of menu link IDs that no longer exist.
diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php b/core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php
index c57272b..b15f9f9 100644
--- a/core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php
+++ b/core/lib/Drupal/Core/Menu/MenuTreeStorageInterface.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Menu;
 
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
+
 /**
  * Defines an interface for storing a menu tree containing menu link IDs.
  *
@@ -35,7 +37,7 @@ public function resetDefinitions();
    * on all future calls, or they will be purged. This allows for automatic
    * cleanup e.g. when modules are uninstalled.
    *
-   * @param array $definitions
+   * @param \Drupal\Core\plugin\Definition\PluginDefinitionInterface[] $definitions
    *   The new menu link definitions.
    *
    */
@@ -47,8 +49,8 @@ public function rebuild(array $definitions);
    * @param string $id
    *   The menu link plugin ID.
    *
-   * @return array|FALSE
-   *   The plugin definition, or FALSE if no definition was found for the ID.
+   * @return \Drupal\Core\plugin\Definition\PluginDefinitionInterface|null
+   *   The plugin definition, or NULL if no definition was found for the ID.
    */
   public function load($id);
 
@@ -58,7 +60,7 @@ public function load($id);
    * @param array $ids
    *   An array of plugin IDs.
    *
-   * @return array
+   * @return \Drupal\Core\plugin\Definition\PluginDefinitionInterface[]
    *   An array of plugin definition arrays keyed by plugin ID, which are the
    *   actual definitions after the loadMultiple() including all those plugins
    *   from $ids.
@@ -74,7 +76,7 @@ public function loadMultiple(array $ids);
    * @throws \InvalidArgumentException
    *   Thrown if an invalid property name is specified in $properties.
    *
-   * @return array
+   * @return \Drupal\Core\plugin\Definition\PluginDefinitionInterface[]
    *   An array of menu link definition arrays.
    */
   public function loadByProperties(array $properties);
@@ -89,7 +91,7 @@ public function loadByProperties(array $properties);
    * @param string $menu_name
    *   (optional) Restricts the found links to just those in the named menu.
    *
-   * @return array
+   * @return \Drupal\Core\plugin\Definition\PluginDefinitionInterface[]
    *   An array of menu link definitions keyed by ID and ordered by depth.
    */
   public function loadByRoute($route_name, array $route_parameters = array(), $menu_name = NULL);
@@ -97,7 +99,7 @@ public function loadByRoute($route_name, array $route_parameters = array(), $men
   /**
    * Saves a plugin definition to the storage.
    *
-   * @param array $definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $definition
    *   A definition for a \Drupal\Core\Menu\MenuLinkInterface plugin.
    *
    * @return array
@@ -112,7 +114,7 @@ public function loadByRoute($route_name, array $route_parameters = array(), $men
    *   parent would cause the links children to be moved to greater than the
    *   maximum depth.
    */
-  public function save(array $definition);
+  public function save(PluginDefinitionInterface $definition);
 
   /**
    * Deletes a menu link definition from the storage.
@@ -160,7 +162,7 @@ public function loadTreeData($menu_name, MenuTreeParameters $parameters);
    * @param int $max_relative_depth
    *   The maximum relative depth of the children relative to the passed parent.
    *
-   * @return array
+   * @return \Drupal\Core\plugin\Definition\PluginDefinitionInterface[]
    *   An array of enabled link definitions, keyed by ID.
    */
   public function loadAllChildren($id, $max_relative_depth = NULL);
diff --git a/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php b/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php
index b3cc23b..a8fe01d 100644
--- a/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php
+++ b/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Plugin;
 
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
@@ -28,10 +29,10 @@
    *
    * If the definition lacks a category, it defaults to the providing module.
    *
-   * @param array $definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $definition
    *   The plugin definition.
    */
-  protected function processDefinitionCategory(&$definition) {
+  protected function processDefinitionCategory(PluginDefinitionInterface $definition) {
     // Ensure that every plugin has a category.
     if (empty($definition['category'])) {
       // Default to the human readable module name if the provider is a module;
diff --git a/core/lib/Drupal/Core/Plugin/ContainerFactoryPluginInterface.php b/core/lib/Drupal/Core/Plugin/ContainerFactoryPluginInterface.php
index 4418f4e..3b1a69a 100644
--- a/core/lib/Drupal/Core/Plugin/ContainerFactoryPluginInterface.php
+++ b/core/lib/Drupal/Core/Plugin/ContainerFactoryPluginInterface.php
@@ -23,7 +23,7 @@
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin ID for the plugin instance.
-   * @param mixed $plugin_definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    *
    * @return static
diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
index 7c8799a..7fd015a 100644
--- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
+++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
@@ -9,6 +9,8 @@
 
 use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
 use Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\Component\Plugin\PluginManagerBase;
 use Drupal\Component\Plugin\PluginManagerInterface;
@@ -123,9 +125,9 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt
    *   (optional) The interface each plugin should implement.
    * @param string $plugin_definition_annotation_name
    *   (optional) The name of the annotation that contains the plugin definition.
-   *   Defaults to 'Drupal\Component\Annotation\Plugin'.
+   *   Defaults to 'Drupal\Core\Annotation\Plugin'.
    */
-  public function __construct($subdir, \Traversable $namespaces, ModuleHandlerInterface $module_handler, $plugin_interface = NULL, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin') {
+  public function __construct($subdir, \Traversable $namespaces, ModuleHandlerInterface $module_handler, $plugin_interface = NULL, $plugin_definition_annotation_name = 'Drupal\Core\Annotation\Plugin') {
     $this->subdir = $subdir;
     $this->namespaces = $namespaces;
     $this->pluginDefinitionAnnotationName = $plugin_definition_annotation_name;
@@ -266,10 +268,14 @@ protected function cacheSet($cid, $data, $expire = Cache::PERMANENT, array $tags
    * By default we add defaults for the type to the definition. If a type has
    * additional processing logic they can do that by replacing or extending the
    * method.
+   *
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $definition
+   *   The definition to process.
    */
-  public function processDefinition(&$definition, $plugin_id) {
-    if (!empty($this->defaults) && is_array($this->defaults)) {
-      $definition = NestedArray::mergeDeep($this->defaults, $definition);
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
+    $class = 'Drupal\Core\Plugin\Definition\ArrayPluginDefinition';
+    if (!empty($this->defaults) && is_array($this->defaults) && in_array(get_class($definition), array_merge(class_parents($class), [$class]))) {
+      $definition->mergeDefaultArrayDefinition($this->defaults);
     }
   }
 
@@ -302,18 +308,13 @@ protected function getFactory() {
    */
   protected function findDefinitions() {
     $definitions = $this->getDiscovery()->getDefinitions();
-    foreach ($definitions as $plugin_id => &$definition) {
+    foreach ($definitions as $plugin_id => $definition) {
       $this->processDefinition($definition, $plugin_id);
     }
     $this->alterDefinitions($definitions);
     // If this plugin was provided by a module that does not exist, remove the
     // plugin definition.
     foreach ($definitions as $plugin_id => $plugin_definition) {
-      // If the plugin definition is an object, attempt to convert it to an
-      // array, if that is not possible, skip further processing.
-      if (is_object($plugin_definition) && !($plugin_definition = (array) $plugin_definition)) {
-        continue;
-      }
       if (isset($plugin_definition['provider']) && !in_array($plugin_definition['provider'], array('core', 'component')) && !$this->providerExists($plugin_definition['provider'])) {
         unset($definitions[$plugin_id]);
       }
diff --git a/core/lib/Drupal/Core/Plugin/Definition/ArrayPluginDefinition.php b/core/lib/Drupal/Core/Plugin/Definition/ArrayPluginDefinition.php
new file mode 100644
index 0000000..c8f0447
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/ArrayPluginDefinition.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Definition\ArrayPluginDefinitionBase.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinition as ComponentArrayPluginDefinition;
+
+/**
+ * Provides a plugin definition based on an array.
+ *
+ * @ingroup Plugin
+ */
+class ArrayPluginDefinition extends ComponentArrayPluginDefinition implements PluginCategoryDefinitionInterface, PluginConfigDependenciesDefinitionInterface, PluginLabelDefinitionInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProvider($provider) {
+    $this->arrayDefinition['provider'] = $provider;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvider() {
+    return isset($this->arrayDefinition['provider']) ? $this->arrayDefinition['provider'] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCategory($category) {
+    $this->arrayDefinition['category'] = $category;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCategory() {
+    return isset($this->arrayDefinition['category']) ? $this->arrayDefinition['category'] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfigDependencies(array $dependencies) {
+    $this->arrayDefinition['config_dependencies'] = $dependencies;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfigDependencies() {
+    return isset($this->arrayDefinition['config_dependencies']) ? $this->arrayDefinition['config_dependencies'] : [];
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginCategoryDefinitionInterface.php b/core/lib/Drupal/Core/Plugin/Definition/PluginCategoryDefinitionInterface.php
new file mode 100644
index 0000000..2c0c943
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginCategoryDefinitionInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\PluginCategoryDefinitionInterface.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that includes a category.
+ *
+ * @ingroup Plugin
+ */
+interface PluginCategoryDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the category.
+   *
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationWrapper|string $category
+   *   The category.
+   *
+   * @return $this
+   */
+  public function setCategory($category);
+
+  /**
+   * Gets the category.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslationWrapper|string|null
+   *   The category.
+   */
+  public function getCategory();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginCategoryDefinitionTrait.php b/core/lib/Drupal/Core/Plugin/Definition/PluginCategoryDefinitionTrait.php
new file mode 100644
index 0000000..b4d3cd2
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginCategoryDefinitionTrait.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginCategoryDefinitionTrait.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+/**
+ * Implements \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface.
+ *
+ * @ingroup Plugin
+ */
+trait PluginCategoryDefinitionTrait {
+
+  /**
+   * The plugin category.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationWrapper|string|null
+   */
+  protected $category;
+
+  /**
+   * Implements \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface::setCategory().
+   */
+  public function setCategory($category) {
+    $this->category = $category;
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface::getCategory().
+   */
+  public function getCategory() {
+    return $this->category;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginConfigDependenciesDefinitionInterface.php b/core/lib/Drupal/Core/Plugin/Definition/PluginConfigDependenciesDefinitionInterface.php
new file mode 100644
index 0000000..23f77c7
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginConfigDependenciesDefinitionInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\PluginConfigDependenciesDefinitionInterface.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that includes config dependencies.
+ *
+ * @ingroup Plugin
+ */
+interface PluginConfigDependenciesDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the dependencies.
+   *
+   * @param array[] $dependencies.
+   *   An array of dependencies keyed by the type of dependency. One example:
+   *   @code
+   *   array(
+   *     'module' => array(
+   *       'node',
+   *       'field',
+   *       'image',
+   *     ),
+   *   );
+   *   @endcode
+   *
+   * @return $this
+   */
+  public function setConfigDependencies(array $dependencies);
+
+  /**
+   * Gets the dependencies.
+   *
+   * @return array[]
+   *   See self::setConfigDependencies().
+   */
+  public function getConfigDependencies();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginConfigDependenciesDefinitionTrait.php b/core/lib/Drupal/Core/Plugin/Definition/PluginConfigDependenciesDefinitionTrait.php
new file mode 100644
index 0000000..cb8117f
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginConfigDependenciesDefinitionTrait.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\PluginConfigDependenciesDefinitionTrait.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+/**
+ * Implements \Drupal\Core\Plugin\PluginConfigDependenciesDefinitionInterface.
+ *
+ * @ingroup Plugin
+ */
+trait PluginConfigDependenciesDefinitionTrait {
+
+  /**
+   * The dependencies.
+   *
+   * @var array[]
+   *   An array of dependencies keyed by the type of dependency. One example:
+   *   @code
+   *   array(
+   *     'module' => array(
+   *       'node',
+   *       'field',
+   *       'image',
+   *     ),
+   *   );
+   *   @endcode
+   *
+   * @return $this
+   */
+  protected $configDependencies = [];
+
+  /**
+   * Implements \Drupal\Core\Plugin\PluginConfigDependenciesDefinitionInterface::setConfigDependencies().
+   */
+  public function setConfigDependencies(array $dependencies) {
+    $this->configDependencies = $dependencies;
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Core\Plugin\PluginConfigDependenciesDefinitionInterface::getConfigDependencies().
+   */
+  public function getConfigDependencies() {
+    return $this->configDependencies;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginDefinition.php b/core/lib/Drupal/Core/Plugin/Definition/PluginDefinition.php
new file mode 100644
index 0000000..5b9f2e3
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginDefinition.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\PluginDefinition.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\PluginDefinition as ComponentPluginDefinition;
+
+/**
+ * Provides a plugin definition.
+ *
+ * @ingroup Plugin
+ */
+abstract class PluginDefinition extends ComponentPluginDefinition implements PluginDefinitionInterface {
+
+  /**
+   * The plugin provider.
+   *
+   * @var string|null
+   */
+  protected $provider;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProvider($provider) {
+    $this->provider = $provider;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvider() {
+    return $this->provider;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginDefinitionInterface.php b/core/lib/Drupal/Core/Plugin/Definition/PluginDefinitionInterface.php
new file mode 100644
index 0000000..3cf634b
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginDefinitionInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\PluginDefinitionInterface.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface as ComponentPluginDefinitionInterface;
+
+/**
+ * Defines a plugin definition.
+ *
+ * @ingroup Plugin
+ */
+interface PluginDefinitionInterface extends ComponentPluginDefinitionInterface {
+
+  /**
+   * Sets the plugin provider.
+   *
+   * The provider is the name of the module that provides the plugin, or "core',
+   * or "component".
+   *
+   * @param string $provider
+   *   The provider.
+   *
+   * @return $this
+   */
+  public function setProvider($provider);
+
+  /**
+   * Gets the plugin provider.
+   *
+   * The provider is the name of the module that provides the plugin, or "core',
+   * or "component".
+   *
+   * @return string
+   *   The provider.
+   */
+  public function getProvider();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginLabelDefinitionInterface.php b/core/lib/Drupal/Core/Plugin/Definition/PluginLabelDefinitionInterface.php
new file mode 100644
index 0000000..04ceb95
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginLabelDefinitionInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\PluginLabelDefinitionInterface.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that includes a label.
+ *
+ * @ingroup Plugin
+ */
+interface PluginLabelDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the human-readable plugin label.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationWrapper|string $label
+   *   The label.
+   *
+   * @return $this
+   */
+  public function setLabel($label);
+
+  /**
+   * Gets the human-readable plugin label.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslationWrapper|string|null
+   *   The label or NULL if there is none.
+   */
+  public function getLabel();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php
index b7a2dfd..c135339 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php
@@ -51,9 +51,9 @@ class AnnotatedClassDiscovery extends ComponentAnnotatedClassDiscovery {
    *   If $subdir is not an empty string, it will be appended to each namespace.
    * @param string $plugin_definition_annotation_name
    *   (optional) The name of the annotation that contains the plugin definition.
-   *   Defaults to 'Drupal\Component\Annotation\Plugin'.
+   *   Defaults to 'Drupal\Core\Annotation\Plugin'.
    */
-  function __construct($subdir, \Traversable $root_namespaces, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin') {
+  function __construct($subdir, \Traversable $root_namespaces, $plugin_definition_annotation_name = 'Drupal\Core\Annotation\Plugin') {
     if ($subdir) {
       // Prepend a directory separator to $subdir,
       // if it does not already have one.
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecorator.php b/core/lib/Drupal/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecorator.php
index 6951c77..f705af3 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecorator.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecorator.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Plugin\Discovery;
 
+use Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface;
 use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
 
 class ContainerDerivativeDiscoveryDecorator extends DerivativeDiscoveryDecorator {
@@ -14,10 +15,10 @@ class ContainerDerivativeDiscoveryDecorator extends DerivativeDiscoveryDecorator
   /**
    * {@inheritdoc}
    */
-  protected function getDeriver($base_plugin_id, $base_definition) {
+  protected function getDeriver($base_plugin_id, PluginDeriverDefinitionInterface $base_definition) {
     if (!isset($this->derivers[$base_plugin_id])) {
       $this->derivers[$base_plugin_id] = FALSE;
-      $class = $this->getDeriverClass($base_definition);
+      $class = $base_definition->getDeriverClass();
       if ($class) {
         // If the deriver provides a factory method, pass the container to it.
         if (is_subclass_of($class, '\Drupal\Core\Plugin\Discovery\ContainerDeriverInterface')) {
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
index 4bab897..9dd3b79 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Provides a hook-based plugin discovery class.
@@ -55,7 +56,7 @@ public function getDefinitions() {
       $result = $this->moduleHandler->invoke($module, $this->hook);
       foreach ($result as $plugin_id => $definition) {
         $definition['provider'] = $module;
-        $definitions[$plugin_id] = $definition;
+        $definitions[$plugin_id] = new ArrayPluginDefinition($definition);
       }
     }
     return $definitions;
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/YamlDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/YamlDiscovery.php
index 0d519f4..2f1914f 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/YamlDiscovery.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/YamlDiscovery.php
@@ -11,6 +11,7 @@
 use Drupal\Component\Discovery\YamlDiscovery as ComponentYamlDiscovery;
 use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
 use Drupal\Core\StringTranslation\TranslationWrapper;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Allows YAML files to define plugin definitions.
@@ -97,13 +98,26 @@ public function getDefinitions() {
           }
         }
         // Add ID and provider.
-        $definitions[$id] = $definition + array(
+        $definitions[$id] = $this->createDefinitionFromArray($definition + [
           'provider' => $provider,
           'id' => $id,
-        );
+        ]);
       }
     }
 
     return $definitions;
   }
+
+  /**
+   * Creates a definition from an array.
+   *
+   * @param mixed[] $definition
+   *   The array definition.
+   *
+   * @return \Drupal\Core\Plugin\Definition\PluginDefinitionInterface
+   */
+  protected function createDefinitionFromArray(array $definition) {
+    return new ArrayPluginDefinition($definition);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Plugin/PluginBase.php b/core/lib/Drupal/Core/Plugin/PluginBase.php
index 513d2aa..41128b9 100644
--- a/core/lib/Drupal/Core/Plugin/PluginBase.php
+++ b/core/lib/Drupal/Core/Plugin/PluginBase.php
@@ -20,4 +20,11 @@
   use StringTranslationTrait;
   use DependencySerializationTrait;
 
+  /**
+   * The plugin implementation definition.
+   *
+   * @var \Drupal\Core\Plugin\Definition\PluginDefinitionInterface
+   */
+  protected $pluginDefinition;
+
 }
diff --git a/core/lib/Drupal/Core/Queue/QueueWorkerManager.php b/core/lib/Drupal/Core/Queue/QueueWorkerManager.php
index 6339065..82c5d55 100644
--- a/core/lib/Drupal/Core/Queue/QueueWorkerManager.php
+++ b/core/lib/Drupal/Core/Queue/QueueWorkerManager.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * Defines the queue worker manager.
@@ -42,7 +43,7 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
 
     // Assign a default time if a cron is specified.
diff --git a/core/lib/Drupal/Core/Render/Annotation/RenderElement.php b/core/lib/Drupal/Core/Render/Annotation/RenderElement.php
index fc36b32..ee13dee 100644
--- a/core/lib/Drupal/Core/Render/Annotation/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Annotation/RenderElement.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Render\Annotation;
 
-use Drupal\Component\Annotation\PluginID;
+use Drupal\Core\Annotation\PluginID;
 
 /**
  * Defines a render element plugin annotation object.
diff --git a/core/lib/Drupal/Core/TypedData/Annotation/DataType.php b/core/lib/Drupal/Core/TypedData/Annotation/DataType.php
index 3f277f2..3912831 100644
--- a/core/lib/Drupal/Core/TypedData/Annotation/DataType.php
+++ b/core/lib/Drupal/Core/TypedData/Annotation/DataType.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\TypedData\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a data type annotation object.
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
index 89fa657..614a116 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
@@ -376,7 +376,7 @@ public function getValidationConstraintManager() {
    * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
    *   A data definition.
    *
-   * @return array
+   * @return \Drupal\Core\Plugin\Definition\PluginDefinitionInterface[]
    *   An array of validation constraint definitions, keyed by constraint name.
    *   Each constraint definition can be used for instantiating
    *   \Symfony\Component\Validator\Constraint objects.
diff --git a/core/lib/Drupal/Core/Validation/Annotation/Constraint.php b/core/lib/Drupal/Core/Validation/Annotation/Constraint.php
index 7fce6e8..64239fa 100644
--- a/core/lib/Drupal/Core/Validation/Annotation/Constraint.php
+++ b/core/lib/Drupal/Core/Validation/Annotation/Constraint.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Validation\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a validation constraint annotation object.
diff --git a/core/lib/Drupal/Core/Validation/ConstraintManager.php b/core/lib/Drupal/Core/Validation/ConstraintManager.php
index e8b276f..a79fe1b 100644
--- a/core/lib/Drupal/Core/Validation/ConstraintManager.php
+++ b/core/lib/Drupal/Core/Validation/ConstraintManager.php
@@ -11,6 +11,8 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\StringTranslation\TranslationWrapper;
 
 /**
@@ -90,32 +92,32 @@ public function create($name, $options) {
    * @see ConstraintManager::__construct()
    */
   public function registerDefinitions() {
-    $this->getDiscovery()->setDefinition('Callback', array(
+    $this->getDiscovery()->setDefinition('Callback', new ArrayPluginDefinition([
       'label' => new TranslationWrapper('Callback'),
       'class' => '\Symfony\Component\Validator\Constraints\Callback',
       'type' => FALSE,
-    ));
-    $this->getDiscovery()->setDefinition('Blank', array(
+    ]));
+    $this->getDiscovery()->setDefinition('Blank', new ArrayPluginDefinition([
       'label' => new TranslationWrapper('Blank'),
       'class' => '\Symfony\Component\Validator\Constraints\Blank',
       'type' => FALSE,
-    ));
-    $this->getDiscovery()->setDefinition('NotBlank', array(
+    ]));
+    $this->getDiscovery()->setDefinition('NotBlank', new ArrayPluginDefinition([
       'label' => new TranslationWrapper('Not blank'),
       'class' => '\Symfony\Component\Validator\Constraints\NotBlank',
       'type' => FALSE,
-    ));
-    $this->getDiscovery()->setDefinition('Email', array(
+    ]));
+    $this->getDiscovery()->setDefinition('Email', new ArrayPluginDefinition([
       'label' => new TranslationWrapper('Email'),
       'class' => '\Drupal\Core\Validation\Plugin\Validation\Constraint\EmailConstraint',
       'type' => array('string'),
-    ));
+    ]));
   }
 
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     // Make sure 'type' is set and either an array or FALSE.
     if ($definition['type'] !== FALSE && !is_array($definition['type'])) {
       $definition['type'] = array($definition['type']);
diff --git a/core/modules/aggregator/src/Annotation/AggregatorFetcher.php b/core/modules/aggregator/src/Annotation/AggregatorFetcher.php
index db66347..9f2dc29 100644
--- a/core/modules/aggregator/src/Annotation/AggregatorFetcher.php
+++ b/core/modules/aggregator/src/Annotation/AggregatorFetcher.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\aggregator\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a Plugin annotation object for aggregator fetcher plugins.
diff --git a/core/modules/aggregator/src/Annotation/AggregatorParser.php b/core/modules/aggregator/src/Annotation/AggregatorParser.php
index 45842b6..7f5e3c1 100644
--- a/core/modules/aggregator/src/Annotation/AggregatorParser.php
+++ b/core/modules/aggregator/src/Annotation/AggregatorParser.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\aggregator\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a Plugin annotation object for aggregator parser plugins.
diff --git a/core/modules/aggregator/src/Annotation/AggregatorProcessor.php b/core/modules/aggregator/src/Annotation/AggregatorProcessor.php
index 3aaba5b..4aa5a41 100644
--- a/core/modules/aggregator/src/Annotation/AggregatorProcessor.php
+++ b/core/modules/aggregator/src/Annotation/AggregatorProcessor.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\aggregator\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a Plugin annotation object for aggregator processor plugins.
diff --git a/core/modules/block/src/Plugin/Derivative/ThemeLocalTask.php b/core/modules/block/src/Plugin/Derivative/ThemeLocalTask.php
index c91bc7c..fb42eb4 100644
--- a/core/modules/block/src/Plugin/Derivative/ThemeLocalTask.php
+++ b/core/modules/block/src/Plugin/Derivative/ThemeLocalTask.php
@@ -7,9 +7,11 @@
 
 namespace Drupal\block\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Extension\ThemeHandlerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -58,14 +60,18 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $default_theme = $this->config->get('default');
 
     foreach ($this->themeHandler->listInfo() as $theme_name => $theme) {
       if ($theme->status) {
-        $this->derivatives[$theme_name] = $base_plugin_definition;
-        $this->derivatives[$theme_name]['title'] = $theme->info['name'];
-        $this->derivatives[$theme_name]['route_parameters'] = array('theme' => $theme_name);
+        $this->derivatives[$theme_name] = new ArrayPluginDefinition([
+          'title' => $theme->info['name'],
+          'route_parameters' => [
+            'theme' => $theme_name,
+          ],
+        ]);
+        $this->derivatives[$theme_name]->mergeDefaultDefinition($base_plugin_definition);
       }
       // Default task!
       if ($default_theme == $theme_name) {
diff --git a/core/modules/block_content/src/Plugin/Derivative/BlockContent.php b/core/modules/block_content/src/Plugin/Derivative/BlockContent.php
index 115f2a4..7efda32 100644
--- a/core/modules/block_content/src/Plugin/Derivative/BlockContent.php
+++ b/core/modules/block_content/src/Plugin/Derivative/BlockContent.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\block_content\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -47,15 +49,17 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $block_contents = $this->blockContentStorage->loadMultiple();
     /** @var $block_content \Drupal\block_content\Entity\BlockContent */
     foreach ($block_contents as $block_content) {
-      $this->derivatives[$block_content->uuid()] = $base_plugin_definition;
-      $this->derivatives[$block_content->uuid()]['admin_label'] = $block_content->label();
-      $this->derivatives[$block_content->uuid()]['config_dependencies']['content'] = array(
-        $block_content->getConfigDependencyName()
-      );
+      $this->derivatives[$block_content->uuid()] = new ArrayPluginDefinition([
+        'admin_label' => $block_content->label(),
+        'config_dependencies' => [
+          'content' => [$block_content->getConfigDependencyName()],
+        ],
+      ]);
+      $this->derivatives[$block_content->uuid()]->mergeDefaultDefinition($base_plugin_definition);
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
   }
diff --git a/core/modules/breakpoint/src/BreakpointManager.php b/core/modules/breakpoint/src/BreakpointManager.php
index 66a0830..5bae58f 100644
--- a/core/modules/breakpoint/src/BreakpointManager.php
+++ b/core/modules/breakpoint/src/BreakpointManager.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\Core\Plugin\Discovery\YamlDiscovery;
 use Drupal\Core\Plugin\Factory\ContainerFactory;
@@ -128,7 +129,7 @@ protected function getDiscovery() {
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
     // Allow custom groups and therefore more than one group per extension.
     if (empty($definition['group'])) {
diff --git a/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php b/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php
index 0668c86..bd3fd06 100644
--- a/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php
+++ b/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php
@@ -86,7 +86,7 @@ public function testThemeBreakpoints() {
 
     $breakpoints = \Drupal::service('breakpoint.manager')->getBreakpointsByGroup('breakpoint_theme_test');
     foreach ($expected_breakpoints as $id => $expected_breakpoint) {
-      $this->assertEqual($expected_breakpoint, $breakpoints[$id]->getPluginDefinition());
+      $this->assertEqual($expected_breakpoint, $breakpoints[$id]->getPluginDefinition()->getArrayDefinition());
     }
 
     // Test that the order is as expected.
@@ -141,7 +141,7 @@ public function testCustomBreakpointGroups () {
 
     $breakpoints = \Drupal::service('breakpoint.manager')->getBreakpointsByGroup('breakpoint_theme_test.group2');
     foreach ($expected_breakpoints as $id => $expected_breakpoint) {
-      $this->assertEqual($expected_breakpoint, $breakpoints[$id]->getPluginDefinition());
+      $this->assertEqual($expected_breakpoint, $breakpoints[$id]->getPluginDefinition()->getArrayDefinition());
     }
   }
 
diff --git a/core/modules/ckeditor/src/Annotation/CKEditorPlugin.php b/core/modules/ckeditor/src/Annotation/CKEditorPlugin.php
index 197cf95..fde44e9 100644
--- a/core/modules/ckeditor/src/Annotation/CKEditorPlugin.php
+++ b/core/modules/ckeditor/src/Annotation/CKEditorPlugin.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\ckeditor\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a CKEditorPlugin annotation object.
diff --git a/core/modules/comment/src/Plugin/Menu/LocalTask/UnapprovedComments.php b/core/modules/comment/src/Plugin/Menu/LocalTask/UnapprovedComments.php
index bbbfa8e..eb4fa37 100644
--- a/core/modules/comment/src/Plugin/Menu/LocalTask/UnapprovedComments.php
+++ b/core/modules/comment/src/Plugin/Menu/LocalTask/UnapprovedComments.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Menu\LocalTaskDefault;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -33,12 +34,12 @@ class UnapprovedComments extends LocalTaskDefault implements ContainerFactoryPlu
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin_id for the plugin instance.
-   * @param array $plugin_definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    * @param \Drupal\comment\CommentStorageInterface $comment_storage
    *   The comment storage service.
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, CommentStorageInterface $comment_storage) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, CommentStorageInterface $comment_storage) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->commentStorage = $comment_storage;
   }
diff --git a/core/modules/config/src/Tests/ConfigSchemaTest.php b/core/modules/config/src/Tests/ConfigSchemaTest.php
index a94e26a..6afb8c3 100644
--- a/core/modules/config/src/Tests/ConfigSchemaTest.php
+++ b/core/modules/config/src/Tests/ConfigSchemaTest.php
@@ -48,12 +48,12 @@ function testSchemaMapping() {
     $expected['class'] = '\Drupal\Core\Config\Schema\Undefined';
     $expected['type'] = 'undefined';
     $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
-    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for nonexistent configuration.');
+    $this->assertEqual($definition->getArrayDefinition(), $expected, 'Retrieved the right metadata for nonexistent configuration.');
 
     // Configuration file without schema will return Undefined as well.
     $this->assertIdentical(FALSE, \Drupal::service('config.typed')->hasConfigSchema('config_schema_test.noschema'));
     $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.noschema');
-    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for configuration with no schema.');
+    $this->assertEqual($definition->getArrayDefinition(), $expected, 'Retrieved the right metadata for configuration with no schema.');
 
     // Configuration file with only some schema.
     $this->assertIdentical(TRUE, \Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'));
@@ -67,7 +67,7 @@ function testSchemaMapping() {
     $expected['mapping']['testlist'] = array('label' => 'Test list');
     $expected['type'] = 'config_schema_test.someschema';
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
-    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for configuration with only some schema.');
+    $this->assertEqual($definition->getArrayDefinition(), $expected, 'Retrieved the right metadata for configuration with only some schema.');
 
     // Check type detection on elements with undefined types.
     $config = \Drupal::service('config.typed')->get('config_schema_test.someschema');
@@ -108,7 +108,7 @@ function testSchemaMapping() {
     );
     $expected['type'] = 'system.maintenance';
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
-    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for system.maintenance');
+    $this->assertEqual($definition->getArrayDefinition(), $expected, 'Retrieved the right metadata for system.maintenance');
 
     // Mixed schema with ignore elements.
     $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.ignore');
@@ -138,7 +138,7 @@ function testSchemaMapping() {
     );
     $expected['type'] = 'config_schema_test.ignore';
 
-    $this->assertEqual($definition, $expected);
+    $this->assertEqual($definition->getArrayDefinition(), $expected);
 
     // The ignore elements themselves.
     $definition = \Drupal::service('config.typed')->get('config_schema_test.ignore')->get('irrelevant')->getDataDefinition()->toArray();
@@ -181,7 +181,7 @@ function testSchemaMapping() {
     $expected['mapping']['third_party_settings']['sequence']['type'] = '[%parent.%parent.%type].third_party.[%key]';
     $expected['type'] = 'image.style.*';
 
-    $this->assertEqual($definition, $expected);
+    $this->assertEqual($definition->getArrayDefinition(), $expected);
 
     // More complex, type based on a complex one.
     $definition = \Drupal::service('config.typed')->getDefinition('image.effect.image_scale');
@@ -199,7 +199,7 @@ function testSchemaMapping() {
     $expected['type'] = 'image.effect.image_scale';
 
 
-    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for image.effect.image_scale');
+    $this->assertEqual($definition->getArrayDefinition(), $expected, 'Retrieved the right metadata for image.effect.image_scale');
 
     // Most complex case, get metadata for actual configuration element.
     $effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects');
@@ -238,11 +238,11 @@ function testSchemaMapping() {
     $expected['type'] = 'config_schema_test.someschema.somemodule.*.*';
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
 
-    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.someschema.somemodule.section_one.subsection');
+    $this->assertEqual($definition->getArrayDefinition(), $expected, 'Retrieved the right metadata for config_schema_test.someschema.somemodule.section_one.subsection');
 
     $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.someschema.somemodule.section_two.subsection');
     // The other file should have the same schema.
-    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.someschema.somemodule.section_two.subsection');
+    $this->assertEqual($definition->getArrayDefinition(), $expected, 'Retrieved the right metadata for config_schema_test.someschema.somemodule.section_two.subsection');
   }
 
   /**
@@ -407,7 +407,7 @@ function testSchemaFallback() {
     $expected['mapping']['testdescription']['label'] = 'Description';
     $expected['type'] = 'config_schema_test.wildcard_fallback.*';
 
-    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.wildcard_fallback.something');
+    $this->assertEqual($definition->getArrayDefinition(), $expected, 'Retrieved the right metadata for config_schema_test.wildcard_fallback.something');
 
     $definition2 = \Drupal::service('config.typed')->getDefinition('config_schema_test.wildcard_fallback.something.something');
     // This should be the schema of config_schema_test.wildcard_fallback.* as
diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module
index 3c81f78..2be1588 100644
--- a/core/modules/config_translation/config_translation.module
+++ b/core/modules/config_translation/config_translation.module
@@ -7,6 +7,7 @@
 
 use Drupal\config_translation\Plugin\Derivative\ConfigTranslationLocalTasks;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\field\FieldConfigInterface;
 use Symfony\Component\Routing\Exception\RouteNotFoundException;
@@ -112,14 +113,14 @@ function config_translation_config_translation_info(&$info) {
     foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
       // Make sure entity type has field UI enabled and has a base route.
       if ($entity_type->get('field_ui_base_route')) {
-        $info[$entity_type_id . '_fields'] = array(
+        $info[$entity_type_id . '_fields'] = new ArrayPluginDefinition([
           'base_route_name' => "entity.field_config.{$entity_type_id}_field_edit_form",
           'entity_type' => 'field_config',
           'title' => '!label field',
           'class' => '\Drupal\config_translation\ConfigFieldMapper',
           'base_entity_type' => $entity_type_id,
           'weight' => 10,
-        );
+        ]);
       }
     }
   }
@@ -141,14 +142,14 @@ function config_translation_config_translation_info(&$info) {
 
     // Use the entity type as the plugin ID.
     $base_route_name = "entity.$entity_type_id.edit_form";
-    $info[$entity_type_id] = array(
+    $info[$entity_type_id] = new ArrayPluginDefinition([
       'class' => '\Drupal\config_translation\ConfigEntityMapper',
       'base_route_name' => $base_route_name,
       'title' => '!label !entity_type',
       'names' => array(),
       'entity_type' => $entity_type_id,
       'weight' => 10,
-    );
+    ]);
   }
 }
 
diff --git a/core/modules/config_translation/src/ConfigMapperManager.php b/core/modules/config_translation/src/ConfigMapperManager.php
index 56b2182..944b341 100644
--- a/core/modules/config_translation/src/ConfigMapperManager.php
+++ b/core/modules/config_translation/src/ConfigMapperManager.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\Discovery\InfoHookDecorator;
 use Drupal\Core\Plugin\Discovery\YamlDiscovery;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
@@ -130,7 +131,7 @@ public function getMappers(RouteCollection $collection = NULL) {
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
 
     if (!isset($definition['base_route_name'])) {
@@ -141,7 +142,7 @@ public function processDefinition(&$definition, $plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function buildDataDefinition(array $definition, $value = NULL, $name = NULL, $parent = NULL) {
+  public function buildDataDefinition(PluginDefinitionInterface $definition, $value = NULL, $name = NULL, $parent = NULL) {
     return $this->typedConfigManager->buildDataDefinition($definition, $value, $name, $parent);
   }
 
diff --git a/core/modules/config_translation/src/Controller/ConfigTranslationListController.php b/core/modules/config_translation/src/Controller/ConfigTranslationListController.php
index dd4d38e..cb6eb53 100644
--- a/core/modules/config_translation/src/Controller/ConfigTranslationListController.php
+++ b/core/modules/config_translation/src/Controller/ConfigTranslationListController.php
@@ -58,7 +58,7 @@ public static function create(ContainerInterface $container) {
    */
   public function listing($mapper_id) {
     $mapper_definition = $this->mapperManager->getDefinition($mapper_id);
-    $mapper = $this->mapperManager->createInstance($mapper_id, $mapper_definition);
+    $mapper = $this->mapperManager->createInstance($mapper_id, $mapper_definition->getArrayDefinition());
     if (!$mapper) {
       throw new NotFoundHttpException();
     }
diff --git a/core/modules/config_translation/src/Plugin/Derivative/ConfigTranslationContextualLinks.php b/core/modules/config_translation/src/Plugin/Derivative/ConfigTranslationContextualLinks.php
index 8ecde5e..4a8a5b3 100644
--- a/core/modules/config_translation/src/Plugin/Derivative/ConfigTranslationContextualLinks.php
+++ b/core/modules/config_translation/src/Plugin/Derivative/ConfigTranslationContextualLinks.php
@@ -7,9 +7,11 @@
 
 namespace Drupal\config_translation\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\config_translation\ConfigMapperManagerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -47,7 +49,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     // Create contextual links for all mappers.
     $mappers = $this->mapperManager->getMappers();
     foreach ($mappers as $plugin_id => $mapper) {
@@ -61,11 +63,14 @@ public function getDerivativeDefinitions($base_plugin_definition) {
 
       /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
       $route_name = $mapper->getOverviewRouteName();
-      $this->derivatives[$route_name] = $base_plugin_definition;
-      $this->derivatives[$route_name]['config_translation_plugin_id'] = $plugin_id;
-      $this->derivatives[$route_name]['class'] = '\Drupal\config_translation\Plugin\Menu\ContextualLink\ConfigTranslationContextualLink';
-      $this->derivatives[$route_name]['route_name'] = $route_name;
-      $this->derivatives[$route_name]['group'] = $group_name;
+      $this->derivatives[$route_name] = new ArrayPluginDefinition([
+        'config_translation_plugin_id' => $plugin_id,
+        'class' => '\Drupal\config_translation\Plugin\Menu\ContextualLink\ConfigTranslationContextualLink',
+        'route_name' => $route_name,
+        'group' => $group_name,
+      ]);
+
+      $this->derivatives[$route_name]->mergeDefaultDefinition($base_plugin_definition);
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
   }
diff --git a/core/modules/config_translation/src/Plugin/Derivative/ConfigTranslationLocalTasks.php b/core/modules/config_translation/src/Plugin/Derivative/ConfigTranslationLocalTasks.php
index c36f72d..cdc5565 100644
--- a/core/modules/config_translation/src/Plugin/Derivative/ConfigTranslationLocalTasks.php
+++ b/core/modules/config_translation/src/Plugin/Derivative/ConfigTranslationLocalTasks.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\config_translation\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\config_translation\ConfigMapperManagerInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -57,18 +59,21 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $mappers = $this->mapperManager->getMappers();
     foreach ($mappers as $plugin_id => $mapper) {
       /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */
       $route_name = $mapper->getOverviewRouteName();
       $base_route = $mapper->getBaseRouteName();
       if (!empty($base_route)) {
-        $this->derivatives[$route_name] = $base_plugin_definition;
-        $this->derivatives[$route_name]['config_translation_plugin_id'] = $plugin_id;
-        $this->derivatives[$route_name]['class'] = '\Drupal\config_translation\Plugin\Menu\LocalTask\ConfigTranslationLocalTask';
-        $this->derivatives[$route_name]['route_name'] = $route_name;
-        $this->derivatives[$route_name]['base_route'] = $base_route;
+        $this->derivatives[$route_name] = new ArrayPluginDefinition([
+          'config_translation_plugin_id' => $plugin_id,
+          'class' => '\Drupal\config_translation\Plugin\Menu\LocalTask\ConfigTranslationLocalTask',
+          'route_name' => $route_name,
+          'base_route' => $base_route,
+        ]);
+
+        $this->derivatives[$route_name]->mergeDefaultDefinition($base_plugin_definition);
       }
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
diff --git a/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationContextualLinks.php b/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationContextualLinks.php
index 9acf394..6f3cf8a 100644
--- a/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationContextualLinks.php
+++ b/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationContextualLinks.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\content_translation\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\content_translation\ContentTranslationManagerInterface;
@@ -49,12 +51,14 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     // Create contextual links for translatable entity types.
     foreach ($this->contentTranslationManager->getSupportedEntityTypes() as $entity_type_id => $entity_type) {
-      $this->derivatives[$entity_type_id]['title'] = t('Translate');
-      $this->derivatives[$entity_type_id]['route_name'] = "entity.$entity_type_id.content_translation_overview";
-      $this->derivatives[$entity_type_id]['group'] = $entity_type_id;
+      $this->derivatives[$entity_type_id] = new ArrayPluginDefinition([
+        'title' => t('Translate'),
+        'route_name' => "entity.$entity_type_id.content_translation_overview",
+        'group' => $entity_type_id,
+      ]);
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
   }
diff --git a/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationLocalTasks.php b/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationLocalTasks.php
index 995de59..47d7178 100644
--- a/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationLocalTasks.php
+++ b/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationLocalTasks.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\content_translation\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\content_translation\ContentTranslationManagerInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -57,19 +59,20 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     // Create tabs for all possible entity types.
     foreach ($this->contentTranslationManager->getSupportedEntityTypes() as $entity_type_id => $entity_type) {
       // Find the route name for the translation overview.
       $translation_route_name = "entity.$entity_type_id.content_translation_overview";
 
       $base_route_name = "entity.$entity_type_id.canonical";
-      $this->derivatives[$translation_route_name] = array(
+      $this->derivatives[$translation_route_name] = new ArrayPluginDefinition([
         'entity_type' => $entity_type_id,
         'title' => 'Translate',
         'route_name' => $translation_route_name,
         'base_route' => $base_route_name,
-      ) + $base_plugin_definition;
+      ]);
+      $this->derivatives[$translation_route_name]->mergeDefaultDefinition($base_plugin_definition);
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
   }
diff --git a/core/modules/dblog/dblog.module b/core/modules/dblog/dblog.module
index 01c4d23..fe23e75 100644
--- a/core/modules/dblog/dblog.module
+++ b/core/modules/dblog/dblog.module
@@ -10,6 +10,7 @@
  */
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
@@ -40,12 +41,12 @@ function dblog_help($route_name, RouteMatchInterface $route_match) {
  */
 function dblog_menu_links_discovered_alter(&$links) {
   if (\Drupal::moduleHandler()->moduleExists('search')) {
-    $links['dblog.search'] = array(
+    $links['dblog.search'] = new ArrayPluginDefinition([
       'title' => 'Top search phrases',
       'route_name' => 'dblog.search',
       'description' => 'View most popular search phrases.',
       'parent' => 'system.admin_reports',
-    );
+    ]);
   }
 
   return $links;
diff --git a/core/modules/editor/src/Annotation/Editor.php b/core/modules/editor/src/Annotation/Editor.php
index 58d2450..68bab1b 100644
--- a/core/modules/editor/src/Annotation/Editor.php
+++ b/core/modules/editor/src/Annotation/Editor.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\editor\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines an Editor annotation object.
diff --git a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
index 71df411..f35cf6a 100644
--- a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
+++ b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\field_ui\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -54,21 +56,21 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $this->derivatives = array();
 
     foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
       if ($entity_type->get('field_ui_base_route')) {
-        $this->derivatives["field_storage_config_add_$entity_type_id"] = array(
+        $this->derivatives["field_storage_config_add_$entity_type_id"] = new ArrayPluginDefinition([
           'route_name' => "field_ui.field_storage_config_add_$entity_type_id",
           'title' => $this->t('Add field'),
           'appears_on' => array("entity.$entity_type_id.field_ui_fields"),
-        );
+        ]);
       }
     }
 
-    foreach ($this->derivatives as &$entry) {
-      $entry += $base_plugin_definition;
+    foreach ($this->derivatives as $entry) {
+      $entry->mergeDefaultDefinition($base_plugin_definition);
     }
 
     return $this->derivatives;
diff --git a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php
index 5260513..96814d5 100644
--- a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php
+++ b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\field_ui\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -65,47 +67,47 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $this->derivatives = array();
 
     foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
       if ($entity_type->get('field_ui_base_route')) {
-        $this->derivatives["overview_$entity_type_id"] = array(
+        $this->derivatives["overview_$entity_type_id"] = new ArrayPluginDefinition([
           'route_name' => "entity.$entity_type_id.field_ui_fields",
           'weight' => 1,
           'title' => $this->t('Manage fields'),
           'base_route' => "entity.$entity_type_id.field_ui_fields",
-        );
+        ]);
 
         // 'Manage form display' tab.
-        $this->derivatives["form_display_overview_$entity_type_id"] = array(
+        $this->derivatives["form_display_overview_$entity_type_id"] = new ArrayPluginDefinition([
           'route_name' => "entity.entity_form_display.$entity_type_id.default",
           'weight' => 2,
           'title' => $this->t('Manage form display'),
           'base_route' => "entity.$entity_type_id.field_ui_fields",
-        );
+        ]);
 
         // 'Manage display' tab.
-        $this->derivatives["display_overview_$entity_type_id"] = array(
+        $this->derivatives["display_overview_$entity_type_id"] = new ArrayPluginDefinition([
           'route_name' => "entity.entity_view_display.$entity_type_id.default",
           'weight' => 3,
           'title' => $this->t('Manage display'),
           'base_route' => "entity.$entity_type_id.field_ui_fields",
-        );
+        ]);
 
         // Field edit tab.
-        $this->derivatives["field_edit_$entity_type_id"] = array(
+        $this->derivatives["field_edit_$entity_type_id"] = new ArrayPluginDefinition([
           'route_name' => "entity.field_config.{$entity_type_id}_field_edit_form",
           'title' => $this->t('Edit'),
           'base_route' => "entity.field_config.{$entity_type_id}_field_edit_form",
-        );
+        ]);
 
         // Field settings tab.
-        $this->derivatives["field_storage_$entity_type_id"] = array(
+        $this->derivatives["field_storage_$entity_type_id"] = new ArrayPluginDefinition([
           'route_name' => "entity.field_config.{$entity_type_id}_storage_edit_form",
           'title' => $this->t('Field settings'),
           'base_route' => "entity.field_config.{$entity_type_id}_field_edit_form",
-        );
+        ]);
 
         // View and form modes secondary tabs.
         // The same base $path for the menu item (with a placeholder) can be
@@ -114,23 +116,23 @@ public function getDerivativeDefinitions($base_plugin_definition) {
         // modes available for customisation. So we define menu items for all
         // view modes, and use a route requirement to determine which ones are
         // actually visible for a given bundle.
-        $this->derivatives['field_form_display_default_' . $entity_type_id] = array(
+        $this->derivatives['field_form_display_default_' . $entity_type_id] = new ArrayPluginDefinition([
           'title' => 'Default',
           'route_name' => "entity.entity_form_display.$entity_type_id.default",
           'parent_id' => "field_ui.fields:form_display_overview_$entity_type_id",
           'weight' => -1,
-        );
-        $this->derivatives['field_display_default_' . $entity_type_id] = array(
+        ]);
+        $this->derivatives['field_display_default_' . $entity_type_id] = new ArrayPluginDefinition([
           'title' => 'Default',
           'route_name' => "entity.entity_view_display.$entity_type_id.default",
           'parent_id' => "field_ui.fields:display_overview_$entity_type_id",
           'weight' => -1,
-        );
+        ]);
 
         // One local task for each form mode.
         $weight = 0;
         foreach ($this->entityManager->getFormModes($entity_type_id) as $form_mode => $form_mode_info) {
-          $this->derivatives['field_form_display_' . $form_mode . '_' . $entity_type_id] = array(
+          $this->derivatives['field_form_display_' . $form_mode . '_' . $entity_type_id] = new ArrayPluginDefinition([
             'title' => $form_mode_info['label'],
             'route_name' => "entity.entity_form_display.$entity_type_id.form_mode",
             'route_parameters' => array(
@@ -138,13 +140,13 @@ public function getDerivativeDefinitions($base_plugin_definition) {
             ),
             'parent_id' => "field_ui.fields:form_display_overview_$entity_type_id",
             'weight' => $weight++,
-          );
+          ]);
         }
 
         // One local task for each view mode.
         $weight = 0;
         foreach ($this->entityManager->getViewModes($entity_type_id) as $view_mode => $form_mode_info) {
-          $this->derivatives['field_display_' . $view_mode . '_' . $entity_type_id] = array(
+          $this->derivatives['field_display_' . $view_mode . '_' . $entity_type_id] = new ArrayPluginDefinition([
             'title' => $form_mode_info['label'],
             'route_name' => "entity.entity_view_display.$entity_type_id.view_mode",
             'route_parameters' => array(
@@ -152,13 +154,13 @@ public function getDerivativeDefinitions($base_plugin_definition) {
             ),
             'parent_id' => "field_ui.fields:display_overview_$entity_type_id",
             'weight' => $weight++,
-          );
+          ]);
         }
       }
     }
 
-    foreach ($this->derivatives as &$entry) {
-      $entry += $base_plugin_definition;
+    foreach ($this->derivatives as $entry) {
+      $entry->mergeDefaultDefinition($base_plugin_definition);
     }
 
     return $this->derivatives;
@@ -167,7 +169,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
   /**
    * Alters the base_route definition for field_ui local tasks.
    *
-   * @param array $local_tasks
+   * @param \Drupal\Component\Plugin\Definition\DefinitionInterface $local_tasks
    *   An array of local tasks plugin definitions, keyed by plugin ID.
    */
   public function alterLocalTasks(&$local_tasks) {
diff --git a/core/modules/filter/src/Annotation/Filter.php b/core/modules/filter/src/Annotation/Filter.php
index 9f2239d..d8a3a21 100644
--- a/core/modules/filter/src/Annotation/Filter.php
+++ b/core/modules/filter/src/Annotation/Filter.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\filter\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines an filter annotation object.
diff --git a/core/modules/filter/src/FilterPluginCollection.php b/core/modules/filter/src/FilterPluginCollection.php
index 3e32470..6b3ee5b 100644
--- a/core/modules/filter/src/FilterPluginCollection.php
+++ b/core/modules/filter/src/FilterPluginCollection.php
@@ -72,7 +72,11 @@ protected function initializePlugin($instance_id) {
     //   as contained in the text format configuration. The default
     //   configuration is the filter plugin definition. Configuration should not
     //   be contained in definitions. Move into a FilterBase::init() method.
-    $configuration = $this->manager->getDefinition($instance_id);
+    // @todo Do not use one plugin's definition as another's configuration,
+    //   which must be an array.
+    /** @var \Drupal\Component\Plugin\Definition\ArrayPluginDefinition $definition */
+    $definition = $this->manager->getDefinition($instance_id);
+    $configuration = $definition->getArrayDefinition();
     // Merge the actual configuration into the default configuration.
     if (isset($this->configurations[$instance_id])) {
       $configuration = NestedArray::mergeDeep($configuration, $this->configurations[$instance_id]);
diff --git a/core/modules/image/src/Annotation/ImageEffect.php b/core/modules/image/src/Annotation/ImageEffect.php
index 1f742b3..1402d4a 100644
--- a/core/modules/image/src/Annotation/ImageEffect.php
+++ b/core/modules/image/src/Annotation/ImageEffect.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\image\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines an image effect annotation object.
diff --git a/core/modules/language/src/Annotation/LanguageNegotiation.php b/core/modules/language/src/Annotation/LanguageNegotiation.php
index 92f767b..47ad6b4 100644
--- a/core/modules/language/src/Annotation/LanguageNegotiation.php
+++ b/core/modules/language/src/Annotation/LanguageNegotiation.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\language\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a language negotiation annotation object.
diff --git a/core/modules/language/src/Plugin/Derivative/LanguageBlock.php b/core/modules/language/src/Plugin/Derivative/LanguageBlock.php
index 3c54c25..30b9661 100644
--- a/core/modules/language/src/Plugin/Derivative/LanguageBlock.php
+++ b/core/modules/language/src/Plugin/Derivative/LanguageBlock.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\language\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\language\ConfigurableLanguageManagerInterface;
 
 /**
@@ -18,15 +20,20 @@ class LanguageBlock extends DeriverBase {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $language_manager = \Drupal::languageManager();
 
     if ($language_manager instanceof ConfigurableLanguageManagerInterface) {
       $info = $language_manager->getDefinedLanguageTypesInfo();
       $configurable_types = $language_manager->getLanguageTypes();
       foreach ($configurable_types as $type) {
-        $this->derivatives[$type] = $base_plugin_definition;
-        $this->derivatives[$type]['admin_label'] = t('Language switcher (!type)', array('!type' => $info[$type]['name']));
+        $this->derivatives[$type] = new ArrayPluginDefinition([
+          'admin_label' => t('Language switcher (!type)', [
+            '!type' => $info[$type]['name'],
+          ]),
+        ]);
+
+        $this->derivatives[$type]->mergeDefaultDefinition($base_plugin_definition);
       }
       // If there is just one configurable type then change the title of the
       // block.
diff --git a/core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php b/core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php
index a850b0d..2100967 100644
--- a/core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php
+++ b/core/modules/locale/src/Plugin/QueueWorker/LocaleTranslation.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Queue\QueueInterface;
 use Drupal\Core\Queue\QueueWorkerBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -45,14 +46,14 @@ class LocaleTranslation extends QueueWorkerBase implements ContainerFactoryPlugi
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin_id for the plugin instance.
-   * @param array $plugin_definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
    * @param \Drupal\Core\Queue\QueueInterface $queue
    *   The queue object.
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ModuleHandlerInterface $module_handler, QueueInterface $queue) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, ModuleHandlerInterface $module_handler, QueueInterface $queue) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->moduleHandler = $module_handler;
diff --git a/core/modules/menu_link_content/menu_link_content.module b/core/modules/menu_link_content/menu_link_content.module
index aee8424..20acc86 100644
--- a/core/modules/menu_link_content/menu_link_content.module
+++ b/core/modules/menu_link_content/menu_link_content.module
@@ -59,7 +59,7 @@ function _menu_link_content_update_path_alias($path) {
     ->getStorage('menu_link_content')
     ->loadByProperties(['link.uri' => 'internal:' . $path]);
   foreach ($entities as $menu_link) {
-    $menu_link_manager->updateDefinition($menu_link->getPluginId(), $menu_link->getPluginDefinition(), FALSE);
+    $menu_link_manager->updateDefinition($menu_link->getPluginId(), $menu_link->getPluginDefinition()->getArrayDefinition(), FALSE);
   }
 }
 
diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
index abbc694..7d241be 100644
--- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\link\LinkItemInterface;
 use Drupal\menu_link_content\MenuLinkContentInterface;
 
@@ -173,7 +174,7 @@ public function getPluginDefinition() {
     $definition['discovered'] = 0;
     $definition['parent'] = $this->getParentId();
 
-    return $definition;
+    return new ArrayPluginDefinition($definition);
   }
 
   /**
@@ -211,7 +212,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
       // When the entity is saved via a plugin instance, we should not call
       // the menu tree manager to update the definition a second time.
       if (!$this->insidePlugin) {
-        $menu_link_manager->updateDefinition($this->getPluginId(), $this->getPluginDefinition(), FALSE);
+        $menu_link_manager->updateDefinition($this->getPluginId(), $this->getPluginDefinition()->getArrayDefinition(), FALSE);
       }
     }
     else {
diff --git a/core/modules/menu_link_content/src/MenuLinkContentInterface.php b/core/modules/menu_link_content/src/MenuLinkContentInterface.php
index d2b8254..fefd9f2 100644
--- a/core/modules/menu_link_content/src/MenuLinkContentInterface.php
+++ b/core/modules/menu_link_content/src/MenuLinkContentInterface.php
@@ -95,7 +95,7 @@ public function getWeight();
   /**
    * Builds up the menu link plugin definition for this entity.
    *
-   * @return array
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface
    *   The plugin definition corresponding to this entity.
    *
    * @see \Drupal\Core\Menu\MenuLinkTree::$defaults
diff --git a/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php b/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php
index 76c2d33..e7b039b 100644
--- a/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php
+++ b/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\menu_link_content\Plugin\Deriver;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\Query\QueryFactory;
@@ -74,7 +75,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     // Get all custom menu links which should be rediscovered.
     $entity_ids = $this->queryFactory->get('menu_link_content')
       ->condition('rediscover', TRUE)
diff --git a/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php b/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php
index f120b61..766b480 100644
--- a/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php
@@ -13,6 +13,8 @@
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Menu\MenuLinkBase;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -214,7 +216,7 @@ public function updateLink(array $new_definition_values, $persist) {
     // Filter the list of updates to only those that are allowed.
     $overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
     // Update the definition.
-    $this->pluginDefinition = $overrides + $this->getPluginDefinition();
+    $this->pluginDefinition = (new ArrayPluginDefinition($overrides))->mergeDefaultDefinition($this->pluginDefinition);
     if ($persist) {
       $entity = $this->getEntity();
       foreach ($overrides as $key => $value) {
diff --git a/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
index 520627e..f12725f 100644
--- a/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuCacheTagsTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\menu_ui\Tests;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Url;
 use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
 
diff --git a/core/modules/migrate/src/Annotation/MigrateDestination.php b/core/modules/migrate/src/Annotation/MigrateDestination.php
index 3837370..0493edf 100644
--- a/core/modules/migrate/src/Annotation/MigrateDestination.php
+++ b/core/modules/migrate/src/Annotation/MigrateDestination.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\migrate\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a migration destination plugin annotation object.
diff --git a/core/modules/migrate/src/Annotation/MigrateProcessPlugin.php b/core/modules/migrate/src/Annotation/MigrateProcessPlugin.php
index d01eb7d..42befb5 100644
--- a/core/modules/migrate/src/Annotation/MigrateProcessPlugin.php
+++ b/core/modules/migrate/src/Annotation/MigrateProcessPlugin.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\migrate\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a migration process plugin annotation object.
diff --git a/core/modules/migrate/src/Annotation/MigrateSource.php b/core/modules/migrate/src/Annotation/MigrateSource.php
index 85a69bb..f2fc7ab 100644
--- a/core/modules/migrate/src/Annotation/MigrateSource.php
+++ b/core/modules/migrate/src/Annotation/MigrateSource.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\migrate\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a migration source plugin annotation object.
diff --git a/core/modules/migrate/src/Plugin/Derivative/MigrateEntity.php b/core/modules/migrate/src/Plugin/Derivative/MigrateEntity.php
index 7688472..f881a96 100644
--- a/core/modules/migrate/src/Plugin/Derivative/MigrateEntity.php
+++ b/core/modules/migrate/src/Plugin/Derivative/MigrateEntity.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\migrate\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -48,7 +50,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
       return $this->derivatives[$derivative_id];
     }
@@ -59,17 +61,17 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     foreach ($this->entityDefinitions as $entity_type => $entity_info) {
       $class = is_subclass_of($entity_info->getClass(), 'Drupal\Core\Config\Entity\ConfigEntityInterface') ?
         'Drupal\migrate\Plugin\migrate\destination\EntityConfigBase' :
         'Drupal\migrate\Plugin\migrate\destination\EntityContentBase';
-      $this->derivatives[$entity_type] = array(
+      $this->derivatives[$entity_type] = new ArrayPluginDefinition([
         'id' => "entity:$entity_type",
         'class' => $class,
         'requirements_met' => 1,
         'provider' => $entity_info->getProvider(),
-      );
+      ]);
     }
     return $this->derivatives;
   }
diff --git a/core/modules/migrate/src/Plugin/Derivative/MigrateEntityRevision.php b/core/modules/migrate/src/Plugin/Derivative/MigrateEntityRevision.php
index dd0e418..9d6a49a 100644
--- a/core/modules/migrate/src/Plugin/Derivative/MigrateEntityRevision.php
+++ b/core/modules/migrate/src/Plugin/Derivative/MigrateEntityRevision.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\migrate\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -48,7 +50,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
       return $this->derivatives[$derivative_id];
     }
@@ -59,15 +61,15 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     foreach ($this->entityDefinitions as $entity_type => $entity_info) {
       if ($entity_info->getKey('revision')) {
-        $this->derivatives[$entity_type] = array(
+        $this->derivatives[$entity_type] = new ArrayPluginDefinition([
           'id' => "entity_revision:$entity_type",
           'class' => 'Drupal\migrate\Plugin\migrate\destination\EntityRevision',
           'requirements_met' => 1,
           'provider' => $entity_info->getProvider(),
-        );
+        ]);
       }
     }
     return $this->derivatives;
diff --git a/core/modules/migrate/src/Plugin/MigratePluginManager.php b/core/modules/migrate/src/Plugin/MigratePluginManager.php
index 84f2278..3f3870e 100644
--- a/core/modules/migrate/src/Plugin/MigratePluginManager.php
+++ b/core/modules/migrate/src/Plugin/MigratePluginManager.php
@@ -45,7 +45,7 @@ class MigratePluginManager extends DefaultPluginManager {
    * @param string $annotation
    *   The annotation class name.
    */
-  public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, $annotation = 'Drupal\Component\Annotation\PluginID') {
+  public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, $annotation = 'Drupal\Core\Annotation\PluginID') {
     $plugin_interface = isset($plugin_interface_map[$type]) ? $plugin_interface_map[$type] : NULL;
     parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, $plugin_interface, $annotation);
     $this->alterInfo('migrate_' . $type . '_info');
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/load/LoadEntity.php b/core/modules/migrate_drupal/src/Plugin/migrate/load/LoadEntity.php
index 9edbcd0..eb00944 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/load/LoadEntity.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/load/LoadEntity.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\migrate\Entity\MigrationInterface;
 use Drupal\migrate\Exception\RequirementsException;
@@ -36,7 +37,7 @@ class LoadEntity extends PluginBase implements MigrateLoadInterface {
   /**
    * {@inheritdoc}
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, MigrationInterface $migration) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, MigrationInterface $migration) {
     parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
     $this->migration = $migration;
     $source_plugin = $this->migration->getSourcePlugin();
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/load/d6/LoadTermNode.php b/core/modules/migrate_drupal/src/Plugin/migrate/load/d6/LoadTermNode.php
index 1a7e61a..8cf0c29 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/load/d6/LoadTermNode.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/load/d6/LoadTermNode.php
@@ -8,6 +8,7 @@
 namespace Drupal\migrate_drupal\Plugin\migrate\load\d6;
 
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\migrate\Entity\MigrationInterface;
 use Drupal\migrate\MigrateExecutable;
 use Drupal\migrate\MigrateMessage;
@@ -22,7 +23,7 @@ class LoadTermNode extends LoadEntity {
   /**
    * {@inheritdoc}
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, MigrationInterface $migration) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, MigrationInterface $migration) {
     $configuration['bundle_migration'] = 'd6_taxonomy_vocabulary';
     parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
   }
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/BlockPluginId.php b/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/BlockPluginId.php
index 01854bd..25934b9 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/BlockPluginId.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/BlockPluginId.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\migrate\Entity\MigrationInterface;
 use Drupal\migrate\MigrateExecutableInterface;
 use Drupal\migrate\MigrateSkipRowException;
@@ -37,7 +38,7 @@ class BlockPluginId extends ProcessPluginBase implements ContainerFactoryPluginI
   /**
    * {@inheritdoc}
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, MigratePluginManager $process_plugin_manager) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, MigratePluginManager $process_plugin_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->blockContentStorage = $storage;
     $this->migration = $migration;
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/UserPicture.php b/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/UserPicture.php
index 537eacf..c0d1578 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/UserPicture.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/UserPicture.php
@@ -8,6 +8,7 @@
 namespace Drupal\migrate_drupal\Plugin\migrate\process\d6;
 
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\migrate\Entity\MigrationInterface;
 use Drupal\migrate\MigrateExecutableInterface;
 use Drupal\migrate\Plugin\MigrateProcessInterface;
@@ -34,7 +35,7 @@ class UserPicture extends ProcessPluginBase implements ContainerFactoryPluginInt
   /**
    * {@inheritdoc}
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, MigrationInterface $migration, MigrateProcessInterface $migration_plugin) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, MigrationInterface $migration, MigrateProcessInterface $migration_plugin) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->migration = $migration;
     $this->migrationPlugin = $migration_plugin;
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/UserUpdate7002.php b/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/UserUpdate7002.php
index 6424583..d2b0c73 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/UserUpdate7002.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/process/d6/UserUpdate7002.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\migrate_drupal\Plugin\migrate\process\d6;
 
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\migrate\MigrateExecutableInterface;
 use Drupal\migrate\ProcessPluginBase;
 use Drupal\migrate\Row;
@@ -30,7 +31,7 @@ class UserUpdate7002 extends ProcessPluginBase {
   /**
    * {@inheritdoc}
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     if (!isset(static::$timezones)) {
       static::$timezones = system_time_zones();
diff --git a/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php b/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php
index 88f9794..c0b10c6 100644
--- a/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php
+++ b/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\node\Unit\Plugin\views\field;
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\node\Plugin\views\field\NodeBulkForm;
 use Drupal\Tests\UnitTestCase;
 
@@ -86,7 +87,9 @@ public function testConstructor() {
       ->disableOriginalConstructor()
       ->getMock();
 
-    $definition['title'] = '';
+    $definition = new ArrayPluginDefinition([
+      'title' => '',
+    ]);
     $options = array();
 
     $node_bulk_form = new NodeBulkForm(array(), 'node_bulk_form', $definition, $entity_manager, $language_manager);
diff --git a/core/modules/quickedit/src/Annotation/InPlaceEditor.php b/core/modules/quickedit/src/Annotation/InPlaceEditor.php
index d90a2c8..af1a778 100644
--- a/core/modules/quickedit/src/Annotation/InPlaceEditor.php
+++ b/core/modules/quickedit/src/Annotation/InPlaceEditor.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\quickedit\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines an InPlaceEditor annotation object.
diff --git a/core/modules/rest/src/Annotation/RestResource.php b/core/modules/rest/src/Annotation/RestResource.php
index 28293b6..d58c088 100644
--- a/core/modules/rest/src/Annotation/RestResource.php
+++ b/core/modules/rest/src/Annotation/RestResource.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\rest\Annotation;
 
-use \Drupal\Component\Annotation\Plugin;
+use \Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a REST resource annotation object.
diff --git a/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php b/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
index b3d4088..75f2798 100644
--- a/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
+++ b/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\rest\Plugin\Deriver;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -54,7 +56,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * Implements DerivativeInterface::getDerivativeDefinition().
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     if (!isset($this->derivatives)) {
       $this->getDerivativeDefinitions($base_plugin_definition);
     }
@@ -66,16 +68,16 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * Implements DerivativeInterface::getDerivativeDefinitions().
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     if (!isset($this->derivatives)) {
       // Add in the default plugin configuration and the resource type.
       foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
-        $this->derivatives[$entity_type_id] = array(
+        $this->derivatives[$entity_type_id] = new ArrayPluginDefinition([
           'id' => 'entity:' . $entity_type_id,
           'entity_type' => $entity_type_id,
           'serialization_class' => $entity_type->getClass(),
           'label' => $entity_type->getLabel(),
-        );
+        ]);
 
         $default_uris = array(
           'canonical' => "/entity/$entity_type_id/" . '{' . $entity_type_id . '}',
@@ -93,7 +95,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
           }
         }
 
-        $this->derivatives[$entity_type_id] += $base_plugin_definition;
+        $this->derivatives[$entity_type_id]->mergeDefaultDefinition($base_plugin_definition);
       }
     }
     return $this->derivatives;
diff --git a/core/modules/rest/src/Plugin/views/style/Serializer.php b/core/modules/rest/src/Plugin/views/style/Serializer.php
index 67a8364..071a4d3 100644
--- a/core/modules/rest/src/Plugin/views/style/Serializer.php
+++ b/core/modules/rest/src/Plugin/views/style/Serializer.php
@@ -8,6 +8,7 @@
 namespace Drupal\rest\Plugin\views\style;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\views\Plugin\CacheablePluginInterface;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
@@ -72,7 +73,7 @@ public static function create(ContainerInterface $container, array $configuratio
   public function __construct(array $configuration, $plugin_id, $plugin_definition, SerializerInterface $serializer, array $serializer_formats) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
-    $this->definition = $plugin_definition + $configuration;
+    $this->definition = $plugin_definition->mergeDefaultArrayDefinition($configuration);
     $this->serializer = $serializer;
     $this->formats = $serializer_formats;
   }
diff --git a/core/modules/rest/tests/src/Unit/CollectRoutesTest.php b/core/modules/rest/tests/src/Unit/CollectRoutesTest.php
index c49ff87..35d33df 100644
--- a/core/modules/rest/tests/src/Unit/CollectRoutesTest.php
+++ b/core/modules/rest/tests/src/Unit/CollectRoutesTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\rest\Unit;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\rest\Plugin\views\display\RestExport;
@@ -80,7 +81,7 @@ protected function setUp() {
 
     \Drupal::setContainer($container);
 
-    $this->restExport = RestExport::create($container, array(), "test_routes", array());
+    $this->restExport = RestExport::create($container, array(), "test_routes", new ArrayPluginDefinition());
     $this->restExport->view = $view_executable;
 
     // Initialize a display.
@@ -89,9 +90,13 @@ protected function setUp() {
     // Set the style option.
     $this->restExport->setOption('style', array('type' => 'serializer'));
 
+    $definition = new ArrayPluginDefinition([
+      'id' => 'test',
+      'provider' => 'test',
+    ]);
     $display_manager->expects($this->once())
       ->method('getDefinition')
-      ->will($this->returnValue(array('id' => 'test', 'provider' => 'test')));
+      ->willReturn($definition);
 
     $none = $this->getMockBuilder('\Drupal\views\Plugin\views\access\None')
       ->disableOriginalConstructor()
diff --git a/core/modules/search/src/Annotation/SearchPlugin.php b/core/modules/search/src/Annotation/SearchPlugin.php
index 890b03a..b681e5f 100644
--- a/core/modules/search/src/Annotation/SearchPlugin.php
+++ b/core/modules/search/src/Annotation/SearchPlugin.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\search\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a SearchPlugin type annotation object.
diff --git a/core/modules/search/src/Plugin/Derivative/SearchLocalTask.php b/core/modules/search/src/Plugin/Derivative/SearchLocalTask.php
index f9e07a0..d622405 100644
--- a/core/modules/search/src/Plugin/Derivative/SearchLocalTask.php
+++ b/core/modules/search/src/Plugin/Derivative/SearchLocalTask.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\search\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Drupal\search\SearchPageRepositoryInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -46,18 +48,18 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $this->derivatives = array();
 
     if ($default = $this->searchPageRepository->getDefaultSearchPage()) {
       $active_search_pages = $this->searchPageRepository->getActiveSearchPages();
       foreach ($this->searchPageRepository->sortSearchPages($active_search_pages) as $entity_id => $entity) {
-        $this->derivatives[$entity_id] = array(
+        $this->derivatives[$entity_id] = new ArrayPluginDefinition([
           'title' => $entity->label(),
           'route_name' => 'search.view_' . $entity_id,
           'base_route' => 'search.plugins:' . $default,
           'weight' => $entity->getWeight(),
-        );
+        ]);
       }
     }
     return $this->derivatives;
diff --git a/core/modules/system/src/Plugin/Condition/RequestPath.php b/core/modules/system/src/Plugin/Condition/RequestPath.php
index 64331be..9e01f62 100644
--- a/core/modules/system/src/Plugin/Condition/RequestPath.php
+++ b/core/modules/system/src/Plugin/Condition/RequestPath.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Path\CurrentPathStack;
 use Drupal\Core\Path\PathMatcherInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
@@ -71,10 +72,10 @@ class RequestPath extends ConditionPluginBase implements ContainerFactoryPluginI
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin_id for the plugin instance.
-   * @param array $plugin_definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    */
-  public function __construct(AliasManagerInterface $alias_manager, PathMatcherInterface $path_matcher, RequestStack $request_stack, CurrentPathStack $current_path, array $configuration, $plugin_id, array $plugin_definition) {
+  public function __construct(AliasManagerInterface $alias_manager, PathMatcherInterface $path_matcher, RequestStack $request_stack, CurrentPathStack $current_path, array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->aliasManager = $alias_manager;
     $this->pathMatcher = $path_matcher;
diff --git a/core/modules/system/src/Plugin/Derivative/SystemMenuBlock.php b/core/modules/system/src/Plugin/Derivative/SystemMenuBlock.php
index 7936128..a003f11 100644
--- a/core/modules/system/src/Plugin/Derivative/SystemMenuBlock.php
+++ b/core/modules/system/src/Plugin/Derivative/SystemMenuBlock.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\system\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -48,12 +50,16 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     foreach ($this->menuStorage->loadMultiple() as $menu => $entity) {
-      $this->derivatives[$menu] = $base_plugin_definition;
-      $this->derivatives[$menu]['admin_label'] = $entity->label();
-      $this->derivatives[$menu]['config_dependencies']['config'] = array($entity->getConfigDependencyName());
+      $this->derivatives[$menu] = (new ArrayPluginDefinition([
+        'admin_label' => $entity->label(),
+        'config_dependencies' => [
+          'config' => [$entity->getConfigDependencyName()],
+        ],
+      ]))->mergeDefaultDefinition($base_plugin_definition);
     }
+
     return $this->derivatives;
   }
 
diff --git a/core/modules/system/src/Plugin/Derivative/ThemeLocalTask.php b/core/modules/system/src/Plugin/Derivative/ThemeLocalTask.php
index 9c9de19..3470703 100644
--- a/core/modules/system/src/Plugin/Derivative/ThemeLocalTask.php
+++ b/core/modules/system/src/Plugin/Derivative/ThemeLocalTask.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\system\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Core\Extension\ThemeHandlerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -46,12 +48,17 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     foreach ($this->themeHandler->listInfo() as $theme_name => $theme) {
       if ($theme->status) {
-        $this->derivatives[$theme_name] = $base_plugin_definition;
-        $this->derivatives[$theme_name]['title'] = $theme->info['name'];
-        $this->derivatives[$theme_name]['route_parameters'] = array('theme' => $theme_name);
+        $this->derivatives[$theme_name] = new ArrayPluginDefinition([
+          'title' => $theme->info['name'],
+          'route_parameters' => [
+            'theme' => $theme_name,
+            ],
+        ]);
+
+        $this->derivatives[$theme_name]->mergeDefaultDefinition($base_plugin_definition);
       }
     }
     return $this->derivatives;
diff --git a/core/modules/system/src/Tests/Block/SystemMenuBlockTest.php b/core/modules/system/src/Tests/Block/SystemMenuBlockTest.php
index a28b696..eb3bcc4 100644
--- a/core/modules/system/src/Tests/Block/SystemMenuBlockTest.php
+++ b/core/modules/system/src/Tests/Block/SystemMenuBlockTest.php
@@ -6,6 +6,7 @@
 
 namespace Drupal\system\Tests\Block;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Render\Element;
 use Drupal\simpletest\KernelTestBase;
 use Drupal\system\Tests\Routing\MockRouteProvider;
@@ -136,14 +137,14 @@ protected function setUp() {
     // - 8
     // With link 6 being the only external link.
     $links = array(
-      1 => MenuLinkMock::create(array('id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '', 'weight' => 0)),
-      2 => MenuLinkMock::create(array('id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => '', 'route_parameters' => array('foo' => 'bar'), 'weight' => 1)),
-      3 => MenuLinkMock::create(array('id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'weight' => 2)),
-      4 => MenuLinkMock::create(array('id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3', 'weight' => 3)),
-      5 => MenuLinkMock::create(array('id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '', 'expanded' => TRUE, 'weight' => 4)),
-      6 => MenuLinkMock::create(array('id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => '', 'weight' => 5)),
-      7 => MenuLinkMock::create(array('id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => 'test.example5', 'weight' => 6)),
-      8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '', 'weight' => 7)),
+      1 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '', 'weight' => 0])),
+      2 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => '', 'route_parameters' => array('foo' => 'bar'), 'weight' => 1])),
+      3 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'weight' => 2])),
+      4 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3', 'weight' => 3])),
+      5 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '', 'expanded' => TRUE, 'weight' => 4])),
+      6 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => '', 'weight' => 5])),
+      7 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => 'test.example5', 'weight' => 6])),
+      8 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '', 'weight' => 7])),
     );
     foreach ($links as $instance) {
       $this->menuLinkManager->addDefinition($instance->getPluginId(), $instance->getPluginDefinition());
diff --git a/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php b/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php
index 6078875..7e83d63 100644
--- a/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuLinkDefaultIntegrationTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Menu;
 
 use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\simpletest\KernelTestBase;
 
 /**
diff --git a/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php b/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php
index 011b58f..d0aabd6 100644
--- a/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuLinkTreeTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Menu\MenuLinkTreeElement;
 use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\simpletest\KernelTestBase;
 use Drupal\Tests\Core\Menu\MenuLinkMock;
 
@@ -101,14 +102,14 @@ public function testCreateLinksInMenu() {
      // With link 6 being the only external link.
 
     $links = array(
-      1 => MenuLinkMock::create(array('id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '')),
-      2 => MenuLinkMock::create(array('id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => 'test.example1', 'route_parameters' => array('foo' => 'bar'))),
-      3 => MenuLinkMock::create(array('id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'route_parameters' => array('baz' => 'qux'))),
-      4 => MenuLinkMock::create(array('id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3')),
-      5 => MenuLinkMock::create(array('id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '')),
-      6 => MenuLinkMock::create(array('id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => '')),
-      7 => MenuLinkMock::create(array('id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => '')),
-      8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '')),
+      1 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => ''])),
+      2 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => 'test.example1', 'route_parameters' => ['foo' => 'bar']])),
+      3 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'route_parameters' => ['baz' => 'qux']])),
+      4 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3'])),
+      5 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => ''])),
+      6 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => ''])),
+      7 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => ''])),
+      8 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => ''])),
     );
     foreach ($links as $instance) {
       $this->menuLinkManager->addDefinition($instance->getPluginId(), $instance->getPluginDefinition());
diff --git a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
index 1df4050..c7a1d23 100644
--- a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\Core\Menu\MenuTreeStorage;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\simpletest\KernelTestBase;
 
 /**
@@ -349,7 +350,7 @@ public function testLoadByProperties() {
    * Adds a link with the given ID and supply defaults.
    */
   protected function addMenuLink($id, $parent = '', $route_name = 'test', $route_parameters = array(), $menu_name = 'tools', $extra = array()) {
-    $link = array(
+    $link = (new ArrayPluginDefinition([
       'id' => $id,
       'menu_name' => $menu_name,
       'route_name' => $route_name,
@@ -359,7 +360,7 @@ protected function addMenuLink($id, $parent = '', $route_name = 'test', $route_p
       'parent' => $parent,
       'options' => array(),
       'metadata' => array(),
-    ) + $extra;
+    ]))->mergeDefaultArrayDefinition($extra);
     $this->treeStorage->save($link);
   }
 
diff --git a/core/modules/system/src/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php b/core/modules/system/src/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php
index 6cca8f5..0fd961f 100644
--- a/core/modules/system/src/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php
+++ b/core/modules/system/src/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Plugin\Discovery;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
 
 /**
@@ -19,14 +20,14 @@ class AnnotatedClassDiscoveryTest extends DiscoveryTestBase {
   protected function setUp() {
     parent::setUp();
     $this->expectedDefinitions = array(
-      'apple' => array(
+      'apple' => new ArrayPluginDefinition([
         'id' => 'apple',
         'label' => 'Apple',
         'color' => 'green',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Apple',
         'provider' => 'plugin_test',
-      ),
-      'banana' => array(
+      ]),
+      'banana' => new ArrayPluginDefinition([
         'id' => 'banana',
         'label' => 'Banana',
         'color' => 'yellow',
@@ -35,28 +36,28 @@ protected function setUp() {
         ),
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Banana',
         'provider' => 'plugin_test',
-      ),
-      'cherry' => array(
+      ]),
+      'cherry' => new ArrayPluginDefinition([
         'id' => 'cherry',
         'label' => 'Cherry',
         'color' => 'red',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry',
         'provider' => 'plugin_test',
-      ),
-      'kale' => array(
+      ]),
+      'kale' => new ArrayPluginDefinition([
         'id' => 'kale',
         'label' => 'Kale',
         'color' => 'green',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale',
         'provider' => 'plugin_test',
-      ),
-      'orange' => array(
+      ]),
+      'orange' => new ArrayPluginDefinition([
         'id' => 'orange',
         'label' => 'Orange',
         'color' => 'orange',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Orange',
         'provider' => 'plugin_test',
-      ),
+      ]),
     );
 
     $base_directory = \Drupal::root() . '/core/modules/system/tests/modules/plugin_test/src';
diff --git a/core/modules/system/src/Tests/Plugin/Discovery/CustomAnnotationClassDiscoveryTest.php b/core/modules/system/src/Tests/Plugin/Discovery/CustomAnnotationClassDiscoveryTest.php
index 890329c..6425767 100644
--- a/core/modules/system/src/Tests/Plugin/Discovery/CustomAnnotationClassDiscoveryTest.php
+++ b/core/modules/system/src/Tests/Plugin/Discovery/CustomAnnotationClassDiscoveryTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Plugin\Discovery;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
 
 /**
@@ -21,18 +22,18 @@ protected function setUp() {
     parent::setUp();
 
     $this->expectedDefinitions = array(
-      'example_1' => array(
+      'example_1' => new ArrayPluginDefinition([
         'id' => 'example_1',
         'custom' => 'John',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\custom_annotation\Example1',
         'provider' => 'plugin_test',
-      ),
-      'example_2' => array(
+      ]),
+      'example_2' => new ArrayPluginDefinition([
         'id' => 'example_2',
         'custom' => 'Paul',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\custom_annotation\Example2',
         'provider' => 'plugin_test',
-      ),
+      ]),
     );
 
     $base_directory = \Drupal::root() . '/core/modules/system/tests/modules/plugin_test/src';
diff --git a/core/modules/system/src/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php b/core/modules/system/src/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php
index 39e7caf..eb79036 100644
--- a/core/modules/system/src/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php
+++ b/core/modules/system/src/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Plugin\Discovery;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
 
 /**
@@ -21,26 +22,26 @@ protected function setUp() {
     parent::setUp();
 
     $this->expectedDefinitions = array(
-      'custom_example_1' => array(
+      'custom_example_1' => new ArrayPluginDefinition([
         'id' => 'custom_example_1',
         'custom' => 'Tim',
         'class' => 'Drupal\plugin_test\CustomDirectoryExample1',
         'provider' => 'plugin_test',
-      ),
-      'custom_example_2' => array(
+      ]),
+      'custom_example_2' => new ArrayPluginDefinition([
         'id' => 'custom_example_2',
         'custom' => 'Meghan',
         'class' => 'Drupal\plugin_test\CustomDirectoryExample2',
         'provider' => 'plugin_test',
-      ),
-      'apple' => array(
+      ]),
+      'apple' => new ArrayPluginDefinition([
         'id' => 'apple',
         'label' => 'Apple',
         'color' => 'green',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Apple',
         'provider' => 'plugin_test',
-      ),
-      'banana' => array(
+      ]),
+      'banana' => new ArrayPluginDefinition([
         'id' => 'banana',
         'label' => 'Banana',
         'color' => 'yellow',
@@ -49,28 +50,28 @@ protected function setUp() {
         ),
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Banana',
         'provider' => 'plugin_test',
-      ),
-      'cherry' => array(
+      ]),
+      'cherry' => new ArrayPluginDefinition([
         'id' => 'cherry',
         'label' => 'Cherry',
         'color' => 'red',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry',
         'provider' => 'plugin_test',
-      ),
-      'kale' => array(
+      ]),
+      'kale' => new ArrayPluginDefinition([
         'id' => 'kale',
         'label' => 'Kale',
         'color' => 'green',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale',
         'provider' => 'plugin_test',
-      ),
-      'orange' => array(
+      ]),
+      'orange' => new ArrayPluginDefinition([
         'id' => 'orange',
         'label' => 'Orange',
         'color' => 'orange',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Orange',
         'provider' => 'plugin_test',
-      ),
+      ]),
     );
 
     $base_directory = \Drupal::root() . '/core/modules/system/tests/modules/plugin_test/src';
diff --git a/core/modules/system/src/Tests/Plugin/Discovery/DiscoveryTestBase.php b/core/modules/system/src/Tests/Plugin/Discovery/DiscoveryTestBase.php
index 6313ce1..13654fa 100644
--- a/core/modules/system/src/Tests/Plugin/Discovery/DiscoveryTestBase.php
+++ b/core/modules/system/src/Tests/Plugin/Discovery/DiscoveryTestBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Plugin\Discovery;
 
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\simpletest\KernelTestBase;
 
@@ -44,15 +45,15 @@
    */
   function testDiscoveryInterface() {
     // Ensure that getDefinitions() returns the expected definitions.
-    // For the arrays to be identical (instead of only equal), they must be
-    // sorted equally, which seems unnecessary here.
     // The discovered definitions may contain circular references; use a custom
     // assertion message to prevent var_export() from getting called.
     $this->assertEqual($this->discovery->getDefinitions(), $this->expectedDefinitions, 'Expected definitions found.');
 
     // Ensure that getDefinition() returns the expected definition.
+    // The discovered definitions may contain circular references; use a custom
+    // assertion message to prevent var_export() from getting called.
     foreach ($this->expectedDefinitions as $id => $definition) {
-      $this->assertDefinitionIdentical($this->discovery->getDefinition($id), $definition);
+      $this->assertEqual($this->discovery->getDefinition($id), $definition, 'Expected definition found.');
     }
 
     // Ensure that an empty array is returned if no plugin definitions are found.
@@ -62,29 +63,5 @@ function testDiscoveryInterface() {
     $this->assertIdentical($this->emptyDiscovery->getDefinition('non_existing', FALSE), NULL, 'NULL returned as the definition of a non-existing plugin.');
   }
 
-  /**
-   * Asserts a definition against an expected definition.
-   *
-   * Converts any instances of \Drupal\Core\Annotation\Translation to a string.
-   *
-   * @param array $definition
-   *   The definition to test.
-   * @param array $expected_definition
-   *   The expected definition to test against.
-   *
-   * @return bool
-   *   TRUE if the assertion succeeded, FALSE otherwise.
-   */
-  protected function assertDefinitionIdentical(array $definition, array $expected_definition) {
-    $func = function (&$item){
-      if ($item instanceof TranslationWrapper) {
-        $item = (string) $item;
-      }
-    };
-    array_walk_recursive($definition, $func);
-    array_walk_recursive($expected_definition, $func);
-    return $this->assertIdentical($definition, $expected_definition);
-  }
-
 }
 
diff --git a/core/modules/system/src/Tests/Plugin/Discovery/StaticDiscoveryTest.php b/core/modules/system/src/Tests/Plugin/Discovery/StaticDiscoveryTest.php
index c527e02..d53270d 100644
--- a/core/modules/system/src/Tests/Plugin/Discovery/StaticDiscoveryTest.php
+++ b/core/modules/system/src/Tests/Plugin/Discovery/StaticDiscoveryTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Plugin\Discovery;
 
 use Drupal\Component\Plugin\Discovery\StaticDiscovery;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Tests that plugins using static discovery are correctly discovered.
@@ -19,18 +20,18 @@ class StaticDiscoveryTest extends DiscoveryTestBase {
   protected function setUp() {
     parent::setUp();
     $this->expectedDefinitions = array(
-      'apple' => array(
+      'apple' => new ArrayPluginDefinition([
         'label' => 'Apple',
         'color' => 'green',
-      ),
-      'cherry' => array(
+      ]),
+      'cherry' => new ArrayPluginDefinition([
         'label' => 'Cherry',
         'color' => 'red',
-      ),
-      'orange' => array(
+      ]),
+      'orange' => new ArrayPluginDefinition([
         'label' => 'Orange',
         'color' => 'orange',
-      ),
+      ]),
     );
     // Instead of registering the empty discovery component first and then
     // setting the plugin definitions, we set them first and then delete them
diff --git a/core/modules/system/src/Tests/Plugin/InspectionTest.php b/core/modules/system/src/Tests/Plugin/InspectionTest.php
index 814a174..795f93c 100644
--- a/core/modules/system/src/Tests/Plugin/InspectionTest.php
+++ b/core/modules/system/src/Tests/Plugin/InspectionTest.php
@@ -22,8 +22,8 @@ function testInspection() {
       $plugin = $this->testPluginManager->createInstance($id);
       $expected_definition = $this->testPluginExpectedDefinitions[$id];
       $this->assertIdentical($plugin->getPluginId(), $id);
-      $this->assertIdentical($this->testPluginManager->getDefinition($id), $expected_definition);
-      $this->assertIdentical($plugin->getPluginDefinition(), $expected_definition);
+      $this->assertIdentical($this->testPluginManager->getDefinition($id)->getArrayDefinition(), $expected_definition->getArrayDefinition());
+      $this->assertIdentical($plugin->getPluginDefinition()->getArrayDefinition(), $expected_definition->getArrayDefinition());
     }
     // Skip the 'menu' derived blocks, because MockMenuBlock does not implement
     // PluginInspectionInterface. The others do by extending PluginBase.
@@ -31,16 +31,16 @@ function testInspection() {
       $plugin = $this->mockBlockManager->createInstance($id);
       $expected_definition = $this->mockBlockExpectedDefinitions[$id];
       $this->assertIdentical($plugin->getPluginId(), $id);
-      $this->assertIdentical($this->mockBlockManager->getDefinition($id), $expected_definition);
-      $this->assertIdentical($plugin->getPluginDefinition(), $expected_definition);
+      $this->assertIdentical($this->mockBlockManager->getDefinition($id)->getArrayDefinition(), $expected_definition->getArrayDefinition());
+      $this->assertIdentical($plugin->getPluginDefinition()->getArrayDefinition(), $expected_definition->getArrayDefinition());
     }
     // Test a plugin manager that provides defaults.
     foreach (array('test_block1', 'test_block2') as $id) {
       $plugin = $this->defaultsTestPluginManager->createInstance($id);
       $expected_definition = $this->defaultsTestPluginExpectedDefinitions[$id];
       $this->assertIdentical($plugin->getPluginId(), $id);
-      $this->assertIdentical($this->defaultsTestPluginManager->getDefinition($id), $expected_definition);
-      $this->assertIdentical($plugin->getPluginDefinition(), $expected_definition);
+      $this->assertIdentical($this->defaultsTestPluginManager->getDefinition($id)->getArrayDefinition(), $expected_definition->getArrayDefinition());
+      $this->assertIdentical($plugin->getPluginDefinition()->getArrayDefinition(), $expected_definition->getArrayDefinition());
     }
   }
 
diff --git a/core/modules/system/src/Tests/Plugin/PluginTestBase.php b/core/modules/system/src/Tests/Plugin/PluginTestBase.php
index b4d6c1d..94b7763 100644
--- a/core/modules/system/src/Tests/Plugin/PluginTestBase.php
+++ b/core/modules/system/src/Tests/Plugin/PluginTestBase.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Plugin;
 
 use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\simpletest\KernelTestBase;
 use Drupal\plugin_test\Plugin\TestPluginManager;
 use Drupal\plugin_test\Plugin\MockBlockManager;
@@ -56,80 +57,87 @@ protected function setUp() {
     // @see TestPluginManager::_construct().
     // @see MockBlockManager::_construct().
     $this->testPluginExpectedDefinitions = array(
-      'user_login' => array(
+      'user_login' => new ArrayPluginDefinition([
         'label' => 'User login',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock',
-      ),
+      ]),
     );
     $this->mockBlockExpectedDefinitions = array(
-      'user_login' => array(
+      'user_login' => new ArrayPluginDefinition([
         'label' => 'User login',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock',
-      ),
-      'menu:main_menu' => array(
+      ]),
+      'menu:main_menu' => new ArrayPluginDefinition([
         'label' => 'Main menu',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
-      ),
-      'menu:navigation' => array(
+      ]),
+      'menu:navigation' => new ArrayPluginDefinition([
         'label' => 'Navigation',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
-      ),
-      'menu:foo' => array(
+      ]),
+      'menu:foo' => new ArrayPluginDefinition([
         'label' => 'Base label',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
         'setting' => 'default',
-      ),
-      'layout' => array(
+      ]),
+      'layout' => new ArrayPluginDefinition([
         'label' => 'Layout',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
-      ),
-      'layout:foo' => array(
+      ]),
+      'layout:foo' => new ArrayPluginDefinition([
         'label' => 'Layout Foo',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
-      ),
-      'user_name' => array(
+      ]),
+      'user_name' => new ArrayPluginDefinition([
         'label' => 'User name',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
         'context' => array(
           'user' => new ContextDefinition('entity:user', 'User'),
         ),
-      ),
-      'user_name_optional' => array(
+      ]),
+      'user_name_optional' => new ArrayPluginDefinition([
         'label' => 'User name optional',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
         'context' => array(
           'user' => new ContextDefinition('entity:user', 'User', FALSE),
         ),
-      ),
-      'string_context' => array(
+      ]),
+      'string_context' => new ArrayPluginDefinition([
         'label' => 'String typed data',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock',
-      ),
-      'complex_context' => array(
+      ]),
+      'complex_context' => new ArrayPluginDefinition([
         'label' => 'Complex context',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock',
         'context' => array(
           'user' => new ContextDefinition('entity:user', 'User'),
           'node' => new ContextDefinition('entity:node', 'Node'),
         ),
-      ),
+      ]),
     );
     $this->defaultsTestPluginExpectedDefinitions = array(
-      'test_block1' => array(
+      'test_block1' => new ArrayPluginDefinition([
         'metadata' => array(
           'default' => TRUE,
           'custom' => TRUE,
         ),
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockTestBlock',
-      ),
-      'test_block2' => array(
+      ]),
+      'test_block2' => new ArrayPluginDefinition([
         'metadata' => array(
           'default' => FALSE,
           'custom' => TRUE,
         ),
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockTestBlock',
-      ),
+      ]),
     );
+
+    $plugin_definition_collections = [$this->testPluginExpectedDefinitions, $this->mockBlockExpectedDefinitions, $this->defaultsTestPluginExpectedDefinitions];
+    foreach ($plugin_definition_collections as $plugin_definition_collection) {
+      foreach ($plugin_definition_collection as $plugin_id => $plugin_definition) {
+        $plugin_definition->setId($plugin_id);
+      }
+    }
   }
 
 }
diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Derivative/EntityTestLocalTasks.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Derivative/EntityTestLocalTasks.php
index e4f8e49..a543d84 100644
--- a/core/modules/system/tests/modules/entity_test/src/Plugin/Derivative/EntityTestLocalTasks.php
+++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Derivative/EntityTestLocalTasks.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\entity_test\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Defines the local tasks for all the entity_test entities.
@@ -17,20 +19,22 @@ class EntityTestLocalTasks extends DeriverBase {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $this->derivatives = array();
     $types = entity_test_entity_types(ENTITY_TEST_TYPES_ROUTING);
 
     foreach($types as $entity_type) {
-      $this->derivatives[$entity_type . '.canonical'] = array();
-      $this->derivatives[$entity_type . '.canonical']['base_route'] = "entity.$entity_type.canonical";
-      $this->derivatives[$entity_type . '.canonical']['route_name'] = "entity.$entity_type.canonical";
-      $this->derivatives[$entity_type . '.canonical']['title'] = 'View';
-
-      $this->derivatives[$entity_type . '.edit'] = array();
-      $this->derivatives[$entity_type . '.edit']['base_route'] = "entity.$entity_type.canonical";
-      $this->derivatives[$entity_type . '.edit']['route_name'] = "entity.$entity_type.edit_form";
-      $this->derivatives[$entity_type . '.edit']['title'] = 'Edit';
+      $this->derivatives[$entity_type . '.canonical'] = new ArrayPluginDefinition([
+        'base_route' => "entity.$entity_type.canonical",
+        'route_name' => "entity.$entity_type.canonical",
+        'title' => 'View',
+      ]);
+
+      $this->derivatives[$entity_type . '.edit'] = new ArrayPluginDefinition([
+        'base_route' => "entity.$entity_type.canonical",
+        'route_name' => "entity.$entity_type.edit_form",
+        'title' => 'Edit',
+      ]);
     }
 
     return parent::getDerivativeDefinitions($base_plugin_definition);
diff --git a/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/TestToolkit.php b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/TestToolkit.php
index 7513ac2..7a6b1f3 100644
--- a/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/TestToolkit.php
+++ b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/TestToolkit.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\ImageToolkit\ImageToolkitBase;
 use Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\State\StateInterface;
 use Psr\Log\LoggerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -61,7 +62,7 @@ class TestToolkit extends ImageToolkitBase {
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin_id for the plugin instance.
-   * @param array $plugin_definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    * @param \Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface $operation_manager
    *   The toolkit operation manager.
@@ -72,7 +73,7 @@ class TestToolkit extends ImageToolkitBase {
    * @param \Drupal\Core\State\StateInterface $state
    *   The state key value store.
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImageToolkitOperationManagerInterface $operation_manager, LoggerInterface $logger, ConfigFactoryInterface $config_factory, StateInterface $state) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, ImageToolkitOperationManagerInterface $operation_manager, LoggerInterface $logger, ConfigFactoryInterface $config_factory, StateInterface $state) {
     parent::__construct($configuration, $plugin_id, $plugin_definition, $operation_manager, $logger, $config_factory);
     $this->state = $state;
   }
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module
index 429b357..d9cce21 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.module
+++ b/core/modules/system/tests/modules/menu_test/menu_test.module
@@ -4,6 +4,7 @@
  * @file
  * Module that implements various hooks for menu tests.
  */
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 use Drupal\Core\Url;
 
@@ -18,12 +19,12 @@ function menu_test_menu_links_discovered_alter(&$links) {
   $links['menu_test.context']['title'] = \Drupal::config('menu_test.menu_item')->get('title');
 
   // Adds a custom menu link.
-  $links['menu_test.custom'] = array(
+  $links['menu_test.custom'] = new ArrayPluginDefinition([
     'title' => 'Custom link',
     'route_name' => 'menu_test.custom',
     'description' => 'Custom link used to check the integrity of manually added menu links.',
     'parent' => 'menu_test',
-  );
+  ]);
 }
 
 /**
diff --git a/core/modules/system/tests/modules/menu_test/src/Plugin/Derivative/LocalTaskTest.php b/core/modules/system/tests/modules/menu_test/src/Plugin/Derivative/LocalTaskTest.php
index 807311a..509efc9 100644
--- a/core/modules/system/tests/modules/menu_test/src/Plugin/Derivative/LocalTaskTest.php
+++ b/core/modules/system/tests/modules/menu_test/src/Plugin/Derivative/LocalTaskTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\menu_test\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 
 class LocalTaskTest extends DeriverBase {
@@ -14,10 +15,10 @@ class LocalTaskTest extends DeriverBase {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $weight = $base_plugin_definition['weight'];
     foreach (array('derive1' => 'Derive 1', 'derive2' => 'Derive 2') as $key => $title) {
-      $this->derivatives[$key] = $base_plugin_definition;
+      $this->derivatives[$key] = clone $base_plugin_definition;
       $this->derivatives[$key]['title'] = $title;
       $this->derivatives[$key]['route_parameters'] = array('placeholder' => $key);
       $this->derivatives[$key]['weight'] = $weight++; // ensure weights for testing.
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/Annotation/PluginExample.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/Annotation/PluginExample.php
index 155e25d..c09f769 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/Annotation/PluginExample.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/Annotation/PluginExample.php
@@ -8,6 +8,7 @@
 namespace Drupal\plugin_test\Plugin\Annotation;
 
 use Drupal\Component\Annotation\AnnotationBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Defines a custom Plugin annotation.
@@ -34,12 +35,12 @@ class PluginExample extends AnnotationBase {
    * {@inheritdoc}
    */
   public function get() {
-    return array(
+    return new ArrayPluginDefinition([
       'id' => $this->id,
       'custom' => $this->custom,
       'class' => $this->class,
       'provider' => $this->provider,
-    );
+    ]);
   }
 
 }
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/DefaultsTestPluginManager.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/DefaultsTestPluginManager.php
index 742f84c..a7a02e8 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/DefaultsTestPluginManager.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/DefaultsTestPluginManager.php
@@ -11,6 +11,7 @@
 use Drupal\Component\Plugin\Factory\DefaultFactory;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Defines a plugin manager used by Plugin API unit tests.
@@ -40,20 +41,20 @@ public function __construct(ModuleHandlerInterface $module_handler) {
     );
 
     // Add a plugin with a custom value.
-    $this->discovery->setDefinition('test_block1', array(
+    $this->discovery->setDefinition('test_block1', new ArrayPluginDefinition([
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockTestBlock',
       'metadata' => array(
         'custom' => TRUE,
       ),
-    ));
+    ]));
     // Add a plugin that overrides the default value.
-    $this->discovery->setDefinition('test_block2', array(
+    $this->discovery->setDefinition('test_block2', new ArrayPluginDefinition([
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockTestBlock',
       'metadata' => array(
         'custom' => TRUE,
         'default' => FALSE,
       ),
-    ));
+    ]));
   }
 
 }
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
index e435c0a..104357c 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
@@ -12,6 +12,7 @@
 use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
 use Drupal\Component\Plugin\Factory\ReflectionFactory;
 use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Defines a plugin manager used by Plugin API derivative unit tests.
@@ -39,10 +40,10 @@ public function __construct() {
     // plugins to the system.
 
     // A simple plugin: the user login block.
-    $this->discovery->setDefinition('user_login', array(
+    $this->discovery->setDefinition('user_login', new ArrayPluginDefinition([
       'label' => t('User login'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock',
-    ));
+    ]));
 
     // A plugin that requires derivatives: the menu block plugin. We do not want
     // a generic "Menu" block showing up in the Block administration UI.
@@ -50,15 +51,15 @@ public function __construct() {
     // system and each one's title is user configurable. The
     // MockMenuBlockDeriver class ensures that only derivatives, and not the
     // base plugin, are available to the system.
-    $this->discovery->setDefinition('menu', array(
+    $this->discovery->setDefinition('menu', new ArrayPluginDefinition([
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
       'deriver' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlockDeriver',
-    ));
+    ]));
     // A plugin defining itself as a derivative.
-    $this->discovery->setDefinition('menu:foo', array(
+    $this->discovery->setDefinition('menu:foo', new ArrayPluginDefinition([
       'label' => t('Base label'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
-    ));
+    ]));
 
     // A block plugin that can optionally be derived: the layout block plugin.
     // A layout is a special kind of block into which other blocks can be
@@ -66,46 +67,46 @@ public function __construct() {
     // administration UI as well as additional user-created custom layouts. The
     // MockLayoutBlockDeriver class ensures that both the base plugin and the
     // derivatives are available to the system.
-    $this->discovery->setDefinition('layout', array(
+    $this->discovery->setDefinition('layout', new ArrayPluginDefinition([
       'label' => t('Layout'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
       'deriver' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlockDeriver',
-    ));
+    ]));
 
     // A block plugin that requires context to function. This block requires a
     // user object in order to return the user name from the getTitle() method.
-    $this->discovery->setDefinition('user_name', array(
+    $this->discovery->setDefinition('user_name', new ArrayPluginDefinition([
       'label' => t('User name'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
       'context' => array(
         'user' => new ContextDefinition('entity:user', t('User')),
       ),
-    ));
+    ]));
 
     // An optional context version of the previous block plugin.
-    $this->discovery->setDefinition('user_name_optional', array(
+    $this->discovery->setDefinition('user_name_optional', new ArrayPluginDefinition([
       'label' => t('User name optional'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
       'context' => array(
         'user' => new ContextDefinition('entity:user', t('User'), FALSE),
       ),
-    ));
+    ]));
 
     // A block plugin that requires a typed data string context to function.
-    $this->discovery->setDefinition('string_context', array(
+    $this->discovery->setDefinition('string_context', new ArrayPluginDefinition([
       'label' => t('String typed data'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock',
-    ));
+    ]));
 
     // A complex context plugin that requires both a user and node for context.
-    $this->discovery->setDefinition('complex_context', array(
+    $this->discovery->setDefinition('complex_context', new ArrayPluginDefinition([
       'label' => t('Complex context'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock',
       'context' => array(
         'user' => new ContextDefinition('entity:user', t('User')),
         'node' => new ContextDefinition('entity:node', t('Node')),
       ),
-    ));
+    ]));
 
     // In addition to finding all of the plugins available for a type, a plugin
     // type must also be able to create instances of that plugin. For example, a
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/TestPluginManager.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/TestPluginManager.php
index 330ef4e..22d5d00 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/TestPluginManager.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/TestPluginManager.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\PluginManagerBase;
 use Drupal\Component\Plugin\Discovery\StaticDiscovery;
 use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Defines a plugin manager used by Plugin API unit tests.
@@ -24,10 +25,10 @@ public function __construct() {
     $this->discovery = new StaticDiscovery();
 
     // A simple plugin: a mock user login block.
-    $this->discovery->setDefinition('user_login', array(
+    $this->discovery->setDefinition('user_login', new ArrayPluginDefinition([
       'label' => 'User login',
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock',
-    ));
+    ]));
 
     // In addition to finding all of the plugins available for a type, a plugin
     // type must also be able to create instances of that plugin. For example, a
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockLayoutBlockDeriver.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockLayoutBlockDeriver.php
index 4eb087a..ce2b12f 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockLayoutBlockDeriver.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockLayoutBlockDeriver.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\plugin_test\Plugin\plugin_test\mock_block;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Mock implementation of DeriverInterface for the mock layout block plugin.
@@ -19,7 +21,7 @@ class MockLayoutBlockDeriver implements DeriverInterface {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     $derivatives = $this->getDerivativeDefinitions($base_plugin_definition);
     if (isset($derivatives[$derivative_id])) {
       return $derivatives[$derivative_id];
@@ -29,7 +31,7 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     // This isn't strictly necessary, but it helps reduce clutter in
     // DerivativePluginTest::testDerivativeDecorator()'s $expected variable.
     // Since derivative definitions don't need further deriving, we remove this
@@ -40,14 +42,14 @@ public function getDerivativeDefinitions($base_plugin_definition) {
       // Adding a NULL key signifies that the base plugin may also be used in
       // addition to the derivatives. In this case, we allow the administrator
       // to add a generic layout block to the page.
-      NULL => $base_plugin_definition,
+      NULL => clone $base_plugin_definition,
 
       // We also allow them to add a customized one. Here, we just mock the
       // customized one, but in a real implementation, this would be fetched
       // from some \Drupal::config() object.
-      'foo' => array(
+      'foo' => (new ArrayPluginDefinition([
         'label' => t('Layout Foo'),
-      ) + $base_plugin_definition,
+      ]))->mergeDefaultDefinition($base_plugin_definition),
     );
 
     return $derivatives;
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockMenuBlockDeriver.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockMenuBlockDeriver.php
index f4832e0..a45f548 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockMenuBlockDeriver.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/mock_block/MockMenuBlockDeriver.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\plugin_test\Plugin\plugin_test\mock_block;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 
 /**
  * Mock implementation of DeriverInterface for the mock menu block plugin.
@@ -19,7 +21,7 @@ class MockMenuBlockDeriver implements DeriverInterface {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     $derivatives = $this->getDerivativeDefinitions($base_plugin_definition);
     if (isset($derivatives[$derivative_id])) {
       return $derivatives[$derivative_id];
@@ -29,7 +31,7 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     // This isn't strictly necessary, but it helps reduce clutter in
     // DerivativePluginTest::testDerivativeDecorator()'s $expected variable.
     // Since derivative definitions don't need further deriving, we remove this
@@ -40,18 +42,18 @@ public function getDerivativeDefinitions($base_plugin_definition) {
     // exist in a typical Drupal site. In a real implementation, we would query
     // Drupal's configuration to find out which menus actually exist.
     $derivatives = array(
-      'main_menu' => array(
+      'main_menu' => (new ArrayPluginDefinition([
         'label' => t('Main menu'),
-      ) + $base_plugin_definition,
-      'navigation' => array(
+      ]))->mergeDefaultDefinition($base_plugin_definition),
+      'navigation' => (new ArrayPluginDefinition([
         'label' => t('Navigation'),
-      ) + $base_plugin_definition,
-      'foo' => array(
+      ]))->mergeDefaultDefinition($base_plugin_definition),
+      'foo' => (new ArrayPluginDefinition([
         // Instead of the derivative label, the specific label will be used.
         'label' => t('Derivative label'),
         // This setting will be merged in.
          'setting' => 'default'
-      ) + $base_plugin_definition,
+      ]))->mergeDefaultDefinition($base_plugin_definition),
     );
 
     return $derivatives;
diff --git a/core/modules/system/tests/src/Unit/Menu/MenuLinkTreeTest.php b/core/modules/system/tests/src/Unit/Menu/MenuLinkTreeTest.php
index 5e7f2a2..e8565ee 100644
--- a/core/modules/system/tests/src/Unit/Menu/MenuLinkTreeTest.php
+++ b/core/modules/system/tests/src/Unit/Menu/MenuLinkTreeTest.php
@@ -12,6 +12,7 @@
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Menu\MenuLinkTree;
 use Drupal\Core\Menu\MenuLinkTreeElement;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Url;
 use Drupal\Tests\Core\Menu\MenuLinkMock;
@@ -158,12 +159,12 @@ public function providerTestBuildCacheability() {
     $cache_defaults = ['cache_max_age' => Cache::PERMANENT, 'cache_tags' => []];
     $links_scenarios = [
       [
-        MenuLinkMock::create(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'Example 1']),
-        MenuLinkMock::create(['id' => 'test.example2', 'route_name' => 'example1', 'title' => 'Example 2', 'metadata' => ['cache_contexts' => ['llama']] + $cache_defaults]),
+        MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'Example 1'])),
+        MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example2', 'route_name' => 'example1', 'title' => 'Example 2', 'metadata' => ['cache_contexts' => ['llama']] + $cache_defaults])),
       ],
       [
-        MenuLinkMock::create(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'Example 1', 'metadata' => ['cache_contexts' => ['foo']] + $cache_defaults]),
-        MenuLinkMock::create(['id' => 'test.example2', 'route_name' => 'example1', 'title' => 'Example 2', 'metadata' => ['cache_contexts' => ['bar']] + $cache_defaults]),
+        MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'Example 1', 'metadata' => ['cache_contexts' => ['foo']] + $cache_defaults])),
+        MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example2', 'route_name' => 'example1', 'title' => 'Example 2', 'metadata' => ['cache_contexts' => ['bar']] + $cache_defaults])),
       ],
     ];
 
@@ -224,9 +225,9 @@ public function providerTestBuildCacheability() {
         ];
 
         // Multi-level tree.
-        $multi_level_root_a = MenuLinkMock::create(['id' => 'test.roota', 'route_name' => 'roota', 'title' => 'Root A']);
-        $multi_level_root_b = MenuLinkMock::create(['id' => 'test.rootb', 'route_name' => 'rootb', 'title' => 'Root B']);
-        $multi_level_parent_c = MenuLinkMock::create(['id' => 'test.parentc', 'route_name' => 'parentc', 'title' => 'Parent C']);
+        $multi_level_root_a = MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.roota', 'route_name' => 'roota', 'title' => 'Root A']));
+        $multi_level_root_b = MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.rootb', 'route_name' => 'rootb', 'title' => 'Root B']));
+        $multi_level_parent_c = MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.parentc', 'route_name' => 'parentc', 'title' => 'Parent C']));
         $tree = [
           new MenuLinkTreeElement($multi_level_root_a, TRUE, 0, FALSE, [
             new MenuLinkTreeElement($multi_level_parent_c, TRUE, 0, FALSE, [
diff --git a/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php b/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php
index a5e5650..436d122 100644
--- a/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php
+++ b/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php
@@ -143,7 +143,7 @@ public function query() {
       $def['table formula'] = $query;
     }
 
-    $join = \Drupal::service('plugin.manager.views.join')->createInstance('standard', $def);
+    $join = \Drupal::service('plugin.manager.views.join')->createInstance('standard', $def->getArrayDefinition());
 
     // use a short alias for this:
     $alias = $def['table'] . '_' . $this->table;
diff --git a/core/modules/tour/src/Annotation/Tip.php b/core/modules/tour/src/Annotation/Tip.php
index c1075d1..a120b73 100644
--- a/core/modules/tour/src/Annotation/Tip.php
+++ b/core/modules/tour/src/Annotation/Tip.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\tour\Annotation;
 
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines a tour item annotation object.
diff --git a/core/modules/user/tests/src/Unit/Plugin/views/field/UserBulkFormTest.php b/core/modules/user/tests/src/Unit/Plugin/views/field/UserBulkFormTest.php
index 8eb26d8..dbc94cc 100644
--- a/core/modules/user/tests/src/Unit/Plugin/views/field/UserBulkFormTest.php
+++ b/core/modules/user/tests/src/Unit/Plugin/views/field/UserBulkFormTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\user\Unit\Plugin\views\field;
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\user\Plugin\views\field\UserBulkForm;
 
@@ -86,7 +87,9 @@ public function testConstructor() {
       ->disableOriginalConstructor()
       ->getMock();
 
-    $definition['title'] = '';
+    $definition = new ArrayPluginDefinition([
+      'title' => '',
+    ]);
     $options = array();
 
     $user_bulk_form = new UserBulkForm(array(), 'user_bulk_form', $definition, $entity_manager, $language_manager);
diff --git a/core/modules/user/tests/src/Unit/Views/Argument/RolesRidTest.php b/core/modules/user/tests/src/Unit/Views/Argument/RolesRidTest.php
index 3a4db29..569832b 100644
--- a/core/modules/user/tests/src/Unit/Views/Argument/RolesRidTest.php
+++ b/core/modules/user/tests/src/Unit/Views/Argument/RolesRidTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\user\Entity\Role;
 use Drupal\user\Plugin\views\argument\RolesRid;
@@ -69,7 +70,7 @@ public function testTitleQuery() {
     $container->set('entity.manager', $entity_manager);
     \Drupal::setContainer($container);
 
-    $roles_rid_argument = new RolesRid(array(), 'user__roles_rid', array(), $entity_manager);
+    $roles_rid_argument = new RolesRid(array(), 'user__roles_rid', new ArrayPluginDefinition(), $entity_manager);
 
     $roles_rid_argument->value = array();
     $titles = $roles_rid_argument->title_query();
diff --git a/core/modules/views/src/Annotation/ViewsHandlerAnnotationBase.php b/core/modules/views/src/Annotation/ViewsHandlerAnnotationBase.php
index e12131b..bb182e6 100644
--- a/core/modules/views/src/Annotation/ViewsHandlerAnnotationBase.php
+++ b/core/modules/views/src/Annotation/ViewsHandlerAnnotationBase.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\views\Annotation;
 
-use Drupal\Component\Annotation\PluginID;
+use Drupal\Core\Annotation\PluginID;
 
 /**
  * Defines an abstract base class for all views handler annotations.
diff --git a/core/modules/views/src/Annotation/ViewsPluginAnnotationBase.php b/core/modules/views/src/Annotation/ViewsPluginAnnotationBase.php
index 869cc69..a863d42 100644
--- a/core/modules/views/src/Annotation/ViewsPluginAnnotationBase.php
+++ b/core/modules/views/src/Annotation/ViewsPluginAnnotationBase.php
@@ -8,7 +8,7 @@
 namespace Drupal\views\Annotation;
 
 use Drupal\Component\Annotation\AnnotationInterface;
-use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Plugin;
 
 /**
  * Defines an abstract base class for all views plugin annotations.
diff --git a/core/modules/views/src/Plugin/Derivative/DefaultWizardDeriver.php b/core/modules/views/src/Plugin/Derivative/DefaultWizardDeriver.php
index f7610f2..7e994ae 100644
--- a/core/modules/views/src/Plugin/Derivative/DefaultWizardDeriver.php
+++ b/core/modules/views/src/Plugin/Derivative/DefaultWizardDeriver.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\views\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\views\Views;
 
 /**
@@ -19,19 +21,19 @@ class DefaultWizardDeriver extends DeriverBase {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $views_data = Views::viewsData();
     $base_tables = array_keys($views_data->fetchBaseTables());
     $this->derivatives = array();
     foreach ($base_tables as $table) {
       $views_info = $views_data->get($table);
       if (empty($views_info['table']['wizard_id'])) {
-        $this->derivatives[$table] = array(
+        $this->derivatives[$table] = new ArrayPluginDefinition([
           'id' => 'standard',
           'base_table' => $table,
           'title' => $views_info['table']['base']['title'],
           'class' => 'Drupal\views\Plugin\views\wizard\Standard'
-        );
+        ]);
       }
     }
     return parent::getDerivativeDefinitions($base_plugin_definition);
diff --git a/core/modules/views/src/Plugin/Derivative/ViewsBlock.php b/core/modules/views/src/Plugin/Derivative/ViewsBlock.php
index 3ebfc5f..3b4515a 100644
--- a/core/modules/views/src/Plugin/Derivative/ViewsBlock.php
+++ b/core/modules/views/src/Plugin/Derivative/ViewsBlock.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\views\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -21,7 +23,7 @@ class ViewsBlock implements ContainerDeriverInterface {
   /**
    * List of derivative definitions.
    *
-   * @var array
+   * @var \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]
    */
   protected $derivatives = array();
 
@@ -65,7 +67,7 @@ public function __construct($base_plugin_id, EntityStorageInterface $view_storag
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
       return $this->derivatives[$derivative_id];
     }
@@ -76,7 +78,7 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     // Check all Views for block displays.
     foreach ($this->viewStorage->loadMultiple() as $view) {
       // Do not return results for disabled views.
@@ -99,7 +101,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
               $desc = t('!view: !display', array('!view' => $view->label(), '!display' => $display->display['display_title']));
             }
           }
-          $this->derivatives[$delta] = array(
+          $this->derivatives[$delta] = new ArrayPluginDefinition([
             'category' => $display->getOption('block_category'),
             'admin_label' => $desc,
             'config_dependencies' => array(
@@ -107,8 +109,8 @@ public function getDerivativeDefinitions($base_plugin_definition) {
                 $view->getConfigDependencyName(),
               )
             )
-          );
-          $this->derivatives[$delta] += $base_plugin_definition;
+          ]);
+          $this->derivatives[$delta]->mergeDefaultDefinition($base_plugin_definition);
         }
       }
     }
diff --git a/core/modules/views/src/Plugin/Derivative/ViewsEntityArgumentValidator.php b/core/modules/views/src/Plugin/Derivative/ViewsEntityArgumentValidator.php
index ee6a582..2f4372b 100644
--- a/core/modules/views/src/Plugin/Derivative/ViewsEntityArgumentValidator.php
+++ b/core/modules/views/src/Plugin/Derivative/ViewsEntityArgumentValidator.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\views\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -75,18 +77,18 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $entity_types = $this->entityManager->getDefinitions();
     $this->derivatives = array();
     foreach ($entity_types as $entity_type_id => $entity_type) {
-      $this->derivatives[$entity_type_id] = array(
+      $this->derivatives[$entity_type_id] = new ArrayPluginDefinition([
         'id' => 'entity:' . $entity_type_id,
         'provider' => 'views',
         'title' => $entity_type->getLabel(),
         'help' => $this->t('Validate @label', array('@label' => $entity_type->getLabel())),
         'entity_type' => $entity_type_id,
         'class' => $base_plugin_definition['class'],
-      );
+      ]);
     }
 
     return $this->derivatives;
diff --git a/core/modules/views/src/Plugin/Derivative/ViewsEntityRow.php b/core/modules/views/src/Plugin/Derivative/ViewsEntityRow.php
index 5f82d91..c94df5e 100644
--- a/core/modules/views/src/Plugin/Derivative/ViewsEntityRow.php
+++ b/core/modules/views/src/Plugin/Derivative/ViewsEntityRow.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\views\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Drupal\views\ViewsData;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -79,7 +81,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
       return $this->derivatives[$derivative_id];
     }
@@ -90,11 +92,11 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
       // Just add support for entity types which have a views integration.
       if (($base_table = $entity_type->getBaseTable()) && $this->viewsData->get($base_table) && $this->entityManager->hasHandler($entity_type_id, 'view_builder')) {
-        $this->derivatives[$entity_type_id] = array(
+        $this->derivatives[$entity_type_id] = new ArrayPluginDefinition([
           'id' => 'entity:' . $entity_type_id,
           'provider' => 'views',
           'title' => $entity_type->getLabel(),
@@ -103,7 +105,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
           'entity_type' => $entity_type_id,
           'display_types' => array('normal'),
           'class' => $base_plugin_definition['class'],
-        );
+        ]);
       }
     }
 
diff --git a/core/modules/views/src/Plugin/Derivative/ViewsExposedFilterBlock.php b/core/modules/views/src/Plugin/Derivative/ViewsExposedFilterBlock.php
index f405aed..6847c92 100644
--- a/core/modules/views/src/Plugin/Derivative/ViewsExposedFilterBlock.php
+++ b/core/modules/views/src/Plugin/Derivative/ViewsExposedFilterBlock.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\views\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -21,7 +23,7 @@ class ViewsExposedFilterBlock implements ContainerDeriverInterface {
   /**
    * List of derivative definitions.
    *
-   * @var array
+   * @var \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]
    */
   protected $derivatives = array();
 
@@ -65,7 +67,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
       return $this->derivatives[$derivative_id];
     }
@@ -76,7 +78,7 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     // Check all Views for displays with an exposed filter block.
     foreach ($this->viewStorage->loadMultiple() as $view) {
       // Do not return results for disabled views.
@@ -91,15 +93,15 @@ public function getDerivativeDefinitions($base_plugin_definition) {
           if ($display->usesExposedFormInBlock()) {
             $delta = $view->id() . '-' . $display->display['id'];
             $desc = t('Exposed form: @view-@display_id', array('@view' => $view->id(), '@display_id' => $display->display['id']));
-            $this->derivatives[$delta] = array(
+            $this->derivatives[$delta] = new ArrayPluginDefinition([
               'admin_label' => $desc,
               'config_dependencies' => array(
                 'config' => array(
                   $view->getConfigDependencyName(),
                 )
               )
-            );
-            $this->derivatives[$delta] += $base_plugin_definition;
+            ]);
+            $this->derivatives[$delta]->mergeDefaultDefinition($base_plugin_definition);
           }
         }
       }
diff --git a/core/modules/views/src/Plugin/Derivative/ViewsLocalTask.php b/core/modules/views/src/Plugin/Derivative/ViewsLocalTask.php
index 343dc76..efcb8d8 100644
--- a/core/modules/views/src/Plugin/Derivative/ViewsLocalTask.php
+++ b/core/modules/views/src/Plugin/Derivative/ViewsLocalTask.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\views\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\State\StateInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
@@ -72,9 +74,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
-    $this->derivatives = array();
-
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $view_route_names = $this->state->get('views.view_route_names');
     foreach ($this->getApplicableMenuViews() as $pair) {
       /** @var $executable \Drupal\views\ViewExecutable */
@@ -93,11 +93,12 @@ public function getDerivativeDefinitions($base_plugin_definition) {
           continue;
         }
 
-        $this->derivatives[$plugin_id] = array(
+        $this->derivatives[$plugin_id] = new ArrayPluginDefinition([
           'route_name' => $route_name,
           'weight' => $menu['weight'],
           'title' => $menu['title'],
-        ) + $base_plugin_definition;
+        ]);
+        $this->derivatives[$plugin_id]->mergeDefaultDefinition($base_plugin_definition);
 
         // Default local tasks have themselves as root tab.
         if ($menu['type'] == 'default tab') {
diff --git a/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php b/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php
index 27bf9cc..1e371a2 100644
--- a/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php
+++ b/core/modules/views/src/Plugin/Derivative/ViewsMenuLink.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\views\Plugin\Derivative;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Drupal\views\Views;
 use Drupal\Core\Entity\EntityStorageInterface;
@@ -49,8 +51,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
-    $links = array();
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $views = Views::getApplicableViews('uses_menu_links');
 
     foreach ($views as $data) {
@@ -60,12 +61,13 @@ public function getDerivativeDefinitions($base_plugin_definition) {
 
       if ($result = $executable->getMenuLinks($display_id)) {
         foreach ($result as $link_id => $link) {
-          $links[$link_id] = $link + $base_plugin_definition;
+          $this->derivatives[$link_id] = new ArrayPluginDefinition($link);
+          $this->derivatives[$link_id]->mergeDefaultDefinition($base_plugin_definition);
         }
       }
     }
 
-    return $links;
+    return $this->derivatives;
   }
 
 }
diff --git a/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php b/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php
index 85c2350..dea4a0d 100644
--- a/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php
+++ b/core/modules/views/src/Plugin/Menu/ViewsMenuLink.php
@@ -10,6 +10,8 @@
 use Drupal\Core\Menu\MenuLinkBase;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\views\ViewExecutableFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -133,7 +135,7 @@ public function getDescription() {
   public function updateLink(array $new_definition_values, $persist) {
     $overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
     // Update the definition.
-    $this->pluginDefinition = $overrides + $this->pluginDefinition;
+    $this->pluginDefinition = (new ArrayPluginDefinition($overrides))->mergeDefaultDefinition($this->pluginDefinition);
     if ($persist) {
       $view = $this->loadView();
       $display = &$view->storage->getDisplay($view->current_display);
diff --git a/core/modules/views/src/Plugin/views/PluginBase.php b/core/modules/views/src/Plugin/views/PluginBase.php
index e56dcae..840501f 100644
--- a/core/modules/views/src/Plugin/views/PluginBase.php
+++ b/core/modules/views/src/Plugin/views/PluginBase.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\PluginBase as ComponentPluginBase;
 use Drupal\Core\Render\Element;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
@@ -94,7 +95,7 @@
   /**
    * Plugins's definition
    *
-   * @var array
+   * @var \Drupal\Core\Plugin\Definition\PluginDefinitionInterface
    */
   public $definition;
 
@@ -124,8 +125,8 @@
    */
   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
-
-    $this->definition = $plugin_definition + $configuration;
+    $this->definition = clone $plugin_definition;
+    $this->definition->mergeDefaultArrayDefinition($configuration);
   }
 
   /**
diff --git a/core/modules/views/src/Plugin/views/field/EntityOperations.php b/core/modules/views/src/Plugin/views/field/EntityOperations.php
index 35fbfc3..741b7d0 100644
--- a/core/modules/views/src/Plugin/views/field/EntityOperations.php
+++ b/core/modules/views/src/Plugin/views/field/EntityOperations.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Routing\RedirectDestinationTrait;
 use Drupal\views\ResultRow;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -38,12 +39,12 @@ class EntityOperations extends FieldPluginBase {
    *   A configuration array containing information about the plugin instance.
    * @param string $plugin_id
    *   The plugin_id for the plugin instance.
-   * @param array $plugin_definition
+   * @param \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $plugin_definition
    *   The plugin implementation definition.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *    The entity manager.
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManagerInterface $entity_manager) {
+  public function __construct(array $configuration, $plugin_id, PluginDefinitionInterface $plugin_definition, EntityManagerInterface $entity_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->entityManager = $entity_manager;
   }
diff --git a/core/modules/views/src/Plugin/views/relationship/GroupwiseMax.php b/core/modules/views/src/Plugin/views/relationship/GroupwiseMax.php
index 52b1ff7..e9497c5 100644
--- a/core/modules/views/src/Plugin/views/relationship/GroupwiseMax.php
+++ b/core/modules/views/src/Plugin/views/relationship/GroupwiseMax.php
@@ -380,7 +380,7 @@ public function query() {
     else {
       $id = 'subquery';
     }
-    $join = Views::pluginManager('join')->createInstance($id, $def);
+    $join = Views::pluginManager('join')->createInstance($id, $def->getArrayDefinition());
 
     // use a short alias for this:
     $alias = $def['table'] . '_' . $this->table;
diff --git a/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php b/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php
index 34a2a9a..878d4ff 100644
--- a/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php
+++ b/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php
@@ -157,7 +157,7 @@ public function query() {
     else {
       $id = 'standard';
     }
-    $join = Views::pluginManager('join')->createInstance($id, $def);
+    $join = Views::pluginManager('join')->createInstance($id, $def->getArrayDefinition());
 
     // use a short alias for this:
     $alias = $def['table'] . '_' . $this->table;
diff --git a/core/modules/views/tests/src/Unit/Plugin/Derivative/ViewsLocalTaskTest.php b/core/modules/views/tests/src/Unit/Plugin/Derivative/ViewsLocalTaskTest.php
index af9a683..83485c0 100644
--- a/core/modules/views/tests/src/Unit/Plugin/Derivative/ViewsLocalTaskTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/Derivative/ViewsLocalTaskTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\Derivative;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\Derivative\ViewsLocalTask;
 use Symfony\Component\Routing\Route;
@@ -37,10 +38,7 @@ class ViewsLocalTaskTest extends UnitTestCase {
    */
   protected $viewStorage;
 
-  protected $baseDefinition = array(
-    'class' => '\Drupal\views\Plugin\Menu\LocalTask\ViewsLocalTask',
-    'deriver' => '\Drupal\views\Plugin\Derivative\ViewsLocalTask'
-  );
+  protected $baseDefinition;
 
   /**
    * The tested local task derivative class.
@@ -50,6 +48,10 @@ class ViewsLocalTaskTest extends UnitTestCase {
   protected $localTaskDerivative;
 
   protected function setUp() {
+    $this->baseDefinition = new ArrayPluginDefinition([
+      'class' => '\stdClass',
+      'deriver' => '\Drupal\views\Plugin\Derivative\ViewsLocalTask'
+    ]);
     $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
     $this->state = $this->getMock('Drupal\Core\State\StateInterface');
     $this->viewStorage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
diff --git a/core/modules/views/tests/src/Unit/Plugin/HandlerBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/HandlerBaseTest.php
index 12b7ca1..bc15ced 100644
--- a/core/modules/views/tests/src/Unit/Plugin/HandlerBaseTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/HandlerBaseTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\views\HandlerBase;
 
@@ -33,7 +34,7 @@ protected function setUp() {
    * @covers ::getEntityType
    */
   public function testGetEntityTypeForFieldOnBaseTable() {
-    $handler = new TestHandler([], 'test_handler', []);
+    $handler = new TestHandler([], 'test_handler', new ArrayPluginDefinition());
     $handler->init($this->executable, $this->display);
 
     $this->view->expects($this->any())
@@ -55,7 +56,7 @@ public function testGetEntityTypeForFieldOnBaseTable() {
    * @covers ::getEntityType
    */
   public function testGetEntityTypeForFieldWithRelationship() {
-    $handler = new TestHandler([], 'test_handler', []);
+    $handler = new TestHandler([], 'test_handler', new ArrayPluginDefinition());
 
     $options = ['relationship' => 'test_relationship'];
     $handler->init($this->executable, $this->display, $options);
diff --git a/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php b/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php
index bb89281..f278f07 100644
--- a/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\area;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\views\area\Entity;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -87,7 +88,10 @@ protected function setUp() {
       ->getMock();
     $this->executable->style_plugin = $this->stylePlugin;
 
-    $this->entityHandler = new Entity(array(), 'entity', array('entity_type' => 'entity_test'), $this->entityManager);
+    $definition = new ArrayPluginDefinition([
+      'entity_type' => 'entity_test',
+    ]);
+    $this->entityHandler = new Entity(array(), 'entity', $definition, $this->entityManager);
 
     $this->display->expects($this->any())
       ->method('getPlugin')
diff --git a/core/modules/views/tests/src/Unit/Plugin/area/MessagesTest.php b/core/modules/views/tests/src/Unit/Plugin/area/MessagesTest.php
index 3c18e79..dfbd2e1 100644
--- a/core/modules/views/tests/src/Unit/Plugin/area/MessagesTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/area/MessagesTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\area;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\views\area\Messages;
 
@@ -36,7 +37,7 @@ class MessagesTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
 
-    $this->messagesHandler = new Messages(array(), 'result', array());
+    $this->messagesHandler = new Messages(array(), 'result', new ArrayPluginDefinition());
   }
 
   /**
diff --git a/core/modules/views/tests/src/Unit/Plugin/area/ResultTest.php b/core/modules/views/tests/src/Unit/Plugin/area/ResultTest.php
index 87c79a3d..93fcb6a 100644
--- a/core/modules/views/tests/src/Unit/Plugin/area/ResultTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/area/ResultTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\area;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\area\Result;
@@ -49,7 +50,7 @@ protected function setUp() {
     $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
     $this->view = new ViewExecutable($storage, $user, $views_data, $route_provider);
 
-    $this->resultHandler = new Result(array(), 'result', array());
+    $this->resultHandler = new Result(array(), 'result', new ArrayPluginDefinition());
     $this->resultHandler->view = $this->view;
   }
 
diff --git a/core/modules/views/tests/src/Unit/Plugin/area/ViewTest.php b/core/modules/views/tests/src/Unit/Plugin/area/ViewTest.php
index fd7d497..f3f2267 100644
--- a/core/modules/views/tests/src/Unit/Plugin/area/ViewTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/area/ViewTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\area;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\views\area\View as ViewAreaPlugin;
 
@@ -36,7 +37,7 @@ class ViewTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
     $this->entityStorage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
-    $this->viewHandler = new ViewAreaPlugin(array(), 'view', array(), $this->entityStorage);
+    $this->viewHandler = new ViewAreaPlugin(array(), 'view', new ArrayPluginDefinition(), $this->entityStorage);
     $this->viewHandler->view = $this->getMockBuilder('Drupal\views\ViewExecutable')
       ->disableOriginalConstructor()
       ->getMock();
diff --git a/core/modules/views/tests/src/Unit/Plugin/argument_default/QueryParameterTest.php b/core/modules/views/tests/src/Unit/Plugin/argument_default/QueryParameterTest.php
index 0df15f2..c69489a 100644
--- a/core/modules/views/tests/src/Unit/Plugin/argument_default/QueryParameterTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/argument_default/QueryParameterTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\argument_default;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\views\argument_default\QueryParameter;
 use Symfony\Component\HttpFoundation\Request;
@@ -33,7 +34,7 @@ public function testGetArgument($options, Request $request, $expected) {
       ->disableOriginalConstructor()
       ->getMock();
 
-    $raw = new QueryParameter(array(), 'query_parameter', array());
+    $raw = new QueryParameter(array(), 'query_parameter', new ArrayPluginDefinition());
     $raw->init($view, $display_plugin, $options);
     $this->assertEquals($expected, $raw->getArgument());
   }
diff --git a/core/modules/views/tests/src/Unit/Plugin/argument_default/RawTest.php b/core/modules/views/tests/src/Unit/Plugin/argument_default/RawTest.php
index f50b928..f362008 100644
--- a/core/modules/views/tests/src/Unit/Plugin/argument_default/RawTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/argument_default/RawTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\views\Unit\Plugin\argument_default;
 
 use Drupal\Core\Path\CurrentPathStack;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\views\argument_default\Raw;
 use Symfony\Component\HttpFoundation\Request;
@@ -43,7 +44,7 @@ public function testGetArgument() {
       ->method('getAliasByPath');
 
     // Don't use aliases.
-    $raw = new Raw(array(), 'raw', array(), $alias_manager, $current_path);
+    $raw = new Raw(array(), 'raw', new ArrayPluginDefinition(), $alias_manager, $current_path);
     $options = array(
       'use_alias' => FALSE,
       'index' => 0,
@@ -51,7 +52,7 @@ public function testGetArgument() {
     $raw->init($view, $display_plugin, $options);
     $this->assertEquals('test', $raw->getArgument());
 
-    $raw = new Raw(array(), 'raw', array(), $alias_manager, $current_path);
+    $raw = new Raw(array(), 'raw', new ArrayPluginDefinition(), $alias_manager, $current_path);
     $options = array(
       'use_alias' => FALSE,
       'index' => 1,
@@ -66,7 +67,7 @@ public function testGetArgument() {
       ->with($this->equalTo('test/example'))
       ->will($this->returnValue('other/example'));
 
-    $raw = new Raw(array(), 'raw', array(), $alias_manager, $current_path);
+    $raw = new Raw(array(), 'raw', new ArrayPluginDefinition(), $alias_manager, $current_path);
     $options = array(
       'use_alias' => TRUE,
       'index' => 0,
@@ -74,7 +75,7 @@ public function testGetArgument() {
     $raw->init($view, $display_plugin, $options);
     $this->assertEquals('other', $raw->getArgument());
 
-    $raw = new Raw(array(), 'raw', array(), $alias_manager, $current_path);
+    $raw = new Raw(array(), 'raw', new ArrayPluginDefinition(), $alias_manager, $current_path);
     $options = array(
       'use_alias' => TRUE,
       'index' => 1,
diff --git a/core/modules/views/tests/src/Unit/Plugin/argument_validator/EntityTest.php b/core/modules/views/tests/src/Unit/Plugin/argument_validator/EntityTest.php
index 0a00f8b..8e76f92 100644
--- a/core/modules/views/tests/src/Unit/Plugin/argument_validator/EntityTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/argument_validator/EntityTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\argument_validator;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\views\argument_validator\Entity;
 
@@ -105,9 +106,9 @@ protected function setUp() {
       ->disableOriginalConstructor()
       ->getMock();
 
-    $definition = array(
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'entity_test',
-    );
+    ]);
 
     $this->argumentValidator = new Entity(array(), 'entity_test', $definition, $this->entityManager);
   }
@@ -213,7 +214,10 @@ public function testCalculateDependencies() {
       ->willReturn($storage);
 
     // Set up the argument validator.
-    $argumentValidator = new Entity(array(), 'entity_test', ['entity_type' => 'entity_test'], $entityManager);
+    $definition = new ArrayPluginDefinition([
+      'entity_type' => 'entity_test',
+    ]);
+    $argumentValidator = new Entity(array(), 'entity_test', $definition, $entityManager);
     $options = array();
     $options['access'] = FALSE;
     $options['bundles'] = array('test_bundle' => 1);
diff --git a/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php
index b0ebe13..2626946 100644
--- a/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/display/PathPluginBaseTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\views\Unit\Plugin\display;
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
@@ -55,7 +56,7 @@ protected function setUp() {
     $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
     $this->state = $this->getMock('\Drupal\Core\State\StateInterface');
     $this->pathPlugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase')
-      ->setConstructorArgs(array(array(), 'path_base', array(), $this->routeProvider, $this->state))
+      ->setConstructorArgs(array(array(), 'path_base', new ArrayPluginDefinition(), $this->routeProvider, $this->state))
       ->setMethods(NULL)
       ->getMock();
     $this->setupContainer();
@@ -124,8 +125,11 @@ public function testCollectRoutesWithDisplayReturnResponse() {
     $display['display_options'] = array(
       'path' => 'test_route',
     );
+    $definition = new ArrayPluginDefinition([
+      'returns_response' => TRUE,
+    ]);
     $this->pathPlugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase')
-      ->setConstructorArgs(array(array(), 'path_base', array('returns_response' => TRUE), $this->routeProvider, $this->state))
+      ->setConstructorArgs(array(array(), 'path_base', $definition, $this->routeProvider, $this->state))
       ->setMethods(NULL)
       ->getMock();
     $this->pathPlugin->initDisplay($view, $display);
diff --git a/core/modules/views/tests/src/Unit/Plugin/field/CounterTest.php b/core/modules/views/tests/src/Unit/Plugin/field/CounterTest.php
index 5a68eb2..1449350 100644
--- a/core/modules/views/tests/src/Unit/Plugin/field/CounterTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/field/CounterTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\views\Unit\Plugin\field;
 
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Entity\View;
 use Drupal\views\Plugin\views\field\Counter;
@@ -94,7 +95,10 @@ protected function setUp() {
       $this->testData[] = new ResultRow($set + ['index' => $index]);
     }
 
-    $this->definition = array('title' => 'counter field', 'plugin_type' => 'field');
+    $this->definition = new ArrayPluginDefinition([
+      'title' => 'counter field',
+      'plugin_type' => 'field',
+    ]);
   }
 
   /**
diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php
index ec3b0d0..6af66eb 100644
--- a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\views\Unit\Plugin\field;
 
 use Drupal\Core\Language\Language;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Url;
 use Drupal\Core\Utility\LinkGenerator;
 use Drupal\Core\Utility\LinkGeneratorInterface;
@@ -43,9 +44,9 @@ class FieldPluginBaseTest extends UnitTestCase {
   /**
    * The definition of the plugin under test.
    *
-   * @var array
+   * @var \Drupal\Component\Plugin\Definition\PluginDefinitionInterface
    */
-  protected $pluginDefinition = [];
+  protected $pluginDefinition;
 
   /**
    * Default configuration for URL output.
@@ -131,6 +132,8 @@ class FieldPluginBaseTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
 
+    $this->pluginDefinition = new ArrayPluginDefinition();
+
     $this->executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
       ->disableOriginalConstructor()
       ->getMock();
diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php
index 90bd2cc..1e18e26 100644
--- a/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\views\Unit\Plugin\field;
 
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Tests\views\Unit\Plugin\HandlerTestTrait;
 use Drupal\views\Plugin\views\field\Field;
@@ -101,12 +102,12 @@ protected function setUp() {
    * @covers ::__construct
    */
   public function testConstruct() {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       // Just provide 'entity field' as definition. This is how EntityViewsData
       // provides it.
       'entity field' => 'title',
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
 
     $this->assertEquals('title', $handler->definition['field_name']);
@@ -116,10 +117,10 @@ public function testConstruct() {
    * @covers ::defineOptions
    */
   public function testDefineOptionsWithNoOptions() {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'title'
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
 
     // Setup the entity manager to allow fetching the storage definitions.
@@ -143,12 +144,12 @@ public function testDefineOptionsWithNoOptions() {
    * @covers ::defineOptions
    */
   public function testDefineOptionsWithDefaultFormatterOnFieldDefinition() {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'title',
       'default_formatter' => 'test_example',
       'default_formatter_settings' => ['link_to_entity' => TRUE]
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
 
     // Setup the entity manager to allow fetching the storage definitions.
@@ -171,11 +172,11 @@ public function testDefineOptionsWithDefaultFormatterOnFieldDefinition() {
    * @covers ::defineOptions
    */
   public function testDefineOptionsWithDefaultFormatterOnFieldType() {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'title',
       'default_formatter_settings' => ['link_to_entity' => TRUE]
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
 
     // Setup the entity manager to allow fetching the storage definitions.
@@ -198,10 +199,10 @@ public function testDefineOptionsWithDefaultFormatterOnFieldType() {
    * @covers ::calculateDependencies
    */
   public function testCalculateDependenciesWithBaseField() {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
-      'field_name' => 'title'
-    ];
+      'field_name' => 'title',
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
 
     $title_storage = $this->getBaseFieldStorage();
@@ -220,10 +221,10 @@ public function testCalculateDependenciesWithBaseField() {
    * @covers ::calculateDependencies
    */
   public function testCalculateDependenciesWithConfiguredField() {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'body'
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
 
     $body_storage = $this->getConfigFieldStorage();
@@ -246,10 +247,10 @@ public function testCalculateDependenciesWithConfiguredField() {
    * @covers ::access
    */
   public function testAccess() {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'title',
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
     $handler->view = $this->executable;
     $handler->setViewsData($this->viewsData);
@@ -297,10 +298,10 @@ public function testAccess() {
    *   The sort order.
    */
   public function testClickSortWithOutConfiguredColumn($order) {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'title',
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
     $handler->view = $this->executable;
 
@@ -319,10 +320,10 @@ public function testClickSortWithOutConfiguredColumn($order) {
    * @covers ::clickSort
    */
   public function testClickSortWithBaseField($order) {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'title',
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
     $handler->view = $this->executable;
 
@@ -379,10 +380,10 @@ public function testClickSortWithBaseField($order) {
    * @covers ::clickSort
    */
   public function testClickSortWithConfiguredField($order) {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'body',
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
     $handler->view = $this->executable;
 
@@ -434,10 +435,10 @@ public function testClickSortWithConfiguredField($order) {
    * @covers ::query
    */
   public function testQueryWithGroupByForBaseField() {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'title',
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
     $handler->view = $this->executable;
     $handler->view->field = [$handler];
@@ -496,10 +497,10 @@ public function testQueryWithGroupByForBaseField() {
    * @covers ::query
    */
   public function testQueryWithGroupByForConfigField() {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'body',
-    ];
+    ]);
     $handler = new Field([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
     $handler->view = $this->executable;
     $handler->view->field = [$handler];
@@ -560,10 +561,10 @@ public function testQueryWithGroupByForConfigField() {
    * @dataProvider providerTestPrepareItemsByDelta
    */
   public function testPrepareItemsByDelta(array $options, array $expected_values) {
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'entity_type' => 'test_entity',
       'field_name' => 'integer',
-    ];
+    ]);
     $handler = new FieldTestField([], 'field', $definition, $this->entityManager, $this->formatterPluginManager, $this->fieldTypePluginManager, $this->languageManager, $this->renderer);
     $handler->view = $this->executable;
     $handler->view->field = [$handler];
diff --git a/core/modules/views/tests/src/Unit/Plugin/query/SqlTest.php b/core/modules/views/tests/src/Unit/Plugin/query/SqlTest.php
index feb3ce2..0ed9bb6 100644
--- a/core/modules/views/tests/src/Unit/Plugin/query/SqlTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/query/SqlTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\query;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\views\query\Sql;
 use Drupal\views\ResultRow;
@@ -25,7 +26,7 @@ class SqlTest extends UnitTestCase {
   public function testGetCacheTags() {
     $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
 
-    $query = new Sql([], 'sql', []);
+    $query = new Sql([], 'sql', new ArrayPluginDefinition());
     $query->view = $view;
 
     $result = [];
@@ -70,7 +71,7 @@ public function testGetCacheTags() {
   public function testGetCacheMaxAge() {
     $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
 
-    $query = new Sql([], 'sql', []);
+    $query = new Sql([], 'sql', new ArrayPluginDefinition());
     $query->view = $view;
 
     $view->result = [];
diff --git a/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php b/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php
index c7c7002..48604c7 100644
--- a/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php
+++ b/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit\Plugin\views\field {
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Plugin\views\field\EntityOperations;
 use Drupal\views\ResultRow;
@@ -41,9 +42,9 @@ public function setUp() {
 
     $configuration = array();
     $plugin_id = $this->randomMachineName();
-    $plugin_definition = array(
+    $plugin_definition = new ArrayPluginDefinition([
       'title' => $this->randomMachineName(),
-    );
+    ]);
     $this->plugin = new EntityOperations($configuration, $plugin_id, $plugin_definition, $this->entityManager);
 
     $redirect_service = $this->getMock('Drupal\Core\Routing\RedirectDestinationInterface');
diff --git a/core/modules/views/tests/src/Unit/PluginBaseTest.php b/core/modules/views/tests/src/Unit/PluginBaseTest.php
index 4da75f4..e8a16ee 100644
--- a/core/modules/views/tests/src/Unit/PluginBaseTest.php
+++ b/core/modules/views/tests/src/Unit/PluginBaseTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\views\Unit;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Tests\TestHelperPlugin;
 
@@ -29,7 +30,7 @@ class PluginBaseTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
 
-    $this->testHelperPlugin = new TestHelperPlugin(array(), 'default', array());
+    $this->testHelperPlugin = new TestHelperPlugin(array(), 'default', new ArrayPluginDefinition());
   }
 
   /**
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index b321940..2061cd8 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -14,6 +14,7 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Database\Query\AlterableInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
@@ -425,7 +426,9 @@ function views_add_contextual_links(&$render_element, $location, $display_id, ar
       $plugin['contextual_links_locations'] = array();
     }
     else {
-      $plugin += array('contextual_links_locations' => array('view'));
+      $plugin->mergeDefaultArrayDefinition([
+        'contextual_links_locations' => ['view'],
+      ]);
     }
 
     // On exposed_forms blocks contextual links should always be visible.
diff --git a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
index a42a879..4659dd8 100644
--- a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
+++ b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Entity\View;
 use Drupal\views\ViewExecutableFactory;
@@ -42,18 +43,18 @@ public function testBuildRowEntityList() {
         array(
           'default',
           TRUE,
-          array(
+          new ArrayPluginDefinition([
             'id' => 'default',
             'title' => 'Master',
             'theme' => 'views_view',
             'no_ui' => TRUE,
             'admin' => '',
-          )
+          ])
         ),
         array(
           'page',
           TRUE,
-          array(
+          new ArrayPluginDefinition([
             'id' => 'page',
             'title' => 'Page',
             'uses_menu_links' => TRUE,
@@ -61,17 +62,17 @@ public function testBuildRowEntityList() {
             'contextual_links_locations' => array('page'),
             'theme' => 'views_view',
             'admin' => 'Page admin label',
-          )
+          ])
         ),
         array(
           'embed',
           TRUE,
-          array(
+          new ArrayPluginDefinition([
             'id' => 'embed',
             'title' => 'embed',
             'theme' => 'views_view',
             'admin' => 'Embed admin label',
-          )
+          ])
         ),
       )));
 
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index 4d4416a..ded1d90 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -5,6 +5,7 @@
  */
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\user\Entity\User;
 use Drupal\user\RoleInterface;
 
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/ArrayPluginDefinitionTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/ArrayPluginDefinitionTest.php
new file mode 100644
index 0000000..8397059
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/ArrayPluginDefinitionTest.php
@@ -0,0 +1,323 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\ArrayPluginDefinitionTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\ArrayPluginDefinition
+ *
+ * @group Plugin
+ */
+class ArrayPluginDefinitionTest extends UnitTestCase {
+
+  /**
+   * The array definition.
+   *
+   * @var mixed[]
+   */
+  protected $arrayDefinition = [];
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\ArrayPluginDefinition
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->arrayDefinition = [
+      'id' => $this->randomMachineName(),
+      'label' => $this->randomMachineName(),
+      'class' => $this->getMockClass('\Drupal\Component\Plugin\Derivative\DeriverInterface'),
+      'category' => $this->randomMachineName(),
+      'provider' => $this->randomMachineName(),
+      'deriver' => $this->getMockClass('\Drupal\Component\Plugin\Derivative\DeriverInterface'),
+      'context' => [
+        $this->randomMachineName() => $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface'),
+      ],
+    ];
+
+    $this->sut = new ArrayPluginDefinition($this->arrayDefinition);
+  }
+
+  /**
+   * @covers ::setId
+   * @covers ::getId
+   * @covers ::offsetExists
+   * @covers ::offsetSet
+   * @covers ::offsetGet
+   * @covers ::offsetUnset
+   */
+  public function testGetId() {
+    // Test the injected value.
+    $this->assertSame($this->arrayDefinition['id'], $this->sut->getId());
+    $this->assertSame($this->arrayDefinition['id'], $this->sut->getArrayDefinition()['id']);
+
+    // Test changing the value.
+    $value = $this->randomMachineName();
+    $this->assertSame($this->sut, $this->sut->setId($value));
+    $this->assertSame($value, $this->sut->getId());
+    $this->assertSame($value, $this->sut->getArrayDefinition()['id']);
+
+    // Test unsetting the value.
+    unset($this->sut['id']);
+    $this->assertFalse(isset($this->sut['id']));
+    $this->assertNull($this->sut->getId());
+    $this->assertFalse(isset($this->sut->getArrayDefinition()['id']));
+  }
+
+  /**
+   * @covers ::setLabel
+   * @covers ::getLabel
+   * @covers ::offsetExists
+   * @covers ::offsetSet
+   * @covers ::offsetGet
+   * @covers ::offsetUnset
+   */
+  public function testGetLabel() {
+    // Test the injected value.
+    $this->assertSame($this->arrayDefinition['label'], $this->sut->getLabel());
+    $this->assertSame($this->arrayDefinition['label'], $this->sut->getArrayDefinition()['label']);
+
+    // Test changing the value.
+    $value = $this->randomMachineName();
+    $this->assertSame($this->sut, $this->sut->setLabel($value));
+    $this->assertSame($value, $this->sut->getLabel());
+    $this->assertSame($value, $this->sut->getArrayDefinition()['label']);
+
+    // Test unsetting the value.
+    unset($this->sut['label']);
+    $this->assertFalse(isset($this->sut['label']));
+    $this->assertNull($this->sut->getLabel());
+    $this->assertFalse(isset($this->sut->getArrayDefinition()['label']));
+  }
+
+  /**
+   * @covers ::setClass
+   * @covers ::getClass
+   * @covers ::offsetExists
+   * @covers ::offsetSet
+   * @covers ::offsetGet
+   * @covers ::offsetUnset
+   */
+  public function testGetClass() {
+    // Test the injected value.
+    $this->assertSame($this->arrayDefinition['class'], $this->sut->getClass());
+    $this->assertSame($this->arrayDefinition['class'], $this->sut->getArrayDefinition()['class']);
+
+    // Test changing the value.
+    $value = '\stdClass';
+    $this->assertSame($this->sut, $this->sut->setClass($value));
+    $this->assertSame($value, $this->sut->getClass());
+    $this->assertSame($value, $this->sut->getArrayDefinition()['class']);
+
+    // Test unsetting the value.
+    unset($this->sut['class']);
+    $this->assertFalse(isset($this->sut['class']));
+    $this->assertNull($this->sut->getClass());
+    $this->assertFalse(isset($this->sut->getArrayDefinition()['class']));
+  }
+
+  /**
+   * @covers ::setDeriverClass
+   * @covers ::getDeriverClass
+   * @covers ::offsetExists
+   * @covers ::offsetSet
+   * @covers ::offsetGet
+   * @covers ::offsetUnset
+   */
+  public function testGetDeriverClass() {
+    // Test the injected value.
+    $this->assertSame($this->arrayDefinition['deriver'], $this->sut->getDeriverClass());
+    $this->assertSame($this->arrayDefinition['deriver'], $this->sut->getArrayDefinition()['deriver']);
+
+    // Test changing the value.
+    $value = $this->getMockClass('\Drupal\Component\Plugin\Derivative\DeriverInterface');
+    $this->assertSame($this->sut, $this->sut->setDeriverClass($value));
+    $this->assertSame($value, $this->sut->getDeriverClass());
+    $this->assertSame($value, $this->sut->getArrayDefinition()['deriver']);
+
+    // Test unsetting the value.
+    unset($this->sut['deriver']);
+    $this->assertFalse(isset($this->sut['deriver']));
+    $this->assertNull($this->sut->getDeriverClass());
+    $this->assertFalse(isset($this->sut->getArrayDefinition()['deriver']));
+  }
+
+  /**
+   * @covers ::setContextDefinitions
+   * @covers ::getContextDefinitions
+   * @covers ::offsetExists
+   * @covers ::offsetSet
+   * @covers ::offsetGet
+   * @covers ::offsetUnset
+   */
+  public function testGetContextDefinitions() {
+    // Test the injected value.
+    $this->assertSame($this->arrayDefinition['context'], $this->sut->getContextDefinitions());
+    $this->assertSame($this->arrayDefinition['context'], $this->sut->getArrayDefinition()['context']);
+
+    // Test changing the value.
+    $context_definition_name_a = $this->randomMachineName();
+    $context_definition_a = $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface');
+    $context_definition_name_b = $this->randomMachineName();
+    $context_definition_b = $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface');
+    $value = [
+      $context_definition_name_a => $context_definition_a,
+      $context_definition_name_b => $context_definition_b,
+    ];
+    $this->assertSame($this->sut, $this->sut->setContextDefinitions($value));
+    $this->assertSame($value, $this->sut->getContextDefinitions());
+    $this->assertSame($value, $this->sut->getArrayDefinition()['context']);
+
+    // Test unsetting the value.
+    unset($this->sut['context']);
+    $this->assertFalse(isset($this->sut['context']));
+    $this->assertSame([], $this->sut->getContextDefinitions());
+    $this->assertFalse(isset($this->sut->getArrayDefinition()['context']));
+  }
+
+  /**
+   * @covers ::setContextDefinitions
+   * @covers ::offsetSet
+   *
+   * @depends testGetContextDefinitions
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testSetContextDefinitionsWithInvalidDefinition() {
+    $context_definitions = [
+      $this->randomMachineName() => new \stdClass(),
+    ];
+
+    $this->sut['context'] = $context_definitions;
+  }
+
+  /**
+   * @covers ::setContextDefinition
+   * @covers ::getContextDefinition
+   * @covers ::hasContextDefinition
+   */
+  public function testGetContextDefinition() {
+    $name = $this->randomMachineName();
+    $context_definition = $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface');
+
+    $this->assertSame($this->sut, $this->sut->setContextDefinition($name, $context_definition));
+    $this->assertSame($context_definition, $this->sut->getContextDefinition($name));
+    $this->assertTrue($this->sut->hasContextDefinition($name));
+  }
+
+  /**
+   * @covers ::getContextDefinition
+   * @covers ::hasContextDefinition
+   *
+   * @depends testGetContextDefinition
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testGetContextDefinitionWithNonExistentDefinition() {
+    $name = $this->randomMachineName();
+
+    $this->assertFalse($this->sut->hasContextDefinition($name));
+    $this->sut->getContextDefinition($name);
+  }
+
+  /**
+   * @covers ::mergeDefaultArrayDefinition
+   */
+  public function testMergeDefaultArrayDefinition() {
+    $other_definition = [
+      'foo' => $this->randomMachineName(),
+      'label' => $this->randomMachineName(),
+    ];
+
+    $this->assertSame($this->sut, $this->sut->mergeDefaultArrayDefinition($other_definition));
+    $this->assertSame($other_definition['foo'], $this->sut->getArrayDefinition()['foo']);
+    $this->assertNotSame($other_definition['label'], $this->sut->getArrayDefinition()['label']);
+  }
+
+  /**
+   * @covers ::mergeDefaultDefinition
+   * @covers ::doMergeDefaultDefinition
+   *
+   * @depends testMergeDefaultArrayDefinition
+   */
+  public function testMergeDefaultDefinition() {
+    $other_definition = new ArrayPluginDefinition();
+    $other_definition['foo'] = $this->randomMachineName();
+    $other_definition['label'] = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->mergeDefaultDefinition($other_definition));
+    $this->assertSame($other_definition['foo'], $this->sut->getArrayDefinition()['foo']);
+    $this->assertNotSame($other_definition['label'], $this->sut->getArrayDefinition()['label']);
+  }
+
+  /**
+   * @covers ::mergeDefaultDefinition
+   *
+   * @depends testMergeDefaultDefinition
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testMergeDefaultDefinitionWithInvalidOtherDefinition() {
+    $other_definition = $this->getMock('\Drupal\Component\Plugin\Definition\PluginDefinitionInterface');
+
+    $this->sut->mergeDefaultDefinition($other_definition);
+  }
+
+  /**
+   * @covers ::mergeOverrideArrayDefinition
+   */
+  public function testMergeOverrideArrayDefinition() {
+    $other_definition = [
+      'foo' => $this->randomMachineName(),
+      'label' => $this->randomMachineName(),
+    ];
+
+    $this->assertSame($this->sut, $this->sut->mergeOverrideArrayDefinition($other_definition));
+    $this->assertSame($other_definition['foo'], $this->sut->getArrayDefinition()['foo']);
+    $this->assertSame($other_definition['label'], $this->sut->getArrayDefinition()['label']);
+  }
+
+  /**
+   * @covers ::mergeOverrideDefinition
+   * @covers ::doMergeOverrideDefinition
+   *
+   * @depends testMergeOverrideArrayDefinition
+   */
+  public function testMergeOverrideDefinition() {
+    $other_definition = new ArrayPluginDefinition();
+    $other_definition['foo'] = $this->randomMachineName();
+    $other_definition['label'] = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->mergeOverrideDefinition($other_definition));
+    $this->assertSame($other_definition['foo'], $this->sut->getArrayDefinition()['foo']);
+    $this->assertSame($other_definition['label'], $this->sut->getArrayDefinition()['label']);
+  }
+
+  /**
+   * @covers ::mergeOverrideDefinition
+   *
+   * @depends testMergeOverrideDefinition
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testMergeOverrideDefinitionWithInvalidOtherDefinition() {
+    $other_definition = $this->getMock('\Drupal\Component\Plugin\Definition\PluginDefinitionInterface');
+
+    $this->sut->mergeOverrideDefinition($other_definition);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/MergeablePluginDefinitionTraitTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/MergeablePluginDefinitionTraitTest.php
new file mode 100644
index 0000000..64eab05
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/MergeablePluginDefinitionTraitTest.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\MergeablePluginDefinitionTraitTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\MergeablePluginDefinitionTrait
+ * @group Plugin
+ */
+class MergeablePluginDefinitionTraitTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\MergeablePluginDefinitionTrait|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForTrait('\Drupal\Component\Plugin\Definition\MergeablePluginDefinitionTrait');
+  }
+
+  /**
+   * @covers ::mergeDefaultDefinition
+   * @covers ::isDefinitionCompatible
+   * @covers ::doMergeDefaultDefinition
+   */
+  public function testMergeDefaultDefinition() {
+    $other_definition = $this->getMock('\Drupal\Component\Plugin\Definition\PluginDefinitionInterface');
+
+    $this->sut->expects($this->atLeastOnce())
+      ->method('isDefinitionCompatible')
+      ->willReturnCallback(function ($value) use ($other_definition) {
+        return $value == $other_definition;
+      });
+
+    $this->assertSame($this->sut, $this->sut->mergeDefaultDefinition($other_definition));
+  }
+
+  /**
+   * @covers ::mergeDefaultDefinition
+   * @covers ::isDefinitionCompatible
+   *
+   * @depends testMergeDefaultDefinition
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testMergeDefaultDefinitionWithInvalidOtherDefinition() {
+    $other_definition = $this->getMock('\Drupal\Component\Plugin\Definition\PluginDefinitionInterface');
+
+    $this->sut->expects($this->atLeastOnce())
+      ->method('isDefinitionCompatible')
+      ->willReturn(FALSE);
+
+    $this->sut->mergeDefaultDefinition($other_definition);
+  }
+
+  /**
+   * @covers ::mergeOverrideDefinition
+   * @covers ::isDefinitionCompatible
+   * @covers ::doMergeOverrideDefinition
+   */
+  public function testMergeOverrideDefinition() {
+    $other_definition = $this->getMock('\Drupal\Component\Plugin\Definition\PluginDefinitionInterface');
+
+    $this->sut->expects($this->atLeastOnce())
+      ->method('isDefinitionCompatible')
+      ->willReturnCallback(function ($value) use ($other_definition) {
+        return $value == $other_definition;
+      });
+
+    $this->assertSame($this->sut, $this->sut->mergeOverrideDefinition($other_definition));
+  }
+
+  /**
+   * @covers ::mergeOverrideDefinition
+   * @covers ::isDefinitionCompatible
+   *
+   * @depends testMergeOverrideDefinition
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testMergeOverrideDefinitionWithInvalidOtherDefinition() {
+    $other_definition = $this->getMock('\Drupal\Component\Plugin\Definition\PluginDefinitionInterface');
+
+    $this->sut->expects($this->atLeastOnce())
+      ->method('isDefinitionCompatible')
+      ->willReturn(FALSE);
+
+    $this->sut->mergeOverrideDefinition($other_definition);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginContextDefinitionTraitTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginContextDefinitionTraitTest.php
new file mode 100644
index 0000000..27eb654
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginContextDefinitionTraitTest.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\PluginContextDefinitionTraitTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\PluginContextDefinitionTrait
+ *
+ * @group Plugin
+ */
+class PluginContextDefinitionTraitTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\PluginContextDefinitionTrait
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForTrait('\Drupal\Component\Plugin\Definition\PluginContextDefinitionTrait');
+  }
+
+  /**
+   * @covers ::setContextDefinitions
+   * @covers ::getContextDefinitions
+   */
+  public function testGetContextDefinitions() {
+    $context_definition_name_a = $this->randomMachineName();
+    $context_definition_a = $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface');
+    $context_definition_name_b = $this->randomMachineName();
+    $context_definition_b = $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface');
+
+    $context_definitions = [
+      $context_definition_name_a => $context_definition_a,
+      $context_definition_name_b => $context_definition_b,
+    ];
+
+    $this->assertSame($this->sut, $this->sut->setContextDefinitions($context_definitions));
+    $this->assertSame($context_definitions, $this->sut->getContextDefinitions());
+  }
+
+  /**
+   * @covers ::setContextDefinitions
+   *
+   * @depends testGetContextDefinitions
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testSetContextDefinitionsWithInvalidDefinition() {
+    $context_definitions = [
+      $this->randomMachineName() => new \stdClass(),
+    ];
+
+    $this->sut->setContextDefinitions($context_definitions);
+  }
+
+  /**
+   * @covers ::setContextDefinition
+   * @covers ::getContextDefinition
+   * @covers ::hasContextDefinition
+   */
+  public function testGetContextDefinition() {
+    $name = $this->randomMachineName();
+    $context_definition = $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface');
+
+    $this->assertSame($this->sut, $this->sut->setContextDefinition($name, $context_definition));
+    $this->assertSame($context_definition, $this->sut->getContextDefinition($name));
+    $this->assertTrue($this->sut->hasContextDefinition($name));
+  }
+
+  /**
+   * @covers ::getContextDefinition
+   * @covers ::hasContextDefinition
+   *
+   * @depends testGetContextDefinition
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testGetContextDefinitionWithNonExistentDefinition() {
+    $name = $this->randomMachineName();
+
+    $this->assertFalse($this->sut->hasContextDefinition($name));
+    $this->sut->getContextDefinition($name);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDefinitionTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDefinitionTest.php
new file mode 100644
index 0000000..9c6f2ae
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDefinitionTest.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\PluginDefinitionTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\PluginDefinition
+ *
+ * @group Plugin
+ */
+class PluginDefinitionTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\PluginDefinition
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForAbstractClass('\Drupal\Component\Plugin\Definition\PluginDefinition');
+  }
+
+  /**
+   * @covers ::setId
+   * @covers ::getId
+   */
+  public function testGetId() {
+    $id = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setId($id));
+    $this->assertSame($id, $this->sut->getId());
+  }
+
+  /**
+   * @covers ::setClass
+   * @covers ::getClass
+   */
+  public function testGetClass() {
+    $class = get_class($this->getMock('\Drupal\Component\Plugin\PluginInspectionInterface'));
+
+    $this->assertSame($this->sut, $this->sut->setClass($class));
+    $this->assertSame($class, $this->sut->getClass());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDeriverDefinitionTraitTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDeriverDefinitionTraitTest.php
new file mode 100644
index 0000000..a051b15
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDeriverDefinitionTraitTest.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\PluginDeriverDefinitionTraitTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionTrait
+ *
+ * @group Plugin
+ */
+class PluginDeriverDefinitionTraitTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionTrait
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForTrait('\Drupal\Component\Plugin\Definition\PluginDeriverDefinitionTrait');
+  }
+
+
+  /**
+   * @covers ::setDeriverClass
+   * @covers ::getDeriverClass
+   */
+  public function testGetDeriverClass() {
+    $class = get_class($this->getMock('\Drupal\Component\Plugin\Derivative\DeriverInterface'));
+
+    $this->assertSame($this->sut, $this->sut->setDeriverClass($class));
+    $this->assertSame($class, $this->sut->getDeriverClass());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginLabelDefinitionTraitTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginLabelDefinitionTraitTest.php
new file mode 100644
index 0000000..4d6a5b1
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginLabelDefinitionTraitTest.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\PluginLabelDefinitionTraitTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\PluginLabelDefinitionTrait
+ *
+ * @group Plugin
+ */
+class PluginLabelDefinitionTraitTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\PluginLabelDefinitionTrait
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForTrait('\Drupal\Component\Plugin\Definition\PluginLabelDefinitionTrait');
+  }
+
+  /**
+   * @covers ::setLabel
+   * @covers ::getLabel
+   */
+  public function testGetLabel() {
+    $label = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setLabel($label));
+    $this->assertSame($label, $this->sut->getLabel());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
index c350b48..83c4ebc 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
@@ -212,23 +212,30 @@ public function testgetOriginalClassUnchanged() {
   }
 
   /**
+   * @covers ::getClass
    * @covers ::setClass
    * @covers ::getOriginalClass
    */
   public function testgetOriginalClassChanged() {
-    $class = $this->randomMachineName();
-    $entity_type = $this->setUpEntityType(array('class' => $class));
-    $entity_type->setClass($this->randomMachineName());
-    $this->assertEquals($class, $entity_type->getOriginalClass());
+    $original_class = get_class($this->getMock('\Drupal\Core\Entity\EntityInterface'));
+    $new_class = get_class($this->getMock('\Drupal\Core\Entity\EntityInterface'));
+    $entity_type = $this->setUpEntityType(array('class' => $original_class));
+    $this->assertEquals($original_class, $entity_type->getClass());
+    $entity_type->setClass($new_class);
+    $this->assertEquals($original_class, $entity_type->getOriginalClass());
+    $this->assertEquals($new_class, $entity_type->getClass());
   }
 
   /**
    * @covers ::id
+   * @covers ::setId
+   * @covers ::getId
    */
   public function testId() {
     $id = $this->randomMachineName(32);
     $entity_type = $this->setUpEntityType(array('id' => $id));
     $this->assertEquals($id, $entity_type->id());
+    $this->assertEquals($id, $entity_type->getId());
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Image/ImageTest.php b/core/tests/Drupal/Tests/Core/Image/ImageTest.php
index e81bb76..4dccae2 100644
--- a/core/tests/Drupal/Tests/Core/Image/ImageTest.php
+++ b/core/tests/Drupal/Tests/Core/Image/ImageTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Image\Image;
 use Drupal\Core\ImageToolkit\ImageToolkitInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -86,7 +87,7 @@ protected function getToolkitOperationMock($class_name, ImageToolkitInterface $t
     $logger = $this->getMock('Psr\Log\LoggerInterface');
     return $mock_builder
       ->setMethods(array('execute'))
-      ->setConstructorArgs(array(array(), '', array(), $toolkit, $logger))
+      ->setConstructorArgs(array(array(), '', new ArrayPluginDefinition(), $toolkit, $logger))
       ->getMock();
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php
index f7ee941..43e2f8b 100644
--- a/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php
@@ -6,6 +6,7 @@
 
 namespace Drupal\Tests\Core\Mail;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Core\Mail\MailManager;
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
@@ -56,22 +57,25 @@ class MailManagerTest extends UnitTestCase {
    *
    * @var array
    */
-  protected $definitions = array(
-    'php_mail' => array(
-      'id' => 'php_mail',
-      'class' => 'Drupal\Core\Mail\Plugin\Mail\PhpMail',
-    ),
-    'test_mail_collector' => array(
-      'id' => 'test_mail_collector',
-      'class' => 'Drupal\Core\Mail\Plugin\Mail\TestMailCollector',
-    ),
-  );
+  protected $definitions = [];
 
   /**
    * {@inheritdoc}
    */
   protected function setUp() {
     parent::setUp();
+
+    $this->definitions = [
+      'php_mail' => new ArrayPluginDefinition([
+        'id' => 'php_mail',
+        'class' => 'Drupal\Core\Mail\Plugin\Mail\PhpMail',
+      ]),
+      'test_mail_collector' => new ArrayPluginDefinition([
+        'id' => 'test_mail_collector',
+        'class' => 'Drupal\Core\Mail\Plugin\Mail\TestMailCollector',
+      ]),
+    ];
+
     // Prepare the default constructor arguments required by MailManager.
     $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
 
diff --git a/core/tests/Drupal/Tests/Core/Menu/ContextualLinkManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/ContextualLinkManagerTest.php
index 773da8e..74242c5 100644
--- a/core/tests/Drupal/Tests/Core/Menu/ContextualLinkManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/ContextualLinkManagerTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\RequestStack;
 
@@ -137,24 +138,24 @@ protected function setUp() {
    */
   public function testGetContextualLinkPluginsByGroup() {
     $definitions = array(
-      'test_plugin1' => array(
+      'test_plugin1' => new ArrayPluginDefinition([
         'id' => 'test_plugin1',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'group' => 'group1',
         'route_name' => 'test_route',
-      ),
-      'test_plugin2' => array(
+      ]),
+      'test_plugin2' => new ArrayPluginDefinition([
         'id' => 'test_plugin2',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'group' => 'group1',
         'route_name' => 'test_route2',
-      ),
-      'test_plugin3' => array(
+      ]),
+      'test_plugin3' => new ArrayPluginDefinition([
         'id' => 'test_plugin3',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'group' => 'group2',
         'route_name' => 'test_router3',
-      ),
+      ]),
     );
     $this->pluginDiscovery->expects($this->once())
       ->method('getDefinitions')
@@ -176,18 +177,18 @@ public function testGetContextualLinkPluginsByGroup() {
    */
   public function testGetContextualLinkPluginsByGroupWithCache() {
     $definitions = array(
-      'test_plugin1' => array(
+      'test_plugin1' => new ArrayPluginDefinition([
         'id' => 'test_plugin1',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'group' => 'group1',
         'route_name' => 'test_route',
-      ),
-      'test_plugin2' => array(
+      ]),
+      'test_plugin2' => new ArrayPluginDefinition([
         'id' => 'test_plugin2',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'group' => 'group1',
         'route_name' => 'test_route2',
-      ),
+      ]),
     );
 
     $this->cacheBackend->expects($this->once())
@@ -212,11 +213,11 @@ public function testGetContextualLinkPluginsByGroupWithCache() {
    * @expectedException \Drupal\Component\Plugin\Exception\PluginException
    */
   public function testProcessDefinitionWithoutRoute() {
-    $definition = array(
+    $definition = new ArrayPluginDefinition([
       'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
       'group' => 'example',
       'id' => 'test_plugin',
-    );
+    ]);
     $this->contextualLinkManager->processDefinition($definition, 'test_plugin');
   }
 
@@ -228,11 +229,11 @@ public function testProcessDefinitionWithoutRoute() {
    * @expectedException \Drupal\Component\Plugin\Exception\PluginException
    */
   public function testProcessDefinitionWithoutGroup() {
-    $definition = array(
+    $definition = new ArrayPluginDefinition([
       'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
       'route_name' => 'example',
       'id' => 'test_plugin',
-    );
+    ]);
     $this->contextualLinkManager->processDefinition($definition, 'test_plugin');
   }
 
@@ -243,7 +244,7 @@ public function testProcessDefinitionWithoutGroup() {
    */
   public function testGetContextualLinksArrayByGroup() {
     $definitions = array(
-      'test_plugin1' => array(
+      'test_plugin1' => new ArrayPluginDefinition([
         'id' => 'test_plugin1',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'title' => 'Plugin 1',
@@ -251,8 +252,8 @@ public function testGetContextualLinksArrayByGroup() {
         'group' => 'group1',
         'route_name' => 'test_route',
         'options' => array(),
-      ),
-      'test_plugin2' => array(
+      ]),
+      'test_plugin2' => new ArrayPluginDefinition([
         'id' => 'test_plugin2',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'title' => 'Plugin 2',
@@ -260,8 +261,8 @@ public function testGetContextualLinksArrayByGroup() {
         'group' => 'group1',
         'route_name' => 'test_route2',
         'options' => array('key' => 'value'),
-      ),
-      'test_plugin3' => array(
+      ]),
+      'test_plugin3' => new ArrayPluginDefinition([
         'id' => 'test_plugin3',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'title' => 'Plugin 3',
@@ -269,7 +270,7 @@ public function testGetContextualLinksArrayByGroup() {
         'group' => 'group2',
         'route_name' => 'test_router3',
         'options' => array(),
-      ),
+      ]),
     );
 
     $this->pluginDiscovery->expects($this->once())
@@ -323,7 +324,7 @@ public function testGetContextualLinksArrayByGroup() {
    */
   public function testGetContextualLinksArrayByGroupAccessCheck() {
     $definitions = array(
-      'test_plugin1' => array(
+      'test_plugin1' => new ArrayPluginDefinition([
         'id' => 'test_plugin1',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'title' => 'Plugin 1',
@@ -331,8 +332,8 @@ public function testGetContextualLinksArrayByGroupAccessCheck() {
         'group' => 'group1',
         'route_name' => 'test_route',
         'options' => array(),
-      ),
-      'test_plugin2' => array(
+      ]),
+      'test_plugin2' => new ArrayPluginDefinition([
         'id' => 'test_plugin2',
         'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
         'title' => 'Plugin 2',
@@ -340,7 +341,7 @@ public function testGetContextualLinksArrayByGroupAccessCheck() {
         'group' => 'group1',
         'route_name' => 'test_route2',
         'options' => array('key' => 'value'),
-      ),
+      ]),
     );
 
     $this->pluginDiscovery->expects($this->once())
@@ -387,7 +388,7 @@ public function testGetContextualLinksArrayByGroupAccessCheck() {
    * Tests the plugins alter hook.
    */
   public function testPluginDefinitionAlter() {
-    $definitions['test_plugin'] = array(
+    $definitions['test_plugin'] = new ArrayPluginDefinition([
       'id' => 'test_plugin',
       'class' => '\Drupal\Core\Menu\ContextualLinkDefault',
       'title' => 'Plugin',
@@ -395,7 +396,7 @@ public function testPluginDefinitionAlter() {
       'group' => 'group1',
       'route_name' => 'test_route',
       'options' => array('key' => 'value'),
-    );
+    ]);
 
     $this->pluginDiscovery->expects($this->once())
       ->method('getDefinitions')
diff --git a/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php
index c20a99b..d5f28b6 100644
--- a/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Menu\DefaultMenuLinkTreeManipulators;
 use Drupal\Core\Menu\MenuLinkTreeElement;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -95,14 +96,14 @@ protected function setUp() {
    */
   protected function mockTree() {
     $this->links = array(
-      1 => MenuLinkMock::create(array('id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '')),
-      2 => MenuLinkMock::create(array('id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => 'test.example1', 'route_parameters' => array('foo' => 'bar'))),
-      3 => MenuLinkMock::create(array('id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'route_parameters' => array('baz' => 'qux'))),
-      4 => MenuLinkMock::create(array('id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3')),
-      5 => MenuLinkMock::create(array('id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '')),
-      6 => MenuLinkMock::create(array('id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => '')),
-      7 => MenuLinkMock::create(array('id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => '')),
-      8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '')),
+      1 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => ''])),
+      2 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => 'test.example1', 'route_parameters' => ['foo' => 'bar']])),
+      3 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'route_parameters' => ['baz' => 'qux']])),
+      4 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3'])),
+      5 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => ''])),
+      6 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => ''])),
+      7 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => ''])),
+      8 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => ''])),
     );
     $this->originalTree = array();
     $this->originalTree[1] = new MenuLinkTreeElement($this->links[1], FALSE, 1, FALSE, array());
@@ -259,12 +260,12 @@ public function testFlatten() {
    */
   public function testCheckNodeAccess() {
     $links = array(
-      1 => MenuLinkMock::create(array('id' => 'node.1', 'route_name' => 'entity.node.canonical', 'title' => 'foo', 'parent' => '', 'route_parameters' => array('node' => 1))),
-      2 => MenuLinkMock::create(array('id' => 'node.2', 'route_name' => 'entity.node.canonical', 'title' => 'bar', 'parent' => '', 'route_parameters' => array('node' => 2))),
-      3 => MenuLinkMock::create(array('id' => 'node.3', 'route_name' => 'entity.node.canonical', 'title' => 'baz', 'parent' => 'node.2', 'route_parameters' => array('node' => 3))),
-      4 => MenuLinkMock::create(array('id' => 'node.4', 'route_name' => 'entity.node.canonical', 'title' => 'qux', 'parent' => 'node.3', 'route_parameters' => array('node' => 4))),
-      5 => MenuLinkMock::create(array('id' => 'test.1', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => '')),
-      6 => MenuLinkMock::create(array('id' => 'test.2', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => 'test.1')),
+      1 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'node.1', 'route_name' => 'entity.node.canonical', 'title' => 'foo', 'parent' => '', 'route_parameters' => ['node' => 1]])),
+      2 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'node.2', 'route_name' => 'entity.node.canonical', 'title' => 'bar', 'parent' => '', 'route_parameters' => ['node' => 2]])),
+      3 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'node.3', 'route_name' => 'entity.node.canonical', 'title' => 'baz', 'parent' => 'node.2', 'route_parameters' => ['node' => 3]])),
+      4 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'node.4', 'route_name' => 'entity.node.canonical', 'title' => 'qux', 'parent' => 'node.3', 'route_parameters' => ['node' => 4]])),
+      5 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.1', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => ''])),
+      6 => MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test.2', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => 'test.1'])),
     );
     $tree = array();
     $tree[1] = new MenuLinkTreeElement($links[1], FALSE, 1, FALSE, array());
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php
index 85731c7..c9f6170 100644
--- a/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalActionManagerTest.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Menu\LocalActionManager;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -185,14 +186,14 @@ public function getActionsForRouteProvider() {
     $data[] = array(
       'test_route',
       array(
-        'plugin_id_1' => array(
+        'plugin_id_1' => new ArrayPluginDefinition([
           'appears_on' => array(
             'test_route',
           ),
           'route_name' => 'test_route_2',
           'title' => 'Plugin ID 1',
           'weight' => 0,
-        ),
+        ]),
       ),
       array(
         'plugin_id_1' => array(
@@ -211,22 +212,22 @@ public function getActionsForRouteProvider() {
     $data[] = array(
       'test_route',
       array(
-        'plugin_id_1' => array(
+        'plugin_id_1' => new ArrayPluginDefinition([
           'appears_on' => array(
             'test_route',
           ),
           'route_name' => 'test_route_2',
           'title' => 'Plugin ID 1',
           'weight' => 0,
-        ),
-        'plugin_id_2' => array(
+        ]),
+        'plugin_id_2' => new ArrayPluginDefinition([
           'appears_on' => array(
             'test_route2',
           ),
           'route_name' => 'test_route_3',
           'title' => 'Plugin ID 2',
           'weight' => 0,
-        ),
+        ]),
       ),
       array(
         'plugin_id_1' => array(
@@ -246,22 +247,22 @@ public function getActionsForRouteProvider() {
     $data[] = array(
       'test_route',
       array(
-        'plugin_id_1' => array(
+        'plugin_id_1' => new ArrayPluginDefinition([
           'appears_on' => array(
             'test_route',
           ),
           'route_name' => 'test_route_2',
           'title' => 'Plugin ID 1',
           'weight' => 1,
-        ),
-        'plugin_id_2' => array(
+        ]),
+        'plugin_id_2' => new ArrayPluginDefinition([
           'appears_on' => array(
             'test_route',
           ),
           'route_name' => 'test_route_3',
           'title' => 'Plugin ID 2',
           'weight' => 0,
-        ),
+        ]),
       ),
       array(
         'plugin_id_1' => array(
@@ -291,7 +292,7 @@ public function getActionsForRouteProvider() {
     $data[] = array(
       'test_route',
       array(
-        'plugin_id_1' => array(
+        'plugin_id_1' => new ArrayPluginDefinition([
           'appears_on' => array(
             'test_route',
           ),
@@ -299,8 +300,8 @@ public function getActionsForRouteProvider() {
           'route_parameters' => array('test1'),
           'title' => 'Plugin ID 1',
           'weight' => 1,
-        ),
-        'plugin_id_2' => array(
+        ]),
+        'plugin_id_2' => new ArrayPluginDefinition([
           'appears_on' => array(
             'test_route',
           ),
@@ -308,7 +309,7 @@ public function getActionsForRouteProvider() {
           'route_parameters' => array('test2'),
           'title' => 'Plugin ID 2',
           'weight' => 0,
-        ),
+        ]),
       ),
       array(
         'plugin_id_1' => array(
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
index 92b2b54..664b21a 100644
--- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Menu\LocalTaskManager;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RequestStack;
@@ -265,45 +266,46 @@ protected function setupLocalTaskManager() {
   /**
    * Return some local tasks plugin definitions.
    *
-   * @return array
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[]
    *   An array of plugin definition keyed by plugin ID.
    */
   protected function getLocalTaskFixtures() {
+    /** @var \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[] $definitions */
     $definitions = array();
-    $definitions['menu_local_task_test_tasks_settings'] = array(
+    $definitions['menu_local_task_test_tasks_settings'] = new ArrayPluginDefinition([
       'route_name' => 'menu_local_task_test_tasks_settings',
       'title' => 'Settings',
       'base_route' => 'menu_local_task_test_tasks_view',
-    );
-    $definitions['menu_local_task_test_tasks_edit'] = array(
+    ]);
+    $definitions['menu_local_task_test_tasks_edit'] = new ArrayPluginDefinition([
       'route_name' => 'menu_local_task_test_tasks_edit',
       'title' => 'Settings',
       'base_route' => 'menu_local_task_test_tasks_view',
       'weight' => 20,
-    );
+    ]);
     // Make this ID different from the route name to catch code that
     // confuses them.
-    $definitions['menu_local_task_test_tasks_view.tab'] = array(
+    $definitions['menu_local_task_test_tasks_view.tab'] = new ArrayPluginDefinition([
       'route_name' => 'menu_local_task_test_tasks_view',
       'title' => 'Settings',
       'base_route' => 'menu_local_task_test_tasks_view',
-    );
+    ]);
 
-    $definitions['menu_local_task_test_tasks_view_child1'] = array(
+    $definitions['menu_local_task_test_tasks_view_child1'] = new ArrayPluginDefinition([
       'route_name' => 'menu_local_task_test_tasks_child1_page',
       'title' => 'Settings child #1',
       'parent_id' => 'menu_local_task_test_tasks_view.tab',
-    );
-    $definitions['menu_local_task_test_tasks_view_child2'] = array(
+    ]);
+    $definitions['menu_local_task_test_tasks_view_child2'] = new ArrayPluginDefinition([
       'route_name' => 'menu_local_task_test_tasks_child2_page',
       'title' => 'Settings child #2',
       'parent_id' => 'menu_local_task_test_tasks_view.tab',
       'base_route' => 'this_should_be_replaced',
-    );
+    ]);
     // Add the ID and defaults from the LocalTaskManager.
-    foreach ($definitions as $id => &$info) {
+    foreach ($definitions as $id => $info) {
       $info['id'] = $id;
-      $info += array(
+      $info->mergeDefaultArrayDefinition([
         'id' => '',
         'route_name' => '',
         'route_parameters' => array(),
@@ -313,7 +315,7 @@ protected function getLocalTaskFixtures() {
         'weight' => 0,
         'options' => array(),
         'class' => 'Drupal\Core\Menu\LocalTaskDefault',
-      );
+      ]);
     }
     return $definitions;
   }
@@ -380,8 +382,12 @@ protected function getLocalTasksCache() {
         ),
         'menu_local_task_test_tasks_view.tab' => array(
           // The manager will fill in the base_route before caching.
-          'menu_local_task_test_tasks_view_child1' => array('base_route' => 'menu_local_task_test_tasks_view') + $local_task_fixtures['menu_local_task_test_tasks_view_child1'],
-          'menu_local_task_test_tasks_view_child2' => array('base_route' => 'menu_local_task_test_tasks_view') + $local_task_fixtures['menu_local_task_test_tasks_view_child2'],
+          'menu_local_task_test_tasks_view_child1' => (new ArrayPluginDefinition([
+              'base_route' => 'menu_local_task_test_tasks_view',
+            ]))->mergeDefaultDefinition($local_task_fixtures['menu_local_task_test_tasks_view_child1']),
+          'menu_local_task_test_tasks_view_child2' => (new ArrayPluginDefinition([
+              'base_route' => 'menu_local_task_test_tasks_view',
+            ]))->mergeDefaultDefinition($local_task_fixtures['menu_local_task_test_tasks_view_child2']),
         ),
       ),
     );
diff --git a/core/tests/Drupal/Tests/Core/Menu/MenuActiveTrailTest.php b/core/tests/Drupal/Tests/Core/Menu/MenuActiveTrailTest.php
index 495d298..015a39e 100644
--- a/core/tests/Drupal/Tests/Core/Menu/MenuActiveTrailTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/MenuActiveTrailTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\Core\Menu;
 
 use Drupal\Core\Menu\MenuActiveTrail;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Routing\CurrentRouteMatch;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -108,8 +109,8 @@ public function provider() {
     $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $mock_route);
     $request->attributes->set('_raw_variables', new ParameterBag(array()));
 
-    $link_1 = MenuLinkMock::create(array('id' => 'baby_llama_link_1', 'route_name' => 'baby_llama', 'title' => 'Baby llama', 'parent' => 'mama_llama_link'));
-    $link_2 = MenuLinkMock::create(array('id' => 'baby_llama_link_2', 'route_name' => 'baby_llama', 'title' => 'Baby llama', 'parent' => 'papa_llama_link'));
+    $link_1 = MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'baby_llama_link_1', 'route_name' => 'baby_llama', 'title' => 'Baby llama', 'parent' => 'mama_llama_link']));
+    $link_2 = MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'baby_llama_link_2', 'route_name' => 'baby_llama', 'title' => 'Baby llama', 'parent' => 'papa_llama_link']));
 
     // @see \Drupal\Core\Menu\MenuLinkManagerInterface::getParentIds()
     $link_1_parent_ids = array('baby_llama_link_1', 'mama_llama_link', '');
diff --git a/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php b/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php
index ea3d9d4..cd32193 100644
--- a/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php
+++ b/core/tests/Drupal/Tests/Core/Menu/MenuLinkMock.php
@@ -9,6 +9,8 @@
 
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Menu\MenuLinkBase;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * Defines a mock implementation of a menu link used in tests only.
@@ -43,8 +45,8 @@ class MenuLinkMock extends MenuLinkBase {
   /**
    * Create an instance from a definition with at least id, title, route_name.
    */
-  public static function create($definition) {
-    return new static(array(), $definition['id'], $definition + static::$defaults);
+  public static function create(ArrayPluginDefinition $definition) {
+    return new static(array(), $definition['id'], $definition->mergeDefaultArrayDefinition(static::$defaults));
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeElementTest.php b/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeElementTest.php
index da1a318..1b1a4b4 100644
--- a/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeElementTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeElementTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\Core\Menu;
 
 use Drupal\Core\Menu\MenuLinkTreeElement;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -25,7 +26,7 @@ class MenuLinkTreeElementTest extends UnitTestCase {
    * @covers ::__construct
    */
   public function testConstruction() {
-    $link = MenuLinkMock::create(array('id' => 'test'));
+    $link = MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test']));
     $item = new MenuLinkTreeElement($link, FALSE, 3, FALSE, array());
     $this->assertSame($link, $item->link);
     $this->assertSame(FALSE, $item->hasChildren);
@@ -40,8 +41,8 @@ public function testConstruction() {
    * @covers ::count
    */
   public function testCount() {
-    $link_1 = MenuLinkMock::create(array('id' => 'test_1'));
-    $link_2 = MenuLinkMock::create(array('id' => 'test_2'));
+    $link_1 = MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test_1']));
+    $link_2 = MenuLinkMock::create(new ArrayPluginDefinition(['id' => 'test_2']));
     $child_item = new MenuLinkTreeElement($link_2, FALSE, 2, FALSE, array());
     $parent_item = new MenuLinkTreeElement($link_1, FALSE, 2, FALSE, array($child_item));
     $this->assertSame(1, $child_item->count());
diff --git a/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php b/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php
index db67ad7..9bff0c1 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php
@@ -11,6 +11,8 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -76,27 +78,27 @@ public function testGetGroupedDefinitions() {
    */
   public function testProcessDefinitionCategory() {
     // Existing category.
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'label' => 'some',
       'provider' => 'core',
       'category' => 'bag',
-    ];
+    ]);
     $this->pluginManager->processDefinition($definition, 'some');
     $this->assertSame($definition['category'], 'bag');
 
     // No category, provider without label.
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'label' => 'some',
       'provider' => 'core',
-    ];
+    ]);
     $this->pluginManager->processDefinition($definition, 'some');
     $this->assertSame($definition['category'], 'core');
 
     // No category, provider is module with label.
-    $definition = [
+    $definition = new ArrayPluginDefinition([
       'label' => 'some',
       'provider' => 'node',
-    ];
+    ]);
     $this->pluginManager->processDefinition($definition, 'some');
     $this->assertSame($definition['category'], 'Node');
   }
@@ -127,25 +129,25 @@ public function __construct(ModuleHandlerInterface $module_handler) {
    */
   public function getDefinitions() {
     return [
-      'cucumber' => [
+      'cucumber' => new ArrayPluginDefinition([
         'label' => 'cucumber',
         'category' => 'vegetables',
-      ],
-      'apple' => [
+      ]),
+      'apple' => new ArrayPluginDefinition([
         'label' => 'apple',
         'category' => 'fruits',
-      ],
-      'mango' => [
+      ]),
+      'mango' => new ArrayPluginDefinition([
         'label' => 'mango',
         'category' => 'fruits',
-      ],
+      ]),
     ];
   }
 
   /**
    * {@inheritdoc}
    */
-  public function processDefinition(&$definition, $plugin_id) {
+  public function processDefinition(PluginDefinitionInterface $definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
     $this->processDefinitionCategory($definition);
   }
diff --git a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
index 180fd88..448c3b4 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Plugin;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -37,13 +38,13 @@ class DefaultPluginManagerTest extends UnitTestCase {
    */
   protected function setUp() {
     $this->expectedDefinitions = array(
-      'apple' => array(
+      'apple' => new ArrayPluginDefinition([
         'id' => 'apple',
         'label' => 'Apple',
         'color' => 'green',
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Apple',
-      ),
-      'banana' => array(
+      ]),
+      'banana' => new ArrayPluginDefinition([
         'id' => 'banana',
         'label' => 'Banana',
         'color' => 'yellow',
@@ -51,7 +52,7 @@ protected function setUp() {
           'bread' => 'Banana bread',
         ),
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Banana',
-      ),
+      ]),
     );
 
     $this->namespaces = new \ArrayObject();
@@ -63,13 +64,13 @@ protected function setUp() {
    */
   public function testDefaultPluginManagerWithDisabledModule() {
     $definitions = $this->expectedDefinitions;
-    $definitions['cherry'] = array(
+    $definitions['cherry'] = new ArrayPluginDefinition([
       'id' => 'cherry',
       'label' => 'Cherry',
       'color' => 'red',
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry',
       'provider' => 'disabled_module',
-    );
+    ]);
 
     $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
 
@@ -88,13 +89,13 @@ public function testDefaultPluginManagerWithDisabledModule() {
    */
   public function testDefaultPluginManagerWithObjects() {
     $definitions = $this->expectedDefinitions;
-    $definitions['cherry'] = (object) array(
+    $definitions['cherry'] = new ArrayPluginDefinition([
       'id' => 'cherry',
       'label' => 'Cherry',
       'color' => 'red',
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry',
       'provider' => 'disabled_module',
-    );
+    ]);
 
     $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
 
@@ -261,13 +262,13 @@ public function testCreateInstanceWithInvalidInterfaces() {
       ->with('plugin_test')
       ->willReturn(TRUE);
 
-    $this->expectedDefinitions['kale'] = array(
+    $this->expectedDefinitions['kale'] = new ArrayPluginDefinition([
       'id' => 'kale',
       'label' => 'Kale',
       'color' => 'green',
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale',
       'provider' => 'plugin_test',
-    );
+    ]);
     $this->expectedDefinitions['apple']['provider'] = 'plugin_test';
     $this->expectedDefinitions['banana']['provider'] = 'plugin_test';
 
@@ -288,13 +289,13 @@ public function testGetDefinitionsWithoutRequiredInterface() {
       ->with('plugin_test')
       ->willReturn(FALSE);
 
-    $this->expectedDefinitions['kale'] = array(
+    $this->expectedDefinitions['kale'] = new ArrayPluginDefinition([
       'id' => 'kale',
       'label' => 'Kale',
       'color' => 'green',
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale',
       'provider' => 'plugin_test',
-    );
+    ]);
     $this->expectedDefinitions['apple']['provider'] = 'plugin_test';
     $this->expectedDefinitions['banana']['provider'] = 'plugin_test';
 
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Definition/ArrayPluginDefinitionTest.php b/core/tests/Drupal/Tests/Core/Plugin/Definition/ArrayPluginDefinitionTest.php
new file mode 100644
index 0000000..22af119
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Plugin/Definition/ArrayPluginDefinitionTest.php
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Plugin\Definition\ArrayPluginDefinitionTest.
+ */
+
+namespace Drupal\Tests\Core\Plugin\Definition;
+
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinition;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Plugin\Definition\ArrayPluginDefinition
+ *
+ * @group Plugin
+ */
+class ArrayPluginDefinitionTest extends UnitTestCase {
+
+  /**
+   * The array definition.
+   *
+   * @var mixed[]
+   */
+  protected $arrayDefinition = [];
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Core\Plugin\Definition\ArrayPluginDefinition
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->arrayDefinition += [
+      'category' => $this->randomMachineName(),
+      'provider' => $this->randomMachineName(),
+      'config_dependencies' => [
+        'module' => [$this->randomMachineName()],
+      ],
+    ];
+
+    $this->sut = new ArrayPluginDefinition($this->arrayDefinition);
+  }
+
+  /**
+   * @covers ::setCategory
+   * @covers ::getCategory
+   */
+  public function testGetCategory() {
+    // Test the injected value.
+    $this->assertSame($this->arrayDefinition['category'], $this->sut->getCategory());
+    $this->assertSame($this->arrayDefinition['category'], $this->sut->getArrayDefinition()['category']);
+
+    // Test changing the value.
+    $value = $this->randomMachineName();
+    $this->assertSame($this->sut, $this->sut->setCategory($value));
+    $this->assertSame($value, $this->sut->getCategory());
+    $this->assertSame($value, $this->sut->getArrayDefinition()['category']);
+
+    // Test unsetting the value.
+    unset($this->sut['category']);
+    $this->assertFalse(isset($this->sut['category']));
+    $this->assertNull($this->sut->getCategory());
+    $this->assertFalse(isset($this->sut->getArrayDefinition()['category']));
+  }
+
+  /**
+   * @covers ::setProvider
+   * @covers ::getProvider
+   */
+  public function testGetProvider() {
+    // Test the injected value.
+    $this->assertSame($this->arrayDefinition['provider'], $this->sut->getProvider());
+    $this->assertSame($this->arrayDefinition['provider'], $this->sut->getArrayDefinition()['provider']);
+
+    // Test changing the value.
+    $value = $this->randomMachineName();
+    $this->assertSame($this->sut, $this->sut->setProvider($value));
+    $this->assertSame($value, $this->sut->getProvider());
+    $this->assertSame($value, $this->sut->getArrayDefinition()['provider']);
+
+    // Test unsetting the value.
+    unset($this->sut['provider']);
+    $this->assertFalse(isset($this->sut['provider']));
+    $this->assertNull($this->sut->getProvider());
+    $this->assertFalse(isset($this->sut->getArrayDefinition()['provider']));
+  }
+
+  /**
+   * @covers ::setConfigDependencies
+   * @covers ::getConfigDependencies
+   */
+  public function testGetConfigDependencies() {
+    // Test the injected value.
+    $this->assertSame($this->arrayDefinition['config_dependencies'], $this->sut->getConfigDependencies());
+    $this->assertSame($this->arrayDefinition['config_dependencies'], $this->sut->getArrayDefinition()['config_dependencies']);
+
+    // Test changing the value.
+    $value = [
+      'module' => [$this->randomMachineName()],
+    ];
+    $this->assertSame($this->sut, $this->sut->setConfigDependencies($value));
+    $this->assertSame($value, $this->sut->getConfigDependencies());
+    $this->assertSame($value, $this->sut->getArrayDefinition()['config_dependencies']);
+
+    // Test unsetting the value.
+    unset($this->sut['config_dependencies']);
+    $this->assertFalse(isset($this->sut['config_dependencies']));
+    $this->assertSame([], $this->sut->getConfigDependencies());
+    $this->assertFalse(isset($this->sut->getArrayDefinition()['config_dependencies']));
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginCategoryDefinitionTraitTest.php b/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginCategoryDefinitionTraitTest.php
new file mode 100644
index 0000000..5b36803
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginCategoryDefinitionTraitTest.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Plugin\Definition\PluginCategoryDefinitionTraitTest.
+ */
+
+namespace Drupal\Tests\Core\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionTrait
+ *
+ * @group Plugin
+ */
+class PluginCategoryDefinitionTraitTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionTrait
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForTrait('\Drupal\Core\Plugin\Definition\PluginCategoryDefinitionTrait');
+  }
+
+  /**
+   * @covers ::setCategory
+   * @covers ::getCategory
+   */
+  public function testGetCategory() {
+    $category = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setCategory($category));
+    $this->assertSame($category, $this->sut->getCategory());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginConfigDependenciesDefinitionTraitTest.php b/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginConfigDependenciesDefinitionTraitTest.php
new file mode 100644
index 0000000..acbaf1f
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginConfigDependenciesDefinitionTraitTest.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Plugin\Definition\PluginConfigDependenciesDefinitionTraitTest.
+ */
+
+namespace Drupal\Tests\Core\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Plugin\Definition\PluginConfigDependenciesDefinitionTrait
+ *
+ * @group Plugin
+ */
+class PluginConfigDependenciesDefinitionTraitTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Core\Plugin\Definition\PluginConfigDependenciesDefinitionTrait
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForTrait('\Drupal\Core\Plugin\Definition\PluginConfigDependenciesDefinitionTrait');
+  }
+
+  /**
+   * @covers ::setConfigDependencies
+   * @covers ::getConfigDependencies
+   */
+  public function testGetConfigDependencies() {
+    $dependencies = [
+      'module' => [$this->randomMachineName()],
+    ];
+
+    $this->assertSame($this->sut, $this->sut->setConfigDependencies($dependencies));
+    $this->assertSame($dependencies, $this->sut->getConfigDependencies());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginDefinitionTest.php b/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginDefinitionTest.php
new file mode 100644
index 0000000..230a559
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginDefinitionTest.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Plugin\Definition\PluginDefinitionTest.
+ */
+
+namespace Drupal\Tests\Core\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Plugin\Definition\PluginDefinition
+ *
+ * @group Plugin
+ */
+class PluginDefinitionTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Core\Plugin\Definition\PluginDefinition
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForAbstractClass('\Drupal\Core\Plugin\Definition\PluginDefinition');
+  }
+
+  /**
+   * @covers ::setProvider
+   * @covers ::getProvider
+   */
+  public function testGetProvider() {
+    $provider = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setProvider($provider));
+    $this->assertSame($provider, $this->sut->getProvider());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecoratorTest.php b/core/tests/Drupal/Tests/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecoratorTest.php
index 89f5f2f..29ec538 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecoratorTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecoratorTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Plugin\Discovery;
 
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\Tests\UnitTestCase;
 
@@ -32,14 +33,14 @@ public function testGetDefinitions() {
     \Drupal::setContainer($example_container);
 
     $definitions = array();
-    $definitions['container_aware_discovery'] = array(
+    $definitions['container_aware_discovery'] = new ArrayPluginDefinition([
       'id' => 'container_aware_discovery',
       'deriver' => '\Drupal\Tests\Core\Plugin\Discovery\TestContainerDerivativeDiscovery',
-    );
-    $definitions['non_container_aware_discovery'] = array(
+    ]);
+    $definitions['non_container_aware_discovery'] = new ArrayPluginDefinition([
       'id' => 'non_container_aware_discovery',
       'deriver' => '\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscovery',
-    );
+    ]);
 
     $discovery_main = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface');
     $discovery_main->expects($this->any())
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php b/core/tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php
index 4cc05ad..08b66b3 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\Tests\Core\Plugin\Discovery;
 
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinition;
 use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
-use Drupal\Component\Plugin\Exception\InvalidDeriverException;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -39,10 +39,10 @@ protected function setUp() {
    */
   public function testGetDerivativeFetcher() {
     $definitions = array();
-    $definitions['non_container_aware_discovery'] = array(
+    $definitions['non_container_aware_discovery'] = new ArrayPluginDefinition([
       'id' => 'non_container_aware_discovery',
       'deriver' => '\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscovery',
-    );
+    ]);
 
     $this->discoveryMain->expects($this->any())
       ->method('getDefinitions')
@@ -53,111 +53,38 @@ public function testGetDerivativeFetcher() {
 
     // Ensure that both test derivatives got added.
     $this->assertEquals(2, count($definitions));
-    $this->assertEquals('non_container_aware_discovery', $definitions['non_container_aware_discovery:test_discovery_0']['id']);
+    $this->assertEquals('non_container_aware_discovery:test_discovery_0', $definitions['non_container_aware_discovery:test_discovery_0']['id']);
     $this->assertEquals('\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscovery', $definitions['non_container_aware_discovery:test_discovery_0']['deriver']);
 
-    $this->assertEquals('non_container_aware_discovery', $definitions['non_container_aware_discovery:test_discovery_1']['id']);
+    $this->assertEquals('non_container_aware_discovery:test_discovery_1', $definitions['non_container_aware_discovery:test_discovery_1']['id']);
     $this->assertEquals('\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscovery', $definitions['non_container_aware_discovery:test_discovery_1']['deriver']);
   }
 
   /**
-   * Tests the getDerivativeFetcher method with objects instead of arrays.
-   */
-  public function testGetDerivativeFetcherWithAnnotationObjects() {
-    $definitions = array();
-    $definitions['non_container_aware_discovery'] = (object) array(
-      'id' => 'non_container_aware_discovery',
-      'deriver' => '\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscoveryWithObject',
-    );
-
-    $this->discoveryMain->expects($this->any())
-      ->method('getDefinitions')
-      ->will($this->returnValue($definitions));
-
-    $discovery = new DerivativeDiscoveryDecorator($this->discoveryMain);
-    $definitions = $discovery->getDefinitions();
-
-    // Ensure that both test derivatives got added.
-    $this->assertEquals(2, count($definitions));
-    $this->assertInstanceOf('\stdClass', $definitions['non_container_aware_discovery:test_discovery_0']);
-    $this->assertEquals('non_container_aware_discovery', $definitions['non_container_aware_discovery:test_discovery_0']->id);
-    $this->assertEquals('\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscoveryWithObject', $definitions['non_container_aware_discovery:test_discovery_0']->deriver);
-
-    $this->assertInstanceOf('\stdClass', $definitions['non_container_aware_discovery:test_discovery_1']);
-    $this->assertEquals('non_container_aware_discovery', $definitions['non_container_aware_discovery:test_discovery_1']->id);
-    $this->assertEquals('\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscoveryWithObject', $definitions['non_container_aware_discovery:test_discovery_1']->deriver);
-  }
-
-  /**
-   * Tests the getDerivativeFetcher method with a non-existent class.
-   *
-   * @see \Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator::getDeriver().\
-   *
-   * @expectedException \Drupal\Component\Plugin\Exception\InvalidDeriverException
-   * @expectedExceptionMessage Plugin (non_existent_discovery) deriver "\Drupal\system\Tests\Plugin\NonExistentDeriver" does not exist.
-   */
-  public function testNonExistentDerivativeFetcher() {
-    $definitions = array();
-    // Do this with a class that doesn't exist.
-    $definitions['non_existent_discovery'] = array(
-      'id' => 'non_existent_discovery',
-      'deriver' => '\Drupal\system\Tests\Plugin\NonExistentDeriver',
-    );
-    $this->discoveryMain->expects($this->any())
-      ->method('getDefinitions')
-      ->will($this->returnValue($definitions));
-
-    $discovery = new DerivativeDiscoveryDecorator($this->discoveryMain);
-    $discovery->getDefinitions();
-  }
-
-  /**
-   * Tests the getDerivativeFetcher method with an invalid class.
-   *
-   * @see \Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator::getDeriver().\
-   *
-   * @expectedException \Drupal\Component\Plugin\Exception\InvalidDeriverException
-   * @expectedExceptionMessage Plugin (invalid_discovery) deriver "\Drupal\system\Tests\Plugin\DerivativeTest" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
-   */
-  public function testInvalidDerivativeFetcher() {
-    $definitions = array();
-    // Do this with a class that doesn't implement the interface.
-    $definitions['invalid_discovery'] = array(
-      'id' => 'invalid_discovery',
-      'deriver' => '\Drupal\system\Tests\Plugin\DerivativeTest',
-    );
-    $this->discoveryMain->expects($this->any())
-      ->method('getDefinitions')
-      ->will($this->returnValue($definitions));
-
-    $discovery = new DerivativeDiscoveryDecorator($this->discoveryMain);
-    $discovery->getDefinitions();
-  }
-
-  /**
    * Tests derivative definitions when a definition already exists.
    */
   public function testExistingDerivative() {
     $definitions = array();
-    $definitions['non_container_aware_discovery'] = array(
+    $definitions['non_container_aware_discovery'] = new ArrayPluginDefinition([
       'id' => 'non_container_aware_discovery',
       'deriver' => '\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscovery',
       'string' => 'string',
       'empty_string' => 'not_empty',
       'array' => array('one', 'two'),
-      'empty_array' => array('three'),
+      'empty_array' => array(),
       'null_value' => TRUE,
-    );
+    ]);
     // This will clash with a derivative id.
     // @see \Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscovery
-    $definitions['non_container_aware_discovery:test_discovery_1'] = array(
+    $definitions['non_container_aware_discovery:test_discovery_1'] = new ArrayPluginDefinition([
       'id' => 'non_container_aware_discovery:test_discovery_1',
       'string' => 'string',
       'empty_string' => '',
       'array' => array('one', 'two'),
       'empty_array' => array(),
       'null_value' => NULL,
-    );
+      'existing_derivative_value' => $this->randomMachineName(),
+    ]);
 
     $this->discoveryMain->expects($this->any())
       ->method('getDefinitions')
@@ -169,16 +96,17 @@ public function testExistingDerivative() {
     // If the definition was merged, there should only be two.
     $this->assertCount(2, $returned_definitions);
 
-    $expected = $definitions['non_container_aware_discovery'];
-    $expected['id'] = 'non_container_aware_discovery:test_discovery_1';
-    $this->assertArrayEquals($expected, $returned_definitions['non_container_aware_discovery:test_discovery_1']);
+    $expected = clone $definitions['non_container_aware_discovery:test_discovery_1'];
+    $expected['deriver'] = $definitions['non_container_aware_discovery']['deriver'];
+    $this->assertEquals($expected, $returned_definitions['non_container_aware_discovery:test_discovery_1']);
+    $this->assertSame('non_container_aware_discovery:test_discovery_1', $returned_definitions['non_container_aware_discovery:test_discovery_1']->getId());
   }
 
   /**
    * Tests a single definition when a derivative already exists.
    */
   public function testSingleExistingDerivative() {
-    $base_definition = array(
+    $base_definition = new ArrayPluginDefinition([
       'id' => 'non_container_aware_discovery',
       'deriver' => '\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscovery',
       'string' => 'string',
@@ -186,32 +114,28 @@ public function testSingleExistingDerivative() {
       'array' => array('one', 'two'),
       'empty_array' => array('three'),
       'null_value' => TRUE,
-    );
+    ]);
     // This will clash with a derivative id.
     // @see \Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscovery
-    $derivative_definition = array(
+    $derivative_definition = new ArrayPluginDefinition([
       'id' => 'non_container_aware_discovery:test_discovery_1',
       'string' => 'string',
       'empty_string' => '',
       'array' => array('one', 'two'),
       'empty_array' => array(),
       'null_value' => NULL,
-    );
+    ]);
 
-    $this->discoveryMain->expects($this->at(0))
+    $map = [
+      ['non_container_aware_discovery', TRUE , $base_definition],
+      ['non_container_aware_discovery:test_discovery_1', TRUE , $derivative_definition],
+    ];
+    $this->discoveryMain->expects($this->atLeastOnce())
       ->method('getDefinition')
-      ->with('non_container_aware_discovery:test_discovery_1')
-      ->will($this->returnValue($derivative_definition));
-    $this->discoveryMain->expects($this->at(1))
-      ->method('getDefinition')
-      ->with('non_container_aware_discovery')
-      ->will($this->returnValue($base_definition));
+      ->willReturnMap($map);
 
     $discovery = new DerivativeDiscoveryDecorator($this->discoveryMain);
-
-    $expected = $base_definition;
-    $expected['id'] = 'non_container_aware_discovery:test_discovery_1';
-    $this->assertArrayEquals($expected, $discovery->getDefinition('non_container_aware_discovery:test_discovery_1'));
+    $this->assertSame('non_container_aware_discovery:test_discovery_1', $discovery->getDefinition('non_container_aware_discovery:test_discovery_1')->getId());
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Discovery/TestDerivativeDiscovery.php b/core/tests/Drupal/Tests/Core/Plugin/Discovery/TestDerivativeDiscovery.php
index fd5e9c6..b85bd17 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/Discovery/TestDerivativeDiscovery.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/Discovery/TestDerivativeDiscovery.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Plugin\Discovery;
 
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Component\Plugin\Derivative\DeriverInterface;
 
 /**
@@ -17,7 +18,7 @@ class TestDerivativeDiscovery implements DeriverInterface {
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
+  public function getDerivativeDefinition($derivative_id, PluginDefinitionInterface $base_plugin_definition) {
     $definitions = $this->getDerivativeDefinitions($base_plugin_definition);
     return $definitions[$derivative_id];
   }
@@ -25,10 +26,10 @@ public function getDerivativeDefinition($derivative_id, $base_plugin_definition)
   /**
    * {@inheritdoc}
    */
-  public function getDerivativeDefinitions($base_plugin_definition) {
+  public function getDerivativeDefinitions(PluginDefinitionInterface $base_plugin_definition) {
     $plugins = array();
     for ($i = 0; $i < 2; $i++) {
-      $plugins['test_discovery_' . $i] = $base_plugin_definition;
+      $plugins['test_discovery_' . $i] = clone $base_plugin_definition;
     }
     return $plugins;
   }
