Repository: robotframework/PythonLibCore (language: python)

Issue:
Support Python 3.10 and ensure that new type hints works
3.10 brings new way to present type hints. Make sure that example this works:
```
def func(arg: list[int] | list[str]):
    ...
```


Discussion / hints:
Doesn't PLC just forward `__annotations__` to RF? If yes, this ought to work with RF 4.1.1 and newer out of the box. Adding explicit tests is definitely a good idea anyway.
Yes, it does forwards. If there is no changes in that area, things will most likely just work. But testing it out, is always a good idea. 

Fix patch:
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index ef3d47b..b5e4f9a 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -8,7 +8,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.7, 3.9, 3.10.0-rc.1]
+        python-version: [3.7, 3.9, 3.10.0-rc.2]
         rf-version: [4.0.2, 4.1.1]
 
     steps:
@@ -37,6 +37,7 @@ jobs:
       run: |
         python atest/run.py
     - uses: actions/upload-artifact@v1
+      if: ${{ always() }}
       with:
         name: atest_results
         path: atest/results


Test patch:
diff --git a/atest/DynamicTypesLibrary.py b/atest/DynamicTypesLibrary.py
index 3626fe7..b196fbd 100644
--- a/atest/DynamicTypesLibrary.py
+++ b/atest/DynamicTypesLibrary.py
@@ -1,6 +1,8 @@
 import functools
 import sys
 
+from robot import version as rf_version
+
 from robotlibcore import DynamicCore, keyword
 
 
@@ -58,8 +60,8 @@ def keyword_none(self, arg=None):
         return '{}: {}'.format(arg, type(arg))
 
     @keyword
-    def is_python_3(self):
-        return sys.version_info >= (3,)
+    def is_python_3_10(self):
+        return sys.version_info >= (3, 10)
 
     @keyword
     @def_deco
@@ -79,3 +81,7 @@ def varargs_and_kwargs(self, *args, **kwargs):
     @keyword
     def keyword_booleans(self, arg1=True, arg2=False):
         return '{}: {}, {}: {}'.format(arg1, type(arg1), arg2, type(arg2))
+
+    @keyword
+    def is_rf_401(self):
+        return "4.0." in rf_version.VERSION
\ No newline at end of file
diff --git a/atest/Python310Library.py b/atest/Python310Library.py
new file mode 100644
index 0000000..d773b0b
--- /dev/null
+++ b/atest/Python310Library.py
@@ -0,0 +1,14 @@
+from robot.api import logger
+
+from robotlibcore import DynamicCore, keyword
+
+class Python310Library(DynamicCore):
+
+    def __init__(self):
+        DynamicCore.__init__(self, [])
+
+    @keyword
+    def python310_style(self, arg: int | dict):
+        typing = f"arg: {arg}, type: {type(arg)}"
+        logger.info(typing)
+        return typing
diff --git a/atest/run.py b/atest/run.py
index c4e4e30..26df6b7 100755
--- a/atest/run.py
+++ b/atest/run.py
@@ -25,7 +25,8 @@
         sys.exit(rc)
     process_output(output, verbose=False)
 output = join(outdir, 'lib-DynamicTypesLibrary-python-{}-robot-{}.xml'.format(python_version, rf_version))
-rc = run(tests_types, name='Types', output=output, report=None, log=None, loglevel='debug')
+exclude = "py310" if sys.version_info < (3, 10) else ""
+rc = run(tests_types, name='Types', output=output, report=None, log=None, loglevel='debug', exclude=exclude)
 if rc > 250:
     sys.exit(rc)
 process_output(output, verbose=False)
diff --git a/atest/tests_types.robot b/atest/tests_types.robot
index eee8e48..ab9af7d 100644
--- a/atest/tests_types.robot
+++ b/atest/tests_types.robot
@@ -91,3 +91,27 @@ Type Conversion With Union And Multiple Types
     ${types} =    Keyword Union With None    {"key": 1}
     Should Contain    ${types}    arg: {"key": 1},
     Should Contain    ${types}    <class 'str'>
+
+Python 3.10 New Type Hints
+    [Tags]    py310
+    [Setup]    Import DynamicTypesAnnotationsLibrary In Python 3.10 Only
+    ${types} =    Python310 Style    111
+    ${rf401} =    DynamicTypesLibrary.Is Rf 401
+    IF    ${rf401} != ${True}
+        Should Be Equal    ${types}    arg: 111, type: <class 'int'>
+    ELSE
+        Should Be Equal    ${types}    arg: 111, type: <class 'str'>
+    END
+    ${types} =    Python310 Style    {"key": 1}
+    IF    ${rf401} != ${True}
+        Should Be Equal    ${types}    arg: {'key': 1}, type: <class 'dict'>
+    ELSE
+        Should Be Equal    ${types}    arg: {"key": 1}, type: <class 'str'>
+    END
+
+*** Keywords ***
+Import DynamicTypesAnnotationsLibrary In Python 3.10 Only
+    ${py3} =    DynamicTypesLibrary.Is Python 3 10
+    IF    ${py3}
+        Import Library    Python310Library.py
+    END
diff --git a/utest/test_get_keyword_source.py b/utest/test_get_keyword_source.py
index 95d65a0..212e19d 100644
--- a/utest/test_get_keyword_source.py
+++ b/utest/test_get_keyword_source.py
@@ -50,7 +50,7 @@ def test_location_in_class(lib, lib_path_components):
 
 def test_decorator_wrapper(lib_types, lib_path_types):
     source = lib_types.get_keyword_source('keyword_wrapped')
-    assert source == '%s:72' % lib_path_types
+    assert source == '%s:74' % lib_path_types
 
 
 def test_location_in_class_custom_keyword_name(lib, lib_path_components):
@@ -79,7 +79,7 @@ def test_no_path_and_no_line_number(lib, when):
 
 def test_def_in_decorator(lib_types, lib_path_types):
     source = lib_types.get_keyword_source('keyword_with_def_deco')
-    assert source == '%s:66' % lib_path_types
+    assert source == '%s:68' % lib_path_types
 
 
 def test_error_in_getfile(lib, when):