Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.511
diff -u -p -r1.511 form.inc
--- includes/form.inc	17 Nov 2010 04:10:51 -0000	1.511
+++ includes/form.inc	27 Nov 2010 16:58:21 -0000
@@ -88,6 +88,11 @@
  * the form system and each other.
  *
  * The $form_state keys are:
+ * - build_info: Do not change; internal information stored by Form API to be
+ *   able to build and rebuild the form:
+ *   - args: A list of arguments used to rebuild the form from cache.
+ *   - files: A list of include files to be loaded to rebuild the form. See
+ *     form_load_include().
  * - 'values': An associative array of values submitted to the form. The
  *   validation functions and submit functions use this array for nearly all
  *   their decision making. (Note that
@@ -163,7 +168,7 @@
  *   Any additional arguments are passed on to the functions called by
  *   drupal_get_form(), including the unique form constructor function. For
  *   example, the node_edit form requires that a node object is passed in here
- *   when it is called. These are available to implementations of 
+ *   when it is called. These are available to implementations of
  *   hook_form_alter() and hook_form_FORM_ID_alter() as the array
  *   $form_state['build_info']['args'].
  *
@@ -213,8 +218,8 @@ function drupal_get_form($form_id) {
  *       for building the form. Each array entry may be the path to a file or
  *       another array containing values for the parameters 'type', 'module' and
  *       'name' as needed by module_load_include(). The files listed here are
- *       automatically loaded by form_get_cache(). Defaults to the current menu
- *       router item's 'file' definition, if existent.
+ *       automatically loaded by form_get_cache(). By default the current menu
+ *       router item's 'file' definition is added, if existent.
  *   - rebuild: Normally, after the entire form processing is completed and
  *     submit handlers ran, a form is considered to be done and
  *     drupal_redirect_form() will redirect the user to a new page using a GET
@@ -363,7 +368,12 @@ function form_state_defaults() {
     'rebuild' => FALSE,
     'rebuild_info' => array(),
     'redirect' => NULL,
-    'build_info' => array('args' => array()),
+    // @todo 'args' is usually set, so no other default 'build_info' keys are
+    //   appended via += form_state_defaults().
+    'build_info' => array(
+      'args' => array(),
+      'files' => array(),
+    ),
     'temporary' => array(),
     'submitted' => FALSE,
     'executed' => FALSE,
@@ -470,7 +480,7 @@ function form_get_cache($form_build_id, 
         $form_state = $cached->data + $form_state;
 
         // If the original form is contained in include files, load the files.
-        // See drupal_build_form().
+        // @see form_load_include()
         $form_state['build_info'] += array('files' => array());
         foreach ($form_state['build_info']['files'] as $file) {
           if (is_array($file)) {
@@ -538,6 +548,55 @@ function form_state_keys_no_cache() {
 }
 
 /**
+ * Loads an include file and makes sure it is loaded whenever the form is processed.
+ *
+ * Example:
+ * @code
+ *   // Load node.admin.inc from Node module.
+ *   form_load_include($form_state, 'inc', 'node', 'node.admin');
+ * @endcode
+ *
+ * Use this function instead of module_load_include() from inside a form
+ * constructor or any form processing logic as it ensures that the include file
+ * is loaded whenever the form is processed. In contrast to using
+ * module_load_include() directly, form_load_include() makes sure the include
+ * file is correctly loaded also if the form is cached.
+ *
+ * @param $form_state
+ *   The current state of the form.
+ * @param $type
+ *   The include file's type (file extension).
+ * @param $module
+ *   The module to which the include file belongs.
+ * @param $name
+ *   (optional) The base file name (without the $type extension). If omitted,
+ *   $module is used; i.e., resulting in "$module.$type" by default.
+ *
+ * @return
+ *   The filepath of the loaded include file, or FALSE if the include file was
+ *   not found or has been loaded already.
+ *
+ * @see module_load_include()
+ */
+function form_load_include(&$form_state, $type, $module, $name = NULL) {
+  if (!isset($name)) {
+    $name = $module;
+  }
+  if (!isset($form_state['build_info']['files']["$module:$name.$type"])) {
+    // Only add successfully included files to the form state.
+    if ($result = module_load_include($type, $module, $name)) {
+      $form_state['build_info']['files']["$module:$name.$type"] = array(
+        'type' => $type,
+        'module' => $module,
+        'name' => $name,
+      );
+      return $result;
+    }
+  }
+  return FALSE;
+}
+
+/**
  * Retrieves, populates, and processes a form.
  *
  * This function allows you to supply values for form elements and submit a
@@ -641,6 +700,8 @@ function drupal_retrieve_form($form_id, 
   if (!isset($form_state['build_info']['files']['menu']) && !defined('MAINTENANCE_MODE')) {
     $item = menu_get_item();
     if (!empty($item['include_file'])) {
+      // Do not use form_load_include() here, as the file is already loaded.
+      // Anyway, form_get_cache() is able to handle filepaths too.
       $form_state['build_info']['files']['menu'] = $item['include_file'];
     }
   }
Index: includes/module.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/module.inc,v
retrieving revision 1.207
diff -u -p -r1.207 module.inc
--- includes/module.inc	24 Nov 2010 16:47:44 -0000	1.207
+++ includes/module.inc	27 Nov 2010 16:55:48 -0000
@@ -283,14 +283,14 @@ function module_load_install($module) {
  * @param $module
  *   The module to which the include file belongs.
  * @param $name
- *   Optionally, specify the base file name (without the $type extension).
- *   If not set, $module is used.
+ *   (optional) The base file name (without the $type extension). If omitted,
+ *   $module is used; i.e., resulting in "$module.$type" by default.
  *
  * @return
  *   The name of the included file, if successful; FALSE otherwise.
  */
 function module_load_include($type, $module, $name = NULL) {
-  if (empty($name)) {
+  if (!isset($name)) {
     $name = $module;
   }
 
Index: modules/simpletest/tests/form_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v
retrieving revision 1.55
diff -u -p -r1.55 form_test.module
--- modules/simpletest/tests/form_test.module	15 Nov 2010 10:06:32 -0000	1.55
+++ modules/simpletest/tests/form_test.module	27 Nov 2010 16:53:58 -0000
@@ -1506,7 +1506,8 @@ function form_test_load_include_custom($
   // Specify the include file and enable form caching. That way the form is
   // cached when it is submitted, but needs to find the specified submit handler
   // in the include.
-  $form_state['build_info']['files'][] = array('module' => 'form_test', 'name' => 'form_test.file');
+  // Filename is a bit weird here: modules/simpletest/tests/form_test.file.inc
+  form_load_include($form_state, 'inc', 'form_test', 'form_test.file');
   $form_state['cache'] = TRUE;
   return $form;
 }
