diff --git a/omegaconf/base.py b/omegaconf/base.py index d76e557ad..ff8793db5 100644 --- a/omegaconf/base.py +++ b/omegaconf/base.py @@ -403,7 +403,7 @@ def _resolve_interpolation_from_parse_tree( ) -> Optional["Node"]: from .basecontainer import BaseContainer from .nodes import AnyNode, ValueNode - from .omegaconf import _node_wrap + from .omegaconf import OmegaConf, _node_wrap try: resolved = self.resolve_parse_tree( @@ -420,8 +420,22 @@ def _resolve_interpolation_from_parse_tree( # custom resolver), then we will need to wrap it within a Node. must_wrap = not isinstance(resolved, Node) - # If the node is typed, validate (and possibly convert) the result. - if isinstance(value, ValueNode) and not isinstance(value, AnyNode): + if isinstance(value, AnyNode): + # If the node is untyped, we still need to validate against `None` + # in case the `optional` flag is set to `False`. + if not value._is_optional() and OmegaConf.is_none(resolved): + if throw_on_resolution_failure: + self._format_and_raise( + key=key, + value=resolved, + cause=InterpolationValidationError( + "Non optional field cannot be assigned None" + ), + ) + return None + + elif isinstance(value, ValueNode): + # If the node is typed, validate (and possibly convert) the result. res_value = _get_value(resolved) try: conv_value = value.validate_and_convert(res_value) diff --git a/tests/test_interpolation.py b/tests/test_interpolation.py index 8d28ad570..d64d549f1 100644 --- a/tests/test_interpolation.py +++ b/tests/test_interpolation.py @@ -920,6 +920,53 @@ def cast(t: Any, v: Any) -> Any: assert OmegaConf.select(cfg, key, throw_on_resolution_failure=False) is None +@pytest.mark.parametrize( + ("cfg", "key", "expected_error"), + [ + ( + {"x": "${none}", "none": None}, + "x", + pytest.raises( + InterpolationValidationError, + match=re.escape( + dedent( + """\ + Non optional field cannot be assigned None + full_key: x + """ + ) + ), + ), + ), + ( + {"x": "${get_none:}"}, + "x", + pytest.raises( + InterpolationValidationError, + match=re.escape( + dedent( + """\ + Non optional field cannot be assigned None + full_key: x + """ + ) + ), + ), + ), + ], +) +def test_interpolation_non_optional_any( + restore_resolvers: Any, cfg: Any, key: str, expected_error: Any +) -> None: + OmegaConf.register_new_resolver("get_none", lambda: None) + cfg = OmegaConf.create(cfg) + node = cfg._get_node(key) + node._metadata.optional = False + with expected_error: + cfg[key] + assert OmegaConf.select(cfg, key, throw_on_resolution_failure=False) is None + + def test_type_validation_error_no_throw() -> None: cfg = OmegaConf.structured(User(name="Bond", age=SI("${name}"))) bad_node = cfg._get_node("age")