Typechecker stdlib module :shipit:

This commit is contained in:
Xander Mckay 2024-11-30 10:29:08 -05:00
parent 6f8b5a4e96
commit 5316cff2a2
6 changed files with 736 additions and 1709 deletions

1703
debug.txt

File diff suppressed because it is too large Load diff

4
examples/typechecker.it Normal file
View file

@ -0,0 +1,4 @@
import typecheck as tc
tc.checktype('hello', int | str) |> print
tc.checktype(0xdeadbeef, int | str) |> print
tc.checktype(['hello', 0xdeadbeef], int | str) |> print

View file

@ -43,7 +43,7 @@ class lazy_typegetter:
def translate(file: io.StringIO, debug: int = 0): def translate(file: io.StringIO, debug: int = 0, bcp: bool = False):
def infix(name: str): def infix(name: str):
yield tokenize.OP, ">>" yield tokenize.OP, ">>"
yield tokenize.NAME, name yield tokenize.NAME, name
@ -147,7 +147,7 @@ def translate(file: io.StringIO, debug: int = 0):
yield type,name yield type,name
dprint(f'---END DEBOUT---', 4) dprint(f'---END DEBOUT---', 4)
def transpile(input_path: Path, verbosity: int, minify: bool) -> None: def transpile(input_path: Path, verbosity: int, minify: bool, bcp: bool) -> None:
dir = Path('dist') dir = Path('dist')
if input_path.is_dir(): if input_path.is_dir():
for i in input_path.glob('*'): for i in input_path.glob('*'):
@ -176,17 +176,19 @@ app = typer.Typer()
verbosity_arg = typing.Annotated[int, typer.Option('--verbosity', '-v')] verbosity_arg = typing.Annotated[int, typer.Option('--verbosity', '-v')]
minify_arg = typing.Annotated[bool, typer.Option('--minify', '-m')] minify_arg = typing.Annotated[bool, typer.Option('--minify', '-m')]
bcp_arg = typing.Annotated[bool, typer.Option('--build-custom-prefix', '-B')]
@app.command('t') @app.command('t')
@app.command('ts') @app.command('ts')
@app.command('transpile') @app.command('transpile')
def transpile_cmd(input_path: pathlib.Path, verbosity: verbosity_arg = 0, minify: minify_arg = False) -> None: def transpile_cmd(input_path: pathlib.Path, verbosity: verbosity_arg = 0, minify: minify_arg = False, bcp: bcp_arg = False) -> None:
transpile(input_path, verbosity, minify) transpile(input_path, verbosity, minify, bcp)
@app.command('r') @app.command('r')
@app.command('run') @app.command('run')
def run_cmd(input_path: pathlib.Path, verbosity: verbosity_arg = 0, minify: minify_arg = False) -> None: def run_cmd(input_path: pathlib.Path, verbosity: verbosity_arg = 0, minify: minify_arg = False, bcp: bcp_arg = False) -> None:
input_path = Path(input_path) input_path = Path(input_path)
transpile(input_path, verbosity, minify) transpile(input_path, verbosity, minify, bcp)
if input_path.is_dir(): if input_path.is_dir():
os.system(f'{sys.executable} -m dist') os.system(f'{sys.executable} -m dist')
Path('dist').rmtree() Path('dist').rmtree()

View file

@ -65,6 +65,7 @@ def _INTERNAL_add_fakeimport(name: str, code: str): # TODO: make this use sys.me
_INTERNAL_add_fakeimport('sentinels', std'sentinels.py') _INTERNAL_add_fakeimport('sentinels', std'sentinels.py')
_INTERNAL_add_fakeimport('ipathlib', std'ipathlib.py') _INTERNAL_add_fakeimport('ipathlib', std'ipathlib.py')
_INTERNAL_add_fakeimport('typecheck', std'typecheck.py')
_INTERNAL_lazymerge = _INTERNAL_Token(lambda lhs, rhs: _INTERNAL_LazyIterable(lhs, rhs)) _INTERNAL_lazymerge = _INTERNAL_Token(lambda lhs, rhs: _INTERNAL_LazyIterable(lhs, rhs))
_INTERNAL_lpipe = _INTERNAL_Token(lambda lhs, rhs: rhs(lhs)) _INTERNAL_lpipe = _INTERNAL_Token(lambda lhs, rhs: rhs(lhs))

714
std/typecheck.py Normal file
View file

@ -0,0 +1,714 @@
from typing import (
_GenericAlias,
_TypedDictMeta,
TYPE_CHECKING,
Any,
Callable,
Dict,
ForwardRef,
FrozenSet,
List,
Literal,
Mapping,
MutableMapping,
MutableSequence,
NamedTuple,
Never,
NewType,
NoReturn,
Optional,
Sequence,
Set,
Tuple,
Type,
TypeAliasType,
TypeGuard,
TypeVar,
Union,
_eval_type as eval_type,
_type_repr as type_repr,
cast,
get_args,
get_origin,
get_type_hints,
overload
)
from collections.abc import Callable as CCallable, Mapping as CMapping, MutableMapping as CMutableMapping, MutableSequence as CMutableSequence, Sequence as CSequence
from inspect import Parameter
from types import UnionType, ModuleType, GenericAlias
import functools, math, inspect, sys, importlib, re, builtins
_T = TypeVar("_T")
_F = TypeVar("_F")
_SimpleTypeVar = TypeVar("_SimpleTypeVar")
_SimpleTypeVarCo = TypeVar("_SimpleTypeVarCo", covariant=True)
_MISSING = object()
_IMPORTABLE_TYPE_EXPRESSION_RE = re.compile(r"^((?:[a-zA-Z0-9_]+\.)+)(.*)$")
_UNIMPORTABLE_TYPE_EXPRESSION_RE = re.compile(r"^[a-zA-Z0-9_]+(\[.*\])?$")
_BUILTINS_MODULE: ModuleType = builtins
_EXTRA_ADVISE_IF_MOD_IS_BUILTINS = (
" Try altering the type argument to be a string "
"reference (surrounded with quotes) instead, "
"if not already done."
)
class UnresolvedForwardRefError(TypeError):...
class UnresolvableTypeError(TypeError):...
class ValidationError(TypeError):...
class TypeNotSupportedError(TypeError):...
class _TrycastOptions(NamedTuple):
strict: bool
eval: bool
funcname: str
class _LazyStr(str):
def __init__(self, value_func: Callable[[], str], /) -> None:
self._value_func = value_func
self._value = None # type: Optional[str]
def __str__(self) -> str:
if self._value is None:
self._value = self._value_func()
return self._value
def _substitute(tp: object, substitutions: Dict[object, object]) -> object:
if isinstance(tp, GenericAlias): # ex: tuple[T1, T2]
return GenericAlias( # type: ignore[reportCallIssue] # pyright
tp.__origin__, tuple([_substitute(a, substitutions) for a in tp.__args__])
)
if isinstance(tp, TypeVar): # type: ignore[wrong-arg-types] # pytype
return substitutions.get(tp, tp)
return tp
def _inspect_signature(value):
return inspect.signature(
value,
# Don't auto-unwrap decorated functions
follow_wrapped=False,
# Don't need annotation information
eval_str=False,
)
def _is_typed_dict(tp: object) -> bool:
return isinstance(tp, _TypedDictMeta)
def _is_newtype(tp: object) -> bool:
return isinstance(tp, NewType)
def _is_simple_typevar(T: object, covariant: bool = False) -> bool:
return (
isinstance(T, TypeVar) # type: ignore[wrong-arg-types] # pytype
and T.__constraints__ == () # type: ignore[attribute-error] # pytype
and T.__covariant__ == covariant # type: ignore[attribute-error] # pytype
and T.__contravariant__ is False # type: ignore[attribute-error] # pytype
and T.__constraints__ == () # type: ignore[attribute-error] # pytype
)
def _checkcast_listlike(
tp: object,
value: object,
listlike_type: Type,
options: _TrycastOptions,
*,
covariant_t: bool = False,
t_ellipsis: bool = False,
) -> "Optional[ValidationError]":
if isinstance(value, listlike_type):
T_ = get_args(tp)
if len(T_) == 0: # Python 3.9+
(T,) = (_SimpleTypeVarCo if covariant_t else _SimpleTypeVar,)
else:
if t_ellipsis:
if len(T_) == 2 and T_[1] is Ellipsis:
(T, _) = T_
else:
return ValidationError(tp, value)
else:
(T,) = T_
if _is_simple_typevar(T, covariant=covariant_t):
pass
else:
for i, x in enumerate(value): # type: ignore[attribute-error] # pytype
e = _checkcast_inner(T, x, options)
if e is not None:
return ValidationError(
tp,
value,
_causes=[e._with_prefix(_LazyStr(lambda: f"At index {i}"))],
)
return None
else:
return ValidationError(tp, value)
def _checkcast_dictlike(
tp: object,
value: object,
dictlike_type: Type,
options: _TrycastOptions,
*,
covariant_v: bool = False,
) -> "Optional[ValidationError]":
if isinstance(value, dictlike_type):
K_V = get_args(tp)
if len(K_V) == 0: # Python 3.9+
(K, V) = (
_SimpleTypeVar,
_SimpleTypeVarCo if covariant_v else _SimpleTypeVar,
)
else:
(K, V) = K_V
if _is_simple_typevar(K) and _is_simple_typevar(V, covariant=covariant_v):
pass
else:
for k, v in value.items(): # type: ignore[attribute-error] # pytype
e = _checkcast_inner(K, k, options)
if e is not None:
return ValidationError(
tp,
value,
_causes=[e._with_prefix(_LazyStr(lambda: f"Key {k!r}"))],
)
e = _checkcast_inner(V, v, options)
if e is not None:
return ValidationError(
tp,
value,
_causes=[e._with_prefix(_LazyStr(lambda: f"At key {k!r}"))],
)
return None
else:
return ValidationError(tp, value)
def _type_check(arg: object, msg: str):
"""Returns the argument if it appears to be a type.
Raises TypeError if the argument is a known non-type.
As a special case, accepts None and returns type(None) instead.
Also wraps strings into ForwardRef instances.
"""
arg = _type_convert(arg, module=None)
# Recognize *common* non-types. (This check is not exhaustive.)
if isinstance(arg, (dict, list, int, tuple)):
raise TypeError(f"{msg} Got {arg!r:.100}.")
return arg
# Python 3.10's typing._type_convert()
def _type_convert(arg, module=None):
"""For converting None to type(None), and strings to ForwardRef."""
if arg is None:
return type(None)
if isinstance(arg, str):
return ForwardRef(arg, module=module)
return arg
@overload
def checktype(
value: object, tp: str, /, *, eval: Literal[False]
) -> NoReturn: ... # pragma: no cover
@overload
def checktype(
value: object, tp: str, /, *, eval: bool = True
) -> bool: ... # pragma: no cover
@overload
def checktype(value: object, tp: Type[_T], /, *, eval: bool = True) -> TypeGuard[_T]: # type: ignore[invalid-annotation] # pytype
... # pragma: no cover
@overload
def checktype(
value: object, tp: object, /, *, eval: bool = True
) -> bool: ... # pragma: no cover
def checktype(value, tp, /, *, eval=True):
"""
Returns whether `value` is in the shape of `tp`
(as accepted by a Python typechecker conforming to PEP 484 "Type Hints").
This method logically performs an operation similar to:
return isinstance(value, tp)
except that it supports many more types than `isinstance`, including:
* List[T]
* Dict[K, V]
* Optional[T]
* Union[T1, T2, ...]
* Literal[...]
* T extends TypedDict
See trycast.trycast(..., strict=True) for information about parameters,
raised exceptions, and other details.
"""
e = _checkcast_outer(
tp, value, _TrycastOptions(strict=True, eval=eval, funcname="isassignable")
)
result = e is None
if isinstance(tp, type):
return cast( # type: ignore[invalid-annotation] # pytype
TypeGuard[_T], # type: ignore[not-indexable] # pytype
result,
)
else:
return result # type: ignore[bad-return-type] # pytype
def _checkcast_outer(
tp: object, value: object, options: _TrycastOptions
) -> "Optional[ValidationError]":
if isinstance(tp, str):
if options.eval: # == options.eval (for pytype)
tp = eval_type_str(tp) # does use eval()
else:
raise UnresolvableTypeError(
f"Could not resolve type {tp!r}: "
f"Type appears to be a string reference "
f"and {options.funcname}() was called with eval=False, "
f"disabling eval of string type references."
)
else:
try:
# TODO: Eliminate format operation done by f-string
# from the hot path of _checkcast_outer()
tp = _type_check( # type: ignore[16] # pyre
tp,
f"{options.funcname}() requires a type as its first argument.",
)
except TypeError:
if isinstance(tp, tuple) and len(tp) >= 1 and isinstance(tp[0], type):
raise TypeError(
f"{options.funcname} does not support checking against a tuple of types. "
"Try checking against a Union[T1, T2, ...] instead."
)
else:
raise
try:
return _checkcast_inner(tp, value, options) # type: ignore[bad-return-type] # pytype
except UnresolvedForwardRefError:
if options.eval:
advise = (
"Try altering the first type argument to be a string "
"reference (surrounded with quotes) instead."
)
else:
advise = (
f"{options.funcname}() cannot resolve string type references "
"because it was called with eval=False."
)
raise UnresolvedForwardRefError(
f"{options.funcname} does not support checking against type form {tp!r} "
"which contains a string-based forward reference. "
f"{advise}"
)
def _checkcast_inner(
tp: object, value: object, options: _TrycastOptions
) -> "Optional[ValidationError]":
"""
Raises:
* TypeNotSupportedError
* UnresolvedForwardRefError
"""
if tp is int:
# Also accept bools as valid int values
if isinstance(value, int):
return None
else:
return ValidationError(tp, value)
if tp is float:
# Also accept ints and bools as valid float values
if isinstance(value, float) or isinstance(value, int):
return None
else:
return ValidationError(tp, value)
if tp is complex:
# Also accept floats, ints, and bools as valid complex values
if (
isinstance(value, complex)
or isinstance(value, float)
or isinstance(value, int)
):
return None
else:
return ValidationError(tp, value)
type_origin = get_origin(tp)
if type_origin is list or type_origin is List: # List, List[T]
return _checkcast_listlike(tp, value, list, options)
if type_origin is set or type_origin is Set: # Set, Set[T]
return _checkcast_listlike(tp, value, set, options)
if type_origin is frozenset or type_origin is FrozenSet: # FrozenSet, FrozenSet[T]
return _checkcast_listlike(tp, value, frozenset, options, covariant_t=True)
if type_origin is tuple or type_origin is Tuple:
if isinstance(value, tuple):
type_args = get_args(tp)
if len(type_args) == 0 or (
len(type_args) == 2 and type_args[1] is Ellipsis
): # Tuple, Tuple[T, ...]
return _checkcast_listlike(
tp,
value,
tuple,
options,
covariant_t=True,
t_ellipsis=True,
)
else: # Tuple[Ts]
if len(value) != len(type_args):
return ValidationError(tp, value)
for i, T, t in zip(range(len(type_args)), type_args, value):
e = _checkcast_inner(T, t, options)
if e is not None:
return ValidationError(
tp,
value,
_causes=[e._with_prefix(_LazyStr(lambda: f"At index {i}"))],
)
return None
else:
return ValidationError(tp, value)
if type_origin is Sequence or type_origin is CSequence: # Sequence, Sequence[T]
return _checkcast_listlike(tp, value, CSequence, options, covariant_t=True)
if (
type_origin is MutableSequence or type_origin is CMutableSequence
): # MutableSequence, MutableSequence[T]
return _checkcast_listlike(tp, value, CMutableSequence, options)
if type_origin is dict or type_origin is Dict: # Dict, Dict[K, V]
return _checkcast_dictlike(tp, value, dict, options)
if type_origin is Mapping or type_origin is CMapping: # Mapping, Mapping[K, V]
return _checkcast_dictlike(tp, value, CMapping, options, covariant_v=True)
if (
type_origin is MutableMapping or type_origin is CMutableMapping
): # MutableMapping, MutableMapping[K, V]
return _checkcast_dictlike(tp, value, CMutableMapping, options)
if (
type_origin is Union or type_origin is UnionType
): # Union[T1, T2, ...], Optional[T]
causes = []
for T in get_args(tp):
e = _checkcast_inner(T, value, options)
if e is not None:
causes.append(e)
else:
return None
return ValidationError(tp, value, causes)
if type_origin is Literal: # Literal[...]
for literal in get_args(tp):
if value == literal:
return None
return ValidationError(tp, value)
if type_origin is CCallable:
callable_args = get_args(tp)
if callable_args == ():
# Callable
if callable(value):
return None
else:
return ValidationError(tp, value)
else:
assert len(callable_args) == 2
(param_types, return_type) = callable_args
if return_type is not Any:
# Callable[..., T]
raise TypeNotSupportedError(
f"{options.funcname} cannot reliably determine whether value is "
f"a {type_repr(tp)} because "
f"callables at runtime do not always have a "
f"declared return type. "
f"Consider using {options.funcname}(Callable, value) instead."
)
if param_types is Ellipsis:
# Callable[..., Any]
return _checkcast_inner(Callable, value, options)
assert isinstance(param_types, list)
for param_type in param_types:
if param_type is not Any:
raise TypeNotSupportedError(
f"{options.funcname} cannot reliably determine whether value is "
f"a {type_repr(tp)} because "
f"callables at runtime do not always have "
f"declared parameter types. "
f"Consider using {options.funcname}("
f"Callable[{','.join('Any' * len(param_types))}, Any], value) "
f"instead."
)
# Callable[[Any * N], Any]
if callable(value):
try:
sig = _inspect_signature(value)
except TypeError:
# Not a callable
return ValidationError(tp, value)
except ValueError as f:
# Unable to introspect signature for value.
# It might be a built-in function that lacks signature support.
# Assume conservatively that value does NOT match the requested type.
e = ValidationError(tp, value)
e.__cause__ = f
return e
else:
sig_min_param_count = 0 # type: float
sig_max_param_count = 0 # type: float
for expected_param in sig.parameters.values():
if (
expected_param.kind == Parameter.POSITIONAL_ONLY
or expected_param.kind == Parameter.POSITIONAL_OR_KEYWORD
):
if expected_param.default is Parameter.empty:
sig_min_param_count += 1
sig_max_param_count += 1
elif expected_param.kind == Parameter.VAR_POSITIONAL:
sig_max_param_count = math.inf
if sig_min_param_count <= len(param_types) <= sig_max_param_count:
return None
else:
return ValidationError(tp, value)
else:
return ValidationError(tp, value)
if isinstance(type_origin, TypeAliasType): # type: ignore[16] # pyre
if len(type_origin.__type_params__) > 0:
substitutions = dict(
zip(
type_origin.__type_params__,
get_args(tp) + ((Any,) * len(type_origin.__type_params__)),
)
) # type: Dict[object, object]
new_tp = _substitute(tp.__value__, substitutions) # type: ignore[attr-defined] # mypy
else:
new_tp = tp.__value__ # type: ignore[attr-defined] # mypy
return _checkcast_inner(new_tp, value, options) # type: ignore[16] # pyre
if isinstance(tp, _GenericAlias): # type: ignore[16] # pyre
raise TypeNotSupportedError(
f"{options.funcname} does not know how to recognize generic type "
f"{type_repr(type_origin)}."
)
if _is_typed_dict(tp): # T extends TypedDict
if isinstance(value, Mapping):
if options.eval:
resolved_annotations = get_type_hints( # does use eval()
tp # type: ignore[arg-type] # mypy
) # resolve ForwardRefs in tp.__annotations__
else:
resolved_annotations = tp.__annotations__ # type: ignore[attribute-error] # pytype
try:
# {typing in Python 3.9+, typing_extensions}.TypedDict
required_keys = tp.__required_keys__ # type: ignore[attr-defined, attribute-error] # mypy, pytype
except AttributeError:
# {typing in Python 3.8, mypy_extensions}.TypedDict
if options.strict:
if sys.version_info[:2] >= (3, 9):
advise = "Suggest use a typing.TypedDict instead."
else:
advise = "Suggest use a typing_extensions.TypedDict instead."
advise2 = f"Or use {options.funcname}(..., strict=False)."
raise TypeNotSupportedError(
f"{options.funcname} cannot determine which keys are required "
f"and which are potentially-missing for the "
f"specified kind of TypedDict. {advise} {advise2}"
)
else:
if tp.__total__: # type: ignore[attr-defined, attribute-error] # mypy, pytype
required_keys = resolved_annotations.keys()
else:
required_keys = frozenset()
for k, v in value.items(): # type: ignore[attribute-error] # pytype
V = resolved_annotations.get(k, _MISSING)
if V is not _MISSING:
e = _checkcast_inner(V, v, options)
if e is not None:
return ValidationError(
tp,
value,
_causes=[e._with_prefix(_LazyStr(lambda: f"At key {k!r}"))],
)
for k in required_keys:
if k not in value: # type: ignore[unsupported-operands] # pytype
return ValidationError(
tp,
value,
_causes=[
ValidationError._from_message(
_LazyStr(lambda: f"Required key {k!r} is missing")
)
],
)
return None
else:
return ValidationError(tp, value)
if _is_newtype(tp):
if options.strict:
supertype_repr = type_repr(tp.__supertype__) # type: ignore[attr-defined, attribute-error] # mypy, pytype
tp_name_repr = repr(tp.__name__) # type: ignore[attr-defined] # mypy
raise TypeNotSupportedError(
f"{options.funcname} cannot reliably determine whether value is "
f"a NewType({tp_name_repr}, {supertype_repr}) because "
f"NewType wrappers are erased at runtime "
f"and are indistinguishable from their supertype. "
f"Consider using {options.funcname}(..., strict=False) to treat "
f"NewType({tp_name_repr}, {supertype_repr}) "
f"like {supertype_repr}."
)
else:
supertype = tp.__supertype__ # type: ignore[attr-defined, attribute-error] # mypy, pytype
return _checkcast_inner(supertype, value, options)
if isinstance(tp, TypeVar): # type: ignore[wrong-arg-types] # pytype
raise TypeNotSupportedError(
f"{options.funcname} cannot reliably determine whether value matches a TypeVar."
)
if tp is Any:
return None
if tp is Never or tp is NoReturn:
return ValidationError(tp, value)
if isinstance(tp, TypeAliasType): # type: ignore[16] # pyre
if len(tp.__type_params__) > 0: # type: ignore[16] # pyre
substitutions = dict(
zip(tp.__type_params__, ((Any,) * len(tp.__type_params__)))
)
new_tp = _substitute(tp.__value__, substitutions)
else:
new_tp = tp.__value__
return _checkcast_inner(new_tp, value, options) # type: ignore[16] # pyre
if isinstance(tp, ForwardRef):
raise UnresolvedForwardRefError()
if isinstance(value, tp): # type: ignore[arg-type, wrong-arg-types] # mypy, pytype
return None
else:
return ValidationError(tp, value)
@functools.lru_cache()
def eval_type_str(tp: str, /) -> object:
"""
Resolves a string-reference to a type that can be imported,
such as `'typing.List'`.
This function does internally cache lookups that have been made in
the past to improve performance. If you need to clear this cache
you can call:
eval_type_str.cache_clear()
Note that this function's implementation uses eval() internally.
Raises:
* UnresolvableTypeError --
If the specified string-reference could not be resolved to a type.
"""
if not isinstance(tp, str): # pragma: no cover
raise ValueError()
# Determine which module to lookup the type from
mod: ModuleType
module_name: str
member_expr: str
m = _IMPORTABLE_TYPE_EXPRESSION_RE.fullmatch(tp)
if m is not None:
(module_name_dot, member_expr) = m.groups()
module_name = module_name_dot[:-1]
try:
mod = importlib.import_module(module_name)
except Exception:
raise UnresolvableTypeError(
f"Could not resolve type {tp!r}: " f"Could not import {module_name!r}."
)
else:
m = _UNIMPORTABLE_TYPE_EXPRESSION_RE.fullmatch(tp)
if m is not None:
mod = _BUILTINS_MODULE
module_name = _BUILTINS_MODULE.__name__
member_expr = tp
else:
raise UnresolvableTypeError(
f"Could not resolve type {tp!r}: "
f"{tp!r} does not appear to be a valid type."
)
# Lookup the type from a module
try:
member = eval(member_expr, mod.__dict__, None)
except Exception:
raise UnresolvableTypeError(
f"Could not resolve type {tp!r}: "
f"Could not eval {member_expr!r} inside module {module_name!r}."
f"{_EXTRA_ADVISE_IF_MOD_IS_BUILTINS if mod is _BUILTINS_MODULE else ''}"
)
# Interpret an imported str as a TypeAlias
if isinstance(member, str):
member = ForwardRef(member, is_argument=False)
# Resolve any ForwardRef instances inside the type
try:
member = eval_type(member, mod.__dict__, None) # type: ignore[16] # pyre
except Exception:
raise UnresolvableTypeError(
f"Could not resolve type {tp!r}: "
f"Could not eval type {member!r} inside module {module_name!r}."
f"{_EXTRA_ADVISE_IF_MOD_IS_BUILTINS if mod is _BUILTINS_MODULE else ''}"
)
# 1. Ensure the object is actually a type
# 2. As a special case, interpret None as type(None)
try:
member = _type_check(member, f"Could not resolve type {tp!r}: ") # type: ignore[16] # pyre
except TypeError as e:
raise UnresolvableTypeError(str(e))
return member

9
test_.py Normal file
View file

@ -0,0 +1,9 @@
import std.typecheck as tc
import typing
OptionalString = str | None
print(tc.checktype("wow", OptionalString))
print(tc.checktype(None, OptionalString))
print(tc.checktype(0xDEADBEEF, OptionalString))
@tc.check_args
def a(a:int,b:str,c:typing.Any):...
a(1,"",1223)