Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.432
diff -u -p -r1.432 form.inc
--- includes/form.inc	12 Feb 2010 15:11:29 -0000	1.432
+++ includes/form.inc	19 Feb 2010 11:12:43 -0000
@@ -1964,6 +1964,30 @@ function theme_fieldset($variables) {
 }
 
 /**
+ * Initialize #checked on radio and checkbox elements.
+ */
+function form_pre_render_checked($element) {
+  // An empty value (NULL, FALSE, integer 0, and empty string) always means
+  // unchecked, except string '0', which we do not want to treat as empty in
+  // this context.
+  if (empty($element['#value']) && $element['#value'] !== '0') {
+    $element['#checked'] = FALSE;
+  }
+  // A value of TRUE always means checked.
+  elseif ($element['#value'] === TRUE) {
+    $element['#checked'] = TRUE;
+  }
+  // For all other values, checked is determined by comparing the string
+  // representations of #value and #return_value. We compare string
+  // representations, since that's what gets output to the HTML 'value'
+  // attribute.
+  else {
+    $element['#checked'] = ((string) $element['#value'] == (string) $element['#return_value']);
+  }
+  return $element;
+}
+
+/**
  * Theme a radio button form element.
  *
  * @param $variables
@@ -1983,8 +2007,10 @@ function theme_radio($variables) {
   $output = '<input type="radio" ';
   $output .= 'id="' . $element['#id'] . '" ';
   $output .= 'name="' . $element['#name'] . '" ';
-  $output .= 'value="' . $element['#return_value'] . '" ';
-  $output .= (check_plain($element['#value']) == $element['#return_value']) ? ' checked="checked" ' : ' ';
+  $output .= 'value="' . check_plain($element['#return_value']) . '" ';
+  if ($element['#checked']) {
+    $output .= 'checked="checked" ';
+  }
   $output .= drupal_attributes($element['#attributes']) . ' />';
 
   return $output;
@@ -2190,7 +2216,9 @@ function form_process_radios($element) {
         $element[$key] = array(
           '#type' => 'radio',
           '#title' => $choice,
-          '#return_value' => check_plain($key),
+          // $key might be integer 0, but that's not a valid #return_value, so
+          // cast to string.
+          '#return_value' => (string)$key,
           '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
           '#attributes' => $element['#attributes'],
           '#parents' => $element['#parents'],
@@ -2329,9 +2357,8 @@ function theme_checkbox($variables) {
   $checkbox .= 'type="checkbox" ';
   $checkbox .= 'name="' . $element['#name'] . '" ';
   $checkbox .= 'id="' . $element['#id'] . '" ' ;
-  $checkbox .= 'value="' . $element['#return_value'] . '" ';
-  // Unchecked checkbox has #value of numeric 0.
-  if ($element['#value'] !== 0 && $element['#value'] == $element['#return_value']) {
+  $checkbox .= 'value="' . check_plain($element['#return_value']) . '" ';
+  if ($element['#checked']) {
     $checkbox .= 'checked="checked" ';
   }
   $checkbox .= drupal_attributes($element['#attributes']) . ' />';
@@ -2398,8 +2425,10 @@ function form_process_checkboxes($elemen
           '#type' => 'checkbox',
           '#processed' => TRUE,
           '#title' => $choice,
-          '#return_value' => $key,
-          '#default_value' => isset($value[$key]) ? $key : NULL,
+          // $key might be integer 0, but that's not a valid #return_value, so
+          // cast to string.
+          '#return_value' => (string)$key,
+          '#default_value' => isset($value[$key]) ? (string)$key : NULL,
           '#attributes' => $element['#attributes'],
           '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
         );
Index: modules/field/modules/options/options.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/options/options.module,v
retrieving revision 1.25
diff -u -p -r1.25 options.module
--- modules/field/modules/options/options.module	11 Feb 2010 15:42:14 -0000	1.25
+++ modules/field/modules/options/options.module	19 Feb 2010 11:12:44 -0000
@@ -148,7 +148,6 @@ function options_field_widget_validate($
  */
 function _options_properties($type, $multiple, $required) {
   $base = array(
-    'zero_placeholder' => FALSE,
     'filter_xss' => FALSE,
     'strip_tags' => FALSE,
     'empty_value' => FALSE,
@@ -170,9 +169,6 @@ function _options_properties($type, $mul
     case 'buttons':
       $properties = array(
         'filter_xss' => TRUE,
-        // Form API 'checkboxes' do not suport 0 as an option, so we replace it with
-        // a placeholder within the form workflow.
-        'zero_placeholder' => $multiple,
         // Checkboxes do not need a 'none' choice.
         'empty_value' => !$required && !$multiple,
       );
@@ -215,18 +211,6 @@ function _options_get_options($field, $i
  * The function is recursive to support optgroups.
  */
 function _options_prepare_options(&$options, $properties) {
-  // Substitute the '_0' placeholder.
-  if ($properties['zero_placeholder']) {
-    $values = array_keys($options);
-    $labels = array_values($options);
-    // Use a strict comparison, because 0 == 'any string'.
-    $index = array_search(0, $values, TRUE);
-    if ($index !== FALSE && !is_array($options[$index])) {
-      $values[$index] = '_0';
-      $options = array_combine($values, $labels);
-    }
-  }
-
   foreach ($options as $value => $label) {
     // Recurse for optgroups.
     if (is_array($label)) {
@@ -250,14 +234,6 @@ function _options_storage_to_form($items
   $items_transposed = options_array_transpose($items);
   $values = (isset($items_transposed[$column]) && is_array($items_transposed[$column])) ? $items_transposed[$column] : array();
 
-  // Substitute the '_0' placeholder.
-  if ($properties['zero_placeholder']) {
-    $index = array_search('0', $values);
-    if ($index !== FALSE) {
-      $values[$index] = '_0';
-    }
-  }
-
   // Discard values that are not in the current list of options. Flatten the
   // array if needed.
   if ($properties['optgroups']) {
@@ -279,14 +255,6 @@ function _options_form_to_storage($eleme
     $values = array($values[0] ? $element['#on_value'] : $element['#off_value']);
   }
 
-  // Substitute the '_0' placeholder.
-  if ($properties['zero_placeholder']) {
-    $index = array_search('_0', $values);
-    if ($index !== FALSE) {
-      $values[$index] = 0;
-    }
-  }
-
   // Filter out the 'none' option. Use a strict comparison, because
   // 0 == 'any string'.
   if ($properties['empty_value']) {
Index: modules/field/modules/options/options.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/options/options.test,v
retrieving revision 1.10
diff -u -p -r1.10 options.test
--- modules/field/modules/options/options.test	14 Dec 2009 20:18:55 -0000	1.10
+++ modules/field/modules/options/options.test	19 Feb 2010 11:12:44 -0000
@@ -135,14 +135,14 @@ class OptionsWidgetsTestCase extends Fie
 
     // Display form: with no field data, nothing is checked.
     $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
-    $this->assertNoFieldChecked("edit-card-2-$langcode--0");
+    $this->assertNoFieldChecked("edit-card-2-$langcode-0");
     $this->assertNoFieldChecked("edit-card-2-$langcode-1");
     $this->assertNoFieldChecked("edit-card-2-$langcode-2");
     $this->assertRaw('Some dangerous &amp; unescaped <strong>markup</strong>', t('Option text was properly filtered.'));
 
     // Submit form: select first and third options.
     $edit = array(
-      "card_2[$langcode][_0]" => TRUE,
+      "card_2[$langcode][0]" => TRUE,
       "card_2[$langcode][1]" => FALSE,
       "card_2[$langcode][2]" => TRUE,
     );
@@ -151,13 +151,13 @@ class OptionsWidgetsTestCase extends Fie
 
     // Display form: check that the right options are selected.
     $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
-    $this->assertFieldChecked("edit-card-2-$langcode--0");
+    $this->assertFieldChecked("edit-card-2-$langcode-0");
     $this->assertNoFieldChecked("edit-card-2-$langcode-1");
     $this->assertFieldChecked("edit-card-2-$langcode-2");
 
     // Submit form: select only first option.
     $edit = array(
-      "card_2[$langcode][_0]" => TRUE,
+      "card_2[$langcode][0]" => TRUE,
       "card_2[$langcode][1]" => FALSE,
       "card_2[$langcode][2]" => FALSE,
     );
@@ -166,13 +166,13 @@ class OptionsWidgetsTestCase extends Fie
 
     // Display form: check that the right options are selected.
     $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
-    $this->assertFieldChecked("edit-card-2-$langcode--0");
+    $this->assertFieldChecked("edit-card-2-$langcode-0");
     $this->assertNoFieldChecked("edit-card-2-$langcode-1");
     $this->assertNoFieldChecked("edit-card-2-$langcode-2");
 
     // Submit form: select the three options while the field accepts only 2.
     $edit = array(
-      "card_2[$langcode][_0]" => TRUE,
+      "card_2[$langcode][0]" => TRUE,
       "card_2[$langcode][1]" => TRUE,
       "card_2[$langcode][2]" => TRUE,
     );
@@ -181,7 +181,7 @@ class OptionsWidgetsTestCase extends Fie
 
     // Submit form: uncheck all options.
     $edit = array(
-      "card_2[$langcode][_0]" => FALSE,
+      "card_2[$langcode][0]" => FALSE,
       "card_2[$langcode][1]" => FALSE,
       "card_2[$langcode][2]" => FALSE,
     );
Index: modules/simpletest/tests/form.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v
retrieving revision 1.37
diff -u -p -r1.37 form.test
--- modules/simpletest/tests/form.test	12 Feb 2010 15:11:29 -0000	1.37
+++ modules/simpletest/tests/form.test	19 Feb 2010 11:12:45 -0000
@@ -544,7 +544,7 @@ class FormsFormStorageTestCase extends D
     $this->drupalGet('form_test/form-storage');
     $this->assertText('Form constructions: 1');
 
-    $edit = array('title' => 'new', 'value' => 'value_is_set');
+    $edit = array('title' => 'new', 'value' => 'value_is_set', 'checkbox1' => TRUE);
     // Reload the form, but don't rebuild.
     $this->drupalPost(NULL, $edit, 'Reload');
     $this->assertText('Form constructions: 2');
@@ -553,11 +553,15 @@ class FormsFormStorageTestCase extends D
     $this->drupalPost(NULL, $edit, 'Continue submit');
     $this->assertText('Form constructions: 3');
     $this->assertText('Form constructions: 4');
+    $this->assertFieldChecked('edit-checkbox1', t('Checkbox checked state persisted.'));
+    $this->assertNoFieldChecked('edit-checkbox2', t('Checkbox checked state persisted.'));
 
     // Reset the form to the values of the storage, using a form rebuild
     // triggered by button of type button.
-    $this->drupalPost(NULL, array('title' => 'changed'), 'Reset');
-    $this->assertFieldByName('title', 'new', 'Values have been resetted.');
+    $this->drupalPost(NULL, array('title' => 'changed', 'checkbox1' => FALSE, 'checkbox2' => TRUE), 'Reset');
+    $this->assertFieldByName('title', 'new', 'Values have been reset.');
+    $this->assertFieldChecked('edit-checkbox1', t('Checkbox checked state was reset.'));
+    $this->assertNoFieldChecked('edit-checkbox2', t('Checkbox checked state was reset.'));
     // After rebuilding, the form has been cached.
     $this->assertText('Form constructions: 5');
 
@@ -573,7 +577,7 @@ class FormsFormStorageTestCase extends D
     $this->drupalGet('form_test/form-storage', array('query' => array('cache' => 1)));
     $this->assertText('Form constructions: 1');
 
-    $edit = array('title' => 'new', 'value' => 'value_is_set');
+    $edit = array('title' => 'new', 'value' => 'value_is_set', 'checkbox1' => TRUE);
     // Reload the form, but don't rebuild.
     $this->drupalPost(NULL, $edit, 'Reload');
     $this->assertNoText('Form constructions');
@@ -581,11 +585,15 @@ class FormsFormStorageTestCase extends D
     // Now use form rebuilding triggered by a submit button.
     $this->drupalPost(NULL, $edit, 'Continue submit');
     $this->assertText('Form constructions: 2');
+    $this->assertFieldChecked('edit-checkbox1', t('Checkbox checked state persisted.'));
+    $this->assertNoFieldChecked('edit-checkbox2', t('Checkbox checked state persisted.'));
 
     // Reset the form to the values of the storage, using a form rebuild
     // triggered by button of type button.
-    $this->drupalPost(NULL, array('title' => 'changed'), 'Reset');
-    $this->assertFieldByName('title', 'new', 'Values have been resetted.');
+    $this->drupalPost(NULL, array('title' => 'changed', 'checkbox1' => FALSE, 'checkbox2' => TRUE), 'Reset');
+    $this->assertFieldByName('title', 'new', 'Values have been reset.');
+    $this->assertFieldChecked('edit-checkbox1', t('Checkbox checked state was reset.'));
+    $this->assertNoFieldChecked('edit-checkbox2', t('Checkbox checked state was reset.'));
     $this->assertText('Form constructions: 3');
 
     $this->drupalPost(NULL, $edit, 'Save');
Index: modules/simpletest/tests/form_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v
retrieving revision 1.28
diff -u -p -r1.28 form_test.module
--- modules/simpletest/tests/form_test.module	8 Feb 2010 22:16:11 -0000	1.28
+++ modules/simpletest/tests/form_test.module	19 Feb 2010 11:12:45 -0000
@@ -392,6 +392,8 @@ function form_test_storage_form($form, &
       'thing' => array(
         'title' => 'none',
         'value' => '',
+        'checkbox1' => 0,
+        'checkbox2' => 0,
       ),
     );
   }
@@ -411,6 +413,18 @@ function form_test_storage_form($form, &
     '#default_value' => $form_state['storage']['thing']['value'],
     '#element_validate' => array('form_test_storage_element_validate_value_cached'),
   );
+
+  $form['checkbox1'] = array(
+    '#type' => 'checkbox',
+    '#title' => 'One',
+    '#default_value' => $form_state['storage']['thing']['checkbox1'],
+  );
+  $form['checkbox2'] = array(
+    '#type' => 'checkbox',
+    '#title' => 'Two',
+    '#default_value' => $form_state['storage']['thing']['checkbox2'],
+  );
+
   $form['button'] = array(
     '#type' => 'button',
     '#value' => 'Reload',
@@ -465,6 +479,8 @@ function form_test_storage_element_valid
 function form_storage_test_form_continue_submit($form, &$form_state) {
   $form_state['storage']['thing']['title'] = $form_state['values']['title'];
   $form_state['storage']['thing']['value'] = $form_state['values']['value'];
+  $form_state['storage']['thing']['checkbox1'] = $form_state['values']['checkbox1'];
+  $form_state['storage']['thing']['checkbox2'] = $form_state['values']['checkbox2'];
   $form_state['rebuild'] = TRUE;
 }
 
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.890
diff -u -p -r1.890 system.module
--- modules/system/system.module	17 Feb 2010 09:09:30 -0000	1.890
+++ modules/system/system.module	19 Feb 2010 11:12:46 -0000
@@ -388,6 +388,7 @@ function system_element_info() {
     '#process' => array('ajax_process_form'),
     '#theme' => 'radio',
     '#theme_wrappers' => array('form_element'),
+    '#pre_render' => array('form_pre_render_checked'),
     '#title_display' => 'after',
   );
   $types['checkboxes'] = array(
@@ -403,6 +404,7 @@ function system_element_info() {
     '#process' => array('ajax_process_form'),
     '#theme' => 'checkbox',
     '#theme_wrappers' => array('form_element'),
+    '#pre_render' => array('form_pre_render_checked'),
     '#title_display' => 'after',
   );
   $types['select'] = array(
