Repository: strawberry-graphql/strawberry

Issue:
Scalars and Mypy on >= 0.100
Following #1690, mypy issues `error: Variable "<scalar>" is not valid as a type  [valid-type]` on all scalars.

To reproduce:
```mypy tests/schema/test_scalars.py```


Discussion:

Resolving patch:
diff --git a/strawberry/custom_scalar.py b/strawberry/custom_scalar.py
--- a/strawberry/custom_scalar.py
+++ b/strawberry/custom_scalar.py
@@ -1,5 +1,5 @@
 from dataclasses import dataclass
-from typing import Any, Callable, Mapping, Optional, TypeVar, Union, overload
+from typing import Any, Callable, Mapping, Optional, Type, TypeVar, Union, overload
 
 from graphql import GraphQLScalarType
 
@@ -49,7 +49,7 @@ def __call__(self, *args, **kwargs):
 
 
 def _process_scalar(
-    cls,
+    cls: Type[_T],
     *,
     name: Optional[str] = None,
     description: Optional[str] = None,
@@ -58,7 +58,6 @@ def _process_scalar(
     parse_value: Optional[Callable] = None,
     parse_literal: Optional[Callable] = None,
 ):
-
     name = name or to_camel_case(cls.__name__)
 
     wrapper = ScalarWrapper(cls)
diff --git a/strawberry/ext/mypy_plugin.py b/strawberry/ext/mypy_plugin.py
--- a/strawberry/ext/mypy_plugin.py
+++ b/strawberry/ext/mypy_plugin.py
@@ -255,6 +255,56 @@ def enum_hook(ctx: DynamicClassDefContext) -> None:
     )
 
 
+def scalar_hook(ctx: DynamicClassDefContext) -> None:
+    first_argument = ctx.call.args[0]
+
+    if isinstance(first_argument, NameExpr):
+        if not first_argument.node:
+            ctx.api.defer()
+
+            return
+
+        if isinstance(first_argument.node, Var):
+            var_type = first_argument.node.type or AnyType(
+                TypeOfAny.implementation_artifact
+            )
+
+            type_alias = TypeAlias(
+                var_type,
+                fullname=ctx.api.qualified_name(ctx.name),
+                line=ctx.call.line,
+                column=ctx.call.column,
+            )
+
+            ctx.api.add_symbol_table_node(
+                ctx.name, SymbolTableNode(GDEF, type_alias, plugin_generated=False)
+            )
+            return
+
+    scalar_type: Optional[Type]
+
+    # TODO: add proper support for NewType
+
+    try:
+        scalar_type = _get_type_for_expr(first_argument, ctx.api)
+    except InvalidNodeTypeException:
+        scalar_type = None
+
+    if not scalar_type:
+        scalar_type = AnyType(TypeOfAny.from_error)
+
+    type_alias = TypeAlias(
+        scalar_type,
+        fullname=ctx.api.qualified_name(ctx.name),
+        line=ctx.call.line,
+        column=ctx.call.column,
+    )
+
+    ctx.api.add_symbol_table_node(
+        ctx.name, SymbolTableNode(GDEF, type_alias, plugin_generated=False)
+    )
+
+
 def add_static_method_to_class(
     api: Union[SemanticAnalyzerPluginInterface, CheckerPluginInterface],
     cls: ClassDef,
@@ -760,6 +810,9 @@ def get_dynamic_class_hook(
         if self._is_strawberry_enum(fullname):
             return enum_hook
 
+        if self._is_strawberry_scalar(fullname):
+            return scalar_hook
+
         if self._is_strawberry_create_type(fullname):
             return create_type_hook
 
@@ -815,6 +868,11 @@ def _is_strawberry_enum(self, fullname: str) -> bool:
             "strawberry.enum"
         )
 
+    def _is_strawberry_scalar(self, fullname: str) -> bool:
+        return fullname == "strawberry.custom_scalar.scalar" or fullname.endswith(
+            "strawberry.scalar"
+        )
+
     def _is_strawberry_lazy_type(self, fullname: str) -> bool:
         return fullname == "strawberry.lazy_type.LazyType"
 
diff --git a/strawberry/federation/schema_directives.py b/strawberry/federation/schema_directives.py
--- a/strawberry/federation/schema_directives.py
+++ b/strawberry/federation/schema_directives.py
@@ -12,14 +12,14 @@ class External:
 
 @schema_directive(locations=[Location.FIELD_DEFINITION], name="requires")
 class Requires:
-    fields: FieldSet  # type: ignore
+    fields: FieldSet
 
 
 @schema_directive(locations=[Location.FIELD_DEFINITION], name="provides")
 class Provides:
-    fields: FieldSet  # type: ignore
+    fields: FieldSet
 
 
 @schema_directive(locations=[Location.OBJECT, Location.INTERFACE], name="key")
 class Key:
-    fields: FieldSet  # type: ignore
+    fields: FieldSet


Test patch:
diff --git a/tests/mypy/test_scalar.yml b/tests/mypy/test_scalar.yml
new file mode 100644
--- /dev/null
+++ b/tests/mypy/test_scalar.yml
@@ -0,0 +1,72 @@
+- case: test_scalar_decorator
+  main: |
+    import strawberry
+
+    @strawberry.scalar()
+    class X:
+        pass
+
+    @strawberry.scalar
+    class Y:
+        pass
+
+    @strawberry.scalar(name="Zed")
+    class Z:
+        pass
+
+    @strawberry.type
+    class Me:
+        z: Z
+
+    reveal_type(X)
+    reveal_type(Y)
+    reveal_type(Z)
+    reveal_type(X())
+    reveal_type(Y())
+    reveal_type(Z())
+    reveal_type(Me(Z()).z)
+  out: |
+    main:19: note: Revealed type is "def () -> main.X"
+    main:20: note: Revealed type is "def () -> main.Y"
+    main:21: note: Revealed type is "def () -> main.Z"
+    main:22: note: Revealed type is "main.X"
+    main:23: note: Revealed type is "main.Y"
+    main:24: note: Revealed type is "main.Z"
+    main:25: note: Revealed type is "main.Z"
+
+- case: test_scalar_as_function
+  main: |
+    import strawberry
+
+    X = strawberry.scalar(int)
+    Y = strawberry.scalar(str, name="Y")
+
+    @strawberry.type
+    class Me:
+        x: X
+
+    reveal_type(X())
+    reveal_type(Y())
+    reveal_type(Me(X("1")).x)
+  out: |
+    main:10: note: Revealed type is "builtins.int"
+    main:11: note: Revealed type is "builtins.str"
+    main:12: note: Revealed type is "builtins.int"
+
+
+- case: test_scalar_as_function_new_type
+  main: |
+    from typing import NewType
+    import strawberry
+
+    Z = strawberry.scalar(NewType("X", int))
+
+    @strawberry.type
+    class Me:
+        x: Z
+
+    reveal_type(Z())
+    reveal_type(Me(Z("1")).x)
+  out: |
+    main:10: note: Revealed type is "Any"
+    main:11: note: Revealed type is "Any"