From f13aa59fb06d59efd7b4e18af5622eb0af421b90 Mon Sep 17 00:00:00 2001
From: Marco Eilers <eilers.marco@googlemail.com>
Date: Thu, 15 Feb 2024 23:37:21 +0100
Subject: [PATCH] Proper error message for partially inferred types

---
 src/nagini_translation/analyzer.py                | 10 ++++++++++
 src/nagini_translation/lib/typeinfo.py            |  3 +++
 tests/functional/translation/test_partial_type.py | 12 ++++++++++++
 3 files changed, 25 insertions(+)
 create mode 100644 tests/functional/translation/test_partial_type.py

diff --git a/src/nagini_translation/analyzer.py b/src/nagini_translation/analyzer.py
index fe4e054c3..b2326cd98 100644
--- a/src/nagini_translation/analyzer.py
+++ b/src/nagini_translation/analyzer.py
@@ -1219,6 +1219,16 @@ def convert_type(self, mypy_type, node) -> PythonType:
             return self._convert_callable_type(mypy_type, node)
         elif self.types.is_type_alias_type(mypy_type):
             return self.convert_type(mypy_type.alias.target, node)
+        elif self.types.is_partial_type(mypy_type):
+            try:
+                partial_type = self.convert_type(mypy_type.type, node)
+            except:
+                partial_type = None
+            if partial_type:
+                msg = f'Type {partial_type.python_class.name} could not be fully inferred (this usually means that a type argument is unknown)'
+            else:
+                msg = f'Type could not be fully inferred (this usually means that a type argument is unknown)'
+            raise InvalidProgramException(node, 'partial.type', message=msg)
         else:
             msg = 'Unsupported type: {}'.format(mypy_type.__class__.__name__)
             raise UnsupportedException(node, desc=msg)
diff --git a/src/nagini_translation/lib/typeinfo.py b/src/nagini_translation/lib/typeinfo.py
index cf3b107a8..675cd3fe0 100644
--- a/src/nagini_translation/lib/typeinfo.py
+++ b/src/nagini_translation/lib/typeinfo.py
@@ -432,6 +432,9 @@ def is_type_type(self, type: mypy.types.Type) -> bool:
     def is_type_alias_type(self, type: mypy.types.Type) -> bool:
         return isinstance(type, mypy.types.TypeAliasType)
 
+    def is_partial_type(self, type: mypy.types.Type) -> bool:
+        return isinstance(type, mypy.types.PartialType)
+
     def is_any_type_from_error(self, type: mypy.types.Type) -> bool:
         if isinstance(type, mypy.types.AnyType):
             if type.type_of_any == mypy.types.TypeOfAny.from_error:
diff --git a/tests/functional/translation/test_partial_type.py b/tests/functional/translation/test_partial_type.py
new file mode 100644
index 000000000..4e6b10200
--- /dev/null
+++ b/tests/functional/translation/test_partial_type.py
@@ -0,0 +1,12 @@
+from typing import List
+
+
+def get_odd_collatz(n: int) -> List[int]:
+    #:: ExpectedOutput(invalid.program:partial.type)
+    ans, x = [], n
+    while x != 1:
+        if x % 2 == 1:
+            ans.append(x)
+        x = x // 2 if x % 2 == 0 else x * 3 + 1
+    ans.append(1)
+    return sorted(ans)