Repository: mit-ll-responsible-ai/hydra-zen
Pull request #224 (2022-02-11 17:21:41)

Add autocomplete support for `builds(<target>, populate_full_signature=True)`
This PR:

1. Leverages [ParamSpec](https://www.python.org/dev/peps/pep-0612/) to enable IDE autocompletion on the configs returned by `builds(<target>, populate_full_signature=True)`.
2. Adds overloads to `make_custom_builds_fn` to reflect relevant updated default values on the resulting `builds` function. Specifically, this holds for `zen_partial=True` and `populate_full_signature=True`, respectively.
3. Makes `ZenWrappers` a "public" type via `hydra_zen.typing`

## Adding autocomplete for `builds(<target>, populate_full_signature=True)`

Consider the following builds pattern:

```python
def f(x: int, y: str = "hello") -> bool:
    return False


Conf = builds(f, populate_full_signature=True)
```

Presently, IDEs / type-checkers cannot infer the signature to `Conf`, even though the user can infer that the full signature of `f` is reflected in `Conf`. This PR fixes that!

**Before:**

![image](https://user-images.githubusercontent.com/29104956/153636476-91fb7960-078c-4195-898b-ee7ee4d1448a.png)


**After:**

![image](https://user-images.githubusercontent.com/29104956/153636566-bde0ccb6-f60f-4f1f-b4e6-829a483c4afb.png)


This means that instances of `Conf` are now amenable to static type-checking:

![image](https://user-images.githubusercontent.com/29104956/153637215-8e3bf928-9fcb-48e3-86bb-bcd2e6aacf63.png)

Lastly, type-checkers are still able to "see" the correct type returned via `instantiate`:

![image](https://user-images.githubusercontent.com/29104956/153637419-fdd31b0c-507c-46b7-ab2e-e42e18ed63f0.png)

## Adding overloads to `make_custom_builds_fn`

Previously, we did not provide type-checkers with "dynamic" information about the builds-function produced by `make_custom_builds_fn`. Now, type-checkers can "see" some of the new defaults in the `builds` function created by `make_custom_builds_fn`.

**Before**:

```python
partial_builds = make_custom_builds_fn(zen_partial=True)

# type-checker doesn't "see" that `zen_partial=True`
Conf = partial_builds(int)  # type: Type[Builds[Type[int]]]
```

**After**

```python
partial_builds = make_custom_builds_fn(zen_partial=True)

# `zen_partial=True` is inferred by type-checker!
Conf = partial_builds(int)  # type: Type[PartialBuilds[Type[int]]]


# `Manually-overriding zen_partial behaves as-expected
Conf = partial_builds(int, zen_partial=False)  # type: Type[Builds[Type[int]]]
```

This is especially nice, because now the aforementioned improvements to static inference on  `builds(<target>, populate_full_signature=True)` can manifest via `make_custom_builds_fn` (otherwise, users would always have to write 
`builds(<target>, populate_full_signature=True)` to get the nice static inference):

```python

def f(x: int): return

full_builds = make_custom_builds_fn(populate_full_signature=True)

Conf = full_builds(f)  # type: Conf: (x: int) -> Builds[Type[None]]

Conf(x="hi")  # type-checker: error
```

This was accomplished by adding special overloads for `builds` with updated default values -- specifically for `populate_full_signature=True` and `zen_partial=True`, respectively -- which are stored in `hydra_zen.typing._builds_overloads`. We then use some fanciness to conditionally dispatch the output of `make_custom_builds_fn` using these specialized overloads. 

This adds some unfortunate boilerplate and redundancies, but I think it is ultimately well worth it. Otherwise the very common use case of `make_custom_builds_fn(populate_full_signature=True)` would not be as powerful as manually writing `builds(<t>, populate_full_signature=True)`; furthermore, it would be highly non-trivial for end users to write their own aliases for `builds(<t>, populate_full_signature=True)` such that they would have the desired type-inference behavior.

## Some Implementation Details

This signature reflection occurs *only* when:
- `populate_full_signature=True` is specified 
- *and* `zen_partial=False`
- *and* when no `*args`/`**kwargs**` are provided by the user to `builds
- *and* no "parents" have been supplied via `builds_bases`
 
The reason for this is that when any of these conditions do not hold, then the user does not need to specify all of the parameters in the target's signature. E.g. consider:

```python
Conf = builds(f, x=1, populate_full_signature=True)
```

because `x=1` is specified, the *true* signature of `Conf` is `(x: int = 1. y: str = "hello")`. Unfortunately, there is not way (that I know of) to tell the type-checkers about this modification to the signature. Thus if we enabled signature-reflection in this case, then you would get the false-negative:

```python
# This is OK at runtime, but
# type-checker sees error: missing value x
Conf()
```

I do not want users to ever encounter false-positives due to our type annotations. Thus we disable signature reflection here:

![image](https://user-images.githubusercontent.com/29104956/153683958-a9de0a58-9bc4-4f7f-8966-9bce0be869ca.png)


It is also noteworthy that functions with `overloads` are not amenable to being `ParamSpec`'d:

![image](https://user-images.githubusercontent.com/29104956/153641423-85365235-cd88-4b2c-ba35-8299f83b76ec.png)



rsokl: VSCode even reflects the docstring!

![image](https://user-images.githubusercontent.com/29104956/153642705-280919be-f0f9-4775-ae49-e5afc902b219.png)

esafak: Is this only for Python 3.10+?
rsokl: > Is this only for Python 3.10+?

No, this works for Python 3.6+. We use the backport of `ParamSpec` provided by `typing_extensions`.

Patch:
diff --git a/README.md b/README.md
index 6854eb64..d34b4000 100644
--- a/README.md
+++ b/README.md
@@ -40,18 +40,11 @@ hydra-zen is a Python library that simplifies the process of writing code (resea
 
 It builds off – and is fully compatible with – [Hydra](https://hydra.cc/), a framework for elegantly 
 configuring complex applications. hydra-zen helps simplify the 
-process of using Hydra by providing specialized functions for :ref:`creating configs <create-config>` and 
-launching Hydra jobs. 
-
-hydra-zen also also provides Hydra users with powerful, novel functionality. With it, we can:
-
-- Add enhanced runtime type-checking for our Hydra application, via pydantic, beartype, and other third-party libraries.
-- Design configs with specialized behaviors, like partial configs, or configs with meta-fields. 
-- Use additional data types in our configs, like `pathlib.Path`, that are not natively supported by Hydra.
-- Validate our configs at runtime, prior to launching our application.
-- Leverage a powerful functionality-injection framework in our Hydra applications.
-- Run static type-checkers on our config-creation code to catch incompatibilities with Hydra.
-    
+process of using Hydra by providing specialized functions for creating configs and 
+launching Hydra jobs. Please refer to [our documentation](https://mit-ll-responsible-ai.github.io/hydra-zen/) for examples and more details.
+
+
+  
 ## Installation
 `hydra-zen` is lightweight: its only dependencies are `hydra-core` and `typing-extensions`.
 
diff --git a/src/hydra_zen/_hydra_overloads.py b/src/hydra_zen/_hydra_overloads.py
index d575094d..a0f4d03b 100644
--- a/src/hydra_zen/_hydra_overloads.py
+++ b/src/hydra_zen/_hydra_overloads.py
@@ -90,6 +90,13 @@ def instantiate(
     ...
 
 
+@overload
+def instantiate(
+    config: Callable[..., Builds[Type[T]]], *args: Any, **kwargs: Any
+) -> T:  # pragma: no cover
+    ...
+
+
 @overload
 def instantiate(
     config: Union[Builds[Type[T]], Type[Builds[Type[T]]]], *args: Any, **kwargs: Any
diff --git a/src/hydra_zen/structured_configs/_implementations.py b/src/hydra_zen/structured_configs/_implementations.py
index 3ab0f023..f89e59ed 100644
--- a/src/hydra_zen/structured_configs/_implementations.py
+++ b/src/hydra_zen/structured_configs/_implementations.py
@@ -19,7 +19,6 @@ from enum import Enum
 from functools import wraps
 from itertools import chain
 from typing import (
-    TYPE_CHECKING,
     Any,
     Callable,
     Dict,
@@ -39,7 +38,7 @@ from typing import (
 )
 
 from omegaconf import DictConfig, ListConfig
-from typing_extensions import Final, Literal, TypeGuard
+from typing_extensions import Final, Literal, ParamSpec, TypeGuard
 
 from hydra_zen._compatibility import (
     HYDRA_SUPPORTED_PRIMITIVES,
@@ -57,6 +56,11 @@ from hydra_zen.typing import (
     Partial,
     PartialBuilds,
     SupportedPrimitive,
+    ZenWrappers,
+)
+from hydra_zen.typing._builds_overloads import (
+    full_builds as _full_builds,
+    partial_builds as _partial_builds,
 )
 from hydra_zen.typing._implementations import DataClass, HasTarget, _DataClass
 
@@ -64,21 +68,10 @@ from ._value_conversion import ZEN_VALUE_CONVERSION
 
 _T = TypeVar("_T")
 _T2 = TypeVar("_T2", bound=Callable)
-ZenWrapper = Union[
-    None,
-    Builds[Callable[[_T2], _T2]],
-    PartialBuilds[Callable[[_T2], _T2]],
-    Just[Callable[[_T2], _T2]],
-    Type[Builds[Callable[[_T2], _T2]]],
-    Type[PartialBuilds[Callable[[_T2], _T2]]],
-    Type[Just[Callable[[_T2], _T2]]],
-    Callable[[_T2], _T2],
-    str,
-]
-if TYPE_CHECKING:  # pragma: no cover
-    ZenWrappers = Union[ZenWrapper, Sequence[ZenWrapper]]
-else:
-    ZenWrappers = TypeVar("ZenWrappers")
+
+P = ParamSpec("P")
+R = TypeVar("R")
+
 
 # Hydra-specific fields
 _TARGET_FIELD_NAME: Final[str] = "_target_"
@@ -613,61 +606,101 @@ def sanitized_field(
     )
 
 
-# overloads when `zen_partial=False`
+# `builds(<t>, [*args], [**kwargs], zen_partial=False, populate_full_signature=False)`
 @overload
 def builds(
     hydra_target: Importable,
     *pos_args: SupportedPrimitive,
-    zen_partial: Literal[False] = False,
-    zen_wrappers: ZenWrappers = tuple(),
-    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = None,
-    populate_full_signature: bool = False,
-    hydra_recursive: Optional[bool] = None,
-    hydra_convert: Optional[Literal["none", "partial", "all"]] = None,
-    dataclass_name: Optional[str] = None,
-    builds_bases: Tuple[Type[_DataClass], ...] = (),
-    frozen: bool = False,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: Literal[False] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
     **kwargs_for_target: SupportedPrimitive,
 ) -> Type[Builds[Importable]]:  # pragma: no cover
     ...
 
 
-# overloads when `zen_partial=True`
+# `builds(<t>, zen_partial=False, populate_full_signature=True)`
+# -- without any user-specified args/kwargs or bases for the target
+@overload
+def builds(
+    hydra_target: Callable[P, R],
+    *,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: Literal[True],
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[()] = ...,
+    frozen: bool = ...,
+) -> Callable[P, Builds[Type[R]]]:  # pragma: no cover
+    ...
+
+
+# `builds(<t>, *args, **kwargs, zen_partial=False, populate_full_signature=True)`
 @overload
 def builds(
     hydra_target: Importable,
     *pos_args: SupportedPrimitive,
-    zen_partial: Literal[True],
-    zen_wrappers: ZenWrappers = tuple(),
-    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = None,
-    populate_full_signature: bool = False,
-    hydra_recursive: Optional[bool] = None,
-    hydra_convert: Optional[Literal["none", "partial", "all"]] = None,
-    dataclass_name: Optional[str] = None,
-    builds_bases: Tuple[Type[_DataClass], ...] = (),
-    frozen: bool = False,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
     **kwargs_for_target: SupportedPrimitive,
-) -> Type[PartialBuilds[Importable]]:  # pragma: no cover
+) -> Type[Builds[Importable]]:  # pragma: no cover
     ...
 
 
-# overloads when `zen_partial: bool`
+# `builds(<t>, [*args], [**kwargs], zen_partial=True)`
 @overload
 def builds(
     hydra_target: Importable,
     *pos_args: SupportedPrimitive,
-    zen_partial: bool,
-    zen_wrappers: ZenWrappers = tuple(),
-    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = None,
-    populate_full_signature: bool = False,
-    hydra_recursive: Optional[bool] = None,
-    hydra_convert: Optional[Literal["none", "partial", "all"]] = None,
-    dataclass_name: Optional[str] = None,
-    builds_bases: Tuple[Type[_DataClass], ...] = (),
-    frozen: bool = False,
+    zen_partial: Literal[True] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Type[PartialBuilds[Importable]]:  # pragma: no cover
+    ...
+
+
+# broadest annotation
+@overload
+def builds(
+    hydra_target: Union[Importable, Callable[P, R]],
+    *pos_args: SupportedPrimitive,
+    zen_partial: bool = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
     **kwargs_for_target: SupportedPrimitive,
 ) -> Union[
-    Type[Builds[Importable]], Type[PartialBuilds[Importable]]
+    Type[Builds[Importable]],
+    Type[PartialBuilds[Importable]],
+    Callable[P, Builds[Type[R]]],
 ]:  # pragma: no cover
     ...
 
@@ -684,7 +717,7 @@ def builds(
     builds_bases=(),
     dataclass_name=None,
     **kwargs_for_target,
-) -> Union[Type[Builds[Importable]], Type[PartialBuilds[Importable]]]:
+):
     """builds(hydra_target, /, *pos_args, zen_partial=False, zen_meta=None,
     hydra_recursive=None, populate_full_signature=False, hydra_convert=None,
     frozen=False, dataclass_name=None, builds_bases=(), **kwargs_for_target)
@@ -1679,7 +1712,7 @@ def builds(
             out.__doc__ += (
                 f"\n\nThe docstring for {_utils.safe_name(target)} :\n\n" + target_doc
             )
-    return cast(Type[Builds[Importable]], out)
+    return cast(Union[Type[Builds[Importable]], Callable[P, Builds[Type[R]]]], out)
 
 
 # We need to check if things are Builds, Just, PartialBuilds to a higher
@@ -1860,20 +1893,84 @@ __BUILDS_DEFAULTS: Final[Dict[str, Any]] = {
 del _builds_sig
 
 
+@overload
 def make_custom_builds_fn(
     *,
-    zen_partial: bool = False,
-    zen_wrappers: ZenWrappers = tuple(),
-    zen_meta: Optional[Mapping[str, Any]] = None,
-    populate_full_signature: bool = False,
-    hydra_recursive: Optional[bool] = None,
-    hydra_convert: Optional[Literal["none", "partial", "all"]] = None,
-    frozen: bool = False,
-    builds_bases: Tuple[Any, ...] = (),
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, Any]] = ...,
+    populate_full_signature: Literal[False] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    frozen: bool = ...,
+    builds_bases: Tuple[Any, ...] = ...,
+    __b: _T2 = builds,
+) -> _T2:  # pragma: no cover
+    ...
+
+
+@overload
+def make_custom_builds_fn(
+    *,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, Any]] = ...,
+    populate_full_signature: Literal[True] = ...,  # <- special case
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    frozen: bool = ...,
+    builds_bases: Tuple[()] = ...,
+    __b: _T2 = _full_builds,
+) -> _T2:  # pragma: no cover
+    ...
+
+
+@overload
+def make_custom_builds_fn(
+    *,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, Any]] = ...,
+    populate_full_signature: Literal[True] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    frozen: bool = ...,
+    builds_bases: Tuple[Any, ...] = ...,
+    __b: _T2 = builds,
+) -> _T2:  # pragma: no cover
+    ...
+
+
+@overload
+def make_custom_builds_fn(
+    *,
+    zen_partial: Literal[True] = ...,  # <- special case
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, Any]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    frozen: bool = ...,
+    builds_bases: Tuple[Any, ...] = ...,
+    __b: _T2 = _partial_builds,
+) -> _T2:  # pragma: no cover
+    ...
+
+
+def make_custom_builds_fn(
+    *,
+    zen_partial=False,
+    zen_wrappers=tuple(),
+    zen_meta=None,
+    populate_full_signature=False,
+    hydra_recursive=None,
+    hydra_convert=None,
+    frozen=False,
+    builds_bases=(),
     # This is the easiest way to get static tooling to see the output
     # as `builds`.
-    __b: _T2 = builds,
-) -> _T2:
+    __b=builds,
+):
     """Returns the `builds` function, but with customized default values.
 
     E.g. ``make_custom_builds_fn(hydra_convert='all')`` will return a version
diff --git a/src/hydra_zen/typing/__init__.py b/src/hydra_zen/typing/__init__.py
index 3e44b4f5..24824dac 100644
--- a/src/hydra_zen/typing/__init__.py
+++ b/src/hydra_zen/typing/__init__.py
@@ -8,6 +8,7 @@ from ._implementations import (
     Partial,
     PartialBuilds,
     SupportedPrimitive,
+    ZenWrappers,
 )
 
 __all__ = [
@@ -17,4 +18,5 @@ __all__ = [
     "Partial",
     "PartialBuilds",
     "SupportedPrimitive",
+    "ZenWrappers",
 ]
diff --git a/src/hydra_zen/typing/_builds_overloads.py b/src/hydra_zen/typing/_builds_overloads.py
new file mode 100644
index 00000000..87c07d7b
--- /dev/null
+++ b/src/hydra_zen/typing/_builds_overloads.py
@@ -0,0 +1,244 @@
+# Copyright (c) 2022 Massachusetts Institute of Technology
+# SPDX-License-Identifier: MIT
+
+# Stores overloads for `builds` with different default-values for signature
+
+from typing import Callable, Mapping, Optional, Tuple, Type, TypeVar, Union, overload
+
+from typing_extensions import Literal, ParamSpec
+
+from ._implementations import (
+    Builds,
+    Importable,
+    PartialBuilds,
+    SupportedPrimitive,
+    ZenWrappers,
+    _DataClass,
+)
+
+R = TypeVar("R")
+P = ParamSpec("P")
+
+__all__ = ["full_builds", "partial_builds"]
+
+
+# full_builds -> populate_full_signature=True  (new default value)
+@overload
+def full_builds(
+    hydra_target: Callable[P, R],
+    *,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: Literal[True] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[()] = ...,
+    frozen: bool = ...,
+) -> Callable[P, Builds[Type[R]]]:  # pragma: no cover
+    ...
+
+
+@overload
+def full_builds(
+    hydra_target: Importable,
+    *pos_args: SupportedPrimitive,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: Literal[False] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Type[Builds[Importable]]:  # pragma: no cover
+    ...
+
+
+@overload
+def full_builds(
+    hydra_target: Importable,
+    *pos_args: SupportedPrimitive,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Type[Builds[Importable]]:  # pragma: no cover
+    ...
+
+
+@overload
+def full_builds(
+    hydra_target: Importable,
+    *pos_args: SupportedPrimitive,
+    zen_partial: Literal[True] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Type[PartialBuilds[Importable]]:  # pragma: no cover
+    ...
+
+
+@overload
+def full_builds(
+    hydra_target: Union[Importable, Callable[P, R]],
+    *pos_args: SupportedPrimitive,
+    zen_partial: bool = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Union[
+    Type[Builds[Importable]],
+    Type[PartialBuilds[Importable]],
+    Callable[P, Builds[Type[R]]],
+]:  # pragma: no cover
+    ...
+
+
+def full_builds(  # type: ignore
+    *pos_args,
+    zen_partial=False,
+    zen_wrappers=tuple(),
+    zen_meta=None,
+    populate_full_signature=True,  # updated default
+    hydra_recursive=None,
+    hydra_convert=None,
+    frozen: bool = False,
+    builds_bases=(),
+    dataclass_name=None,
+    **kwargs_for_target,
+):  # pragma: no cover
+    raise NotImplementedError()
+
+
+# partial_builds -> zen_partial=True (new default value)
+
+
+@overload
+def partial_builds(
+    hydra_target: Importable,
+    *pos_args: SupportedPrimitive,
+    zen_partial: Literal[True] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Type[PartialBuilds[Importable]]:  # pragma: no cover
+    ...
+
+
+@overload
+def partial_builds(
+    hydra_target: Callable[P, R],
+    *,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: Literal[True] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[()] = ...,
+    frozen: bool = ...,
+) -> Callable[P, Builds[Type[R]]]:  # pragma: no cover
+    ...
+
+
+@overload
+def partial_builds(
+    hydra_target: Importable,
+    *pos_args: SupportedPrimitive,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: Literal[False] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Type[Builds[Importable]]:  # pragma: no cover
+    ...
+
+
+@overload
+def partial_builds(
+    hydra_target: Importable,
+    *pos_args: SupportedPrimitive,
+    zen_partial: Literal[False] = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Type[Builds[Importable]]:  # pragma: no cover
+    ...
+
+
+@overload
+def partial_builds(
+    hydra_target: Union[Importable, Callable[P, R]],
+    *pos_args: SupportedPrimitive,
+    zen_partial: bool = ...,
+    zen_wrappers: ZenWrappers = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    populate_full_signature: bool = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[_DataClass], ...] = ...,
+    frozen: bool = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Union[
+    Type[Builds[Importable]],
+    Type[PartialBuilds[Importable]],
+    Callable[P, Builds[Type[R]]],
+]:  # pragma: no cover
+    ...
+
+
+def partial_builds(  # type: ignore
+    *pos_args,
+    zen_partial=True,  # updated default
+    zen_wrappers=tuple(),
+    zen_meta=None,
+    populate_full_signature=False,
+    hydra_recursive=None,
+    hydra_convert=None,
+    frozen: bool = False,
+    builds_bases=(),
+    dataclass_name=None,
+    **kwargs_for_target,
+):  # pragma: no cover
+    raise NotImplementedError()
diff --git a/src/hydra_zen/typing/_implementations.py b/src/hydra_zen/typing/_implementations.py
index c5adad78..0baa3cbe 100644
--- a/src/hydra_zen/typing/_implementations.py
+++ b/src/hydra_zen/typing/_implementations.py
@@ -5,6 +5,7 @@ from dataclasses import Field
 from enum import Enum
 from pathlib import Path
 from typing import (
+    TYPE_CHECKING,
     Any,
     Callable,
     ClassVar,
@@ -29,6 +30,7 @@ __all__ = [
     "Partial",
     "Importable",
     "SupportedPrimitive",
+    "ZenWrappers",
 ]
 
 
@@ -36,10 +38,12 @@ class EmptyDict(TypedDict):
     pass
 
 
-_T = TypeVar("_T", covariant=True)
+T = TypeVar("T", covariant=True)
 T2 = TypeVar("T2")
 T3 = TypeVar("T3")
 
+T4 = TypeVar("T4", bound=Callable)
+
 
 @runtime_checkable
 class Partial(Protocol[T2]):
@@ -76,18 +80,18 @@ class DataClass(_DataClass, Protocol):  # pragma: no cover
 
 
 @runtime_checkable
-class Builds(DataClass, Protocol[_T]):  # pragma: no cover
+class Builds(DataClass, Protocol[T]):  # pragma: no cover
     _target_: ClassVar[str]
 
 
 @runtime_checkable
-class Just(Builds, Protocol[_T]):  # pragma: no cover
+class Just(Builds, Protocol[T]):  # pragma: no cover
     path: ClassVar[str]  # interpolated string for importing obj
     _target_: ClassVar[Literal["hydra_zen.funcs.get_obj"]] = "hydra_zen.funcs.get_obj"
 
 
 @runtime_checkable
-class PartialBuilds(Builds, Protocol[_T]):  # pragma: no cover
+class PartialBuilds(Builds, Protocol[T]):  # pragma: no cover
     _target_: ClassVar[
         Literal["hydra_zen.funcs.zen_processing"]
     ] = "hydra_zen.funcs.zen_processing"
@@ -96,7 +100,7 @@ class PartialBuilds(Builds, Protocol[_T]):  # pragma: no cover
 
 
 @runtime_checkable
-class HydraPartialBuilds(Builds, Protocol[_T]):  # pragma: no cover
+class HydraPartialBuilds(Builds, Protocol[T]):  # pragma: no cover
     _partial_: ClassVar[Literal[True]] = True
 
 
@@ -144,3 +148,20 @@ SupportedPrimitive = Union[
     # Mapping is covariant only in value
     Mapping[Any, "SupportedPrimitive"],
 ]
+
+
+ZenWrapper = Union[
+    None,
+    Builds[Callable[[T4], T4]],
+    PartialBuilds[Callable[[T4], T4]],
+    Just[Callable[[T4], T4]],
+    Type[Builds[Callable[[T4], T4]]],
+    Type[PartialBuilds[Callable[[T4], T4]]],
+    Type[Just[Callable[[T4], T4]]],
+    Callable[[T4], T4],
+    str,
+]
+if TYPE_CHECKING:  # pragma: no cover
+    ZenWrappers = Union[ZenWrapper, Sequence[ZenWrapper]]
+else:
+    ZenWrappers = TypeVar("ZenWrappers")


Test patch:
diff --git a/tests/annotations/behaviors.py b/tests/annotations/behaviors.py
index a83833bb..b444f84e 100644
--- a/tests/annotations/behaviors.py
+++ b/tests/annotations/behaviors.py
@@ -80,7 +80,7 @@ def behaviors():
 
     # Check that @hydrated_dataclass reveals init/attr info
     b = B(x=2)
-    b.x = 3
+    reveal_type(b.x, expected_str="int")
 
     # Check that `Builds` constructor can take arguments
     X = builds(dict, a=1)
@@ -89,3 +89,6 @@ def behaviors():
     PartialBuild_g = builds(g, x=1, zen_partial=True)
     partial_g = instantiate(PartialBuild_g)
     g_out: str = partial_g(y=10)
+
+    Conf = builds(f, populate_full_signature=True)
+    reveal_type(Conf(x=1).x, expected_str="Any")
diff --git a/tests/annotations/declarations.py b/tests/annotations/declarations.py
index 0a3be995..249e772c 100644
--- a/tests/annotations/declarations.py
+++ b/tests/annotations/declarations.py
@@ -33,6 +33,7 @@ from hydra_zen import (
     mutable_value,
 )
 from hydra_zen.typing import Builds, Partial
+from hydra_zen.typing._builds_overloads import full_builds, partial_builds
 from hydra_zen.typing._implementations import HydraPartialBuilds
 
 T = TypeVar("T")
@@ -430,3 +431,208 @@ def check_protocols():
     Just = just(int)
     reveal_type(Just._target_, expected_text="Literal['hydra_zen.funcs.get_obj']")
     reveal_type(Just()._target_, expected_text="Literal['hydra_zen.funcs.get_obj']")
+
+
+def check_populate_full_sig():
+    class C:
+        def __init__(self) -> None:
+            pass
+
+    def f(x: int, y: str, z: bool = False):
+        return C()
+
+    # should be able to specify arguments other than `populate_full_signature=True`
+    Conf_f = builds(
+        f,
+        populate_full_signature=True,
+        zen_wrappers=[None],
+        zen_meta=dict(a=1),
+        hydra_convert="all",
+    )
+
+    # The following should get flagged by type-checkers
+
+    Conf_f()  # type: ignore
+    Conf_f(not_a_arg=1)  # type: ignore
+    Conf_f("not int", "a string")  # type: ignore
+    Conf_f(x="not int", y="a string")  # type: ignore
+    Conf_f(x=1, y=2)  # type: ignore
+    Conf_f(x=1, y="a string", z="not a bool")  # type: ignore
+
+    # The following should be ok
+    reveal_type(Conf_f(1, "hi"), expected_text="Builds[Type[C]]")
+    reveal_type(Conf_f(1, "hi", True), expected_text="Builds[Type[C]]")
+    reveal_type(Conf_f(1, y="hi"), expected_text="Builds[Type[C]]")
+    reveal_type(Conf_f(x=1, y="hi", z=False), expected_text="Builds[Type[C]]")
+
+    # check instantiation
+    reveal_type(instantiate(Conf_f), expected_text="C")
+
+    conf1 = Conf_f(1, "hi")
+    reveal_type(instantiate(conf1), expected_text="C")
+
+    Conf_C = builds(C, populate_full_signature=True)
+
+    # Should be flagged by type-checker
+    Conf_C(not_a_arg=1)  # type: ignore
+
+    reveal_type(instantiate(Conf_C), expected_text="C")
+    reveal_type(instantiate(Conf_C()), expected_text="C")
+
+    # specifying `zen_partial=True` should disable sig-reflection
+    Conf_f_partial = builds(f, populate_full_signature=True, zen_partial=True)
+    conf2 = Conf_f_partial(not_a_valid_arg=1)  # should be ok
+    reveal_type(
+        conf2, expected_text="PartialBuilds[(x: int, y: str, z: bool = False) -> C]"
+    )
+
+    # specifying `populate_full_signature=False` should disable sig-reflection
+    Conf_f_not_full = builds(f, populate_full_signature=False)
+    conf3 = Conf_f_not_full(not_a_valid_arg=1)  # should be ok
+    reveal_type(conf3, expected_text="Builds[(x: int, y: str, z: bool = False) -> C]")
+
+    # not specifying `populate_full_signature` should disable sig-reflection
+    Conf_f_not_full2 = builds(f)
+    conf4 = Conf_f_not_full2(not_a_valid_arg=1)  # should be ok
+    reveal_type(conf4, expected_text="Builds[(x: int, y: str, z: bool = False) -> C]")
+
+    # Providing any *args directly in `builds` should distable sig-reflection
+    Conf_f_with_args = builds(f, 1, populate_full_signature=True)
+    conf5 = Conf_f_with_args()  # should be ok
+    reveal_type(conf5, expected_text="Builds[(x: int, y: str, z: bool = False) -> C]")
+
+    # Providing any **kwargs directly in `builds` should distable sig-reflection
+    Conf_f_with_kwargs = builds(f, x=1, populate_full_signature=True)
+    conf6 = Conf_f_with_kwargs()  # should be ok
+    reveal_type(conf6, expected_text="Builds[(x: int, y: str, z: bool = False) -> C]")
+
+    # Providing any bases in `builds` should distable sig-reflection
+    Parent = make_config(x=1)
+    Conf_f_with_base = builds(f, populate_full_signature=True, builds_bases=(Parent,))
+    conf7 = Conf_f_with_base()  # should be ok
+    reveal_type(conf7, expected_text="Builds[(x: int, y: str, z: bool = False) -> C]")
+
+
+def check_full_builds():
+    def f(x: int, y: str, z: bool = False):
+        return 1
+
+    # type-checker should see default: `populate_full_signature=True`
+    Conf_f = full_builds(f)
+    Conf_f()  # type: ignore
+
+    # explicitly specifying `populate_full_signature=True` should produce
+    # same behaviore
+    Conf_f2 = full_builds(f, populate_full_signature=True)
+    Conf_f2()  # type: ignore
+
+    Conf_f3 = full_builds(f, zen_partial=True)
+    reveal_type(
+        Conf_f3,
+        expected_text="Type[PartialBuilds[(x: int, y: str, z: bool = False) -> Literal[1]]]",
+    )
+    Conf_f3()
+
+
+def check_partial_builds():
+    def f(x: int, y: str, z: bool = False):
+        return 1
+
+    # type-checker should see default: `populate_full_signature=True`
+    Conf_f = partial_builds(f)
+    reveal_type(
+        Conf_f,
+        expected_text="Type[PartialBuilds[(x: int, y: str, z: bool = False) -> Literal[1]]]",
+    )
+
+    # type-checker should see default: `populate_full_signature=True`
+    Conf_f2 = partial_builds(f, zen_partial=True)
+    reveal_type(
+        Conf_f2,
+        expected_text="Type[PartialBuilds[(x: int, y: str, z: bool = False) -> Literal[1]]]",
+    )
+
+    # signature should be required
+    Conf_f3 = partial_builds(f, zen_partial=False, populate_full_signature=True)
+    Conf_f3()  # type: ignore
+
+
+def check_make_custom_builds_no_args():
+    def f(x: int, y: str, z: bool = False):
+        return 1
+
+    builds_ = make_custom_builds_fn()
+    Conf = builds_(f)
+    Conf()  # should be OK
+
+    reveal_type(
+        Conf,
+        expected_text="Type[Builds[(x: int, y: str, z: bool = False) -> Literal[1]]]",
+    )
+
+    Parent = make_config(x=1)
+
+    # specifying parent should produce default `builds`
+    builds2 = make_custom_builds_fn(
+        populate_full_signature=True, builds_bases=(Parent,)
+    )
+
+    Conf2 = builds2(f)
+
+    reveal_type(
+        Conf2,
+        expected_text="Type[Builds[(x: int, y: str, z: bool = False) -> Literal[1]]]",
+    )
+
+
+def check_make_custom_builds_pop_sig():
+    def f(x: int, y: str, z: bool = False):
+        return 1
+
+    full_builds = make_custom_builds_fn(populate_full_signature=True)
+
+    Conf = full_builds(f)
+    Conf()  # type: ignore
+
+    reveal_type(
+        Conf,
+        expected_text="(x: int, y: str, z: bool = ...) -> Builds[Type[int]]",
+    )
+
+
+def check_make_custom_builds_partial():
+    def f(x: int, y: str, z: bool = False) -> int:
+        return 1
+
+    partial_builds = make_custom_builds_fn(zen_partial=True)
+
+    Conf = partial_builds(f)
+
+    reveal_type(
+        Conf,
+        expected_text="Type[PartialBuilds[(x: int, y: str, z: bool = False) -> int]]",
+    )
+
+    partial_builds2 = make_custom_builds_fn(
+        zen_partial=True, populate_full_signature=True
+    )
+
+    Conf2 = partial_builds2(f)
+
+    reveal_type(
+        Conf2,
+        expected_text="Type[PartialBuilds[(x: int, y: str, z: bool = False) -> int]]",
+    )
+
+    Parent = make_config(x=1)
+
+    partial_builds3 = make_custom_builds_fn(
+        zen_partial=True, builds_bases=(Parent,), populate_full_signature=True
+    )
+
+    Conf3 = partial_builds3(f)
+
+    reveal_type(
+        Conf3,
+        expected_text="Type[PartialBuilds[(x: int, y: str, z: bool = False) -> int]]",
+    )
diff --git a/tests/test_signature_parsing.py b/tests/test_signature_parsing.py
index 040d8218..3ba5ffb7 100644
--- a/tests/test_signature_parsing.py
+++ b/tests/test_signature_parsing.py
@@ -166,8 +166,8 @@ def test_builds_with_full_sig_mirrors_target_sig(
     assert expected_sig == actual_sig
 
     if not partial:
-        conf = Conf(x=-100)
-        assert conf.x == -100
+        conf = Conf(x="-100")
+        assert conf.x == "-100"
     else:
         # x should be excluded when partial=True and full-sig is populated
         conf = Conf()
@@ -565,3 +565,20 @@ def test_Counter():
         with pytest.raises(TypeError):
             # signature: Counter(iterable=None, /, **kwds)
             builds(Counter, [1], [2])
+
+
+def f_x(x: int):
+    return int
+
+
+def test_inheritance_populates_init_field():
+    Parent = make_config(x=1)
+    Conf1 = builds(f_x, populate_full_signature=True)
+    Conf2 = builds(f_x, populate_full_signature=True, builds_bases=(Parent,))
+
+    with pytest.raises(TypeError):
+        # x is missing
+        Conf1()  # type: ignore
+
+    # x is 'filled' via inheritance
+    Conf2()