Repository: facebookresearch/hydra

Issue:
[Bug] Bug for overriding to sub-structured config
# 🐛 Bug
## Description
Overriding variable to sub-structured config fails when using CLI 

## Checklist
- [x] I checked on the latest version of Hydra
- [x] I created a minimal repro (See [this](https://stackoverflow.com/help/minimal-reproducible-example) for tips).

## To reproduce

** Minimal Code/Config snippet to reproduce **
```
from dataclasses import dataclass
import hydra
from hydra.core.config_store import ConfigStore


@dataclass
class A:
    i: int = 1


@dataclass
class Config:
    a: A = A()


@hydra.main(config_name="config")
def hydra_main(cfg: Config):
    print(cfg)


if __name__ == '__main__':
    cs = ConfigStore.instance()
    cs.store(name='config', node=Config)

    hydra_main()
```

** Stack trace/error message **
```
$ python hydra.py +a.j=2
Error merging override +a.j=2
Key 'j' not in 'A'
	full_key: a.j
	reference_type=A
	object_type=A

Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace.
```

## Expected Behavior
```
{'a': {'i': 1, 'j': 2}}
```

## System information
- **Hydra Version** :  1.0.6, 1.1.0 dev5
- **Python version** : 3.7.9
- **Virtual environment type and version** : pyenv, vent
- **Operating system** : Mac, Ubuntu



Discussion:
Thanks for reporting.

You can enable adding keys to a Structured Config by extending Dict:
```python
from dataclasses import dataclass,field
import hydra
from hydra.core.config_store import ConfigStore
from typing import Dict, Any

@dataclass
class A(Dict[str, Any]):
    i: int = 1


@dataclass
class Config:
    a: A = field(default_factory=lambda: A())


@hydra.main(config_name="config")
def hydra_main(cfg: Config):
    print(cfg)


if __name__ == '__main__':
    cs = ConfigStore.instance()
    cs.store(name='config', node=Config)

    hydra_main()
```
Output:
```shell
$ python 1.py  a.x=10
Could not override 'a.x'.
To append to your config use +a.x=10
Key 'x' is not in struct
    full_key: a.x
    reference_type=Dict[str, Any]
    object_type=A

Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace.

$ python 1.py  +a.x=10
{'a': {'i': 1, 'x': 10}}
```

About your PR:
I will want to take a closer look at this. not sure I want it fixed.


Resolving patch:
diff --git a/hydra/_internal/config_loader_impl.py b/hydra/_internal/config_loader_impl.py
--- a/hydra/_internal/config_loader_impl.py
+++ b/hydra/_internal/config_loader_impl.py
@@ -352,8 +352,7 @@ def _apply_overrides_to_config(overrides: List[Override], cfg: DictConfig) -> No
                     if OmegaConf.select(
                         cfg, key, throw_on_missing=False
                     ) is None or isinstance(value, (dict, list)):
-                        with open_dict(cfg):
-                            OmegaConf.update(cfg, key, value, merge=True)
+                        OmegaConf.update(cfg, key, value, merge=True, force_add=True)
                     else:
                         assert override.input_line is not None
                         raise ConfigCompositionException(
@@ -366,8 +365,7 @@ def _apply_overrides_to_config(overrides: List[Override], cfg: DictConfig) -> No
                             )
                         )
                 elif override.is_force_add():
-                    with open_dict(cfg):
-                        OmegaConf.update(cfg, key, value, merge=True)
+                    OmegaConf.update(cfg, key, value, merge=True, force_add=True)
                 else:
                     try:
                         OmegaConf.update(cfg, key, value, merge=True)
@@ -379,7 +377,7 @@ def _apply_overrides_to_config(overrides: List[Override], cfg: DictConfig) -> No
             except OmegaConfBaseException as ex:
                 raise ConfigCompositionException(
                     f"Error merging override {override.input_line}"
-                ) from ex
+                ).with_traceback(sys.exc_info()[2]) from ex
 
     def _load_single_config(
         self, default: ResultDefault, repo: IConfigRepository


Test patch:
diff --git a/tests/test_compose.py b/tests/test_compose.py
--- a/tests/test_compose.py
+++ b/tests/test_compose.py
@@ -387,6 +387,25 @@ def test_add_config_group(self) -> None:
         ):
             compose(overrides=["++group=a1"])
 
+    def test_add_to_structured_config(self, hydra_restore_singletons: Any) -> None:
+        @dataclass
+        class Config:
+            a: int = 10
+
+        ConfigStore.instance().store(name="config", node=Config, package="nested")
+
+        assert compose("config", overrides=["+nested.b=20"]) == {
+            "nested": {"a": 10, "b": 20}
+        }
+
+        assert compose("config", overrides=["++nested.a=30", "++nested.b=20"]) == {
+            "nested": {"a": 30, "b": 20}
+        }
+
+        assert compose("config", overrides=["+nested.b.c=20"]) == {
+            "nested": {"a": 10, "b": {"c": 20}}
+        }
+
 
 @mark.usefixtures("hydra_restore_singletons")
 @mark.usefixtures("initialize_hydra_no_path")