Repository: mit-ll-responsible-ai/hydra-zen

Issue:
meta fields dont get set properly when populate_full_signature=True
```python

def foo(a, b):
    return a, b
```

Here, `c` is appropriately included in the config.

```python
>>> print(to_yaml(builds(foo, a="${.c}", zen_meta=dict(c=2), populate_full_signature=False)))
_target_: hydra_zen.funcs.zen_processing
_zen_target: __main__.foo
_zen_exclude:
- c
a: ${.c}
c: 2
```

Setting `populate_full_signature=True`, `c` is no longer included in the config.
```python
>>> print(to_yaml(builds(foo, a="${.c}", zen_meta=dict(c=2), populate_full_signature=True)))
_target_: hydra_zen.funcs.zen_processing
_zen_target: __main__.foo
_zen_exclude:
- c
b: ???
a: ${.c}
```



Discussion:

Patch:
diff --git a/src/hydra_zen/structured_configs/_implementations.py b/src/hydra_zen/structured_configs/_implementations.py
index a05ae3d2..ce826967 100644
--- a/src/hydra_zen/structured_configs/_implementations.py
+++ b/src/hydra_zen/structured_configs/_implementations.py
@@ -1272,33 +1272,6 @@ def builds(
         for name, value in kwargs_for_target.items()
     }
 
-    if zen_meta:
-        _meta_names = set(zen_meta)
-
-        if _meta_names & nameable_params_in_sig:
-            raise ValueError(
-                f"`builds(..., zen_meta=<...>)`: `zen_meta` cannot not specify "
-                f"names that exist in the target's signature: "
-                f"{','.join(_meta_names & nameable_params_in_sig)}"
-            )
-
-        if _meta_names & set(user_specified_named_params):
-            raise ValueError(
-                f"`builds(..., zen_meta=<...>)`: `zen_meta` cannot not specify "
-                f"names that are common with those specified in **kwargs_for_target: "
-                f"{','.join(_meta_names & set(user_specified_named_params))}"
-            )
-
-        # We don't check for collisions between `zen_meta` names and the
-        # names of inherited fields. Thus `zen_meta` can effectively be used
-        # to "delete" names from a config, via inheritance.
-        user_specified_named_params.update(
-            {
-                name: (name, Any, sanitized_default_value(value))
-                for name, value in zen_meta.items()
-            }
-        )
-
     if populate_full_signature is True:
         # Populate dataclass fields based on the target's signature.
         #
@@ -1371,6 +1344,31 @@ def builds(
     else:
         base_fields.extend(user_specified_named_params.values())
 
+    if zen_meta:
+        _meta_names = set(zen_meta)
+
+        if _meta_names & nameable_params_in_sig:
+            raise ValueError(
+                f"`builds(..., zen_meta=<...>)`: `zen_meta` cannot not specify "
+                f"names that exist in the target's signature: "
+                f"{','.join(_meta_names & nameable_params_in_sig)}"
+            )
+
+        if _meta_names & set(user_specified_named_params):
+            raise ValueError(
+                f"`builds(..., zen_meta=<...>)`: `zen_meta` cannot not specify "
+                f"names that are common with those specified in **kwargs_for_target: "
+                f"{','.join(_meta_names & set(user_specified_named_params))}"
+            )
+
+        # We don't check for collisions between `zen_meta` names and the
+        # names of inherited fields. Thus `zen_meta` can effectively be used
+        # to "delete" names from a config, via inheritance.
+        base_fields.extend(
+            (name, Any, sanitized_default_value(value))
+            for name, value in zen_meta.items()
+        )
+
     if dataclass_name is None:
         if zen_partial is False:
             dataclass_name = f"Builds_{_utils.safe_name(target)}"


Test patch:
diff --git a/tests/test_meta_fields.py b/tests/test_meta_fields.py
index 4982b707..2f3d4f2f 100644
--- a/tests/test_meta_fields.py
+++ b/tests/test_meta_fields.py
@@ -43,7 +43,7 @@ def test_basic_zen_meta_behavior(
         **kwargs,
         zen_meta=zen_meta,
         zen_partial=zen_partial,
-        populate_full_signature=pop_sig
+        populate_full_signature=pop_sig,
     )
 
     conf = Conf()
@@ -63,6 +63,62 @@ def test_basic_zen_meta_behavior(
     assert set(out_kwargs).isdisjoint(zen_meta)
 
 
+def f_concrete_sig(a, b):
+    return (a, b)
+
+
+@given(
+    a=st.integers(),
+    b=st.integers(),
+    as_kwarg=st.booleans(),
+    zen_meta=st.dictionaries(
+        st.text(string.ascii_letters, min_size=1, max_size=1).filter(
+            lambda x: x not in {"a", "b"}
+        ),
+        st.integers(),
+    ),
+    zen_partial=st.booleans(),
+    pop_sig=st.booleans(),
+)
+def test_basic_zen_meta_behavior_vs_concrete_sig(
+    a: int,
+    b: int,
+    as_kwarg: bool,
+    zen_meta: dict,
+    zen_partial: bool,
+    pop_sig: bool,
+):
+    if as_kwarg:
+        args = ()
+        kwargs = dict(a=a, b=b)
+    else:
+        args = (a, b)
+        kwargs = {}
+
+    Conf = builds(
+        f_concrete_sig,
+        *args,
+        **kwargs,
+        zen_meta=zen_meta,
+        zen_partial=zen_partial,
+        populate_full_signature=pop_sig,
+    )
+
+    conf = Conf()
+
+    # ensure all meta-fields are present
+    for meta_name, meta_val in zen_meta.items():
+        assert getattr(conf, meta_name) == meta_val
+
+    if not zen_partial:
+        out_args = instantiate(Conf)
+    else:
+        assert is_partial_builds(Conf)
+        out_args = instantiate(Conf)()
+
+    assert out_args == (a, b)
+
+
 def test_zen_meta_via_hydrated_dataclass():
     @hydrated_dataclass(dict, zen_meta=dict(a=1))
     class Conf: