deepcode-ci-bot[bot]: <img src="https://www.deepcode.ai/icons/green_check.svg" width= "50px" align= "left"/> Congratulations :tada:. DeepCode [analyzed](https://www.deepcode.ai/app/gh/omry/omegaconf/b6a35f255e8ff20f992f05e96fc73686044c67d9/omry/omegaconf/8d1121ef9c5cffee7149be8c36613931d3810a46/_/%2F/code/?utm_source=gh_review) your code in 1.654 seconds and we found no issues. Enjoy a moment of no bugs :sunny:.

##### 👉 View analysis in [**DeepCode’s Dashboard**](https://www.deepcode.ai/app/gh/omry/omegaconf/b6a35f255e8ff20f992f05e96fc73686044c67d9/omry/omegaconf/8d1121ef9c5cffee7149be8c36613931d3810a46/_/%2F/code/?utm_source=gh_review)
---

diff --git a/news/184.feature b/news/184.feature
new file mode 100644
index 0000000..bf4c914
--- /dev/null
+++ b/news/184.feature
@@ -0,0 +1,1 @@
+Merge into node interpolation is now by value (copying target node)
diff --git a/omegaconf/base.py b/omegaconf/base.py
index 52177ce..ad8fd44 100644
--- a/omegaconf/base.py
+++ b/omegaconf/base.py
@@ -101,7 +101,9 @@ class Node(ABC):
     def _get_full_key(self, key: Union[str, Enum, int, None]) -> str:
         ...  # pragma: no cover
 
-    def _dereference_node(self, throw_on_missing: bool = False) -> "Node":
+    def _dereference_node(
+        self, throw_on_missing: bool = False, throw_on_resolution_failure: bool = True
+    ) -> Optional["Node"]:
         from .nodes import StringNode
 
         if self._is_interpolation():
@@ -118,12 +120,18 @@ class Node(ABC):
                     inter_type=match.group(1),
                     inter_key=match.group(2),
                     throw_on_missing=throw_on_missing,
+                    throw_on_resolution_failure=throw_on_resolution_failure,
                 )
                 return v
             elif value_kind == ValueKind.STR_INTERPOLATION:
                 ret = parent._resolve_str_interpolation(
-                    key=key, value=self, throw_on_missing=throw_on_missing
+                    key=key,
+                    value=self,
+                    throw_on_missing=throw_on_missing,
+                    throw_on_resolution_failure=throw_on_resolution_failure,
                 )
+                if ret is None:
+                    return ret
                 return StringNode(
                     value=ret,
                     key=key,
@@ -231,7 +239,7 @@ class Container(Node):
         return root
 
     def _select_impl(
-        self, key: str, throw_on_missing: bool
+        self, key: str, throw_on_missing: bool, throw_on_resolution_failure: bool
     ) -> Tuple[Optional["Container"], Optional[str], Optional[Node]]:
         """
         Select a value using dot separated key sequence
@@ -249,7 +257,12 @@ class Container(Node):
             if root is None:
                 break
             k = split[i]
-            ret, _ = _select_one(c=root, key=k, throw_on_missing=throw_on_missing)
+            ret, _ = _select_one(
+                c=root,
+                key=k,
+                throw_on_missing=throw_on_missing,
+                throw_on_type_error=throw_on_resolution_failure,
+            )
             assert ret is None or isinstance(ret, Container)
             root = ret
 
@@ -257,17 +270,30 @@ class Container(Node):
             return None, None, None
 
         last_key = split[-1]
-        value, _ = _select_one(c=root, key=last_key, throw_on_missing=throw_on_missing)
+        value, _ = _select_one(
+            c=root,
+            key=last_key,
+            throw_on_missing=throw_on_missing,
+            throw_on_type_error=throw_on_resolution_failure,
+        )
         if value is None:
             return root, last_key, value
         value = root._resolve_str_interpolation(
-            key=last_key, value=value, throw_on_missing=False
+            key=last_key,
+            value=value,
+            throw_on_missing=False,
+            throw_on_resolution_failure=True,
         )
         return root, last_key, value
 
     def _resolve_interpolation(
-        self, key: Any, inter_type: str, inter_key: str, throw_on_missing: bool,
-    ) -> "Node":
+        self,
+        key: Any,
+        inter_type: str,
+        inter_key: str,
+        throw_on_missing: bool,
+        throw_on_resolution_failure: bool,
+    ) -> Optional["Node"]:
         from omegaconf import OmegaConf
 
         from .nodes import ValueNode
@@ -277,13 +303,18 @@ class Container(Node):
         inter_type = ("str:" if inter_type is None else inter_type)[0:-1]
         if inter_type == "str":
             parent, last_key, value = root_node._select_impl(
-                inter_key, throw_on_missing=throw_on_missing
+                inter_key,
+                throw_on_missing=throw_on_missing,
+                throw_on_resolution_failure=throw_on_resolution_failure,
             )
 
             if parent is None or (value is None and last_key not in parent):  # type: ignore
-                raise KeyError(
-                    f"{inter_type} interpolation key '{inter_key}' not found"
-                )
+                if throw_on_resolution_failure:
+                    raise KeyError(
+                        f"{inter_type} interpolation key '{inter_key}' not found"
+                    )
+                else:
+                    return None
             assert isinstance(value, Node)
             return value
         else:
@@ -302,12 +333,19 @@ class Container(Node):
                     self._format_and_raise(key=inter_key, value=None, cause=e)
                     assert False
             else:
-                raise UnsupportedInterpolationType(
-                    f"Unsupported interpolation type {inter_type}"
-                )
+                if throw_on_resolution_failure:
+                    raise UnsupportedInterpolationType(
+                        f"Unsupported interpolation type {inter_type}"
+                    )
+                else:
+                    return None
 
     def _resolve_str_interpolation(
-        self, key: Any, value: "Node", throw_on_missing: bool
+        self,
+        key: Any,
+        value: "Node",
+        throw_on_missing: bool,
+        throw_on_resolution_failure: bool,
     ) -> Any:
         from .nodes import StringNode
 
@@ -323,6 +361,7 @@ class Container(Node):
                 inter_type=match.group(1),
                 inter_key=match.group(2),
                 throw_on_missing=throw_on_missing,
+                throw_on_resolution_failure=throw_on_resolution_failure,
             )
         elif value_kind == ValueKind.STR_INTERPOLATION:
             value = _get_value(value)
@@ -336,7 +375,11 @@ class Container(Node):
                     inter_type=match.group(1),
                     inter_key=match.group(2),
                     throw_on_missing=throw_on_missing,
+                    throw_on_resolution_failure=throw_on_resolution_failure,
                 )
+                # if failed to resolve, return None for the whole thing.
+                if new_val is None:
+                    return None
                 new += orig[last_index : match.start(0)] + str(new_val)
                 last_index = match.end(0)
 
diff --git a/omegaconf/basecontainer.py b/omegaconf/basecontainer.py
index 07e7d7e..6dbf1c8 100644
--- a/omegaconf/basecontainer.py
+++ b/omegaconf/basecontainer.py
@@ -54,7 +54,10 @@ class BaseContainer(Container, ABC):
             value = default_value
 
         resolved = self._resolve_str_interpolation(
-            key=key, value=value, throw_on_missing=True
+            key=key,
+            value=value,
+            throw_on_missing=True,
+            throw_on_resolution_failure=True,
         )
         if is_mandatory_missing(resolved):
             raise MissingMandatoryValue("Missing mandatory value: $FULL_KEY")
@@ -222,8 +225,17 @@ class BaseContainer(Container, ABC):
                     if OmegaConf.is_missing(dest, key):
                         dest[key] = src_value
 
-            if (dest._get_node(key) is not None) or element_typed:
-                dest_node = dest._get_node(key)
+            dest_node = dest._get_node(key)
+            if dest_node is not None and dest_node._is_interpolation():
+                target_node = dest_node._dereference_node(
+                    throw_on_resolution_failure=False
+                )
+                if isinstance(target_node, Container):
+                    dest_node = copy.deepcopy(target_node)
+                    dest[key] = dest_node
+
+            if dest_node is not None or element_typed:
+
                 if dest_node is None and element_typed:
                     dest[key] = DictConfig(content=dest_element_type, parent=dest)
                     dest_node = dest._get_node(key)
@@ -360,53 +372,51 @@ class BaseContainer(Container, ABC):
 
     @staticmethod
     def _item_eq(
-        c1: "BaseContainer",
-        k1: Union[str, int],
-        c2: "BaseContainer",
-        k2: Union[str, int],
+        c1: Container, k1: Union[str, int], c2: Container, k2: Union[str, int],
     ) -> bool:
         v1 = c1._get_node(k1)
         v2 = c2._get_node(k2)
-        v1_kind = get_value_kind(v1)
-        v2_kind = get_value_kind(v2)
+        assert v1 is not None and v2 is not None
 
-        if v1_kind == v2_kind and v1_kind == ValueKind.MANDATORY_MISSING:
+        if v1._is_none() and v2._is_none():
             return True
 
-        v1 = _get_value(v1)
-        v2 = _get_value(v2)
-
-        # special case for two interpolations. just compare them literally.
-        # This is helping in cases where the two interpolations are not resolvable
-        # but the objects are still considered equal.
-        if v1_kind in (
-            ValueKind.STR_INTERPOLATION,
-            ValueKind.INTERPOLATION,
-        ) and v2_kind in (ValueKind.STR_INTERPOLATION, ValueKind.INTERPOLATION):
+        if v1._is_missing() and v2._is_missing():
             return True
-        if isinstance(v1, str):
-            v1 = c1._resolve_str_interpolation(key=k1, value=v1, throw_on_missing=False)
-        if isinstance(v2, str):
-            v2 = c2._resolve_str_interpolation(key=k2, value=v2, throw_on_missing=False)
 
-        if isinstance(v1, BaseContainer) and isinstance(v2, BaseContainer):
-            if not BaseContainer._config_eq(v1, v2):
-                return False
-        return v1 == v2
+        v1_inter = v1._is_interpolation()
+        v2_inter = v2._is_interpolation()
+        dv1: Optional[Node] = v1
+        dv2: Optional[Node] = v2
 
-    @staticmethod
-    def _config_eq(c1: "BaseContainer", c2: "BaseContainer") -> bool:
-        from .dictconfig import DictConfig
-        from .listconfig import ListConfig
+        if v1_inter:
+            dv1 = v1._dereference_node(
+                throw_on_missing=False, throw_on_resolution_failure=False
+            )
+        if v2_inter:
+            dv2 = v2._dereference_node(
+                throw_on_missing=False, throw_on_resolution_failure=False
+            )
 
-        assert isinstance(c1, Container)
-        assert isinstance(c2, Container)
-        if isinstance(c1, DictConfig) and isinstance(c2, DictConfig):
-            return DictConfig._dict_conf_eq(c1, c2)
-        if isinstance(c1, ListConfig) and isinstance(c2, ListConfig):
-            return ListConfig._list_eq(c1, c2)
-        # if type does not match objects are different
-        return False
+        if v1_inter and v2_inter:
+            if dv1 is None or dv2 is None:
+                return v1 == v2
+            else:
+                # both are not none, if both are containers compare as container
+                if isinstance(dv1, Container) and isinstance(dv2, Container):
+                    if dv1 != dv2:
+                        return False
+                dv1 = _get_value(dv1)
+                dv2 = _get_value(dv2)
+                return dv1 == dv2
+        elif not v1_inter and not v2_inter:
+            v1 = _get_value(v1)
+            v2 = _get_value(v2)
+            return v1 == v2
+        else:
+            dv1 = _get_value(dv1)
+            dv2 = _get_value(dv2)
+            return dv1 == dv2
 
     def _re_parent(self) -> None:
         from .dictconfig import DictConfig
diff --git a/omegaconf/dictconfig.py b/omegaconf/dictconfig.py
index f23a4b1..3d041af 100644
--- a/omegaconf/dictconfig.py
+++ b/omegaconf/dictconfig.py
@@ -446,7 +446,8 @@ class DictConfig(BaseContainer, MutableMapping[str, Any]):  # type: ignore
         if other is None:
             return self.__dict__["_content"] is None
         if is_primitive_dict(other) or is_structured_config(other):
-            return DictConfig._dict_conf_eq(self, DictConfig(other))
+            other = DictConfig(other)
+            return DictConfig._dict_conf_eq(self, other)
         if isinstance(other, DictConfig):
             return DictConfig._dict_conf_eq(self, other)
         return NotImplemented
diff --git a/omegaconf/listconfig.py b/omegaconf/listconfig.py
index fed3f70..59a01d8 100644
--- a/omegaconf/listconfig.py
+++ b/omegaconf/listconfig.py
@@ -328,7 +328,8 @@ class ListConfig(BaseContainer, MutableSequence[Any]):
 
     def __eq__(self, other: Any) -> bool:
         if isinstance(other, (list, tuple)) or other is None:
-            return ListConfig._list_eq(self, ListConfig(other))
+            other = ListConfig(other)
+            return ListConfig._list_eq(self, other)
         if other is None or isinstance(other, ListConfig):
             return ListConfig._list_eq(self, other)
         return NotImplemented
diff --git a/omegaconf/nodes.py b/omegaconf/nodes.py
index fde6d65..50b78fa 100644
--- a/omegaconf/nodes.py
+++ b/omegaconf/nodes.py
@@ -70,16 +70,35 @@ class ValueNode(Node):
         res.__dict__ = copy.deepcopy(self.__dict__, memo=memo)
 
     def _is_none(self) -> bool:
-        node = self._dereference_node()
+        if self._is_interpolation():
+            node = self._dereference_node(
+                throw_on_resolution_failure=False, throw_on_missing=False
+            )
+            if node is None:
+                # missing or resolution failure
+                return False
+        else:
+            node = self
         return node._value() is None
 
     def _is_optional(self) -> bool:
         node = self._dereference_node()
+        assert node is not None
         return node._metadata.optional
 
     def _is_missing(self) -> bool:
         try:
-            node = self._dereference_node(throw_on_missing=True)
+            if self._is_interpolation():
+                node = self._dereference_node(
+                    throw_on_resolution_failure=False, throw_on_missing=True
+                )
+                if node is None:
+                    # resolution failure
+                    return False
+            else:
+                node = self
+
+            assert node is not None
             if isinstance(node, Container):
                 ret = node._is_missing()
             else:
diff --git a/omegaconf/omegaconf.py b/omegaconf/omegaconf.py
index eb7cf5f..f95b9d6 100644
--- a/omegaconf/omegaconf.py
+++ b/omegaconf/omegaconf.py
@@ -491,7 +491,10 @@ class OmegaConf:
     @staticmethod
     def select(cfg: Container, key: str, throw_on_missing: bool = False) -> Any:
         try:
-            _root, _last_key, value = cfg._select_impl(key, throw_on_missing=False)
+            # TODO: should be throw_on_missing=throw_on_missing ?
+            _root, _last_key, value = cfg._select_impl(
+                key, throw_on_missing=False, throw_on_resolution_failure=True
+            )
             if value is not None and value._is_missing():
                 if throw_on_missing:
                     raise MissingMandatoryValue("missing node selected")

[bug] OmegaConf.merge should respect interpolations
```py
from omegaconf import OmegaConf

first = OmegaConf.create({
    "a": {
        "aa": 1,
        "ab": 2
    },
    "b": "${a}"
})

second = OmegaConf.create({
    "b": {
        "ba": 1,
        "bb": 2
    }
})

third = OmegaConf.merge(first, second)


expected = OmegaConf.create({
   "a": {
        "aa": 1,
        "ab": 2
   },
   "b": { 
        "aa": 1,
        "ab": 2,
        "ba": 1,
        "bb": 2
    }       
})
assert third == expected
# throws AssertionError
```

Version: 2.0.0rc16
diff --git a/tests/test_base_config.py b/tests/test_base_config.py
index 39a42d1..fd1a58b 100644
--- a/tests/test_base_config.py
+++ b/tests/test_base_config.py
@@ -434,7 +434,10 @@ def test_resolve_str_interpolation(query: str, result: Any) -> None:
     cfg = OmegaConf.create({"foo": 10, "bar": "${foo}"})
     assert (
         cfg._resolve_str_interpolation(
-            key=None, value=StringNode(value=query), throw_on_missing=False
+            key=None,
+            value=StringNode(value=query),
+            throw_on_missing=False,
+            throw_on_resolution_failure=True,
         )
         == result
     )
diff --git a/tests/test_config_eq.py b/tests/test_config_eq.py
index bbb1d05..1ed5f09 100644
--- a/tests/test_config_eq.py
+++ b/tests/test_config_eq.py
@@ -1,38 +1,37 @@
-from typing import Any, List
+from typing import Any
 
 import pytest
 
 from omegaconf import AnyNode, DictConfig, OmegaConf
-from omegaconf.basecontainer import BaseContainer
 
 from . import Group, User
 
 
 @pytest.mark.parametrize(  # type: ignore
-    "l1,l2",
+    "i1,i2",
     [
         # === LISTS ===
         # empty list
-        ([], []),
+        pytest.param([], [], id="empty"),
         # simple list
-        (["a", 12, "15"], ["a", 12, "15"]),
+        pytest.param(["a", 12, "15"], ["a", 12, "15"], id="simple_list"),
         # raw vs any
         ([1, 2, 12], [1, 2, AnyNode(12)]),
         # nested empty dict
-        ([12, dict()], [12, dict()]),
+        ([12, {}], [12, {}]),
         # nested dict
-        ([12, dict(c=10)], [12, dict(c=10)]),
+        ([12, {"c": 10}], [12, {"c": 10}]),
         # nested list
         ([1, 2, 3, [10, 20, 30]], [1, 2, 3, [10, 20, 30]]),
         # nested list with any
         ([1, 2, 3, [1, 2, AnyNode(3)]], [1, 2, 3, [1, 2, AnyNode(3)]]),
-        # === DICTS ==
+        # === DICTS ===
         # empty
-        (dict(), dict()),
+        ({}, {}),
         # simple
-        (dict(a=12), dict(a=12)),
+        ({"a": 12}, {"a": 12}),
         # any vs raw
-        (dict(a=12), dict(a=AnyNode(12))),
+        ({"a": 12}, {"a": AnyNode(12)}),
         # nested dict empty
         (dict(a=12, b=dict()), dict(a=12, b=dict())),
         # nested dict
@@ -44,21 +43,32 @@ from . import Group, User
         # In python 3.6+ insert order changes iteration order. this ensures that equality is preserved.
         (dict(a=1, b=2, c=3, d=4, e=5), dict(e=5, b=2, c=3, d=4, a=1)),
         (DictConfig(content=None), DictConfig(content=None)),
+        pytest.param({"a": [1, 2]}, {"a": [1, 2]}, id="list_in_dict"),
         # With interpolations
         ([10, "${0}"], [10, 10]),
         (dict(a=12, b="${a}"), dict(a=12, b=12)),
         # With missing interpolation
-        ([10, "${0}"], [10, 10]),
-        (dict(a="${missing}"), dict(a="${missing}")),
-        (User, User),
-        ({"name": "poo", "age": 7}, User(name="poo", age=7)),
-        (Group, Group),
-        ({"group": {"admin": None}}, {"group": Group}),
+        pytest.param([10, "${0}"], [10, 10], id="list_simple_interpolation"),
+        pytest.param(
+            {"a": "${ref_error}"}, {"a": "${ref_error}"}, id="dict==dict,ref_error"
+        ),
+        pytest.param({"a": "???"}, {"a": "???"}, id="dict==dict,missing"),
+        pytest.param(User, User, id="User==User"),
+        pytest.param(
+            {"name": "poo", "age": 7}, User(name="poo", age=7), id="dict==User"
+        ),
+        pytest.param(Group, Group, id="Group==Group"),
+        pytest.param({"group": {"admin": None}}, {"group": Group}, id="dict==Group"),
+        pytest.param(
+            {"i1": "${n1}", "n1": {"a": 10}},
+            {"i1": "${n1}", "n1": {"a": 10}},
+            id="node_interpolation",
+        ),
     ],
 )
-def test_eq(l1: List[Any], l2: List[Any]) -> None:
-    c1 = OmegaConf.create(l1)
-    c2 = OmegaConf.create(l2)
+def test_eq(i1: Any, i2: Any) -> None:
+    c1 = OmegaConf.create(i1)
+    c2 = OmegaConf.create(i2)
 
     def eq(a: Any, b: Any) -> None:
         assert a == b
@@ -67,32 +77,41 @@ def test_eq(l1: List[Any], l2: List[Any]) -> None:
         assert not b != a
 
     eq(c1, c2)
-    eq(c1, l1)
-    eq(c2, l2)
+    eq(c1, i1)
+    eq(c2, i2)
 
 
 @pytest.mark.parametrize(  # type: ignore
     "input1, input2",
     [
         # Dicts
-        ({}, {"a": 10}),
-        ({}, []),
-        ({}, None),
-        (dict(a=12), dict(a=13)),
-        (dict(a=0), dict(b=0)),
-        (dict(a=12), dict(a=AnyNode(13))),
-        (dict(a=12, b=dict()), dict(a=13, b=dict())),
-        (dict(a=12, b=dict(c=10)), dict(a=13, b=dict(c=10))),
-        (dict(a=12, b=[1, 2, 3]), dict(a=12, b=[10, 2, 3])),
-        (dict(a=12, b=[1, 2, AnyNode(3)]), dict(a=12, b=[1, 2, AnyNode(30)])),
+        pytest.param({}, {"a": 10}, id="empty_dict_neq_dict"),
+        pytest.param({}, [], id="empty_dict_vs_list"),
+        pytest.param({}, None, id="dict_neq_none"),
+        pytest.param({"foo": None}, {"foo": "bar"}, id="dict_none_neq_dict_not_none"),
+        pytest.param({"a": 12}, {"a": 13}, id="simple_dict_neq"),
+        pytest.param({"a": 0}, {"b": 0}, id="different_key_same_value"),
+        pytest.param(dict(a=12), dict(a=AnyNode(13))),
+        pytest.param(dict(a=12, b=dict()), dict(a=13, b=dict())),
+        pytest.param(dict(a=12, b=dict(c=10)), dict(a=13, b=dict(c=10))),
+        pytest.param(dict(a=12, b=[1, 2, 3]), dict(a=12, b=[10, 2, 3])),
+        pytest.param(
+            dict(a=12, b=[1, 2, AnyNode(3)]), dict(a=12, b=[1, 2, AnyNode(30)])
+        ),
         # Lists
-        ([], [10]),
-        ([10], [11]),
+        pytest.param([], [10], id="list:empty_vs_full"),
+        pytest.param([10], [11], id="list:different_value"),
         ([12], [AnyNode(13)]),
         ([12, dict()], [13, dict()]),
         ([12, dict(c=10)], [13, dict(c=10)]),
         ([12, [1, 2, 3]], [12, [10, 2, 3]]),
         ([12, [1, 2, AnyNode(3)]], [12, [1, 2, AnyNode(30)]]),
+        (dict(a="${foo1}"), dict(a="${foo2}")),
+        pytest.param(
+            {"i1": "${n1}", "n1": {"a": 10}},
+            {"i1": "${n1}", "n1": {"a": 20}},
+            id="node_interpolation",
+        ),
     ],
 )
 def test_not_eq(input1: Any, input2: Any) -> None:
@@ -113,8 +132,7 @@ def test_not_eq(input1: Any, input2: Any) -> None:
 def test_config_eq_mismatch_types() -> None:
     c1 = OmegaConf.create({})
     c2 = OmegaConf.create([])
-    assert not BaseContainer._config_eq(c1, c2)
-    assert not BaseContainer._config_eq(c2, c1)
+    assert c1 != c2
 
 
 def test_dict_not_eq_with_another_class() -> None:
diff --git a/tests/test_merge.py b/tests/test_merge.py
index 5d6eaee..4f38fe7 100644
--- a/tests/test_merge.py
+++ b/tests/test_merge.py
@@ -36,14 +36,62 @@ from . import ConcretePlugin, ConfWithMissingDict, Group, Plugin, User, Users
         (([[1, 2, 3]], [[4, 5, 6]]), [[4, 5, 6]]),
         (([1, 2, {"a": 10}], [4, 5, {"b": 20}]), [4, 5, {"b": 20}]),
         # Interpolations
-        (
-            ({"data": 123, "reference": "${data}"}, {"data": 456}),
-            {"data": 456, "reference": 456},
+        # value interpolation
+        pytest.param(
+            ({"d1": 1, "inter": "${d1}"}, {"d1": 2}),
+            {"d1": 2, "inter": 2},
+            id="inter:updating_data",
         ),
-        (({"missing": "${data}"}, {"missing": 123}), {"missing": 123}),
-        (
-            ({"missing": 123}, {"missing": "${data}"}, {"missing": 456}),
-            {"missing": 456},
+        pytest.param(
+            ({"d1": 1, "d2": 2, "inter": "${d1}"}, {"inter": "${d2}"}),
+            {"d1": 1, "d2": 2, "inter": 2},
+            id="inter:value_inter_over_value_inter",
+        ),
+        pytest.param(
+            ({"inter": "${d1}"}, {"inter": 123}),
+            {"inter": 123},
+            id="inter:data_over_value_inter",
+        ),
+        pytest.param(
+            ({"inter": "${d1}", "d1": 1, "n1": {"foo": "bar"}}, {"inter": "${n1}"}),
+            {"inter": {"foo": "bar"}, "d1": 1, "n1": {"foo": "bar"}},
+            id="inter:node_inter_over_value_inter",
+        ),
+        pytest.param(
+            ({"inter": 123}, {"inter": "${data}"}),
+            {"inter": "${data}"},
+            id="inter:inter_over_data",
+        ),
+        # node interpolation
+        pytest.param(
+            ({"n": {"a": 10}, "i": "${n}"}, {"n": {"a": 20}}),
+            {"n": {"a": 20}, "i": {"a": 20}},
+            id="node_inter:node_update",
+        ),
+        pytest.param(
+            ({"d": 20, "n": {"a": 10}, "i": "${n}"}, {"i": "${d}"}),
+            {"d": 20, "n": {"a": 10}, "i": 20},
+            id="node_inter:value_inter_over_node_inter",
+        ),
+        pytest.param(
+            ({"n": {"a": 10}, "i": "${n}"}, {"i": 30}),
+            {"n": {"a": 10}, "i": 30},
+            id="node_inter:data_over_node_inter",
+        ),
+        pytest.param(
+            ({"n1": {"a": 10}, "n2": {"b": 20}, "i": "${n1}"}, {"i": "${n2}"}),
+            {"n1": {"a": 10}, "n2": {"b": 20}, "i": {"b": 20}},
+            id="node_inter:node_inter_over_node_inter",
+        ),
+        pytest.param(
+            ({"v": 10, "n": {"a": 20}}, {"v": "${n}"}),
+            {"v": {"a": 20}, "n": {"a": 20}},
+            id="inter:node_inter_over_data",
+        ),
+        pytest.param(
+            ({"n": {"a": 10}, "i": "${n}"}, {"i": {"b": 20}}),
+            {"n": {"a": 10}, "i": {"a": 10, "b": 20}},
+            id="inter:node_over_node_interpolation",
         ),
         # Structured configs
         (({"user": User}, {}), {"user": User(name=MISSING, age=MISSING)}),
@@ -72,7 +120,11 @@ from . import ConcretePlugin, ConfWithMissingDict, Group, Plugin, User, Users
             {"name2user": {"joe": {"name": "joe", "age": MISSING}}},
         ),
         ([ConfWithMissingDict, {"dict": {"foo": "bar"}}], {"dict": {"foo": "bar"}}),
-        ([{}, ConfWithMissingDict], {"dict": "???"}),
+        pytest.param(
+            [{}, ConfWithMissingDict],
+            {"dict": "???"},
+            id="merge_missing_dict_into_missing_dict",
+        ),
         ([{"user": User}, {"user": Group}], pytest.raises(ValidationError)),
         (
             [{"user": DictConfig(ref_type=User, content=User)}, {"user": Group}],

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
iniconfig==2.1.0
-e git+https://github.com/omry/omegaconf.git@b6a35f255e8ff20f992f05e96fc73686044c67d9#egg=omegaconf
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
PyYAML==6.0.2
tomli==2.2.1
typing_extensions==4.13.0

name: omegaconf
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - omegaconf==2.0.0rc17
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - pyyaml==6.0.2
      - tomli==2.2.1
      - typing-extensions==4.13.0
prefix: /opt/conda/envs/omegaconf

tests/test_base_config.py::test_resolve_str_interpolation[a-a] tests/test_base_config.py::test_resolve_str_interpolation[${foo}-10] tests/test_base_config.py::test_resolve_str_interpolation[${bar}-10] tests/test_base_config.py::test_resolve_str_interpolation[foo_${foo}-foo_10] tests/test_base_config.py::test_resolve_str_interpolation[foo_${bar}-foo_10] tests/test_config_eq.py::test_not_eq[input118-input218] tests/test_merge.py::test_merge[inter:node_over_node_interpolation]
tests/test_base_config.py::test_set_value[input_0-foo-10-expected0] tests/test_base_config.py::test_set_value[input_1-foo-value1-expected1] tests/test_base_config.py::test_set_value[input_2-foo-value2-expected2] tests/test_base_config.py::test_set_value[input_3-foo-value3-expected3] tests/test_base_config.py::test_set_value[input_4-0-10-expected4] tests/test_base_config.py::test_set_value[input_5-1-10-expected5] tests/test_base_config.py::test_set_value[input_6-1-value6-expected6] tests/test_base_config.py::test_set_value[input_7-1-value7-expected7] tests/test_base_config.py::test_set_value[input_8-1-value8-expected8] tests/test_base_config.py::test_set_value_validation_fail[input_0-foo-str] tests/test_base_config.py::test_set_value_validation_fail[input_1-1-str] tests/test_base_config.py::test_replace_value_node_type_with_another[input_0-foo-value0] tests/test_base_config.py::test_replace_value_node_type_with_another[input_1-1-value1] tests/test_base_config.py::test_to_container_returns_primitives[input_0] tests/test_base_config.py::test_to_container_returns_primitives[input_1] tests/test_base_config.py::test_to_container_returns_primitives[input_2] tests/test_base_config.py::test_to_container_returns_primitives[input_3] tests/test_base_config.py::test_to_container_returns_primitives[input_4] tests/test_base_config.py::test_empty[input_0-True] tests/test_base_config.py::test_empty[input_1-True] tests/test_base_config.py::test_empty[input_2-False] tests/test_base_config.py::test_empty[input_3-False] tests/test_base_config.py::test_str[input_0-[]-str] tests/test_base_config.py::test_str[input_0-[]-repr] tests/test_base_config.py::test_str[input_1-{}-str] tests/test_base_config.py::test_str[input_1-{}-repr] tests/test_base_config.py::test_str[input_2-[1, tests/test_base_config.py::test_str[input_3-[1, tests/test_base_config.py::test_str[input_4-[1, tests/test_base_config.py::test_str[input_5-{'b': tests/test_base_config.py::test_str[input_6-{'b': tests/test_base_config.py::test_str[StructuredWithMissing-{'num': tests/test_base_config.py::test_flag_dict[readonly] tests/test_base_config.py::test_flag_dict[struct] tests/test_base_config.py::test_freeze_nested_dict[readonly] tests/test_base_config.py::test_freeze_nested_dict[struct] tests/test_base_config.py::TestDeepCopy::test_deepcopy[src0] tests/test_base_config.py::TestDeepCopy::test_deepcopy[src1] tests/test_base_config.py::TestDeepCopy::test_deepcopy[src2] tests/test_base_config.py::TestDeepCopy::test_deepcopy[src3] tests/test_base_config.py::TestDeepCopy::test_deepcopy[StructuredWithMissing] tests/test_base_config.py::TestDeepCopy::test_deepcopy_readonly[src0] tests/test_base_config.py::TestDeepCopy::test_deepcopy_readonly[src1] tests/test_base_config.py::TestDeepCopy::test_deepcopy_readonly[src2] tests/test_base_config.py::TestDeepCopy::test_deepcopy_readonly[src3] tests/test_base_config.py::TestDeepCopy::test_deepcopy_readonly[StructuredWithMissing] tests/test_base_config.py::TestDeepCopy::test_deepcopy_struct[src0] tests/test_base_config.py::TestDeepCopy::test_deepcopy_struct[src1] tests/test_base_config.py::TestDeepCopy::test_deepcopy_struct[src2] tests/test_base_config.py::TestDeepCopy::test_deepcopy_struct[src3] tests/test_base_config.py::TestDeepCopy::test_deepcopy_struct[StructuredWithMissing] tests/test_base_config.py::test_deepcopy_after_del tests/test_base_config.py::test_deepcopy_after_pop tests/test_base_config.py::test_deepcopy_with_interpolation tests/test_base_config.py::test_deepcopy_and_merge_and_flags tests/test_base_config.py::test_deepcopy_preserves_container_type[cfg0] tests/test_base_config.py::test_deepcopy_preserves_container_type[cfg1] tests/test_base_config.py::test_flag_override[struct_setiitem] tests/test_base_config.py::test_flag_override[struct_setattr] tests/test_base_config.py::test_flag_override[readonly] tests/test_base_config.py::test_read_write_override[src0-<lambda>-expectation0] tests/test_base_config.py::test_read_write_override[src1-<lambda>-expectation1] tests/test_base_config.py::test_tokenize_with_escapes[dog,cat-tokenized0] tests/test_base_config.py::test_tokenize_with_escapes[dog\\,cat\\ tests/test_base_config.py::test_tokenize_with_escapes[dog,\\ tests/test_base_config.py::test_tokenize_with_escapes[\\ tests/test_base_config.py::test_tokenize_with_escapes[dog, tests/test_base_config.py::test_tokenize_with_escapes[whitespace\\ tests/test_base_config.py::test_tokenize_with_escapes[None-tokenized8] tests/test_base_config.py::test_tokenize_with_escapes[-tokenized9] tests/test_base_config.py::test_tokenize_with_escapes[no tests/test_base_config.py::test_struct_override[src0-<lambda>-expectation0] tests/test_base_config.py::test_open_dict_restore[struct-open_dict] tests/test_base_config.py::test_open_dict_restore[readonly-read_write] tests/test_base_config.py::TestCopy::test_copy[src0-<lambda>0] tests/test_base_config.py::TestCopy::test_copy[src0-<lambda>1] tests/test_base_config.py::TestCopy::test_copy[src1-<lambda>0] tests/test_base_config.py::TestCopy::test_copy[src1-<lambda>1] tests/test_base_config.py::TestCopy::test_copy[src2-<lambda>0] tests/test_base_config.py::TestCopy::test_copy[src2-<lambda>1] tests/test_base_config.py::TestCopy::test_copy[src3-<lambda>0] tests/test_base_config.py::TestCopy::test_copy[src3-<lambda>1] tests/test_base_config.py::TestCopy::test_copy[src4-<lambda>0] tests/test_base_config.py::TestCopy::test_copy[src4-<lambda>1] tests/test_base_config.py::TestCopy::test_copy[src5-<lambda>0] tests/test_base_config.py::TestCopy::test_copy[src5-<lambda>1] tests/test_base_config.py::TestCopy::test_copy_with_interpolation[src0-2-0-<lambda>0] tests/test_base_config.py::TestCopy::test_copy_with_interpolation[src0-2-0-<lambda>1] tests/test_base_config.py::TestCopy::test_copy_with_interpolation[src1-b-a-<lambda>0] tests/test_base_config.py::TestCopy::test_copy_with_interpolation[src1-b-a-<lambda>1] tests/test_base_config.py::TestCopy::test_list_copy_is_shallow[<lambda>0] tests/test_base_config.py::TestCopy::test_list_copy_is_shallow[<lambda>1] tests/test_base_config.py::test_not_implemented tests/test_base_config.py::test_omegaconf_create tests/test_base_config.py::test_assign[parent0-0-value0-expected0] tests/test_base_config.py::test_assign[parent1-0-value1-expected1] tests/test_base_config.py::test_assign[parent2-0-value2-expected2] tests/test_base_config.py::test_assign[parent3-foo-value3-expected3] tests/test_base_config.py::test_assign[parent4-foo-value4-expected4] tests/test_base_config.py::test_assign[parent5-foo-value5-expected5] tests/test_config_eq.py::test_eq[empty] tests/test_config_eq.py::test_eq[simple_list] tests/test_config_eq.py::test_eq[i12-i22] tests/test_config_eq.py::test_eq[i13-i23] tests/test_config_eq.py::test_eq[i14-i24] tests/test_config_eq.py::test_eq[i15-i25] tests/test_config_eq.py::test_eq[i16-i26] tests/test_config_eq.py::test_eq[i17-i27] tests/test_config_eq.py::test_eq[i18-i28] tests/test_config_eq.py::test_eq[i19-i29] tests/test_config_eq.py::test_eq[i110-i210] tests/test_config_eq.py::test_eq[i111-i211] tests/test_config_eq.py::test_eq[i112-i212] tests/test_config_eq.py::test_eq[i113-i213] tests/test_config_eq.py::test_eq[i114-i214] tests/test_config_eq.py::test_eq[i115-i215] tests/test_config_eq.py::test_eq[list_in_dict] tests/test_config_eq.py::test_eq[i117-i217] tests/test_config_eq.py::test_eq[i118-i218] tests/test_config_eq.py::test_eq[list_simple_interpolation] tests/test_config_eq.py::test_eq[dict==dict,ref_error] tests/test_config_eq.py::test_eq[dict==dict,missing] tests/test_config_eq.py::test_eq[User==User] tests/test_config_eq.py::test_eq[dict==User] tests/test_config_eq.py::test_eq[Group==Group] tests/test_config_eq.py::test_eq[dict==Group] tests/test_config_eq.py::test_eq[node_interpolation] tests/test_config_eq.py::test_not_eq[empty_dict_neq_dict] tests/test_config_eq.py::test_not_eq[empty_dict_vs_list] tests/test_config_eq.py::test_not_eq[dict_neq_none] tests/test_config_eq.py::test_not_eq[dict_none_neq_dict_not_none] tests/test_config_eq.py::test_not_eq[simple_dict_neq] tests/test_config_eq.py::test_not_eq[different_key_same_value] tests/test_config_eq.py::test_not_eq[input16-input26] tests/test_config_eq.py::test_not_eq[input17-input27] tests/test_config_eq.py::test_not_eq[input18-input28] tests/test_config_eq.py::test_not_eq[input19-input29] tests/test_config_eq.py::test_not_eq[input110-input210] tests/test_config_eq.py::test_not_eq[list:empty_vs_full] tests/test_config_eq.py::test_not_eq[list:different_value] tests/test_config_eq.py::test_not_eq[input113-input213] tests/test_config_eq.py::test_not_eq[input114-input214] tests/test_config_eq.py::test_not_eq[input115-input215] tests/test_config_eq.py::test_not_eq[input116-input216] tests/test_config_eq.py::test_not_eq[input117-input217] tests/test_config_eq.py::test_not_eq[node_interpolation] tests/test_config_eq.py::test_config_eq_mismatch_types tests/test_config_eq.py::test_dict_not_eq_with_another_class tests/test_merge.py::test_merge[inputs0-expected0] tests/test_merge.py::test_merge[inputs1-expected1] tests/test_merge.py::test_merge[inputs2-expected2] tests/test_merge.py::test_merge[inputs3-expected3] tests/test_merge.py::test_merge[inputs4-expected4] tests/test_merge.py::test_merge[inputs5-expected5] tests/test_merge.py::test_merge[inputs6-expected6] tests/test_merge.py::test_merge[inputs7-expected7] tests/test_merge.py::test_merge[inputs8-expected8] tests/test_merge.py::test_merge[inputs9-expected9] tests/test_merge.py::test_merge[inputs10-expected10] tests/test_merge.py::test_merge[inputs11-expected11] tests/test_merge.py::test_merge[inputs12-expected12] tests/test_merge.py::test_merge[inputs13-expected13] tests/test_merge.py::test_merge[inputs14-expected14] tests/test_merge.py::test_merge[inter:updating_data] tests/test_merge.py::test_merge[inter:value_inter_over_value_inter] tests/test_merge.py::test_merge[inter:data_over_value_inter] tests/test_merge.py::test_merge[inter:node_inter_over_value_inter] tests/test_merge.py::test_merge[inter:inter_over_data] tests/test_merge.py::test_merge[node_inter:node_update] tests/test_merge.py::test_merge[node_inter:value_inter_over_node_inter] tests/test_merge.py::test_merge[node_inter:data_over_node_inter] tests/test_merge.py::test_merge[node_inter:node_inter_over_node_inter] tests/test_merge.py::test_merge[inter:node_inter_over_data] tests/test_merge.py::test_merge[inputs26-expected26] tests/test_merge.py::test_merge[inputs27-expected27] tests/test_merge.py::test_merge[inputs28-expected28] tests/test_merge.py::test_merge[inputs29-expected29] tests/test_merge.py::test_merge[inputs30-expected30] tests/test_merge.py::test_merge[inputs31-expected31] tests/test_merge.py::test_merge[inputs32-expected32] tests/test_merge.py::test_merge[inputs33-expected33] tests/test_merge.py::test_merge[inputs34-expected34] tests/test_merge.py::test_merge[inputs35-expected35] tests/test_merge.py::test_merge[inputs36-expected36] tests/test_merge.py::test_merge[merge_missing_dict_into_missing_dict] tests/test_merge.py::test_merge[inputs38-expected38] tests/test_merge.py::test_merge[inputs39-expected39] tests/test_merge.py::test_merge[inputs40-ConcretePlugin] tests/test_merge.py::test_merge[merge_into_missing_node] tests/test_merge.py::test_primitive_dicts tests/test_merge.py::test_merge_no_eq_verify[a_0-b_0-expected0] tests/test_merge.py::test_merge_with_1 tests/test_merge.py::test_merge_with_2 tests/test_merge.py::test_3way_dict_merge tests/test_merge.py::test_merge_list_list tests/test_merge.py::test_merge_error[base0-merge0-TypeError] tests/test_merge.py::test_merge_error[base1-merge1-TypeError] tests/test_merge.py::test_merge_error[base2-None-ValueError] tests/test_merge.py::test_merge_error[base3-None-ValueError] tests/test_merge.py::test_with_readonly[c10-c20] tests/test_merge.py::test_with_readonly[c11-c21] tests/test_merge.py::test_into_readonly[c10-c20] tests/test_merge.py::test_into_readonly[c11-c21] tests/test_merge.py::test_parent_maintained
news/184.feature omegaconf/listconfig.py omegaconf/dictconfig.py omegaconf/nodes.py omegaconf/omegaconf.py omegaconf/base.py omegaconf/basecontainer.py
news/184.feature omegaconf/listconfig.py omegaconf/dictconfig.py omegaconf/nodes.py omegaconf/omegaconf.py omegaconf/base.py omegaconf/basecontainer.py