Repository: facebookresearch/hydra

Issue:
[Feature Request] Support for Var Args and Positional Only Args
# 🚀 Feature Request
Hi Omry!
In a yaml file under the `_target_` I'd like the ability to pass things to functions and constructors which accept var args or positional only arguments. 
## Motivation

**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
<!-- Please link to any relevant issues or other PRs! -->
Functions like `np.random.randn` are currently uncallable. Some Objects are unconstructable.

## Pitch

**Describe the solution you'd like**
I'd like to be able to write something like this in a yaml file
```yaml
positional_only_ob:
  _target_: PositionalOnlyArgs
  args:
    foo: 15 #positional only arg
  bar: 20 # regular arg
```
for var args the syntax might be something like this:
```yaml
var_args_ob:
  _target_: VarArgs
  args: #var args
   - 1 
   - 2
   - 3
  bar: 20 # regular arg
```

**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
- We could make it so we use `_args_` instead of `args`. 
- We could use a different keyword of `kwargs`, and pass by position by default. 
- You could always use the proposed positional only args syntax since names will be ignored anyways
- You could always use the proposed var args syntax (I think)

**Are you willing to open a pull request?** (See [CONTRIBUTING](../../CONTRIBUTING.md))
Yes

## Additional context
I've tried on master (1.1.dev5)

As a side note, this feature is necessary to support instantiation of tuples and sets, but that is a separate feature request I'll likely make once if this becomes supported.


Discussion:
Hi @monney.
There is already a feature request for it (#808).

I am open to an attempt to add this, but as I said in the other feature request the surface area of instantiate is very large.
in other words, it's already one of the most complicated pieces of code in Hydra.
The bar for such a contribution will be really high in terms of testing.

A few things to consider:
1. There is already a way to send positional arguments (although I think are currently incorrectly being passed to recursive instantiations as well, which should be fixed - they they should only be passed to the first instantiation)
2. There should be a way to pass positional arguments as passthrough to nested config nodes.
3. I don't mind deprecating the current form of positional arguments in favor of a more flexible one:
```python
# example passing 1,2,3 as positional to the first node, and 4,5,6 to the nested one.
instantiate(cfg.node, {"_args_": [1,2,3], foo={"_args_" : [4,5,6]}})
```
4. I think the best thing to do if both the config and the passthrough has `_args_` is to have the passthrough override full replace the list in the config.
5. We should consider this in the context of interpolations. What is supported here?
6. Whatever we come up with should be well documented.

As you can see, there is a lot going on here.

Regarding support for instantiating tuples and sets: This is supported, but not with recursive instantiation.
```python
In [17]: utils.instantiate({"_target_" : "builtins.tuple"}, [1,2,3])
Out[17]: (1, 2, 3)

In [18]: utils.instantiate({"_target_" : "builtins.set"}, [1,2,3])
Out[18]: {1, 2, 3}
```
Will take a look at it for 1.2.

It may get into 1.1 if you can deliver a solution before it gets released.
I agree with the 1.2 timeline, given the complexity, it will take me some time. 

A, few questions, currently the tuple instantiation and "_args_" doesn't work through yaml properties, correct? Only through passing it in the instantiate function? 

Could you clarify what you mean by passthrough to nested nodes?

Thanks
> A, few questions, currently the tuple instantiation and "args" doesn't work through yaml properties, correct? Only through passing it in the instantiate function?

instantiation of builtins works.
passing positional arguments only works via the instantiate function right now (and I think it does not work well if you are doing recursive instantiation).

> Could you clarify what you mean by passthrough to nested nodes?

You mean "There should be a way to pass positional arguments as passthrough to nested config nodes" ?

This if we use `_args_` as the holder of positional arguments, this should be made to work:
```python
# example passing 1,2,3 as positional to the first node, and 4,5,6 to the nested one.
instantiate(cfg.node, {"_args_": [1,2,3], foo={"_args_" : [4,5,6]}})
```
For builtins I can't get tuple at least to work with yaml only, since 3.8 because it requires positional only.

For passthrough I had never tried that, I'll keep that in mind for implementation and tests.

I'll start going over the new instantiate code to get a gauge on things.
I have a pretty big PR changing instantiation that I am going to merge soon. see the outstanding pull requests.

Resolving patch:
diff --git a/hydra/_internal/instantiate/_instantiate2.py b/hydra/_internal/instantiate/_instantiate2.py
--- a/hydra/_internal/instantiate/_instantiate2.py
+++ b/hydra/_internal/instantiate/_instantiate2.py
@@ -3,7 +3,7 @@
 import copy
 import sys
 from enum import Enum
-from typing import Any, Callable, Union
+from typing import Any, Callable, Sequence, Tuple, Union
 
 from omegaconf import OmegaConf, SCMode
 from omegaconf._utils import is_structured_config
@@ -19,6 +19,7 @@ class _Keys(str, Enum):
     TARGET = "_target_"
     CONVERT = "_convert_"
     RECURSIVE = "_recursive_"
+    ARGS = "_args_"
 
 
 def _is_target(x: Any) -> bool:
@@ -29,9 +30,25 @@ def _is_target(x: Any) -> bool:
     return False
 
 
+def _extract_pos_args(*input_args: Any, **kwargs: Any) -> Tuple[Any, Any]:
+    config_args = kwargs.pop(_Keys.ARGS, ())
+    output_args = config_args
+
+    if isinstance(config_args, Sequence):
+        if len(input_args) > 0:
+            output_args = input_args
+    else:
+        raise InstantiationException(
+            f"Unsupported _args_ type: {type(config_args).__name__}. value: {config_args}"
+        )
+
+    return output_args, kwargs
+
+
 def _call_target(target: Callable, *args, **kwargs) -> Any:  # type: ignore
     """Call target (type) with args and kwargs."""
     try:
+        args, kwargs = _extract_pos_args(*args, **kwargs)
         return target(*args, **kwargs)
     except Exception as e:
         raise type(e)(
@@ -90,6 +107,7 @@ def instantiate(config: Any, *args: Any, **kwargs: Any) -> Any:
                    In addition to the parameters, the config must contain:
                    _target_ : target class or callable name (str)
                    And may contain:
+                   _args_: List-like of positional arguments to pass to the target
                    _recursive_: Construct nested objects as well (bool).
                                 True by default.
                                 may be overridden via a _recursive_ key in
@@ -100,6 +118,7 @@ def instantiate(config: Any, *args: Any, **kwargs: Any) -> Any:
                                   the exception of Structured Configs (and their fields).
                         all     : Passed objects are dicts, lists and primitives without
                                   a trace of OmegaConf containers
+                   _args_: List-like of positional arguments
     :param args: Optional positional parameters pass-through
     :param kwargs: Optional named parameters to override
                    parameters in the config object. Parameters not present
diff --git a/noxfile.py b/noxfile.py
--- a/noxfile.py
+++ b/noxfile.py
@@ -224,6 +224,7 @@ def lint(session):
         "hydra/grammar/gen",
         "tools/configen/example/gen",
         "tools/configen/tests/test_modules/expected",
+        "temp",
     ]
     isort = _isort_cmd() + [f"--skip={skip}" for skip in skiplist]
 


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,11 +1,4 @@
 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
-import collections.abc
-from dataclasses import dataclass
-from typing import Any, Dict, List, Optional, Tuple
-
-from omegaconf import MISSING
-
-from hydra.types import TargetConf
 
 
 def verify_hydra_pytest_plugin_not_installed() -> None:
@@ -21,318 +14,3 @@ def verify_hydra_pytest_plugin_not_installed() -> None:
 
 
 verify_hydra_pytest_plugin_not_installed()
-
-
-def add_values(a: int, b: int) -> int:
-    return a + b
-
-
-def module_function(x: int) -> int:
-    return x
-
-
-@dataclass
-class AClass:
-    a: Any
-    b: Any
-    c: Any
-    d: Any = "default_value"
-
-    @staticmethod
-    def static_method(z: int) -> int:
-        return z
-
-
-@dataclass
-class BClass:
-    a: Any
-    b: Any
-    c: Any = "c"
-    d: Any = "d"
-
-
-@dataclass
-class UntypedPassthroughConf:
-    _target_: str = "tests.UntypedPassthroughClass"
-    a: Any = MISSING
-
-
-@dataclass
-class UntypedPassthroughClass:
-    a: Any
-
-
-# Type not legal in a config
-class IllegalType:
-    def __eq__(self, other: Any) -> Any:
-        return isinstance(other, IllegalType)
-
-
-@dataclass
-class AnotherClass:
-    x: int
-
-
-class ASubclass(AnotherClass):
-    @classmethod
-    def class_method(cls, y: int) -> Any:
-        return cls(y + 1)
-
-    @staticmethod
-    def static_method(z: int) -> int:
-        return z
-
-
-class Parameters:
-    def __init__(self, params: List[float]):
-        self.params = params
-
-    def __eq__(self, other: Any) -> Any:
-        if isinstance(other, Parameters):
-            return self.params == other.params
-        return False
-
-    def __deepcopy__(self, memodict: Any = {}) -> Any:
-        raise NotImplementedError("Pytorch parameters does not support deepcopy")
-
-
-@dataclass
-class Adam:
-    params: Parameters
-    lr: float = 0.001
-    betas: Tuple[float, ...] = (0.9, 0.999)
-    eps: float = 1e-08
-    weight_decay: int = 0
-    amsgrad: bool = False
-
-
-@dataclass
-class NestingClass:
-    a: ASubclass = ASubclass(10)
-
-
-nesting = NestingClass()
-
-
-class ClassWithMissingModule:
-    def __init__(self) -> None:
-        import some_missing_module  # type: ignore # noqa: F401
-
-        self.x = 1
-
-
-@dataclass
-class AdamConf:
-    _target_: str = "tests.Adam"
-    lr: float = 0.001
-    betas: Tuple[float, ...] = (0.9, 0.999)
-    eps: float = 1e-08
-    weight_decay: int = 0
-    amsgrad: bool = False
-
-
-@dataclass
-class BadAdamConf(TargetConf):
-    # Missing str annotation
-    _target_ = "tests.Adam"
-
-
-@dataclass
-class User:
-    name: str = MISSING
-    age: int = MISSING
-
-
-@dataclass
-class UserGroup:
-    name: str = MISSING
-    users: List[User] = MISSING
-
-
-# RECURSIVE
-# Classes
-class Transform:
-    ...
-
-
-class CenterCrop(Transform):
-    def __init__(self, size: int):
-        self.size = size
-
-    def __eq__(self, other: Any) -> Any:
-        if isinstance(other, type(self)):
-            return self.size == other.size
-        else:
-            return False
-
-
-class Rotation(Transform):
-    def __init__(self, degrees: int):
-        self.degrees = degrees
-
-    def __eq__(self, other: Any) -> Any:
-        if isinstance(other, type(self)):
-            return self.degrees == other.degrees
-        else:
-            return False
-
-
-class Compose:
-    transforms: List[Transform]
-
-    def __init__(self, transforms: List[Transform]):
-        self.transforms = transforms
-
-    def __eq__(self, other: Any) -> Any:
-        if isinstance(other, type(self)):
-            return self.transforms == other.transforms
-        else:
-            return False
-
-
-class Tree:
-    value: Any
-    # annotated any because of non recursive instantiation tests
-    left: Any = None
-    right: Any = None
-
-    def __init__(self, value: Any, left: Any = None, right: Any = None) -> None:
-        self.value = value
-        self.left = left
-        self.right = right
-
-    def __eq__(self, other: Any) -> Any:
-        if isinstance(other, type(self)):
-            return (
-                self.value == other.value
-                and self.left == other.left
-                and self.right == other.right
-            )
-
-        else:
-            return False
-
-    def __repr__(self) -> str:
-        return f"Tree(value={self.value}, left={self.left}, right={self.right})"
-
-
-class Mapping:
-    dictionary: Optional[Dict[str, "Mapping"]] = None
-    value: Any = None
-
-    def __init__(
-        self, value: Any = None, dictionary: Optional[Dict[str, "Mapping"]] = None
-    ) -> None:
-        self.dictionary = dictionary
-        self.value = value
-
-    def __eq__(self, other: Any) -> Any:
-        if isinstance(other, type(self)):
-            return self.dictionary == other.dictionary and self.value == other.value
-        else:
-            return False
-
-    def __repr__(self) -> str:
-        return f"dictionary={self.dictionary}"
-
-
-# Configs
-@dataclass
-class TransformConf:
-    ...
-
-
-@dataclass
-class CenterCropConf(TransformConf):
-    _target_: str = "tests.CenterCrop"
-    size: int = MISSING
-
-
-@dataclass
-class RotationConf(TransformConf):
-    _target_: str = "tests.Rotation"
-    degrees: int = MISSING
-
-
-@dataclass
-class ComposeConf:
-    _target_: str = "tests.Compose"
-    transforms: List[TransformConf] = MISSING
-
-
-@dataclass
-class TreeConf:
-    _target_: str = "tests.Tree"
-    left: Optional["TreeConf"] = None
-    right: Optional["TreeConf"] = None
-    value: Any = MISSING
-
-
-@dataclass
-class MappingConf:
-    _target_: str = "tests.Mapping"
-    dictionary: Optional[Dict[str, "MappingConf"]] = None
-
-    def __init__(self, dictionary: Optional[Dict[str, "MappingConf"]] = None):
-        self.dictionary = dictionary
-
-
-@dataclass
-class SimpleDataClass:
-    a: Any = None
-    b: Any = None
-
-
-class SimpleClass:
-    a: Any = None
-    b: Any = None
-
-    def __init__(self, a: Any, b: Any) -> None:
-        self.a = a
-        self.b = b
-
-    def __eq__(self, other: Any) -> Any:
-        if isinstance(other, SimpleClass):
-            return self.a == other.a and self.b == other.b
-        return False
-
-
-@dataclass
-class SimpleClassPrimitiveConf:
-    _target_: str = "tests.SimpleClass"
-    _convert_: str = "partial"
-    a: Any = None
-    b: Any = None
-
-
-@dataclass
-class SimpleClassNonPrimitiveConf:
-    _target_: str = "tests.SimpleClass"
-    _convert_: str = "none"
-    a: Any = None
-    b: Any = None
-
-
-@dataclass
-class SimpleClassDefaultPrimitiveConf:
-    _target_: str = "tests.SimpleClass"
-    a: Any = None
-    b: Any = None
-
-
-@dataclass
-class NestedConf:
-    _target_: str = "tests.SimpleClass"
-    a: Any = User(name="a", age=1)
-    b: Any = User(name="b", age=2)
-
-
-def recisinstance(got: Any, expected: Any) -> bool:
-    """Compare got with expected type, recursively on dict and list."""
-    if not isinstance(got, type(expected)):
-        return False
-    if isinstance(expected, collections.abc.Mapping):
-        return all(recisinstance(got[key], expected[key]) for key in expected)
-    elif isinstance(expected, collections.abc.Iterable):
-        return all(recisinstance(got[idx], exp) for idx, exp in enumerate(expected))
-    return True
diff --git a/tests/instantiate/__init__.py b/tests/instantiate/__init__.py
new file mode 100644
--- /dev/null
+++ b/tests/instantiate/__init__.py
@@ -0,0 +1,341 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+import collections
+import collections.abc
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional, Tuple
+
+from omegaconf import MISSING
+
+from hydra.types import TargetConf
+
+
+class ArgsClass:
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
+        assert isinstance(args, tuple)
+        assert isinstance(kwargs, dict)
+        self.args = args
+        self.kwargs = kwargs
+
+    def __repr__(self) -> str:
+        return f"self.args={self.args},self.kwarg={self.kwargs}"
+
+    def __eq__(self, other: Any) -> Any:
+        if isinstance(other, ArgsClass):
+            return self.args == other.args and self.kwargs == other.kwargs
+        else:
+            return NotImplemented
+
+
+def add_values(a: int, b: int) -> int:
+    return a + b
+
+
+def module_function(x: int) -> int:
+    return x
+
+
+@dataclass
+class AClass:
+    a: Any
+    b: Any
+    c: Any
+    d: Any = "default_value"
+
+    @staticmethod
+    def static_method(z: int) -> int:
+        return z
+
+
+@dataclass
+class BClass:
+    a: Any
+    b: Any
+    c: Any = "c"
+    d: Any = "d"
+
+
+@dataclass
+class UntypedPassthroughConf:
+    _target_: str = "tests.instantiate.UntypedPassthroughClass"
+    a: Any = MISSING
+
+
+@dataclass
+class UntypedPassthroughClass:
+    a: Any
+
+
+# Type not legal in a config
+class IllegalType:
+    def __eq__(self, other: Any) -> Any:
+        return isinstance(other, IllegalType)
+
+
+@dataclass
+class AnotherClass:
+    x: int
+
+
+class ASubclass(AnotherClass):
+    @classmethod
+    def class_method(cls, y: int) -> Any:
+        return cls(y + 1)
+
+    @staticmethod
+    def static_method(z: int) -> int:
+        return z
+
+
+class Parameters:
+    def __init__(self, params: List[float]):
+        self.params = params
+
+    def __eq__(self, other: Any) -> Any:
+        if isinstance(other, Parameters):
+            return self.params == other.params
+        return False
+
+    def __deepcopy__(self, memodict: Any = {}) -> Any:
+        raise NotImplementedError("Pytorch parameters does not support deepcopy")
+
+
+@dataclass
+class Adam:
+    params: Parameters
+    lr: float = 0.001
+    betas: Tuple[float, ...] = (0.9, 0.999)
+    eps: float = 1e-08
+    weight_decay: int = 0
+    amsgrad: bool = False
+
+
+@dataclass
+class NestingClass:
+    a: ASubclass = ASubclass(10)
+
+
+nesting = NestingClass()
+
+
+class ClassWithMissingModule:
+    def __init__(self) -> None:
+        import some_missing_module  # type: ignore # noqa: F401
+
+        self.x = 1
+
+
+@dataclass
+class AdamConf:
+    _target_: str = "tests.instantiate.Adam"
+    lr: float = 0.001
+    betas: Tuple[float, ...] = (0.9, 0.999)
+    eps: float = 1e-08
+    weight_decay: int = 0
+    amsgrad: bool = False
+
+
+@dataclass
+class BadAdamConf(TargetConf):
+    # Missing str annotation
+    _target_ = "tests.instantiate.Adam"
+
+
+@dataclass
+class User:
+    name: str = MISSING
+    age: int = MISSING
+
+
+@dataclass
+class UserGroup:
+    name: str = MISSING
+    users: List[User] = MISSING
+
+
+# RECURSIVE
+# Classes
+class Transform:
+    ...
+
+
+class CenterCrop(Transform):
+    def __init__(self, size: int):
+        self.size = size
+
+    def __eq__(self, other: Any) -> Any:
+        if isinstance(other, type(self)):
+            return self.size == other.size
+        else:
+            return False
+
+
+class Rotation(Transform):
+    def __init__(self, degrees: int):
+        self.degrees = degrees
+
+    def __eq__(self, other: Any) -> Any:
+        if isinstance(other, type(self)):
+            return self.degrees == other.degrees
+        else:
+            return False
+
+
+class Compose:
+    transforms: List[Transform]
+
+    def __init__(self, transforms: List[Transform]):
+        self.transforms = transforms
+
+    def __eq__(self, other: Any) -> Any:
+        if isinstance(other, type(self)):
+            return self.transforms == other.transforms
+        else:
+            return False
+
+
+class Tree:
+    value: Any
+    # annotated any because of non recursive instantiation tests
+    left: Any = None
+    right: Any = None
+
+    def __init__(self, value: Any, left: Any = None, right: Any = None) -> None:
+        self.value = value
+        self.left = left
+        self.right = right
+
+    def __eq__(self, other: Any) -> Any:
+        if isinstance(other, type(self)):
+            return (
+                self.value == other.value
+                and self.left == other.left
+                and self.right == other.right
+            )
+
+        else:
+            return False
+
+    def __repr__(self) -> str:
+        return f"Tree(value={self.value}, left={self.left}, right={self.right})"
+
+
+class Mapping:
+    dictionary: Optional[Dict[str, "Mapping"]] = None
+    value: Any = None
+
+    def __init__(
+        self, value: Any = None, dictionary: Optional[Dict[str, "Mapping"]] = None
+    ) -> None:
+        self.dictionary = dictionary
+        self.value = value
+
+    def __eq__(self, other: Any) -> Any:
+        if isinstance(other, type(self)):
+            return self.dictionary == other.dictionary and self.value == other.value
+        else:
+            return False
+
+    def __repr__(self) -> str:
+        return f"dictionary={self.dictionary}"
+
+
+# Configs
+@dataclass
+class TransformConf:
+    ...
+
+
+@dataclass
+class CenterCropConf(TransformConf):
+    _target_: str = "tests.instantiate.CenterCrop"
+    size: int = MISSING
+
+
+@dataclass
+class RotationConf(TransformConf):
+    _target_: str = "tests.instantiate.Rotation"
+    degrees: int = MISSING
+
+
+@dataclass
+class ComposeConf:
+    _target_: str = "tests.instantiate.Compose"
+    transforms: List[TransformConf] = MISSING
+
+
+@dataclass
+class TreeConf:
+    _target_: str = "tests.instantiate.Tree"
+    left: Optional["TreeConf"] = None
+    right: Optional["TreeConf"] = None
+    value: Any = MISSING
+
+
+@dataclass
+class MappingConf:
+    _target_: str = "tests.instantiate.Mapping"
+    dictionary: Optional[Dict[str, "MappingConf"]] = None
+
+    def __init__(self, dictionary: Optional[Dict[str, "MappingConf"]] = None):
+        self.dictionary = dictionary
+
+
+@dataclass
+class SimpleDataClass:
+    a: Any = None
+    b: Any = None
+
+
+class SimpleClass:
+    a: Any = None
+    b: Any = None
+
+    def __init__(self, a: Any, b: Any) -> None:
+        self.a = a
+        self.b = b
+
+    def __eq__(self, other: Any) -> Any:
+        if isinstance(other, SimpleClass):
+            return self.a == other.a and self.b == other.b
+        return False
+
+
+@dataclass
+class SimpleClassPrimitiveConf:
+    _target_: str = "tests.instantiate.SimpleClass"
+    _convert_: str = "partial"
+    a: Any = None
+    b: Any = None
+
+
+@dataclass
+class SimpleClassNonPrimitiveConf:
+    _target_: str = "tests.instantiate.SimpleClass"
+    _convert_: str = "none"
+    a: Any = None
+    b: Any = None
+
+
+@dataclass
+class SimpleClassDefaultPrimitiveConf:
+    _target_: str = "tests.instantiate.SimpleClass"
+    a: Any = None
+    b: Any = None
+
+
+@dataclass
+class NestedConf:
+    _target_: str = "tests.instantiate.SimpleClass"
+    a: Any = User(name="a", age=1)
+    b: Any = User(name="b", age=2)
+
+
+def recisinstance(got: Any, expected: Any) -> bool:
+    """Compare got with expected type, recursively on dict and list."""
+    if not isinstance(got, type(expected)):
+        return False
+    if isinstance(expected, collections.abc.Mapping):
+        return all(recisinstance(got[key], expected[key]) for key in expected)
+    elif isinstance(expected, collections.abc.Iterable):
+        return all(recisinstance(got[idx], exp) for idx, exp in enumerate(expected))
+    return True
diff --git a/tests/instantiate/positional_only.py b/tests/instantiate/positional_only.py
new file mode 100644
--- /dev/null
+++ b/tests/instantiate/positional_only.py
@@ -0,0 +1,40 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+
+# Contains Python 3.8 syntax
+# flake does not like it when running on older versions of Python
+# flake8: noqa
+# With mypy, there does not seem to be a way to prevent an error when running in Python < 3.8.
+# For this reason, I am including the code as a string (the horror).
+# Once we upgrade to mypy 0.812, we should be able to use --exclude and eliminate this hack.
+from typing import Any
+
+code = """
+
+
+class PosOnlyArgsClass:
+    def __init__(self, a: Any, b: Any, /, **kwargs: Any) -> None:
+        assert isinstance(kwargs, dict)
+        self.a = a
+        self.b = b
+        self.kwargs = kwargs
+
+    def __repr__(self) -> str:
+        return f"{self.a=},{self.b},{self.kwargs=}"
+
+    def __eq__(self, other: Any) -> Any:
+        if isinstance(other, PosOnlyArgsClass):
+            return (
+                self.a == other.a and self.b == other.b and self.kwargs == other.kwargs
+            )
+        else:
+            return NotImplemented
+"""
+
+
+# Dummy class to keep mypy happy
+class PosOnlyArgsClass:
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
+        ...
+
+
+exec(code)  # nosec
diff --git a/tests/instantiate/test_helpers.py b/tests/instantiate/test_helpers.py
new file mode 100644
--- /dev/null
+++ b/tests/instantiate/test_helpers.py
@@ -0,0 +1,52 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+import re
+from typing import Any
+
+from _pytest.python_api import RaisesContext, raises
+from pytest import mark
+
+from hydra._internal.utils import _locate
+from hydra.utils import get_class
+from tests.instantiate import (
+    AClass,
+    Adam,
+    AnotherClass,
+    ASubclass,
+    NestingClass,
+    Parameters,
+)
+
+
+@mark.parametrize(
+    "name,expected",
+    [
+        ("tests.instantiate.Adam", Adam),
+        ("tests.instantiate.Parameters", Parameters),
+        ("tests.instantiate.AClass", AClass),
+        ("tests.instantiate.ASubclass", ASubclass),
+        ("tests.instantiate.NestingClass", NestingClass),
+        ("tests.instantiate.AnotherClass", AnotherClass),
+        ("", raises(ImportError, match=re.escape("Empty path"))),
+        [
+            "not_found",
+            raises(ImportError, match=re.escape("Error loading module 'not_found'")),
+        ],
+        (
+            "tests.instantiate.b.c.Door",
+            raises(
+                ImportError, match=re.escape("No module named 'tests.instantiate.b'")
+            ),
+        ),
+    ],
+)
+def test_locate(name: str, expected: Any) -> None:
+    if isinstance(expected, RaisesContext):
+        with expected:
+            _locate(name)
+    else:
+        assert _locate(name) == expected
+
+
+@mark.parametrize("path,expected_type", [("tests.instantiate.AClass", AClass)])
+def test_get_class(path: str, expected_type: type) -> None:
+    assert get_class(path) == expected_type
diff --git a/tests/instantiate/test_instantiate.py b/tests/instantiate/test_instantiate.py
new file mode 100644
--- /dev/null
+++ b/tests/instantiate/test_instantiate.py
@@ -0,0 +1,1297 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+import copy
+import re
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional, Tuple
+
+from omegaconf import MISSING, DictConfig, ListConfig, OmegaConf
+from pytest import fixture, mark, param, raises, warns
+
+import hydra
+from hydra.errors import InstantiationException
+from hydra.types import ConvertMode, TargetConf
+from tests.instantiate import (
+    AClass,
+    Adam,
+    AdamConf,
+    AnotherClass,
+    ASubclass,
+    BadAdamConf,
+    BClass,
+    CenterCrop,
+    CenterCropConf,
+    Compose,
+    ComposeConf,
+    IllegalType,
+    Mapping,
+    MappingConf,
+    NestedConf,
+    NestingClass,
+    Parameters,
+    Rotation,
+    RotationConf,
+    SimpleClass,
+    SimpleClassDefaultPrimitiveConf,
+    SimpleClassNonPrimitiveConf,
+    SimpleClassPrimitiveConf,
+    SimpleDataClass,
+    Tree,
+    TreeConf,
+    UntypedPassthroughClass,
+    UntypedPassthroughConf,
+    User,
+    recisinstance,
+)
+
+
+@fixture(
+    params=[
+        hydra._internal.instantiate._instantiate2.instantiate,
+    ],
+    ids=[
+        "instantiate2",
+    ],
+)
+def instantiate_func(request: Any) -> Any:
+    return request.param
+
+
+@fixture(
+    params=[
+        lambda cfg: copy.deepcopy(cfg),
+        lambda cfg: OmegaConf.create(cfg),
+    ],
+    ids=[
+        "dict",
+        "dict_config",
+    ],
+)
+def config(request: Any, src: Any) -> Any:
+    config = request.param(src)
+    cfg_copy = copy.deepcopy(config)
+    yield config
+    assert config == cfg_copy
+
+
+@mark.parametrize(
+    "recursive", [param(False, id="not_recursive"), param(True, id="recursive")]
+)
+@mark.parametrize(
+    "src, passthrough, expected",
+    [
+        param(
+            {
+                "_target_": "tests.instantiate.AClass",
+                "a": 10,
+                "b": 20,
+                "c": 30,
+                "d": 40,
+            },
+            {},
+            AClass(10, 20, 30, 40),
+            id="class",
+        ),
+        param(
+            {"_target_": "tests.instantiate.AClass", "b": 20, "c": 30},
+            {"a": 10, "d": 40},
+            AClass(10, 20, 30, 40),
+            id="class+override",
+        ),
+        param(
+            {"_target_": "tests.instantiate.AClass", "b": 200, "c": "${b}"},
+            {"a": 10, "b": 99, "d": 40},
+            AClass(10, 99, 99, 40),
+            id="class+override+interpolation",
+        ),
+        # Check class and static methods
+        param(
+            {"_target_": "tests.instantiate.ASubclass.class_method", "y": 10},
+            {},
+            ASubclass(11),
+            id="class_method",
+        ),
+        param(
+            {"_target_": "tests.instantiate.AClass.static_method", "z": 43},
+            {},
+            43,
+            id="static_method",
+        ),
+        # Check nested types and static methods
+        param(
+            {"_target_": "tests.instantiate.NestingClass"},
+            {},
+            NestingClass(ASubclass(10)),
+            id="class_with_nested_class",
+        ),
+        param(
+            {"_target_": "tests.instantiate.nesting.a.class_method", "y": 10},
+            {},
+            ASubclass(11),
+            id="class_method_on_an_object_nested_in_a_global",
+        ),
+        param(
+            {"_target_": "tests.instantiate.nesting.a.static_method", "z": 43},
+            {},
+            43,
+            id="static_method_on_an_object_nested_in_a_global",
+        ),
+        # Check that default value is respected
+        param(
+            {"_target_": "tests.instantiate.AClass"},
+            {"a": 10, "b": 20, "c": 30},
+            AClass(10, 20, 30, "default_value"),
+            id="instantiate_respects_default_value",
+        ),
+        # call a function from a module
+        param(
+            {"_target_": "tests.instantiate.module_function", "x": 43},
+            {},
+            43,
+            id="call_function_in_module",
+        ),
+        # Check builtins
+        param(
+            {"_target_": "builtins.str", "object": 43},
+            {},
+            "43",
+            id="builtin_types",
+        ),
+        # Check that none is instantiated correctly
+        param(None, {}, None, id="instantiate_none"),
+        # passthrough
+        param(
+            {"_target_": "tests.instantiate.AClass"},
+            {"a": 10, "b": 20, "c": 30},
+            AClass(a=10, b=20, c=30),
+            id="passthrough",
+        ),
+        param(
+            {"_target_": "tests.instantiate.AClass"},
+            {"a": 10, "b": 20, "c": 30, "d": {"x": IllegalType()}},
+            AClass(a=10, b=20, c=30, d={"x": IllegalType()}),
+            id="oc_incompatible_passthrough",
+        ),
+        param(
+            {"_target_": "tests.instantiate.AClass"},
+            {
+                "a": 10,
+                "b": 20,
+                "c": 30,
+                "d": {"x": [10, IllegalType()]},
+            },
+            AClass(a=10, b=20, c=30, d={"x": [10, IllegalType()]}),
+            id="passthrough:list",
+        ),
+        param(
+            UntypedPassthroughConf,
+            {"a": IllegalType()},
+            UntypedPassthroughClass(a=IllegalType()),
+            id="untyped_passthrough",
+        ),
+    ],
+)
+def test_class_instantiate(
+    instantiate_func: Any,
+    config: Any,
+    passthrough: Dict[str, Any],
+    expected: Any,
+    recursive: bool,
+) -> Any:
+    passthrough["_recursive_"] = recursive
+    obj = instantiate_func(config, **passthrough)
+    assert obj == expected
+
+
+@mark.parametrize(
+    "input_conf, passthrough, expected",
+    [
+        param(
+            {
+                "node": {
+                    "_target_": "tests.instantiate.AClass",
+                    "a": "${value}",
+                    "b": 20,
+                    "c": 30,
+                    "d": 40,
+                },
+                "value": 99,
+            },
+            {},
+            AClass(99, 20, 30, 40),
+            id="interpolation_into_parent",
+        ),
+        param(
+            {
+                "A": {"_target_": "tests.instantiate.add_values", "a": 1, "b": 2},
+                "node": {
+                    "_target_": "tests.instantiate.add_values",
+                    "a": "${A}",
+                    "b": 3,
+                },
+            },
+            {},
+            6,
+            id="interpolation_from_recursive",
+        ),
+    ],
+)
+def test_interpolation_accessing_parent(
+    instantiate_func: Any, input_conf: Any, passthrough: Dict[str, Any], expected: Any
+) -> Any:
+    cfg_copy = OmegaConf.create(input_conf)
+    input_conf = OmegaConf.create(input_conf)
+    obj = instantiate_func(input_conf.node, **passthrough)
+    assert obj == expected
+    assert input_conf == cfg_copy
+
+
+def test_interpolation_is_live_in_instantiated_object(instantiate_func: Any) -> None:
+    """
+    Interpolations in instantiated objects are live config objects but not for primitive objects.
+    """
+    cfg = OmegaConf.create(
+        {
+            "node": {
+                "_target_": "tests.instantiate.AClass",
+                "a": "${value}",
+                "b": {"x": "${value}"},
+                "c": 30,
+                "d": 40,
+            },
+            "value": 99,
+        }
+    )
+    obj = instantiate_func(cfg.node)
+    assert obj.a == 99
+    assert obj.b.x == 99
+
+    cfg.value = 3.14
+
+    # interpolation is not live for primitive objects
+    assert obj.a == 99  # unchanged
+    # but is live for config objects
+    assert obj.b.x == 3.14
+
+
+@mark.parametrize(
+    "src",
+    [({"_target_": "tests.instantiate.AClass", "b": 200, "c": {"x": 10, "y": "${b}"}})],
+)
+def test_class_instantiate_omegaconf_node(instantiate_func: Any, config: Any) -> Any:
+    obj = instantiate_func(config, a=10, d=AnotherClass(99))
+    assert obj == AClass(a=10, b=200, c={"x": 10, "y": 200}, d=AnotherClass(99))
+    assert OmegaConf.is_config(obj.c)
+
+
+@mark.parametrize("src", [{"_target_": "tests.instantiate.Adam"}])
+def test_instantiate_adam(instantiate_func: Any, config: Any) -> None:
+    with raises(Exception):
+        # can't instantiate without passing params
+        instantiate_func(config)
+
+    adam_params = Parameters([1, 2, 3])
+    res = instantiate_func(config, params=adam_params)
+    assert res == Adam(params=adam_params)
+
+
+def test_instantiate_adam_conf(instantiate_func: Any) -> None:
+    with raises(Exception):
+        # can't instantiate without passing params
+        instantiate_func({"_target_": "tests.Adam"})
+
+    adam_params = Parameters([1, 2, 3])
+    res = instantiate_func(AdamConf(lr=0.123), params=adam_params)
+    expected = Adam(lr=0.123, params=adam_params)
+    assert res.params == expected.params
+    assert res.lr == expected.lr
+    assert list(res.betas) == list(expected.betas)  # OmegaConf converts tuples to lists
+    assert res.eps == expected.eps
+    assert res.weight_decay == expected.weight_decay
+    assert res.amsgrad == expected.amsgrad
+
+
+def test_instantiate_adam_conf_with_convert(instantiate_func: Any) -> None:
+    adam_params = Parameters([1, 2, 3])
+    res = instantiate_func(AdamConf(lr=0.123), params=adam_params, _convert_="all")
+    expected = Adam(lr=0.123, params=adam_params)
+    assert res.params == expected.params
+    assert res.lr == expected.lr
+    assert isinstance(res.betas, list)
+    assert list(res.betas) == list(expected.betas)  # OmegaConf converts tuples to lists
+    assert res.eps == expected.eps
+    assert res.weight_decay == expected.weight_decay
+    assert res.amsgrad == expected.amsgrad
+
+
+def test_targetconf_deprecated() -> None:
+    with warns(
+        expected_warning=UserWarning,
+        match=re.escape(
+            "TargetConf is deprecated since Hydra 1.1 and will be removed in Hydra 1.2."
+        ),
+    ):
+        TargetConf()
+
+
+def test_instantiate_bad_adam_conf(instantiate_func: Any, recwarn: Any) -> None:
+    msg = (
+        "Missing value for BadAdamConf._target_. Check that it's properly annotated and overridden."
+        "\nA common problem is forgetting to annotate _target_ as a string : '_target_: str = ...'"
+    )
+    with raises(
+        InstantiationException,
+        match=re.escape(msg),
+    ):
+        instantiate_func(BadAdamConf())
+
+
+def test_instantiate_with_missing_module(instantiate_func: Any) -> None:
+
+    with raises(
+        ModuleNotFoundError, match=re.escape("No module named 'some_missing_module'")
+    ):
+        # can't instantiate when importing a missing module
+        instantiate_func({"_target_": "tests.instantiate.ClassWithMissingModule"})
+
+
+def test_pass_extra_variables(instantiate_func: Any) -> None:
+    cfg = OmegaConf.create({"_target_": "tests.instantiate.AClass", "a": 10, "b": 20})
+    assert instantiate_func(cfg, c=30) == AClass(a=10, b=20, c=30)
+
+
+@mark.parametrize(
+    "src, passthrough, expected",
+    [
+        # direct
+        param(
+            {
+                "_target_": "tests.instantiate.Tree",
+                "value": 1,
+                "left": {
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 21,
+                },
+                "right": {
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 22,
+                },
+            },
+            {},
+            Tree(value=1, left=Tree(value=21), right=Tree(value=22)),
+            id="recursive:direct:dict",
+        ),
+        param(
+            {"_target_": "tests.instantiate.Tree"},
+            {"value": 1},
+            Tree(value=1),
+            id="recursive:direct:dict:passthrough",
+        ),
+        param(
+            {"_target_": "tests.instantiate.Tree"},
+            {"value": 1, "left": {"_target_": "tests.instantiate.Tree", "value": 2}},
+            Tree(value=1, left=Tree(2)),
+            id="recursive:direct:dict:passthrough",
+        ),
+        param(
+            {"_target_": "tests.instantiate.Tree"},
+            {
+                "value": 1,
+                "left": {"_target_": "tests.instantiate.Tree", "value": 2},
+                "right": {"_target_": "tests.instantiate.Tree", "value": 3},
+            },
+            Tree(value=1, left=Tree(2), right=Tree(3)),
+            id="recursive:direct:dict:passthrough",
+        ),
+        param(
+            {"_target_": "tests.instantiate.Tree"},
+            {"value": IllegalType()},
+            Tree(value=IllegalType()),
+            id="recursive:direct:dict:passthrough:incompatible_value",
+        ),
+        param(
+            {"_target_": "tests.instantiate.Tree"},
+            {
+                "value": 1,
+                "left": {"_target_": "tests.instantiate.Tree", "value": IllegalType()},
+            },
+            Tree(value=1, left=Tree(value=IllegalType())),
+            id="recursive:direct:dict:passthrough:incompatible_value",
+        ),
+        param(
+            TreeConf(
+                value=1,
+                left=TreeConf(value=21),
+                right=TreeConf(value=22),
+            ),
+            {},
+            Tree(value=1, left=Tree(value=21), right=Tree(value=22)),
+            id="recursive:direct:dataclass",
+        ),
+        param(
+            TreeConf(
+                value=1,
+                left=TreeConf(value=21),
+            ),
+            {"right": {"value": 22}},
+            Tree(value=1, left=Tree(value=21), right=Tree(value=22)),
+            id="recursive:direct:dataclass:passthrough",
+        ),
+        param(
+            TreeConf(
+                value=1,
+                left=TreeConf(value=21),
+            ),
+            {"right": TreeConf(value=22)},
+            Tree(value=1, left=Tree(value=21), right=Tree(value=22)),
+            id="recursive:direct:dataclass:passthrough",
+        ),
+        param(
+            TreeConf(
+                value=1,
+                left=TreeConf(value=21),
+            ),
+            {
+                "right": TreeConf(value=IllegalType()),
+            },
+            Tree(value=1, left=Tree(value=21), right=Tree(value=IllegalType())),
+            id="recursive:direct:dataclass:passthrough",
+        ),
+        # list
+        # note that passthrough to a list element is not currently supported
+        param(
+            ComposeConf(
+                transforms=[
+                    CenterCropConf(size=10),
+                    RotationConf(degrees=45),
+                ]
+            ),
+            {},
+            Compose(
+                transforms=[
+                    CenterCrop(size=10),
+                    Rotation(degrees=45),
+                ]
+            ),
+            id="recursive:list:dataclass",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Compose",
+                "transforms": [
+                    {"_target_": "tests.instantiate.CenterCrop", "size": 10},
+                    {"_target_": "tests.instantiate.Rotation", "degrees": 45},
+                ],
+            },
+            {},
+            Compose(
+                transforms=[
+                    CenterCrop(size=10),
+                    Rotation(degrees=45),
+                ]
+            ),
+            id="recursive:list:dict",
+        ),
+        # map
+        param(
+            MappingConf(
+                dictionary={
+                    "a": MappingConf(),
+                    "b": MappingConf(),
+                }
+            ),
+            {},
+            Mapping(
+                dictionary={
+                    "a": Mapping(),
+                    "b": Mapping(),
+                }
+            ),
+            id="recursive:map:dataclass",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Mapping",
+                "dictionary": {
+                    "a": {"_target_": "tests.instantiate.Mapping"},
+                    "b": {"_target_": "tests.instantiate.Mapping"},
+                },
+            },
+            {},
+            Mapping(
+                dictionary={
+                    "a": Mapping(),
+                    "b": Mapping(),
+                }
+            ),
+            id="recursive:map:dict",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Mapping",
+                "dictionary": {
+                    "a": {"_target_": "tests.instantiate.Mapping"},
+                },
+            },
+            {
+                "dictionary": {
+                    "b": {"_target_": "tests.instantiate.Mapping"},
+                },
+            },
+            Mapping(
+                dictionary={
+                    "a": Mapping(),
+                    "b": Mapping(),
+                }
+            ),
+            id="recursive:map:dict:passthrough",
+        ),
+    ],
+)
+def test_recursive_instantiation(
+    instantiate_func: Any,
+    config: Any,
+    passthrough: Dict[str, Any],
+    expected: Any,
+) -> None:
+    obj = instantiate_func(config, **passthrough)
+    assert obj == expected
+
+
+@mark.parametrize(
+    ("src", "passthrough", "expected"),
+    [
+        param(
+            {
+                "_target_": "tests.instantiate.Tree",
+                "value": 1,
+                "left": {
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 21,
+                },
+            },
+            {},
+            Tree(value=1, left=Tree(value=21)),
+            id="default",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Tree",
+                "_recursive_": True,
+                "value": 1,
+                "left": {
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 21,
+                },
+            },
+            {"_recursive_": True},
+            Tree(value=1, left=Tree(value=21)),
+            id="cfg:true,override:true",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Tree",
+                "_recursive_": True,
+                "value": 1,
+                "left": {
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 21,
+                },
+            },
+            {"_recursive_": False},
+            Tree(value=1, left={"_target_": "tests.instantiate.Tree", "value": 21}),
+            id="cfg:true,override:false",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Tree",
+                "_recursive_": False,
+                "value": 1,
+                "left": {
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 21,
+                },
+            },
+            {"_recursive_": True},
+            Tree(value=1, left=Tree(value=21)),
+            id="cfg:false,override:true",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Tree",
+                "_recursive_": False,
+                "value": 1,
+                "left": {
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 21,
+                },
+            },
+            {"_recursive_": False},
+            Tree(value=1, left={"_target_": "tests.instantiate.Tree", "value": 21}),
+            id="cfg:false,override:false",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Tree",
+                "value": 1,
+                "left": {
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 2,
+                    "left": {
+                        "_target_": "tests.instantiate.Tree",
+                        "value": 3,
+                    },
+                },
+            },
+            {},
+            Tree(value=1, left=Tree(value=2, left=Tree(value=3))),
+            id="3_levels:default",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Tree",
+                "_recursive_": False,
+                "value": 1,
+                "left": {
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 2,
+                    "left": {
+                        "_target_": "tests.instantiate.Tree",
+                        "value": 3,
+                    },
+                },
+            },
+            {},
+            Tree(
+                value=1,
+                left={
+                    "_target_": "tests.instantiate.Tree",
+                    "value": 2,
+                    "left": {"_target_": "tests.instantiate.Tree", "value": 3},
+                },
+            ),
+            id="3_levels:cfg1=false",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.Tree",
+                "value": 1,
+                "left": {
+                    "_target_": "tests.instantiate.Tree",
+                    "_recursive_": False,
+                    "value": 2,
+                    "left": {
+                        "_target_": "tests.instantiate.Tree",
+                        "value": 3,
+                    },
+                },
+            },
+            {},
+            Tree(
+                value=1,
+                left=Tree(
+                    value=2, left={"_target_": "tests.instantiate.Tree", "value": 3}
+                ),
+            ),
+            id="3_levels:cfg2=false",
+        ),
+    ],
+)
+def test_recursive_override(
+    instantiate_func: Any,
+    config: Any,
+    passthrough: Any,
+    expected: Any,
+) -> None:
+    obj = instantiate_func(config, **passthrough)
+    assert obj == expected
+
+
+@mark.parametrize(
+    ("src", "passthrough", "expected"),
+    [
+        param(
+            {
+                "_target_": "tests.instantiate.AClass",
+                "a": 10,
+                "b": 20,
+                "c": 30,
+                "d": 40,
+            },
+            {"_target_": "tests.instantiate.BClass"},
+            BClass(10, 20, 30, 40),
+            id="str:override_same_args",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.AClass",
+                "a": 10,
+                "b": 20,
+                "c": 30,
+                "d": 40,
+            },
+            {"_target_": BClass},
+            BClass(10, 20, 30, 40),
+            id="type:override_same_args",
+        ),
+        param(
+            {"_target_": "tests.instantiate.AClass", "a": 10, "b": 20},
+            {"_target_": "tests.instantiate.BClass"},
+            BClass(10, 20, "c", "d"),
+            id="str:override_other_args",
+        ),
+        param(
+            {"_target_": "tests.instantiate.AClass", "a": 10, "b": 20},
+            {"_target_": BClass},
+            BClass(10, 20, "c", "d"),
+            id="type:override_other_args",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.AClass",
+                "a": 10,
+                "b": 20,
+                "c": {
+                    "_target_": "tests.instantiate.AClass",
+                    "a": "aa",
+                    "b": "bb",
+                    "c": "cc",
+                },
+            },
+            {
+                "_target_": "tests.instantiate.BClass",
+                "c": {
+                    "_target_": "tests.instantiate.BClass",
+                },
+            },
+            BClass(10, 20, BClass(a="aa", b="bb", c="cc"), "d"),
+            id="str:recursive_override",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.AClass",
+                "a": 10,
+                "b": 20,
+                "c": {
+                    "_target_": "tests.instantiate.AClass",
+                    "a": "aa",
+                    "b": "bb",
+                    "c": "cc",
+                },
+            },
+            {"_target_": BClass, "c": {"_target_": BClass}},
+            BClass(10, 20, BClass(a="aa", b="bb", c="cc"), "d"),
+            id="type:recursive_override",
+        ),
+    ],
+)
+def test_override_target(
+    instantiate_func: Any, config: Any, passthrough: Any, expected: Any
+) -> None:
+    obj = instantiate_func(config, **passthrough)
+    assert obj == expected
+
+
+@mark.parametrize(
+    "config, passthrough, expected",
+    [
+        param(
+            {"_target_": AnotherClass, "x": 10},
+            {},
+            AnotherClass(10),
+            id="class_in_config_dict",
+        ),
+    ],
+)
+def test_instantiate_from_class_in_dict(
+    instantiate_func: Any, config: Any, passthrough: Any, expected: Any
+) -> None:
+    config_copy = copy.deepcopy(config)
+    assert instantiate_func(config, **passthrough) == expected
+    assert config == config_copy
+
+
+@mark.parametrize(
+    "config, passthrough, expected",
+    [
+        param(
+            OmegaConf.create({"_target_": AClass}),
+            {},
+            AClass(10, 20, 30, 40),
+            id="class_in_config_dict",
+        ),
+    ],
+)
+def test_instantiate_from_dataclass_in_dict_fails(
+    instantiate_func: Any, config: Any, passthrough: Any, expected: Any
+) -> None:
+    # not the best error, but it will get the user to check their input config.
+    msg = "Unsupported target type: DictConfig. value: {'a': '???', 'b': '???', 'c': '???', 'd': 'default_value'}"
+    with raises(
+        InstantiationException,
+        match=re.escape(msg),
+    ):
+        assert instantiate_func(config, **passthrough) == expected
+
+
+@mark.parametrize(
+    "primitive,expected_primitive",
+    [
+        param(None, False, id="unspecified"),
+        param(ConvertMode.NONE, False, id="none"),
+        param(ConvertMode.PARTIAL, True, id="partial"),
+        param(ConvertMode.ALL, True, id="all"),
+    ],
+)
+@mark.parametrize(
+    "input_,expected",
+    [
+        param(
+            {
+                "obj": {
+                    "_target_": "tests.instantiate.AClass",
+                    "a": {"foo": "bar"},
+                    "b": OmegaConf.create({"foo": "bar"}),
+                    "c": [1, 2, 3],
+                    "d": OmegaConf.create([1, 2, 3]),
+                },
+            },
+            AClass(
+                a={"foo": "bar"},
+                b={"foo": "bar"},
+                c=[1, 2, 3],
+                d=[1, 2, 3],
+            ),
+            id="simple",
+        ),
+        param(
+            {
+                "value": 99,
+                "obj": {
+                    "_target_": "tests.instantiate.AClass",
+                    "a": {"foo": "${value}"},
+                    "b": OmegaConf.create({"foo": "${value}"}),
+                    "c": [1, "${value}"],
+                    "d": OmegaConf.create([1, "${value}"]),
+                },
+            },
+            AClass(
+                a={"foo": 99},
+                b={"foo": 99},
+                c=[1, 99],
+                d=[1, 99],
+            ),
+            id="interpolation",
+        ),
+    ],
+)
+def test_convert_params_override(
+    instantiate_func: Any,
+    primitive: Optional[bool],
+    expected_primitive: bool,
+    input_: Any,
+    expected: Any,
+) -> None:
+    input_cfg = OmegaConf.create(input_)
+    if primitive is not None:
+        ret = instantiate_func(input_cfg.obj, _convert_=primitive)
+    else:
+        ret = instantiate_func(input_cfg.obj)
+
+    expected_list: Any
+    expected_dict: Any
+    if expected_primitive is True:
+        expected_list = list
+        expected_dict = dict
+    else:
+        expected_list = ListConfig
+        expected_dict = DictConfig
+
+    assert ret == expected
+    assert isinstance(ret.a, expected_dict)
+    assert isinstance(ret.b, expected_dict)
+    assert isinstance(ret.c, expected_list)
+    assert isinstance(ret.d, expected_list)
+
+
+@mark.parametrize(
+    "convert_mode",
+    [
+        param(None, id="none"),
+        param(ConvertMode.NONE, id="none"),
+        param("none", id="none"),
+        param(ConvertMode.PARTIAL, id="partial"),
+        param("partial", id="partial"),
+        param(ConvertMode.ALL, id="all"),
+        param("all", id="all"),
+    ],
+)
+@mark.parametrize(
+    "input_,expected",
+    [
+        param(
+            {
+                "value": 99,
+                "obj": {
+                    "_target_": "tests.instantiate.SimpleClass",
+                    "a": {
+                        "_target_": "tests.instantiate.SimpleClass",
+                        "a": {"foo": "${value}"},
+                        "b": [1, "${value}"],
+                    },
+                    "b": None,
+                },
+            },
+            SimpleClass(a={"foo": 99}, b=[1, 99]),
+            id="simple",
+        ),
+    ],
+)
+def test_convert_params(
+    instantiate_func: Any, input_: Any, expected: Any, convert_mode: Any
+) -> None:
+    cfg = OmegaConf.create(input_)
+    kwargs = {"a": {"_convert_": convert_mode}}
+    ret = instantiate_func(cfg.obj, **kwargs)
+
+    if convert_mode in (ConvertMode.PARTIAL, ConvertMode.ALL):
+        assert isinstance(ret.a.a, dict)
+        assert isinstance(ret.a.b, list)
+    elif convert_mode in (None, ConvertMode.NONE):
+        assert isinstance(ret.a.a, DictConfig)
+        assert isinstance(ret.a.b, ListConfig)
+    else:
+        assert False
+
+    assert ret.a == expected
+
+
+@mark.parametrize("nested_recursive", [True, False])
+def test_convert_and_recursive_node(
+    instantiate_func: Any, nested_recursive: bool
+) -> None:
+    cfg = {
+        "_target_": "tests.instantiate.SimpleClass",
+        "a": {
+            "_target_": "tests.instantiate.SimpleClass",
+            "_convert_": "all",
+            "_recursive_": nested_recursive,
+            "a": {},
+            "b": [],
+        },
+        "b": None,
+    }
+
+    obj = instantiate_func(cfg)
+    assert isinstance(obj.a.a, dict)
+    assert isinstance(obj.a.b, list)
+
+
+@mark.parametrize(
+    "src,expected",
+    [
+        param(
+            {
+                "value": 99,
+                "obj": {
+                    "_target_": "tests.instantiate.SimpleDataClass",
+                    "a": {
+                        "_target_": "tests.instantiate.SimpleDataClass",
+                        "a": {"foo": "${value}"},
+                        "b": [1, "${value}"],
+                    },
+                    "b": None,
+                },
+            },
+            (
+                SimpleDataClass(
+                    a=SimpleDataClass(
+                        a=OmegaConf.create({"foo": 99}), b=OmegaConf.create([1, 99])
+                    ),
+                    b=None,
+                ),
+                SimpleDataClass(a=SimpleDataClass(a={"foo": 99}, b=[1, 99]), b=None),
+                SimpleDataClass(a=SimpleDataClass(a={"foo": 99}, b=[1, 99]), b=None),
+            ),
+            id="dataclass+dataclass",
+        ),
+        param(
+            {
+                "value": 99,
+                "obj": {
+                    "_target_": "tests.instantiate.SimpleClass",
+                    "a": {
+                        "_target_": "tests.instantiate.SimpleDataClass",
+                        "a": {"foo": "${value}"},
+                        "b": [1, "${value}"],
+                    },
+                    "b": None,
+                },
+            },
+            (
+                SimpleClass(
+                    a=SimpleDataClass(
+                        a=OmegaConf.create({"foo": 99}), b=OmegaConf.create([1, 99])
+                    ),
+                    b=None,
+                ),
+                SimpleClass(a=SimpleDataClass(a={"foo": 99}, b=[1, 99]), b=None),
+                SimpleClass(a=SimpleDataClass(a={"foo": 99}, b=[1, 99]), b=None),
+            ),
+            id="class+dataclass",
+        ),
+        param(
+            {
+                "value": 99,
+                "obj": {
+                    "a": {
+                        "_target_": "tests.instantiate.SimpleDataClass",
+                        "a": {"foo": "${value}"},
+                        "b": [1, "${value}"],
+                    },
+                    "b": None,
+                },
+            },
+            (
+                # a is a DictConfig because of top level DictConfig
+                OmegaConf.create(
+                    {
+                        "a": SimpleDataClass(
+                            a=OmegaConf.create({"foo": 99}), b=OmegaConf.create([1, 99])
+                        ),
+                        "b": None,
+                    }
+                ),
+                {"a": SimpleDataClass(a={"foo": 99}, b=[1, 99]), "b": None},
+                {"a": SimpleDataClass(a={"foo": 99}, b=[1, 99]), "b": None},
+            ),
+            id="dict+dataclass",
+        ),
+    ],
+)
+def test_instantiate_convert_dataclasses(
+    instantiate_func: Any, config: Any, expected: Tuple[Any, Any, Any]
+) -> None:
+    """Instantiate on nested dataclass + dataclass."""
+    modes = [ConvertMode.NONE, ConvertMode.PARTIAL, ConvertMode.ALL]
+    for exp, mode in zip(expected, modes):
+        # create DictConfig to ensure interpolations are working correctly when we pass a cfg.obj
+        cfg = OmegaConf.create(config)
+        instance = instantiate_func(cfg.obj, _convert_=mode)
+        assert instance == exp
+        assert recisinstance(instance, exp)
+
+
+@mark.parametrize(
+    ("mode", "expected_dict", "expected_list"),
+    [
+        param(ConvertMode.NONE, DictConfig, ListConfig, id="none"),
+        param(ConvertMode.ALL, dict, list, id="all"),
+        param(ConvertMode.PARTIAL, dict, list, id="partial"),
+    ],
+)
+def test_instantiated_regular_class_container_types(
+    instantiate_func: Any, mode: Any, expected_dict: Any, expected_list: Any
+) -> None:
+    cfg = {"_target_": "tests.instantiate.SimpleClass", "a": {}, "b": []}
+    ret = instantiate_func(cfg, _convert_=mode)
+    assert isinstance(ret.a, expected_dict)
+    assert isinstance(ret.b, expected_list)
+
+    cfg2 = {
+        "_target_": "tests.instantiate.SimpleClass",
+        "a": {"_target_": "tests.instantiate.SimpleClass", "a": {}, "b": []},
+        "b": [{"_target_": "tests.instantiate.SimpleClass", "a": {}, "b": []}],
+    }
+    ret = instantiate_func(cfg2, _convert_=mode)
+    assert isinstance(ret.a.a, expected_dict)
+    assert isinstance(ret.a.b, expected_list)
+    assert isinstance(ret.b[0].a, expected_dict)
+    assert isinstance(ret.b[0].b, expected_list)
+
+
+def test_instantiated_regular_class_container_types_partial(
+    instantiate_func: Any,
+) -> None:
+    cfg = {"_target_": "tests.instantiate.SimpleClass", "a": {}, "b": User()}
+    ret = instantiate_func(cfg, _convert_=ConvertMode.PARTIAL)
+    assert isinstance(ret.a, dict)
+    assert isinstance(ret.b, DictConfig)
+    assert OmegaConf.get_type(ret.b) is User
+
+
+def test_instantiated_regular_class_container_types_partial2(
+    instantiate_func: Any,
+) -> None:
+    cfg = {"_target_": "tests.instantiate.SimpleClass", "a": [{}, User()], "b": None}
+    ret = instantiate_func(cfg, _convert_=ConvertMode.PARTIAL)
+    assert isinstance(ret.a, list)
+    assert isinstance(ret.a[0], dict)
+    assert isinstance(ret.a[1], DictConfig)
+    assert OmegaConf.get_type(ret.a[1]) is User
+
+
+@mark.parametrize(
+    "src",
+    [
+        {
+            "_target_": "tests.instantiate.SimpleClass",
+            "a": {"_target_": "tests.instantiate.SimpleClass", "a": {}, "b": User},
+            "b": None,
+        }
+    ],
+)
+def test_instantiated_regular_class_container_types_partial__recursive(
+    instantiate_func: Any, config: Any
+) -> None:
+    ret = instantiate_func(config, _convert_=ConvertMode.PARTIAL)
+    assert isinstance(ret.a, SimpleClass)
+    assert isinstance(ret.a.a, dict)
+    assert isinstance(ret.a.b, DictConfig)
+    assert OmegaConf.get_type(ret.a.b) is User
+
+
+@mark.parametrize(
+    "input_,is_primitive,expected",
+    [
+        param(
+            {
+                "value": 99,
+                "obj": SimpleClassPrimitiveConf(
+                    a={"foo": "${value}"}, b=[1, "${value}"]
+                ),
+            },
+            True,
+            SimpleClass(a={"foo": 99}, b=[1, 99]),
+            id="primitive_specified_true",
+        ),
+        param(
+            {
+                "value": 99,
+                "obj": SimpleClassNonPrimitiveConf(
+                    a={"foo": "${value}"}, b=[1, "${value}"]
+                ),
+            },
+            False,
+            SimpleClass(a={"foo": 99}, b=[1, 99]),
+            id="primitive_specified_false",
+        ),
+        param(
+            {
+                "value": 99,
+                "obj": SimpleClassDefaultPrimitiveConf(
+                    a={"foo": "${value}"}, b=[1, "${value}"]
+                ),
+            },
+            False,
+            SimpleClass(a={"foo": 99}, b=[1, 99]),
+            id="default_behavior",
+        ),
+    ],
+)
+def test_convert_in_config(
+    instantiate_func: Any, input_: Any, is_primitive: bool, expected: Any
+) -> None:
+    cfg = OmegaConf.create(input_)
+    ret = instantiate_func(cfg.obj)
+    assert ret == expected
+
+    if is_primitive:
+        assert isinstance(ret.a, dict)
+        assert isinstance(ret.b, list)
+    else:
+        assert isinstance(ret.a, DictConfig)
+        assert isinstance(ret.b, ListConfig)
+
+
+def test_nested_dataclass_with_partial_convert(instantiate_func: Any) -> None:
+    # dict
+    cfg = OmegaConf.structured(NestedConf)
+    ret = instantiate_func(cfg, _convert_="partial")
+    assert isinstance(ret.a, DictConfig) and OmegaConf.get_type(ret.a) == User
+    assert isinstance(ret.b, DictConfig) and OmegaConf.get_type(ret.b) == User
+    expected = SimpleClass(a=User(name="a", age=1), b=User(name="b", age=2))
+    assert ret == expected
+
+    # list
+    lst = [User(name="a", age=1)]
+    cfg = OmegaConf.structured(NestedConf(a=lst))
+    ret = instantiate_func(cfg, _convert_="partial")
+    assert isinstance(ret.a, list) and OmegaConf.get_type(ret.a[0]) == User
+    assert isinstance(ret.b, DictConfig) and OmegaConf.get_type(ret.b) == User
+    expected = SimpleClass(a=lst, b=User(name="b", age=2))
+    assert ret == expected
+
+
+class DictValues:
+    def __init__(self, d: Dict[str, User]):
+        self.d = d
+
+
+class ListValues:
+    def __init__(self, d: List[User]):
+        self.d = d
+
+
+def test_dict_with_structured_config(instantiate_func: Any) -> None:
+    @dataclass
+    class DictValuesConf:
+        _target_: str = "tests.instantiate.test_instantiate.DictValues"
+        d: Dict[str, User] = MISSING
+
+    schema = OmegaConf.structured(DictValuesConf)
+    cfg = OmegaConf.merge(schema, {"d": {"007": {"name": "Bond", "age": 7}}})
+    obj = instantiate_func(config=cfg, _convert_="none")
+    assert OmegaConf.is_dict(obj.d)
+    assert OmegaConf.get_type(obj.d["007"]) == User
+
+    obj = instantiate_func(config=cfg, _convert_="partial")
+    assert isinstance(obj.d, dict)
+    assert OmegaConf.get_type(obj.d["007"]) == User
+
+    obj = instantiate_func(config=cfg, _convert_="all")
+    assert isinstance(obj.d, dict)
+    assert isinstance(obj.d["007"], dict)
+
+
+def test_list_with_structured_config(instantiate_func: Any) -> None:
+    @dataclass
+    class ListValuesConf:
+        _target_: str = "tests.instantiate.test_instantiate.ListValues"
+        d: List[User] = MISSING
+
+    schema = OmegaConf.structured(ListValuesConf)
+    cfg = OmegaConf.merge(schema, {"d": [{"name": "Bond", "age": 7}]})
+
+    obj = instantiate_func(config=cfg, _convert_="none")
+    assert isinstance(obj.d, ListConfig)
+    assert OmegaConf.get_type(obj.d[0]) == User
+
+    obj = instantiate_func(config=cfg, _convert_="partial")
+    assert isinstance(obj.d, list)
+    assert OmegaConf.get_type(obj.d[0]) == User
+
+    obj = instantiate_func(config=cfg, _convert_="all")
+    assert isinstance(obj.d, list)
+    assert isinstance(obj.d[0], dict)
+
+
+def test_list_as_none(instantiate_func: Any) -> None:
+    @dataclass
+    class ListValuesConf:
+        _target_: str = "tests.instantiate.test_instantiate.ListValues"
+        d: Optional[List[User]] = None
+
+    cfg = OmegaConf.structured(ListValuesConf)
+    obj = instantiate_func(config=cfg)
+    assert obj.d is None
+
+
+def test_dict_as_none(instantiate_func: Any) -> None:
+    @dataclass
+    class DictValuesConf:
+        _target_: str = "tests.instantiate.test_instantiate.DictValues"
+        d: Optional[Dict[str, User]] = None
+
+    cfg = OmegaConf.structured(DictValuesConf)
+    obj = instantiate_func(config=cfg)
+    assert obj.d is None
diff --git a/tests/instantiate/test_positional.py b/tests/instantiate/test_positional.py
new file mode 100644
--- /dev/null
+++ b/tests/instantiate/test_positional.py
@@ -0,0 +1,241 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+from typing import Any
+
+from pytest import mark, param
+
+from hydra.utils import instantiate
+from tests.instantiate import ArgsClass
+
+#######################
+# non-recursive tests #
+#######################
+
+
+@mark.parametrize(
+    ("cfg", "expected"),
+    [
+        param(
+            {"_target_": "tests.instantiate.ArgsClass"},
+            ArgsClass(),
+            id="config:no_params",
+        ),
+        param(
+            {"_target_": "tests.instantiate.ArgsClass", "_args_": [1]},
+            ArgsClass(1),
+            id="config:args_only",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "_args_": [1],
+                "foo": 10,
+            },
+            ArgsClass(1, foo=10),
+            id="config:args+kwargs",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "foo": 10,
+            },
+            ArgsClass(foo=10),
+            id="config:kwargs_only",
+        ),
+    ],
+)
+def test_instantiate_args_kwargs(cfg: Any, expected: Any) -> None:
+    assert instantiate(cfg) == expected
+
+
+@mark.parametrize(
+    ("cfg", "expected"),
+    [
+        param(
+            {"_target_": "tests.instantiate.ArgsClass", "_args_": ["${.1}", 2]},
+            ArgsClass(2, 2),
+            id="config:args_only",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "_args_": [1],
+                "foo": "${._args_}",
+            },
+            ArgsClass(1, foo=[1]),
+            id="config:args+kwargs",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "foo": "${._target_}",
+            },
+            ArgsClass(foo="tests.instantiate.ArgsClass"),
+            id="config:kwargs_only)",
+        ),
+    ],
+)
+def test_instantiate_args_kwargs_with_interpolation(cfg: Any, expected: Any) -> None:
+    assert instantiate(cfg) == expected
+
+
+@mark.parametrize(
+    ("cfg", "args_override", "kwargs_override", "expected"),
+    [
+        param(
+            {"_target_": "tests.instantiate.ArgsClass", "_args_": [1]},
+            [2],
+            {},
+            ArgsClass(2),
+            id="direct_args",
+        ),
+        param(
+            {"_target_": "tests.instantiate.ArgsClass", "_args_": [1]},
+            [],
+            {"_args_": [2]},
+            ArgsClass(2),
+            id="indirect_args",
+        ),
+        param(
+            {"_target_": "tests.instantiate.ArgsClass", "_args_": [1]},
+            [],
+            {"foo": 10},
+            ArgsClass(1, foo=10),
+            id="kwargs",
+        ),
+        param(
+            {"_target_": "tests.instantiate.ArgsClass", "_args_": [1]},
+            [],
+            {"foo": 10, "_args_": [2]},
+            ArgsClass(2, foo=10),
+            id="kwargs+indirect_args",
+        ),
+    ],
+)
+def test_instantiate_args_kwargs_with_override(
+    cfg: Any, args_override: Any, expected: Any, kwargs_override: Any
+) -> None:
+    assert instantiate(cfg, *args_override, **kwargs_override) == expected
+
+
+# TODO: recursive
+
+###################
+# recursive tests #
+###################
+
+
+@mark.parametrize(
+    ("cfg", "expected"),
+    [
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "child": {"_target_": "tests.instantiate.ArgsClass"},
+            },
+            ArgsClass(child=ArgsClass()),
+            id="config:no_params",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "_args_": [1],
+                "child": {
+                    "_target_": "tests.instantiate.ArgsClass",
+                    "_args_": [2],
+                },
+            },
+            ArgsClass(1, child=ArgsClass(2)),
+            id="config:args_only",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "_args_": [1],
+                "foo": 10,
+                "child": {
+                    "_target_": "tests.instantiate.ArgsClass",
+                    "_args_": [2],
+                },
+            },
+            ArgsClass(1, foo=10, child=ArgsClass(2)),
+            id="config:args+kwargs",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "child": {
+                    "_target_": "tests.instantiate.ArgsClass",
+                    "_args_": [2],
+                },
+                "foo": 10,
+            },
+            ArgsClass(foo=10, child=ArgsClass(2)),
+            id="config:kwargs_only",
+        ),
+    ],
+)
+def test_recursive_instantiate_args_kwargs(cfg: Any, expected: Any) -> None:
+    assert instantiate(cfg) == expected
+
+
+@mark.parametrize(
+    ("cfg", "args_override", "kwargs_override", "expected"),
+    [
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "child": {"_target_": "tests.instantiate.ArgsClass"},
+            },
+            [1],
+            {},
+            ArgsClass(1, child=ArgsClass()),
+            id="direct_args_not_in_nested",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "_args_": [1],
+                "child": {
+                    "_target_": "tests.instantiate.ArgsClass",
+                    "_args_": [2],
+                },
+            },
+            [],
+            {"_args_": [3], "child": {"_args_": [4]}},
+            ArgsClass(3, child=ArgsClass(4)),
+            id="indirect_args",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "_args_": [1],
+                "child": {
+                    "_target_": "tests.instantiate.ArgsClass",
+                    "_args_": [2],
+                },
+            },
+            [],
+            {"foo": 10},
+            ArgsClass(1, foo=10, child=ArgsClass(2)),
+            id="kwargs",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.ArgsClass",
+                "_args_": [1],
+                "child": {
+                    "_target_": "tests.instantiate.ArgsClass",
+                    "_args_": [2],
+                },
+            },
+            [],
+            {"foo": 10, "_args_": [3], "child": {"_args_": [4]}},
+            ArgsClass(3, foo=10, child=ArgsClass(4)),
+            id="kwargs+indirect_args",
+        ),
+    ],
+)
+def test_recursive_instantiate_args_kwargs_with_override(
+    cfg: Any, args_override: Any, expected: Any, kwargs_override: Any
+) -> None:
+    assert instantiate(cfg, *args_override, **kwargs_override) == expected
diff --git a/tests/instantiate/test_positional_only_arguments.py b/tests/instantiate/test_positional_only_arguments.py
new file mode 100644
--- /dev/null
+++ b/tests/instantiate/test_positional_only_arguments.py
@@ -0,0 +1,51 @@
+# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
+import sys
+from typing import Any
+
+from pytest import mark, param, skip
+
+from hydra.utils import instantiate
+
+if sys.version_info < (3, 8):
+    skip(
+        msg="Positional-only syntax is only supported in Python 3.8 or newer",
+        allow_module_level=True,
+    )
+
+
+from .positional_only import PosOnlyArgsClass
+
+
+@mark.parametrize(
+    ("cfg", "args", "expected"),
+    [
+        param(
+            {
+                "_target_": "tests.instantiate.positional_only.PosOnlyArgsClass",
+                "_args_": [1, 2],
+            },
+            [],
+            PosOnlyArgsClass(1, 2),
+            id="pos_only_in_config",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.positional_only.PosOnlyArgsClass",
+            },
+            [1, 2],
+            PosOnlyArgsClass(1, 2),
+            id="pos_only_in_override",
+        ),
+        param(
+            {
+                "_target_": "tests.instantiate.positional_only.PosOnlyArgsClass",
+                "_args_": [1, 2],
+            },
+            [3, 4],
+            PosOnlyArgsClass(3, 4),
+            id="pos_only_in_both",
+        ),
+    ],
+)
+def test_positional_only_arguments(cfg: Any, args: Any, expected: Any) -> None:
+    assert instantiate(cfg, *args) == expected
diff --git a/tests/test_config_loader.py b/tests/test_config_loader.py
--- a/tests/test_config_loader.py
+++ b/tests/test_config_loader.py
@@ -20,7 +20,7 @@
 )
 from hydra.test_utils.test_utils import chdir_hydra_root
 from hydra.types import RunMode
-from tests import UserGroup
+from tests.instantiate import UserGroup
 
 chdir_hydra_root()
 
diff --git a/tests/test_internal_utils.py b/tests/test_internal_utils.py
--- a/tests/test_internal_utils.py
+++ b/tests/test_internal_utils.py
@@ -1,14 +1,10 @@
 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
-import re
 from typing import Any
 
-from _pytest.python_api import RaisesContext
 from omegaconf import DictConfig, OmegaConf
-from pytest import mark, param, raises
+from pytest import mark, param
 
 from hydra._internal import utils
-from hydra._internal.utils import _locate
-from tests import AClass, Adam, AnotherClass, ASubclass, NestingClass, Parameters
 
 
 @mark.parametrize(
@@ -36,31 +32,3 @@ def test_get_column_widths(matrix: Any, expected: Any) -> None:
 )
 def test_get_class_name(config: DictConfig, expected: Any) -> None:
     assert utils._get_cls_name(config) == expected
-
-
-@mark.parametrize(
-    "name,expected",
-    [
-        ("tests.Adam", Adam),
-        ("tests.Parameters", Parameters),
-        ("tests.AClass", AClass),
-        ("tests.ASubclass", ASubclass),
-        ("tests.NestingClass", NestingClass),
-        ("tests.AnotherClass", AnotherClass),
-        ("", raises(ImportError, match=re.escape("Empty path"))),
-        (
-            "not_found",
-            raises(ImportError, match=re.escape("Error loading module 'not_found'")),
-        ),
-        (
-            "tests.b.c.Door",
-            raises(ImportError, match=re.escape("No module named 'tests.b'")),
-        ),
-    ],
-)
-def test_locate(name: str, expected: Any) -> None:
-    if isinstance(expected, RaisesContext):
-        with expected:
-            _locate(name)
-    else:
-        assert _locate(name) == expected
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,287 +1,14 @@
 # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
-import copy
 import os
-import re
-from dataclasses import dataclass
 from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any
 
-from omegaconf import MISSING, DictConfig, ListConfig, OmegaConf
-from pytest import fixture, mark, param, raises, warns
+from omegaconf import DictConfig, OmegaConf
+from pytest import mark, raises
 
-import hydra._internal.instantiate._instantiate2
-import tests
 from hydra import utils
 from hydra.conf import HydraConf, RuntimeConf
 from hydra.core.hydra_config import HydraConfig
-from hydra.errors import InstantiationException
-from hydra.types import ConvertMode, TargetConf
-from tests import (
-    AClass,
-    Adam,
-    AdamConf,
-    AnotherClass,
-    ASubclass,
-    BadAdamConf,
-    BClass,
-    CenterCrop,
-    CenterCropConf,
-    Compose,
-    ComposeConf,
-    IllegalType,
-    Mapping,
-    MappingConf,
-    NestedConf,
-    NestingClass,
-    Parameters,
-    Rotation,
-    RotationConf,
-    SimpleClass,
-    SimpleClassDefaultPrimitiveConf,
-    SimpleClassNonPrimitiveConf,
-    SimpleClassPrimitiveConf,
-    SimpleDataClass,
-    Tree,
-    TreeConf,
-    UntypedPassthroughClass,
-    UntypedPassthroughConf,
-    User,
-    recisinstance,
-)
-
-
-@fixture(
-    params=[
-        hydra._internal.instantiate._instantiate2.instantiate,
-    ],
-    ids=[
-        "instantiate2",
-    ],
-)
-def instantiate_func(request: Any) -> Any:
-    return request.param
-
-
-@fixture(
-    params=[
-        lambda cfg: copy.deepcopy(cfg),
-        lambda cfg: OmegaConf.create(cfg),
-    ],
-    ids=[
-        "dict",
-        "dict_config",
-    ],
-)
-def config(request: Any, src: Any) -> Any:
-    config = request.param(src)
-    cfg_copy = copy.deepcopy(config)
-    yield config
-    assert config == cfg_copy
-
-
-@mark.parametrize("path,expected_type", [("tests.AClass", AClass)])
-def test_get_class(path: str, expected_type: type) -> None:
-    assert utils.get_class(path) == expected_type
-
-
-@mark.parametrize(
-    "recursive", [param(False, id="not_recursive"), param(True, id="recursive")]
-)
-@mark.parametrize(
-    "src, passthrough, expected",
-    [
-        param(
-            {"_target_": "tests.AClass", "a": 10, "b": 20, "c": 30, "d": 40},
-            {},
-            AClass(10, 20, 30, 40),
-            id="class",
-        ),
-        param(
-            {"_target_": "tests.AClass", "b": 20, "c": 30},
-            {"a": 10, "d": 40},
-            AClass(10, 20, 30, 40),
-            id="class+override",
-        ),
-        param(
-            {"_target_": "tests.AClass", "b": 200, "c": "${b}"},
-            {"a": 10, "b": 99, "d": 40},
-            AClass(10, 99, 99, 40),
-            id="class+override+interpolation",
-        ),
-        # Check class and static methods
-        param(
-            {"_target_": "tests.ASubclass.class_method", "y": 10},
-            {},
-            ASubclass(11),
-            id="class_method",
-        ),
-        param(
-            {"_target_": "tests.AClass.static_method", "z": 43},
-            {},
-            43,
-            id="static_method",
-        ),
-        # Check nested types and static methods
-        param(
-            {"_target_": "tests.NestingClass"},
-            {},
-            NestingClass(ASubclass(10)),
-            id="class_with_nested_class",
-        ),
-        param(
-            {"_target_": "tests.nesting.a.class_method", "y": 10},
-            {},
-            ASubclass(11),
-            id="class_method_on_an_object_nested_in_a_global",
-        ),
-        param(
-            {"_target_": "tests.nesting.a.static_method", "z": 43},
-            {},
-            43,
-            id="static_method_on_an_object_nested_in_a_global",
-        ),
-        # Check that default value is respected
-        param(
-            {"_target_": "tests.AClass"},
-            {"a": 10, "b": 20, "c": 30},
-            AClass(10, 20, 30, "default_value"),
-            id="instantiate_respects_default_value",
-        ),
-        # call a function from a module
-        param(
-            {"_target_": "tests.module_function", "x": 43},
-            {},
-            43,
-            id="call_function_in_module",
-        ),
-        # Check builtins
-        param(
-            {"_target_": "builtins.str", "object": 43},
-            {},
-            "43",
-            id="builtin_types",
-        ),
-        # Check that none is instantiated correctly
-        param(None, {}, None, id="instantiate_none"),
-        # passthrough
-        param(
-            {"_target_": "tests.AClass"},
-            {"a": 10, "b": 20, "c": 30},
-            AClass(a=10, b=20, c=30),
-            id="passthrough",
-        ),
-        param(
-            {"_target_": "tests.AClass"},
-            {"a": 10, "b": 20, "c": 30, "d": {"x": IllegalType()}},
-            AClass(a=10, b=20, c=30, d={"x": IllegalType()}),
-            id="oc_incompatible_passthrough",
-        ),
-        param(
-            {"_target_": "tests.AClass"},
-            {
-                "a": 10,
-                "b": 20,
-                "c": 30,
-                "d": {"x": [10, IllegalType()]},
-            },
-            AClass(a=10, b=20, c=30, d={"x": [10, IllegalType()]}),
-            id="passthrough:list",
-        ),
-        param(
-            UntypedPassthroughConf,
-            {"a": IllegalType()},
-            UntypedPassthroughClass(a=IllegalType()),
-            id="untyped_passthrough",
-        ),
-    ],
-)
-def test_class_instantiate(
-    instantiate_func: Any,
-    config: Any,
-    passthrough: Dict[str, Any],
-    expected: Any,
-    recursive: bool,
-) -> Any:
-    passthrough["_recursive_"] = recursive
-    obj = instantiate_func(config, **passthrough)
-    assert obj == expected
-
-
-@mark.parametrize(
-    "input_conf, passthrough, expected",
-    [
-        param(
-            {
-                "node": {
-                    "_target_": "tests.AClass",
-                    "a": "${value}",
-                    "b": 20,
-                    "c": 30,
-                    "d": 40,
-                },
-                "value": 99,
-            },
-            {},
-            AClass(99, 20, 30, 40),
-            id="interpolation_into_parent",
-        ),
-        param(
-            {
-                "A": {"_target_": "tests.add_values", "a": 1, "b": 2},
-                "node": {"_target_": "tests.add_values", "a": "${A}", "b": 3},
-            },
-            {},
-            6,
-            id="interpolation_from_recursive",
-        ),
-    ],
-)
-def test_interpolation_accessing_parent(
-    instantiate_func: Any, input_conf: Any, passthrough: Dict[str, Any], expected: Any
-) -> Any:
-    cfg_copy = OmegaConf.create(input_conf)
-    input_conf = OmegaConf.create(input_conf)
-    obj = instantiate_func(input_conf.node, **passthrough)
-    assert obj == expected
-    assert input_conf == cfg_copy
-
-
-def test_interpolation_is_live_in_instantiated_object(instantiate_func: Any) -> None:
-    """
-    Interpolations in instantiated objects are live config objects but not for primitive objects.
-    """
-    cfg = OmegaConf.create(
-        {
-            "node": {
-                "_target_": "tests.AClass",
-                "a": "${value}",
-                "b": {"x": "${value}"},
-                "c": 30,
-                "d": 40,
-            },
-            "value": 99,
-        }
-    )
-    obj = instantiate_func(cfg.node)
-    assert obj.a == 99
-    assert obj.b.x == 99
-
-    cfg.value = 3.14
-
-    # interpolation is not live for primitive objects
-    assert obj.a == 99  # unchanged
-    # but is live for config objects
-    assert obj.b.x == 3.14
-
-
-@mark.parametrize(
-    "src",
-    [({"_target_": "tests.AClass", "b": 200, "c": {"x": 10, "y": "${b}"}})],
-)
-def test_class_instantiate_omegaconf_node(instantiate_func: Any, config: Any) -> Any:
-    obj = instantiate_func(config, a=10, d=AnotherClass(99))
-    assert obj == AClass(a=10, b=200, c={"x": 10, "y": 200}, d=AnotherClass(99))
-    assert OmegaConf.is_config(obj.c)
 
 
 def test_get_original_cwd(hydra_restore_singletons: Any) -> None:
@@ -333,988 +60,3 @@ def test_to_absolute_path_without_hydra(
     path = str(Path(path))
     expected = str(Path(expected).absolute())
     assert utils.to_absolute_path(path) == expected
-
-
-@mark.parametrize("src", [{"_target_": "tests.Adam"}])
-def test_instantiate_adam(instantiate_func: Any, config: Any) -> None:
-    with raises(Exception):
-        # can't instantiate without passing params
-        instantiate_func(config)
-
-    adam_params = Parameters([1, 2, 3])
-    res = instantiate_func(config, params=adam_params)
-    assert res == Adam(params=adam_params)
-
-
-def test_instantiate_adam_conf(instantiate_func: Any) -> None:
-    with raises(Exception):
-        # can't instantiate without passing params
-        instantiate_func({"_target_": "tests.Adam"})
-
-    adam_params = Parameters([1, 2, 3])
-    res = instantiate_func(AdamConf(lr=0.123), params=adam_params)
-    expected = Adam(lr=0.123, params=adam_params)
-    assert res.params == expected.params
-    assert res.lr == expected.lr
-    assert list(res.betas) == list(expected.betas)  # OmegaConf converts tuples to lists
-    assert res.eps == expected.eps
-    assert res.weight_decay == expected.weight_decay
-    assert res.amsgrad == expected.amsgrad
-
-
-def test_instantiate_adam_conf_with_convert(instantiate_func: Any) -> None:
-    adam_params = Parameters([1, 2, 3])
-    res = instantiate_func(AdamConf(lr=0.123), params=adam_params, _convert_="all")
-    expected = Adam(lr=0.123, params=adam_params)
-    assert res.params == expected.params
-    assert res.lr == expected.lr
-    assert isinstance(res.betas, list)
-    assert list(res.betas) == list(expected.betas)  # OmegaConf converts tuples to lists
-    assert res.eps == expected.eps
-    assert res.weight_decay == expected.weight_decay
-    assert res.amsgrad == expected.amsgrad
-
-
-def test_targetconf_deprecated() -> None:
-    with warns(
-        expected_warning=UserWarning,
-        match=re.escape(
-            "TargetConf is deprecated since Hydra 1.1 and will be removed in Hydra 1.2."
-        ),
-    ):
-        TargetConf()
-
-
-def test_instantiate_bad_adam_conf(instantiate_func: Any, recwarn: Any) -> None:
-    msg = (
-        "Missing value for BadAdamConf._target_. Check that it's properly annotated and overridden."
-        "\nA common problem is forgetting to annotate _target_ as a string : '_target_: str = ...'"
-    )
-    with raises(
-        InstantiationException,
-        match=re.escape(msg),
-    ):
-        instantiate_func(BadAdamConf())
-
-
-def test_instantiate_with_missing_module(instantiate_func: Any) -> None:
-
-    with raises(
-        ModuleNotFoundError, match=re.escape("No module named 'some_missing_module'")
-    ):
-        # can't instantiate when importing a missing module
-        instantiate_func({"_target_": "tests.ClassWithMissingModule"})
-
-
-def test_pass_extra_variables(instantiate_func: Any) -> None:
-    cfg = OmegaConf.create({"_target_": "tests.AClass", "a": 10, "b": 20})
-    assert instantiate_func(cfg, c=30) == AClass(a=10, b=20, c=30)
-
-
-@mark.parametrize(
-    "src, passthrough, expected",
-    [
-        # direct
-        param(
-            {
-                "_target_": "tests.Tree",
-                "value": 1,
-                "left": {
-                    "_target_": "tests.Tree",
-                    "value": 21,
-                },
-                "right": {
-                    "_target_": "tests.Tree",
-                    "value": 22,
-                },
-            },
-            {},
-            Tree(value=1, left=Tree(value=21), right=Tree(value=22)),
-            id="recursive:direct:dict",
-        ),
-        param(
-            {"_target_": "tests.Tree"},
-            {"value": 1},
-            Tree(value=1),
-            id="recursive:direct:dict:passthrough",
-        ),
-        param(
-            {"_target_": "tests.Tree"},
-            {"value": 1, "left": {"_target_": "tests.Tree", "value": 2}},
-            Tree(value=1, left=Tree(2)),
-            id="recursive:direct:dict:passthrough",
-        ),
-        param(
-            {"_target_": "tests.Tree"},
-            {
-                "value": 1,
-                "left": {"_target_": "tests.Tree", "value": 2},
-                "right": {"_target_": "tests.Tree", "value": 3},
-            },
-            Tree(value=1, left=Tree(2), right=Tree(3)),
-            id="recursive:direct:dict:passthrough",
-        ),
-        param(
-            {"_target_": "tests.Tree"},
-            {"value": IllegalType()},
-            Tree(value=IllegalType()),
-            id="recursive:direct:dict:passthrough:incompatible_value",
-        ),
-        param(
-            {"_target_": "tests.Tree"},
-            {"value": 1, "left": {"_target_": "tests.Tree", "value": IllegalType()}},
-            Tree(value=1, left=Tree(value=IllegalType())),
-            id="recursive:direct:dict:passthrough:incompatible_value",
-        ),
-        param(
-            TreeConf(
-                value=1,
-                left=TreeConf(value=21),
-                right=TreeConf(value=22),
-            ),
-            {},
-            Tree(value=1, left=Tree(value=21), right=Tree(value=22)),
-            id="recursive:direct:dataclass",
-        ),
-        param(
-            TreeConf(
-                value=1,
-                left=TreeConf(value=21),
-            ),
-            {"right": {"value": 22}},
-            Tree(value=1, left=Tree(value=21), right=Tree(value=22)),
-            id="recursive:direct:dataclass:passthrough",
-        ),
-        param(
-            TreeConf(
-                value=1,
-                left=TreeConf(value=21),
-            ),
-            {"right": TreeConf(value=22)},
-            Tree(value=1, left=Tree(value=21), right=Tree(value=22)),
-            id="recursive:direct:dataclass:passthrough",
-        ),
-        param(
-            TreeConf(
-                value=1,
-                left=TreeConf(value=21),
-            ),
-            {
-                "right": TreeConf(value=IllegalType()),
-            },
-            Tree(value=1, left=Tree(value=21), right=Tree(value=IllegalType())),
-            id="recursive:direct:dataclass:passthrough",
-        ),
-        # list
-        # note that passthrough to a list element is not currently supported
-        param(
-            ComposeConf(
-                transforms=[
-                    CenterCropConf(size=10),
-                    RotationConf(degrees=45),
-                ]
-            ),
-            {},
-            Compose(
-                transforms=[
-                    CenterCrop(size=10),
-                    Rotation(degrees=45),
-                ]
-            ),
-            id="recursive:list:dataclass",
-        ),
-        param(
-            {
-                "_target_": "tests.Compose",
-                "transforms": [
-                    {"_target_": "tests.CenterCrop", "size": 10},
-                    {"_target_": "tests.Rotation", "degrees": 45},
-                ],
-            },
-            {},
-            Compose(
-                transforms=[
-                    CenterCrop(size=10),
-                    Rotation(degrees=45),
-                ]
-            ),
-            id="recursive:list:dict",
-        ),
-        # map
-        param(
-            MappingConf(
-                dictionary={
-                    "a": MappingConf(),
-                    "b": MappingConf(),
-                }
-            ),
-            {},
-            Mapping(
-                dictionary={
-                    "a": Mapping(),
-                    "b": Mapping(),
-                }
-            ),
-            id="recursive:map:dataclass",
-        ),
-        param(
-            {
-                "_target_": "tests.Mapping",
-                "dictionary": {
-                    "a": {"_target_": "tests.Mapping"},
-                    "b": {"_target_": "tests.Mapping"},
-                },
-            },
-            {},
-            Mapping(
-                dictionary={
-                    "a": Mapping(),
-                    "b": Mapping(),
-                }
-            ),
-            id="recursive:map:dict",
-        ),
-        param(
-            {
-                "_target_": "tests.Mapping",
-                "dictionary": {
-                    "a": {"_target_": "tests.Mapping"},
-                },
-            },
-            {
-                "dictionary": {
-                    "b": {"_target_": "tests.Mapping"},
-                },
-            },
-            Mapping(
-                dictionary={
-                    "a": Mapping(),
-                    "b": Mapping(),
-                }
-            ),
-            id="recursive:map:dict:passthrough",
-        ),
-    ],
-)
-def test_recursive_instantiation(
-    instantiate_func: Any,
-    config: Any,
-    passthrough: Dict[str, Any],
-    expected: Any,
-) -> None:
-    obj = instantiate_func(config, **passthrough)
-    assert obj == expected
-
-
-@mark.parametrize(
-    ("src", "passthrough", "expected"),
-    [
-        param(
-            {
-                "_target_": "tests.Tree",
-                "value": 1,
-                "left": {
-                    "_target_": "tests.Tree",
-                    "value": 21,
-                },
-            },
-            {},
-            Tree(value=1, left=Tree(value=21)),
-            id="default",
-        ),
-        param(
-            {
-                "_target_": "tests.Tree",
-                "_recursive_": True,
-                "value": 1,
-                "left": {
-                    "_target_": "tests.Tree",
-                    "value": 21,
-                },
-            },
-            {"_recursive_": True},
-            Tree(value=1, left=Tree(value=21)),
-            id="cfg:true,override:true",
-        ),
-        param(
-            {
-                "_target_": "tests.Tree",
-                "_recursive_": True,
-                "value": 1,
-                "left": {
-                    "_target_": "tests.Tree",
-                    "value": 21,
-                },
-            },
-            {"_recursive_": False},
-            Tree(value=1, left={"_target_": "tests.Tree", "value": 21}),
-            id="cfg:true,override:false",
-        ),
-        param(
-            {
-                "_target_": "tests.Tree",
-                "_recursive_": False,
-                "value": 1,
-                "left": {
-                    "_target_": "tests.Tree",
-                    "value": 21,
-                },
-            },
-            {"_recursive_": True},
-            Tree(value=1, left=Tree(value=21)),
-            id="cfg:false,override:true",
-        ),
-        param(
-            {
-                "_target_": "tests.Tree",
-                "_recursive_": False,
-                "value": 1,
-                "left": {
-                    "_target_": "tests.Tree",
-                    "value": 21,
-                },
-            },
-            {"_recursive_": False},
-            Tree(value=1, left={"_target_": "tests.Tree", "value": 21}),
-            id="cfg:false,override:false",
-        ),
-        param(
-            {
-                "_target_": "tests.Tree",
-                "value": 1,
-                "left": {
-                    "_target_": "tests.Tree",
-                    "value": 2,
-                    "left": {
-                        "_target_": "tests.Tree",
-                        "value": 3,
-                    },
-                },
-            },
-            {},
-            Tree(value=1, left=Tree(value=2, left=Tree(value=3))),
-            id="3_levels:default",
-        ),
-        param(
-            {
-                "_target_": "tests.Tree",
-                "_recursive_": False,
-                "value": 1,
-                "left": {
-                    "_target_": "tests.Tree",
-                    "value": 2,
-                    "left": {
-                        "_target_": "tests.Tree",
-                        "value": 3,
-                    },
-                },
-            },
-            {},
-            Tree(
-                value=1,
-                left={
-                    "_target_": "tests.Tree",
-                    "value": 2,
-                    "left": {"_target_": "tests.Tree", "value": 3},
-                },
-            ),
-            id="3_levels:cfg1=false",
-        ),
-        param(
-            {
-                "_target_": "tests.Tree",
-                "value": 1,
-                "left": {
-                    "_target_": "tests.Tree",
-                    "_recursive_": False,
-                    "value": 2,
-                    "left": {
-                        "_target_": "tests.Tree",
-                        "value": 3,
-                    },
-                },
-            },
-            {},
-            Tree(
-                value=1, left=Tree(value=2, left={"_target_": "tests.Tree", "value": 3})
-            ),
-            id="3_levels:cfg2=false",
-        ),
-    ],
-)
-def test_recursive_override(
-    instantiate_func: Any,
-    config: Any,
-    passthrough: Any,
-    expected: Any,
-) -> None:
-    obj = instantiate_func(config, **passthrough)
-    assert obj == expected
-
-
-@mark.parametrize(
-    ("src", "passthrough", "expected"),
-    [
-        param(
-            {"_target_": "tests.AClass", "a": 10, "b": 20, "c": 30, "d": 40},
-            {"_target_": "tests.BClass"},
-            BClass(10, 20, 30, 40),
-            id="str:override_same_args",
-        ),
-        param(
-            {"_target_": "tests.AClass", "a": 10, "b": 20, "c": 30, "d": 40},
-            {"_target_": BClass},
-            BClass(10, 20, 30, 40),
-            id="type:override_same_args",
-        ),
-        param(
-            {"_target_": "tests.AClass", "a": 10, "b": 20},
-            {"_target_": "tests.BClass"},
-            BClass(10, 20, "c", "d"),
-            id="str:override_other_args",
-        ),
-        param(
-            {"_target_": "tests.AClass", "a": 10, "b": 20},
-            {"_target_": BClass},
-            BClass(10, 20, "c", "d"),
-            id="type:override_other_args",
-        ),
-        param(
-            {
-                "_target_": "tests.AClass",
-                "a": 10,
-                "b": 20,
-                "c": {
-                    "_target_": "tests.AClass",
-                    "a": "aa",
-                    "b": "bb",
-                    "c": "cc",
-                },
-            },
-            {
-                "_target_": "tests.BClass",
-                "c": {
-                    "_target_": "tests.BClass",
-                },
-            },
-            BClass(10, 20, BClass(a="aa", b="bb", c="cc"), "d"),
-            id="str:recursive_override",
-        ),
-        param(
-            {
-                "_target_": "tests.AClass",
-                "a": 10,
-                "b": 20,
-                "c": {
-                    "_target_": "tests.AClass",
-                    "a": "aa",
-                    "b": "bb",
-                    "c": "cc",
-                },
-            },
-            {"_target_": BClass, "c": {"_target_": BClass}},
-            BClass(10, 20, BClass(a="aa", b="bb", c="cc"), "d"),
-            id="type:recursive_override",
-        ),
-    ],
-)
-def test_override_target(
-    instantiate_func: Any, config: Any, passthrough: Any, expected: Any
-) -> None:
-    obj = instantiate_func(config, **passthrough)
-    assert obj == expected
-
-
-@mark.parametrize(
-    "config, passthrough, expected",
-    [
-        param(
-            {"_target_": tests.AnotherClass, "x": 10},
-            {},
-            AnotherClass(10),
-            id="class_in_config_dict",
-        ),
-    ],
-)
-def test_instantiate_from_class_in_dict(
-    instantiate_func: Any, config: Any, passthrough: Any, expected: Any
-) -> None:
-    config_copy = copy.deepcopy(config)
-    assert instantiate_func(config, **passthrough) == expected
-    assert config == config_copy
-
-
-@mark.parametrize(
-    "config, passthrough, expected",
-    [
-        param(
-            OmegaConf.create({"_target_": tests.AClass}),
-            {},
-            AClass(10, 20, 30, 40),
-            id="class_in_config_dict",
-        ),
-    ],
-)
-def test_instantiate_from_dataclass_in_dict_fails(
-    instantiate_func: Any, config: Any, passthrough: Any, expected: Any
-) -> None:
-    # not the best error, but it will get the user to check their input config.
-    msg = "Unsupported target type: DictConfig. value: {'a': '???', 'b': '???', 'c': '???', 'd': 'default_value'}"
-    with raises(
-        InstantiationException,
-        match=re.escape(msg),
-    ):
-        assert instantiate_func(config, **passthrough) == expected
-
-
-@mark.parametrize(
-    "primitive,expected_primitive",
-    [
-        param(None, False, id="unspecified"),
-        param(ConvertMode.NONE, False, id="none"),
-        param(ConvertMode.PARTIAL, True, id="partial"),
-        param(ConvertMode.ALL, True, id="all"),
-    ],
-)
-@mark.parametrize(
-    "input_,expected",
-    [
-        param(
-            {
-                "obj": {
-                    "_target_": "tests.AClass",
-                    "a": {"foo": "bar"},
-                    "b": OmegaConf.create({"foo": "bar"}),
-                    "c": [1, 2, 3],
-                    "d": OmegaConf.create([1, 2, 3]),
-                },
-            },
-            AClass(
-                a={"foo": "bar"},
-                b={"foo": "bar"},
-                c=[1, 2, 3],
-                d=[1, 2, 3],
-            ),
-            id="simple",
-        ),
-        param(
-            {
-                "value": 99,
-                "obj": {
-                    "_target_": "tests.AClass",
-                    "a": {"foo": "${value}"},
-                    "b": OmegaConf.create({"foo": "${value}"}),
-                    "c": [1, "${value}"],
-                    "d": OmegaConf.create([1, "${value}"]),
-                },
-            },
-            AClass(
-                a={"foo": 99},
-                b={"foo": 99},
-                c=[1, 99],
-                d=[1, 99],
-            ),
-            id="interpolation",
-        ),
-    ],
-)
-def test_convert_params_override(
-    instantiate_func: Any,
-    primitive: Optional[bool],
-    expected_primitive: bool,
-    input_: Any,
-    expected: Any,
-) -> None:
-    input_cfg = OmegaConf.create(input_)
-    if primitive is not None:
-        ret = instantiate_func(input_cfg.obj, _convert_=primitive)
-    else:
-        ret = instantiate_func(input_cfg.obj)
-
-    expected_list: Any
-    expected_dict: Any
-    if expected_primitive is True:
-        expected_list = list
-        expected_dict = dict
-    else:
-        expected_list = ListConfig
-        expected_dict = DictConfig
-
-    assert ret == expected
-    assert isinstance(ret.a, expected_dict)
-    assert isinstance(ret.b, expected_dict)
-    assert isinstance(ret.c, expected_list)
-    assert isinstance(ret.d, expected_list)
-
-
-@mark.parametrize(
-    "convert_mode",
-    [
-        param(None, id="none"),
-        param(ConvertMode.NONE, id="none"),
-        param("none", id="none"),
-        param(ConvertMode.PARTIAL, id="partial"),
-        param("partial", id="partial"),
-        param(ConvertMode.ALL, id="all"),
-        param("all", id="all"),
-    ],
-)
-@mark.parametrize(
-    "input_,expected",
-    [
-        param(
-            {
-                "value": 99,
-                "obj": {
-                    "_target_": "tests.SimpleClass",
-                    "a": {
-                        "_target_": "tests.SimpleClass",
-                        "a": {"foo": "${value}"},
-                        "b": [1, "${value}"],
-                    },
-                    "b": None,
-                },
-            },
-            SimpleClass(a={"foo": 99}, b=[1, 99]),
-            id="simple",
-        ),
-    ],
-)
-def test_convert_params(
-    instantiate_func: Any, input_: Any, expected: Any, convert_mode: Any
-) -> None:
-    cfg = OmegaConf.create(input_)
-    kwargs = {"a": {"_convert_": convert_mode}}
-    ret = instantiate_func(cfg.obj, **kwargs)
-
-    if convert_mode in (ConvertMode.PARTIAL, ConvertMode.ALL):
-        assert isinstance(ret.a.a, dict)
-        assert isinstance(ret.a.b, list)
-    elif convert_mode in (None, ConvertMode.NONE):
-        assert isinstance(ret.a.a, DictConfig)
-        assert isinstance(ret.a.b, ListConfig)
-    else:
-        assert False
-
-    assert ret.a == expected
-
-
-@mark.parametrize("nested_recursive", [True, False])
-def test_convert_and_recursive_node(
-    instantiate_func: Any, nested_recursive: bool
-) -> None:
-    cfg = {
-        "_target_": "tests.SimpleClass",
-        "a": {
-            "_target_": "tests.SimpleClass",
-            "_convert_": "all",
-            "_recursive_": nested_recursive,
-            "a": {},
-            "b": [],
-        },
-        "b": None,
-    }
-
-    obj = instantiate_func(cfg)
-    assert isinstance(obj.a.a, dict)
-    assert isinstance(obj.a.b, list)
-
-
-@mark.parametrize(
-    "src,expected",
-    [
-        param(
-            {
-                "value": 99,
-                "obj": {
-                    "_target_": "tests.SimpleDataClass",
-                    "a": {
-                        "_target_": "tests.SimpleDataClass",
-                        "a": {"foo": "${value}"},
-                        "b": [1, "${value}"],
-                    },
-                    "b": None,
-                },
-            },
-            (
-                SimpleDataClass(
-                    a=SimpleDataClass(
-                        a=OmegaConf.create({"foo": 99}), b=OmegaConf.create([1, 99])
-                    ),
-                    b=None,
-                ),
-                SimpleDataClass(a=SimpleDataClass(a={"foo": 99}, b=[1, 99]), b=None),
-                SimpleDataClass(a=SimpleDataClass(a={"foo": 99}, b=[1, 99]), b=None),
-            ),
-            id="dataclass+dataclass",
-        ),
-        param(
-            {
-                "value": 99,
-                "obj": {
-                    "_target_": "tests.SimpleClass",
-                    "a": {
-                        "_target_": "tests.SimpleDataClass",
-                        "a": {"foo": "${value}"},
-                        "b": [1, "${value}"],
-                    },
-                    "b": None,
-                },
-            },
-            (
-                SimpleClass(
-                    a=SimpleDataClass(
-                        a=OmegaConf.create({"foo": 99}), b=OmegaConf.create([1, 99])
-                    ),
-                    b=None,
-                ),
-                SimpleClass(a=SimpleDataClass(a={"foo": 99}, b=[1, 99]), b=None),
-                SimpleClass(a=SimpleDataClass(a={"foo": 99}, b=[1, 99]), b=None),
-            ),
-            id="class+dataclass",
-        ),
-        param(
-            {
-                "value": 99,
-                "obj": {
-                    "a": {
-                        "_target_": "tests.SimpleDataClass",
-                        "a": {"foo": "${value}"},
-                        "b": [1, "${value}"],
-                    },
-                    "b": None,
-                },
-            },
-            (
-                # a is a DictConfig because of top level DictConfig
-                OmegaConf.create(
-                    {
-                        "a": SimpleDataClass(
-                            a=OmegaConf.create({"foo": 99}), b=OmegaConf.create([1, 99])
-                        ),
-                        "b": None,
-                    }
-                ),
-                {"a": SimpleDataClass(a={"foo": 99}, b=[1, 99]), "b": None},
-                {"a": SimpleDataClass(a={"foo": 99}, b=[1, 99]), "b": None},
-            ),
-            id="dict+dataclass",
-        ),
-    ],
-)
-def test_instantiate_convert_dataclasses(
-    instantiate_func: Any, config: Any, expected: Tuple[Any, Any, Any]
-) -> None:
-    """Instantiate on nested dataclass + dataclass."""
-    modes = [ConvertMode.NONE, ConvertMode.PARTIAL, ConvertMode.ALL]
-    for exp, mode in zip(expected, modes):
-        # create DictConfig to ensure interpolations are working correctly when we pass a cfg.obj
-        cfg = OmegaConf.create(config)
-        instance = instantiate_func(cfg.obj, _convert_=mode)
-        assert instance == exp
-        assert recisinstance(instance, exp)
-
-
-@mark.parametrize(
-    ("mode", "expected_dict", "expected_list"),
-    [
-        param(ConvertMode.NONE, DictConfig, ListConfig, id="none"),
-        param(ConvertMode.ALL, dict, list, id="all"),
-        param(ConvertMode.PARTIAL, dict, list, id="partial"),
-    ],
-)
-def test_instantiated_regular_class_container_types(
-    instantiate_func: Any, mode: Any, expected_dict: Any, expected_list: Any
-) -> None:
-    cfg = {"_target_": "tests.SimpleClass", "a": {}, "b": []}
-    ret = instantiate_func(cfg, _convert_=mode)
-    assert isinstance(ret.a, expected_dict)
-    assert isinstance(ret.b, expected_list)
-
-    cfg2 = {
-        "_target_": "tests.SimpleClass",
-        "a": {"_target_": "tests.SimpleClass", "a": {}, "b": []},
-        "b": [{"_target_": "tests.SimpleClass", "a": {}, "b": []}],
-    }
-    ret = instantiate_func(cfg2, _convert_=mode)
-    assert isinstance(ret.a.a, expected_dict)
-    assert isinstance(ret.a.b, expected_list)
-    assert isinstance(ret.b[0].a, expected_dict)
-    assert isinstance(ret.b[0].b, expected_list)
-
-
-def test_instantiated_regular_class_container_types_partial(
-    instantiate_func: Any,
-) -> None:
-    cfg = {"_target_": "tests.SimpleClass", "a": {}, "b": User()}
-    ret = instantiate_func(cfg, _convert_=ConvertMode.PARTIAL)
-    assert isinstance(ret.a, dict)
-    assert isinstance(ret.b, DictConfig)
-    assert OmegaConf.get_type(ret.b) is User
-
-
-def test_instantiated_regular_class_container_types_partial2(
-    instantiate_func: Any,
-) -> None:
-    cfg = {"_target_": "tests.SimpleClass", "a": [{}, User()], "b": None}
-    ret = instantiate_func(cfg, _convert_=ConvertMode.PARTIAL)
-    assert isinstance(ret.a, list)
-    assert isinstance(ret.a[0], dict)
-    assert isinstance(ret.a[1], DictConfig)
-    assert OmegaConf.get_type(ret.a[1]) is User
-
-
-@mark.parametrize(
-    "src",
-    [
-        {
-            "_target_": "tests.SimpleClass",
-            "a": {"_target_": "tests.SimpleClass", "a": {}, "b": User},
-            "b": None,
-        }
-    ],
-)
-def test_instantiated_regular_class_container_types_partial__recursive(
-    instantiate_func: Any, config: Any
-) -> None:
-    ret = instantiate_func(config, _convert_=ConvertMode.PARTIAL)
-    assert isinstance(ret.a, SimpleClass)
-    assert isinstance(ret.a.a, dict)
-    assert isinstance(ret.a.b, DictConfig)
-    assert OmegaConf.get_type(ret.a.b) is User
-
-
-@mark.parametrize(
-    "input_,is_primitive,expected",
-    [
-        param(
-            {
-                "value": 99,
-                "obj": SimpleClassPrimitiveConf(
-                    a={"foo": "${value}"}, b=[1, "${value}"]
-                ),
-            },
-            True,
-            SimpleClass(a={"foo": 99}, b=[1, 99]),
-            id="primitive_specified_true",
-        ),
-        param(
-            {
-                "value": 99,
-                "obj": SimpleClassNonPrimitiveConf(
-                    a={"foo": "${value}"}, b=[1, "${value}"]
-                ),
-            },
-            False,
-            SimpleClass(a={"foo": 99}, b=[1, 99]),
-            id="primitive_specified_false",
-        ),
-        param(
-            {
-                "value": 99,
-                "obj": SimpleClassDefaultPrimitiveConf(
-                    a={"foo": "${value}"}, b=[1, "${value}"]
-                ),
-            },
-            False,
-            SimpleClass(a={"foo": 99}, b=[1, 99]),
-            id="default_behavior",
-        ),
-    ],
-)
-def test_convert_in_config(
-    instantiate_func: Any, input_: Any, is_primitive: bool, expected: Any
-) -> None:
-    cfg = OmegaConf.create(input_)
-    ret = instantiate_func(cfg.obj)
-    assert ret == expected
-
-    if is_primitive:
-        assert isinstance(ret.a, dict)
-        assert isinstance(ret.b, list)
-    else:
-        assert isinstance(ret.a, DictConfig)
-        assert isinstance(ret.b, ListConfig)
-
-
-def test_nested_dataclass_with_partial_convert(instantiate_func: Any) -> None:
-    # dict
-    cfg = OmegaConf.structured(NestedConf)
-    ret = instantiate_func(cfg, _convert_="partial")
-    assert isinstance(ret.a, DictConfig) and OmegaConf.get_type(ret.a) == User
-    assert isinstance(ret.b, DictConfig) and OmegaConf.get_type(ret.b) == User
-    expected = SimpleClass(a=User(name="a", age=1), b=User(name="b", age=2))
-    assert ret == expected
-
-    # list
-    lst = [User(name="a", age=1)]
-    cfg = OmegaConf.structured(NestedConf(a=lst))
-    ret = instantiate_func(cfg, _convert_="partial")
-    assert isinstance(ret.a, list) and OmegaConf.get_type(ret.a[0]) == User
-    assert isinstance(ret.b, DictConfig) and OmegaConf.get_type(ret.b) == User
-    expected = SimpleClass(a=lst, b=User(name="b", age=2))
-    assert ret == expected
-
-
-class DictValues:
-    def __init__(self, d: Dict[str, User]):
-        self.d = d
-
-
-class ListValues:
-    def __init__(self, d: List[User]):
-        self.d = d
-
-
-def test_dict_with_structured_config(instantiate_func: Any) -> None:
-    @dataclass
-    class DictValuesConf:
-        _target_: str = "tests.test_utils.DictValues"
-        d: Dict[str, User] = MISSING
-
-    schema = OmegaConf.structured(DictValuesConf)
-    cfg = OmegaConf.merge(schema, {"d": {"007": {"name": "Bond", "age": 7}}})
-    obj = instantiate_func(config=cfg, _convert_="none")
-    assert OmegaConf.is_dict(obj.d)
-    assert OmegaConf.get_type(obj.d["007"]) == User
-
-    obj = instantiate_func(config=cfg, _convert_="partial")
-    assert isinstance(obj.d, dict)
-    assert OmegaConf.get_type(obj.d["007"]) == User
-
-    obj = instantiate_func(config=cfg, _convert_="all")
-    assert isinstance(obj.d, dict)
-    assert isinstance(obj.d["007"], dict)
-
-
-def test_list_with_structured_config(instantiate_func: Any) -> None:
-    @dataclass
-    class ListValuesConf:
-        _target_: str = "tests.test_utils.ListValues"
-        d: List[User] = MISSING
-
-    schema = OmegaConf.structured(ListValuesConf)
-    cfg = OmegaConf.merge(schema, {"d": [{"name": "Bond", "age": 7}]})
-
-    obj = instantiate_func(config=cfg, _convert_="none")
-    assert isinstance(obj.d, ListConfig)
-    assert OmegaConf.get_type(obj.d[0]) == User
-
-    obj = instantiate_func(config=cfg, _convert_="partial")
-    assert isinstance(obj.d, list)
-    assert OmegaConf.get_type(obj.d[0]) == User
-
-    obj = instantiate_func(config=cfg, _convert_="all")
-    assert isinstance(obj.d, list)
-    assert isinstance(obj.d[0], dict)
-
-
-def test_list_as_none(instantiate_func: Any) -> None:
-    @dataclass
-    class ListValuesConf:
-        _target_: str = "tests.test_utils.ListValues"
-        d: Optional[List[User]] = None
-
-    cfg = OmegaConf.structured(ListValuesConf)
-    obj = instantiate_func(config=cfg)
-    assert obj.d is None