diff --git a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListBooleanItem.php b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListBooleanItem.php
index 558a103..7d446f0 100644
--- a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListBooleanItem.php
+++ b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListBooleanItem.php
@@ -39,13 +39,16 @@ class ListBooleanItem extends ListItemBase {
    * {@inheritdoc}
    */
   public static function schema(FieldDefinitionInterface $field_definition) {
-    return parent::schema($field_definition) + array(
-     'columns' => array(
-       'value' => array(
-         'type' => 'int',
-         'not null' => FALSE,
-       ),
-     ),
+    return array(
+      'columns' => array(
+        'value' => array(
+          'type' => 'int',
+          'not null' => FALSE,
+        ),
+      ),
+      'indexes' => array(
+        'value' => array('value'),
+      ),
     );
   }
 
@@ -60,4 +63,68 @@ public function getPropertyDefinitions() {
     return static::$propertyDefinitions;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, array &$form_state, $has_data) {
+    $allowed_values = $this->getFieldSetting('allowed_values');
+    $allowed_values_function = $this->getFieldSetting('allowed_values_function');
+
+    $values = $allowed_values;
+    $off_value = array_shift($values);
+    $on_value = array_shift($values);
+
+    $element['allowed_values'] = array(
+      '#type' => 'value',
+      '#description' => '',
+      '#value_callback' => 'options_field_settings_form_value_boolean_allowed_values',
+      '#access' => empty($allowed_values_function),
+    );
+    $element['allowed_values']['on'] = array(
+      '#type' => 'textfield',
+      '#title' => t('On value'),
+      '#default_value' => $on_value,
+      '#required' => FALSE,
+      '#description' => t('If left empty, "1" will be used.'),
+      // Change #parents to make sure the element is not saved into field
+      // settings.
+      '#parents' => array('on'),
+    );
+    $element['allowed_values']['off'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Off value'),
+      '#default_value' => $off_value,
+      '#required' => FALSE,
+      '#description' => t('If left empty, "0" will be used.'),
+      // Change #parents to make sure the element is not saved into field
+      // settings.
+      '#parents' => array('off'),
+    );
+
+    // Link the allowed value to the on / off elements to prepare for the rare
+    // case of an alter changing #parents.
+    $element['allowed_values']['#on_parents'] = &$element['allowed_values']['on']['#parents'];
+    $element['allowed_values']['#off_parents'] = &$element['allowed_values']['off']['#parents'];
+
+    // Provide additional information about how to format allowed_values
+    // of a boolean field for use by a single on/off checkbox widget. Since
+    // the widget might not have been selected yet, can be changed independently
+    // of this form, and can vary by form mode, we display this information
+    // regardless of current widget selection.
+    $description = '<p>' . t("For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the <strong>Allowed values</strong> section. Note that the checkbox will be labeled with the label of the 'on' value.") . '</p>';
+    $description .= '<p>' . t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())) . '</p>';
+
+    $element['allowed_values']['#description'] = $description;
+
+    $element['allowed_values_function'] = array(
+      '#type' => 'item',
+      '#title' => t('Allowed values list'),
+      '#markup' => t('The value of this field is being determined by the %function function and may not be changed.', array('%function' => $allowed_values_function)),
+      '#access' => !empty($allowed_values_function),
+      '#value' => $allowed_values_function,
+    );
+
+    return $element;
+  }
+
 }
diff --git a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListFloatItem.php b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListFloatItem.php
index f8d0adb..2d20fd6 100644
--- a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListFloatItem.php
+++ b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListFloatItem.php
@@ -39,13 +39,16 @@ class ListFloatItem extends ListItemBase {
    * {@inheritdoc}
    */
   public static function schema(FieldDefinitionInterface $field_definition) {
-    return parent::schema($field_definition) + array(
-     'columns' => array(
-       'value' => array(
-         'type' => 'float',
-         'not null' => FALSE,
-       ),
-     ),
+    return array(
+      'columns' => array(
+        'value' => array(
+          'type' => 'float',
+          'not null' => FALSE,
+        ),
+      ),
+      'indexes' => array(
+        'value' => array('value'),
+      ),
     );
   }
 
@@ -60,4 +63,133 @@ public function getPropertyDefinitions() {
     return static::$propertyDefinitions;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, array &$form_state, $has_data) {
+    $allowed_values = $this->getFieldSetting('allowed_values');
+    $allowed_values_function = $this->getFieldSetting('allowed_values_function');
+
+    $element['allowed_values'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Allowed values list'),
+      '#default_value' => $this->allowedValuesString($allowed_values),
+      '#rows' => 10,
+      '#element_validate' => array(array($this, 'validateAllowedValues')),
+      '#field_has_data' => $has_data,
+      '#access' => empty($allowed_values_function),
+    );
+
+    $description = '<p>' . t('The possible values this field can contain. Enter one value per line, in the format key|label.');
+    $description .= '<br/>' . t('The key is the stored value, and must be numeric. The label will be used in displayed values and edit forms.');
+    $description .= '<br/>' . t('The label is optional: if a line contains a single number, it will be used as key and label.');
+    $description .= '<br/>' . t('Lists of labels are also accepted (one label per line), only if the field does not hold any values yet. Numeric keys will be automatically generated from the positions in the list.');
+    $description .= '</p>';
+    $description .= '<p>' . t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())) . '</p>';
+    $element['allowed_values']['#description'] = $description;
+
+    $element['allowed_values_function'] = array(
+      '#type' => 'item',
+      '#title' => t('Allowed values list'),
+      '#markup' => t('The value of this field is being determined by the %function function and may not be changed.', array('%function' => $allowed_values_function)),
+      '#access' => !empty($allowed_values_function),
+      '#value' => $allowed_values_function,
+    );
+
+    return $element;
+  }
+
+  /**
+   * Element validate callback; check that the entered values are valid.
+   */
+  public function validateAllowedValues($element, &$form_state) {
+    $values = $this->extractAllowedValues($element);
+
+    if (!is_array($values)) {
+      \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: invalid input.'));
+    }
+    else {
+      // Check that keys are valid for the field type.
+      foreach ($values as $key => $value) {
+        if (!is_numeric($key)) {
+          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: each key must be a valid integer or decimal.'));
+          break;
+        }
+      }
+
+      // Prevent removing values currently in use.
+      if ($element['#field_has_data']) {
+        $lost_keys = array_diff(array_keys($this->getFieldSetting('allowed_values')), array_keys($values));
+        if (_options_values_in_use($this->getEntity()->entityTypeId(), $this->getFieldDefinition()->getName(), $lost_keys)) {
+          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: some values are being removed while currently in use.'));
+        }
+      }
+
+      \Drupal::formBuilder()->setValue($element, $values, $form_state);
+    }
+  }
+
+  /**
+   * Extracts the allowed values array from the allowed_values element.
+   *
+   * @param array $element
+   *   The form element to extract values from.
+   *
+   * @return array
+   *   The array of extracted key/value pairs, or NULL if the string is invalid.
+   *
+   * @see \Drupal\options\Plugin\Field\FieldType\ListFloatItem::allowedValuesString()
+   */
+  protected function extractAllowedValues($element) {
+    $values = array();
+
+    $list = explode("\n", $element['#value']);
+    $list = array_map('trim', $list);
+    $list = array_filter($list, 'strlen');
+
+    $generated_keys = $explicit_keys = FALSE;
+    foreach ($list as $position => $text) {
+      $value = $key = FALSE;
+
+      // Check for an explicit key.
+      $matches = array();
+      if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
+        // Trim key and value to avoid unwanted spaces issues.
+        $key = trim($matches[1]);
+        $value = trim($matches[2]);
+        $explicit_keys = TRUE;
+      }
+      // Otherwise see if we can use the value as the key. Detecting true
+      // integer strings takes a little trick.
+      elseif (is_numeric($text)) {
+        $key = $value = $text;
+        $explicit_keys = TRUE;
+      }
+      // Otherwise see if we can generate a key from the position.
+      elseif (!$element['#field_has_data']) {
+        $key = (string) $position;
+        $value = $text;
+        $generated_keys = TRUE;
+      }
+      else {
+        return;
+      }
+
+      // Float keys are represented as strings and need to be disambiguated
+      // ('.5' is '0.5').
+      if (is_numeric($key)) {
+        $key = (string) (float) $key;
+      }
+
+      $values[$key] = $value;
+    }
+
+    // We generate keys only if the list contains no explicit key at all.
+    if ($explicit_keys && $generated_keys) {
+      return;
+    }
+
+    return $values;
+  }
+
 }
diff --git a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListIntegerItem.php b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListIntegerItem.php
index 9c6e7d7..67f5a1a 100644
--- a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListIntegerItem.php
+++ b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListIntegerItem.php
@@ -39,13 +39,16 @@ class ListIntegerItem extends ListItemBase {
    * {@inheritdoc}
    */
   public static function schema(FieldDefinitionInterface $field_definition) {
-    return parent::schema($field_definition) + array(
-     'columns' => array(
-       'value' => array(
-         'type' => 'int',
-         'not null' => FALSE,
-       ),
-     ),
+    return array(
+      'columns' => array(
+        'value' => array(
+          'type' => 'int',
+          'not null' => FALSE,
+        ),
+      ),
+      'indexes' => array(
+        'value' => array('value'),
+      ),
     );
   }
 
@@ -60,4 +63,127 @@ public function getPropertyDefinitions() {
     return static::$propertyDefinitions;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, array &$form_state, $has_data) {
+    $allowed_values = $this->getFieldSetting('allowed_values');
+    $allowed_values_function = $this->getFieldSetting('allowed_values_function');
+
+    $element['allowed_values'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Allowed values list'),
+      '#default_value' => $this->allowedValuesString($allowed_values),
+      '#rows' => 10,
+      '#element_validate' => array(array($this, 'validateAllowedValues')),
+      '#field_has_data' => $has_data,
+      '#access' => empty($allowed_values_function),
+    );
+
+    $description = '<p>' . t('The possible values this field can contain. Enter one value per line, in the format key|label.');
+    $description .= '<br/>' . t('The key is the stored value, and must be numeric. The label will be used in displayed values and edit forms.');
+    $description .= '<br/>' . t('The label is optional: if a line contains a single number, it will be used as key and label.');
+    $description .= '<br/>' . t('Lists of labels are also accepted (one label per line), only if the field does not hold any values yet. Numeric keys will be automatically generated from the positions in the list.');
+    $description .= '</p>';
+    $description .= '<p>' . t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())) . '</p>';
+    $element['allowed_values']['#description'] = $description;
+
+    $element['allowed_values_function'] = array(
+      '#type' => 'item',
+      '#title' => t('Allowed values list'),
+      '#markup' => t('The value of this field is being determined by the %function function and may not be changed.', array('%function' => $allowed_values_function)),
+      '#access' => !empty($allowed_values_function),
+      '#value' => $allowed_values_function,
+    );
+
+    return $element;
+  }
+
+  /**
+   * Element validate callback; check that the entered values are valid.
+   */
+  public function validateAllowedValues($element, &$form_state) {
+    $values = $this->extractAllowedValues($element);
+
+    if (!is_array($values)) {
+      \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: invalid input.'));
+    }
+    else {
+      // Check that keys are valid for the field type.
+      foreach ($values as $key => $value) {
+        if (!preg_match('/^-?\d+$/', $key)) {
+          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: keys must be integers.'));
+          break;
+        }
+      }
+
+      // Prevent removing values currently in use.
+      if ($element['#field_has_data']) {
+        $lost_keys = array_diff(array_keys($this->getFieldSetting('allowed_values')), array_keys($values));
+        if (_options_values_in_use($this->getEntity()->entityTypeId(), $this->getFieldDefinition()->getName(), $lost_keys)) {
+          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: some values are being removed while currently in use.'));
+        }
+      }
+
+      \Drupal::formBuilder()->setValue($element, $values, $form_state);
+    }
+  }
+
+  /**
+   * Extracts the allowed values array from the allowed_values element.
+   *
+   * @param array $element
+   *   The form element to extract values from.
+   *
+   * @return array
+   *   The array of extracted key/value pairs, or NULL if the string is invalid.
+   *
+   * @see \Drupal\options\Plugin\Field\FieldType\ListIntegerItem::allowedValuesString()
+   */
+  protected function extractAllowedValues($element) {
+    $values = array();
+
+    $list = explode("\n", $element['#value']);
+    $list = array_map('trim', $list);
+    $list = array_filter($list, 'strlen');
+
+    $generated_keys = $explicit_keys = FALSE;
+    foreach ($list as $position => $text) {
+      $value = $key = FALSE;
+
+      // Check for an explicit key.
+      $matches = array();
+      if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
+        // Trim key and value to avoid unwanted spaces issues.
+        $key = trim($matches[1]);
+        $value = trim($matches[2]);
+        $explicit_keys = TRUE;
+      }
+      // Otherwise see if we can use the value as the key. Detecting true
+      // integer strings takes a little trick.
+      elseif (is_numeric($text) && (float) $text == intval($text)) {
+        $key = $value = $text;
+        $explicit_keys = TRUE;
+      }
+      // Otherwise see if we can generate a key from the position.
+      elseif (!$element['#field_has_data']) {
+        $key = (string) $position;
+        $value = $text;
+        $generated_keys = TRUE;
+      }
+      else {
+        return;
+      }
+
+      $values[$key] = $value;
+    }
+
+    // We generate keys only if the list contains no explicit key at all.
+    if ($explicit_keys && $generated_keys) {
+      return;
+    }
+
+    return $values;
+  }
+
 }
diff --git a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListItemBase.php b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListItemBase.php
index bd89bb6..eeef991 100644
--- a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListItemBase.php
+++ b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListItemBase.php
@@ -53,110 +53,6 @@ public function getSettableOptions(AccountInterface $account = NULL) {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  public static function schema(FieldDefinitionInterface $field_definition) {
-    return array(
-     'indexes' => array(
-       'value' => array('value'),
-     ),
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function settingsForm(array $form, array &$form_state, $has_data) {
-    // @todo Move type-specific logic to the type-specific subclass:
-    //   https://drupal.org/node/2169983.
-    $field_type = $this->getFieldDefinition()->getType();
-
-    $allowed_values = $this->getFieldSetting('allowed_values');
-    $allowed_values_function = $this->getFieldSetting('allowed_values_function');
-
-    if (in_array($field_type, array('list_integer', 'list_float', 'list_text'))) {
-      $element['allowed_values'] = array(
-        '#type' => 'textarea',
-        '#title' => t('Allowed values list'),
-        '#default_value' => $this->allowedValuesString($allowed_values),
-        '#rows' => 10,
-        '#element_validate' => array(array($this, 'validateAllowedValues')),
-        '#field_has_data' => $has_data,
-        '#access' => empty($allowed_values_function),
-      );
-
-      $description = '<p>' . t('The possible values this field can contain. Enter one value per line, in the format key|label.');
-      if ($field_type == 'list_integer' || $field_type == 'list_float') {
-        $description .= '<br/>' . t('The key is the stored value, and must be numeric. The label will be used in displayed values and edit forms.');
-        $description .= '<br/>' . t('The label is optional: if a line contains a single number, it will be used as key and label.');
-        $description .= '<br/>' . t('Lists of labels are also accepted (one label per line), only if the field does not hold any values yet. Numeric keys will be automatically generated from the positions in the list.');
-      }
-      else {
-        $description .= '<br/>' . t('The key is the stored value. The label will be used in displayed values and edit forms.');
-        $description .= '<br/>' . t('The label is optional: if a line contains a single string, it will be used as key and label.');
-      }
-      $description .= '</p>';
-      $element['allowed_values']['#description'] = $description;
-    }
-    elseif ($field_type == 'list_boolean') {
-      $values = $allowed_values;
-      $off_value = array_shift($values);
-      $on_value = array_shift($values);
-
-      $element['allowed_values'] = array(
-        '#type' => 'value',
-        '#description' => '',
-        '#value_callback' => 'options_field_settings_form_value_boolean_allowed_values',
-        '#access' => empty($allowed_values_function),
-      );
-      $element['allowed_values']['on'] = array(
-        '#type' => 'textfield',
-        '#title' => t('On value'),
-        '#default_value' => $on_value,
-        '#required' => FALSE,
-        '#description' => t('If left empty, "1" will be used.'),
-        // Change #parents to make sure the element is not saved into field
-        // settings.
-        '#parents' => array('on'),
-      );
-      $element['allowed_values']['off'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Off value'),
-        '#default_value' => $off_value,
-        '#required' => FALSE,
-        '#description' => t('If left empty, "0" will be used.'),
-        // Change #parents to make sure the element is not saved into field
-        // settings.
-        '#parents' => array('off'),
-      );
-
-      // Link the allowed value to the on / off elements to prepare for the rare
-      // case of an alter changing #parents.
-      $element['allowed_values']['#on_parents'] = &$element['allowed_values']['on']['#parents'];
-      $element['allowed_values']['#off_parents'] = &$element['allowed_values']['off']['#parents'];
-
-      // Provide additional information about how to format allowed_values
-      // of a boolean field for use by a single on/off checkbox widget. Since
-      // the widget might not have been selected yet, can be changed independently
-      // of this form, and can vary by form mode, we display this information
-      // regardless of current widget selection.
-      $element['allowed_values']['#description'] .= '<p>' . t("For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the <strong>Allowed values</strong> section. Note that the checkbox will be labeled with the label of the 'on' value.") . '</p>';
-    }
-
-    $element['allowed_values']['#description'] .= '<p>' . t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())) . '</p>';
-
-    $element['allowed_values_function'] = array(
-      '#type' => 'item',
-      '#title' => t('Allowed values list'),
-      '#markup' => t('The value of this field is being determined by the %function function and may not be changed.', array('%function' => $allowed_values_function)),
-      '#access' => !empty($allowed_values_function),
-      '#value' => $allowed_values_function,
-    );
-
-    return $element;
-  }
-
-  /**
    * Generates a string representation of an array of 'allowed values'.
    *
    * This string format is suitable for edition in a textarea.
@@ -179,126 +75,6 @@ protected function allowedValuesString($values) {
   }
 
   /**
-   * Element validate callback; check that the entered values are valid.
-   */
-  public function validateAllowedValues($element, &$form_state) {
-    // @todo Move type-specific logic to the type-specific subclass:
-    //   https://drupal.org/node/2169983.
-    $field_type = $this->getFieldDefinition()->getType();
-
-    $has_data = $element['#field_has_data'];
-    $generate_keys = ($field_type == 'list_integer' || $field_type == 'list_float') && !$has_data;
-
-    $values = $this->extractAllowedValues($element['#value'], $generate_keys);
-
-    if (!is_array($values)) {
-      \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: invalid input.'));
-    }
-    else {
-      // Check that keys are valid for the field type.
-      foreach ($values as $key => $value) {
-        if ($field_type == 'list_integer' && !preg_match('/^-?\d+$/', $key)) {
-          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: keys must be integers.'));
-          break;
-        }
-        if ($field_type == 'list_float' && !is_numeric($key)) {
-          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: each key must be a valid integer or decimal.'));
-          break;
-        }
-        elseif ($field_type == 'list_text' && drupal_strlen($key) > 255) {
-          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: each key must be a string at most 255 characters long.'));
-          break;
-        }
-      }
-
-      // Prevent removing values currently in use.
-      if ($has_data) {
-        $lost_keys = array_diff(array_keys($this->getFieldSetting('allowed_values')), array_keys($values));
-        if (_options_values_in_use($this->getEntity()->getEntityTypeId(), $this->getFieldDefinition()->getName(), $lost_keys)) {
-          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: some values are being removed while currently in use.'));
-        }
-      }
-
-      form_set_value($element, $values, $form_state);
-    }
-  }
-
-  /**
-   * Parses a string of 'allowed values' into an array.
-   *
-   * @param string $string
-   *   The list of allowed values in string format described in
-   *   \Drupal\options\Plugin\Field\FieldType\ListItemBase::allowedValuesString().
-   * @param bool $generate_keys
-   *   Boolean value indicating whether to generate keys based on the position
-   *   of the value if a key is not manually specified, and if the value cannot
-   *   be used as a key. This should only be TRUE for fields of type
-   *   'list_number'.
-   *
-   * @return array
-   *   The array of extracted key/value pairs, or NULL if the string is invalid.
-   *
-   * @see \Drupal\options\Plugin\Field\FieldType\ListItemBase::allowedValuesString()
-   */
-  protected function extractAllowedValues($string, $generate_keys) {
-    // @todo Move type-specific logic to the type-specific subclass:
-    //   https://drupal.org/node/2169983.
-    $field_type = $this->getFieldDefinition()->getType();
-
-    $values = array();
-
-    $list = explode("\n", $string);
-    $list = array_map('trim', $list);
-    $list = array_filter($list, 'strlen');
-
-    $generated_keys = $explicit_keys = FALSE;
-    foreach ($list as $position => $text) {
-      $value = $key = FALSE;
-
-      // Check for an explicit key.
-      $matches = array();
-      if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
-        // Trim key and value to avoid unwanted spaces issues.
-        $key = trim($matches[1]);
-        $value = trim($matches[2]);
-        $explicit_keys = TRUE;
-      }
-      // Otherwise see if we can use the value as the key. Detecting true integer
-      // strings takes a little trick.
-      elseif ($field_type == 'list_text'
-      || ($field_type == 'list_float' && is_numeric($text))
-      || ($field_type == 'list_integer' && is_numeric($text) && (float) $text == intval($text))) {
-        $key = $value = $text;
-        $explicit_keys = TRUE;
-      }
-      // Otherwise see if we can generate a key from the position.
-      elseif ($generate_keys) {
-        $key = (string) $position;
-        $value = $text;
-        $generated_keys = TRUE;
-      }
-      else {
-        return;
-      }
-
-      // Float keys are represented as strings and need to be disambiguated
-      // ('.5' is '0.5').
-      if ($field_type == 'list_float' && is_numeric($key)) {
-        $key = (string) (float) $key;
-      }
-
-      $values[$key] = $value;
-    }
-
-    // We generate keys only if the list contains no explicit key at all.
-    if ($explicit_keys && $generated_keys) {
-      return;
-    }
-
-    return $values;
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function isEmpty() {
diff --git a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListTextItem.php b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListTextItem.php
index 1e27eac..642f15e 100644
--- a/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListTextItem.php
+++ b/core/modules/options/lib/Drupal/options/Plugin/Field/FieldType/ListTextItem.php
@@ -39,14 +39,17 @@ class ListTextItem extends ListItemBase {
    * {@inheritdoc}
    */
   public static function schema(FieldDefinitionInterface $field_definition) {
-    return parent::schema($field_definition) + array(
-     'columns' => array(
-       'value' => array(
-         'type' => 'varchar',
-         'length' => 255,
-         'not null' => FALSE,
-       ),
-     ),
+    return array(
+      'columns' => array(
+        'value' => array(
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => FALSE,
+        ),
+      ),
+      'indexes' => array(
+        'value' => array('value'),
+      ),
     );
   }
 
@@ -59,7 +62,121 @@ public function getPropertyDefinitions() {
       static::$propertyDefinitions['value'] = DataDefinition::create('string')
         ->setLabel(t('Text value'))
         ->setConstraints($constraints);
-   }
+    }
     return static::$propertyDefinitions;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, array &$form_state, $has_data) {
+    $allowed_values = $this->getFieldSetting('allowed_values');
+    $allowed_values_function = $this->getFieldSetting('allowed_values_function');
+
+    $element['allowed_values'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Allowed values list'),
+      '#default_value' => $this->allowedValuesString($allowed_values),
+      '#rows' => 10,
+      '#element_validate' => array(array($this, 'validateAllowedValues')),
+      '#field_has_data' => $has_data,
+      '#access' => empty($allowed_values_function),
+    );
+
+    $description = '<p>' . t('The possible values this field can contain. Enter one value per line, in the format key|label.');
+    $description .= '<br/>' . t('The key is the stored value. The label will be used in displayed values and edit forms.');
+    $description .= '<br/>' . t('The label is optional: if a line contains a single string, it will be used as key and label.');
+    $description .= '</p>';
+    $description .= '<p>' . t('Allowed HTML tags in labels: @tags', array('@tags' => _field_filter_xss_display_allowed_tags())) . '</p>';
+    $element['allowed_values']['#description'] = $description;
+
+    $element['allowed_values_function'] = array(
+      '#type' => 'item',
+      '#title' => t('Allowed values list'),
+      '#markup' => t('The value of this field is being determined by the %function function and may not be changed.', array('%function' => $allowed_values_function)),
+      '#access' => !empty($allowed_values_function),
+      '#value' => $allowed_values_function,
+    );
+
+    return $element;
+  }
+
+  /**
+   * Element validate callback; check that the entered values are valid.
+   */
+  public function validateAllowedValues($element, &$form_state) {
+    $values = $this->extractAllowedValues($element);
+
+    if (!is_array($values)) {
+      \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: invalid input.'));
+    }
+    else {
+      // Check that keys are valid for the field type.
+      foreach ($values as $key => $value) {
+        if (drupal_strlen($key) > 255) {
+          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: each key must be a string at most 255 characters long.'));
+          break;
+        }
+      }
+
+      // Prevent removing values currently in use.
+      if ($element['#field_has_data']) {
+        $lost_keys = array_diff(array_keys($this->getFieldSetting('allowed_values')), array_keys($values));
+        if (_options_values_in_use($this->getEntity()->entityTypeId(), $this->getFieldDefinition()->getName(), $lost_keys)) {
+          \Drupal::formBuilder()->setError($element, $form_state, t('Allowed values list: some values are being removed while currently in use.'));
+        }
+      }
+
+      \Drupal::formBuilder()->setValue($element, $values, $form_state);
+    }
+  }
+
+  /**
+   * Extracts the allowed values array from the allowed_values element.
+   *
+   * @param array $element
+   *   The form element to extract values from.
+   *
+   * @return array
+   *   The array of extracted key/value pairs, or NULL if the string is invalid.
+   *
+   * @see \Drupal\options\Plugin\Field\FieldType\ListTextItem::allowedValuesString()
+   */
+  protected function extractAllowedValues($element) {
+    $values = array();
+
+    $list = explode("\n", $element['#value']);
+    $list = array_map('trim', $list);
+    $list = array_filter($list, 'strlen');
+
+    $generated_keys = $explicit_keys = FALSE;
+    foreach ($list as $position => $text) {
+      $value = $key = FALSE;
+
+      // Check for an explicit key.
+      $matches = array();
+      if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
+        // Trim key and value to avoid unwanted spaces issues.
+        $key = trim($matches[1]);
+        $value = trim($matches[2]);
+        $explicit_keys = TRUE;
+      }
+      // Otherwise see if we can use the value as the key. Detecting true
+      // integer strings takes a little trick.
+      else {
+        $key = $value = $text;
+        $explicit_keys = TRUE;
+      }
+
+      $values[$key] = $value;
+    }
+
+    // We generate keys only if the list contains no explicit key at all.
+    if ($explicit_keys && $generated_keys) {
+      return;
+    }
+
+    return $values;
+  }
+
 }
