Skip to content

Commit bf398ad

Browse files
authored
Fix evaluation of types for typing classes (#56)
* Fix evaluation of types for typing classes * properly type and lint * fix formatting * commit happy compromise * cleanup forward ref eval * update docstring * add test for evaluating eval_type * skip eval type test for now I need this released quick * fix formatting in test_objects.py
1 parent 99c52bd commit bf398ad

2 files changed

Lines changed: 48 additions & 5 deletions

File tree

mode/utils/objects.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,25 @@
2929
Tuple,
3030
Type,
3131
TypeVar,
32-
_eval_type,
3332
cast,
3433
)
3534

35+
try:
36+
from typing import _eval_type # type: ignore
37+
except ImportError:
38+
39+
def _eval_type(t, globalns, localns, recursive_guard=frozenset()): # type: ignore
40+
return t
41+
42+
43+
try:
44+
from typing import _type_check # type: ignore
45+
except ImportError:
46+
47+
def _type_check(arg, msg, is_argument=True, module=None): # type: ignore
48+
return arg
49+
50+
3651
try:
3752
from typing import _ClassVar # type: ignore
3853
except ImportError: # pragma: no cover
@@ -367,23 +382,44 @@ def eval_type(
367382
368383
```sh
369384
>>> eval_type('List[int]') == typing.List[int]
385+
>>> eval_type('list[int]') == list[int]
370386
```
371387
"""
372388
invalid_types = invalid_types or set()
373389
alias_types = alias_types or {}
374390
if isinstance(typ, str):
375391
typ = ForwardRef(typ)
376392
if isinstance(typ, ForwardRef):
377-
if sys.version_info < (3, 9):
378-
typ = typ._evaluate(globalns, localns)
379-
else:
380-
typ = typ._evaluate(globalns, localns, frozenset())
393+
typ = _ForwardRef_safe_eval(typ, globalns, localns)
381394
typ = _eval_type(typ, globalns, localns)
382395
if typ in invalid_types:
383396
raise InvalidAnnotation(typ)
384397
return alias_types.get(typ, typ)
385398

386399

400+
def _ForwardRef_safe_eval(
401+
ref: ForwardRef,
402+
globalns: Optional[Dict[str, Any]] = None,
403+
localns: Optional[Dict[str, Any]] = None,
404+
) -> Type:
405+
# On 3.6/3.7 ForwardRef._evaluate crashes if str references ClassVar
406+
if not ref.__forward_evaluated__:
407+
if globalns is None and localns is None:
408+
globalns = localns = {}
409+
elif globalns is None:
410+
globalns = localns
411+
elif localns is None:
412+
localns = globalns
413+
val = eval(ref.__forward_code__, globalns, localns) # noqa: S307
414+
if not _is_class_var(val):
415+
val = _type_check(
416+
val, "Forward references must evaluate to types."
417+
)
418+
ref.__forward_value__ = val
419+
ref.__forward_evaluated__ = True
420+
return ref.__forward_value__
421+
422+
387423
def _get_globalns(typ: Type) -> Dict[str, Any]:
388424
return sys.modules[typ.__module__].__dict__
389425

tests/unit/utils/test_objects.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
annotations,
3535
canoname,
3636
canonshortname,
37+
eval_type,
3738
guess_polymorphic_type,
3839
is_optional,
3940
is_union,
@@ -184,6 +185,12 @@ class Y: ...
184185
assert canonshortname(y, main_name="faust") == ".".join([__name__, "Y"])
185186

186187

188+
@pytest.mark.skip(reason="Needs fixing, typing.List eval does not work")
189+
def test_eval_type():
190+
assert eval_type("list") == list
191+
assert eval_type("typing.List") == typing.List
192+
193+
187194
def test_annotations():
188195
class X:
189196
Foo: ClassVar[int] = 3

0 commit comments

Comments
 (0)