Repository: facebookresearch/hydra

Issue:
Somewhat confusing error if `defaults` is not a list
If you modify `demos/6_sweep/conf/config.yaml` from a list:

```yaml
defaults:
  - hydra/launcher: fairtask
  - optimizer: nesterov
```

to a dict like this, which I accidentally did when creating a new config from scratch:

```yaml
defaults:
  hydra/launcher: fairtask
  optimizer: nesterov
```

then you'll get this error message, which is pretty unintuitive to debug/know what's causing it. Could you easily check and see if `defaults` is a list before passing it down to this point and print out an error for this?

```python
Traceback (most recent call last):
  File "experiment.py", line 15, in <module>
    experiment()
  File "/private/home/bda/repos/hydra/hydra/main.py", line 62, in decorated_main
    run_hydra(get_args(), task_function, config_path, strict)
  File "/private/home/bda/repos/hydra/hydra/_internal/utils.py", line 55, in run_hydra
    hydra.run(overrides=args.overrides)
  File "/private/home/bda/repos/hydra/hydra/_internal/hydra.py", line 88, in run
    cfg = self._load_config(overrides)
  File "/private/home/bda/repos/hydra/hydra/_internal/hydra.py", line 120, in _load_config
    cfg = self.config_loader.load_configuration(overrides)
  File "/private/home/bda/repos/hydra/hydra/_internal/config_loader.py", line 46, in load_configuration
    cfg = self._merge_configs(hydra_cfg, job_cfg)
  File "/private/home/bda/repos/hydra/hydra/_internal/config_loader.py", line 214, in _merge_configs
    cfg = OmegaConf.merge(hydra_cfg, job_cfg)
  File "/private/home/bda/anaconda3/lib/python3.7/site-packages/omegaconf-1.3.0-py3.7.egg/omegaconf/omegaconf.py", line 137, in merge
    target.merge_with(*others[1:])
  File "/private/home/bda/anaconda3/lib/python3.7/site-packages/omegaconf-1.3.0-py3.7.egg/omegaconf/config.py", line 392, in merge_with
    Config._map_merge(self, other)
  File "/private/home/bda/anaconda3/lib/python3.7/site-packages/omegaconf-1.3.0-py3.7.egg/omegaconf/config.py", line 339, in _map_merge
    dest_node.merge_with(value)
  File "/private/home/bda/anaconda3/lib/python3.7/site-packages/omegaconf-1.3.0-py3.7.egg/omegaconf/config.py", line 396, in merge_with
    raise TypeError("Merging DictConfig with ListConfig is not supported")
TypeError: Merging DictConfig with ListConfig is not supported
```


Discussion:
yup, that's wrong.
It supposed to have a better error, it must have broken in my recent changes.
are you running on the latest code?
Yes, should be running the latest master

Resolving patch:
diff --git a/hydra/_internal/config_loader.py b/hydra/_internal/config_loader.py
--- a/hydra/_internal/config_loader.py
+++ b/hydra/_internal/config_loader.py
@@ -370,10 +370,12 @@ def _create_cfg(self, cfg_filename):
                         cfg_filename, "\n\t".join(self.full_search_path)
                     )
                 )
+        if cfg.defaults is not None:
+            self._validate_defaults(cfg.defaults)
         return cfg
 
     @staticmethod
-    def _validate_config(cfg):
+    def _validate_defaults(defaults):
         valid_example = """
         Example of a valid defaults:
         defaults:
@@ -382,26 +384,33 @@ def _validate_config(cfg):
             optional: true
           - optimizer: nesterov
         """
-        assert isinstance(cfg.all_defaults, DictConfig)
-        for _, defaults in cfg.all_defaults.items():
-            assert isinstance(defaults, ListConfig), (
-                "defaults must be a list because composition is order sensitive : "
+        if not isinstance(defaults, ListConfig):
+            raise ValueError(
+                "defaults must be a list because composition is order sensitive, "
                 + valid_example
             )
-            for default in defaults:
-                assert isinstance(default, DictConfig) or isinstance(default, str)
-                if isinstance(default, DictConfig):
-                    assert len(default) in (1, 2)
-                    if len(default) == 2:
-                        assert default.optional is not None and isinstance(
-                            default.optional, bool
-                        )
-                    else:
-                        # optional can't be the only config key in a default
-                        assert default.optional is None, "Missing config key"
-                elif isinstance(default, str):
-                    # single file to load
-                    pass
+
+        for default in defaults:
+            assert isinstance(default, DictConfig) or isinstance(default, str)
+            if isinstance(default, DictConfig):
+                assert len(default) in (1, 2)
+                if len(default) == 2:
+                    assert default.optional is not None and isinstance(
+                        default.optional, bool
+                    )
+                else:
+                    # optional can't be the only config key in a default
+                    assert default.optional is None, "Missing config key"
+            elif isinstance(default, str):
+                # single file to load
+                pass
+
+    @staticmethod
+    def _validate_config(cfg):
+
+        assert isinstance(cfg.all_defaults, DictConfig)
+        for _, defaults in cfg.all_defaults.items():
+            ConfigLoader._validate_defaults(defaults)
 
     @staticmethod
     def _update_defaults(cfg, defaults_changes):


Test patch:
diff --git a/tests/configs/defaults_not_list.yaml b/tests/configs/defaults_not_list.yaml
new file mode 100644
--- /dev/null
+++ b/tests/configs/defaults_not_list.yaml
@@ -0,0 +1,3 @@
+defaults:
+  all: wrong
+  should_be: list
\ No newline at end of file
diff --git a/tests/test_conf_loader.py b/tests/test_conf_loader.py
--- a/tests/test_conf_loader.py
+++ b/tests/test_conf_loader.py
@@ -239,3 +239,14 @@ def test_default_removal(config_file, overrides):
         ("pkg://hydra.conf.hydra.job_logging/default.yaml", True),
         ("pkg://hydra.conf.hydra.sweeper/basic.yaml", True),
     ]
+
+
+def test_defaults_not_list_exception():
+    config_loader = ConfigLoader(
+        job_search_path=["tests/configs"],
+        hydra_search_path=["pkg://hydra.conf"],
+        strict_cfg=False,
+        config_file="defaults_not_list.yaml",
+    )
+    with pytest.raises(ValueError):
+        config_loader.load_configuration(overrides=[])