Repository: Fatal1ty/mashumaro (language: python)

Issue:
TypedDicts not working with `from __future__ import annotations`
* mashumaro version: 3.11
* Python version: 3.10.13
* Operating System: Ubuntu

### What I Did

```py
from __future__ import annotations
from mashumaro.jsonschema import build_json_schema
from typing import TypedDict


class TypedDictRequiredKeys(TypedDict):
    int: int
    float: float


build_json_schema(TypedDictRequiredKeys)
```

```
Traceback (most recent call last):
  File "main.py", line 11, in <module>
    build_json_schema(TypedDictRequiredKeys)
  File "/usr/lib/python3.10/site-packages/mashumaro/jsonschema/builder.py", line 45, in build_json_schema
    schema = get_schema(instance, context, with_dialect_uri=with_dialect_uri)
  File "/usr/lib/python3.10/site-packages/mashumaro/jsonschema/schema.py", line 258, in get_schema
    schema = schema_creator(instance, ctx)
  File "/usr/lib/python3.10/site-packages/mashumaro/jsonschema/schema.py", line 802, in on_collection
    return on_typed_dict(instance, ctx)
  File "/usr/lib/python3.10/site-packages/mashumaro/jsonschema/schema.py", line 666, in on_typed_dict
    properties={
  File "/usr/lib/python3.10/site-packages/mashumaro/jsonschema/schema.py", line 667, in <dictcomp>
    key: get_schema(instance.derive(type=annotations[key]), ctx)
  File "/usr/lib/python3.10/site-packages/mashumaro/jsonschema/schema.py", line 258, in get_schema
    schema = schema_creator(instance, ctx)
  File "/usr/lib/python3.10/site-packages/mashumaro/jsonschema/schema.py", line 720, in on_collection
    if not issubclass(instance.origin_type, typing.Collection):
```
`instance.origin_type` is ForwardRef('int') 
https://github.com/Fatal1ty/mashumaro/blob/ab8242448631ab7a1ad2e437561377cd698a99e9/mashumaro/jsonschema/schema.py#L718C23-L718C43
```
  File "/usr/lib/python3.10/typing.py", line 1158, in __subclasscheck__
    return issubclass(cls, self.__origin__)
  File "/usr/lib/python3.10/abc.py", line 123, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: issubclass() arg 1 must be a class
```




### Description

TypedDicts are not working with `from __future__ import annotations`.

Also, it's worth noting that adding this line on top of `tests/test_jsonschema/test_jsonschema_generation.py` breaks tests:
- `test_jsonschema_for_named_tuple_with_overridden_serialization_method`
- `test_jsonschema_for_typeddict`
- `test_overridden_serialization_method_with_return_annotation`
- `test_dataclass_overridden_serialization_method`
- `test_third_party_overridden_serialization_method`


Discussion / hints:

Fix patch:
diff --git a/mashumaro/core/meta/code/builder.py b/mashumaro/core/meta/code/builder.py
index e4fbb27f..8d361ad6 100644
--- a/mashumaro/core/meta/code/builder.py
+++ b/mashumaro/core/meta/code/builder.py
@@ -1,7 +1,6 @@
 import importlib
 import inspect
 import math
-import sys
 import types
 import typing
 import uuid
@@ -35,6 +34,7 @@
     get_args,
     get_class_that_defines_field,
     get_class_that_defines_method,
+    get_forward_ref_referencing_globals,
     get_literal_values,
     get_name_error_name,
     hash_type_args,
@@ -334,19 +334,9 @@ def evaluate_forward_ref(
         typ: typing.ForwardRef,
         owner: typing.Optional[typing.Type],
     ) -> typing.Optional[typing.Type]:
-        forward_module = getattr(typ, "__forward_module__", None)
-        if not forward_module and owner:
-            # We can't get the module in which ForwardRef's value is defined on
-            # Python < 3.10, ForwardRef evaluation might not work properly
-            # without this information, so we will consider the namespace of
-            # the module in which this ForwardRef is used as globalns.
-            globalns = getattr(
-                sys.modules.get(owner.__module__, None),
-                "__dict__",
-                self.globals,
-            )
-        else:
-            globalns = getattr(forward_module, "__dict__", self.globals)
+        globalns = get_forward_ref_referencing_globals(
+            typ, owner, self.globals
+        )
         return evaluate_forward_ref(typ, globalns, self.__dict__)
 
     def get_declared_hook(self, method_name: str) -> typing.Any:
diff --git a/mashumaro/core/meta/helpers.py b/mashumaro/core/meta/helpers.py
index 5ad25d0b..40889074 100644
--- a/mashumaro/core/meta/helpers.py
+++ b/mashumaro/core/meta/helpers.py
@@ -2,6 +2,7 @@
 import enum
 import inspect
 import re
+import sys
 import types
 import typing
 from contextlib import suppress
@@ -82,6 +83,7 @@
     "is_hashable",
     "is_hashable_type",
     "evaluate_forward_ref",
+    "get_forward_ref_referencing_globals",
 ]
 
 
@@ -670,7 +672,7 @@ def is_not_required(typ: Type) -> bool:
 
 
 def get_function_arg_annotation(
-    function: typing.Callable[[Any], Any],
+    function: typing.Callable[..., Any],
     arg_name: typing.Optional[str] = None,
     arg_pos: typing.Optional[int] = None,
 ) -> typing.Type:
@@ -769,3 +771,25 @@ def evaluate_forward_ref(
         )  # type: ignore[call-arg]
     else:
         return typ._evaluate(globalns, localns)  # type: ignore[call-arg]
+
+
+def get_forward_ref_referencing_globals(
+    referenced_type: typing.ForwardRef,
+    referencing_object: Optional[Any] = None,
+    fallback: Optional[Dict[str, Any]] = None,
+) -> Dict[str, Any]:
+    if fallback is None:
+        fallback = {}
+    forward_module = getattr(referenced_type, "__forward_module__", None)
+    if not forward_module and referencing_object:
+        # We can't get the module in which ForwardRef's value is defined on
+        # Python < 3.10, ForwardRef evaluation might not work properly
+        # without this information, so we will consider the namespace of
+        # the module in which this ForwardRef is used as globalns.
+        return getattr(
+            sys.modules.get(referencing_object.__module__, None),
+            "__dict__",
+            fallback,
+        )
+    else:
+        return getattr(forward_module, "__dict__", fallback)
diff --git a/mashumaro/jsonschema/schema.py b/mashumaro/jsonschema/schema.py
index c509e30f..ca2ae0e7 100644
--- a/mashumaro/jsonschema/schema.py
+++ b/mashumaro/jsonschema/schema.py
@@ -13,6 +13,7 @@
     Any,
     Callable,
     Dict,
+    ForwardRef,
     Iterable,
     List,
     Optional,
@@ -28,7 +29,9 @@
 from mashumaro.core.const import PY_39_MIN, PY_311_MIN
 from mashumaro.core.meta.code.builder import CodeBuilder
 from mashumaro.core.meta.helpers import (
+    evaluate_forward_ref,
     get_args,
+    get_forward_ref_referencing_globals,
     get_function_return_annotation,
     get_literal_values,
     get_type_origin,
@@ -138,6 +141,13 @@ def owner_class(self) -> Optional[Type]:
         return None
 
     def derive(self, **changes: Any) -> "Instance":
+        new_type = changes.get("type")
+        if isinstance(new_type, ForwardRef):
+            changes["type"] = evaluate_forward_ref(
+                new_type,
+                get_forward_ref_referencing_globals(new_type, self.type),
+                self.__dict__,
+            )
         new_instance = replace(self, **changes)
         if is_dataclass(self.origin_type):
             new_instance.__owner_builder = self.__self_builder
@@ -437,6 +447,14 @@ def on_special_typing_primitive(
         )
     elif is_type_var_tuple(instance.type):
         return get_schema(instance.derive(type=Tuple[Any, ...]), ctx)
+    elif isinstance(instance.type, ForwardRef):
+        evaluated = evaluate_forward_ref(
+            instance.type,
+            get_forward_ref_referencing_globals(instance.type),
+            None,
+        )
+        if evaluated is not None:
+            return get_schema(instance.derive(type=evaluated), ctx)
 
 
 @register


Test patch:
diff --git a/tests/test_jsonschema/test_jsonschema_generation_with_forward_refs.py b/tests/test_jsonschema/test_jsonschema_generation_with_forward_refs.py
new file mode 100644
index 00000000..d9d0134d
--- /dev/null
+++ b/tests/test_jsonschema/test_jsonschema_generation_with_forward_refs.py
@@ -0,0 +1,43 @@
+from __future__ import annotations
+
+from typing import ForwardRef, TypedDict
+
+import pytest
+
+from mashumaro.core.const import PY_39_MIN
+from mashumaro.core.meta.helpers import get_function_arg_annotation
+from mashumaro.jsonschema import build_json_schema
+from mashumaro.jsonschema.models import (
+    JSONObjectSchema,
+    JSONSchema,
+    JSONSchemaInstanceType,
+)
+
+
+class MyTypedDict(TypedDict):
+    x: int
+
+
+@pytest.mark.skipif(
+    not PY_39_MIN,
+    reason=(
+        "On Python 3.8 ForwardRef doesn't have __forward_module__ "
+        "which is needed here"
+    ),
+)
+def test_jsonschema_generation_for_forward_refs():
+    def foo(x: int, y: MyTypedDict):
+        pass
+
+    x_type = get_function_arg_annotation(foo, "x")
+    assert isinstance(x_type, ForwardRef)
+    assert build_json_schema(x_type).type is JSONSchemaInstanceType.INTEGER
+
+    y_type = get_function_arg_annotation(foo, "y")
+    assert isinstance(y_type, ForwardRef)
+    assert build_json_schema(y_type) == JSONObjectSchema(
+        type=JSONSchemaInstanceType.OBJECT,
+        properties={"x": JSONSchema(type=JSONSchemaInstanceType.INTEGER)},
+        additionalProperties=False,
+        required=["x"],
+    )