Repository: reagento/dishka
Pull request #249 (2024-09-29 18:05:44)

Support type[T] in factory params
related to #37 


Patch:
diff --git a/docs/provider/provide.rst b/docs/provider/provide.rst
index 04669d4..fd1459f 100644
--- a/docs/provider/provide.rst
+++ b/docs/provider/provide.rst
@@ -153,3 +153,13 @@ WithParents generates only one factory and many aliases and is equivalent to ``A
 
     class MyProvider(Provider):
         c = provide(C, scope=Scope.APP, recursive=True)
+
+
+* You can use factory with Generic classes
+
+.. code-block:: python
+
+    class MyProvider(Provider):
+        @provide
+        def make_a(self, type_: type[T]) -> A[T]:
+            ...
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 37de389..3e2eab6 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -1,4 +1,4 @@
 ruff==0.5.*
 tox==4.15.*
 tox-uv==1.11.*
-mypy==1.10.1
+mypy==1.11.2
diff --git a/src/dishka/_adaptix/common.py b/src/dishka/_adaptix/common.py
index bae88ea..ee3be80 100644
--- a/src/dishka/_adaptix/common.py
+++ b/src/dishka/_adaptix/common.py
@@ -1,4 +1,4 @@
-from typing import TYPE_CHECKING, Any, Callable, Tuple, Type, TypeVar, Union
+from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union
 
 K_contra = TypeVar("K_contra", contravariant=True)
 V_co = TypeVar("V_co", covariant=True)
@@ -12,9 +12,9 @@ OneArgCoercer = Callable[[Any], Any]
 
 TypeHint = Any
 
-VarTuple = Tuple[T, ...]
+VarTuple = tuple[T, ...]
 
-Catchable = Union[Type[BaseException], VarTuple[Type[BaseException]]]
+Catchable = Union[type[BaseException], VarTuple[type[BaseException]]]
 
 # https://github.com/python/typing/issues/684#issuecomment-548203158
 if TYPE_CHECKING:
diff --git a/src/dishka/_adaptix/feature_requirement.py b/src/dishka/_adaptix/feature_requirement.py
index ea10c9c..613f06e 100644
--- a/src/dishka/_adaptix/feature_requirement.py
+++ b/src/dishka/_adaptix/feature_requirement.py
@@ -3,7 +3,8 @@ import itertools
 import re
 import sys
 from abc import ABC, abstractmethod
-from typing import Any, Iterable
+from collections.abc import Iterable
+from typing import Any
 
 from .common import VarTuple
 
@@ -17,11 +18,11 @@ def _false():
 
 
 class Requirement(ABC):
-    __slots__ = ("is_meet", "__bool__", "__dict__")
+    __slots__ = ("is_met", "__bool__", "__dict__")
 
     def __init__(self):
-        self.is_meet = self._evaluate()
-        self.__bool__ = _true if self.is_meet else _false
+        self.is_met = self._evaluate()
+        self.__bool__ = _true if self.is_met else _false
 
     @abstractmethod
     def _evaluate(self) -> bool:
@@ -148,10 +149,6 @@ class PythonImplementationRequirement(Requirement):
         return f"{self.implementation_name} is required"
 
 
-HAS_PY_39 = PythonVersionRequirement((3, 9))
-HAS_ANNOTATED = HAS_PY_39
-HAS_STD_CLASSES_GENERICS = HAS_PY_39
-
 HAS_PY_310 = PythonVersionRequirement((3, 10))
 HAS_TYPE_UNION_OP = HAS_PY_310
 HAS_TYPE_GUARD = HAS_PY_310
diff --git a/src/dishka/_adaptix/type_tools/__init__.py b/src/dishka/_adaptix/type_tools/__init__.py
index 79d25e6..989ebf7 100644
--- a/src/dishka/_adaptix/type_tools/__init__.py
+++ b/src/dishka/_adaptix/type_tools/__init__.py
@@ -23,3 +23,4 @@ from .normalize_type import (
     make_norm_type,
     normalize_type,
 )
+from .type_evaler import exec_type_checking, make_fragments_collector
diff --git a/src/dishka/_adaptix/type_tools/basic_utils.py b/src/dishka/_adaptix/type_tools/basic_utils.py
index 198b8ac..beedeed 100644
--- a/src/dishka/_adaptix/type_tools/basic_utils.py
+++ b/src/dishka/_adaptix/type_tools/basic_utils.py
@@ -1,9 +1,9 @@
 import types
 import typing
-from typing import Any, Dict, ForwardRef, Generic, NewType, Protocol, TypedDict, TypeVar, Union
+from typing import Annotated, Any, ForwardRef, Generic, NewType, Protocol, TypedDict, TypeVar, Union
 
 from ..common import TypeHint, VarTuple
-from ..feature_requirement import HAS_ANNOTATED, HAS_PY_39, HAS_PY_312, HAS_STD_CLASSES_GENERICS
+from ..feature_requirement import HAS_PY_312
 from .constants import BUILTIN_ORIGIN_TO_TYPEVARS
 from .fundamentals import get_generic_args, get_type_vars, strip_alias
 
@@ -81,16 +81,12 @@ def is_generic(tp: TypeHint) -> bool:
         bool(get_type_vars(tp))
         or (
             strip_alias(tp) in BUILTIN_ORIGIN_TO_TYPEVARS
-            and tp != type
+            and tp is not type
             and not is_parametrized(tp)
-            and (
-                bool(HAS_STD_CLASSES_GENERICS) or not isinstance(tp, type)
-            )
         )
         or (
-            bool(HAS_ANNOTATED)
-            and strip_alias(tp) == typing.Annotated
-            and tp != typing.Annotated
+            strip_alias(tp) == Annotated
+            and tp != Annotated
             and is_generic(tp.__origin__)
         )
     )
@@ -129,7 +125,7 @@ def get_type_vars_of_parametrized(tp: TypeHint) -> VarTuple[TypeVar]:
     if not params:
         return ()
     if isinstance(tp, type):
-        if HAS_STD_CLASSES_GENERICS and isinstance(tp, types.GenericAlias):
+        if isinstance(tp, types.GenericAlias):
             return params
         return ()
     if strip_alias(tp) != tp and get_generic_args(tp) == ():
@@ -137,9 +133,5 @@ def get_type_vars_of_parametrized(tp: TypeHint) -> VarTuple[TypeVar]:
     return params
 
 
-if HAS_PY_39:
-    def eval_forward_ref(namespace: Dict[str, Any], forward_ref: ForwardRef):
-        return forward_ref._evaluate(namespace, None, frozenset())
-else:
-    def eval_forward_ref(namespace: Dict[str, Any], forward_ref: ForwardRef):
-        return forward_ref._evaluate(namespace, None)  # type: ignore[call-arg]
+def eval_forward_ref(namespace: dict[str, Any], forward_ref: ForwardRef):
+    return forward_ref._evaluate(namespace, None, recursive_guard=frozenset())
diff --git a/src/dishka/_adaptix/type_tools/constants.py b/src/dishka/_adaptix/type_tools/constants.py
index e296399..b6a13eb 100644
--- a/src/dishka/_adaptix/type_tools/constants.py
+++ b/src/dishka/_adaptix/type_tools/constants.py
@@ -3,8 +3,9 @@ import collections
 import concurrent.futures
 import queue
 import re
+from collections.abc import Mapping
 from os import PathLike
-from typing import Mapping, TypeVar
+from typing import TypeVar
 
 from ..common import VarTuple
 
diff --git a/src/dishka/_adaptix/type_tools/fundamentals.py b/src/dishka/_adaptix/type_tools/fundamentals.py
index f83bfc3..0a40380 100644
--- a/src/dishka/_adaptix/type_tools/fundamentals.py
+++ b/src/dishka/_adaptix/type_tools/fundamentals.py
@@ -2,7 +2,7 @@ import types
 from typing import TypeVar, get_args, get_origin, get_type_hints
 
 from ..common import TypeHint, VarTuple
-from ..feature_requirement import HAS_ANNOTATED, HAS_SUPPORTED_PYDANTIC_PKG
+from ..feature_requirement import HAS_SUPPORTED_PYDANTIC_PKG
 
 __all__ = ("is_pydantic_class", "strip_alias", "get_type_vars", "get_generic_args", "get_all_type_hints")
 
@@ -41,8 +41,5 @@ def get_generic_args(tp: TypeHint) -> VarTuple[TypeHint]:
     return get_args(tp)
 
 
-if HAS_ANNOTATED:
-    def get_all_type_hints(obj, globalns=None, localns=None):
-        return get_type_hints(obj, globalns, localns, include_extras=True)
-else:
-    get_all_type_hints = get_type_hints
+def get_all_type_hints(obj, globalns=None, localns=None):
+    return get_type_hints(obj, globalns, localns, include_extras=True)
diff --git a/src/dishka/_adaptix/type_tools/generic_resolver.py b/src/dishka/_adaptix/type_tools/generic_resolver.py
index 90233a3..3f8c734 100644
--- a/src/dishka/_adaptix/type_tools/generic_resolver.py
+++ b/src/dishka/_adaptix/type_tools/generic_resolver.py
@@ -1,12 +1,13 @@
 import typing
+from collections.abc import Collection, Hashable, Mapping
 from dataclasses import dataclass, replace
 from itertools import chain
-from typing import Callable, Collection, Dict, Generic, Hashable, Mapping, TypeVar
+from typing import Callable, Generic, TypeVar
 
 from ..common import TypeHint
 from ..feature_requirement import HAS_TV_TUPLE, HAS_UNPACK
-from . import get_generic_args
 from .basic_utils import get_type_vars, get_type_vars_of_parametrized, is_generic, is_parametrized, strip_alias
+from .fundamentals import get_generic_args
 from .implicit_params import fill_implicit_params
 from .normalize_type import normalize_type
 
@@ -85,7 +86,7 @@ class GenericResolver(Generic[K, M]):
         if not hasattr(tp, "__orig_bases__"):
             return members_storage
 
-        bases_members: Dict[K, TypeHint] = {}
+        bases_members: dict[K, TypeHint] = {}
         for base in reversed(tp.__orig_bases__):
             bases_members.update(self.get_resolved_members(base).members)
 
diff --git a/src/dishka/_adaptix/type_tools/implicit_params.py b/src/dishka/_adaptix/type_tools/implicit_params.py
index 99f4fff..34c45e9 100644
--- a/src/dishka/_adaptix/type_tools/implicit_params.py
+++ b/src/dishka/_adaptix/type_tools/implicit_params.py
@@ -1,6 +1,6 @@
 import sys
 import typing
-from typing import Any, ForwardRef, Tuple, TypeVar
+from typing import Any, ForwardRef, TypeVar
 
 from ..common import TypeHint, VarTuple
 from ..feature_requirement import HAS_PARAM_SPEC, HAS_TV_TUPLE
@@ -18,7 +18,7 @@ class ImplicitParamsGetter:
         if HAS_PARAM_SPEC and isinstance(type_var, typing.ParamSpec):
             return ...
         if HAS_TV_TUPLE and isinstance(type_var, typing.TypeVarTuple):
-            return typing.Unpack[Tuple[Any, ...]]
+            return typing.Unpack[tuple[Any, ...]]
         if type_var.__constraints__:
             return create_union(
                 tuple(
diff --git a/src/dishka/_adaptix/type_tools/norm_utils.py b/src/dishka/_adaptix/type_tools/norm_utils.py
index 4a41443..a3aeaa5 100644
--- a/src/dishka/_adaptix/type_tools/norm_utils.py
+++ b/src/dishka/_adaptix/type_tools/norm_utils.py
@@ -1,14 +1,11 @@
 import typing
 from dataclasses import InitVar
-from typing import ClassVar, Final, TypeVar
+from typing import Annotated, ClassVar, Final, TypeVar
 
-from ..feature_requirement import HAS_ANNOTATED, HAS_TYPED_DICT_REQUIRED
+from ..feature_requirement import HAS_TYPED_DICT_REQUIRED
 from .normalize_type import BaseNormType
 
-_TYPE_TAGS = [Final, ClassVar, InitVar]
-
-if HAS_ANNOTATED:
-    _TYPE_TAGS.append(typing.Annotated)
+_TYPE_TAGS = [Final, ClassVar, InitVar, Annotated]
 
 if HAS_TYPED_DICT_REQUIRED:
     _TYPE_TAGS.extend([typing.Required, typing.NotRequired])
@@ -26,12 +23,6 @@ def strip_tags(norm: BaseNormType) -> BaseNormType:
 N = TypeVar("N", bound=BaseNormType)
 
 
-def strip_annotated(value: N) -> N:
-    if HAS_ANNOTATED and isinstance(value, BaseNormType) and value.origin == typing.Annotated:
-        return strip_annotated(value)
-    return value
-
-
 def is_class_var(norm: BaseNormType) -> bool:
     if norm.origin == ClassVar:
         return True
diff --git a/src/dishka/_adaptix/type_tools/normalize_type.py b/src/dishka/_adaptix/type_tools/normalize_type.py
index 34e8b14..201dae0 100644
--- a/src/dishka/_adaptix/type_tools/normalize_type.py
+++ b/src/dishka/_adaptix/type_tools/normalize_type.py
@@ -1,32 +1,26 @@
-# ruff: noqa: RET503
+# ruff: noqa: RET503, UP006
 import dataclasses
 import sys
 import types
 import typing
 from abc import ABC, abstractmethod
 from collections import abc as c_abc, defaultdict
+from collections.abc import Hashable, Iterable, Sequence
 from copy import copy
 from dataclasses import InitVar, dataclass
 from enum import Enum, EnumMeta
 from functools import lru_cache, partial
 from typing import (
+    Annotated,
     Any,
     Callable,
     ClassVar,
-    DefaultDict,
-    Dict,
     Final,
     ForwardRef,
-    Hashable,
-    Iterable,
-    List,
     Literal,
     NewType,
     NoReturn,
     Optional,
-    Sequence,
-    Tuple,
-    Type,
     TypeVar,
     Union,
     overload,
@@ -34,7 +28,6 @@ from typing import (
 
 from ..common import TypeHint, VarTuple
 from ..feature_requirement import (
-    HAS_ANNOTATED,
     HAS_PARAM_SPEC,
     HAS_PY_310,
     HAS_PY_311,
@@ -175,7 +168,7 @@ class _LiteralNormType(_BasicNormType):
 class _AnnotatedNormType(_BasicNormType):
     @property
     def origin(self) -> Any:
-        return typing.Annotated
+        return Annotated
 
     __slots__ = (*_BasicNormType.__slots__, "_hash")
 
@@ -243,7 +236,7 @@ class NormTV(BaseNormType):
         return self._var
 
     @property
-    def args(self) -> Tuple[()]:
+    def args(self) -> tuple[()]:
         return ()
 
     @property
@@ -288,7 +281,7 @@ class NormTVTuple(BaseNormType):
         return self._var
 
     @property
-    def args(self) -> Tuple[()]:
+    def args(self) -> tuple[()]:
         return ()
 
     @property
@@ -325,7 +318,7 @@ class NormParamSpecMarker(BaseNormType, ABC):
         return self._param_spec
 
     @property
-    def args(self) -> Tuple[()]:
+    def args(self) -> tuple[()]:
         return ()
 
     @property
@@ -418,7 +411,7 @@ def make_norm_type(
         if not all(type(arg) in [int, bool, str, bytes] or isinstance(type(arg), EnumMeta) for arg in args):
             raise TypeError
         return _LiteralNormType(args, source=source)
-    if HAS_ANNOTATED and origin == typing.Annotated:
+    if origin == Annotated:
         return _AnnotatedNormType(args, source=source)
     if isinstance(origin, TypeVar):
         raise TypeError
@@ -471,7 +464,7 @@ def _replace_source_with_union(norm: BaseNormType, sources: list) -> BaseNormTyp
 NormAspect = Callable[["TypeNormalizer", Any, Any, tuple], Optional[BaseNormType]]
 
 
-class AspectStorage(List[str]):
+class AspectStorage(list[str]):
     @overload
     def add(self, *, condition: object = True) -> Callable[[NormAspect], NormAspect]:
         ...
@@ -503,9 +496,9 @@ TN = TypeVar("TN", bound="TypeNormalizer")
 class TypeNormalizer:
     def __init__(self, implicit_params_getter: ImplicitParamsGetter):
         self.implicit_params_getter = implicit_params_getter
-        self._namespace: Optional[Dict[str, Any]] = None
+        self._namespace: Optional[dict[str, Any]] = None
 
-    def _with_namespace(self: TN, namespace: Dict[str, Any]) -> TN:
+    def _with_namespace(self: TN, namespace: dict[str, Any]) -> TN:
         self_copy = copy(self)
         self_copy._namespace = namespace
         return self_copy
@@ -563,9 +556,8 @@ class TypeNormalizer:
     MUST_SUBSCRIBED_ORIGINS = [
         ClassVar, Final, Literal,
         Union, Optional, InitVar,
+        Annotated,
     ]
-    if HAS_ANNOTATED:
-        MUST_SUBSCRIBED_ORIGINS.append(typing.Annotated)
     if HAS_TYPE_GUARD:
         MUST_SUBSCRIBED_ORIGINS.append(typing.TypeGuard)
     if HAS_TYPED_DICT_REQUIRED:
@@ -584,9 +576,9 @@ class TypeNormalizer:
         if origin is None or origin is NoneType:
             return _NormType(None, (), source=tp)
 
-    @_aspect_storage.add(condition=HAS_ANNOTATED)
+    @_aspect_storage.add
     def _norm_annotated(self, tp, origin, args):
-        if origin == typing.Annotated:
+        if origin == Annotated:
             return _AnnotatedNormType(
                 (self.normalize(args[0]), *args[1:]),
                 source=tp,
@@ -663,8 +655,8 @@ class TypeNormalizer:
 
     @_aspect_storage.add
     def _norm_tuple(self, tp, origin, args):
-        if origin == tuple:
-            if tp in (tuple, Tuple):  # not subscribed values
+        if origin is tuple:
+            if tp in (tuple, typing.Tuple):  # not subscribed values
                 return _NormType(
                     tuple,
                     (ANY_NT, ...),
@@ -691,7 +683,7 @@ class TypeNormalizer:
         # it is necessary to unpack the variable-length tuple as well
         if len(args) == 1 and args[0].origin == typing.Unpack:
             inner_tp = args[0].args[0]
-            if inner_tp.origin == tuple:
+            if inner_tp.origin is tuple:
                 return inner_tp.args
 
         return self._unpack_generic_elements(args)
@@ -706,7 +698,7 @@ class TypeNormalizer:
         return tuple(result)
 
     def _is_fixed_size_tuple(self, tp: BaseNormType) -> bool:
-        return tp.origin == tuple and (not tp.args or tp.args[-1] is not Ellipsis)
+        return tp.origin is tuple and (not tp.args or tp.args[-1] is not Ellipsis)
 
     @_aspect_storage.add
     def _norm_callable(self, tp, origin, args):
@@ -749,7 +741,7 @@ class TypeNormalizer:
             return _LiteralNormType(args, source=tp)
 
     def _unfold_union_args(self, norm_args: Iterable[N]) -> Iterable[N]:
-        result: List[N] = []
+        result: list[N] = []
         for norm in norm_args:
             if norm.origin == Union:
                 result.extend(norm.args)
@@ -758,7 +750,7 @@ class TypeNormalizer:
         return result
 
     def _dedup_union_args(self, args: Iterable[BaseNormType]) -> Iterable[BaseNormType]:
-        args_to_sources: DefaultDict[BaseNormType, List[Any]] = defaultdict(list)
+        args_to_sources: defaultdict[BaseNormType, list[Any]] = defaultdict(list)
 
         for arg in args:
             args_to_sources[arg].append(arg.source)
@@ -772,7 +764,7 @@ class TypeNormalizer:
 
     def _merge_literals(self, args: Iterable[N]) -> Sequence[N]:
         result = []
-        lit_args: List[N] = []
+        lit_args: list[N] = []
         for norm in args:
             if norm.origin == Literal:
                 lit_args.extend(norm.args)
@@ -783,7 +775,7 @@ class TypeNormalizer:
             result.append(_create_norm_literal(lit_args))
         return result
 
-    _UNION_ORIGINS: List[Any] = [Union]
+    _UNION_ORIGINS: list[Any] = [Union]
     if HAS_TYPE_UNION_OP:
         _UNION_ORIGINS.append(types.UnionType)
 
@@ -808,13 +800,13 @@ class TypeNormalizer:
             if norm.origin == Union:
                 return _UnionNormType(
                     tuple(
-                        _NormType(type, (arg,), source=Type[arg.source])
+                        _NormType(type, (arg,), source=type[arg.source])
                         for arg in norm.args
                     ),
                     source=tp,
                 )
 
-    ALLOWED_ZERO_PARAMS_ORIGINS = {Any, NoReturn}
+    ALLOWED_ZERO_PARAMS_ORIGINS: set[Any] = {Any, NoReturn}
     if HAS_TYPE_ALIAS:
         ALLOWED_ZERO_PARAMS_ORIGINS.add(typing.TypeAlias)
     if HAS_PY_310:
diff --git a/src/dishka/_adaptix/type_tools/type_evaler.py b/src/dishka/_adaptix/type_tools/type_evaler.py
new file mode 100644
index 0000000..e3236e4
--- /dev/null
+++ b/src/dishka/_adaptix/type_tools/type_evaler.py
@@ -0,0 +1,65 @@
+import ast
+import inspect
+from collections.abc import Callable, Sequence
+from types import ModuleType
+
+
+def make_fragments_collector(*, typing_modules: Sequence[str]) -> Callable[[ast.Module], list[ast.stmt]]:
+    def check_condition(expr: ast.expr) -> bool:
+        # searches for `TYPE_CHECKING`
+        if (
+            isinstance(expr, ast.Name)
+            and isinstance(expr.ctx, ast.Load)
+            and expr.id == "TYPE_CHECKING"
+        ):
+            return True
+
+        # searches for `typing.TYPE_CHECKING`
+        if (  # noqa: SIM103
+            isinstance(expr, ast.Attribute)
+            and expr.attr == "TYPE_CHECKING"
+            and isinstance(expr.ctx, ast.Load)
+            and isinstance(expr.value, ast.Name)
+            and expr.value.id in typing_modules
+            and isinstance(expr.value.ctx, ast.Load)
+        ):
+            return True
+        return False
+
+    def collect_type_checking_only_fragments(module: ast.Module) -> list[ast.stmt]:
+        fragments = []
+        for stmt in module.body:
+            if isinstance(stmt, ast.If) and not stmt.orelse and check_condition(stmt.test):
+                fragments.extend(stmt.body)
+
+        return fragments
+
+    return collect_type_checking_only_fragments
+
+
+default_collector = make_fragments_collector(typing_modules=["typing"])
+
+
+def exec_type_checking(
+    module: ModuleType,
+    *,
+    collector: Callable[[ast.Module], list[ast.stmt]] = default_collector,
+) -> None:
+    """This function scans module source code,
+    collects fragments under ``if TYPE_CHECKING`` and ``if typing.TYPE_CHECKING``
+    and executes them in the context of module.
+    After these, all imports and type definitions became available at runtime for analysis.
+
+    By default, it ignores ``if`` with ``else`` branch.
+
+    :param module: A module for processing
+    :param collector: A function collecting code fragments to execute
+    """
+    source = inspect.getsource(module)
+    fragments = collector(ast.parse(source))
+    code = compile(ast.Module(fragments, type_ignores=[]), f"<exec_type_checking of {module}>", "exec")
+    namespace = module.__dict__.copy()
+    exec(code, namespace)  # noqa: S102
+    for k, v in namespace.items():
+        if not hasattr(module, k):
+            setattr(module, k, v)
diff --git a/src/dishka/registry.py b/src/dishka/registry.py
index 3098797..8469b27 100644
--- a/src/dishka/registry.py
+++ b/src/dishka/registry.py
@@ -86,26 +86,42 @@ class Registry:
             origin = get_origin(dependency.type_hint)
             if not origin:
                 return None
+            if origin is type:
+                return self._get_type_var_factory(dependency)
+
             origin_key = DependencyKey(origin, dependency.component)
             factory = self.factories.get(origin_key)
             if not factory:
                 return None
 
-            factory = self._specialize_generic(factory, dependency)
+            else:
+                factory = self._specialize_generic(factory, dependency)
             self.factories[dependency] = factory
             return factory
 
+    def _get_type_var_factory(self, dependency: DependencyKey) -> Factory:
+        args = get_args(dependency.type_hint)
+        if args:
+            typevar = args[0]
+        else:
+            typevar = Any
+        return Factory(
+            scope=self.scope,
+            dependencies=[],
+            kw_dependencies={},
+            provides=DependencyKey(type[typevar], dependency.component),
+            type_=FactoryType.FACTORY,
+            is_to_bind=False,
+            cache=False,
+            source=lambda: typevar,
+        )
+
     def _specialize_generic(
             self, factory: Factory, dependency_key: DependencyKey,
     ) -> Factory:
         dependency = dependency_key.type_hint
-        type_var_deps = (
-            d.type_hint
-            for d in factory.dependencies
-            if isinstance(d.type_hint, TypeVar)
-        )
         params_replacement = dict(zip(
-            type_var_deps,
+            get_type_vars(factory.provides.type_hint),
             get_args(dependency),
             strict=False,
         ))


Test patch:
diff --git a/tests/unit/container/test_generic.py b/tests/unit/container/test_generic.py
index 65b4910..61faebc 100644
--- a/tests/unit/container/test_generic.py
+++ b/tests/unit/container/test_generic.py
@@ -162,3 +162,26 @@ def test_generic_validation_fail():
     provider.provide(factory_invalid)
     with pytest.raises(GraphMissingFactoryError):
         assert make_container(provider)
+
+
+def type_var_factory(type_: type[T]) -> EventEmitter[T]:
+    return type_
+
+
+def test_passing_type_var():
+    provider = Provider(scope=Scope.APP)
+    provider.provide(type_var_factory)
+    container = make_container(provider)
+    assert container.get(EventEmitter[int]) is int
+
+
+def parametrized_param_func(param: A[T]) -> EventEmitter[T]:
+    return param.x
+
+
+def test_func_with_generic_params():
+    provider = Provider(scope=Scope.APP)
+    provider.provide(parametrized_param_func)
+    provider.provide(lambda: A(42), provides=A[T])
+    container = make_container(provider)
+    assert container.get(EventEmitter[int]) == 42