diff --git a/.github/workflows/github-action-test-python.yml b/.github/workflows/github-action-test-python.yml
index db70dcc9..46b34cf1 100644
--- a/.github/workflows/github-action-test-python.yml
+++ b/.github/workflows/github-action-test-python.yml
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
# You need to change to branch protection rules if you change the versions here
- python-version: [3.12.1, 3.13.0]
+ python-version: [3.12.11, 3.13.7]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -24,5 +24,4 @@ jobs:
cd python && ./allTestsForPyVersion
- name: Test pytrace-generator
run: |
- python3 python/src/runYourProgram.py --install-mode installOnly
python3 pytrace-generator/test/runTests.py
diff --git a/ChangeLog.md b/ChangeLog.md
index c2ecf21c..d9be4f54 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,13 @@
# Write Your Python Program - CHANGELOG
+* 2.0.0 (2025-09-24)
+ * Remove wrappers, only check types at function enter/exit points
+ * Restructure directory layout
+ * RUN button now longer copies files to the site-lib directory
+* 1.3.2 (2025-05-11)
+ * Fix release (version 1.3.1. did no include the latest changes)
+* 1.3.1 (2025-05-10)
+ * Fix bug with python 3.13 and wrappers
* 1.3.0 (2024-12-01)
* Fix bug with union of unions
* Improve scrolling #148
diff --git a/README.md b/README.md
index 8cc650ae..fde41b84 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,3 @@
-[](https://github.com/skogsbaer/write-your-python-program/actions/workflows/github-action-test-python.yml)
-[](https://github.com/skogsbaer/write-your-python-program/actions/workflows/github-action-test-js.yml)
-
# Write Your Python Program
A user-friendly python programming environment for beginners.
@@ -40,6 +37,11 @@ Run `wypp --help` for usage information.
Here is the [Changelog](ChangeLog.md).
+* **Breaking change** in version 2.0.0 (2025-0-24): type annotations are now only
+ checked when entering/exiting a function. Before, certain things such as lists
+ or callable were put behind wrapper objects. For example, these wrappers ensured
+ that only ints could be appended to a list of type `list[int]`. However, these
+ wrappers came with several drawbacks, so they were removed in release 2.0.0
* **Breaking change** in version 0.12.0 (2021-09-28): type annotations are now checked
dynamically when the code is executed.
This behavior can be deactivated in the settings of the extension.
@@ -50,7 +52,10 @@ You need an explicit import statement such as `from wypp import *`.
Here is a screen shot:
-
+
+
+There is also a visualization mode, similar to [Python Tutor](https://pythontutor.com/):
+
When hitting the RUN button, the vscode extension saves the current file, opens
a terminal and executes the file with Python, staying in interactive mode after
@@ -59,7 +64,7 @@ all definitions have been executed.
The file being executed should contain the following import statement in the first line:
~~~python
-from wypp import*
+from wypp import *
~~~
Running the file with the RUN button makes the following features available:
@@ -193,39 +198,6 @@ before the type being defined, for example to define recursive types or as
the type of `self` inside of classes. In fact, there is no check at all to make sure
that anotations refer to existing types.
-For builtin `list[T]` the following operations are typechecked:
-- `list[idx]`
-- `list[idx] = value`
-- `list += [...]`
-- `list.append(value)`
-- `list.insert(idx, value)`
-- `list.extend(iterator)`
-- `for i in list:` (Iterator)
-
-For builtin `set[T]` these operations are typechecked:
-- `set.add(value)`
-- `set.pop()`
-- `set.remove(value)` Value must be of `T`
-- `set.update(other, ...)`
-- `value in set` Value must be of `T`
-- `for i in set:` (Iterator)
-
-For builtin `dict[K,V]` the supported typechecked operations are:
-- `dict.get(key)`
-- `dict.items()`
-- `dict.keys()`
-- `dict.pop()`
-- `dict.popitem()`
-- `dict.setdefault(key, default)`
_Note:_ In contrast to the standard library `default` is required, to avoid inserting `None` as value into otherwise typed dicts.
-- `dict.update(other)`
-- `dict.update(key=value, ...)`
-- `dict.values()`
-- `key in dict` Key must be of `K`
-- `del dict[key]`
-- `for k in dict` (Iterator)
-- `reversed(dict)`
-- `dict[key]`
-- `dict[key] = value`
## Module name and current working directory
diff --git a/package.json b/package.json
index 39112370..195b628a 100644
--- a/package.json
+++ b/package.json
@@ -3,11 +3,11 @@
"displayName": "Write Your Python Program!",
"description": "A user friendly python environment for beginners",
"license": "See license in LICENSE",
- "version": "1.3.0",
+ "version": "2.0.0",
"publisher": "StefanWehr",
"icon": "icon.png",
"engines": {
- "vscode": "^1.85.0"
+ "vscode": "^1.94.2"
},
"keywords": [
"Python",
diff --git a/python/.ignore b/python/.ignore
deleted file mode 100644
index eed76414..00000000
--- a/python/.ignore
+++ /dev/null
@@ -1,2 +0,0 @@
-site-lib/wypp
-site-lib/untypy
diff --git a/python/.vscode/settings.json b/python/.vscode/settings.json
new file mode 100644
index 00000000..1e488efe
--- /dev/null
+++ b/python/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "python.analysis.diagnosticMode": "workspace",
+ "python.autoComplete.extraPaths": [
+ "/Users/swehr/.vscode/extensions/stefanwehr.write-your-python-program-1.3.2/python/src/"
+ ]
+}
diff --git a/python/TODO.org b/python/TODO.org
new file mode 100644
index 00000000..6a6a06d1
--- /dev/null
+++ b/python/TODO.org
@@ -0,0 +1,2 @@
+* Code duplication
+- wrappedclass.py, protocol.py and typedfunction.py
diff --git a/python/TODO_nowrappers.md b/python/TODO_nowrappers.md
new file mode 100644
index 00000000..d5352319
--- /dev/null
+++ b/python/TODO_nowrappers.md
@@ -0,0 +1,4 @@
+* Test windows
+* location matcher for vscode
+* show "@record\nclass C" for record attributes
+
diff --git a/python/allTests b/python/allTests
index 3837a1e3..1128200f 100755
--- a/python/allTests
+++ b/python/allTests
@@ -15,5 +15,5 @@ function run()
echo
}
-PYENV_VERSION=3.12.1 run
-PYENV_VERSION=3.13.0 run
+PYENV_VERSION=3.12 run
+PYENV_VERSION=3.13 run
diff --git a/python/allTestsForPyVersion b/python/allTestsForPyVersion
index fac2ba23..52d27349 100755
--- a/python/allTestsForPyVersion
+++ b/python/allTestsForPyVersion
@@ -5,16 +5,8 @@ set -u
cd $(dirname $0)
-unit_test_path=src:tests:deps/untypy
-
-function prepare_integration_tests()
-{
- echo "Preparing integration tests by install the WYPP library"
- local d=$(mktemp -d)
- trap "rm -rf $d" EXIT
- WYPP_INSTALL_DIR=$d python3 src/runYourProgram.py --install-mode installOnly
- integ_test_path=integration-tests:$d
-}
+unit_test_path=code/wypp:tests:code
+integration_test_path=code/wypp:integration-tests:code
function usage()
{
@@ -22,47 +14,52 @@ function usage()
exit 1
}
-if [ -z "${1:-}" ]; then
- echo "Python version:"
- python3 --version
- echo "Running untypy tests"
- pushd deps/untypy > /dev/null
- ./runtests.sh || exit 1
- popd > /dev/null
- echo "Done with untypy tests"
- echo "Running all unit tests, PYTHONPATH=$unit_test_path"
- PYTHONPATH=$unit_test_path python3 -m unittest tests/test*.py
- echo "Done with unit tests"
- echo
- prepare_integration_tests
- echo "Running all integration tests, PYTHONPATH=$integ_test_path"
- PYTHONPATH=$integ_test_path python3 -m unittest integration-tests/test*.py
- echo "Done with integration tests"
-else
- if [ "$1" == "--unit" ]; then
- what="unit"
- dir=tests
- p=$unit_test_path
- elif [ "$1" == "--integration" ]; then
- what="integration"
- dir=integration-tests
- prepare_integration_tests
- p=$integ_test_path
+function run_unit_tests()
+{
+ echo "Running unit tests, PYTHONPATH=$unit_test_path"
+ if [ -z "${1:-}" ]; then
+ PYTHONPATH=$unit_test_path python3 -m unittest tests/test*.py
+ ecode=$?
else
- usage
+ PYTHONPATH=$unit_test_path python3 -m unittest "$@"
+ ecode=$?
fi
- shift
- echo "Running $what tests $@ with PYTHONPATH=$p"
+ echo "Done with unit tests"
+}
+
+function run_integration_tests()
+{
+ echo "Running all integration tests, PYTHONPATH=$integration_test_path"
if [ -z "${1:-}" ]; then
- PYTHONPATH=$p python3 -m unittest $dir/test*.py
+ PYTHONPATH=$integration_test_path python3 -m unittest integration-tests/test*.py
ecode=$?
else
- PYTHONPATH=$p python3 -m unittest "$@"
+ PYTHONPATH=$integration_test_path python3 -m unittest "$@"
ecode=$?
fi
- echo "$what tests finished with exit code $ecode"
+ echo "Done with integration tests"
+}
+
+
+echo "Python version:"
+python3 --version
+
+if [ -z "${1:-}" ]; then
+ run_unit_tests
+ echo
+ run_integration_tests
+ echo
+ echo "Running file tests ..."
+ python3 ./fileTests.py
+elif [ "$1" == "--unit" ]; then
+ shift
+ run_unit_tests "$@"
exit $ecode
+elif [ "$1" == "--integration" ]; then
+ shift
+ run_integration_tests "$@"
+ exit $ecode
+else
+ usage
fi
-echo "Running file tests ..."
-./fileTests
diff --git a/python/code/typeguard/__init__.py b/python/code/typeguard/__init__.py
new file mode 100644
index 00000000..6781cad0
--- /dev/null
+++ b/python/code/typeguard/__init__.py
@@ -0,0 +1,48 @@
+import os
+from typing import Any
+
+from ._checkers import TypeCheckerCallable as TypeCheckerCallable
+from ._checkers import TypeCheckLookupCallback as TypeCheckLookupCallback
+from ._checkers import check_type_internal as check_type_internal
+from ._checkers import checker_lookup_functions as checker_lookup_functions
+from ._checkers import load_plugins as load_plugins
+from ._config import CollectionCheckStrategy as CollectionCheckStrategy
+from ._config import ForwardRefPolicy as ForwardRefPolicy
+from ._config import TypeCheckConfiguration as TypeCheckConfiguration
+from ._decorators import typechecked as typechecked
+from ._decorators import typeguard_ignore as typeguard_ignore
+from ._exceptions import InstrumentationWarning as InstrumentationWarning
+from ._exceptions import TypeCheckError as TypeCheckError
+from ._exceptions import TypeCheckWarning as TypeCheckWarning
+from ._exceptions import TypeHintWarning as TypeHintWarning
+from ._functions import TypeCheckFailCallback as TypeCheckFailCallback
+from ._functions import check_type as check_type
+from ._functions import warn_on_error as warn_on_error
+from ._importhook import ImportHookManager as ImportHookManager
+from ._importhook import TypeguardFinder as TypeguardFinder
+from ._importhook import install_import_hook as install_import_hook
+from ._memo import TypeCheckMemo as TypeCheckMemo
+from ._suppression import suppress_type_checks as suppress_type_checks
+from ._utils import Unset as Unset
+
+# Re-export imports so they look like they live directly in this package
+for value in list(locals().values()):
+ if getattr(value, "__module__", "").startswith(f"{__name__}."):
+ value.__module__ = __name__
+
+
+config: TypeCheckConfiguration
+
+
+def __getattr__(name: str) -> Any:
+ if name == "config":
+ from ._config import global_config
+
+ return global_config
+
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
+# Automatically load checker lookup functions unless explicitly disabled
+if "TYPEGUARD_DISABLE_PLUGIN_AUTOLOAD" not in os.environ:
+ load_plugins()
diff --git a/python/code/typeguard/_checkers.py b/python/code/typeguard/_checkers.py
new file mode 100644
index 00000000..d3875f7a
--- /dev/null
+++ b/python/code/typeguard/_checkers.py
@@ -0,0 +1,1094 @@
+from __future__ import annotations
+
+import collections.abc
+import inspect
+import sys
+import types
+import typing
+import warnings
+from collections.abc import Mapping, MutableMapping, Sequence
+from enum import Enum
+from inspect import Parameter, isclass, isfunction
+from io import BufferedIOBase, IOBase, RawIOBase, TextIOBase
+from itertools import zip_longest
+from textwrap import indent
+from typing import (
+ IO,
+ AbstractSet,
+ Annotated,
+ Any,
+ BinaryIO,
+ Callable,
+ Dict,
+ ForwardRef,
+ List,
+ NewType,
+ Optional,
+ Set,
+ TextIO,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+)
+from unittest.mock import Mock
+
+import typing_extensions
+
+# Must use this because typing.is_typeddict does not recognize
+# TypedDict from typing_extensions, and as of version 4.12.0
+# typing_extensions.TypedDict is different from typing.TypedDict
+# on all versions.
+from typing_extensions import is_typeddict
+
+from ._config import ForwardRefPolicy
+from ._exceptions import TypeCheckError, TypeHintWarning
+from ._memo import TypeCheckMemo
+from ._utils import evaluate_forwardref, get_stacklevel, get_type_name, qualified_name
+
+if sys.version_info >= (3, 11):
+ from typing import (
+ NotRequired,
+ TypeAlias,
+ get_args,
+ get_origin,
+ )
+
+ SubclassableAny = Any
+else:
+ from typing_extensions import Any as SubclassableAny
+ from typing_extensions import (
+ NotRequired,
+ TypeAlias,
+ get_args,
+ get_origin,
+ )
+
+if sys.version_info >= (3, 10):
+ from importlib.metadata import entry_points
+ from typing import ParamSpec
+else:
+ from importlib_metadata import entry_points
+ from typing_extensions import ParamSpec
+
+TypeCheckerCallable: TypeAlias = Callable[
+ [Any, Any, Tuple[Any, ...], TypeCheckMemo], Any
+]
+TypeCheckLookupCallback: TypeAlias = Callable[
+ [Any, Tuple[Any, ...], Tuple[Any, ...]], Optional[TypeCheckerCallable]
+]
+
+checker_lookup_functions: list[TypeCheckLookupCallback] = []
+generic_alias_types: tuple[type, ...] = (
+ type(List),
+ type(List[Any]),
+ types.GenericAlias,
+)
+
+# Sentinel
+_missing = object()
+
+# Lifted from mypy.sharedparse
+BINARY_MAGIC_METHODS = {
+ "__add__",
+ "__and__",
+ "__cmp__",
+ "__divmod__",
+ "__div__",
+ "__eq__",
+ "__floordiv__",
+ "__ge__",
+ "__gt__",
+ "__iadd__",
+ "__iand__",
+ "__idiv__",
+ "__ifloordiv__",
+ "__ilshift__",
+ "__imatmul__",
+ "__imod__",
+ "__imul__",
+ "__ior__",
+ "__ipow__",
+ "__irshift__",
+ "__isub__",
+ "__itruediv__",
+ "__ixor__",
+ "__le__",
+ "__lshift__",
+ "__lt__",
+ "__matmul__",
+ "__mod__",
+ "__mul__",
+ "__ne__",
+ "__or__",
+ "__pow__",
+ "__radd__",
+ "__rand__",
+ "__rdiv__",
+ "__rfloordiv__",
+ "__rlshift__",
+ "__rmatmul__",
+ "__rmod__",
+ "__rmul__",
+ "__ror__",
+ "__rpow__",
+ "__rrshift__",
+ "__rshift__",
+ "__rsub__",
+ "__rtruediv__",
+ "__rxor__",
+ "__sub__",
+ "__truediv__",
+ "__xor__",
+}
+
+
+def check_callable(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if not callable(value):
+ raise TypeCheckError("is not callable")
+
+ if args:
+ try:
+ signature = inspect.signature(value)
+ except (TypeError, ValueError):
+ return
+
+ argument_types = args[0]
+ if isinstance(argument_types, list) and not any(
+ type(item) is ParamSpec for item in argument_types
+ ):
+ # The callable must not have keyword-only arguments without defaults
+ unfulfilled_kwonlyargs = [
+ param.name
+ for param in signature.parameters.values()
+ if param.kind == Parameter.KEYWORD_ONLY
+ and param.default == Parameter.empty
+ ]
+ if unfulfilled_kwonlyargs:
+ raise TypeCheckError(
+ f"has mandatory keyword-only arguments in its declaration: "
+ f'{", ".join(unfulfilled_kwonlyargs)}'
+ )
+
+ num_positional_args = num_mandatory_pos_args = 0
+ has_varargs = False
+ for param in signature.parameters.values():
+ if param.kind in (
+ Parameter.POSITIONAL_ONLY,
+ Parameter.POSITIONAL_OR_KEYWORD,
+ ):
+ num_positional_args += 1
+ if param.default is Parameter.empty:
+ num_mandatory_pos_args += 1
+ elif param.kind == Parameter.VAR_POSITIONAL:
+ has_varargs = True
+
+ if num_mandatory_pos_args > len(argument_types):
+ raise TypeCheckError(
+ f"has too many mandatory positional arguments in its declaration; "
+ f"expected {len(argument_types)} but {num_mandatory_pos_args} "
+ f"mandatory positional argument(s) declared"
+ )
+ elif not has_varargs and num_positional_args < len(argument_types):
+ raise TypeCheckError(
+ f"has too few arguments in its declaration; expected "
+ f"{len(argument_types)} but {num_positional_args} argument(s) "
+ f"declared"
+ )
+
+
+def check_mapping(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if origin_type is Dict or origin_type is dict:
+ if not isinstance(value, dict):
+ raise TypeCheckError("is not a dict")
+ if origin_type is MutableMapping or origin_type is collections.abc.MutableMapping:
+ if not isinstance(value, collections.abc.MutableMapping):
+ raise TypeCheckError("is not a mutable mapping")
+ elif not isinstance(value, collections.abc.Mapping):
+ raise TypeCheckError("is not a mapping")
+
+ if args:
+ key_type, value_type = args
+ if key_type is not Any or value_type is not Any:
+ samples = memo.config.collection_check_strategy.iterate_samples(
+ value.items()
+ )
+ for k, v in samples:
+ try:
+ check_type_internal(k, key_type, memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(f"key {k!r}")
+ raise
+
+ try:
+ check_type_internal(v, value_type, memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(f"value of key {k!r}")
+ raise
+
+
+def check_typed_dict(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if not isinstance(value, dict):
+ raise TypeCheckError("is not a dict")
+
+ declared_keys = frozenset(origin_type.__annotations__)
+ if hasattr(origin_type, "__required_keys__"):
+ required_keys = set(origin_type.__required_keys__)
+ else: # py3.8 and lower
+ required_keys = set(declared_keys) if origin_type.__total__ else set()
+
+ existing_keys = set(value)
+ extra_keys = existing_keys - declared_keys
+ if extra_keys:
+ keys_formatted = ", ".join(f'"{key}"' for key in sorted(extra_keys, key=repr))
+ raise TypeCheckError(f"has unexpected extra key(s): {keys_formatted}")
+
+ # Detect NotRequired fields which are hidden by get_type_hints()
+ type_hints: dict[str, type] = {}
+ for key, annotation in origin_type.__annotations__.items():
+ if isinstance(annotation, ForwardRef):
+ annotation = evaluate_forwardref(annotation, memo)
+
+ if get_origin(annotation) is NotRequired:
+ required_keys.discard(key)
+ annotation = get_args(annotation)[0]
+
+ type_hints[key] = annotation
+
+ missing_keys = required_keys - existing_keys
+ if missing_keys:
+ keys_formatted = ", ".join(f'"{key}"' for key in sorted(missing_keys, key=repr))
+ raise TypeCheckError(f"is missing required key(s): {keys_formatted}")
+
+ for key, argtype in type_hints.items():
+ argvalue = value.get(key, _missing)
+ if argvalue is not _missing:
+ try:
+ check_type_internal(argvalue, argtype, memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(f"value of key {key!r}")
+ raise
+
+
+def check_list(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if not isinstance(value, list):
+ raise TypeCheckError("is not a list")
+
+ if args and args != (Any,):
+ samples = memo.config.collection_check_strategy.iterate_samples(value)
+ for i, v in enumerate(samples):
+ try:
+ check_type_internal(v, args[0], memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(f"item {i}")
+ raise
+
+
+def check_sequence(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if not isinstance(value, collections.abc.Sequence):
+ raise TypeCheckError("is not a sequence")
+
+ if args and args != (Any,):
+ samples = memo.config.collection_check_strategy.iterate_samples(value)
+ for i, v in enumerate(samples):
+ try:
+ check_type_internal(v, args[0], memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(f"item {i}")
+ raise
+
+
+def check_set(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if origin_type is frozenset:
+ if not isinstance(value, frozenset):
+ raise TypeCheckError("is not a frozenset")
+ elif not isinstance(value, AbstractSet):
+ raise TypeCheckError("is not a set")
+
+ if args and args != (Any,):
+ samples = memo.config.collection_check_strategy.iterate_samples(value)
+ for v in samples:
+ try:
+ check_type_internal(v, args[0], memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(f"[{v}]")
+ raise
+
+
+def check_tuple(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ # Specialized check for NamedTuples
+ if field_types := getattr(origin_type, "__annotations__", None):
+ if not isinstance(value, origin_type):
+ raise TypeCheckError(
+ f"is not a named tuple of type {qualified_name(origin_type)}"
+ )
+
+ for name, field_type in field_types.items():
+ try:
+ check_type_internal(getattr(value, name), field_type, memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(f"attribute {name!r}")
+ raise
+
+ return
+ elif not isinstance(value, tuple):
+ raise TypeCheckError("is not a tuple")
+
+ if args:
+ use_ellipsis = args[-1] is Ellipsis
+ tuple_params = args[: -1 if use_ellipsis else None]
+ else:
+ # Unparametrized Tuple or plain tuple
+ return
+
+ if use_ellipsis:
+ element_type = tuple_params[0]
+ samples = memo.config.collection_check_strategy.iterate_samples(value)
+ for i, element in enumerate(samples):
+ try:
+ check_type_internal(element, element_type, memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(f"item {i}")
+ raise
+ elif tuple_params == ((),):
+ if value != ():
+ raise TypeCheckError("is not an empty tuple")
+ else:
+ if len(value) != len(tuple_params):
+ raise TypeCheckError(
+ f"has wrong number of elements (expected {len(tuple_params)}, got "
+ f"{len(value)} instead)"
+ )
+
+ for i, (element, element_type) in enumerate(zip(value, tuple_params)):
+ try:
+ check_type_internal(element, element_type, memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(f"item {i}")
+ raise
+
+
+def check_union(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ errors: dict[str, TypeCheckError] = {}
+ try:
+ for type_ in args:
+ try:
+ check_type_internal(value, type_, memo)
+ return
+ except TypeCheckError as exc:
+ errors[get_type_name(type_)] = exc
+
+ formatted_errors = indent(
+ "\n".join(f"{key}: {error}" for key, error in errors.items()), " "
+ )
+ finally:
+ del errors # avoid creating ref cycle
+
+ raise TypeCheckError(f"did not match any element in the union:\n{formatted_errors}")
+
+
+def check_uniontype(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if not args:
+ return check_instance(value, types.UnionType, (), memo)
+
+ errors: dict[str, TypeCheckError] = {}
+ try:
+ for type_ in args:
+ try:
+ check_type_internal(value, type_, memo)
+ return
+ except TypeCheckError as exc:
+ errors[get_type_name(type_)] = exc
+
+ formatted_errors = indent(
+ "\n".join(f"{key}: {error}" for key, error in errors.items()), " "
+ )
+ finally:
+ del errors # avoid creating ref cycle
+
+ raise TypeCheckError(f"did not match any element in the union:\n{formatted_errors}")
+
+
+def check_class(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if not isclass(value) and not isinstance(value, generic_alias_types):
+ raise TypeCheckError("is not a class")
+
+ if not args:
+ return
+
+ if isinstance(args[0], ForwardRef):
+ expected_class = evaluate_forwardref(args[0], memo)
+ else:
+ expected_class = args[0]
+
+ if expected_class is Any:
+ return
+ elif expected_class is typing_extensions.Self:
+ check_self(value, get_origin(expected_class), get_args(expected_class), memo)
+ elif getattr(expected_class, "_is_protocol", False):
+ check_protocol(value, expected_class, (), memo)
+ elif isinstance(expected_class, TypeVar):
+ check_typevar(value, expected_class, (), memo, subclass_check=True)
+ elif get_origin(expected_class) is Union:
+ errors: dict[str, TypeCheckError] = {}
+ try:
+ for arg in get_args(expected_class):
+ if arg is Any:
+ return
+
+ try:
+ check_class(value, type, (arg,), memo)
+ return
+ except TypeCheckError as exc:
+ errors[get_type_name(arg)] = exc
+ else:
+ formatted_errors = indent(
+ "\n".join(f"{key}: {error}" for key, error in errors.items()), " "
+ )
+ raise TypeCheckError(
+ f"did not match any element in the union:\n{formatted_errors}"
+ )
+ finally:
+ del errors # avoid creating ref cycle
+ elif not issubclass(value, expected_class): # type: ignore[arg-type]
+ raise TypeCheckError(f"is not a subclass of {qualified_name(expected_class)}")
+
+
+def check_newtype(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ check_type_internal(value, origin_type.__supertype__, memo)
+
+
+def check_instance(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if not isinstance(value, origin_type):
+ raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
+
+
+def check_typevar(
+ value: Any,
+ origin_type: TypeVar,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+ *,
+ subclass_check: bool = False,
+) -> None:
+ if origin_type.__bound__ is not None:
+ annotation = (
+ type[origin_type.__bound__] if subclass_check else origin_type.__bound__
+ )
+ check_type_internal(value, annotation, memo)
+ elif origin_type.__constraints__:
+ for constraint in origin_type.__constraints__:
+ annotation = Type[constraint] if subclass_check else constraint
+ try:
+ check_type_internal(value, annotation, memo)
+ except TypeCheckError:
+ pass
+ else:
+ break
+ else:
+ formatted_constraints = ", ".join(
+ get_type_name(constraint) for constraint in origin_type.__constraints__
+ )
+ raise TypeCheckError(
+ f"does not match any of the constraints " f"({formatted_constraints})"
+ )
+
+
+def _is_literal_type(typ: object) -> bool:
+ return typ is typing.Literal or typ is typing_extensions.Literal
+
+
+def check_literal(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ def get_literal_args(literal_args: tuple[Any, ...]) -> tuple[Any, ...]:
+ retval: list[Any] = []
+ for arg in literal_args:
+ if _is_literal_type(get_origin(arg)):
+ retval.extend(get_literal_args(arg.__args__))
+ elif arg is None or isinstance(arg, (int, str, bytes, bool, Enum)):
+ retval.append(arg)
+ else:
+ raise TypeError(
+ f"Illegal literal value: {arg}"
+ ) # TypeError here is deliberate
+
+ return tuple(retval)
+
+ final_args = tuple(get_literal_args(args))
+ try:
+ index = final_args.index(value)
+ except ValueError:
+ pass
+ else:
+ if type(final_args[index]) is type(value):
+ return
+
+ formatted_args = ", ".join(repr(arg) for arg in final_args)
+ raise TypeCheckError(f"is not any of ({formatted_args})") from None
+
+
+def check_literal_string(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ check_type_internal(value, str, memo)
+
+
+def check_typeguard(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ check_type_internal(value, bool, memo)
+
+
+def check_none(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if value is not None:
+ raise TypeCheckError("is not None")
+
+
+def check_number(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if origin_type is complex and not isinstance(value, (complex, float, int)):
+ raise TypeCheckError("is neither complex, float or int")
+ elif origin_type is float and not isinstance(value, (float, int)):
+ raise TypeCheckError("is neither float or int")
+
+
+def check_io(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if origin_type is TextIO or (origin_type is IO and args == (str,)):
+ if not isinstance(value, TextIOBase):
+ raise TypeCheckError("is not a text based I/O object")
+ elif origin_type is BinaryIO or (origin_type is IO and args == (bytes,)):
+ if not isinstance(value, (RawIOBase, BufferedIOBase)):
+ raise TypeCheckError("is not a binary I/O object")
+ elif not isinstance(value, IOBase):
+ raise TypeCheckError("is not an I/O object")
+
+
+def check_signature_compatible(subject: type, protocol: type, attrname: str) -> None:
+ subject_attr = getattr(subject, attrname)
+ try:
+ subject_sig = inspect.signature(subject_attr)
+ except ValueError:
+ return # this can happen with builtins where the signature cannot be retrieved
+
+ protocol_sig = inspect.signature(getattr(protocol, attrname))
+ protocol_type: typing.Literal["instance", "class", "static"] = "instance"
+ subject_type: typing.Literal["instance", "class", "static"] = "instance"
+
+ # Check if the protocol-side method is a class method or static method
+ if attrname in protocol.__dict__:
+ descriptor = protocol.__dict__[attrname]
+ if isinstance(descriptor, staticmethod):
+ protocol_type = "static"
+ elif isinstance(descriptor, classmethod):
+ protocol_type = "class"
+
+ # Check if the subject-side method is a class method or static method
+ if attrname in subject.__dict__:
+ descriptor = subject.__dict__[attrname]
+ if isinstance(descriptor, staticmethod):
+ subject_type = "static"
+ elif isinstance(descriptor, classmethod):
+ subject_type = "class"
+
+ if protocol_type == "instance" and subject_type != "instance":
+ raise TypeCheckError(
+ f"should be an instance method but it's a {subject_type} method"
+ )
+ elif protocol_type != "instance" and subject_type == "instance":
+ raise TypeCheckError(
+ f"should be a {protocol_type} method but it's an instance method"
+ )
+
+ expected_varargs = any(
+ param
+ for param in protocol_sig.parameters.values()
+ if param.kind is Parameter.VAR_POSITIONAL
+ )
+ has_varargs = any(
+ param
+ for param in subject_sig.parameters.values()
+ if param.kind is Parameter.VAR_POSITIONAL
+ )
+ if expected_varargs and not has_varargs:
+ raise TypeCheckError("should accept variable positional arguments but doesn't")
+
+ protocol_has_varkwargs = any(
+ param
+ for param in protocol_sig.parameters.values()
+ if param.kind is Parameter.VAR_KEYWORD
+ )
+ subject_has_varkwargs = any(
+ param
+ for param in subject_sig.parameters.values()
+ if param.kind is Parameter.VAR_KEYWORD
+ )
+ if protocol_has_varkwargs and not subject_has_varkwargs:
+ raise TypeCheckError("should accept variable keyword arguments but doesn't")
+
+ # Check that the callable has at least the expect amount of positional-only
+ # arguments (and no extra positional-only arguments without default values)
+ if not has_varargs:
+ protocol_args = [
+ param
+ for param in protocol_sig.parameters.values()
+ if param.kind
+ in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
+ ]
+ subject_args = [
+ param
+ for param in subject_sig.parameters.values()
+ if param.kind
+ in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)
+ ]
+
+ # Remove the "self" parameter from the protocol arguments to match
+ if protocol_type == "instance":
+ protocol_args.pop(0)
+
+ # Remove the "self" parameter from the subject arguments to match
+ if subject_type == "instance":
+ subject_args.pop(0)
+
+ for protocol_arg, subject_arg in zip_longest(protocol_args, subject_args):
+ if protocol_arg is None:
+ if subject_arg.default is Parameter.empty:
+ raise TypeCheckError("has too many mandatory positional arguments")
+
+ break
+
+ if subject_arg is None:
+ raise TypeCheckError("has too few positional arguments")
+
+ if (
+ protocol_arg.kind is Parameter.POSITIONAL_OR_KEYWORD
+ and subject_arg.kind is Parameter.POSITIONAL_ONLY
+ ):
+ raise TypeCheckError(
+ f"has an argument ({subject_arg.name}) that should not be "
+ f"positional-only"
+ )
+
+ if (
+ protocol_arg.kind is Parameter.POSITIONAL_OR_KEYWORD
+ and protocol_arg.name != subject_arg.name
+ ):
+ raise TypeCheckError(
+ f"has a positional argument ({subject_arg.name}) that should be "
+ f"named {protocol_arg.name!r} at this position"
+ )
+
+ protocol_kwonlyargs = {
+ param.name: param
+ for param in protocol_sig.parameters.values()
+ if param.kind is Parameter.KEYWORD_ONLY
+ }
+ subject_kwonlyargs = {
+ param.name: param
+ for param in subject_sig.parameters.values()
+ if param.kind is Parameter.KEYWORD_ONLY
+ }
+ if not subject_has_varkwargs:
+ # Check that the signature has at least the required keyword-only arguments, and
+ # no extra mandatory keyword-only arguments
+ if missing_kwonlyargs := [
+ param.name
+ for param in protocol_kwonlyargs.values()
+ if param.name not in subject_kwonlyargs
+ ]:
+ raise TypeCheckError(
+ "is missing keyword-only arguments: " + ", ".join(missing_kwonlyargs)
+ )
+
+ if not protocol_has_varkwargs:
+ if extra_kwonlyargs := [
+ param.name
+ for param in subject_kwonlyargs.values()
+ if param.default is Parameter.empty
+ and param.name not in protocol_kwonlyargs
+ ]:
+ raise TypeCheckError(
+ "has mandatory keyword-only arguments not present in the protocol: "
+ + ", ".join(extra_kwonlyargs)
+ )
+
+
+def check_protocol(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ origin_annotations = typing.get_type_hints(origin_type)
+ for attrname in sorted(typing_extensions.get_protocol_members(origin_type)):
+ if (annotation := origin_annotations.get(attrname)) is not None:
+ try:
+ subject_member = getattr(value, attrname)
+ except AttributeError:
+ raise TypeCheckError(
+ f"is not compatible with the {origin_type.__qualname__} "
+ f"protocol because it has no attribute named {attrname!r}"
+ ) from None
+
+ try:
+ check_type_internal(subject_member, annotation, memo)
+ except TypeCheckError as exc:
+ raise TypeCheckError(
+ f"is not compatible with the {origin_type.__qualname__} "
+ f"protocol because its {attrname!r} attribute {exc}"
+ ) from None
+ elif callable(getattr(origin_type, attrname)):
+ try:
+ subject_member = getattr(value, attrname)
+ except AttributeError:
+ raise TypeCheckError(
+ f"is not compatible with the {origin_type.__qualname__} "
+ f"protocol because it has no method named {attrname!r}"
+ ) from None
+
+ if not callable(subject_member):
+ raise TypeCheckError(
+ f"is not compatible with the {origin_type.__qualname__} "
+ f"protocol because its {attrname!r} attribute is not a callable"
+ )
+
+ # TODO: implement assignability checks for parameter and return value
+ # annotations
+ subject = value if isclass(value) else value.__class__
+ try:
+ check_signature_compatible(subject, origin_type, attrname)
+ except TypeCheckError as exc:
+ raise TypeCheckError(
+ f"is not compatible with the {origin_type.__qualname__} "
+ f"protocol because its {attrname!r} method {exc}"
+ ) from None
+
+
+def check_byteslike(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if not isinstance(value, (bytearray, bytes, memoryview)):
+ raise TypeCheckError("is not bytes-like")
+
+
+def check_self(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ if memo.self_type is None:
+ raise TypeCheckError("cannot be checked against Self outside of a method call")
+
+ if isclass(value):
+ if not issubclass(value, memo.self_type):
+ raise TypeCheckError(
+ f"is not a subclass of the self type ({qualified_name(memo.self_type)})"
+ )
+ elif not isinstance(value, memo.self_type):
+ raise TypeCheckError(
+ f"is not an instance of the self type ({qualified_name(memo.self_type)})"
+ )
+
+
+def check_paramspec(
+ value: Any,
+ origin_type: Any,
+ args: tuple[Any, ...],
+ memo: TypeCheckMemo,
+) -> None:
+ pass # No-op for now
+
+def resolve_annotation_str(s: str, globalns: dict, localns: dict):
+ Tmp = type("_Tmp", (), {"__annotations__": {"x": s}})
+ return typing.get_type_hints(Tmp, globalns=globalns, localns=localns)["x"]
+
+def resolve_alias_chains(tp: Any):
+ # 1) Follow PEP 695 alias chains (type My = ... -> TypeAliasType)
+ seen = set()
+ t = type(tp)
+ while isinstance(tp, typing.TypeAliasType) and id(tp) not in seen:
+ seen.add(id(tp))
+ tp = tp.__value__ # evaluated lazily in its defining module
+ return tp
+
+def check_type_internal(
+ value: Any,
+ annotation: Any,
+ memo: TypeCheckMemo,
+) -> None:
+ """
+ Check that the given object is compatible with the given type annotation.
+
+ This function should only be used by type checker callables. Applications should use
+ :func:`~.check_type` instead.
+
+ :param value: the value to check
+ :param annotation: the type annotation to check against
+ :param memo: a memo object containing configuration and information necessary for
+ looking up forward references
+ """
+ annotation = resolve_alias_chains(annotation)
+
+ if isinstance(annotation, ForwardRef):
+ try:
+ annotation = evaluate_forwardref(annotation, memo)
+ except NameError:
+ if memo.config.forward_ref_policy is ForwardRefPolicy.ERROR:
+ raise
+ elif memo.config.forward_ref_policy is ForwardRefPolicy.WARN:
+ warnings.warn(
+ f"Cannot resolve forward reference {annotation.__forward_arg__!r}",
+ TypeHintWarning,
+ stacklevel=get_stacklevel(),
+ )
+ return
+ if isinstance(annotation, str):
+ annotation = resolve_annotation_str(annotation, memo.globals, memo.locals)
+
+ if annotation is Any or annotation is SubclassableAny or isinstance(value, Mock):
+ return
+
+ # Skip type checks if value is an instance of a class that inherits from Any
+ if not isclass(value) and SubclassableAny in type(value).__bases__:
+ return
+
+ extras: tuple[Any, ...]
+ origin_type = get_origin(annotation)
+ if origin_type is Annotated:
+ annotation, *extras_ = get_args(annotation)
+ extras = tuple(extras_)
+ origin_type = get_origin(annotation)
+ else:
+ extras = ()
+
+ if origin_type is not None:
+ args = get_args(annotation)
+
+ # Compatibility hack to distinguish between unparametrized and empty tuple
+ # (tuple[()]), necessary due to https://github.com/python/cpython/issues/91137
+ if origin_type in (tuple, Tuple) and annotation is not Tuple and not args:
+ args = ((),)
+ else:
+ origin_type = annotation
+ args = ()
+
+ for lookup_func in checker_lookup_functions:
+ checker = lookup_func(origin_type, args, extras)
+ if checker:
+ checker(value, origin_type, args, memo)
+ return
+
+ if isclass(origin_type):
+ if not isinstance(value, origin_type):
+ raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
+ elif type(origin_type) is str: # noqa: E721
+ warnings.warn(
+ f"Skipping type check against {origin_type!r}; this looks like a "
+ f"string-form forward reference imported from another module",
+ TypeHintWarning,
+ stacklevel=get_stacklevel(),
+ )
+
+
+# Equality checks are applied to these
+origin_type_checkers = {
+ bytes: check_byteslike,
+ AbstractSet: check_set,
+ BinaryIO: check_io,
+ Callable: check_callable,
+ collections.abc.Callable: check_callable,
+ complex: check_number,
+ dict: check_mapping,
+ Dict: check_mapping,
+ float: check_number,
+ frozenset: check_set,
+ IO: check_io,
+ list: check_list,
+ List: check_list,
+ typing.Literal: check_literal,
+ Mapping: check_mapping,
+ MutableMapping: check_mapping,
+ None: check_none,
+ collections.abc.Mapping: check_mapping,
+ collections.abc.MutableMapping: check_mapping,
+ Sequence: check_sequence,
+ collections.abc.Sequence: check_sequence,
+ collections.abc.Set: check_set,
+ set: check_set,
+ Set: check_set,
+ TextIO: check_io,
+ tuple: check_tuple,
+ Tuple: check_tuple,
+ type: check_class,
+ Type: check_class,
+ Union: check_union,
+ # On some versions of Python, these may simply be re-exports from "typing",
+ # but exactly which Python versions is subject to change.
+ # It's best to err on the safe side and just always specify these.
+ typing_extensions.Literal: check_literal,
+ typing_extensions.LiteralString: check_literal_string,
+ typing_extensions.Self: check_self,
+ typing_extensions.TypeGuard: check_typeguard,
+}
+if sys.version_info >= (3, 10):
+ origin_type_checkers[types.UnionType] = check_uniontype
+ origin_type_checkers[typing.TypeGuard] = check_typeguard
+if sys.version_info >= (3, 11):
+ origin_type_checkers.update(
+ {typing.LiteralString: check_literal_string, typing.Self: check_self}
+ )
+
+
+def builtin_checker_lookup(
+ origin_type: Any, args: tuple[Any, ...], extras: tuple[Any, ...]
+) -> TypeCheckerCallable | None:
+ checker = origin_type_checkers.get(origin_type)
+ if checker is not None:
+ return checker
+ elif is_typeddict(origin_type):
+ return check_typed_dict
+ elif isclass(origin_type) and issubclass(
+ origin_type,
+ Tuple, # type: ignore[arg-type]
+ ):
+ # NamedTuple
+ return check_tuple
+ elif getattr(origin_type, "_is_protocol", False):
+ return check_protocol
+ elif isinstance(origin_type, ParamSpec):
+ return check_paramspec
+ elif isinstance(origin_type, TypeVar):
+ return check_typevar
+ elif origin_type.__class__ is NewType:
+ # typing.NewType on Python 3.10+
+ return check_newtype
+ elif (
+ isfunction(origin_type)
+ and getattr(origin_type, "__module__", None) == "typing"
+ and getattr(origin_type, "__qualname__", "").startswith("NewType.")
+ and hasattr(origin_type, "__supertype__")
+ ):
+ # typing.NewType on Python 3.9
+ return check_newtype
+
+ return None
+
+
+checker_lookup_functions.append(builtin_checker_lookup)
+
+
+def load_plugins() -> None:
+ """
+ Load all type checker lookup functions from entry points.
+
+ All entry points from the ``typeguard.checker_lookup`` group are loaded, and the
+ returned lookup functions are added to :data:`typeguard.checker_lookup_functions`.
+
+ .. note:: This function is called implicitly on import, unless the
+ ``TYPEGUARD_DISABLE_PLUGIN_AUTOLOAD`` environment variable is present.
+ """
+
+ for ep in entry_points(group="typeguard.checker_lookup"):
+ try:
+ plugin = ep.load()
+ except Exception as exc:
+ warnings.warn(
+ f"Failed to load plugin {ep.name!r}: " f"{qualified_name(exc)}: {exc}",
+ stacklevel=2,
+ )
+ continue
+
+ if not callable(plugin):
+ warnings.warn(
+ f"Plugin {ep} returned a non-callable object: {plugin!r}", stacklevel=2
+ )
+ continue
+
+ checker_lookup_functions.insert(0, plugin)
diff --git a/python/code/typeguard/_config.py b/python/code/typeguard/_config.py
new file mode 100644
index 00000000..c3097640
--- /dev/null
+++ b/python/code/typeguard/_config.py
@@ -0,0 +1,106 @@
+from __future__ import annotations
+
+from collections.abc import Iterable
+from dataclasses import dataclass
+from enum import Enum, auto
+from typing import TYPE_CHECKING, TypeVar
+
+if TYPE_CHECKING:
+ from ._functions import TypeCheckFailCallback
+
+T = TypeVar("T")
+
+
+class ForwardRefPolicy(Enum):
+ """
+ Defines how unresolved forward references are handled.
+
+ Members:
+
+ * ``ERROR``: propagate the :exc:`NameError` when the forward reference lookup fails
+ * ``WARN``: emit a :class:`~.TypeHintWarning` if the forward reference lookup fails
+ * ``IGNORE``: silently skip checks for unresolveable forward references
+ """
+
+ ERROR = auto()
+ WARN = auto()
+ IGNORE = auto()
+
+
+class CollectionCheckStrategy(Enum):
+ """
+ Specifies how thoroughly the contents of collections are type checked.
+
+ This has an effect on the following built-in checkers:
+
+ * ``AbstractSet``
+ * ``Dict``
+ * ``List``
+ * ``Mapping``
+ * ``Set``
+ * ``Tuple[, ...]`` (arbitrarily sized tuples)
+
+ Members:
+
+ * ``FIRST_ITEM``: check only the first item
+ * ``ALL_ITEMS``: check all items
+ """
+
+ FIRST_ITEM = auto()
+ ALL_ITEMS = auto()
+
+ def iterate_samples(self, collection: Iterable[T]) -> Iterable[T]:
+ if self is CollectionCheckStrategy.FIRST_ITEM:
+ try:
+ return [next(iter(collection))]
+ except StopIteration:
+ return ()
+ else:
+ return collection
+
+
+@dataclass
+class TypeCheckConfiguration:
+ """
+ You can change Typeguard's behavior with these settings.
+
+ .. attribute:: typecheck_fail_callback
+ :type: Callable[[TypeCheckError, TypeCheckMemo], Any]
+
+ Callable that is called when type checking fails.
+
+ Default: ``None`` (the :exc:`~.TypeCheckError` is raised directly)
+
+ .. attribute:: forward_ref_policy
+ :type: ForwardRefPolicy
+
+ Specifies what to do when a forward reference fails to resolve.
+
+ Default: ``WARN``
+
+ .. attribute:: collection_check_strategy
+ :type: CollectionCheckStrategy
+
+ Specifies how thoroughly the contents of collections (list, dict, etc.) are
+ type checked.
+
+ Default: ``FIRST_ITEM``
+
+ .. attribute:: debug_instrumentation
+ :type: bool
+
+ If set to ``True``, the code of modules or functions instrumented by typeguard
+ is printed to ``sys.stderr`` after the instrumentation is done
+
+ Default: ``False``
+ """
+
+ forward_ref_policy: ForwardRefPolicy = ForwardRefPolicy.WARN
+ typecheck_fail_callback: TypeCheckFailCallback | None = None
+ collection_check_strategy: CollectionCheckStrategy = (
+ CollectionCheckStrategy.FIRST_ITEM
+ )
+ debug_instrumentation: bool = False
+
+
+global_config = TypeCheckConfiguration()
diff --git a/python/code/typeguard/_decorators.py b/python/code/typeguard/_decorators.py
new file mode 100644
index 00000000..3ed85b0c
--- /dev/null
+++ b/python/code/typeguard/_decorators.py
@@ -0,0 +1,239 @@
+from __future__ import annotations
+
+import ast
+import inspect
+import sys
+from collections.abc import Sequence
+from functools import partial
+from inspect import isclass, isfunction
+from types import CodeType, FrameType, FunctionType
+from typing import TYPE_CHECKING, Any, Callable, ForwardRef, TypeVar, cast, overload
+from warnings import warn
+
+from ._config import CollectionCheckStrategy, ForwardRefPolicy, global_config
+from ._exceptions import InstrumentationWarning
+from ._functions import TypeCheckFailCallback
+from ._transformer import TypeguardTransformer
+from ._utils import Unset, function_name, get_stacklevel, is_method_of, unset
+
+T_CallableOrType = TypeVar("T_CallableOrType", bound=Callable[..., Any])
+
+if TYPE_CHECKING:
+ from typeshed.stdlib.types import _Cell
+
+ def typeguard_ignore(arg: T_CallableOrType) -> T_CallableOrType:
+ """This decorator is a noop during static type-checking."""
+ return arg
+
+else:
+ from typing import no_type_check as typeguard_ignore # noqa: F401
+
+
+def make_cell(value: object) -> _Cell:
+ return (lambda: value).__closure__[0] # type: ignore[index]
+
+
+def find_target_function(
+ new_code: CodeType, target_path: Sequence[str], firstlineno: int
+) -> CodeType | None:
+ for const in new_code.co_consts:
+ if isinstance(const, CodeType):
+ new_path = (
+ target_path[1:] if const.co_name == target_path[0] else target_path
+ )
+ if not new_path and const.co_firstlineno == firstlineno:
+ return const
+
+ if target_code := find_target_function(const, new_path, firstlineno):
+ return target_code
+
+ return None
+
+
+def instrument(f: T_CallableOrType) -> FunctionType | str:
+ if not getattr(f, "__code__", None):
+ return "no code associated"
+ elif not getattr(f, "__module__", None):
+ return "__module__ attribute is not set"
+ elif f.__code__.co_filename == "":
+ return "cannot instrument functions defined in a REPL"
+ elif hasattr(f, "__wrapped__"):
+ return (
+ "@typechecked only supports instrumenting functions wrapped with "
+ "@classmethod, @staticmethod or @property"
+ )
+
+ target_path = [item for item in f.__qualname__.split(".") if item != ""]
+ module_source = inspect.getsource(sys.modules[f.__module__])
+ module_ast = ast.parse(module_source)
+ instrumentor = TypeguardTransformer(target_path, f.__code__.co_firstlineno)
+ instrumentor.visit(module_ast)
+
+ if not instrumentor.target_node or instrumentor.target_lineno is None:
+ return "instrumentor did not find the target function"
+
+ module_code = compile(module_ast, f.__code__.co_filename, "exec", dont_inherit=True)
+ new_code = find_target_function(
+ module_code, target_path, instrumentor.target_lineno
+ )
+ if not new_code:
+ return "cannot find the target function in the AST"
+
+ if global_config.debug_instrumentation and sys.version_info >= (3, 9):
+ # Find the matching AST node, then unparse it to source and print to stdout
+ print(
+ f"Source code of {f.__qualname__}() after instrumentation:"
+ "\n----------------------------------------------",
+ file=sys.stderr,
+ )
+ print(ast.unparse(instrumentor.target_node), file=sys.stderr)
+ print(
+ "----------------------------------------------",
+ file=sys.stderr,
+ )
+
+ closure = f.__closure__
+ if new_code.co_freevars != f.__code__.co_freevars:
+ # Create a new closure and find values for the new free variables
+ frame = cast(FrameType, inspect.currentframe())
+ frame = cast(FrameType, frame.f_back)
+ frame_locals = cast(FrameType, frame.f_back).f_locals
+ cells: list[_Cell] = []
+ for key in new_code.co_freevars:
+ if key in instrumentor.names_used_in_annotations:
+ # Find the value and make a new cell from it
+ value = frame_locals.get(key) or ForwardRef(key)
+ cells.append(make_cell(value))
+ else:
+ # Reuse the cell from the existing closure
+ assert f.__closure__
+ cells.append(f.__closure__[f.__code__.co_freevars.index(key)])
+
+ closure = tuple(cells)
+
+ new_function = FunctionType(new_code, f.__globals__, f.__name__, closure=closure)
+ new_function.__module__ = f.__module__
+ new_function.__name__ = f.__name__
+ new_function.__qualname__ = f.__qualname__
+ new_function.__doc__ = f.__doc__
+ new_function.__defaults__ = f.__defaults__
+ new_function.__kwdefaults__ = f.__kwdefaults__
+
+ if sys.version_info >= (3, 12):
+ new_function.__type_params__ = f.__type_params__
+
+ if sys.version_info >= (3, 14):
+ new_function.__annotate__ = f.__annotate__
+ else:
+ new_function.__annotations__ = f.__annotations__
+
+ return new_function
+
+
+@overload
+def typechecked(
+ *,
+ forward_ref_policy: ForwardRefPolicy | Unset = unset,
+ typecheck_fail_callback: TypeCheckFailCallback | Unset = unset,
+ collection_check_strategy: CollectionCheckStrategy | Unset = unset,
+ debug_instrumentation: bool | Unset = unset,
+) -> Callable[[T_CallableOrType], T_CallableOrType]: ...
+
+
+@overload
+def typechecked(target: T_CallableOrType) -> T_CallableOrType: ...
+
+
+def typechecked(
+ target: T_CallableOrType | None = None,
+ *,
+ forward_ref_policy: ForwardRefPolicy | Unset = unset,
+ typecheck_fail_callback: TypeCheckFailCallback | Unset = unset,
+ collection_check_strategy: CollectionCheckStrategy | Unset = unset,
+ debug_instrumentation: bool | Unset = unset,
+) -> Any:
+ """
+ Instrument the target function to perform run-time type checking.
+
+ This decorator recompiles the target function, injecting code to type check
+ arguments, return values, yield values (excluding ``yield from``) and assignments to
+ annotated local variables.
+
+ This can also be used as a class decorator. This will instrument all type annotated
+ methods, including :func:`@classmethod `,
+ :func:`@staticmethod `, and :class:`@property ` decorated
+ methods in the class.
+
+ .. note:: When Python is run in optimized mode (``-O`` or ``-OO``, this decorator
+ is a no-op). This is a feature meant for selectively introducing type checking
+ into a code base where the checks aren't meant to be run in production.
+
+ :param target: the function or class to enable type checking for
+ :param forward_ref_policy: override for
+ :attr:`.TypeCheckConfiguration.forward_ref_policy`
+ :param typecheck_fail_callback: override for
+ :attr:`.TypeCheckConfiguration.typecheck_fail_callback`
+ :param collection_check_strategy: override for
+ :attr:`.TypeCheckConfiguration.collection_check_strategy`
+ :param debug_instrumentation: override for
+ :attr:`.TypeCheckConfiguration.debug_instrumentation`
+
+ """
+ if target is None:
+ return partial(
+ typechecked,
+ forward_ref_policy=forward_ref_policy,
+ typecheck_fail_callback=typecheck_fail_callback,
+ collection_check_strategy=collection_check_strategy,
+ debug_instrumentation=debug_instrumentation,
+ )
+
+ if not __debug__:
+ return target
+
+ if isclass(target):
+ for key, attr in target.__dict__.items():
+ if is_method_of(attr, target):
+ retval = instrument(attr)
+ if isfunction(retval):
+ setattr(target, key, retval)
+ elif isinstance(attr, (classmethod, staticmethod)):
+ if is_method_of(attr.__func__, target):
+ retval = instrument(attr.__func__)
+ if isfunction(retval):
+ wrapper = attr.__class__(retval)
+ setattr(target, key, wrapper)
+ elif isinstance(attr, property):
+ kwargs: dict[str, Any] = dict(doc=attr.__doc__)
+ for name in ("fset", "fget", "fdel"):
+ property_func = kwargs[name] = getattr(attr, name)
+ if is_method_of(property_func, target):
+ retval = instrument(property_func)
+ if isfunction(retval):
+ kwargs[name] = retval
+
+ setattr(target, key, attr.__class__(**kwargs))
+
+ return target
+
+ # Find either the first Python wrapper or the actual function
+ wrapper_class: (
+ type[classmethod[Any, Any, Any]] | type[staticmethod[Any, Any]] | None
+ ) = None
+ if isinstance(target, (classmethod, staticmethod)):
+ wrapper_class = target.__class__
+ target = target.__func__ # type: ignore[assignment]
+
+ retval = instrument(target)
+ if isinstance(retval, str):
+ warn(
+ f"{retval} -- not typechecking {function_name(target)}",
+ InstrumentationWarning,
+ stacklevel=get_stacklevel(),
+ )
+ return target
+
+ if wrapper_class is None:
+ return retval
+ else:
+ return wrapper_class(retval)
diff --git a/python/code/typeguard/_exceptions.py b/python/code/typeguard/_exceptions.py
new file mode 100644
index 00000000..625437a6
--- /dev/null
+++ b/python/code/typeguard/_exceptions.py
@@ -0,0 +1,42 @@
+from collections import deque
+from typing import Deque
+
+
+class TypeHintWarning(UserWarning):
+ """
+ A warning that is emitted when a type hint in string form could not be resolved to
+ an actual type.
+ """
+
+
+class TypeCheckWarning(UserWarning):
+ """Emitted by typeguard's type checkers when a type mismatch is detected."""
+
+ def __init__(self, message: str):
+ super().__init__(message)
+
+
+class InstrumentationWarning(UserWarning):
+ """Emitted when there's a problem with instrumenting a function for type checks."""
+
+ def __init__(self, message: str):
+ super().__init__(message)
+
+
+class TypeCheckError(Exception):
+ """
+ Raised by typeguard's type checkers when a type mismatch is detected.
+ """
+
+ def __init__(self, message: str):
+ super().__init__(message)
+ self._path: Deque[str] = deque()
+
+ def append_path_element(self, element: str) -> None:
+ self._path.append(element)
+
+ def __str__(self) -> str:
+ if self._path:
+ return " of ".join(self._path) + " " + str(self.args[0])
+ else:
+ return str(self.args[0])
diff --git a/python/code/typeguard/_functions.py b/python/code/typeguard/_functions.py
new file mode 100644
index 00000000..528d41bb
--- /dev/null
+++ b/python/code/typeguard/_functions.py
@@ -0,0 +1,309 @@
+from __future__ import annotations
+
+import sys
+import warnings
+from collections.abc import Sequence
+from typing import Any, Callable, NoReturn, TypeVar, Union, overload
+
+from . import _suppression
+from ._checkers import BINARY_MAGIC_METHODS, check_type_internal
+from ._config import (
+ CollectionCheckStrategy,
+ ForwardRefPolicy,
+ TypeCheckConfiguration,
+)
+from ._exceptions import TypeCheckError, TypeCheckWarning
+from ._memo import TypeCheckMemo
+from ._utils import get_stacklevel, qualified_name
+
+if sys.version_info >= (3, 11):
+ from typing import Literal, Never, TypeAlias
+else:
+ from typing_extensions import Literal, Never, TypeAlias
+
+T = TypeVar("T")
+TypeCheckFailCallback: TypeAlias = Callable[[TypeCheckError, TypeCheckMemo], Any]
+
+@overload
+def check_type(
+ value: object,
+ expected_type: type[T],
+ *,
+ forward_ref_policy: ForwardRefPolicy = ...,
+ typecheck_fail_callback: TypeCheckFailCallback | None = ...,
+ collection_check_strategy: CollectionCheckStrategy = ...,
+ ns: tuple[dict, dict] | None = None,
+) -> T: ...
+
+
+@overload
+def check_type(
+ value: object,
+ expected_type: Any,
+ *,
+ forward_ref_policy: ForwardRefPolicy = ...,
+ typecheck_fail_callback: TypeCheckFailCallback | None = ...,
+ collection_check_strategy: CollectionCheckStrategy = ...,
+ ns: tuple[dict, dict] | None = None,
+) -> Any: ...
+
+
+def check_type(
+ value: object,
+ expected_type: Any,
+ *,
+ forward_ref_policy: ForwardRefPolicy = TypeCheckConfiguration().forward_ref_policy,
+ typecheck_fail_callback: TypeCheckFailCallback | None = (
+ TypeCheckConfiguration().typecheck_fail_callback
+ ),
+ collection_check_strategy: CollectionCheckStrategy = (
+ TypeCheckConfiguration().collection_check_strategy
+ ),
+ ns: tuple[dict, dict] | None = None,
+) -> Any:
+ """
+ Ensure that ``value`` matches ``expected_type``.
+
+ The types from the :mod:`typing` module do not support :func:`isinstance` or
+ :func:`issubclass` so a number of type specific checks are required. This function
+ knows which checker to call for which type.
+
+ This function wraps :func:`~.check_type_internal` in the following ways:
+
+ * Respects type checking suppression (:func:`~.suppress_type_checks`)
+ * Forms a :class:`~.TypeCheckMemo` from the current stack frame
+ * Calls the configured type check fail callback if the check fails
+
+ Note that this function is independent of the globally shared configuration in
+ :data:`typeguard.config`. This means that usage within libraries is safe from being
+ affected configuration changes made by other libraries or by the integrating
+ application. Instead, configuration options have the same default values as their
+ corresponding fields in :class:`TypeCheckConfiguration`.
+
+ :param value: value to be checked against ``expected_type``
+ :param expected_type: a class or generic type instance, or a tuple of such things
+ :param forward_ref_policy: see :attr:`TypeCheckConfiguration.forward_ref_policy`
+ :param typecheck_fail_callback:
+ see :attr`TypeCheckConfiguration.typecheck_fail_callback`
+ :param collection_check_strategy:
+ see :attr:`TypeCheckConfiguration.collection_check_strategy`
+ :return: ``value``, unmodified
+ :raises TypeCheckError: if there is a type mismatch
+
+ """
+ if type(expected_type) is tuple:
+ expected_type = Union[expected_type]
+
+ config = TypeCheckConfiguration(
+ forward_ref_policy=forward_ref_policy,
+ typecheck_fail_callback=typecheck_fail_callback,
+ collection_check_strategy=collection_check_strategy,
+ )
+
+ if _suppression.type_checks_suppressed or expected_type is Any:
+ return value
+
+ if ns:
+ memo = TypeCheckMemo(ns[0], ns[1], config=config)
+ else:
+ frame = sys._getframe(1)
+ memo = TypeCheckMemo(frame.f_globals, frame.f_locals, config=config)
+ del frame
+ try:
+ check_type_internal(value, expected_type, memo)
+ except TypeCheckError as exc:
+ exc.append_path_element(qualified_name(value, add_class_prefix=True))
+ if config.typecheck_fail_callback:
+ config.typecheck_fail_callback(exc, memo)
+ else:
+ raise
+
+ return value
+
+
+def check_argument_types(
+ func_name: str,
+ arguments: dict[str, tuple[Any, Any]],
+ memo: TypeCheckMemo,
+) -> Literal[True]:
+ if _suppression.type_checks_suppressed:
+ return True
+
+ for argname, (value, annotation) in arguments.items():
+ if annotation is NoReturn or annotation is Never:
+ exc = TypeCheckError(
+ f"{func_name}() was declared never to be called but it was"
+ )
+ if memo.config.typecheck_fail_callback:
+ memo.config.typecheck_fail_callback(exc, memo)
+ else:
+ raise exc
+
+ try:
+ check_type_internal(value, annotation, memo)
+ except TypeCheckError as exc:
+ qualname = qualified_name(value, add_class_prefix=True)
+ exc.append_path_element(f'argument "{argname}" ({qualname})')
+ if memo.config.typecheck_fail_callback:
+ memo.config.typecheck_fail_callback(exc, memo)
+ else:
+ raise
+
+ return True
+
+
+def check_return_type(
+ func_name: str,
+ retval: T,
+ annotation: Any,
+ memo: TypeCheckMemo,
+) -> T:
+ if _suppression.type_checks_suppressed:
+ return retval
+
+ if annotation is NoReturn or annotation is Never:
+ exc = TypeCheckError(f"{func_name}() was declared never to return but it did")
+ if memo.config.typecheck_fail_callback:
+ memo.config.typecheck_fail_callback(exc, memo)
+ else:
+ raise exc
+
+ try:
+ check_type_internal(retval, annotation, memo)
+ except TypeCheckError as exc:
+ # Allow NotImplemented if this is a binary magic method (__eq__() et al)
+ if retval is NotImplemented and annotation is bool:
+ # This does (and cannot) not check if it's actually a method
+ func_name = func_name.rsplit(".", 1)[-1]
+ if func_name in BINARY_MAGIC_METHODS:
+ return retval
+
+ qualname = qualified_name(retval, add_class_prefix=True)
+ exc.append_path_element(f"the return value ({qualname})")
+ if memo.config.typecheck_fail_callback:
+ memo.config.typecheck_fail_callback(exc, memo)
+ else:
+ raise
+
+ return retval
+
+
+def check_send_type(
+ func_name: str,
+ sendval: T,
+ annotation: Any,
+ memo: TypeCheckMemo,
+) -> T:
+ if _suppression.type_checks_suppressed:
+ return sendval
+
+ if annotation is NoReturn or annotation is Never:
+ exc = TypeCheckError(
+ f"{func_name}() was declared never to be sent a value to but it was"
+ )
+ if memo.config.typecheck_fail_callback:
+ memo.config.typecheck_fail_callback(exc, memo)
+ else:
+ raise exc
+
+ try:
+ check_type_internal(sendval, annotation, memo)
+ except TypeCheckError as exc:
+ qualname = qualified_name(sendval, add_class_prefix=True)
+ exc.append_path_element(f"the value sent to generator ({qualname})")
+ if memo.config.typecheck_fail_callback:
+ memo.config.typecheck_fail_callback(exc, memo)
+ else:
+ raise
+
+ return sendval
+
+
+def check_yield_type(
+ func_name: str,
+ yieldval: T,
+ annotation: Any,
+ memo: TypeCheckMemo,
+) -> T:
+ if _suppression.type_checks_suppressed:
+ return yieldval
+
+ if annotation is NoReturn or annotation is Never:
+ exc = TypeCheckError(f"{func_name}() was declared never to yield but it did")
+ if memo.config.typecheck_fail_callback:
+ memo.config.typecheck_fail_callback(exc, memo)
+ else:
+ raise exc
+
+ try:
+ check_type_internal(yieldval, annotation, memo)
+ except TypeCheckError as exc:
+ qualname = qualified_name(yieldval, add_class_prefix=True)
+ exc.append_path_element(f"the yielded value ({qualname})")
+ if memo.config.typecheck_fail_callback:
+ memo.config.typecheck_fail_callback(exc, memo)
+ else:
+ raise
+
+ return yieldval
+
+
+def check_variable_assignment(
+ value: Any, targets: Sequence[list[tuple[str, Any]]], memo: TypeCheckMemo
+) -> Any:
+ if _suppression.type_checks_suppressed:
+ return value
+
+ value_to_return = value
+ for target in targets:
+ star_variable_index = next(
+ (i for i, (varname, _) in enumerate(target) if varname.startswith("*")),
+ None,
+ )
+ if star_variable_index is not None:
+ value_to_return = list(value)
+ remaining_vars = len(target) - 1 - star_variable_index
+ end_index = len(value_to_return) - remaining_vars
+ values_to_check = (
+ value_to_return[:star_variable_index]
+ + [value_to_return[star_variable_index:end_index]]
+ + value_to_return[end_index:]
+ )
+ elif len(target) > 1:
+ values_to_check = value_to_return = []
+ iterator = iter(value)
+ for _ in target:
+ try:
+ values_to_check.append(next(iterator))
+ except StopIteration:
+ raise ValueError(
+ f"not enough values to unpack (expected {len(target)}, got "
+ f"{len(values_to_check)})"
+ ) from None
+
+ else:
+ values_to_check = [value]
+
+ for val, (varname, annotation) in zip(values_to_check, target):
+ try:
+ check_type_internal(val, annotation, memo)
+ except TypeCheckError as exc:
+ qualname = qualified_name(val, add_class_prefix=True)
+ exc.append_path_element(f"value assigned to {varname} ({qualname})")
+ if memo.config.typecheck_fail_callback:
+ memo.config.typecheck_fail_callback(exc, memo)
+ else:
+ raise
+
+ return value_to_return
+
+
+def warn_on_error(exc: TypeCheckError, memo: TypeCheckMemo) -> None:
+ """
+ Emit a warning on a type mismatch.
+
+ This is intended to be used as an error handler in
+ :attr:`TypeCheckConfiguration.typecheck_fail_callback`.
+
+ """
+ warnings.warn(TypeCheckWarning(str(exc)), stacklevel=get_stacklevel())
diff --git a/python/code/typeguard/_importhook.py b/python/code/typeguard/_importhook.py
new file mode 100644
index 00000000..0d1c6274
--- /dev/null
+++ b/python/code/typeguard/_importhook.py
@@ -0,0 +1,213 @@
+from __future__ import annotations
+
+import ast
+import sys
+import types
+from collections.abc import Callable, Iterable, Sequence
+from importlib.abc import MetaPathFinder
+from importlib.machinery import ModuleSpec, SourceFileLoader
+from importlib.util import cache_from_source, decode_source
+from inspect import isclass
+from os import PathLike
+from types import CodeType, ModuleType, TracebackType
+from typing import TypeVar
+from unittest.mock import patch
+
+from ._config import global_config
+from ._transformer import TypeguardTransformer
+
+if sys.version_info >= (3, 12):
+ from collections.abc import Buffer
+else:
+ from typing_extensions import Buffer
+
+if sys.version_info >= (3, 11):
+ from typing import ParamSpec
+else:
+ from typing_extensions import ParamSpec
+
+if sys.version_info >= (3, 10):
+ from importlib.metadata import PackageNotFoundError, version
+else:
+ from importlib_metadata import PackageNotFoundError, version
+
+try:
+ OPTIMIZATION = "typeguard" + "".join(version("typeguard").split(".")[:3])
+except PackageNotFoundError:
+ OPTIMIZATION = "typeguard"
+
+P = ParamSpec("P")
+T = TypeVar("T")
+
+
+# The name of this function is magical
+def _call_with_frames_removed(
+ f: Callable[P, T], *args: P.args, **kwargs: P.kwargs
+) -> T:
+ return f(*args, **kwargs)
+
+
+def optimized_cache_from_source(path: str, debug_override: bool | None = None) -> str:
+ return cache_from_source(path, debug_override, optimization=OPTIMIZATION)
+
+
+class TypeguardLoader(SourceFileLoader):
+ @staticmethod
+ def source_to_code(
+ data: Buffer | str | ast.Module | ast.Expression | ast.Interactive,
+ path: Buffer | str | PathLike[str] = "",
+ ) -> CodeType:
+ if isinstance(data, (ast.Module, ast.Expression, ast.Interactive)):
+ tree = data
+ else:
+ if isinstance(data, str):
+ source = data
+ else:
+ source = decode_source(data)
+
+ tree = _call_with_frames_removed(
+ ast.parse,
+ source,
+ path,
+ "exec",
+ )
+
+ tree = TypeguardTransformer().visit(tree)
+ ast.fix_missing_locations(tree)
+
+ if global_config.debug_instrumentation and sys.version_info >= (3, 9):
+ print(
+ f"Source code of {path!r} after instrumentation:\n"
+ "----------------------------------------------",
+ file=sys.stderr,
+ )
+ print(ast.unparse(tree), file=sys.stderr)
+ print("----------------------------------------------", file=sys.stderr)
+
+ return _call_with_frames_removed(
+ compile, tree, path, "exec", 0, dont_inherit=True
+ )
+
+ def exec_module(self, module: ModuleType) -> None:
+ # Use a custom optimization marker – the import lock should make this monkey
+ # patch safe
+ with patch(
+ "importlib._bootstrap_external.cache_from_source",
+ optimized_cache_from_source,
+ ):
+ super().exec_module(module)
+
+
+class TypeguardFinder(MetaPathFinder):
+ """
+ Wraps another path finder and instruments the module with
+ :func:`@typechecked ` if :meth:`should_instrument` returns
+ ``True``.
+
+ Should not be used directly, but rather via :func:`~.install_import_hook`.
+
+ .. versionadded:: 2.6
+ """
+
+ def __init__(self, packages: list[str] | None, original_pathfinder: MetaPathFinder):
+ self.packages = packages
+ self._original_pathfinder = original_pathfinder
+
+ def find_spec(
+ self,
+ fullname: str,
+ path: Sequence[str] | None,
+ target: types.ModuleType | None = None,
+ ) -> ModuleSpec | None:
+ if self.should_instrument(fullname):
+ spec = self._original_pathfinder.find_spec(fullname, path, target)
+ if spec is not None and isinstance(spec.loader, SourceFileLoader):
+ spec.loader = TypeguardLoader(spec.loader.name, spec.loader.path)
+ return spec
+
+ return None
+
+ def should_instrument(self, module_name: str) -> bool:
+ """
+ Determine whether the module with the given name should be instrumented.
+
+ :param module_name: full name of the module that is about to be imported (e.g.
+ ``xyz.abc``)
+
+ """
+ if self.packages is None:
+ return True
+
+ for package in self.packages:
+ if module_name == package or module_name.startswith(package + "."):
+ return True
+
+ return False
+
+
+class ImportHookManager:
+ """
+ A handle that can be used to uninstall the Typeguard import hook.
+ """
+
+ def __init__(self, hook: MetaPathFinder):
+ self.hook = hook
+
+ def __enter__(self) -> None:
+ pass
+
+ def __exit__(
+ self,
+ exc_type: type[BaseException],
+ exc_val: BaseException,
+ exc_tb: TracebackType,
+ ) -> None:
+ self.uninstall()
+
+ def uninstall(self) -> None:
+ """Uninstall the import hook."""
+ try:
+ sys.meta_path.remove(self.hook)
+ except ValueError:
+ pass # already removed
+
+
+def install_import_hook(
+ packages: Iterable[str] | None = None,
+ *,
+ cls: type[TypeguardFinder] = TypeguardFinder,
+) -> ImportHookManager:
+ """
+ Install an import hook that instruments functions for automatic type checking.
+
+ This only affects modules loaded **after** this hook has been installed.
+
+ :param packages: an iterable of package names to instrument, or ``None`` to
+ instrument all packages
+ :param cls: a custom meta path finder class
+ :return: a context manager that uninstalls the hook on exit (or when you call
+ ``.uninstall()``)
+
+ .. versionadded:: 2.6
+
+ """
+ if packages is None:
+ target_packages: list[str] | None = None
+ elif isinstance(packages, str):
+ target_packages = [packages]
+ else:
+ target_packages = list(packages)
+
+ for finder in sys.meta_path:
+ if (
+ isclass(finder)
+ and finder.__name__ == "PathFinder"
+ and hasattr(finder, "find_spec")
+ ):
+ break
+ else:
+ raise RuntimeError("Cannot find a PathFinder in sys.meta_path")
+
+ hook = cls(target_packages, finder)
+ sys.meta_path.insert(0, hook)
+ return ImportHookManager(hook)
diff --git a/python/code/typeguard/_memo.py b/python/code/typeguard/_memo.py
new file mode 100644
index 00000000..4c97588c
--- /dev/null
+++ b/python/code/typeguard/_memo.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from typing import Any
+
+from ._config import TypeCheckConfiguration, global_config
+
+
+class TypeCheckMemo:
+ """
+ Contains information necessary for type checkers to do their work.
+
+ .. attribute:: globals
+ :type: dict[str, Any]
+
+ Dictionary of global variables to use for resolving forward references.
+
+ .. attribute:: locals
+ :type: dict[str, Any]
+
+ Dictionary of local variables to use for resolving forward references.
+
+ .. attribute:: self_type
+ :type: type | None
+
+ When running type checks within an instance method or class method, this is the
+ class object that the first argument (usually named ``self`` or ``cls``) refers
+ to.
+
+ .. attribute:: config
+ :type: TypeCheckConfiguration
+
+ Contains the configuration for a particular set of type checking operations.
+ """
+
+ __slots__ = "globals", "locals", "self_type", "config"
+
+ def __init__(
+ self,
+ globals: dict[str, Any],
+ locals: dict[str, Any],
+ *,
+ self_type: type | None = None,
+ config: TypeCheckConfiguration = global_config,
+ ):
+ self.globals = globals
+ self.locals = locals
+ self.self_type = self_type
+ self.config = config
diff --git a/python/code/typeguard/_pytest_plugin.py b/python/code/typeguard/_pytest_plugin.py
new file mode 100644
index 00000000..7b2f494e
--- /dev/null
+++ b/python/code/typeguard/_pytest_plugin.py
@@ -0,0 +1,127 @@
+from __future__ import annotations
+
+import sys
+import warnings
+from typing import TYPE_CHECKING, Any, Literal
+
+from typeguard._config import CollectionCheckStrategy, ForwardRefPolicy, global_config
+from typeguard._exceptions import InstrumentationWarning
+from typeguard._importhook import install_import_hook
+from typeguard._utils import qualified_name, resolve_reference
+
+if TYPE_CHECKING:
+ from pytest import Config, Parser
+
+
+def pytest_addoption(parser: Parser) -> None:
+ def add_ini_option(
+ opt_type: (
+ Literal["string", "paths", "pathlist", "args", "linelist", "bool"] | None
+ ),
+ ) -> None:
+ parser.addini(
+ group.options[-1].names()[0][2:],
+ group.options[-1].attrs()["help"],
+ opt_type,
+ )
+
+ group = parser.getgroup("typeguard")
+ group.addoption(
+ "--typeguard-packages",
+ action="store",
+ help="comma separated name list of packages and modules to instrument for "
+ "type checking, or :all: to instrument all modules loaded after typeguard",
+ )
+ add_ini_option("linelist")
+
+ group.addoption(
+ "--typeguard-debug-instrumentation",
+ action="store_true",
+ help="print all instrumented code to stderr",
+ )
+ add_ini_option("bool")
+
+ group.addoption(
+ "--typeguard-typecheck-fail-callback",
+ action="store",
+ help=(
+ "a module:varname (e.g. typeguard:warn_on_error) reference to a function "
+ "that is called (with the exception, and memo object as arguments) to "
+ "handle a TypeCheckError"
+ ),
+ )
+ add_ini_option("string")
+
+ group.addoption(
+ "--typeguard-forward-ref-policy",
+ action="store",
+ choices=list(ForwardRefPolicy.__members__),
+ help=(
+ "determines how to deal with unresolveable forward references in type "
+ "annotations"
+ ),
+ )
+ add_ini_option("string")
+
+ group.addoption(
+ "--typeguard-collection-check-strategy",
+ action="store",
+ choices=list(CollectionCheckStrategy.__members__),
+ help="determines how thoroughly to check collections (list, dict, etc)",
+ )
+ add_ini_option("string")
+
+
+def pytest_configure(config: Config) -> None:
+ def getoption(name: str) -> Any:
+ return config.getoption(name.replace("-", "_")) or config.getini(name)
+
+ packages: list[str] | None = []
+ if packages_option := config.getoption("typeguard_packages"):
+ packages = [pkg.strip() for pkg in packages_option.split(",")]
+ elif packages_ini := config.getini("typeguard-packages"):
+ packages = packages_ini
+
+ if packages:
+ if packages == [":all:"]:
+ packages = None
+ else:
+ already_imported_packages = sorted(
+ package for package in packages if package in sys.modules
+ )
+ if already_imported_packages:
+ warnings.warn(
+ f"typeguard cannot check these packages because they are already "
+ f"imported: {', '.join(already_imported_packages)}",
+ InstrumentationWarning,
+ stacklevel=1,
+ )
+
+ install_import_hook(packages=packages)
+
+ debug_option = getoption("typeguard-debug-instrumentation")
+ if debug_option:
+ global_config.debug_instrumentation = True
+
+ fail_callback_option = getoption("typeguard-typecheck-fail-callback")
+ if fail_callback_option:
+ callback = resolve_reference(fail_callback_option)
+ if not callable(callback):
+ raise TypeError(
+ f"{fail_callback_option} ({qualified_name(callback.__class__)}) is not "
+ f"a callable"
+ )
+
+ global_config.typecheck_fail_callback = callback
+
+ forward_ref_policy_option = getoption("typeguard-forward-ref-policy")
+ if forward_ref_policy_option:
+ forward_ref_policy = ForwardRefPolicy.__members__[forward_ref_policy_option]
+ global_config.forward_ref_policy = forward_ref_policy
+
+ collection_check_strategy_option = getoption("typeguard-collection-check-strategy")
+ if collection_check_strategy_option:
+ collection_check_strategy = CollectionCheckStrategy.__members__[
+ collection_check_strategy_option
+ ]
+ global_config.collection_check_strategy = collection_check_strategy
diff --git a/python/code/typeguard/_suppression.py b/python/code/typeguard/_suppression.py
new file mode 100644
index 00000000..bbbfbfbe
--- /dev/null
+++ b/python/code/typeguard/_suppression.py
@@ -0,0 +1,86 @@
+from __future__ import annotations
+
+import sys
+from collections.abc import Callable, Generator
+from contextlib import contextmanager
+from functools import update_wrapper
+from threading import Lock
+from typing import ContextManager, TypeVar, overload
+
+if sys.version_info >= (3, 10):
+ from typing import ParamSpec
+else:
+ from typing_extensions import ParamSpec
+
+P = ParamSpec("P")
+T = TypeVar("T")
+
+type_checks_suppressed = 0
+type_checks_suppress_lock = Lock()
+
+
+@overload
+def suppress_type_checks(func: Callable[P, T]) -> Callable[P, T]: ...
+
+
+@overload
+def suppress_type_checks() -> ContextManager[None]: ...
+
+
+def suppress_type_checks(
+ func: Callable[P, T] | None = None,
+) -> Callable[P, T] | ContextManager[None]:
+ """
+ Temporarily suppress all type checking.
+
+ This function has two operating modes, based on how it's used:
+
+ #. as a context manager (``with suppress_type_checks(): ...``)
+ #. as a decorator (``@suppress_type_checks``)
+
+ When used as a context manager, :func:`check_type` and any automatically
+ instrumented functions skip the actual type checking. These context managers can be
+ nested.
+
+ When used as a decorator, all type checking is suppressed while the function is
+ running.
+
+ Type checking will resume once no more context managers are active and no decorated
+ functions are running.
+
+ Both operating modes are thread-safe.
+
+ """
+
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
+ global type_checks_suppressed
+
+ with type_checks_suppress_lock:
+ type_checks_suppressed += 1
+
+ assert func is not None
+ try:
+ return func(*args, **kwargs)
+ finally:
+ with type_checks_suppress_lock:
+ type_checks_suppressed -= 1
+
+ def cm() -> Generator[None, None, None]:
+ global type_checks_suppressed
+
+ with type_checks_suppress_lock:
+ type_checks_suppressed += 1
+
+ try:
+ yield
+ finally:
+ with type_checks_suppress_lock:
+ type_checks_suppressed -= 1
+
+ if func is None:
+ # Context manager mode
+ return contextmanager(cm)()
+ else:
+ # Decorator mode
+ update_wrapper(wrapper, func)
+ return wrapper
diff --git a/python/code/typeguard/_transformer.py b/python/code/typeguard/_transformer.py
new file mode 100644
index 00000000..7b6dda85
--- /dev/null
+++ b/python/code/typeguard/_transformer.py
@@ -0,0 +1,1228 @@
+from __future__ import annotations
+
+import ast
+import builtins
+import sys
+import typing
+from ast import (
+ AST,
+ Add,
+ AnnAssign,
+ Assign,
+ AsyncFunctionDef,
+ Attribute,
+ AugAssign,
+ BinOp,
+ BitAnd,
+ BitOr,
+ BitXor,
+ Call,
+ ClassDef,
+ Constant,
+ Dict,
+ Div,
+ Expr,
+ Expression,
+ FloorDiv,
+ FunctionDef,
+ If,
+ Import,
+ ImportFrom,
+ List,
+ Load,
+ LShift,
+ MatMult,
+ Mod,
+ Module,
+ Mult,
+ Name,
+ NamedExpr,
+ NodeTransformer,
+ NodeVisitor,
+ Pass,
+ Pow,
+ Return,
+ RShift,
+ Starred,
+ Store,
+ Sub,
+ Subscript,
+ Tuple,
+ Yield,
+ YieldFrom,
+ alias,
+ copy_location,
+ expr,
+ fix_missing_locations,
+ keyword,
+ walk,
+)
+from collections import defaultdict
+from collections.abc import Generator, Sequence
+from contextlib import contextmanager
+from copy import deepcopy
+from dataclasses import dataclass, field
+from typing import Any, ClassVar, cast, overload
+
+generator_names = (
+ "typing.Generator",
+ "collections.abc.Generator",
+ "typing.Iterator",
+ "collections.abc.Iterator",
+ "typing.Iterable",
+ "collections.abc.Iterable",
+ "typing.AsyncIterator",
+ "collections.abc.AsyncIterator",
+ "typing.AsyncIterable",
+ "collections.abc.AsyncIterable",
+ "typing.AsyncGenerator",
+ "collections.abc.AsyncGenerator",
+)
+anytype_names = (
+ "typing.Any",
+ "typing_extensions.Any",
+)
+literal_names = (
+ "typing.Literal",
+ "typing_extensions.Literal",
+)
+annotated_names = (
+ "typing.Annotated",
+ "typing_extensions.Annotated",
+)
+ignore_decorators = (
+ "typing.no_type_check",
+ "typeguard.typeguard_ignore",
+)
+aug_assign_functions = {
+ Add: "iadd",
+ Sub: "isub",
+ Mult: "imul",
+ MatMult: "imatmul",
+ Div: "itruediv",
+ FloorDiv: "ifloordiv",
+ Mod: "imod",
+ Pow: "ipow",
+ LShift: "ilshift",
+ RShift: "irshift",
+ BitAnd: "iand",
+ BitXor: "ixor",
+ BitOr: "ior",
+}
+
+
+@dataclass
+class TransformMemo:
+ node: Module | ClassDef | FunctionDef | AsyncFunctionDef | None
+ parent: TransformMemo | None
+ path: tuple[str, ...]
+ joined_path: Constant = field(init=False)
+ return_annotation: expr | None = None
+ yield_annotation: expr | None = None
+ send_annotation: expr | None = None
+ is_async: bool = False
+ local_names: set[str] = field(init=False, default_factory=set)
+ imported_names: dict[str, str] = field(init=False, default_factory=dict)
+ ignored_names: set[str] = field(init=False, default_factory=set)
+ load_names: defaultdict[str, dict[str, Name]] = field(
+ init=False, default_factory=lambda: defaultdict(dict)
+ )
+ has_yield_expressions: bool = field(init=False, default=False)
+ has_return_expressions: bool = field(init=False, default=False)
+ memo_var_name: Name | None = field(init=False, default=None)
+ should_instrument: bool = field(init=False, default=True)
+ variable_annotations: dict[str, expr] = field(init=False, default_factory=dict)
+ configuration_overrides: dict[str, Any] = field(init=False, default_factory=dict)
+ code_inject_index: int = field(init=False, default=0)
+
+ def __post_init__(self) -> None:
+ elements: list[str] = []
+ memo = self
+ while isinstance(memo.node, (ClassDef, FunctionDef, AsyncFunctionDef)):
+ elements.insert(0, memo.node.name)
+ if not memo.parent:
+ break
+
+ memo = memo.parent
+ if isinstance(memo.node, (FunctionDef, AsyncFunctionDef)):
+ elements.insert(0, "")
+
+ self.joined_path = Constant(".".join(elements))
+
+ # Figure out where to insert instrumentation code
+ if self.node:
+ for index, child in enumerate(self.node.body):
+ if isinstance(child, ImportFrom) and child.module == "__future__":
+ # (module only) __future__ imports must come first
+ continue
+ elif (
+ isinstance(child, Expr)
+ and isinstance(child.value, Constant)
+ and isinstance(child.value.value, str)
+ ):
+ continue # docstring
+
+ self.code_inject_index = index
+ break
+
+ def get_unused_name(self, name: str) -> str:
+ memo: TransformMemo | None = self
+ while memo is not None:
+ if name in memo.local_names:
+ memo = self
+ name += "_"
+ else:
+ memo = memo.parent
+
+ self.local_names.add(name)
+ return name
+
+ def is_ignored_name(self, expression: expr | Expr | None) -> bool:
+ top_expression = (
+ expression.value if isinstance(expression, Expr) else expression
+ )
+
+ if isinstance(top_expression, Attribute) and isinstance(
+ top_expression.value, Name
+ ):
+ name = top_expression.value.id
+ elif isinstance(top_expression, Name):
+ name = top_expression.id
+ else:
+ return False
+
+ memo: TransformMemo | None = self
+ while memo is not None:
+ if name in memo.ignored_names:
+ return True
+
+ memo = memo.parent
+
+ return False
+
+ def get_memo_name(self) -> Name:
+ if not self.memo_var_name:
+ self.memo_var_name = Name(id="memo", ctx=Load())
+
+ return self.memo_var_name
+
+ def get_import(self, module: str, name: str) -> Name:
+ if module in self.load_names and name in self.load_names[module]:
+ return self.load_names[module][name]
+
+ qualified_name = f"{module}.{name}"
+ if name in self.imported_names and self.imported_names[name] == qualified_name:
+ return Name(id=name, ctx=Load())
+
+ alias = self.get_unused_name(name)
+ node = self.load_names[module][name] = Name(id=alias, ctx=Load())
+ self.imported_names[name] = qualified_name
+ return node
+
+ def insert_imports(self, node: Module | FunctionDef | AsyncFunctionDef) -> None:
+ """Insert imports needed by injected code."""
+ if not self.load_names:
+ return
+
+ # Insert imports after any "from __future__ ..." imports and any docstring
+ for modulename, names in self.load_names.items():
+ aliases = [
+ alias(orig_name, new_name.id if orig_name != new_name.id else None)
+ for orig_name, new_name in sorted(names.items())
+ ]
+ node.body.insert(self.code_inject_index, ImportFrom(modulename, aliases, 0))
+
+ def name_matches(self, expression: expr | Expr | None, *names: str) -> bool:
+ if expression is None:
+ return False
+
+ path: list[str] = []
+ top_expression = (
+ expression.value if isinstance(expression, Expr) else expression
+ )
+
+ if isinstance(top_expression, Subscript):
+ top_expression = top_expression.value
+ elif isinstance(top_expression, Call):
+ top_expression = top_expression.func
+
+ while isinstance(top_expression, Attribute):
+ path.insert(0, top_expression.attr)
+ top_expression = top_expression.value
+
+ if not isinstance(top_expression, Name):
+ return False
+
+ if top_expression.id in self.imported_names:
+ translated = self.imported_names[top_expression.id]
+ elif hasattr(builtins, top_expression.id):
+ translated = "builtins." + top_expression.id
+ else:
+ translated = top_expression.id
+
+ path.insert(0, translated)
+ joined_path = ".".join(path)
+ if joined_path in names:
+ return True
+ elif self.parent:
+ return self.parent.name_matches(expression, *names)
+ else:
+ return False
+
+ def get_config_keywords(self) -> list[keyword]:
+ if self.parent and isinstance(self.parent.node, ClassDef):
+ overrides = self.parent.configuration_overrides.copy()
+ else:
+ overrides = {}
+
+ overrides.update(self.configuration_overrides)
+ return [keyword(key, value) for key, value in overrides.items()]
+
+
+class NameCollector(NodeVisitor):
+ def __init__(self) -> None:
+ self.names: set[str] = set()
+
+ def visit_Import(self, node: Import) -> None:
+ for name in node.names:
+ self.names.add(name.asname or name.name)
+
+ def visit_ImportFrom(self, node: ImportFrom) -> None:
+ for name in node.names:
+ self.names.add(name.asname or name.name)
+
+ def visit_Assign(self, node: Assign) -> None:
+ for target in node.targets:
+ if isinstance(target, Name):
+ self.names.add(target.id)
+
+ def visit_NamedExpr(self, node: NamedExpr) -> Any:
+ if isinstance(node.target, Name):
+ self.names.add(node.target.id)
+
+ def visit_FunctionDef(self, node: FunctionDef) -> None:
+ pass
+
+ def visit_ClassDef(self, node: ClassDef) -> None:
+ pass
+
+
+class GeneratorDetector(NodeVisitor):
+ """Detects if a function node is a generator function."""
+
+ contains_yields: bool = False
+ in_root_function: bool = False
+
+ def visit_Yield(self, node: Yield) -> Any:
+ self.contains_yields = True
+
+ def visit_YieldFrom(self, node: YieldFrom) -> Any:
+ self.contains_yields = True
+
+ def visit_ClassDef(self, node: ClassDef) -> Any:
+ pass
+
+ def visit_FunctionDef(self, node: FunctionDef | AsyncFunctionDef) -> Any:
+ if not self.in_root_function:
+ self.in_root_function = True
+ self.generic_visit(node)
+ self.in_root_function = False
+
+ def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> Any:
+ self.visit_FunctionDef(node)
+
+
+class AnnotationTransformer(NodeTransformer):
+ type_substitutions: ClassVar[dict[str, tuple[str, str]]] = {
+ "builtins.dict": ("typing", "Dict"),
+ "builtins.list": ("typing", "List"),
+ "builtins.tuple": ("typing", "Tuple"),
+ "builtins.set": ("typing", "Set"),
+ "builtins.frozenset": ("typing", "FrozenSet"),
+ }
+
+ def __init__(self, transformer: TypeguardTransformer):
+ self.transformer = transformer
+ self._memo = transformer._memo
+ self._level = 0
+
+ def visit(self, node: AST) -> Any:
+ # Don't process Literals
+ if isinstance(node, expr) and self._memo.name_matches(node, *literal_names):
+ return node
+
+ self._level += 1
+ new_node = super().visit(node)
+ self._level -= 1
+
+ if isinstance(new_node, Expression) and not hasattr(new_node, "body"):
+ return None
+
+ # Return None if this new node matches a variation of typing.Any
+ if (
+ self._level == 0
+ and isinstance(new_node, expr)
+ and self._memo.name_matches(new_node, *anytype_names)
+ ):
+ return None
+
+ return new_node
+
+ def visit_BinOp(self, node: BinOp) -> Any:
+ self.generic_visit(node)
+
+ if isinstance(node.op, BitOr):
+ # If either branch of the BinOp has been transformed to `None`, it means
+ # that a type in the union was ignored, so the entire annotation should be
+ # ignored
+ if not hasattr(node, "left") or not hasattr(node, "right"):
+ return None
+
+ # Return Any if either side is Any
+ if self._memo.name_matches(node.left, *anytype_names):
+ return node.left
+ elif self._memo.name_matches(node.right, *anytype_names):
+ return node.right
+
+ # Turn union types to typing.Union constructs on Python 3.9
+ if sys.version_info < (3, 10):
+ union_name = self.transformer._get_import("typing", "Union")
+ return Subscript(
+ value=union_name,
+ slice=Tuple(elts=[node.left, node.right], ctx=Load()),
+ ctx=Load(),
+ )
+
+ return node
+
+ def visit_Attribute(self, node: Attribute) -> Any:
+ if self._memo.is_ignored_name(node):
+ return None
+
+ return node
+
+ def visit_Subscript(self, node: Subscript) -> Any:
+ if self._memo.is_ignored_name(node.value):
+ return None
+
+ # The subscript of typing(_extensions).Literal can be any arbitrary string, so
+ # don't try to evaluate it as code
+ if node.slice:
+ if isinstance(node.slice, Tuple):
+ if self._memo.name_matches(node.value, *annotated_names):
+ # Only treat the first argument to typing.Annotated as a potential
+ # forward reference
+ items = cast(
+ typing.List[expr],
+ [self.visit(node.slice.elts[0])] + node.slice.elts[1:],
+ )
+ else:
+ items = cast(
+ typing.List[expr],
+ [self.visit(item) for item in node.slice.elts],
+ )
+
+ # If this is a Union and any of the items is Any, erase the entire
+ # annotation
+ if self._memo.name_matches(node.value, "typing.Union") and any(
+ item is None
+ or (
+ isinstance(item, expr)
+ and self._memo.name_matches(item, *anytype_names)
+ )
+ for item in items
+ ):
+ return None
+
+ # If all items in the subscript were Any, erase the subscript entirely
+ if all(item is None for item in items):
+ return node.value
+
+ for index, item in enumerate(items):
+ if item is None:
+ items[index] = self.transformer._get_import("typing", "Any")
+
+ node.slice.elts = items
+ else:
+ self.generic_visit(node)
+
+ # If the transformer erased the slice entirely, just return the node
+ # value without the subscript (unless it's Optional, in which case erase
+ # the node entirely
+ if self._memo.name_matches(
+ node.value, "typing.Optional"
+ ) and not hasattr(node, "slice"):
+ return None
+ if sys.version_info >= (3, 9) and not hasattr(node, "slice"):
+ return node.value
+ elif sys.version_info < (3, 9) and not hasattr(node.slice, "value"):
+ return node.value
+
+ return node
+
+ def visit_Name(self, node: Name) -> Any:
+ if self._memo.is_ignored_name(node):
+ return None
+
+ return node
+
+ def visit_Call(self, node: Call) -> Any:
+ # Don't recurse into calls
+ return node
+
+ def visit_Constant(self, node: Constant) -> Any:
+ if isinstance(node.value, str):
+ expression = ast.parse(node.value, mode="eval")
+ new_node = self.visit(expression)
+ if new_node:
+ return copy_location(new_node.body, node)
+ else:
+ return None
+
+ return node
+
+
+class TypeguardTransformer(NodeTransformer):
+ def __init__(
+ self, target_path: Sequence[str] | None = None, target_lineno: int | None = None
+ ) -> None:
+ self._target_path = tuple(target_path) if target_path else None
+ self._memo = self._module_memo = TransformMemo(None, None, ())
+ self.names_used_in_annotations: set[str] = set()
+ self.target_node: FunctionDef | AsyncFunctionDef | None = None
+ self.target_lineno = target_lineno
+
+ def generic_visit(self, node: AST) -> AST:
+ has_non_empty_body_initially = bool(getattr(node, "body", None))
+ initial_type = type(node)
+
+ node = super().generic_visit(node)
+
+ if (
+ type(node) is initial_type
+ and has_non_empty_body_initially
+ and hasattr(node, "body")
+ and not node.body
+ ):
+ # If we have still the same node type after transformation
+ # but we've optimised it's body away, we add a `pass` statement.
+ node.body = [Pass()]
+
+ return node
+
+ @contextmanager
+ def _use_memo(
+ self, node: ClassDef | FunctionDef | AsyncFunctionDef
+ ) -> Generator[None, Any, None]:
+ new_memo = TransformMemo(node, self._memo, self._memo.path + (node.name,))
+ old_memo = self._memo
+ self._memo = new_memo
+
+ if isinstance(node, (FunctionDef, AsyncFunctionDef)):
+ new_memo.should_instrument = (
+ self._target_path is None or new_memo.path == self._target_path
+ )
+ if new_memo.should_instrument:
+ # Check if the function is a generator function
+ detector = GeneratorDetector()
+ detector.visit(node)
+
+ # Extract yield, send and return types where possible from a subscripted
+ # annotation like Generator[int, str, bool]
+ return_annotation = deepcopy(node.returns)
+ if detector.contains_yields and new_memo.name_matches(
+ return_annotation, *generator_names
+ ):
+ if isinstance(return_annotation, Subscript):
+ if isinstance(return_annotation.slice, Tuple):
+ items = return_annotation.slice.elts
+ else:
+ items = [return_annotation.slice]
+
+ if len(items) > 0:
+ new_memo.yield_annotation = self._convert_annotation(
+ items[0]
+ )
+
+ if len(items) > 1:
+ new_memo.send_annotation = self._convert_annotation(
+ items[1]
+ )
+
+ if len(items) > 2:
+ new_memo.return_annotation = self._convert_annotation(
+ items[2]
+ )
+ else:
+ new_memo.return_annotation = self._convert_annotation(
+ return_annotation
+ )
+
+ if isinstance(node, AsyncFunctionDef):
+ new_memo.is_async = True
+
+ yield
+ self._memo = old_memo
+
+ def _get_import(self, module: str, name: str) -> Name:
+ memo = self._memo if self._target_path else self._module_memo
+ return memo.get_import(module, name)
+
+ @overload
+ def _convert_annotation(self, annotation: None) -> None: ...
+
+ @overload
+ def _convert_annotation(self, annotation: expr) -> expr: ...
+
+ def _convert_annotation(self, annotation: expr | None) -> expr | None:
+ if annotation is None:
+ return None
+
+ # Convert PEP 604 unions (x | y) and generic built-in collections where
+ # necessary, and undo forward references
+ new_annotation = cast(expr, AnnotationTransformer(self).visit(annotation))
+ if isinstance(new_annotation, expr):
+ new_annotation = ast.copy_location(new_annotation, annotation)
+
+ # Store names used in the annotation
+ names = {node.id for node in walk(new_annotation) if isinstance(node, Name)}
+ self.names_used_in_annotations.update(names)
+
+ return new_annotation
+
+ def visit_Name(self, node: Name) -> Name:
+ self._memo.local_names.add(node.id)
+ return node
+
+ def visit_Module(self, node: Module) -> Module:
+ self._module_memo = self._memo = TransformMemo(node, None, ())
+ self.generic_visit(node)
+ self._module_memo.insert_imports(node)
+
+ fix_missing_locations(node)
+ return node
+
+ def visit_Import(self, node: Import) -> Import:
+ for name in node.names:
+ self._memo.local_names.add(name.asname or name.name)
+ self._memo.imported_names[name.asname or name.name] = name.name
+
+ return node
+
+ def visit_ImportFrom(self, node: ImportFrom) -> ImportFrom:
+ for name in node.names:
+ if name.name != "*":
+ alias = name.asname or name.name
+ self._memo.local_names.add(alias)
+ self._memo.imported_names[alias] = f"{node.module}.{name.name}"
+
+ return node
+
+ def visit_ClassDef(self, node: ClassDef) -> ClassDef | None:
+ self._memo.local_names.add(node.name)
+
+ # Eliminate top level classes not belonging to the target path
+ if (
+ self._target_path is not None
+ and not self._memo.path
+ and node.name != self._target_path[0]
+ ):
+ return None
+
+ with self._use_memo(node):
+ for decorator in node.decorator_list.copy():
+ if self._memo.name_matches(decorator, "typeguard.typechecked"):
+ # Remove the decorator to prevent duplicate instrumentation
+ node.decorator_list.remove(decorator)
+
+ # Store any configuration overrides
+ if isinstance(decorator, Call) and decorator.keywords:
+ self._memo.configuration_overrides.update(
+ {kw.arg: kw.value for kw in decorator.keywords if kw.arg}
+ )
+
+ self.generic_visit(node)
+ return node
+
+ def visit_FunctionDef(
+ self, node: FunctionDef | AsyncFunctionDef
+ ) -> FunctionDef | AsyncFunctionDef | None:
+ """
+ Injects type checks for function arguments, and for a return of None if the
+ function is annotated to return something else than Any or None, and the body
+ ends without an explicit "return".
+
+ """
+ self._memo.local_names.add(node.name)
+
+ # Eliminate top level functions not belonging to the target path
+ if (
+ self._target_path is not None
+ and not self._memo.path
+ and node.name != self._target_path[0]
+ ):
+ return None
+
+ # Skip instrumentation if we're instrumenting the whole module and the function
+ # contains either @no_type_check or @typeguard_ignore
+ if self._target_path is None:
+ for decorator in node.decorator_list:
+ if self._memo.name_matches(decorator, *ignore_decorators):
+ return node
+
+ with self._use_memo(node):
+ arg_annotations: dict[str, Any] = {}
+ if self._target_path is None or self._memo.path == self._target_path:
+ # Find line number we're supposed to match against
+ if node.decorator_list:
+ first_lineno = node.decorator_list[0].lineno
+ else:
+ first_lineno = node.lineno
+
+ for decorator in node.decorator_list.copy():
+ if self._memo.name_matches(decorator, "typing.overload"):
+ # Remove overloads entirely
+ return None
+ elif self._memo.name_matches(decorator, "typeguard.typechecked"):
+ # Remove the decorator to prevent duplicate instrumentation
+ node.decorator_list.remove(decorator)
+
+ # Store any configuration overrides
+ if isinstance(decorator, Call) and decorator.keywords:
+ self._memo.configuration_overrides = {
+ kw.arg: kw.value for kw in decorator.keywords if kw.arg
+ }
+
+ if self.target_lineno == first_lineno:
+ assert self.target_node is None
+ self.target_node = node
+ if node.decorator_list:
+ self.target_lineno = node.decorator_list[0].lineno
+ else:
+ self.target_lineno = node.lineno
+
+ all_args = node.args.posonlyargs + node.args.args + node.args.kwonlyargs
+
+ # Ensure that any type shadowed by the positional or keyword-only
+ # argument names are ignored in this function
+ for arg in all_args:
+ self._memo.ignored_names.add(arg.arg)
+
+ # Ensure that any type shadowed by the variable positional argument name
+ # (e.g. "args" in *args) is ignored this function
+ if node.args.vararg:
+ self._memo.ignored_names.add(node.args.vararg.arg)
+
+ # Ensure that any type shadowed by the variable keywrod argument name
+ # (e.g. "kwargs" in *kwargs) is ignored this function
+ if node.args.kwarg:
+ self._memo.ignored_names.add(node.args.kwarg.arg)
+
+ for arg in all_args:
+ annotation = self._convert_annotation(deepcopy(arg.annotation))
+ if annotation:
+ arg_annotations[arg.arg] = annotation
+
+ if node.args.vararg:
+ annotation_ = self._convert_annotation(node.args.vararg.annotation)
+ if annotation_:
+ container = Name("tuple", ctx=Load())
+ subscript_slice = Tuple(
+ [
+ annotation_,
+ Constant(Ellipsis),
+ ],
+ ctx=Load(),
+ )
+ arg_annotations[node.args.vararg.arg] = Subscript(
+ container, subscript_slice, ctx=Load()
+ )
+
+ if node.args.kwarg:
+ annotation_ = self._convert_annotation(node.args.kwarg.annotation)
+ if annotation_:
+ container = Name("dict", ctx=Load())
+ subscript_slice = Tuple(
+ [
+ Name("str", ctx=Load()),
+ annotation_,
+ ],
+ ctx=Load(),
+ )
+ arg_annotations[node.args.kwarg.arg] = Subscript(
+ container, subscript_slice, ctx=Load()
+ )
+
+ if arg_annotations:
+ self._memo.variable_annotations.update(arg_annotations)
+
+ self.generic_visit(node)
+
+ if arg_annotations:
+ annotations_dict = Dict(
+ keys=[Constant(key) for key in arg_annotations.keys()],
+ values=[
+ Tuple([Name(key, ctx=Load()), annotation], ctx=Load())
+ for key, annotation in arg_annotations.items()
+ ],
+ )
+ func_name = self._get_import(
+ "typeguard._functions", "check_argument_types"
+ )
+ args = [
+ self._memo.joined_path,
+ annotations_dict,
+ self._memo.get_memo_name(),
+ ]
+ node.body.insert(
+ self._memo.code_inject_index, Expr(Call(func_name, args, []))
+ )
+
+ # Add a checked "return None" to the end if there's no explicit return
+ # Skip if the return annotation is None or Any
+ if (
+ self._memo.return_annotation
+ and (not self._memo.is_async or not self._memo.has_yield_expressions)
+ and not isinstance(node.body[-1], Return)
+ and (
+ not isinstance(self._memo.return_annotation, Constant)
+ or self._memo.return_annotation.value is not None
+ )
+ ):
+ func_name = self._get_import(
+ "typeguard._functions", "check_return_type"
+ )
+ return_node = Return(
+ Call(
+ func_name,
+ [
+ self._memo.joined_path,
+ Constant(None),
+ self._memo.return_annotation,
+ self._memo.get_memo_name(),
+ ],
+ [],
+ )
+ )
+
+ # Replace a placeholder "pass" at the end
+ if isinstance(node.body[-1], Pass):
+ copy_location(return_node, node.body[-1])
+ del node.body[-1]
+
+ node.body.append(return_node)
+
+ # Insert code to create the call memo, if it was ever needed for this
+ # function
+ if self._memo.memo_var_name:
+ memo_kwargs: dict[str, Any] = {}
+ if self._memo.parent and isinstance(self._memo.parent.node, ClassDef):
+ for decorator in node.decorator_list:
+ if (
+ isinstance(decorator, Name)
+ and decorator.id == "staticmethod"
+ ):
+ break
+ elif (
+ isinstance(decorator, Name)
+ and decorator.id == "classmethod"
+ ):
+ arglist = node.args.posonlyargs or node.args.args
+ memo_kwargs["self_type"] = Name(
+ id=arglist[0].arg, ctx=Load()
+ )
+ break
+ else:
+ if arglist := node.args.posonlyargs or node.args.args:
+ if node.name == "__new__":
+ memo_kwargs["self_type"] = Name(
+ id=arglist[0].arg, ctx=Load()
+ )
+ else:
+ memo_kwargs["self_type"] = Attribute(
+ Name(id=arglist[0].arg, ctx=Load()),
+ "__class__",
+ ctx=Load(),
+ )
+
+ # Construct the function reference
+ # Nested functions get special treatment: the function name is added
+ # to free variables (and the closure of the resulting function)
+ names: list[str] = [node.name]
+ memo = self._memo.parent
+ while memo:
+ if isinstance(memo.node, (FunctionDef, AsyncFunctionDef)):
+ # This is a nested function. Use the function name as-is.
+ del names[:-1]
+ break
+ elif not isinstance(memo.node, ClassDef):
+ break
+
+ names.insert(0, memo.node.name)
+ memo = memo.parent
+
+ config_keywords = self._memo.get_config_keywords()
+ if config_keywords:
+ memo_kwargs["config"] = Call(
+ self._get_import("dataclasses", "replace"),
+ [self._get_import("typeguard._config", "global_config")],
+ config_keywords,
+ )
+
+ self._memo.memo_var_name.id = self._memo.get_unused_name("memo")
+ memo_store_name = Name(id=self._memo.memo_var_name.id, ctx=Store())
+ globals_call = Call(Name(id="globals", ctx=Load()), [], [])
+ locals_call = Call(Name(id="locals", ctx=Load()), [], [])
+ memo_expr = Call(
+ self._get_import("typeguard", "TypeCheckMemo"),
+ [globals_call, locals_call],
+ [keyword(key, value) for key, value in memo_kwargs.items()],
+ )
+ node.body.insert(
+ self._memo.code_inject_index,
+ Assign([memo_store_name], memo_expr),
+ )
+
+ self._memo.insert_imports(node)
+
+ # Special case the __new__() method to create a local alias from the
+ # class name to the first argument (usually "cls")
+ if (
+ isinstance(node, FunctionDef)
+ and node.args
+ and self._memo.parent is not None
+ and isinstance(self._memo.parent.node, ClassDef)
+ and node.name == "__new__"
+ ):
+ first_args_expr = Name(node.args.args[0].arg, ctx=Load())
+ cls_name = Name(self._memo.parent.node.name, ctx=Store())
+ node.body.insert(
+ self._memo.code_inject_index,
+ Assign([cls_name], first_args_expr),
+ )
+
+ # Rmove any placeholder "pass" at the end
+ if isinstance(node.body[-1], Pass):
+ del node.body[-1]
+
+ return node
+
+ def visit_AsyncFunctionDef(
+ self, node: AsyncFunctionDef
+ ) -> FunctionDef | AsyncFunctionDef | None:
+ return self.visit_FunctionDef(node)
+
+ def visit_Return(self, node: Return) -> Return:
+ """This injects type checks into "return" statements."""
+ self.generic_visit(node)
+ if (
+ self._memo.return_annotation
+ and self._memo.should_instrument
+ and not self._memo.is_ignored_name(self._memo.return_annotation)
+ ):
+ func_name = self._get_import("typeguard._functions", "check_return_type")
+ old_node = node
+ retval = old_node.value or Constant(None)
+ node = Return(
+ Call(
+ func_name,
+ [
+ self._memo.joined_path,
+ retval,
+ self._memo.return_annotation,
+ self._memo.get_memo_name(),
+ ],
+ [],
+ )
+ )
+ copy_location(node, old_node)
+
+ return node
+
+ def visit_Yield(self, node: Yield) -> Yield | Call:
+ """
+ This injects type checks into "yield" expressions, checking both the yielded
+ value and the value sent back to the generator, when appropriate.
+
+ """
+ self._memo.has_yield_expressions = True
+ self.generic_visit(node)
+
+ if (
+ self._memo.yield_annotation
+ and self._memo.should_instrument
+ and not self._memo.is_ignored_name(self._memo.yield_annotation)
+ ):
+ func_name = self._get_import("typeguard._functions", "check_yield_type")
+ yieldval = node.value or Constant(None)
+ node.value = Call(
+ func_name,
+ [
+ self._memo.joined_path,
+ yieldval,
+ self._memo.yield_annotation,
+ self._memo.get_memo_name(),
+ ],
+ [],
+ )
+
+ if (
+ self._memo.send_annotation
+ and self._memo.should_instrument
+ and not self._memo.is_ignored_name(self._memo.send_annotation)
+ ):
+ func_name = self._get_import("typeguard._functions", "check_send_type")
+ old_node = node
+ call_node = Call(
+ func_name,
+ [
+ self._memo.joined_path,
+ old_node,
+ self._memo.send_annotation,
+ self._memo.get_memo_name(),
+ ],
+ [],
+ )
+ copy_location(call_node, old_node)
+ return call_node
+
+ return node
+
+ def visit_AnnAssign(self, node: AnnAssign) -> Any:
+ """
+ This injects a type check into a local variable annotation-assignment within a
+ function body.
+
+ """
+ self.generic_visit(node)
+
+ if (
+ isinstance(self._memo.node, (FunctionDef, AsyncFunctionDef))
+ and node.annotation
+ and isinstance(node.target, Name)
+ ):
+ self._memo.ignored_names.add(node.target.id)
+ annotation = self._convert_annotation(deepcopy(node.annotation))
+ if annotation:
+ self._memo.variable_annotations[node.target.id] = annotation
+ if node.value:
+ func_name = self._get_import(
+ "typeguard._functions", "check_variable_assignment"
+ )
+ targets_arg = List(
+ [
+ List(
+ [
+ Tuple(
+ [Constant(node.target.id), annotation],
+ ctx=Load(),
+ )
+ ],
+ ctx=Load(),
+ )
+ ],
+ ctx=Load(),
+ )
+ node.value = Call(
+ func_name,
+ [
+ node.value,
+ targets_arg,
+ self._memo.get_memo_name(),
+ ],
+ [],
+ )
+
+ return node
+
+ def visit_Assign(self, node: Assign) -> Any:
+ """
+ This injects a type check into a local variable assignment within a function
+ body. The variable must have been annotated earlier in the function body.
+
+ """
+ self.generic_visit(node)
+
+ # Only instrument function-local assignments
+ if isinstance(self._memo.node, (FunctionDef, AsyncFunctionDef)):
+ preliminary_targets: list[list[tuple[Constant, expr | None]]] = []
+ check_required = False
+ for target in node.targets:
+ elts: Sequence[expr]
+ if isinstance(target, Name):
+ elts = [target]
+ elif isinstance(target, Tuple):
+ elts = target.elts
+ else:
+ continue
+
+ annotations_: list[tuple[Constant, expr | None]] = []
+ for exp in elts:
+ prefix = ""
+ if isinstance(exp, Starred):
+ exp = exp.value
+ prefix = "*"
+
+ path: list[str] = []
+ while isinstance(exp, Attribute):
+ path.insert(0, exp.attr)
+ exp = exp.value
+
+ if isinstance(exp, Name):
+ if not path:
+ self._memo.ignored_names.add(exp.id)
+
+ path.insert(0, exp.id)
+ name = prefix + ".".join(path)
+ if len(path) == 1 and (
+ annotation := self._memo.variable_annotations.get(exp.id)
+ ):
+ annotations_.append((Constant(name), annotation))
+ check_required = True
+ else:
+ annotations_.append((Constant(name), None))
+
+ preliminary_targets.append(annotations_)
+
+ if check_required:
+ # Replace missing annotations with typing.Any
+ targets: list[list[tuple[Constant, expr]]] = []
+ for items in preliminary_targets:
+ target_list: list[tuple[Constant, expr]] = []
+ targets.append(target_list)
+ for key, expression in items:
+ if expression is None:
+ target_list.append((key, self._get_import("typing", "Any")))
+ else:
+ target_list.append((key, expression))
+
+ func_name = self._get_import(
+ "typeguard._functions", "check_variable_assignment"
+ )
+ targets_arg = List(
+ [
+ List(
+ [Tuple([name, ann], ctx=Load()) for name, ann in target],
+ ctx=Load(),
+ )
+ for target in targets
+ ],
+ ctx=Load(),
+ )
+ node.value = Call(
+ func_name,
+ [node.value, targets_arg, self._memo.get_memo_name()],
+ [],
+ )
+
+ return node
+
+ def visit_NamedExpr(self, node: NamedExpr) -> Any:
+ """This injects a type check into an assignment expression (a := foo())."""
+ self.generic_visit(node)
+
+ # Only instrument function-local assignments
+ if isinstance(self._memo.node, (FunctionDef, AsyncFunctionDef)) and isinstance(
+ node.target, Name
+ ):
+ self._memo.ignored_names.add(node.target.id)
+
+ # Bail out if no matching annotation is found
+ annotation = self._memo.variable_annotations.get(node.target.id)
+ if annotation is None:
+ return node
+
+ func_name = self._get_import(
+ "typeguard._functions", "check_variable_assignment"
+ )
+ node.value = Call(
+ func_name,
+ [
+ node.value,
+ List(
+ [
+ List(
+ [
+ Tuple(
+ [Constant(node.target.id), annotation],
+ ctx=Load(),
+ )
+ ],
+ ctx=Load(),
+ )
+ ],
+ ctx=Load(),
+ ),
+ self._memo.get_memo_name(),
+ ],
+ [],
+ )
+
+ return node
+
+ def visit_AugAssign(self, node: AugAssign) -> Any:
+ """
+ This injects a type check into an augmented assignment expression (a += 1).
+
+ """
+ self.generic_visit(node)
+
+ # Only instrument function-local assignments
+ if isinstance(self._memo.node, (FunctionDef, AsyncFunctionDef)) and isinstance(
+ node.target, Name
+ ):
+ # Bail out if no matching annotation is found
+ annotation = self._memo.variable_annotations.get(node.target.id)
+ if annotation is None:
+ return node
+
+ # Bail out if the operator is not found (newer Python version?)
+ try:
+ operator_func_name = aug_assign_functions[node.op.__class__]
+ except KeyError:
+ return node
+
+ operator_func = self._get_import("operator", operator_func_name)
+ operator_call = Call(
+ operator_func, [Name(node.target.id, ctx=Load()), node.value], []
+ )
+ targets_arg = List(
+ [
+ List(
+ [Tuple([Constant(node.target.id), annotation], ctx=Load())],
+ ctx=Load(),
+ )
+ ],
+ ctx=Load(),
+ )
+ check_call = Call(
+ self._get_import("typeguard._functions", "check_variable_assignment"),
+ [
+ operator_call,
+ targets_arg,
+ self._memo.get_memo_name(),
+ ],
+ [],
+ )
+ return Assign(targets=[node.target], value=check_call)
+
+ return node
+
+ def visit_If(self, node: If) -> Any:
+ """
+ This blocks names from being collected from a module-level
+ "if typing.TYPE_CHECKING:" block, so that they won't be type checked.
+
+ """
+ self.generic_visit(node)
+
+ if (
+ self._memo is self._module_memo
+ and isinstance(node.test, Name)
+ and self._memo.name_matches(node.test, "typing.TYPE_CHECKING")
+ ):
+ collector = NameCollector()
+ collector.visit(node)
+ self._memo.ignored_names.update(collector.names)
+
+ return node
diff --git a/python/code/typeguard/_union_transformer.py b/python/code/typeguard/_union_transformer.py
new file mode 100644
index 00000000..1c296d35
--- /dev/null
+++ b/python/code/typeguard/_union_transformer.py
@@ -0,0 +1,43 @@
+"""
+Transforms lazily evaluated PEP 604 unions into typing.Unions, for compatibility with
+Python versions older than 3.10.
+"""
+
+from __future__ import annotations
+
+from ast import (
+ BinOp,
+ BitOr,
+ Load,
+ Name,
+ NodeTransformer,
+ Subscript,
+ Tuple,
+ fix_missing_locations,
+ parse,
+)
+from types import CodeType
+from typing import Any
+
+
+class UnionTransformer(NodeTransformer):
+ def __init__(self, union_name: Name | None = None):
+ self.union_name = union_name or Name(id="Union", ctx=Load())
+
+ def visit_BinOp(self, node: BinOp) -> Any:
+ self.generic_visit(node)
+ if isinstance(node.op, BitOr):
+ return Subscript(
+ value=self.union_name,
+ slice=Tuple(elts=[node.left, node.right], ctx=Load()),
+ ctx=Load(),
+ )
+
+ return node
+
+
+def compile_type_hint(hint: str) -> CodeType:
+ parsed = parse(hint, "", "eval")
+ UnionTransformer().visit(parsed)
+ fix_missing_locations(parsed)
+ return compile(parsed, "", "eval", flags=0)
diff --git a/python/code/typeguard/_utils.py b/python/code/typeguard/_utils.py
new file mode 100644
index 00000000..72b9b861
--- /dev/null
+++ b/python/code/typeguard/_utils.py
@@ -0,0 +1,169 @@
+from __future__ import annotations
+
+import inspect
+import sys
+from importlib import import_module
+from inspect import currentframe
+from types import FrameType
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ ForwardRef,
+ Union,
+ cast,
+ final,
+ get_args,
+ get_origin,
+)
+
+if TYPE_CHECKING:
+ from ._memo import TypeCheckMemo
+
+if sys.version_info >= (3, 14):
+
+ def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any:
+ return forwardref.evaluate(
+ globals=memo.globals, locals=memo.locals, type_params=()
+ )
+elif sys.version_info >= (3, 13):
+
+ def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any:
+ return forwardref._evaluate(
+ memo.globals, memo.locals, type_params=(), recursive_guard=frozenset()
+ )
+else:
+
+ def evaluate_forwardref(forwardref: ForwardRef, memo: TypeCheckMemo) -> Any:
+ try:
+ return forwardref._evaluate(
+ memo.globals, memo.locals, recursive_guard=frozenset()
+ )
+ except NameError:
+ if sys.version_info < (3, 10):
+ # Try again, with the type substitutions (list -> List etc.) in place
+ new_globals = memo.globals.copy()
+ new_globals.setdefault("Union", Union)
+
+ return forwardref._evaluate(
+ new_globals, memo.locals or new_globals, recursive_guard=frozenset()
+ )
+
+ raise
+
+
+def get_type_name(type_: Any, noPrefix: list[str]=[]) -> str:
+ name: str
+ for attrname in "__name__", "_name", "__forward_arg__":
+ candidate = getattr(type_, attrname, None)
+ if isinstance(candidate, str):
+ name = candidate
+ break
+ else:
+ origin = get_origin(type_)
+ candidate = getattr(origin, "_name", None)
+ if candidate is None:
+ candidate = type_.__class__.__name__.strip("_")
+
+ if isinstance(candidate, str):
+ name = candidate
+ else:
+ return "(unknown)"
+
+ args = get_args(type_)
+ if args:
+ if name == "Literal":
+ formatted_args = ", ".join(repr(arg) for arg in args)
+ else:
+ formatted_args = ", ".join(get_type_name(arg) for arg in args)
+
+ name += f"[{formatted_args}]"
+
+ # For ForwardRefs, use the module stored on the object if available
+ if hasattr(type_, "__forward_module__"):
+ module = type_.__forward_module__
+ else:
+ module = getattr(type_, "__module__", None)
+ if module and module not in (None, "typing", "typing_extensions", "builtins") + tuple(noPrefix):
+ name = module + "." + name
+
+ return name
+
+
+def qualified_name(obj: Any, *, add_class_prefix: bool = False) -> str:
+ """
+ Return the qualified name (e.g. package.module.Type) for the given object.
+
+ Builtins and types from the :mod:`typing` package get special treatment by having
+ the module name stripped from the generated name.
+
+ """
+ if obj is None:
+ return "None"
+ elif inspect.isclass(obj):
+ prefix = "class " if add_class_prefix else ""
+ type_ = obj
+ else:
+ prefix = ""
+ type_ = type(obj)
+
+ module = type_.__module__
+ qualname = type_.__qualname__
+ name = qualname if module in ("typing", "builtins") else f"{module}.{qualname}"
+ return prefix + name
+
+
+def function_name(func: Callable[..., Any]) -> str:
+ """
+ Return the qualified name of the given function.
+
+ Builtins and types from the :mod:`typing` package get special treatment by having
+ the module name stripped from the generated name.
+
+ """
+ # For partial functions and objects with __call__ defined, __qualname__ does not
+ # exist
+ module = getattr(func, "__module__", "")
+ qualname = (module + ".") if module not in ("builtins", "") else ""
+ return qualname + getattr(func, "__qualname__", repr(func))
+
+
+def resolve_reference(reference: str) -> Any:
+ modulename, varname = reference.partition(":")[::2]
+ if not modulename or not varname:
+ raise ValueError(f"{reference!r} is not a module:varname reference")
+
+ obj = import_module(modulename)
+ for attr in varname.split("."):
+ obj = getattr(obj, attr)
+
+ return obj
+
+
+def is_method_of(obj: object, cls: type) -> bool:
+ return (
+ inspect.isfunction(obj)
+ and obj.__module__ == cls.__module__
+ and obj.__qualname__.startswith(cls.__qualname__ + ".")
+ )
+
+
+def get_stacklevel() -> int:
+ level = 1
+ frame = cast(FrameType, currentframe()).f_back
+ while frame and frame.f_globals.get("__name__", "").startswith("typeguard."):
+ level += 1
+ frame = frame.f_back
+
+ return level
+
+
+@final
+class Unset:
+ __slots__ = ()
+
+ def __repr__(self) -> str:
+ return ""
+
+
+unset = Unset()
diff --git a/python/deps/untypy/examples/mylogfile b/python/code/typeguard/py.typed
similarity index 100%
rename from python/deps/untypy/examples/mylogfile
rename to python/code/typeguard/py.typed
diff --git a/python/code/typing_extensions.py b/python/code/typing_extensions.py
new file mode 100644
index 00000000..efa09d55
--- /dev/null
+++ b/python/code/typing_extensions.py
@@ -0,0 +1,4244 @@
+import abc
+import builtins
+import collections
+import collections.abc
+import contextlib
+import enum
+import functools
+import inspect
+import io
+import keyword
+import operator
+import sys
+import types as _types
+import typing
+import warnings
+
+if sys.version_info >= (3, 14):
+ import annotationlib
+
+__all__ = [
+ # Super-special typing primitives.
+ 'Any',
+ 'ClassVar',
+ 'Concatenate',
+ 'Final',
+ 'LiteralString',
+ 'ParamSpec',
+ 'ParamSpecArgs',
+ 'ParamSpecKwargs',
+ 'Self',
+ 'Type',
+ 'TypeVar',
+ 'TypeVarTuple',
+ 'Unpack',
+
+ # ABCs (from collections.abc).
+ 'Awaitable',
+ 'AsyncIterator',
+ 'AsyncIterable',
+ 'Coroutine',
+ 'AsyncGenerator',
+ 'AsyncContextManager',
+ 'Buffer',
+ 'ChainMap',
+
+ # Concrete collection types.
+ 'ContextManager',
+ 'Counter',
+ 'Deque',
+ 'DefaultDict',
+ 'NamedTuple',
+ 'OrderedDict',
+ 'TypedDict',
+
+ # Structural checks, a.k.a. protocols.
+ 'SupportsAbs',
+ 'SupportsBytes',
+ 'SupportsComplex',
+ 'SupportsFloat',
+ 'SupportsIndex',
+ 'SupportsInt',
+ 'SupportsRound',
+ 'Reader',
+ 'Writer',
+
+ # One-off things.
+ 'Annotated',
+ 'assert_never',
+ 'assert_type',
+ 'clear_overloads',
+ 'dataclass_transform',
+ 'deprecated',
+ 'Doc',
+ 'evaluate_forward_ref',
+ 'get_overloads',
+ 'final',
+ 'Format',
+ 'get_annotations',
+ 'get_args',
+ 'get_origin',
+ 'get_original_bases',
+ 'get_protocol_members',
+ 'get_type_hints',
+ 'IntVar',
+ 'is_protocol',
+ 'is_typeddict',
+ 'Literal',
+ 'NewType',
+ 'overload',
+ 'override',
+ 'Protocol',
+ 'Sentinel',
+ 'reveal_type',
+ 'runtime',
+ 'runtime_checkable',
+ 'Text',
+ 'TypeAlias',
+ 'TypeAliasType',
+ 'TypeForm',
+ 'TypeGuard',
+ 'TypeIs',
+ 'TYPE_CHECKING',
+ 'Never',
+ 'NoReturn',
+ 'ReadOnly',
+ 'Required',
+ 'NotRequired',
+ 'NoDefault',
+ 'NoExtraItems',
+
+ # Pure aliases, have always been in typing
+ 'AbstractSet',
+ 'AnyStr',
+ 'BinaryIO',
+ 'Callable',
+ 'Collection',
+ 'Container',
+ 'Dict',
+ 'ForwardRef',
+ 'FrozenSet',
+ 'Generator',
+ 'Generic',
+ 'Hashable',
+ 'IO',
+ 'ItemsView',
+ 'Iterable',
+ 'Iterator',
+ 'KeysView',
+ 'List',
+ 'Mapping',
+ 'MappingView',
+ 'Match',
+ 'MutableMapping',
+ 'MutableSequence',
+ 'MutableSet',
+ 'Optional',
+ 'Pattern',
+ 'Reversible',
+ 'Sequence',
+ 'Set',
+ 'Sized',
+ 'TextIO',
+ 'Tuple',
+ 'Union',
+ 'ValuesView',
+ 'cast',
+ 'no_type_check',
+ 'no_type_check_decorator',
+]
+
+# for backward compatibility
+PEP_560 = True
+GenericMeta = type
+_PEP_696_IMPLEMENTED = sys.version_info >= (3, 13, 0, "beta")
+
+# Added with bpo-45166 to 3.10.1+ and some 3.9 versions
+_FORWARD_REF_HAS_CLASS = "__forward_is_class__" in typing.ForwardRef.__slots__
+
+# The functions below are modified copies of typing internal helpers.
+# They are needed by _ProtocolMeta and they provide support for PEP 646.
+
+
+class _Sentinel:
+ def __repr__(self):
+ return ""
+
+
+_marker = _Sentinel()
+
+
+if sys.version_info >= (3, 10):
+ def _should_collect_from_parameters(t):
+ return isinstance(
+ t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType)
+ )
+else:
+ def _should_collect_from_parameters(t):
+ return isinstance(t, (typing._GenericAlias, _types.GenericAlias))
+
+
+NoReturn = typing.NoReturn
+
+# Some unconstrained type variables. These are used by the container types.
+# (These are not for export.)
+T = typing.TypeVar('T') # Any type.
+KT = typing.TypeVar('KT') # Key type.
+VT = typing.TypeVar('VT') # Value type.
+T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers.
+T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant.
+
+
+if sys.version_info >= (3, 11):
+ from typing import Any
+else:
+
+ class _AnyMeta(type):
+ def __instancecheck__(self, obj):
+ if self is Any:
+ raise TypeError("typing_extensions.Any cannot be used with isinstance()")
+ return super().__instancecheck__(obj)
+
+ def __repr__(self):
+ if self is Any:
+ return "typing_extensions.Any"
+ return super().__repr__()
+
+ class Any(metaclass=_AnyMeta):
+ """Special type indicating an unconstrained type.
+ - Any is compatible with every type.
+ - Any assumed to have all methods.
+ - All values assumed to be instances of Any.
+ Note that all the above statements are true from the point of view of
+ static type checkers. At runtime, Any should not be used with instance
+ checks.
+ """
+ def __new__(cls, *args, **kwargs):
+ if cls is Any:
+ raise TypeError("Any cannot be instantiated")
+ return super().__new__(cls, *args, **kwargs)
+
+
+ClassVar = typing.ClassVar
+
+# Vendored from cpython typing._SpecialFrom
+# Having a separate class means that instances will not be rejected by
+# typing._type_check.
+class _SpecialForm(typing._Final, _root=True):
+ __slots__ = ('_name', '__doc__', '_getitem')
+
+ def __init__(self, getitem):
+ self._getitem = getitem
+ self._name = getitem.__name__
+ self.__doc__ = getitem.__doc__
+
+ def __getattr__(self, item):
+ if item in {'__name__', '__qualname__'}:
+ return self._name
+
+ raise AttributeError(item)
+
+ def __mro_entries__(self, bases):
+ raise TypeError(f"Cannot subclass {self!r}")
+
+ def __repr__(self):
+ return f'typing_extensions.{self._name}'
+
+ def __reduce__(self):
+ return self._name
+
+ def __call__(self, *args, **kwds):
+ raise TypeError(f"Cannot instantiate {self!r}")
+
+ def __or__(self, other):
+ return typing.Union[self, other]
+
+ def __ror__(self, other):
+ return typing.Union[other, self]
+
+ def __instancecheck__(self, obj):
+ raise TypeError(f"{self} cannot be used with isinstance()")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError(f"{self} cannot be used with issubclass()")
+
+ @typing._tp_cache
+ def __getitem__(self, parameters):
+ return self._getitem(self, parameters)
+
+
+# Note that inheriting from this class means that the object will be
+# rejected by typing._type_check, so do not use it if the special form
+# is arguably valid as a type by itself.
+class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+
+Final = typing.Final
+
+if sys.version_info >= (3, 11):
+ final = typing.final
+else:
+ # @final exists in 3.8+, but we backport it for all versions
+ # before 3.11 to keep support for the __final__ attribute.
+ # See https://bugs.python.org/issue46342
+ def final(f):
+ """This decorator can be used to indicate to type checkers that
+ the decorated method cannot be overridden, and decorated class
+ cannot be subclassed. For example:
+
+ class Base:
+ @final
+ def done(self) -> None:
+ ...
+ class Sub(Base):
+ def done(self) -> None: # Error reported by type checker
+ ...
+ @final
+ class Leaf:
+ ...
+ class Other(Leaf): # Error reported by type checker
+ ...
+
+ There is no runtime checking of these properties. The decorator
+ sets the ``__final__`` attribute to ``True`` on the decorated object
+ to allow runtime introspection.
+ """
+ try:
+ f.__final__ = True
+ except (AttributeError, TypeError):
+ # Skip the attribute silently if it is not writable.
+ # AttributeError happens if the object has __slots__ or a
+ # read-only property, TypeError if it's a builtin class.
+ pass
+ return f
+
+
+def IntVar(name):
+ return typing.TypeVar(name)
+
+
+# A Literal bug was fixed in 3.11.0, 3.10.1 and 3.9.8
+if sys.version_info >= (3, 10, 1):
+ Literal = typing.Literal
+else:
+ def _flatten_literal_params(parameters):
+ """An internal helper for Literal creation: flatten Literals among parameters"""
+ params = []
+ for p in parameters:
+ if isinstance(p, _LiteralGenericAlias):
+ params.extend(p.__args__)
+ else:
+ params.append(p)
+ return tuple(params)
+
+ def _value_and_type_iter(params):
+ for p in params:
+ yield p, type(p)
+
+ class _LiteralGenericAlias(typing._GenericAlias, _root=True):
+ def __eq__(self, other):
+ if not isinstance(other, _LiteralGenericAlias):
+ return NotImplemented
+ these_args_deduped = set(_value_and_type_iter(self.__args__))
+ other_args_deduped = set(_value_and_type_iter(other.__args__))
+ return these_args_deduped == other_args_deduped
+
+ def __hash__(self):
+ return hash(frozenset(_value_and_type_iter(self.__args__)))
+
+ class _LiteralForm(_ExtensionsSpecialForm, _root=True):
+ def __init__(self, doc: str):
+ self._name = 'Literal'
+ self._doc = self.__doc__ = doc
+
+ def __getitem__(self, parameters):
+ if not isinstance(parameters, tuple):
+ parameters = (parameters,)
+
+ parameters = _flatten_literal_params(parameters)
+
+ val_type_pairs = list(_value_and_type_iter(parameters))
+ try:
+ deduped_pairs = set(val_type_pairs)
+ except TypeError:
+ # unhashable parameters
+ pass
+ else:
+ # similar logic to typing._deduplicate on Python 3.9+
+ if len(deduped_pairs) < len(val_type_pairs):
+ new_parameters = []
+ for pair in val_type_pairs:
+ if pair in deduped_pairs:
+ new_parameters.append(pair[0])
+ deduped_pairs.remove(pair)
+ assert not deduped_pairs, deduped_pairs
+ parameters = tuple(new_parameters)
+
+ return _LiteralGenericAlias(self, parameters)
+
+ Literal = _LiteralForm(doc="""\
+ A type that can be used to indicate to type checkers
+ that the corresponding value has a value literally equivalent
+ to the provided parameter. For example:
+
+ var: Literal[4] = 4
+
+ The type checker understands that 'var' is literally equal to
+ the value 4 and no other value.
+
+ Literal[...] cannot be subclassed. There is no runtime
+ checking verifying that the parameter is actually a value
+ instead of a type.""")
+
+
+_overload_dummy = typing._overload_dummy
+
+
+if hasattr(typing, "get_overloads"): # 3.11+
+ overload = typing.overload
+ get_overloads = typing.get_overloads
+ clear_overloads = typing.clear_overloads
+else:
+ # {module: {qualname: {firstlineno: func}}}
+ _overload_registry = collections.defaultdict(
+ functools.partial(collections.defaultdict, dict)
+ )
+
+ def overload(func):
+ """Decorator for overloaded functions/methods.
+
+ In a stub file, place two or more stub definitions for the same
+ function in a row, each decorated with @overload. For example:
+
+ @overload
+ def utf8(value: None) -> None: ...
+ @overload
+ def utf8(value: bytes) -> bytes: ...
+ @overload
+ def utf8(value: str) -> bytes: ...
+
+ In a non-stub file (i.e. a regular .py file), do the same but
+ follow it with an implementation. The implementation should *not*
+ be decorated with @overload. For example:
+
+ @overload
+ def utf8(value: None) -> None: ...
+ @overload
+ def utf8(value: bytes) -> bytes: ...
+ @overload
+ def utf8(value: str) -> bytes: ...
+ def utf8(value):
+ # implementation goes here
+
+ The overloads for a function can be retrieved at runtime using the
+ get_overloads() function.
+ """
+ # classmethod and staticmethod
+ f = getattr(func, "__func__", func)
+ try:
+ _overload_registry[f.__module__][f.__qualname__][
+ f.__code__.co_firstlineno
+ ] = func
+ except AttributeError:
+ # Not a normal function; ignore.
+ pass
+ return _overload_dummy
+
+ def get_overloads(func):
+ """Return all defined overloads for *func* as a sequence."""
+ # classmethod and staticmethod
+ f = getattr(func, "__func__", func)
+ if f.__module__ not in _overload_registry:
+ return []
+ mod_dict = _overload_registry[f.__module__]
+ if f.__qualname__ not in mod_dict:
+ return []
+ return list(mod_dict[f.__qualname__].values())
+
+ def clear_overloads():
+ """Clear all overloads in the registry."""
+ _overload_registry.clear()
+
+
+# This is not a real generic class. Don't use outside annotations.
+Type = typing.Type
+
+# Various ABCs mimicking those in collections.abc.
+# A few are simply re-exported for completeness.
+Awaitable = typing.Awaitable
+Coroutine = typing.Coroutine
+AsyncIterable = typing.AsyncIterable
+AsyncIterator = typing.AsyncIterator
+Deque = typing.Deque
+DefaultDict = typing.DefaultDict
+OrderedDict = typing.OrderedDict
+Counter = typing.Counter
+ChainMap = typing.ChainMap
+Text = typing.Text
+TYPE_CHECKING = typing.TYPE_CHECKING
+
+
+if sys.version_info >= (3, 13, 0, "beta"):
+ from typing import AsyncContextManager, AsyncGenerator, ContextManager, Generator
+else:
+ def _is_dunder(attr):
+ return attr.startswith('__') and attr.endswith('__')
+
+
+ class _SpecialGenericAlias(typing._SpecialGenericAlias, _root=True):
+ def __init__(self, origin, nparams, *, inst=True, name=None, defaults=()):
+ super().__init__(origin, nparams, inst=inst, name=name)
+ self._defaults = defaults
+
+ def __setattr__(self, attr, val):
+ allowed_attrs = {'_name', '_inst', '_nparams', '_defaults'}
+ if _is_dunder(attr) or attr in allowed_attrs:
+ object.__setattr__(self, attr, val)
+ else:
+ setattr(self.__origin__, attr, val)
+
+ @typing._tp_cache
+ def __getitem__(self, params):
+ if not isinstance(params, tuple):
+ params = (params,)
+ msg = "Parameters to generic types must be types."
+ params = tuple(typing._type_check(p, msg) for p in params)
+ if (
+ self._defaults
+ and len(params) < self._nparams
+ and len(params) + len(self._defaults) >= self._nparams
+ ):
+ params = (*params, *self._defaults[len(params) - self._nparams:])
+ actual_len = len(params)
+
+ if actual_len != self._nparams:
+ if self._defaults:
+ expected = f"at least {self._nparams - len(self._defaults)}"
+ else:
+ expected = str(self._nparams)
+ if not self._nparams:
+ raise TypeError(f"{self} is not a generic class")
+ raise TypeError(
+ f"Too {'many' if actual_len > self._nparams else 'few'}"
+ f" arguments for {self};"
+ f" actual {actual_len}, expected {expected}"
+ )
+ return self.copy_with(params)
+
+ _NoneType = type(None)
+ Generator = _SpecialGenericAlias(
+ collections.abc.Generator, 3, defaults=(_NoneType, _NoneType)
+ )
+ AsyncGenerator = _SpecialGenericAlias(
+ collections.abc.AsyncGenerator, 2, defaults=(_NoneType,)
+ )
+ ContextManager = _SpecialGenericAlias(
+ contextlib.AbstractContextManager,
+ 2,
+ name="ContextManager",
+ defaults=(typing.Optional[bool],)
+ )
+ AsyncContextManager = _SpecialGenericAlias(
+ contextlib.AbstractAsyncContextManager,
+ 2,
+ name="AsyncContextManager",
+ defaults=(typing.Optional[bool],)
+ )
+
+
+_PROTO_ALLOWLIST = {
+ 'collections.abc': [
+ 'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable',
+ 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer',
+ ],
+ 'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'],
+ 'typing_extensions': ['Buffer'],
+}
+
+
+_EXCLUDED_ATTRS = frozenset(typing.EXCLUDED_ATTRIBUTES) | {
+ "__match_args__", "__protocol_attrs__", "__non_callable_proto_members__",
+ "__final__",
+}
+
+
+def _get_protocol_attrs(cls):
+ attrs = set()
+ for base in cls.__mro__[:-1]: # without object
+ if base.__name__ in {'Protocol', 'Generic'}:
+ continue
+ annotations = getattr(base, '__annotations__', {})
+ for attr in (*base.__dict__, *annotations):
+ if (not attr.startswith('_abc_') and attr not in _EXCLUDED_ATTRS):
+ attrs.add(attr)
+ return attrs
+
+
+def _caller(depth=1, default='__main__'):
+ try:
+ return sys._getframemodulename(depth + 1) or default
+ except AttributeError: # For platforms without _getframemodulename()
+ pass
+ try:
+ return sys._getframe(depth + 1).f_globals.get('__name__', default)
+ except (AttributeError, ValueError): # For platforms without _getframe()
+ pass
+ return None
+
+
+# `__match_args__` attribute was removed from protocol members in 3.13,
+# we want to backport this change to older Python versions.
+if sys.version_info >= (3, 13):
+ Protocol = typing.Protocol
+else:
+ def _allow_reckless_class_checks(depth=2):
+ """Allow instance and class checks for special stdlib modules.
+ The abc and functools modules indiscriminately call isinstance() and
+ issubclass() on the whole MRO of a user class, which may contain protocols.
+ """
+ return _caller(depth) in {'abc', 'functools', None}
+
+ def _no_init(self, *args, **kwargs):
+ if type(self)._is_protocol:
+ raise TypeError('Protocols cannot be instantiated')
+
+ def _type_check_issubclass_arg_1(arg):
+ """Raise TypeError if `arg` is not an instance of `type`
+ in `issubclass(arg, )`.
+
+ In most cases, this is verified by type.__subclasscheck__.
+ Checking it again unnecessarily would slow down issubclass() checks,
+ so, we don't perform this check unless we absolutely have to.
+
+ For various error paths, however,
+ we want to ensure that *this* error message is shown to the user
+ where relevant, rather than a typing.py-specific error message.
+ """
+ if not isinstance(arg, type):
+ # Same error message as for issubclass(1, int).
+ raise TypeError('issubclass() arg 1 must be a class')
+
+ # Inheriting from typing._ProtocolMeta isn't actually desirable,
+ # but is necessary to allow typing.Protocol and typing_extensions.Protocol
+ # to mix without getting TypeErrors about "metaclass conflict"
+ class _ProtocolMeta(type(typing.Protocol)):
+ # This metaclass is somewhat unfortunate,
+ # but is necessary for several reasons...
+ #
+ # NOTE: DO NOT call super() in any methods in this class
+ # That would call the methods on typing._ProtocolMeta on Python <=3.11
+ # and those are slow
+ def __new__(mcls, name, bases, namespace, **kwargs):
+ if name == "Protocol" and len(bases) < 2:
+ pass
+ elif {Protocol, typing.Protocol} & set(bases):
+ for base in bases:
+ if not (
+ base in {object, typing.Generic, Protocol, typing.Protocol}
+ or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
+ or is_protocol(base)
+ ):
+ raise TypeError(
+ f"Protocols can only inherit from other protocols, "
+ f"got {base!r}"
+ )
+ return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)
+
+ def __init__(cls, *args, **kwargs):
+ abc.ABCMeta.__init__(cls, *args, **kwargs)
+ if getattr(cls, "_is_protocol", False):
+ cls.__protocol_attrs__ = _get_protocol_attrs(cls)
+
+ def __subclasscheck__(cls, other):
+ if cls is Protocol:
+ return type.__subclasscheck__(cls, other)
+ if (
+ getattr(cls, '_is_protocol', False)
+ and not _allow_reckless_class_checks()
+ ):
+ if not getattr(cls, '_is_runtime_protocol', False):
+ _type_check_issubclass_arg_1(other)
+ raise TypeError(
+ "Instance and class checks can only be used with "
+ "@runtime_checkable protocols"
+ )
+ if (
+ # this attribute is set by @runtime_checkable:
+ cls.__non_callable_proto_members__
+ and cls.__dict__.get("__subclasshook__") is _proto_hook
+ ):
+ _type_check_issubclass_arg_1(other)
+ non_method_attrs = sorted(cls.__non_callable_proto_members__)
+ raise TypeError(
+ "Protocols with non-method members don't support issubclass()."
+ f" Non-method members: {str(non_method_attrs)[1:-1]}."
+ )
+ return abc.ABCMeta.__subclasscheck__(cls, other)
+
+ def __instancecheck__(cls, instance):
+ # We need this method for situations where attributes are
+ # assigned in __init__.
+ if cls is Protocol:
+ return type.__instancecheck__(cls, instance)
+ if not getattr(cls, "_is_protocol", False):
+ # i.e., it's a concrete subclass of a protocol
+ return abc.ABCMeta.__instancecheck__(cls, instance)
+
+ if (
+ not getattr(cls, '_is_runtime_protocol', False) and
+ not _allow_reckless_class_checks()
+ ):
+ raise TypeError("Instance and class checks can only be used with"
+ " @runtime_checkable protocols")
+
+ if abc.ABCMeta.__instancecheck__(cls, instance):
+ return True
+
+ for attr in cls.__protocol_attrs__:
+ try:
+ val = inspect.getattr_static(instance, attr)
+ except AttributeError:
+ break
+ # this attribute is set by @runtime_checkable:
+ if val is None and attr not in cls.__non_callable_proto_members__:
+ break
+ else:
+ return True
+
+ return False
+
+ def __eq__(cls, other):
+ # Hack so that typing.Generic.__class_getitem__
+ # treats typing_extensions.Protocol
+ # as equivalent to typing.Protocol
+ if abc.ABCMeta.__eq__(cls, other) is True:
+ return True
+ return cls is Protocol and other is typing.Protocol
+
+ # This has to be defined, or the abc-module cache
+ # complains about classes with this metaclass being unhashable,
+ # if we define only __eq__!
+ def __hash__(cls) -> int:
+ return type.__hash__(cls)
+
+ @classmethod
+ def _proto_hook(cls, other):
+ if not cls.__dict__.get('_is_protocol', False):
+ return NotImplemented
+
+ for attr in cls.__protocol_attrs__:
+ for base in other.__mro__:
+ # Check if the members appears in the class dictionary...
+ if attr in base.__dict__:
+ if base.__dict__[attr] is None:
+ return NotImplemented
+ break
+
+ # ...or in annotations, if it is a sub-protocol.
+ annotations = getattr(base, '__annotations__', {})
+ if (
+ isinstance(annotations, collections.abc.Mapping)
+ and attr in annotations
+ and is_protocol(other)
+ ):
+ break
+ else:
+ return NotImplemented
+ return True
+
+ class Protocol(typing.Generic, metaclass=_ProtocolMeta):
+ __doc__ = typing.Protocol.__doc__
+ __slots__ = ()
+ _is_protocol = True
+ _is_runtime_protocol = False
+
+ def __init_subclass__(cls, *args, **kwargs):
+ super().__init_subclass__(*args, **kwargs)
+
+ # Determine if this is a protocol or a concrete subclass.
+ if not cls.__dict__.get('_is_protocol', False):
+ cls._is_protocol = any(b is Protocol for b in cls.__bases__)
+
+ # Set (or override) the protocol subclass hook.
+ if '__subclasshook__' not in cls.__dict__:
+ cls.__subclasshook__ = _proto_hook
+
+ # Prohibit instantiation for protocol classes
+ if cls._is_protocol and cls.__init__ is Protocol.__init__:
+ cls.__init__ = _no_init
+
+
+if sys.version_info >= (3, 13):
+ runtime_checkable = typing.runtime_checkable
+else:
+ def runtime_checkable(cls):
+ """Mark a protocol class as a runtime protocol.
+
+ Such protocol can be used with isinstance() and issubclass().
+ Raise TypeError if applied to a non-protocol class.
+ This allows a simple-minded structural check very similar to
+ one trick ponies in collections.abc such as Iterable.
+
+ For example::
+
+ @runtime_checkable
+ class Closable(Protocol):
+ def close(self): ...
+
+ assert isinstance(open('/some/file'), Closable)
+
+ Warning: this will check only the presence of the required methods,
+ not their type signatures!
+ """
+ if not issubclass(cls, typing.Generic) or not getattr(cls, '_is_protocol', False):
+ raise TypeError(f'@runtime_checkable can be only applied to protocol classes,'
+ f' got {cls!r}')
+ cls._is_runtime_protocol = True
+
+ # typing.Protocol classes on <=3.11 break if we execute this block,
+ # because typing.Protocol classes on <=3.11 don't have a
+ # `__protocol_attrs__` attribute, and this block relies on the
+ # `__protocol_attrs__` attribute. Meanwhile, typing.Protocol classes on 3.12.2+
+ # break if we *don't* execute this block, because *they* assume that all
+ # protocol classes have a `__non_callable_proto_members__` attribute
+ # (which this block sets)
+ if isinstance(cls, _ProtocolMeta) or sys.version_info >= (3, 12, 2):
+ # PEP 544 prohibits using issubclass()
+ # with protocols that have non-method members.
+ # See gh-113320 for why we compute this attribute here,
+ # rather than in `_ProtocolMeta.__init__`
+ cls.__non_callable_proto_members__ = set()
+ for attr in cls.__protocol_attrs__:
+ try:
+ is_callable = callable(getattr(cls, attr, None))
+ except Exception as e:
+ raise TypeError(
+ f"Failed to determine whether protocol member {attr!r} "
+ "is a method member"
+ ) from e
+ else:
+ if not is_callable:
+ cls.__non_callable_proto_members__.add(attr)
+
+ return cls
+
+
+# The "runtime" alias exists for backwards compatibility.
+runtime = runtime_checkable
+
+
+# Our version of runtime-checkable protocols is faster on Python <=3.11
+if sys.version_info >= (3, 12):
+ SupportsInt = typing.SupportsInt
+ SupportsFloat = typing.SupportsFloat
+ SupportsComplex = typing.SupportsComplex
+ SupportsBytes = typing.SupportsBytes
+ SupportsIndex = typing.SupportsIndex
+ SupportsAbs = typing.SupportsAbs
+ SupportsRound = typing.SupportsRound
+else:
+ @runtime_checkable
+ class SupportsInt(Protocol):
+ """An ABC with one abstract method __int__."""
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def __int__(self) -> int:
+ pass
+
+ @runtime_checkable
+ class SupportsFloat(Protocol):
+ """An ABC with one abstract method __float__."""
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def __float__(self) -> float:
+ pass
+
+ @runtime_checkable
+ class SupportsComplex(Protocol):
+ """An ABC with one abstract method __complex__."""
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def __complex__(self) -> complex:
+ pass
+
+ @runtime_checkable
+ class SupportsBytes(Protocol):
+ """An ABC with one abstract method __bytes__."""
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def __bytes__(self) -> bytes:
+ pass
+
+ @runtime_checkable
+ class SupportsIndex(Protocol):
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def __index__(self) -> int:
+ pass
+
+ @runtime_checkable
+ class SupportsAbs(Protocol[T_co]):
+ """
+ An ABC with one abstract method __abs__ that is covariant in its return type.
+ """
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def __abs__(self) -> T_co:
+ pass
+
+ @runtime_checkable
+ class SupportsRound(Protocol[T_co]):
+ """
+ An ABC with one abstract method __round__ that is covariant in its return type.
+ """
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def __round__(self, ndigits: int = 0) -> T_co:
+ pass
+
+
+if hasattr(io, "Reader") and hasattr(io, "Writer"):
+ Reader = io.Reader
+ Writer = io.Writer
+else:
+ @runtime_checkable
+ class Reader(Protocol[T_co]):
+ """Protocol for simple I/O reader instances.
+
+ This protocol only supports blocking I/O.
+ """
+
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def read(self, size: int = ..., /) -> T_co:
+ """Read data from the input stream and return it.
+
+ If *size* is specified, at most *size* items (bytes/characters) will be
+ read.
+ """
+
+ @runtime_checkable
+ class Writer(Protocol[T_contra]):
+ """Protocol for simple I/O writer instances.
+
+ This protocol only supports blocking I/O.
+ """
+
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def write(self, data: T_contra, /) -> int:
+ """Write *data* to the output stream and return the number of items written.""" # noqa: E501
+
+
+_NEEDS_SINGLETONMETA = (
+ not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems")
+)
+
+if _NEEDS_SINGLETONMETA:
+ class SingletonMeta(type):
+ def __setattr__(cls, attr, value):
+ # TypeError is consistent with the behavior of NoneType
+ raise TypeError(
+ f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}"
+ )
+
+
+if hasattr(typing, "NoDefault"):
+ NoDefault = typing.NoDefault
+else:
+ class NoDefaultType(metaclass=SingletonMeta):
+ """The type of the NoDefault singleton."""
+
+ __slots__ = ()
+
+ def __new__(cls):
+ return globals().get("NoDefault") or object.__new__(cls)
+
+ def __repr__(self):
+ return "typing_extensions.NoDefault"
+
+ def __reduce__(self):
+ return "NoDefault"
+
+ NoDefault = NoDefaultType()
+ del NoDefaultType
+
+if hasattr(typing, "NoExtraItems"):
+ NoExtraItems = typing.NoExtraItems
+else:
+ class NoExtraItemsType(metaclass=SingletonMeta):
+ """The type of the NoExtraItems singleton."""
+
+ __slots__ = ()
+
+ def __new__(cls):
+ return globals().get("NoExtraItems") or object.__new__(cls)
+
+ def __repr__(self):
+ return "typing_extensions.NoExtraItems"
+
+ def __reduce__(self):
+ return "NoExtraItems"
+
+ NoExtraItems = NoExtraItemsType()
+ del NoExtraItemsType
+
+if _NEEDS_SINGLETONMETA:
+ del SingletonMeta
+
+
+# Update this to something like >=3.13.0b1 if and when
+# PEP 728 is implemented in CPython
+_PEP_728_IMPLEMENTED = False
+
+if _PEP_728_IMPLEMENTED:
+ # The standard library TypedDict in Python 3.9.0/1 does not honour the "total"
+ # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059
+ # The standard library TypedDict below Python 3.11 does not store runtime
+ # information about optional and required keys when using Required or NotRequired.
+ # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11.
+ # Aaaand on 3.12 we add __orig_bases__ to TypedDict
+ # to enable better runtime introspection.
+ # On 3.13 we deprecate some odd ways of creating TypedDicts.
+ # Also on 3.13, PEP 705 adds the ReadOnly[] qualifier.
+ # PEP 728 (still pending) makes more changes.
+ TypedDict = typing.TypedDict
+ _TypedDictMeta = typing._TypedDictMeta
+ is_typeddict = typing.is_typeddict
+else:
+ # 3.10.0 and later
+ _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters
+
+ def _get_typeddict_qualifiers(annotation_type):
+ while True:
+ annotation_origin = get_origin(annotation_type)
+ if annotation_origin is Annotated:
+ annotation_args = get_args(annotation_type)
+ if annotation_args:
+ annotation_type = annotation_args[0]
+ else:
+ break
+ elif annotation_origin is Required:
+ yield Required
+ annotation_type, = get_args(annotation_type)
+ elif annotation_origin is NotRequired:
+ yield NotRequired
+ annotation_type, = get_args(annotation_type)
+ elif annotation_origin is ReadOnly:
+ yield ReadOnly
+ annotation_type, = get_args(annotation_type)
+ else:
+ break
+
+ class _TypedDictMeta(type):
+
+ def __new__(cls, name, bases, ns, *, total=True, closed=None,
+ extra_items=NoExtraItems):
+ """Create new typed dict class object.
+
+ This method is called when TypedDict is subclassed,
+ or when TypedDict is instantiated. This way
+ TypedDict supports all three syntax forms described in its docstring.
+ Subclasses and instances of TypedDict return actual dictionaries.
+ """
+ for base in bases:
+ if type(base) is not _TypedDictMeta and base is not typing.Generic:
+ raise TypeError('cannot inherit from both a TypedDict type '
+ 'and a non-TypedDict base class')
+ if closed is not None and extra_items is not NoExtraItems:
+ raise TypeError(f"Cannot combine closed={closed!r} and extra_items")
+
+ if any(issubclass(b, typing.Generic) for b in bases):
+ generic_base = (typing.Generic,)
+ else:
+ generic_base = ()
+
+ ns_annotations = ns.pop('__annotations__', None)
+
+ # typing.py generally doesn't let you inherit from plain Generic, unless
+ # the name of the class happens to be "Protocol"
+ tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns)
+ tp_dict.__name__ = name
+ if tp_dict.__qualname__ == "Protocol":
+ tp_dict.__qualname__ = name
+
+ if not hasattr(tp_dict, '__orig_bases__'):
+ tp_dict.__orig_bases__ = bases
+
+ annotations = {}
+ own_annotate = None
+ if ns_annotations is not None:
+ own_annotations = ns_annotations
+ elif sys.version_info >= (3, 14):
+ if hasattr(annotationlib, "get_annotate_from_class_namespace"):
+ own_annotate = annotationlib.get_annotate_from_class_namespace(ns)
+ else:
+ # 3.14.0a7 and earlier
+ own_annotate = ns.get("__annotate__")
+ if own_annotate is not None:
+ own_annotations = annotationlib.call_annotate_function(
+ own_annotate, Format.FORWARDREF, owner=tp_dict
+ )
+ else:
+ own_annotations = {}
+ else:
+ own_annotations = {}
+ msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
+ if _TAKES_MODULE:
+ own_checked_annotations = {
+ n: typing._type_check(tp, msg, module=tp_dict.__module__)
+ for n, tp in own_annotations.items()
+ }
+ else:
+ own_checked_annotations = {
+ n: typing._type_check(tp, msg)
+ for n, tp in own_annotations.items()
+ }
+ required_keys = set()
+ optional_keys = set()
+ readonly_keys = set()
+ mutable_keys = set()
+ extra_items_type = extra_items
+
+ for base in bases:
+ base_dict = base.__dict__
+
+ if sys.version_info <= (3, 14):
+ annotations.update(base_dict.get('__annotations__', {}))
+ required_keys.update(base_dict.get('__required_keys__', ()))
+ optional_keys.update(base_dict.get('__optional_keys__', ()))
+ readonly_keys.update(base_dict.get('__readonly_keys__', ()))
+ mutable_keys.update(base_dict.get('__mutable_keys__', ()))
+
+ # This was specified in an earlier version of PEP 728. Support
+ # is retained for backwards compatibility, but only for Python
+ # 3.13 and lower.
+ if (closed and sys.version_info < (3, 14)
+ and "__extra_items__" in own_checked_annotations):
+ annotation_type = own_checked_annotations.pop("__extra_items__")
+ qualifiers = set(_get_typeddict_qualifiers(annotation_type))
+ if Required in qualifiers:
+ raise TypeError(
+ "Special key __extra_items__ does not support "
+ "Required"
+ )
+ if NotRequired in qualifiers:
+ raise TypeError(
+ "Special key __extra_items__ does not support "
+ "NotRequired"
+ )
+ extra_items_type = annotation_type
+
+ annotations.update(own_checked_annotations)
+ for annotation_key, annotation_type in own_checked_annotations.items():
+ qualifiers = set(_get_typeddict_qualifiers(annotation_type))
+
+ if Required in qualifiers:
+ required_keys.add(annotation_key)
+ elif NotRequired in qualifiers:
+ optional_keys.add(annotation_key)
+ elif total:
+ required_keys.add(annotation_key)
+ else:
+ optional_keys.add(annotation_key)
+ if ReadOnly in qualifiers:
+ mutable_keys.discard(annotation_key)
+ readonly_keys.add(annotation_key)
+ else:
+ mutable_keys.add(annotation_key)
+ readonly_keys.discard(annotation_key)
+
+ if sys.version_info >= (3, 14):
+ def __annotate__(format):
+ annos = {}
+ for base in bases:
+ if base is Generic:
+ continue
+ base_annotate = base.__annotate__
+ if base_annotate is None:
+ continue
+ base_annos = annotationlib.call_annotate_function(
+ base_annotate, format, owner=base)
+ annos.update(base_annos)
+ if own_annotate is not None:
+ own = annotationlib.call_annotate_function(
+ own_annotate, format, owner=tp_dict)
+ if format != Format.STRING:
+ own = {
+ n: typing._type_check(tp, msg, module=tp_dict.__module__)
+ for n, tp in own.items()
+ }
+ elif format == Format.STRING:
+ own = annotationlib.annotations_to_string(own_annotations)
+ elif format in (Format.FORWARDREF, Format.VALUE):
+ own = own_checked_annotations
+ else:
+ raise NotImplementedError(format)
+ annos.update(own)
+ return annos
+
+ tp_dict.__annotate__ = __annotate__
+ else:
+ tp_dict.__annotations__ = annotations
+ tp_dict.__required_keys__ = frozenset(required_keys)
+ tp_dict.__optional_keys__ = frozenset(optional_keys)
+ tp_dict.__readonly_keys__ = frozenset(readonly_keys)
+ tp_dict.__mutable_keys__ = frozenset(mutable_keys)
+ tp_dict.__total__ = total
+ tp_dict.__closed__ = closed
+ tp_dict.__extra_items__ = extra_items_type
+ return tp_dict
+
+ __call__ = dict # static method
+
+ def __subclasscheck__(cls, other):
+ # Typed dicts are only for static structural subtyping.
+ raise TypeError('TypedDict does not support instance and class checks')
+
+ __instancecheck__ = __subclasscheck__
+
+ _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
+
+ def _create_typeddict(
+ typename,
+ fields,
+ /,
+ *,
+ typing_is_inline,
+ total,
+ closed,
+ extra_items,
+ **kwargs,
+ ):
+ if fields is _marker or fields is None:
+ if fields is _marker:
+ deprecated_thing = (
+ "Failing to pass a value for the 'fields' parameter"
+ )
+ else:
+ deprecated_thing = "Passing `None` as the 'fields' parameter"
+
+ example = f"`{typename} = TypedDict({typename!r}, {{}})`"
+ deprecation_msg = (
+ f"{deprecated_thing} is deprecated and will be disallowed in "
+ "Python 3.15. To create a TypedDict class with 0 fields "
+ "using the functional syntax, pass an empty dictionary, e.g. "
+ ) + example + "."
+ warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
+ # Support a field called "closed"
+ if closed is not False and closed is not True and closed is not None:
+ kwargs["closed"] = closed
+ closed = None
+ # Or "extra_items"
+ if extra_items is not NoExtraItems:
+ kwargs["extra_items"] = extra_items
+ extra_items = NoExtraItems
+ fields = kwargs
+ elif kwargs:
+ raise TypeError("TypedDict takes either a dict or keyword arguments,"
+ " but not both")
+ if kwargs:
+ if sys.version_info >= (3, 13):
+ raise TypeError("TypedDict takes no keyword arguments")
+ warnings.warn(
+ "The kwargs-based syntax for TypedDict definitions is deprecated "
+ "in Python 3.11, will be removed in Python 3.13, and may not be "
+ "understood by third-party type checkers.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ ns = {'__annotations__': dict(fields)}
+ module = _caller(depth=4 if typing_is_inline else 2)
+ if module is not None:
+ # Setting correct module is necessary to make typed dict classes
+ # pickleable.
+ ns['__module__'] = module
+
+ td = _TypedDictMeta(typename, (), ns, total=total, closed=closed,
+ extra_items=extra_items)
+ td.__orig_bases__ = (TypedDict,)
+ return td
+
+ class _TypedDictSpecialForm(_SpecialForm, _root=True):
+ def __call__(
+ self,
+ typename,
+ fields=_marker,
+ /,
+ *,
+ total=True,
+ closed=None,
+ extra_items=NoExtraItems,
+ **kwargs
+ ):
+ return _create_typeddict(
+ typename,
+ fields,
+ typing_is_inline=False,
+ total=total,
+ closed=closed,
+ extra_items=extra_items,
+ **kwargs,
+ )
+
+ def __mro_entries__(self, bases):
+ return (_TypedDict,)
+
+ @_TypedDictSpecialForm
+ def TypedDict(self, args):
+ """A simple typed namespace. At runtime it is equivalent to a plain dict.
+
+ TypedDict creates a dictionary type such that a type checker will expect all
+ instances to have a certain set of keys, where each key is
+ associated with a value of a consistent type. This expectation
+ is not checked at runtime.
+
+ Usage::
+
+ class Point2D(TypedDict):
+ x: int
+ y: int
+ label: str
+
+ a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
+ b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check
+
+ assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
+
+ The type info can be accessed via the Point2D.__annotations__ dict, and
+ the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
+ TypedDict supports an additional equivalent form::
+
+ Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})
+
+ By default, all keys must be present in a TypedDict. It is possible
+ to override this by specifying totality::
+
+ class Point2D(TypedDict, total=False):
+ x: int
+ y: int
+
+ This means that a Point2D TypedDict can have any of the keys omitted. A type
+ checker is only expected to support a literal False or True as the value of
+ the total argument. True is the default, and makes all items defined in the
+ class body be required.
+
+ The Required and NotRequired special forms can also be used to mark
+ individual keys as being required or not required::
+
+ class Point2D(TypedDict):
+ x: int # the "x" key must always be present (Required is the default)
+ y: NotRequired[int] # the "y" key can be omitted
+
+ See PEP 655 for more details on Required and NotRequired.
+ """
+ # This runs when creating inline TypedDicts:
+ if not isinstance(args, dict):
+ raise TypeError(
+ "TypedDict[...] should be used with a single dict argument"
+ )
+
+ return _create_typeddict(
+ "",
+ args,
+ typing_is_inline=True,
+ total=True,
+ closed=True,
+ extra_items=NoExtraItems,
+ )
+
+ _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
+
+ def is_typeddict(tp):
+ """Check if an annotation is a TypedDict class
+
+ For example::
+ class Film(TypedDict):
+ title: str
+ year: int
+
+ is_typeddict(Film) # => True
+ is_typeddict(Union[list, str]) # => False
+ """
+ return isinstance(tp, _TYPEDDICT_TYPES)
+
+
+if hasattr(typing, "assert_type"):
+ assert_type = typing.assert_type
+
+else:
+ def assert_type(val, typ, /):
+ """Assert (to the type checker) that the value is of the given type.
+
+ When the type checker encounters a call to assert_type(), it
+ emits an error if the value is not of the specified type::
+
+ def greet(name: str) -> None:
+ assert_type(name, str) # ok
+ assert_type(name, int) # type checker error
+
+ At runtime this returns the first argument unchanged and otherwise
+ does nothing.
+ """
+ return val
+
+
+if hasattr(typing, "ReadOnly"): # 3.13+
+ get_type_hints = typing.get_type_hints
+else: # <=3.13
+ # replaces _strip_annotations()
+ def _strip_extras(t):
+ """Strips Annotated, Required and NotRequired from a given type."""
+ if isinstance(t, typing._AnnotatedAlias):
+ return _strip_extras(t.__origin__)
+ if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired, ReadOnly):
+ return _strip_extras(t.__args__[0])
+ if isinstance(t, typing._GenericAlias):
+ stripped_args = tuple(_strip_extras(a) for a in t.__args__)
+ if stripped_args == t.__args__:
+ return t
+ return t.copy_with(stripped_args)
+ if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias):
+ stripped_args = tuple(_strip_extras(a) for a in t.__args__)
+ if stripped_args == t.__args__:
+ return t
+ return _types.GenericAlias(t.__origin__, stripped_args)
+ if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType):
+ stripped_args = tuple(_strip_extras(a) for a in t.__args__)
+ if stripped_args == t.__args__:
+ return t
+ return functools.reduce(operator.or_, stripped_args)
+
+ return t
+
+ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
+ """Return type hints for an object.
+
+ This is often the same as obj.__annotations__, but it handles
+ forward references encoded as string literals, adds Optional[t] if a
+ default value equal to None is set and recursively replaces all
+ 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T'
+ (unless 'include_extras=True').
+
+ The argument may be a module, class, method, or function. The annotations
+ are returned as a dictionary. For classes, annotations include also
+ inherited members.
+
+ TypeError is raised if the argument is not of a type that can contain
+ annotations, and an empty dictionary is returned if no annotations are
+ present.
+
+ BEWARE -- the behavior of globalns and localns is counterintuitive
+ (unless you are familiar with how eval() and exec() work). The
+ search order is locals first, then globals.
+
+ - If no dict arguments are passed, an attempt is made to use the
+ globals from obj (or the respective module's globals for classes),
+ and these are also used as the locals. If the object does not appear
+ to have globals, an empty dictionary is used.
+
+ - If one dict argument is passed, it is used for both globals and
+ locals.
+
+ - If two dict arguments are passed, they specify globals and
+ locals, respectively.
+ """
+ hint = typing.get_type_hints(
+ obj, globalns=globalns, localns=localns, include_extras=True
+ )
+ if sys.version_info < (3, 11):
+ _clean_optional(obj, hint, globalns, localns)
+ if include_extras:
+ return hint
+ return {k: _strip_extras(t) for k, t in hint.items()}
+
+ _NoneType = type(None)
+
+ def _could_be_inserted_optional(t):
+ """detects Union[..., None] pattern"""
+ if not isinstance(t, typing._UnionGenericAlias):
+ return False
+ # Assume if last argument is not None they are user defined
+ if t.__args__[-1] is not _NoneType:
+ return False
+ return True
+
+ # < 3.11
+ def _clean_optional(obj, hints, globalns=None, localns=None):
+ # reverts injected Union[..., None] cases from typing.get_type_hints
+ # when a None default value is used.
+ # see https://github.com/python/typing_extensions/issues/310
+ if not hints or isinstance(obj, type):
+ return
+ defaults = typing._get_defaults(obj) # avoid accessing __annotations___
+ if not defaults:
+ return
+ original_hints = obj.__annotations__
+ for name, value in hints.items():
+ # Not a Union[..., None] or replacement conditions not fullfilled
+ if (not _could_be_inserted_optional(value)
+ or name not in defaults
+ or defaults[name] is not None
+ ):
+ continue
+ original_value = original_hints[name]
+ # value=NoneType should have caused a skip above but check for safety
+ if original_value is None:
+ original_value = _NoneType
+ # Forward reference
+ if isinstance(original_value, str):
+ if globalns is None:
+ if isinstance(obj, _types.ModuleType):
+ globalns = obj.__dict__
+ else:
+ nsobj = obj
+ # Find globalns for the unwrapped object.
+ while hasattr(nsobj, '__wrapped__'):
+ nsobj = nsobj.__wrapped__
+ globalns = getattr(nsobj, '__globals__', {})
+ if localns is None:
+ localns = globalns
+ elif localns is None:
+ localns = globalns
+
+ original_value = ForwardRef(
+ original_value,
+ is_argument=not isinstance(obj, _types.ModuleType)
+ )
+ original_evaluated = typing._eval_type(original_value, globalns, localns)
+ # Compare if values differ. Note that even if equal
+ # value might be cached by typing._tp_cache contrary to original_evaluated
+ if original_evaluated != value or (
+ # 3.10: ForwardRefs of UnionType might be turned into _UnionGenericAlias
+ hasattr(_types, "UnionType")
+ and isinstance(original_evaluated, _types.UnionType)
+ and not isinstance(value, _types.UnionType)
+ ):
+ hints[name] = original_evaluated
+
+# Python 3.9 has get_origin() and get_args() but those implementations don't support
+# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do.
+if sys.version_info[:2] >= (3, 10):
+ get_origin = typing.get_origin
+ get_args = typing.get_args
+# 3.9
+else:
+ def get_origin(tp):
+ """Get the unsubscripted version of a type.
+
+ This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar
+ and Annotated. Return None for unsupported types. Examples::
+
+ get_origin(Literal[42]) is Literal
+ get_origin(int) is None
+ get_origin(ClassVar[int]) is ClassVar
+ get_origin(Generic) is Generic
+ get_origin(Generic[T]) is Generic
+ get_origin(Union[T, int]) is Union
+ get_origin(List[Tuple[T, T]][int]) == list
+ get_origin(P.args) is P
+ """
+ if isinstance(tp, typing._AnnotatedAlias):
+ return Annotated
+ if isinstance(tp, (typing._BaseGenericAlias, _types.GenericAlias,
+ ParamSpecArgs, ParamSpecKwargs)):
+ return tp.__origin__
+ if tp is typing.Generic:
+ return typing.Generic
+ return None
+
+ def get_args(tp):
+ """Get type arguments with all substitutions performed.
+
+ For unions, basic simplifications used by Union constructor are performed.
+ Examples::
+ get_args(Dict[str, int]) == (str, int)
+ get_args(int) == ()
+ get_args(Union[int, Union[T, int], str][int]) == (int, str)
+ get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
+ get_args(Callable[[], T][int]) == ([], int)
+ """
+ if isinstance(tp, typing._AnnotatedAlias):
+ return (tp.__origin__, *tp.__metadata__)
+ if isinstance(tp, (typing._GenericAlias, _types.GenericAlias)):
+ res = tp.__args__
+ if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
+ res = (list(res[:-1]), res[-1])
+ return res
+ return ()
+
+
+# 3.10+
+if hasattr(typing, 'TypeAlias'):
+ TypeAlias = typing.TypeAlias
+# 3.9
+else:
+ @_ExtensionsSpecialForm
+ def TypeAlias(self, parameters):
+ """Special marker indicating that an assignment should
+ be recognized as a proper type alias definition by type
+ checkers.
+
+ For example::
+
+ Predicate: TypeAlias = Callable[..., bool]
+
+ It's invalid when used anywhere except as in the example above.
+ """
+ raise TypeError(f"{self} is not subscriptable")
+
+
+def _set_default(type_param, default):
+ type_param.has_default = lambda: default is not NoDefault
+ type_param.__default__ = default
+
+
+def _set_module(typevarlike):
+ # for pickling:
+ def_mod = _caller(depth=2)
+ if def_mod != 'typing_extensions':
+ typevarlike.__module__ = def_mod
+
+
+class _DefaultMixin:
+ """Mixin for TypeVarLike defaults."""
+
+ __slots__ = ()
+ __init__ = _set_default
+
+
+# Classes using this metaclass must provide a _backported_typevarlike ClassVar
+class _TypeVarLikeMeta(type):
+ def __instancecheck__(cls, __instance: Any) -> bool:
+ return isinstance(__instance, cls._backported_typevarlike)
+
+
+if _PEP_696_IMPLEMENTED:
+ from typing import TypeVar
+else:
+ # Add default and infer_variance parameters from PEP 696 and 695
+ class TypeVar(metaclass=_TypeVarLikeMeta):
+ """Type variable."""
+
+ _backported_typevarlike = typing.TypeVar
+
+ def __new__(cls, name, *constraints, bound=None,
+ covariant=False, contravariant=False,
+ default=NoDefault, infer_variance=False):
+ if hasattr(typing, "TypeAliasType"):
+ # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar
+ typevar = typing.TypeVar(name, *constraints, bound=bound,
+ covariant=covariant, contravariant=contravariant,
+ infer_variance=infer_variance)
+ else:
+ typevar = typing.TypeVar(name, *constraints, bound=bound,
+ covariant=covariant, contravariant=contravariant)
+ if infer_variance and (covariant or contravariant):
+ raise ValueError("Variance cannot be specified with infer_variance.")
+ typevar.__infer_variance__ = infer_variance
+
+ _set_default(typevar, default)
+ _set_module(typevar)
+
+ def _tvar_prepare_subst(alias, args):
+ if (
+ typevar.has_default()
+ and alias.__parameters__.index(typevar) == len(args)
+ ):
+ args += (typevar.__default__,)
+ return args
+
+ typevar.__typing_prepare_subst__ = _tvar_prepare_subst
+ return typevar
+
+ def __init_subclass__(cls) -> None:
+ raise TypeError(f"type '{__name__}.TypeVar' is not an acceptable base type")
+
+
+# Python 3.10+ has PEP 612
+if hasattr(typing, 'ParamSpecArgs'):
+ ParamSpecArgs = typing.ParamSpecArgs
+ ParamSpecKwargs = typing.ParamSpecKwargs
+# 3.9
+else:
+ class _Immutable:
+ """Mixin to indicate that object should not be copied."""
+ __slots__ = ()
+
+ def __copy__(self):
+ return self
+
+ def __deepcopy__(self, memo):
+ return self
+
+ class ParamSpecArgs(_Immutable):
+ """The args for a ParamSpec object.
+
+ Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.
+
+ ParamSpecArgs objects have a reference back to their ParamSpec:
+
+ P.args.__origin__ is P
+
+ This type is meant for runtime introspection and has no special meaning to
+ static type checkers.
+ """
+ def __init__(self, origin):
+ self.__origin__ = origin
+
+ def __repr__(self):
+ return f"{self.__origin__.__name__}.args"
+
+ def __eq__(self, other):
+ if not isinstance(other, ParamSpecArgs):
+ return NotImplemented
+ return self.__origin__ == other.__origin__
+
+ class ParamSpecKwargs(_Immutable):
+ """The kwargs for a ParamSpec object.
+
+ Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs.
+
+ ParamSpecKwargs objects have a reference back to their ParamSpec:
+
+ P.kwargs.__origin__ is P
+
+ This type is meant for runtime introspection and has no special meaning to
+ static type checkers.
+ """
+ def __init__(self, origin):
+ self.__origin__ = origin
+
+ def __repr__(self):
+ return f"{self.__origin__.__name__}.kwargs"
+
+ def __eq__(self, other):
+ if not isinstance(other, ParamSpecKwargs):
+ return NotImplemented
+ return self.__origin__ == other.__origin__
+
+
+if _PEP_696_IMPLEMENTED:
+ from typing import ParamSpec
+
+# 3.10+
+elif hasattr(typing, 'ParamSpec'):
+
+ # Add default parameter - PEP 696
+ class ParamSpec(metaclass=_TypeVarLikeMeta):
+ """Parameter specification."""
+
+ _backported_typevarlike = typing.ParamSpec
+
+ def __new__(cls, name, *, bound=None,
+ covariant=False, contravariant=False,
+ infer_variance=False, default=NoDefault):
+ if hasattr(typing, "TypeAliasType"):
+ # PEP 695 implemented, can pass infer_variance to typing.TypeVar
+ paramspec = typing.ParamSpec(name, bound=bound,
+ covariant=covariant,
+ contravariant=contravariant,
+ infer_variance=infer_variance)
+ else:
+ paramspec = typing.ParamSpec(name, bound=bound,
+ covariant=covariant,
+ contravariant=contravariant)
+ paramspec.__infer_variance__ = infer_variance
+
+ _set_default(paramspec, default)
+ _set_module(paramspec)
+
+ def _paramspec_prepare_subst(alias, args):
+ params = alias.__parameters__
+ i = params.index(paramspec)
+ if i == len(args) and paramspec.has_default():
+ args = [*args, paramspec.__default__]
+ if i >= len(args):
+ raise TypeError(f"Too few arguments for {alias}")
+ # Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
+ if len(params) == 1 and not typing._is_param_expr(args[0]):
+ assert i == 0
+ args = (args,)
+ # Convert lists to tuples to help other libraries cache the results.
+ elif isinstance(args[i], list):
+ args = (*args[:i], tuple(args[i]), *args[i + 1:])
+ return args
+
+ paramspec.__typing_prepare_subst__ = _paramspec_prepare_subst
+ return paramspec
+
+ def __init_subclass__(cls) -> None:
+ raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type")
+
+# 3.9
+else:
+
+ # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
+ class ParamSpec(list, _DefaultMixin):
+ """Parameter specification variable.
+
+ Usage::
+
+ P = ParamSpec('P')
+
+ Parameter specification variables exist primarily for the benefit of static
+ type checkers. They are used to forward the parameter types of one
+ callable to another callable, a pattern commonly found in higher order
+ functions and decorators. They are only valid when used in ``Concatenate``,
+ or s the first argument to ``Callable``. In Python 3.10 and higher,
+ they are also supported in user-defined Generics at runtime.
+ See class Generic for more information on generic types. An
+ example for annotating a decorator::
+
+ T = TypeVar('T')
+ P = ParamSpec('P')
+
+ def add_logging(f: Callable[P, T]) -> Callable[P, T]:
+ '''A type-safe decorator to add logging to a function.'''
+ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
+ logging.info(f'{f.__name__} was called')
+ return f(*args, **kwargs)
+ return inner
+
+ @add_logging
+ def add_two(x: float, y: float) -> float:
+ '''Add two numbers together.'''
+ return x + y
+
+ Parameter specification variables defined with covariant=True or
+ contravariant=True can be used to declare covariant or contravariant
+ generic types. These keyword arguments are valid, but their actual semantics
+ are yet to be decided. See PEP 612 for details.
+
+ Parameter specification variables can be introspected. e.g.:
+
+ P.__name__ == 'T'
+ P.__bound__ == None
+ P.__covariant__ == False
+ P.__contravariant__ == False
+
+ Note that only parameter specification variables defined in global scope can
+ be pickled.
+ """
+
+ # Trick Generic __parameters__.
+ __class__ = typing.TypeVar
+
+ @property
+ def args(self):
+ return ParamSpecArgs(self)
+
+ @property
+ def kwargs(self):
+ return ParamSpecKwargs(self)
+
+ def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
+ infer_variance=False, default=NoDefault):
+ list.__init__(self, [self])
+ self.__name__ = name
+ self.__covariant__ = bool(covariant)
+ self.__contravariant__ = bool(contravariant)
+ self.__infer_variance__ = bool(infer_variance)
+ if bound:
+ self.__bound__ = typing._type_check(bound, 'Bound must be a type.')
+ else:
+ self.__bound__ = None
+ _DefaultMixin.__init__(self, default)
+
+ # for pickling:
+ def_mod = _caller()
+ if def_mod != 'typing_extensions':
+ self.__module__ = def_mod
+
+ def __repr__(self):
+ if self.__infer_variance__:
+ prefix = ''
+ elif self.__covariant__:
+ prefix = '+'
+ elif self.__contravariant__:
+ prefix = '-'
+ else:
+ prefix = '~'
+ return prefix + self.__name__
+
+ def __hash__(self):
+ return object.__hash__(self)
+
+ def __eq__(self, other):
+ return self is other
+
+ def __reduce__(self):
+ return self.__name__
+
+ # Hack to get typing._type_check to pass.
+ def __call__(self, *args, **kwargs):
+ pass
+
+
+# 3.9
+if not hasattr(typing, 'Concatenate'):
+ # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
+
+ # 3.9.0-1
+ if not hasattr(typing, '_type_convert'):
+ def _type_convert(arg, module=None, *, allow_special_forms=False):
+ """For converting None to type(None), and strings to ForwardRef."""
+ if arg is None:
+ return type(None)
+ if isinstance(arg, str):
+ if sys.version_info <= (3, 9, 6):
+ return ForwardRef(arg)
+ if sys.version_info <= (3, 9, 7):
+ return ForwardRef(arg, module=module)
+ return ForwardRef(arg, module=module, is_class=allow_special_forms)
+ return arg
+ else:
+ _type_convert = typing._type_convert
+
+ class _ConcatenateGenericAlias(list):
+
+ # Trick Generic into looking into this for __parameters__.
+ __class__ = typing._GenericAlias
+
+ def __init__(self, origin, args):
+ super().__init__(args)
+ self.__origin__ = origin
+ self.__args__ = args
+
+ def __repr__(self):
+ _type_repr = typing._type_repr
+ return (f'{_type_repr(self.__origin__)}'
+ f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]')
+
+ def __hash__(self):
+ return hash((self.__origin__, self.__args__))
+
+ # Hack to get typing._type_check to pass in Generic.
+ def __call__(self, *args, **kwargs):
+ pass
+
+ @property
+ def __parameters__(self):
+ return tuple(
+ tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
+ )
+
+ # 3.9 used by __getitem__ below
+ def copy_with(self, params):
+ if isinstance(params[-1], _ConcatenateGenericAlias):
+ params = (*params[:-1], *params[-1].__args__)
+ elif isinstance(params[-1], (list, tuple)):
+ return (*params[:-1], *params[-1])
+ elif (not (params[-1] is ... or isinstance(params[-1], ParamSpec))):
+ raise TypeError("The last parameter to Concatenate should be a "
+ "ParamSpec variable or ellipsis.")
+ return self.__class__(self.__origin__, params)
+
+ # 3.9; accessed during GenericAlias.__getitem__ when substituting
+ def __getitem__(self, args):
+ if self.__origin__ in (Generic, Protocol):
+ # Can't subscript Generic[...] or Protocol[...].
+ raise TypeError(f"Cannot subscript already-subscripted {self}")
+ if not self.__parameters__:
+ raise TypeError(f"{self} is not a generic class")
+
+ if not isinstance(args, tuple):
+ args = (args,)
+ args = _unpack_args(*(_type_convert(p) for p in args))
+ params = self.__parameters__
+ for param in params:
+ prepare = getattr(param, "__typing_prepare_subst__", None)
+ if prepare is not None:
+ args = prepare(self, args)
+ # 3.9 & typing.ParamSpec
+ elif isinstance(param, ParamSpec):
+ i = params.index(param)
+ if (
+ i == len(args)
+ and getattr(param, '__default__', NoDefault) is not NoDefault
+ ):
+ args = [*args, param.__default__]
+ if i >= len(args):
+ raise TypeError(f"Too few arguments for {self}")
+ # Special case for Z[[int, str, bool]] == Z[int, str, bool]
+ if len(params) == 1 and not _is_param_expr(args[0]):
+ assert i == 0
+ args = (args,)
+ elif (
+ isinstance(args[i], list)
+ # 3.9
+ # This class inherits from list do not convert
+ and not isinstance(args[i], _ConcatenateGenericAlias)
+ ):
+ args = (*args[:i], tuple(args[i]), *args[i + 1:])
+
+ alen = len(args)
+ plen = len(params)
+ if alen != plen:
+ raise TypeError(
+ f"Too {'many' if alen > plen else 'few'} arguments for {self};"
+ f" actual {alen}, expected {plen}"
+ )
+
+ subst = dict(zip(self.__parameters__, args))
+ # determine new args
+ new_args = []
+ for arg in self.__args__:
+ if isinstance(arg, type):
+ new_args.append(arg)
+ continue
+ if isinstance(arg, TypeVar):
+ arg = subst[arg]
+ if (
+ (isinstance(arg, typing._GenericAlias) and _is_unpack(arg))
+ or (
+ hasattr(_types, "GenericAlias")
+ and isinstance(arg, _types.GenericAlias)
+ and getattr(arg, "__unpacked__", False)
+ )
+ ):
+ raise TypeError(f"{arg} is not valid as type argument")
+
+ elif isinstance(arg,
+ typing._GenericAlias
+ if not hasattr(_types, "GenericAlias") else
+ (typing._GenericAlias, _types.GenericAlias)
+ ):
+ subparams = arg.__parameters__
+ if subparams:
+ subargs = tuple(subst[x] for x in subparams)
+ arg = arg[subargs]
+ new_args.append(arg)
+ return self.copy_with(tuple(new_args))
+
+# 3.10+
+else:
+ _ConcatenateGenericAlias = typing._ConcatenateGenericAlias
+
+ # 3.10
+ if sys.version_info < (3, 11):
+
+ class _ConcatenateGenericAlias(typing._ConcatenateGenericAlias, _root=True):
+ # needed for checks in collections.abc.Callable to accept this class
+ __module__ = "typing"
+
+ def copy_with(self, params):
+ if isinstance(params[-1], (list, tuple)):
+ return (*params[:-1], *params[-1])
+ if isinstance(params[-1], typing._ConcatenateGenericAlias):
+ params = (*params[:-1], *params[-1].__args__)
+ elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)):
+ raise TypeError("The last parameter to Concatenate should be a "
+ "ParamSpec variable or ellipsis.")
+ return super(typing._ConcatenateGenericAlias, self).copy_with(params)
+
+ def __getitem__(self, args):
+ value = super().__getitem__(args)
+ if isinstance(value, tuple) and any(_is_unpack(t) for t in value):
+ return tuple(_unpack_args(*(n for n in value)))
+ return value
+
+
+# 3.9.2
+class _EllipsisDummy: ...
+
+
+# <=3.10
+def _create_concatenate_alias(origin, parameters):
+ if parameters[-1] is ... and sys.version_info < (3, 9, 2):
+ # Hack: Arguments must be types, replace it with one.
+ parameters = (*parameters[:-1], _EllipsisDummy)
+ if sys.version_info >= (3, 10, 3):
+ concatenate = _ConcatenateGenericAlias(origin, parameters,
+ _typevar_types=(TypeVar, ParamSpec),
+ _paramspec_tvars=True)
+ else:
+ concatenate = _ConcatenateGenericAlias(origin, parameters)
+ if parameters[-1] is not _EllipsisDummy:
+ return concatenate
+ # Remove dummy again
+ concatenate.__args__ = tuple(p if p is not _EllipsisDummy else ...
+ for p in concatenate.__args__)
+ if sys.version_info < (3, 10):
+ # backport needs __args__ adjustment only
+ return concatenate
+ concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__
+ if p is not _EllipsisDummy)
+ return concatenate
+
+
+# <=3.10
+@typing._tp_cache
+def _concatenate_getitem(self, parameters):
+ if parameters == ():
+ raise TypeError("Cannot take a Concatenate of no types.")
+ if not isinstance(parameters, tuple):
+ parameters = (parameters,)
+ if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
+ raise TypeError("The last parameter to Concatenate should be a "
+ "ParamSpec variable or ellipsis.")
+ msg = "Concatenate[arg, ...]: each arg must be a type."
+ parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]),
+ parameters[-1])
+ return _create_concatenate_alias(self, parameters)
+
+
+# 3.11+; Concatenate does not accept ellipsis in 3.10
+if sys.version_info >= (3, 11):
+ Concatenate = typing.Concatenate
+# <=3.10
+else:
+ @_ExtensionsSpecialForm
+ def Concatenate(self, parameters):
+ """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
+ higher order function which adds, removes or transforms parameters of a
+ callable.
+
+ For example::
+
+ Callable[Concatenate[int, P], int]
+
+ See PEP 612 for detailed information.
+ """
+ return _concatenate_getitem(self, parameters)
+
+
+# 3.10+
+if hasattr(typing, 'TypeGuard'):
+ TypeGuard = typing.TypeGuard
+# 3.9
+else:
+ @_ExtensionsSpecialForm
+ def TypeGuard(self, parameters):
+ """Special typing form used to annotate the return type of a user-defined
+ type guard function. ``TypeGuard`` only accepts a single type argument.
+ At runtime, functions marked this way should return a boolean.
+
+ ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
+ type checkers to determine a more precise type of an expression within a
+ program's code flow. Usually type narrowing is done by analyzing
+ conditional code flow and applying the narrowing to a block of code. The
+ conditional expression here is sometimes referred to as a "type guard".
+
+ Sometimes it would be convenient to use a user-defined boolean function
+ as a type guard. Such a function should use ``TypeGuard[...]`` as its
+ return type to alert static type checkers to this intention.
+
+ Using ``-> TypeGuard`` tells the static type checker that for a given
+ function:
+
+ 1. The return value is a boolean.
+ 2. If the return value is ``True``, the type of its argument
+ is the type inside ``TypeGuard``.
+
+ For example::
+
+ def is_str(val: Union[str, float]):
+ # "isinstance" type guard
+ if isinstance(val, str):
+ # Type of ``val`` is narrowed to ``str``
+ ...
+ else:
+ # Else, type of ``val`` is narrowed to ``float``.
+ ...
+
+ Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
+ form of ``TypeA`` (it can even be a wider form) and this may lead to
+ type-unsafe results. The main reason is to allow for things like
+ narrowing ``List[object]`` to ``List[str]`` even though the latter is not
+ a subtype of the former, since ``List`` is invariant. The responsibility of
+ writing type-safe type guards is left to the user.
+
+ ``TypeGuard`` also works with type variables. For more information, see
+ PEP 647 (User-Defined Type Guards).
+ """
+ item = typing._type_check(parameters, f'{self} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+
+# 3.13+
+if hasattr(typing, 'TypeIs'):
+ TypeIs = typing.TypeIs
+# <=3.12
+else:
+ @_ExtensionsSpecialForm
+ def TypeIs(self, parameters):
+ """Special typing form used to annotate the return type of a user-defined
+ type narrower function. ``TypeIs`` only accepts a single type argument.
+ At runtime, functions marked this way should return a boolean.
+
+ ``TypeIs`` aims to benefit *type narrowing* -- a technique used by static
+ type checkers to determine a more precise type of an expression within a
+ program's code flow. Usually type narrowing is done by analyzing
+ conditional code flow and applying the narrowing to a block of code. The
+ conditional expression here is sometimes referred to as a "type guard".
+
+ Sometimes it would be convenient to use a user-defined boolean function
+ as a type guard. Such a function should use ``TypeIs[...]`` as its
+ return type to alert static type checkers to this intention.
+
+ Using ``-> TypeIs`` tells the static type checker that for a given
+ function:
+
+ 1. The return value is a boolean.
+ 2. If the return value is ``True``, the type of its argument
+ is the intersection of the type inside ``TypeIs`` and the argument's
+ previously known type.
+
+ For example::
+
+ def is_awaitable(val: object) -> TypeIs[Awaitable[Any]]:
+ return hasattr(val, '__await__')
+
+ def f(val: Union[int, Awaitable[int]]) -> int:
+ if is_awaitable(val):
+ assert_type(val, Awaitable[int])
+ else:
+ assert_type(val, int)
+
+ ``TypeIs`` also works with type variables. For more information, see
+ PEP 742 (Narrowing types with TypeIs).
+ """
+ item = typing._type_check(parameters, f'{self} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+
+# 3.14+?
+if hasattr(typing, 'TypeForm'):
+ TypeForm = typing.TypeForm
+# <=3.13
+else:
+ class _TypeFormForm(_ExtensionsSpecialForm, _root=True):
+ # TypeForm(X) is equivalent to X but indicates to the type checker
+ # that the object is a TypeForm.
+ def __call__(self, obj, /):
+ return obj
+
+ @_TypeFormForm
+ def TypeForm(self, parameters):
+ """A special form representing the value that results from the evaluation
+ of a type expression. This value encodes the information supplied in the
+ type expression, and it represents the type described by that type expression.
+
+ When used in a type expression, TypeForm describes a set of type form objects.
+ It accepts a single type argument, which must be a valid type expression.
+ ``TypeForm[T]`` describes the set of all type form objects that represent
+ the type T or types that are assignable to T.
+
+ Usage:
+
+ def cast[T](typ: TypeForm[T], value: Any) -> T: ...
+
+ reveal_type(cast(int, "x")) # int
+
+ See PEP 747 for more information.
+ """
+ item = typing._type_check(parameters, f'{self} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+
+
+
+if hasattr(typing, "LiteralString"): # 3.11+
+ LiteralString = typing.LiteralString
+else:
+ @_SpecialForm
+ def LiteralString(self, params):
+ """Represents an arbitrary literal string.
+
+ Example::
+
+ from typing_extensions import LiteralString
+
+ def query(sql: LiteralString) -> ...:
+ ...
+
+ query("SELECT * FROM table") # ok
+ query(f"SELECT * FROM {input()}") # not ok
+
+ See PEP 675 for details.
+
+ """
+ raise TypeError(f"{self} is not subscriptable")
+
+
+if hasattr(typing, "Self"): # 3.11+
+ Self = typing.Self
+else:
+ @_SpecialForm
+ def Self(self, params):
+ """Used to spell the type of "self" in classes.
+
+ Example::
+
+ from typing import Self
+
+ class ReturnsSelf:
+ def parse(self, data: bytes) -> Self:
+ ...
+ return self
+
+ """
+
+ raise TypeError(f"{self} is not subscriptable")
+
+
+if hasattr(typing, "Never"): # 3.11+
+ Never = typing.Never
+else:
+ @_SpecialForm
+ def Never(self, params):
+ """The bottom type, a type that has no members.
+
+ This can be used to define a function that should never be
+ called, or a function that never returns::
+
+ from typing_extensions import Never
+
+ def never_call_me(arg: Never) -> None:
+ pass
+
+ def int_or_str(arg: int | str) -> None:
+ never_call_me(arg) # type checker error
+ match arg:
+ case int():
+ print("It's an int")
+ case str():
+ print("It's a str")
+ case _:
+ never_call_me(arg) # ok, arg is of type Never
+
+ """
+
+ raise TypeError(f"{self} is not subscriptable")
+
+
+if hasattr(typing, 'Required'): # 3.11+
+ Required = typing.Required
+ NotRequired = typing.NotRequired
+else: # <=3.10
+ @_ExtensionsSpecialForm
+ def Required(self, parameters):
+ """A special typing construct to mark a key of a total=False TypedDict
+ as required. For example:
+
+ class Movie(TypedDict, total=False):
+ title: Required[str]
+ year: int
+
+ m = Movie(
+ title='The Matrix', # typechecker error if key is omitted
+ year=1999,
+ )
+
+ There is no runtime checking that a required key is actually provided
+ when instantiating a related TypedDict.
+ """
+ item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+ @_ExtensionsSpecialForm
+ def NotRequired(self, parameters):
+ """A special typing construct to mark a key of a TypedDict as
+ potentially missing. For example:
+
+ class Movie(TypedDict):
+ title: str
+ year: NotRequired[int]
+
+ m = Movie(
+ title='The Matrix', # typechecker error if key is omitted
+ year=1999,
+ )
+ """
+ item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+
+if hasattr(typing, 'ReadOnly'):
+ ReadOnly = typing.ReadOnly
+else: # <=3.12
+ @_ExtensionsSpecialForm
+ def ReadOnly(self, parameters):
+ """A special typing construct to mark an item of a TypedDict as read-only.
+
+ For example:
+
+ class Movie(TypedDict):
+ title: ReadOnly[str]
+ year: int
+
+ def mutate_movie(m: Movie) -> None:
+ m["year"] = 1992 # allowed
+ m["title"] = "The Matrix" # typechecker error
+
+ There is no runtime checking for this property.
+ """
+ item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+
+_UNPACK_DOC = """\
+Type unpack operator.
+
+The type unpack operator takes the child types from some container type,
+such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For
+example:
+
+ # For some generic class `Foo`:
+ Foo[Unpack[tuple[int, str]]] # Equivalent to Foo[int, str]
+
+ Ts = TypeVarTuple('Ts')
+ # Specifies that `Bar` is generic in an arbitrary number of types.
+ # (Think of `Ts` as a tuple of an arbitrary number of individual
+ # `TypeVar`s, which the `Unpack` is 'pulling out' directly into the
+ # `Generic[]`.)
+ class Bar(Generic[Unpack[Ts]]): ...
+ Bar[int] # Valid
+ Bar[int, str] # Also valid
+
+From Python 3.11, this can also be done using the `*` operator:
+
+ Foo[*tuple[int, str]]
+ class Bar(Generic[*Ts]): ...
+
+The operator can also be used along with a `TypedDict` to annotate
+`**kwargs` in a function signature. For instance:
+
+ class Movie(TypedDict):
+ name: str
+ year: int
+
+ # This function expects two keyword arguments - *name* of type `str` and
+ # *year* of type `int`.
+ def foo(**kwargs: Unpack[Movie]): ...
+
+Note that there is only some runtime checking of this operator. Not
+everything the runtime allows may be accepted by static type checkers.
+
+For more information, see PEP 646 and PEP 692.
+"""
+
+
+if sys.version_info >= (3, 12): # PEP 692 changed the repr of Unpack[]
+ Unpack = typing.Unpack
+
+ def _is_unpack(obj):
+ return get_origin(obj) is Unpack
+
+else: # <=3.11
+ class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True):
+ def __init__(self, getitem):
+ super().__init__(getitem)
+ self.__doc__ = _UNPACK_DOC
+
+ class _UnpackAlias(typing._GenericAlias, _root=True):
+ if sys.version_info < (3, 11):
+ # needed for compatibility with Generic[Unpack[Ts]]
+ __class__ = typing.TypeVar
+
+ @property
+ def __typing_unpacked_tuple_args__(self):
+ assert self.__origin__ is Unpack
+ assert len(self.__args__) == 1
+ arg, = self.__args__
+ if isinstance(arg, (typing._GenericAlias, _types.GenericAlias)):
+ if arg.__origin__ is not tuple:
+ raise TypeError("Unpack[...] must be used with a tuple type")
+ return arg.__args__
+ return None
+
+ @property
+ def __typing_is_unpacked_typevartuple__(self):
+ assert self.__origin__ is Unpack
+ assert len(self.__args__) == 1
+ return isinstance(self.__args__[0], TypeVarTuple)
+
+ def __getitem__(self, args):
+ if self.__typing_is_unpacked_typevartuple__:
+ return args
+ return super().__getitem__(args)
+
+ @_UnpackSpecialForm
+ def Unpack(self, parameters):
+ item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+ return _UnpackAlias(self, (item,))
+
+ def _is_unpack(obj):
+ return isinstance(obj, _UnpackAlias)
+
+
+def _unpack_args(*args):
+ newargs = []
+ for arg in args:
+ subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
+ if subargs is not None and (not (subargs and subargs[-1] is ...)):
+ newargs.extend(subargs)
+ else:
+ newargs.append(arg)
+ return newargs
+
+
+if _PEP_696_IMPLEMENTED:
+ from typing import TypeVarTuple
+
+elif hasattr(typing, "TypeVarTuple"): # 3.11+
+
+ # Add default parameter - PEP 696
+ class TypeVarTuple(metaclass=_TypeVarLikeMeta):
+ """Type variable tuple."""
+
+ _backported_typevarlike = typing.TypeVarTuple
+
+ def __new__(cls, name, *, default=NoDefault):
+ tvt = typing.TypeVarTuple(name)
+ _set_default(tvt, default)
+ _set_module(tvt)
+
+ def _typevartuple_prepare_subst(alias, args):
+ params = alias.__parameters__
+ typevartuple_index = params.index(tvt)
+ for param in params[typevartuple_index + 1:]:
+ if isinstance(param, TypeVarTuple):
+ raise TypeError(
+ f"More than one TypeVarTuple parameter in {alias}"
+ )
+
+ alen = len(args)
+ plen = len(params)
+ left = typevartuple_index
+ right = plen - typevartuple_index - 1
+ var_tuple_index = None
+ fillarg = None
+ for k, arg in enumerate(args):
+ if not isinstance(arg, type):
+ subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
+ if subargs and len(subargs) == 2 and subargs[-1] is ...:
+ if var_tuple_index is not None:
+ raise TypeError(
+ "More than one unpacked "
+ "arbitrary-length tuple argument"
+ )
+ var_tuple_index = k
+ fillarg = subargs[0]
+ if var_tuple_index is not None:
+ left = min(left, var_tuple_index)
+ right = min(right, alen - var_tuple_index - 1)
+ elif left + right > alen:
+ raise TypeError(f"Too few arguments for {alias};"
+ f" actual {alen}, expected at least {plen - 1}")
+ if left == alen - right and tvt.has_default():
+ replacement = _unpack_args(tvt.__default__)
+ else:
+ replacement = args[left: alen - right]
+
+ return (
+ *args[:left],
+ *([fillarg] * (typevartuple_index - left)),
+ replacement,
+ *([fillarg] * (plen - right - left - typevartuple_index - 1)),
+ *args[alen - right:],
+ )
+
+ tvt.__typing_prepare_subst__ = _typevartuple_prepare_subst
+ return tvt
+
+ def __init_subclass__(self, *args, **kwds):
+ raise TypeError("Cannot subclass special typing classes")
+
+else: # <=3.10
+ class TypeVarTuple(_DefaultMixin):
+ """Type variable tuple.
+
+ Usage::
+
+ Ts = TypeVarTuple('Ts')
+
+ In the same way that a normal type variable is a stand-in for a single
+ type such as ``int``, a type variable *tuple* is a stand-in for a *tuple*
+ type such as ``Tuple[int, str]``.
+
+ Type variable tuples can be used in ``Generic`` declarations.
+ Consider the following example::
+
+ class Array(Generic[*Ts]): ...
+
+ The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
+ where ``T1`` and ``T2`` are type variables. To use these type variables
+ as type parameters of ``Array``, we must *unpack* the type variable tuple using
+ the star operator: ``*Ts``. The signature of ``Array`` then behaves
+ as if we had simply written ``class Array(Generic[T1, T2]): ...``.
+ In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
+ us to parameterise the class with an *arbitrary* number of type parameters.
+
+ Type variable tuples can be used anywhere a normal ``TypeVar`` can.
+ This includes class definitions, as shown above, as well as function
+ signatures and variable annotations::
+
+ class Array(Generic[*Ts]):
+
+ def __init__(self, shape: Tuple[*Ts]):
+ self._shape: Tuple[*Ts] = shape
+
+ def get_shape(self) -> Tuple[*Ts]:
+ return self._shape
+
+ shape = (Height(480), Width(640))
+ x: Array[Height, Width] = Array(shape)
+ y = abs(x) # Inferred type is Array[Height, Width]
+ z = x + x # ... is Array[Height, Width]
+ x.get_shape() # ... is tuple[Height, Width]
+
+ """
+
+ # Trick Generic __parameters__.
+ __class__ = typing.TypeVar
+
+ def __iter__(self):
+ yield self.__unpacked__
+
+ def __init__(self, name, *, default=NoDefault):
+ self.__name__ = name
+ _DefaultMixin.__init__(self, default)
+
+ # for pickling:
+ def_mod = _caller()
+ if def_mod != 'typing_extensions':
+ self.__module__ = def_mod
+
+ self.__unpacked__ = Unpack[self]
+
+ def __repr__(self):
+ return self.__name__
+
+ def __hash__(self):
+ return object.__hash__(self)
+
+ def __eq__(self, other):
+ return self is other
+
+ def __reduce__(self):
+ return self.__name__
+
+ def __init_subclass__(self, *args, **kwds):
+ if '_root' not in kwds:
+ raise TypeError("Cannot subclass special typing classes")
+
+
+if hasattr(typing, "reveal_type"): # 3.11+
+ reveal_type = typing.reveal_type
+else: # <=3.10
+ def reveal_type(obj: T, /) -> T:
+ """Reveal the inferred type of a variable.
+
+ When a static type checker encounters a call to ``reveal_type()``,
+ it will emit the inferred type of the argument::
+
+ x: int = 1
+ reveal_type(x)
+
+ Running a static type checker (e.g., ``mypy``) on this example
+ will produce output similar to 'Revealed type is "builtins.int"'.
+
+ At runtime, the function prints the runtime type of the
+ argument and returns it unchanged.
+
+ """
+ print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr)
+ return obj
+
+
+if hasattr(typing, "_ASSERT_NEVER_REPR_MAX_LENGTH"): # 3.11+
+ _ASSERT_NEVER_REPR_MAX_LENGTH = typing._ASSERT_NEVER_REPR_MAX_LENGTH
+else: # <=3.10
+ _ASSERT_NEVER_REPR_MAX_LENGTH = 100
+
+
+if hasattr(typing, "assert_never"): # 3.11+
+ assert_never = typing.assert_never
+else: # <=3.10
+ def assert_never(arg: Never, /) -> Never:
+ """Assert to the type checker that a line of code is unreachable.
+
+ Example::
+
+ def int_or_str(arg: int | str) -> None:
+ match arg:
+ case int():
+ print("It's an int")
+ case str():
+ print("It's a str")
+ case _:
+ assert_never(arg)
+
+ If a type checker finds that a call to assert_never() is
+ reachable, it will emit an error.
+
+ At runtime, this throws an exception when called.
+
+ """
+ value = repr(arg)
+ if len(value) > _ASSERT_NEVER_REPR_MAX_LENGTH:
+ value = value[:_ASSERT_NEVER_REPR_MAX_LENGTH] + '...'
+ raise AssertionError(f"Expected code to be unreachable, but got: {value}")
+
+
+if sys.version_info >= (3, 12): # 3.12+
+ # dataclass_transform exists in 3.11 but lacks the frozen_default parameter
+ dataclass_transform = typing.dataclass_transform
+else: # <=3.11
+ def dataclass_transform(
+ *,
+ eq_default: bool = True,
+ order_default: bool = False,
+ kw_only_default: bool = False,
+ frozen_default: bool = False,
+ field_specifiers: typing.Tuple[
+ typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]],
+ ...
+ ] = (),
+ **kwargs: typing.Any,
+ ) -> typing.Callable[[T], T]:
+ """Decorator that marks a function, class, or metaclass as providing
+ dataclass-like behavior.
+
+ Example:
+
+ from typing_extensions import dataclass_transform
+
+ _T = TypeVar("_T")
+
+ # Used on a decorator function
+ @dataclass_transform()
+ def create_model(cls: type[_T]) -> type[_T]:
+ ...
+ return cls
+
+ @create_model
+ class CustomerModel:
+ id: int
+ name: str
+
+ # Used on a base class
+ @dataclass_transform()
+ class ModelBase: ...
+
+ class CustomerModel(ModelBase):
+ id: int
+ name: str
+
+ # Used on a metaclass
+ @dataclass_transform()
+ class ModelMeta(type): ...
+
+ class ModelBase(metaclass=ModelMeta): ...
+
+ class CustomerModel(ModelBase):
+ id: int
+ name: str
+
+ Each of the ``CustomerModel`` classes defined in this example will now
+ behave similarly to a dataclass created with the ``@dataclasses.dataclass``
+ decorator. For example, the type checker will synthesize an ``__init__``
+ method.
+
+ The arguments to this decorator can be used to customize this behavior:
+ - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be
+ True or False if it is omitted by the caller.
+ - ``order_default`` indicates whether the ``order`` parameter is
+ assumed to be True or False if it is omitted by the caller.
+ - ``kw_only_default`` indicates whether the ``kw_only`` parameter is
+ assumed to be True or False if it is omitted by the caller.
+ - ``frozen_default`` indicates whether the ``frozen`` parameter is
+ assumed to be True or False if it is omitted by the caller.
+ - ``field_specifiers`` specifies a static list of supported classes
+ or functions that describe fields, similar to ``dataclasses.field()``.
+
+ At runtime, this decorator records its arguments in the
+ ``__dataclass_transform__`` attribute on the decorated object.
+
+ See PEP 681 for details.
+
+ """
+ def decorator(cls_or_fn):
+ cls_or_fn.__dataclass_transform__ = {
+ "eq_default": eq_default,
+ "order_default": order_default,
+ "kw_only_default": kw_only_default,
+ "frozen_default": frozen_default,
+ "field_specifiers": field_specifiers,
+ "kwargs": kwargs,
+ }
+ return cls_or_fn
+ return decorator
+
+
+if hasattr(typing, "override"): # 3.12+
+ override = typing.override
+else: # <=3.11
+ _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])
+
+ def override(arg: _F, /) -> _F:
+ """Indicate that a method is intended to override a method in a base class.
+
+ Usage:
+
+ class Base:
+ def method(self) -> None:
+ pass
+
+ class Child(Base):
+ @override
+ def method(self) -> None:
+ super().method()
+
+ When this decorator is applied to a method, the type checker will
+ validate that it overrides a method with the same name on a base class.
+ This helps prevent bugs that may occur when a base class is changed
+ without an equivalent change to a child class.
+
+ There is no runtime checking of these properties. The decorator
+ sets the ``__override__`` attribute to ``True`` on the decorated object
+ to allow runtime introspection.
+
+ See PEP 698 for details.
+
+ """
+ try:
+ arg.__override__ = True
+ except (AttributeError, TypeError):
+ # Skip the attribute silently if it is not writable.
+ # AttributeError happens if the object has __slots__ or a
+ # read-only property, TypeError if it's a builtin class.
+ pass
+ return arg
+
+
+# Python 3.13.3+ contains a fix for the wrapped __new__
+if sys.version_info >= (3, 13, 3):
+ deprecated = warnings.deprecated
+else:
+ _T = typing.TypeVar("_T")
+
+ class deprecated:
+ """Indicate that a class, function or overload is deprecated.
+
+ When this decorator is applied to an object, the type checker
+ will generate a diagnostic on usage of the deprecated object.
+
+ Usage:
+
+ @deprecated("Use B instead")
+ class A:
+ pass
+
+ @deprecated("Use g instead")
+ def f():
+ pass
+
+ @overload
+ @deprecated("int support is deprecated")
+ def g(x: int) -> int: ...
+ @overload
+ def g(x: str) -> int: ...
+
+ The warning specified by *category* will be emitted at runtime
+ on use of deprecated objects. For functions, that happens on calls;
+ for classes, on instantiation and on creation of subclasses.
+ If the *category* is ``None``, no warning is emitted at runtime.
+ The *stacklevel* determines where the
+ warning is emitted. If it is ``1`` (the default), the warning
+ is emitted at the direct caller of the deprecated object; if it
+ is higher, it is emitted further up the stack.
+ Static type checker behavior is not affected by the *category*
+ and *stacklevel* arguments.
+
+ The deprecation message passed to the decorator is saved in the
+ ``__deprecated__`` attribute on the decorated object.
+ If applied to an overload, the decorator
+ must be after the ``@overload`` decorator for the attribute to
+ exist on the overload as returned by ``get_overloads()``.
+
+ See PEP 702 for details.
+
+ """
+ def __init__(
+ self,
+ message: str,
+ /,
+ *,
+ category: typing.Optional[typing.Type[Warning]] = DeprecationWarning,
+ stacklevel: int = 1,
+ ) -> None:
+ if not isinstance(message, str):
+ raise TypeError(
+ "Expected an object of type str for 'message', not "
+ f"{type(message).__name__!r}"
+ )
+ self.message = message
+ self.category = category
+ self.stacklevel = stacklevel
+
+ def __call__(self, arg: _T, /) -> _T:
+ # Make sure the inner functions created below don't
+ # retain a reference to self.
+ msg = self.message
+ category = self.category
+ stacklevel = self.stacklevel
+ if category is None:
+ arg.__deprecated__ = msg
+ return arg
+ elif isinstance(arg, type):
+ import functools
+ from types import MethodType
+
+ original_new = arg.__new__
+
+ @functools.wraps(original_new)
+ def __new__(cls, /, *args, **kwargs):
+ if cls is arg:
+ warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
+ if original_new is not object.__new__:
+ return original_new(cls, *args, **kwargs)
+ # Mirrors a similar check in object.__new__.
+ elif cls.__init__ is object.__init__ and (args or kwargs):
+ raise TypeError(f"{cls.__name__}() takes no arguments")
+ else:
+ return original_new(cls)
+
+ arg.__new__ = staticmethod(__new__)
+
+ original_init_subclass = arg.__init_subclass__
+ # We need slightly different behavior if __init_subclass__
+ # is a bound method (likely if it was implemented in Python)
+ if isinstance(original_init_subclass, MethodType):
+ original_init_subclass = original_init_subclass.__func__
+
+ @functools.wraps(original_init_subclass)
+ def __init_subclass__(*args, **kwargs):
+ warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
+ return original_init_subclass(*args, **kwargs)
+
+ arg.__init_subclass__ = classmethod(__init_subclass__)
+ # Or otherwise, which likely means it's a builtin such as
+ # object's implementation of __init_subclass__.
+ else:
+ @functools.wraps(original_init_subclass)
+ def __init_subclass__(*args, **kwargs):
+ warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
+ return original_init_subclass(*args, **kwargs)
+
+ arg.__init_subclass__ = __init_subclass__
+
+ arg.__deprecated__ = __new__.__deprecated__ = msg
+ __init_subclass__.__deprecated__ = msg
+ return arg
+ elif callable(arg):
+ import asyncio.coroutines
+ import functools
+ import inspect
+
+ @functools.wraps(arg)
+ def wrapper(*args, **kwargs):
+ warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
+ return arg(*args, **kwargs)
+
+ if asyncio.coroutines.iscoroutinefunction(arg):
+ if sys.version_info >= (3, 12):
+ wrapper = inspect.markcoroutinefunction(wrapper)
+ else:
+ wrapper._is_coroutine = asyncio.coroutines._is_coroutine
+
+ arg.__deprecated__ = wrapper.__deprecated__ = msg
+ return wrapper
+ else:
+ raise TypeError(
+ "@deprecated decorator with non-None category must be applied to "
+ f"a class or callable, not {arg!r}"
+ )
+
+if sys.version_info < (3, 10):
+ def _is_param_expr(arg):
+ return arg is ... or isinstance(
+ arg, (tuple, list, ParamSpec, _ConcatenateGenericAlias)
+ )
+else:
+ def _is_param_expr(arg):
+ return arg is ... or isinstance(
+ arg,
+ (
+ tuple,
+ list,
+ ParamSpec,
+ _ConcatenateGenericAlias,
+ typing._ConcatenateGenericAlias,
+ ),
+ )
+
+
+# We have to do some monkey patching to deal with the dual nature of
+# Unpack/TypeVarTuple:
+# - We want Unpack to be a kind of TypeVar so it gets accepted in
+# Generic[Unpack[Ts]]
+# - We want it to *not* be treated as a TypeVar for the purposes of
+# counting generic parameters, so that when we subscript a generic,
+# the runtime doesn't try to substitute the Unpack with the subscripted type.
+if not hasattr(typing, "TypeVarTuple"):
+ def _check_generic(cls, parameters, elen=_marker):
+ """Check correct count for parameters of a generic cls (internal helper).
+
+ This gives a nice error message in case of count mismatch.
+ """
+ # If substituting a single ParamSpec with multiple arguments
+ # we do not check the count
+ if (inspect.isclass(cls) and issubclass(cls, typing.Generic)
+ and len(cls.__parameters__) == 1
+ and isinstance(cls.__parameters__[0], ParamSpec)
+ and parameters
+ and not _is_param_expr(parameters[0])
+ ):
+ # Generic modifies parameters variable, but here we cannot do this
+ return
+
+ if not elen:
+ raise TypeError(f"{cls} is not a generic class")
+ if elen is _marker:
+ if not hasattr(cls, "__parameters__") or not cls.__parameters__:
+ raise TypeError(f"{cls} is not a generic class")
+ elen = len(cls.__parameters__)
+ alen = len(parameters)
+ if alen != elen:
+ expect_val = elen
+ if hasattr(cls, "__parameters__"):
+ parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
+ num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters)
+ if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples):
+ return
+
+ # deal with TypeVarLike defaults
+ # required TypeVarLikes cannot appear after a defaulted one.
+ if alen < elen:
+ # since we validate TypeVarLike default in _collect_type_vars
+ # or _collect_parameters we can safely check parameters[alen]
+ if (
+ getattr(parameters[alen], '__default__', NoDefault)
+ is not NoDefault
+ ):
+ return
+
+ num_default_tv = sum(getattr(p, '__default__', NoDefault)
+ is not NoDefault for p in parameters)
+
+ elen -= num_default_tv
+
+ expect_val = f"at least {elen}"
+
+ things = "arguments" if sys.version_info >= (3, 10) else "parameters"
+ raise TypeError(f"Too {'many' if alen > elen else 'few'} {things}"
+ f" for {cls}; actual {alen}, expected {expect_val}")
+else:
+ # Python 3.11+
+
+ def _check_generic(cls, parameters, elen):
+ """Check correct count for parameters of a generic cls (internal helper).
+
+ This gives a nice error message in case of count mismatch.
+ """
+ if not elen:
+ raise TypeError(f"{cls} is not a generic class")
+ alen = len(parameters)
+ if alen != elen:
+ expect_val = elen
+ if hasattr(cls, "__parameters__"):
+ parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
+
+ # deal with TypeVarLike defaults
+ # required TypeVarLikes cannot appear after a defaulted one.
+ if alen < elen:
+ # since we validate TypeVarLike default in _collect_type_vars
+ # or _collect_parameters we can safely check parameters[alen]
+ if (
+ getattr(parameters[alen], '__default__', NoDefault)
+ is not NoDefault
+ ):
+ return
+
+ num_default_tv = sum(getattr(p, '__default__', NoDefault)
+ is not NoDefault for p in parameters)
+
+ elen -= num_default_tv
+
+ expect_val = f"at least {elen}"
+
+ raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments"
+ f" for {cls}; actual {alen}, expected {expect_val}")
+
+if not _PEP_696_IMPLEMENTED:
+ typing._check_generic = _check_generic
+
+
+def _has_generic_or_protocol_as_origin() -> bool:
+ try:
+ frame = sys._getframe(2)
+ # - Catch AttributeError: not all Python implementations have sys._getframe()
+ # - Catch ValueError: maybe we're called from an unexpected module
+ # and the call stack isn't deep enough
+ except (AttributeError, ValueError):
+ return False # err on the side of leniency
+ else:
+ # If we somehow get invoked from outside typing.py,
+ # also err on the side of leniency
+ if frame.f_globals.get("__name__") != "typing":
+ return False
+ origin = frame.f_locals.get("origin")
+ # Cannot use "in" because origin may be an object with a buggy __eq__ that
+ # throws an error.
+ return origin is typing.Generic or origin is Protocol or origin is typing.Protocol
+
+
+_TYPEVARTUPLE_TYPES = {TypeVarTuple, getattr(typing, "TypeVarTuple", None)}
+
+
+def _is_unpacked_typevartuple(x) -> bool:
+ if get_origin(x) is not Unpack:
+ return False
+ args = get_args(x)
+ return (
+ bool(args)
+ and len(args) == 1
+ and type(args[0]) in _TYPEVARTUPLE_TYPES
+ )
+
+
+# Python 3.11+ _collect_type_vars was renamed to _collect_parameters
+if hasattr(typing, '_collect_type_vars'):
+ def _collect_type_vars(types, typevar_types=None):
+ """Collect all type variable contained in types in order of
+ first appearance (lexicographic order). For example::
+
+ _collect_type_vars((T, List[S, T])) == (T, S)
+ """
+ if typevar_types is None:
+ typevar_types = typing.TypeVar
+ tvars = []
+
+ # A required TypeVarLike cannot appear after a TypeVarLike with a default
+ # if it was a direct call to `Generic[]` or `Protocol[]`
+ enforce_default_ordering = _has_generic_or_protocol_as_origin()
+ default_encountered = False
+
+ # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple
+ type_var_tuple_encountered = False
+
+ for t in types:
+ if _is_unpacked_typevartuple(t):
+ type_var_tuple_encountered = True
+ elif (
+ isinstance(t, typevar_types) and not isinstance(t, _UnpackAlias)
+ and t not in tvars
+ ):
+ if enforce_default_ordering:
+ has_default = getattr(t, '__default__', NoDefault) is not NoDefault
+ if has_default:
+ if type_var_tuple_encountered:
+ raise TypeError('Type parameter with a default'
+ ' follows TypeVarTuple')
+ default_encountered = True
+ elif default_encountered:
+ raise TypeError(f'Type parameter {t!r} without a default'
+ ' follows type parameter with a default')
+
+ tvars.append(t)
+ if _should_collect_from_parameters(t):
+ tvars.extend([t for t in t.__parameters__ if t not in tvars])
+ elif isinstance(t, tuple):
+ # Collect nested type_vars
+ # tuple wrapped by _prepare_paramspec_params(cls, params)
+ for x in t:
+ for collected in _collect_type_vars([x]):
+ if collected not in tvars:
+ tvars.append(collected)
+ return tuple(tvars)
+
+ typing._collect_type_vars = _collect_type_vars
+else:
+ def _collect_parameters(args):
+ """Collect all type variables and parameter specifications in args
+ in order of first appearance (lexicographic order).
+
+ For example::
+
+ assert _collect_parameters((T, Callable[P, T])) == (T, P)
+ """
+ parameters = []
+
+ # A required TypeVarLike cannot appear after a TypeVarLike with default
+ # if it was a direct call to `Generic[]` or `Protocol[]`
+ enforce_default_ordering = _has_generic_or_protocol_as_origin()
+ default_encountered = False
+
+ # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple
+ type_var_tuple_encountered = False
+
+ for t in args:
+ if isinstance(t, type):
+ # We don't want __parameters__ descriptor of a bare Python class.
+ pass
+ elif isinstance(t, tuple):
+ # `t` might be a tuple, when `ParamSpec` is substituted with
+ # `[T, int]`, or `[int, *Ts]`, etc.
+ for x in t:
+ for collected in _collect_parameters([x]):
+ if collected not in parameters:
+ parameters.append(collected)
+ elif hasattr(t, '__typing_subst__'):
+ if t not in parameters:
+ if enforce_default_ordering:
+ has_default = (
+ getattr(t, '__default__', NoDefault) is not NoDefault
+ )
+
+ if type_var_tuple_encountered and has_default:
+ raise TypeError('Type parameter with a default'
+ ' follows TypeVarTuple')
+
+ if has_default:
+ default_encountered = True
+ elif default_encountered:
+ raise TypeError(f'Type parameter {t!r} without a default'
+ ' follows type parameter with a default')
+
+ parameters.append(t)
+ else:
+ if _is_unpacked_typevartuple(t):
+ type_var_tuple_encountered = True
+ for x in getattr(t, '__parameters__', ()):
+ if x not in parameters:
+ parameters.append(x)
+
+ return tuple(parameters)
+
+ if not _PEP_696_IMPLEMENTED:
+ typing._collect_parameters = _collect_parameters
+
+# Backport typing.NamedTuple as it exists in Python 3.13.
+# In 3.11, the ability to define generic `NamedTuple`s was supported.
+# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
+# On 3.12, we added __orig_bases__ to call-based NamedTuples
+# On 3.13, we deprecated kwargs-based NamedTuples
+if sys.version_info >= (3, 13):
+ NamedTuple = typing.NamedTuple
+else:
+ def _make_nmtuple(name, types, module, defaults=()):
+ fields = [n for n, t in types]
+ annotations = {n: typing._type_check(t, f"field {n} annotation must be a type")
+ for n, t in types}
+ nm_tpl = collections.namedtuple(name, fields,
+ defaults=defaults, module=module)
+ nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations
+ return nm_tpl
+
+ _prohibited_namedtuple_fields = typing._prohibited
+ _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'})
+
+ class _NamedTupleMeta(type):
+ def __new__(cls, typename, bases, ns):
+ assert _NamedTuple in bases
+ for base in bases:
+ if base is not _NamedTuple and base is not typing.Generic:
+ raise TypeError(
+ 'can only inherit from a NamedTuple type and Generic')
+ bases = tuple(tuple if base is _NamedTuple else base for base in bases)
+ if "__annotations__" in ns:
+ types = ns["__annotations__"]
+ elif "__annotate__" in ns:
+ # TODO: Use inspect.VALUE here, and make the annotations lazily evaluated
+ types = ns["__annotate__"](1)
+ else:
+ types = {}
+ default_names = []
+ for field_name in types:
+ if field_name in ns:
+ default_names.append(field_name)
+ elif default_names:
+ raise TypeError(f"Non-default namedtuple field {field_name} "
+ f"cannot follow default field"
+ f"{'s' if len(default_names) > 1 else ''} "
+ f"{', '.join(default_names)}")
+ nm_tpl = _make_nmtuple(
+ typename, types.items(),
+ defaults=[ns[n] for n in default_names],
+ module=ns['__module__']
+ )
+ nm_tpl.__bases__ = bases
+ if typing.Generic in bases:
+ if hasattr(typing, '_generic_class_getitem'): # 3.12+
+ nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem)
+ else:
+ class_getitem = typing.Generic.__class_getitem__.__func__
+ nm_tpl.__class_getitem__ = classmethod(class_getitem)
+ # update from user namespace without overriding special namedtuple attributes
+ for key, val in ns.items():
+ if key in _prohibited_namedtuple_fields:
+ raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
+ elif key not in _special_namedtuple_fields:
+ if key not in nm_tpl._fields:
+ setattr(nm_tpl, key, ns[key])
+ try:
+ set_name = type(val).__set_name__
+ except AttributeError:
+ pass
+ else:
+ try:
+ set_name(val, nm_tpl, key)
+ except BaseException as e:
+ msg = (
+ f"Error calling __set_name__ on {type(val).__name__!r} "
+ f"instance {key!r} in {typename!r}"
+ )
+ # BaseException.add_note() existed on py311,
+ # but the __set_name__ machinery didn't start
+ # using add_note() until py312.
+ # Making sure exceptions are raised in the same way
+ # as in "normal" classes seems most important here.
+ if sys.version_info >= (3, 12):
+ e.add_note(msg)
+ raise
+ else:
+ raise RuntimeError(msg) from e
+
+ if typing.Generic in bases:
+ nm_tpl.__init_subclass__()
+ return nm_tpl
+
+ _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})
+
+ def _namedtuple_mro_entries(bases):
+ assert NamedTuple in bases
+ return (_NamedTuple,)
+
+ def NamedTuple(typename, fields=_marker, /, **kwargs):
+ """Typed version of namedtuple.
+
+ Usage::
+
+ class Employee(NamedTuple):
+ name: str
+ id: int
+
+ This is equivalent to::
+
+ Employee = collections.namedtuple('Employee', ['name', 'id'])
+
+ The resulting class has an extra __annotations__ attribute, giving a
+ dict that maps field names to types. (The field names are also in
+ the _fields attribute, which is part of the namedtuple API.)
+ An alternative equivalent functional syntax is also accepted::
+
+ Employee = NamedTuple('Employee', [('name', str), ('id', int)])
+ """
+ if fields is _marker:
+ if kwargs:
+ deprecated_thing = "Creating NamedTuple classes using keyword arguments"
+ deprecation_msg = (
+ "{name} is deprecated and will be disallowed in Python {remove}. "
+ "Use the class-based or functional syntax instead."
+ )
+ else:
+ deprecated_thing = "Failing to pass a value for the 'fields' parameter"
+ example = f"`{typename} = NamedTuple({typename!r}, [])`"
+ deprecation_msg = (
+ "{name} is deprecated and will be disallowed in Python {remove}. "
+ "To create a NamedTuple class with 0 fields "
+ "using the functional syntax, "
+ "pass an empty list, e.g. "
+ ) + example + "."
+ elif fields is None:
+ if kwargs:
+ raise TypeError(
+ "Cannot pass `None` as the 'fields' parameter "
+ "and also specify fields using keyword arguments"
+ )
+ else:
+ deprecated_thing = "Passing `None` as the 'fields' parameter"
+ example = f"`{typename} = NamedTuple({typename!r}, [])`"
+ deprecation_msg = (
+ "{name} is deprecated and will be disallowed in Python {remove}. "
+ "To create a NamedTuple class with 0 fields "
+ "using the functional syntax, "
+ "pass an empty list, e.g. "
+ ) + example + "."
+ elif kwargs:
+ raise TypeError("Either list of fields or keywords"
+ " can be provided to NamedTuple, not both")
+ if fields is _marker or fields is None:
+ warnings.warn(
+ deprecation_msg.format(name=deprecated_thing, remove="3.15"),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ fields = kwargs.items()
+ nt = _make_nmtuple(typename, fields, module=_caller())
+ nt.__orig_bases__ = (NamedTuple,)
+ return nt
+
+ NamedTuple.__mro_entries__ = _namedtuple_mro_entries
+
+
+if hasattr(collections.abc, "Buffer"):
+ Buffer = collections.abc.Buffer
+else:
+ class Buffer(abc.ABC): # noqa: B024
+ """Base class for classes that implement the buffer protocol.
+
+ The buffer protocol allows Python objects to expose a low-level
+ memory buffer interface. Before Python 3.12, it is not possible
+ to implement the buffer protocol in pure Python code, or even
+ to check whether a class implements the buffer protocol. In
+ Python 3.12 and higher, the ``__buffer__`` method allows access
+ to the buffer protocol from Python code, and the
+ ``collections.abc.Buffer`` ABC allows checking whether a class
+ implements the buffer protocol.
+
+ To indicate support for the buffer protocol in earlier versions,
+ inherit from this ABC, either in a stub file or at runtime,
+ or use ABC registration. This ABC provides no methods, because
+ there is no Python-accessible methods shared by pre-3.12 buffer
+ classes. It is useful primarily for static checks.
+
+ """
+
+ # As a courtesy, register the most common stdlib buffer classes.
+ Buffer.register(memoryview)
+ Buffer.register(bytearray)
+ Buffer.register(bytes)
+
+
+# Backport of types.get_original_bases, available on 3.12+ in CPython
+if hasattr(_types, "get_original_bases"):
+ get_original_bases = _types.get_original_bases
+else:
+ def get_original_bases(cls, /):
+ """Return the class's "original" bases prior to modification by `__mro_entries__`.
+
+ Examples::
+
+ from typing import TypeVar, Generic
+ from typing_extensions import NamedTuple, TypedDict
+
+ T = TypeVar("T")
+ class Foo(Generic[T]): ...
+ class Bar(Foo[int], float): ...
+ class Baz(list[str]): ...
+ Eggs = NamedTuple("Eggs", [("a", int), ("b", str)])
+ Spam = TypedDict("Spam", {"a": int, "b": str})
+
+ assert get_original_bases(Bar) == (Foo[int], float)
+ assert get_original_bases(Baz) == (list[str],)
+ assert get_original_bases(Eggs) == (NamedTuple,)
+ assert get_original_bases(Spam) == (TypedDict,)
+ assert get_original_bases(int) == (object,)
+ """
+ try:
+ return cls.__dict__.get("__orig_bases__", cls.__bases__)
+ except AttributeError:
+ raise TypeError(
+ f'Expected an instance of type, not {type(cls).__name__!r}'
+ ) from None
+
+
+# NewType is a class on Python 3.10+, making it pickleable
+# The error message for subclassing instances of NewType was improved on 3.11+
+if sys.version_info >= (3, 11):
+ NewType = typing.NewType
+else:
+ class NewType:
+ """NewType creates simple unique types with almost zero
+ runtime overhead. NewType(name, tp) is considered a subtype of tp
+ by static type checkers. At runtime, NewType(name, tp) returns
+ a dummy callable that simply returns its argument. Usage::
+ UserId = NewType('UserId', int)
+ def name_by_id(user_id: UserId) -> str:
+ ...
+ UserId('user') # Fails type check
+ name_by_id(42) # Fails type check
+ name_by_id(UserId(42)) # OK
+ num = UserId(5) + 1 # type: int
+ """
+
+ def __call__(self, obj, /):
+ return obj
+
+ def __init__(self, name, tp):
+ self.__qualname__ = name
+ if '.' in name:
+ name = name.rpartition('.')[-1]
+ self.__name__ = name
+ self.__supertype__ = tp
+ def_mod = _caller()
+ if def_mod != 'typing_extensions':
+ self.__module__ = def_mod
+
+ def __mro_entries__(self, bases):
+ # We defined __mro_entries__ to get a better error message
+ # if a user attempts to subclass a NewType instance. bpo-46170
+ supercls_name = self.__name__
+
+ class Dummy:
+ def __init_subclass__(cls):
+ subcls_name = cls.__name__
+ raise TypeError(
+ f"Cannot subclass an instance of NewType. "
+ f"Perhaps you were looking for: "
+ f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`"
+ )
+
+ return (Dummy,)
+
+ def __repr__(self):
+ return f'{self.__module__}.{self.__qualname__}'
+
+ def __reduce__(self):
+ return self.__qualname__
+
+ if sys.version_info >= (3, 10):
+ # PEP 604 methods
+ # It doesn't make sense to have these methods on Python <3.10
+
+ def __or__(self, other):
+ return typing.Union[self, other]
+
+ def __ror__(self, other):
+ return typing.Union[other, self]
+
+
+if sys.version_info >= (3, 14):
+ TypeAliasType = typing.TypeAliasType
+# <=3.13
+else:
+ if sys.version_info >= (3, 12):
+ # 3.12-3.13
+ def _is_unionable(obj):
+ """Corresponds to is_unionable() in unionobject.c in CPython."""
+ return obj is None or isinstance(obj, (
+ type,
+ _types.GenericAlias,
+ _types.UnionType,
+ typing.TypeAliasType,
+ TypeAliasType,
+ ))
+ else:
+ # <=3.11
+ def _is_unionable(obj):
+ """Corresponds to is_unionable() in unionobject.c in CPython."""
+ return obj is None or isinstance(obj, (
+ type,
+ _types.GenericAlias,
+ _types.UnionType,
+ TypeAliasType,
+ ))
+
+ if sys.version_info < (3, 10):
+ # Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582,
+ # so that we emulate the behaviour of `types.GenericAlias`
+ # on the latest versions of CPython
+ _ATTRIBUTE_DELEGATION_EXCLUSIONS = frozenset({
+ "__class__",
+ "__bases__",
+ "__origin__",
+ "__args__",
+ "__unpacked__",
+ "__parameters__",
+ "__typing_unpacked_tuple_args__",
+ "__mro_entries__",
+ "__reduce_ex__",
+ "__reduce__",
+ "__copy__",
+ "__deepcopy__",
+ })
+
+ class _TypeAliasGenericAlias(typing._GenericAlias, _root=True):
+ def __getattr__(self, attr):
+ if attr in _ATTRIBUTE_DELEGATION_EXCLUSIONS:
+ return object.__getattr__(self, attr)
+ return getattr(self.__origin__, attr)
+
+
+ class TypeAliasType:
+ """Create named, parameterized type aliases.
+
+ This provides a backport of the new `type` statement in Python 3.12:
+
+ type ListOrSet[T] = list[T] | set[T]
+
+ is equivalent to:
+
+ T = TypeVar("T")
+ ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,))
+
+ The name ListOrSet can then be used as an alias for the type it refers to.
+
+ The type_params argument should contain all the type parameters used
+ in the value of the type alias. If the alias is not generic, this
+ argument is omitted.
+
+ Static type checkers should only support type aliases declared using
+ TypeAliasType that follow these rules:
+
+ - The first argument (the name) must be a string literal.
+ - The TypeAliasType instance must be immediately assigned to a variable
+ of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid,
+ as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)').
+
+ """
+
+ def __init__(self, name: str, value, *, type_params=()):
+ if not isinstance(name, str):
+ raise TypeError("TypeAliasType name must be a string")
+ if not isinstance(type_params, tuple):
+ raise TypeError("type_params must be a tuple")
+ self.__value__ = value
+ self.__type_params__ = type_params
+
+ default_value_encountered = False
+ parameters = []
+ for type_param in type_params:
+ if (
+ not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec))
+ # <=3.11
+ # Unpack Backport passes isinstance(type_param, TypeVar)
+ or _is_unpack(type_param)
+ ):
+ raise TypeError(f"Expected a type param, got {type_param!r}")
+ has_default = (
+ getattr(type_param, '__default__', NoDefault) is not NoDefault
+ )
+ if default_value_encountered and not has_default:
+ raise TypeError(f"non-default type parameter '{type_param!r}'"
+ " follows default type parameter")
+ if has_default:
+ default_value_encountered = True
+ if isinstance(type_param, TypeVarTuple):
+ parameters.extend(type_param)
+ else:
+ parameters.append(type_param)
+ self.__parameters__ = tuple(parameters)
+ def_mod = _caller()
+ if def_mod != 'typing_extensions':
+ self.__module__ = def_mod
+ # Setting this attribute closes the TypeAliasType from further modification
+ self.__name__ = name
+
+ def __setattr__(self, name: str, value: object, /) -> None:
+ if hasattr(self, "__name__"):
+ self._raise_attribute_error(name)
+ super().__setattr__(name, value)
+
+ def __delattr__(self, name: str, /) -> Never:
+ self._raise_attribute_error(name)
+
+ def _raise_attribute_error(self, name: str) -> Never:
+ # Match the Python 3.12 error messages exactly
+ if name == "__name__":
+ raise AttributeError("readonly attribute")
+ elif name in {"__value__", "__type_params__", "__parameters__", "__module__"}:
+ raise AttributeError(
+ f"attribute '{name}' of 'typing.TypeAliasType' objects "
+ "is not writable"
+ )
+ else:
+ raise AttributeError(
+ f"'typing.TypeAliasType' object has no attribute '{name}'"
+ )
+
+ def __repr__(self) -> str:
+ return self.__name__
+
+ if sys.version_info < (3, 11):
+ def _check_single_param(self, param, recursion=0):
+ # Allow [], [int], [int, str], [int, ...], [int, T]
+ if param is ...:
+ return ...
+ if param is None:
+ return None
+ # Note in <= 3.9 _ConcatenateGenericAlias inherits from list
+ if isinstance(param, list) and recursion == 0:
+ return [self._check_single_param(arg, recursion+1)
+ for arg in param]
+ return typing._type_check(
+ param, f'Subscripting {self.__name__} requires a type.'
+ )
+
+ def _check_parameters(self, parameters):
+ if sys.version_info < (3, 11):
+ return tuple(
+ self._check_single_param(item)
+ for item in parameters
+ )
+ return tuple(typing._type_check(
+ item, f'Subscripting {self.__name__} requires a type.'
+ )
+ for item in parameters
+ )
+
+ def __getitem__(self, parameters):
+ if not self.__type_params__:
+ raise TypeError("Only generic type aliases are subscriptable")
+ if not isinstance(parameters, tuple):
+ parameters = (parameters,)
+ # Using 3.9 here will create problems with Concatenate
+ if sys.version_info >= (3, 10):
+ return _types.GenericAlias(self, parameters)
+ type_vars = _collect_type_vars(parameters)
+ parameters = self._check_parameters(parameters)
+ alias = _TypeAliasGenericAlias(self, parameters)
+ # alias.__parameters__ is not complete if Concatenate is present
+ # as it is converted to a list from which no parameters are extracted.
+ if alias.__parameters__ != type_vars:
+ alias.__parameters__ = type_vars
+ return alias
+
+ def __reduce__(self):
+ return self.__name__
+
+ def __init_subclass__(cls, *args, **kwargs):
+ raise TypeError(
+ "type 'typing_extensions.TypeAliasType' is not an acceptable base type"
+ )
+
+ # The presence of this method convinces typing._type_check
+ # that TypeAliasTypes are types.
+ def __call__(self):
+ raise TypeError("Type alias is not callable")
+
+ if sys.version_info >= (3, 10):
+ def __or__(self, right):
+ # For forward compatibility with 3.12, reject Unions
+ # that are not accepted by the built-in Union.
+ if not _is_unionable(right):
+ return NotImplemented
+ return typing.Union[self, right]
+
+ def __ror__(self, left):
+ if not _is_unionable(left):
+ return NotImplemented
+ return typing.Union[left, self]
+
+
+if hasattr(typing, "is_protocol"):
+ is_protocol = typing.is_protocol
+ get_protocol_members = typing.get_protocol_members
+else:
+ def is_protocol(tp: type, /) -> bool:
+ """Return True if the given type is a Protocol.
+
+ Example::
+
+ >>> from typing_extensions import Protocol, is_protocol
+ >>> class P(Protocol):
+ ... def a(self) -> str: ...
+ ... b: int
+ >>> is_protocol(P)
+ True
+ >>> is_protocol(int)
+ False
+ """
+ return (
+ isinstance(tp, type)
+ and getattr(tp, '_is_protocol', False)
+ and tp is not Protocol
+ and tp is not typing.Protocol
+ )
+
+ def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]:
+ """Return the set of members defined in a Protocol.
+
+ Example::
+
+ >>> from typing_extensions import Protocol, get_protocol_members
+ >>> class P(Protocol):
+ ... def a(self) -> str: ...
+ ... b: int
+ >>> get_protocol_members(P)
+ frozenset({'a', 'b'})
+
+ Raise a TypeError for arguments that are not Protocols.
+ """
+ if not is_protocol(tp):
+ raise TypeError(f'{tp!r} is not a Protocol')
+ if hasattr(tp, '__protocol_attrs__'):
+ return frozenset(tp.__protocol_attrs__)
+ return frozenset(_get_protocol_attrs(tp))
+
+
+if hasattr(typing, "Doc"):
+ Doc = typing.Doc
+else:
+ class Doc:
+ """Define the documentation of a type annotation using ``Annotated``, to be
+ used in class attributes, function and method parameters, return values,
+ and variables.
+
+ The value should be a positional-only string literal to allow static tools
+ like editors and documentation generators to use it.
+
+ This complements docstrings.
+
+ The string value passed is available in the attribute ``documentation``.
+
+ Example::
+
+ >>> from typing_extensions import Annotated, Doc
+ >>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...
+ """
+ def __init__(self, documentation: str, /) -> None:
+ self.documentation = documentation
+
+ def __repr__(self) -> str:
+ return f"Doc({self.documentation!r})"
+
+ def __hash__(self) -> int:
+ return hash(self.documentation)
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, Doc):
+ return NotImplemented
+ return self.documentation == other.documentation
+
+
+_CapsuleType = getattr(_types, "CapsuleType", None)
+
+if _CapsuleType is None:
+ try:
+ import _socket
+ except ImportError:
+ pass
+ else:
+ _CAPI = getattr(_socket, "CAPI", None)
+ if _CAPI is not None:
+ _CapsuleType = type(_CAPI)
+
+if _CapsuleType is not None:
+ CapsuleType = _CapsuleType
+ __all__.append("CapsuleType")
+
+
+if sys.version_info >= (3,14):
+ from annotationlib import Format, get_annotations
+else:
+ class Format(enum.IntEnum):
+ VALUE = 1
+ VALUE_WITH_FAKE_GLOBALS = 2
+ FORWARDREF = 3
+ STRING = 4
+
+ def get_annotations(obj, *, globals=None, locals=None, eval_str=False,
+ format=Format.VALUE):
+ """Compute the annotations dict for an object.
+
+ obj may be a callable, class, or module.
+ Passing in an object of any other type raises TypeError.
+
+ Returns a dict. get_annotations() returns a new dict every time
+ it's called; calling it twice on the same object will return two
+ different but equivalent dicts.
+
+ This is a backport of `inspect.get_annotations`, which has been
+ in the standard library since Python 3.10. See the standard library
+ documentation for more:
+
+ https://docs.python.org/3/library/inspect.html#inspect.get_annotations
+
+ This backport adds the *format* argument introduced by PEP 649. The
+ three formats supported are:
+ * VALUE: the annotations are returned as-is. This is the default and
+ it is compatible with the behavior on previous Python versions.
+ * FORWARDREF: return annotations as-is if possible, but replace any
+ undefined names with ForwardRef objects. The implementation proposed by
+ PEP 649 relies on language changes that cannot be backported; the
+ typing-extensions implementation simply returns the same result as VALUE.
+ * STRING: return annotations as strings, in a format close to the original
+ source. Again, this behavior cannot be replicated directly in a backport.
+ As an approximation, typing-extensions retrieves the annotations under
+ VALUE semantics and then stringifies them.
+
+ The purpose of this backport is to allow users who would like to use
+ FORWARDREF or STRING semantics once PEP 649 is implemented, but who also
+ want to support earlier Python versions, to simply write:
+
+ typing_extensions.get_annotations(obj, format=Format.FORWARDREF)
+
+ """
+ format = Format(format)
+ if format is Format.VALUE_WITH_FAKE_GLOBALS:
+ raise ValueError(
+ "The VALUE_WITH_FAKE_GLOBALS format is for internal use only"
+ )
+
+ if eval_str and format is not Format.VALUE:
+ raise ValueError("eval_str=True is only supported with format=Format.VALUE")
+
+ if isinstance(obj, type):
+ # class
+ obj_dict = getattr(obj, '__dict__', None)
+ if obj_dict and hasattr(obj_dict, 'get'):
+ ann = obj_dict.get('__annotations__', None)
+ if isinstance(ann, _types.GetSetDescriptorType):
+ ann = None
+ else:
+ ann = None
+
+ obj_globals = None
+ module_name = getattr(obj, '__module__', None)
+ if module_name:
+ module = sys.modules.get(module_name, None)
+ if module:
+ obj_globals = getattr(module, '__dict__', None)
+ obj_locals = dict(vars(obj))
+ unwrap = obj
+ elif isinstance(obj, _types.ModuleType):
+ # module
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = obj.__dict__
+ obj_locals = None
+ unwrap = None
+ elif callable(obj):
+ # this includes types.Function, types.BuiltinFunctionType,
+ # types.BuiltinMethodType, functools.partial, functools.singledispatch,
+ # "class funclike" from Lib/test/test_inspect... on and on it goes.
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = getattr(obj, '__globals__', None)
+ obj_locals = None
+ unwrap = obj
+ elif hasattr(obj, '__annotations__'):
+ ann = obj.__annotations__
+ obj_globals = obj_locals = unwrap = None
+ else:
+ raise TypeError(f"{obj!r} is not a module, class, or callable.")
+
+ if ann is None:
+ return {}
+
+ if not isinstance(ann, dict):
+ raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
+
+ if not ann:
+ return {}
+
+ if not eval_str:
+ if format is Format.STRING:
+ return {
+ key: value if isinstance(value, str) else typing._type_repr(value)
+ for key, value in ann.items()
+ }
+ return dict(ann)
+
+ if unwrap is not None:
+ while True:
+ if hasattr(unwrap, '__wrapped__'):
+ unwrap = unwrap.__wrapped__
+ continue
+ if isinstance(unwrap, functools.partial):
+ unwrap = unwrap.func
+ continue
+ break
+ if hasattr(unwrap, "__globals__"):
+ obj_globals = unwrap.__globals__
+
+ if globals is None:
+ globals = obj_globals
+ if locals is None:
+ locals = obj_locals or {}
+
+ # "Inject" type parameters into the local namespace
+ # (unless they are shadowed by assignments *in* the local namespace),
+ # as a way of emulating annotation scopes when calling `eval()`
+ if type_params := getattr(obj, "__type_params__", ()):
+ locals = {param.__name__: param for param in type_params} | locals
+
+ return_value = {key:
+ value if not isinstance(value, str) else eval(value, globals, locals)
+ for key, value in ann.items() }
+ return return_value
+
+
+if hasattr(typing, "evaluate_forward_ref"):
+ evaluate_forward_ref = typing.evaluate_forward_ref
+else:
+ # Implements annotationlib.ForwardRef.evaluate
+ def _eval_with_owner(
+ forward_ref, *, owner=None, globals=None, locals=None, type_params=None
+ ):
+ if forward_ref.__forward_evaluated__:
+ return forward_ref.__forward_value__
+ if getattr(forward_ref, "__cell__", None) is not None:
+ try:
+ value = forward_ref.__cell__.cell_contents
+ except ValueError:
+ pass
+ else:
+ forward_ref.__forward_evaluated__ = True
+ forward_ref.__forward_value__ = value
+ return value
+ if owner is None:
+ owner = getattr(forward_ref, "__owner__", None)
+
+ if (
+ globals is None
+ and getattr(forward_ref, "__forward_module__", None) is not None
+ ):
+ globals = getattr(
+ sys.modules.get(forward_ref.__forward_module__, None), "__dict__", None
+ )
+ if globals is None:
+ globals = getattr(forward_ref, "__globals__", None)
+ if globals is None:
+ if isinstance(owner, type):
+ module_name = getattr(owner, "__module__", None)
+ if module_name:
+ module = sys.modules.get(module_name, None)
+ if module:
+ globals = getattr(module, "__dict__", None)
+ elif isinstance(owner, _types.ModuleType):
+ globals = getattr(owner, "__dict__", None)
+ elif callable(owner):
+ globals = getattr(owner, "__globals__", None)
+
+ # If we pass None to eval() below, the globals of this module are used.
+ if globals is None:
+ globals = {}
+
+ if locals is None:
+ locals = {}
+ if isinstance(owner, type):
+ locals.update(vars(owner))
+
+ if type_params is None and owner is not None:
+ # "Inject" type parameters into the local namespace
+ # (unless they are shadowed by assignments *in* the local namespace),
+ # as a way of emulating annotation scopes when calling `eval()`
+ type_params = getattr(owner, "__type_params__", None)
+
+ # type parameters require some special handling,
+ # as they exist in their own scope
+ # but `eval()` does not have a dedicated parameter for that scope.
+ # For classes, names in type parameter scopes should override
+ # names in the global scope (which here are called `localns`!),
+ # but should in turn be overridden by names in the class scope
+ # (which here are called `globalns`!)
+ if type_params is not None:
+ globals = dict(globals)
+ locals = dict(locals)
+ for param in type_params:
+ param_name = param.__name__
+ if (
+ _FORWARD_REF_HAS_CLASS and not forward_ref.__forward_is_class__
+ ) or param_name not in globals:
+ globals[param_name] = param
+ locals.pop(param_name, None)
+
+ arg = forward_ref.__forward_arg__
+ if arg.isidentifier() and not keyword.iskeyword(arg):
+ if arg in locals:
+ value = locals[arg]
+ elif arg in globals:
+ value = globals[arg]
+ elif hasattr(builtins, arg):
+ return getattr(builtins, arg)
+ else:
+ raise NameError(arg)
+ else:
+ code = forward_ref.__forward_code__
+ value = eval(code, globals, locals)
+ forward_ref.__forward_evaluated__ = True
+ forward_ref.__forward_value__ = value
+ return value
+
+ def evaluate_forward_ref(
+ forward_ref,
+ *,
+ owner=None,
+ globals=None,
+ locals=None,
+ type_params=None,
+ format=None,
+ _recursive_guard=frozenset(),
+ ):
+ """Evaluate a forward reference as a type hint.
+
+ This is similar to calling the ForwardRef.evaluate() method,
+ but unlike that method, evaluate_forward_ref() also:
+
+ * Recursively evaluates forward references nested within the type hint.
+ * Rejects certain objects that are not valid type hints.
+ * Replaces type hints that evaluate to None with types.NoneType.
+ * Supports the *FORWARDREF* and *STRING* formats.
+
+ *forward_ref* must be an instance of ForwardRef. *owner*, if given,
+ should be the object that holds the annotations that the forward reference
+ derived from, such as a module, class object, or function. It is used to
+ infer the namespaces to use for looking up names. *globals* and *locals*
+ can also be explicitly given to provide the global and local namespaces.
+ *type_params* is a tuple of type parameters that are in scope when
+ evaluating the forward reference. This parameter must be provided (though
+ it may be an empty tuple) if *owner* is not given and the forward reference
+ does not already have an owner set. *format* specifies the format of the
+ annotation and is a member of the annotationlib.Format enum.
+
+ """
+ if format == Format.STRING:
+ return forward_ref.__forward_arg__
+ if forward_ref.__forward_arg__ in _recursive_guard:
+ return forward_ref
+
+ # Evaluate the forward reference
+ try:
+ value = _eval_with_owner(
+ forward_ref,
+ owner=owner,
+ globals=globals,
+ locals=locals,
+ type_params=type_params,
+ )
+ except NameError:
+ if format == Format.FORWARDREF:
+ return forward_ref
+ else:
+ raise
+
+ if isinstance(value, str):
+ value = ForwardRef(value)
+
+ # Recursively evaluate the type
+ if isinstance(value, ForwardRef):
+ if getattr(value, "__forward_module__", True) is not None:
+ globals = None
+ return evaluate_forward_ref(
+ value,
+ globals=globals,
+ locals=locals,
+ type_params=type_params, owner=owner,
+ _recursive_guard=_recursive_guard, format=format
+ )
+ if sys.version_info < (3, 12, 5) and type_params:
+ # Make use of type_params
+ locals = dict(locals) if locals else {}
+ for tvar in type_params:
+ if tvar.__name__ not in locals: # lets not overwrite something present
+ locals[tvar.__name__] = tvar
+ if sys.version_info < (3, 12, 5):
+ return typing._eval_type(
+ value,
+ globals,
+ locals,
+ recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+ )
+ else:
+ return typing._eval_type(
+ value,
+ globals,
+ locals,
+ type_params,
+ recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+ )
+
+
+class Sentinel:
+ """Create a unique sentinel object.
+
+ *name* should be the name of the variable to which the return value shall be assigned.
+
+ *repr*, if supplied, will be used for the repr of the sentinel object.
+ If not provided, "" will be used.
+ """
+
+ def __init__(
+ self,
+ name: str,
+ repr: typing.Optional[str] = None,
+ ):
+ self._name = name
+ self._repr = repr if repr is not None else f'<{name}>'
+
+ def __repr__(self):
+ return self._repr
+
+ if sys.version_info < (3, 11):
+ # The presence of this method convinces typing._type_check
+ # that Sentinels are types.
+ def __call__(self, *args, **kwargs):
+ raise TypeError(f"{type(self).__name__!r} object is not callable")
+
+ if sys.version_info >= (3, 10):
+ def __or__(self, other):
+ return typing.Union[self, other]
+
+ def __ror__(self, other):
+ return typing.Union[other, self]
+
+ def __getstate__(self):
+ raise TypeError(f"Cannot pickle {type(self).__name__!r} object")
+
+
+# Aliases for items that are in typing in all supported versions.
+# We use hasattr() checks so this library will continue to import on
+# future versions of Python that may remove these names.
+_typing_names = [
+ "AbstractSet",
+ "AnyStr",
+ "BinaryIO",
+ "Callable",
+ "Collection",
+ "Container",
+ "Dict",
+ "FrozenSet",
+ "Hashable",
+ "IO",
+ "ItemsView",
+ "Iterable",
+ "Iterator",
+ "KeysView",
+ "List",
+ "Mapping",
+ "MappingView",
+ "Match",
+ "MutableMapping",
+ "MutableSequence",
+ "MutableSet",
+ "Optional",
+ "Pattern",
+ "Reversible",
+ "Sequence",
+ "Set",
+ "Sized",
+ "TextIO",
+ "Tuple",
+ "Union",
+ "ValuesView",
+ "cast",
+ "no_type_check",
+ "no_type_check_decorator",
+ # This is private, but it was defined by typing_extensions for a long time
+ # and some users rely on it.
+ "_AnnotatedAlias",
+]
+globals().update(
+ {name: getattr(typing, name) for name in _typing_names if hasattr(typing, name)}
+)
+# These are defined unconditionally because they are used in
+# typing-extensions itself.
+Generic = typing.Generic
+ForwardRef = typing.ForwardRef
+Annotated = typing.Annotated
diff --git a/python/src/__init__.py b/python/code/wypp/__init__.py
similarity index 82%
rename from python/src/__init__.py
rename to python/code/wypp/__init__.py
index 5117affb..a25b4adc 100644
--- a/python/src/__init__.py
+++ b/python/code/wypp/__init__.py
@@ -1,4 +1,9 @@
-from . import writeYourProgram as w
+try:
+ from . import writeYourProgram as w
+except (ImportError, ModuleNotFoundError):
+ import writeYourProgram as w
+
+import typing
# Exported names that are available for star imports (in alphabetic order)
Any = w.Any
@@ -26,11 +31,14 @@
intPositive = w.intPositive
math = w.math
nat = w.nat
-record = w.record
+
+@typing.dataclass_transform()
+def record(cls=None, mutable=False, globals={}, locals={}):
+ return w.record(cls, mutable, globals, locals)
+
T = w.T
todo = w.todo
U = w.U
-unchecked = w.unchecked
V = w.V
__all__ = [
@@ -63,7 +71,6 @@
'T',
'todo',
'U',
- 'unchecked',
'V'
]
@@ -74,3 +81,4 @@
printTestResults = w.printTestResults
resetTestCount = w.resetTestCount
deepEq = w.deepEq
+wrapTypecheck = w.wrapTypecheck
diff --git a/python/code/wypp/ansi.py b/python/code/wypp/ansi.py
new file mode 100644
index 00000000..a919d0c0
--- /dev/null
+++ b/python/code/wypp/ansi.py
@@ -0,0 +1,32 @@
+RESET = "\u001b[0;0m"
+BOLD = "\u001b[1m"
+REVERSE = "\u001b[2m"
+
+BLACK = "\u001b[0;30m"
+BLUE = "\u001b[0;34m"
+GREEN = "\u001b[0;32m"
+CYAN = "\u001b[0;36m"
+RED = "\u001b[0;31m"
+PURPLE = "\u001b[0;35m"
+BROWN = "\u001b[0;33m"
+GRAY = "\u001b[0;37m"
+DARK_GRAY = "\u001b[1;30m"
+LIGHT_BLUE = "\u001b[1;34m"
+LIGHT_GREEN = "\u001b[1;32m"
+LIGHT_CYAN = "\u001b[1;36m"
+LIGHT_RED = "\u001b[1;31m"
+LIGHT_PURPLE = "\u001b[1;35m"
+YELLOW = "\u001b[1;33m"
+WHITE = "\u001b[1;37m"
+
+def color(s, color):
+ return color + s + RESET
+
+def green(s):
+ return color(s, GREEN)
+
+def red(s):
+ return color(s, RED + BOLD)
+
+def blue(s):
+ return color(s, BLUE + BOLD)
diff --git a/python/code/wypp/cmdlineArgs.py b/python/code/wypp/cmdlineArgs.py
new file mode 100644
index 00000000..7ad2700d
--- /dev/null
+++ b/python/code/wypp/cmdlineArgs.py
@@ -0,0 +1,53 @@
+import argparse
+import sys
+import utils
+from myLogging import *
+
+def parseCmdlineArgs(argList):
+ parser = argparse.ArgumentParser(description='Run Your Program!',
+ formatter_class=argparse.RawTextHelpFormatter)
+ parser.add_argument('--check-runnable', dest='checkRunnable', action='store_const',
+ const=True, default=False,
+ help='Abort with exit code 1 if loading the file raises errors')
+ parser.add_argument('--check', dest='check', action='store_const',
+ const=True, default=False,
+ help='Abort with exit code 1 if there are test errors.')
+ parser.add_argument('--verbose', dest='verbose', action='store_const',
+ const=True, default=False,
+ help='Be verbose')
+ parser.add_argument('--debug', dest='debug', action='store_const',
+ const=True, default=False,
+ help='Enable debugging')
+ parser.add_argument('--quiet', dest='quiet', action='store_const',
+ const=True, default=False, help='Be extra quiet')
+ parser.add_argument('--lang', dest='lang',
+ type=str, help='Display error messages in this language (either en or de).')
+ parser.add_argument('--no-clear', dest='noClear', action='store_const',
+ const=True, default=False, help='Do not clear the terminal')
+ parser.add_argument('--test-file', dest='testFile',
+ type=str, help='Run additional tests contained in this file.')
+ parser.add_argument('--extra-dir', dest='extraDirs', action='append', type=str,
+ help='Also typechecks files contained in the given directory.\n' \
+ 'By default, only files in the same directory as the main file are\n' \
+ 'checked.')
+ parser.add_argument('--change-directory', dest='changeDir', action='store_const',
+ const=True, default=False,
+ help='Change to the directory of FILE before running')
+ parser.add_argument('--interactive', dest='interactive', action='store_const',
+ const=True, default=False,
+ help='Run REPL after the programm has finished')
+ parser.add_argument('--no-typechecking', dest='checkTypes', action='store_const',
+ const=False, default=True,
+ help='Do not check type annotations')
+ parser.add_argument('file', metavar='FILE',
+ help='The file to run', nargs='?')
+ if argList is None:
+ argList = sys.argv[1:]
+ try:
+ args, restArgs = parser.parse_known_args(argList)
+ except SystemExit as ex:
+ utils.die(ex.code)
+ if args.file and not args.file.endswith('.py'):
+ printStderr(f'ERROR: file {args.file} is not a python file')
+ utils.die()
+ return (args, restArgs)
diff --git a/python/code/wypp/constants.py b/python/code/wypp/constants.py
new file mode 100644
index 00000000..d7562644
--- /dev/null
+++ b/python/code/wypp/constants.py
@@ -0,0 +1,6 @@
+import os
+
+SOURCE_DIR = os.path.normpath(os.path.dirname(__file__))
+CODE_DIR = os.path.dirname(SOURCE_DIR)
+
+# print(f'SOURCE_DIR: {SOURCE_DIR}, CODE_DIR: {CODE_DIR}')
diff --git a/python/src/debug.py b/python/code/wypp/debug.py
similarity index 100%
rename from python/src/debug.py
rename to python/code/wypp/debug.py
diff --git a/python/src/drawingLib.py b/python/code/wypp/drawingLib.py
similarity index 100%
rename from python/src/drawingLib.py
rename to python/code/wypp/drawingLib.py
diff --git a/python/code/wypp/errors.py b/python/code/wypp/errors.py
new file mode 100644
index 00000000..90cdde07
--- /dev/null
+++ b/python/code/wypp/errors.py
@@ -0,0 +1,288 @@
+from __future__ import annotations
+from typing import *
+import abc
+import inspect
+import location
+import i18n
+from renderTy import renderTy
+
+class WyppError(abc.ABC):
+ def __init__(self, extraFrames: list[inspect.FrameInfo] = []):
+ self.extraFrames = extraFrames[:]
+
+def renderLoc(loc: location.Loc) -> str:
+ s = '\n'.join([l.highlight() for l in location.highlightedLines(loc)])
+ return s.rstrip()
+
+def renderStr(s: str, quote: str):
+ x = repr(s)
+ return quote + x[1:-1] + quote
+
+def renderGiven(givenValue: Any, givenLoc: Optional[location.Loc]) -> str:
+ if type(givenValue) == str and givenLoc is not None:
+ code = renderLoc(givenLoc)
+ single = renderStr(givenValue, "'")
+ double = renderStr(givenValue, '"')
+ if double in code and single not in code:
+ return double
+ elif '"' in code and "'" not in code:
+ return double
+ else:
+ return single
+ else:
+ return repr(givenValue)
+
+def isParameterizedType(t: Any) -> bool:
+ """True for any parameterized typing construct (incl. Union, Annotated, etc.)."""
+ return get_origin(t) is not None and len(get_args(t)) > 0
+
+def shouldReportTyMismatch(expected: Any, given: Any) -> bool:
+ if isParameterizedType(expected) and get_origin(expected) == given:
+ # don't report a mismatch because givenTy will not be properly parameterized.
+ # Example: resultTy is `list[int]` and givenValue is [1, "blub"]. Then
+ # givenTy will be just `list`
+ return False
+ else:
+ return True
+
+def rewriteInvalidType(s: str) -> Optional[str]:
+ prefixes = ['Union', 'Literal', 'Optional', 'list', 'dict', 'tuple', 'set']
+ for p in prefixes:
+ if s.startswith(f'{p}(') and s.endswith(')'):
+ args = s[len(p)+1:-1]
+ return f'{p}[{args}]'
+
+class WyppTypeError(TypeError, WyppError):
+
+ def __init__(self, msg: str, extraFrames: list[inspect.FrameInfo] = []):
+ WyppError.__init__(self, extraFrames)
+ self.msg = msg
+ self.add_note(msg)
+
+ def __str__(self):
+ return f'WyppTypeError: {self.msg}'
+
+ @staticmethod
+ def invalidType(ty: Any, loc: Optional[location.Loc]) -> WyppTypeError:
+ lines = []
+ tyStr = renderTy(ty)
+ lines.append(i18n.invalidTy(tyStr))
+ lines.append('')
+ rew = rewriteInvalidType(tyStr)
+ if rew:
+ lines.append(i18n.didYouMean(rew))
+ lines.append('')
+ if loc is not None:
+ lines.append(f'## {i18n.tr("File")} {loc.filename}')
+ lines.append(f'## {i18n.tr("Type declared in line")} {loc.startLine}:\n')
+ lines.append(renderLoc(loc))
+ raise WyppTypeError('\n'.join(lines))
+
+ @staticmethod
+ def unknownKeywordArgument(callableName: location.CallableName, callLoc: Optional[location.Loc], name: str) -> WyppTypeError:
+ lines = []
+ lines.append(i18n.tr('unknown keyword argument'))
+ lines.append('')
+ lines.append(i18n.unknownKeywordArgument(callableName, name))
+ if callLoc and (callLocR := renderLoc(callLoc)):
+ lines.append('')
+ lines.append(f'## {i18n.tr("File")} {callLoc.filename}')
+ lines.append(f'## {i18n.tr("Problematic call in line")} {callLoc.startLine}:\n')
+ lines.append(callLocR)
+ raise WyppTypeError('\n'.join(lines))
+
+ @staticmethod
+ def invalidRecordAnnotation(loc: Optional[location.Loc]) -> WyppTypeError:
+ lines = []
+ lines.append(i18n.tr('invalid record definition'))
+ lines.append('')
+ if loc and (locR := renderLoc(loc)):
+ lines.append(f'## {i18n.tr("File")} {loc.filename}')
+ lines.append(f'## {i18n.tr("Line")} {loc.startLine}:\n')
+ lines.append(locR)
+ raise WyppTypeError('\n'.join(lines))
+
+ @staticmethod
+ def resultError(callableName: location.CallableName, resultTypeLoc: Optional[location.Loc], resultTy: Any,
+ returnLoc: Optional[location.Loc], givenValue: Any,
+ callLoc: Optional[location.Loc],
+ extraFrames: list[inspect.FrameInfo]) -> WyppTypeError:
+ lines = []
+ if givenValue is None:
+ lines.append(i18n.tr('no result returned'))
+ else:
+ lines.append(renderGiven(givenValue, returnLoc))
+ lines.append('')
+ if resultTy is None:
+ # no result type expected but given
+ lines.append(i18n.expectingNoReturn(callableName))
+ lines.append(i18n.wrongReturnValue(renderTy(type(givenValue))))
+ elif givenValue is None:
+ # result type expected but none given
+ lines.append(i18n.expectingReturnOfType(callableName, renderTy(resultTy)))
+ lines.append(i18n.noReturnValue())
+ else:
+ # result type expected but different type given
+ lines.append(i18n.expectingReturnOfType(callableName, renderTy(resultTy)))
+ givenTy = type(givenValue)
+ if shouldReportTyMismatch(resultTy, givenTy):
+ lines.append(i18n.wrongReturnValue(renderTy(givenTy)))
+ printedFileName = None
+ if resultTypeLoc and (resultTypeLocR := renderLoc(resultTypeLoc)):
+ lines.append('')
+ lines.append(f'## {i18n.tr("File")} {resultTypeLoc.filename}')
+ printedFileName = resultTypeLoc.filename
+ lines.append(f'## {i18n.tr("Result type declared in line")} {resultTypeLoc.startLine}:\n')
+ lines.append(resultTypeLocR)
+ if givenValue is not None and returnLoc and (returnLocR := renderLoc(returnLoc)):
+ lines.append('')
+ if printedFileName != returnLoc.filename:
+ lines.append(f'## {i18n.tr("File")} {returnLoc.filename}')
+ printedFileName = returnLoc.filename
+ lines.append(f'## {i18n.tr("Problematic return in line")} {returnLoc.startLine}:\n')
+ lines.append(returnLocR)
+ if callLoc and (callLocR := renderLoc(callLoc)):
+ lines.append('')
+ if printedFileName != callLoc.filename:
+ lines.append(f'## {i18n.tr("File")} {callLoc.filename}')
+ if givenValue is None:
+ lines.append(f'## {i18n.unexpectedNoReturn(callLoc.startLine)}\n')
+ else:
+ lines.append(f'## {i18n.unexpectedReturn(callLoc.startLine)}\n')
+ lines.append(callLocR)
+ raise WyppTypeError('\n'.join(lines), extraFrames)
+
+ @staticmethod
+ def argumentError(callableName: location.CallableName, paramName: str, paramIndex: Optional[int],
+ paramLoc: Optional[location.Loc],
+ paramTy: Any, givenValue: Any, givenLoc: Optional[location.Loc]) -> WyppTypeError:
+ lines = []
+ givenStr = renderGiven(givenValue, givenLoc)
+ lines.append(givenStr)
+ lines.append('')
+ if paramIndex is not None:
+ lines.append(i18n.expectingArgumentOfTy(callableName, renderTy(paramTy), paramIndex + 1))
+ else:
+ lines.append(i18n.expectingArgumentOfTy(callableName, renderTy(paramTy), paramName))
+ if shouldReportTyMismatch(paramTy, type(givenValue)):
+ lines.append(i18n.realArgumentTy(renderTy(type(givenValue))))
+ if givenLoc and (givenLocR := renderLoc(givenLoc)):
+ lines.append('')
+ lines.append(f'## {i18n.tr("File")} {givenLoc.filename}')
+ lines.append(f'## {i18n.tr("Problematic call in line")} {givenLoc.startLine}:\n')
+ lines.append(givenLocR)
+ if paramLoc and (paramLocR := renderLoc(paramLoc)):
+ lines.append('')
+ if not givenLoc or paramLoc.filename != givenLoc.filename:
+ lines.append(f'## {i18n.tr("File")} {paramLoc.filename}')
+ lines.append(f'## {i18n.tr("Type declared in line")} {paramLoc.startLine}:\n')
+ lines.append(paramLocR)
+ raise WyppTypeError('\n'.join(lines))
+
+ @staticmethod
+ def defaultError(callableName: location.CallableName, paramName: str, paramLoc: Optional[location.Loc],
+ paramTy: Any, givenValue: Any) -> WyppTypeError:
+ lines = []
+ givenStr = renderGiven(givenValue, paramLoc)
+ lines.append(givenStr)
+ lines.append('')
+ lines.append(i18n.expectingDefaultValueOfTy(callableName, renderTy(paramTy), paramName))
+ if shouldReportTyMismatch(paramTy, type(givenValue)):
+ lines.append(i18n.realDefaultValueTy(renderTy(type(givenValue))))
+ if paramLoc and (paramLocR := renderLoc(paramLoc)):
+ lines.append('')
+ lines.append(f'## {i18n.tr("File")} {paramLoc.filename}')
+ lines.append(f'## {i18n.tr("Parameter declared in line")} {paramLoc.startLine}:\n')
+ lines.append(paramLocR)
+ raise WyppTypeError('\n'.join(lines))
+
+ @staticmethod
+ def argCountMismatch(callableName: location.CallableName, callLoc: Optional[location.Loc],
+ numParams: int, numMandatoryParams: int, numArgs: int) -> WyppTypeError:
+ lines = []
+ lines.append(i18n.tr('argument count mismatch'))
+ lines.append('')
+ if numArgs < numMandatoryParams:
+ if numParams == numMandatoryParams:
+ lines.append(i18n.argCountExact(callableName, numParams))
+ else:
+ lines.append(i18n.argCountMin(callableName, numMandatoryParams))
+ else:
+ if numParams == numMandatoryParams:
+ lines.append(i18n.argCountExact(callableName, numParams))
+ else:
+ lines.append(i18n.argCountMax(callableName, numParams))
+ lines.append(i18n.tr('Given: ') + i18n.argCount(numArgs))
+ if callLoc and (callLocR := renderLoc(callLoc)):
+ lines.append('')
+ lines.append(f'## {i18n.tr("File")} {callLoc.filename}')
+ lines.append(f'## {i18n.tr("Call in line")} {callLoc.startLine}:\n')
+ lines.append(callLocR)
+ raise WyppTypeError('\n'.join(lines))
+
+ @staticmethod
+ def partialAnnotationError(callableName: location.CallableName, paramName: str, paramLoc: Optional[location.Loc]) -> WyppTypeError:
+ lines = []
+ lines.append(i18n.expectingTypeAnnotation(callableName, paramName))
+ if paramLoc and (paramLocR := renderLoc(paramLoc)):
+ lines.append('')
+ lines.append(f'## {i18n.tr("File")} {paramLoc.filename}')
+ lines.append(f'## {i18n.tr("Parameter declared in line")} {paramLoc.startLine}:\n')
+ lines.append(paramLocR)
+ raise WyppTypeError('\n'.join(lines))
+
+ @staticmethod
+ def noTypeAnnotationForRecordAttribute(attrName: str, recordName: str) -> WyppTypeError:
+ return WyppTypeError(i18n.noTypeAnnotationForAttribute(attrName, recordName))
+
+ @staticmethod
+ def recordAssignError(recordName: str,
+ attrName: str,
+ attrTy: Any,
+ attrLoc: Optional[location.Loc],
+ setterValue: Any,
+ setterLoc: Optional[location.Loc]) -> WyppTypeError:
+ lines = []
+ givenStr = renderGiven(setterValue, setterLoc)
+ lines.append(givenStr)
+ lines.append('')
+ lines.append(i18n.recordAttrDeclTy(recordName, attrName, renderTy(attrTy)))
+ if shouldReportTyMismatch(attrTy, type(setterValue)):
+ lines.append(i18n.realSetAttrTy(renderTy(type(setterValue))))
+ if setterLoc and (setterLocR := renderLoc(setterLoc)):
+ lines.append('')
+ lines.append(f'## {i18n.tr("File")} {setterLoc.filename}')
+ lines.append(f'## {i18n.tr("Problematic assignment in line")} {setterLoc.startLine}:\n')
+ lines.append(setterLocR)
+ if attrLoc and (attrLocR := renderLoc(attrLoc)):
+ lines.append('')
+ if not setterLoc or setterLoc.filename != attrLoc.filename:
+ lines.append(f'## {i18n.tr("File")} {attrLoc.filename}')
+ lines.append(f'## {i18n.tr("Type declared in line")} {attrLoc.startLine}:\n')
+ lines.append(attrLocR)
+ raise WyppTypeError('\n'.join(lines))
+
+class WyppAttributeError(AttributeError, WyppError):
+ def __init__(self, msg: str, extraFrames: list[inspect.FrameInfo] = []):
+ WyppError.__init__(self, extraFrames)
+ self.msg = msg
+ self.add_note(msg)
+
+ @staticmethod
+ def unknownAttr(clsName: str, attrName: str) -> WyppAttributeError:
+ return WyppAttributeError(i18n.tr('Unknown attribute {attrName} for record {clsName}',
+ clsName=clsName, attrName=attrName))
+
+class TodoError(Exception, WyppError):
+ def __init__(self, msg: str, extraFrames: list[inspect.FrameInfo] = []):
+ WyppError.__init__(self, extraFrames)
+ self.msg = msg
+ self.add_note(msg)
+
+
+class ImpossibleError(Exception, WyppError):
+ def __init__(self, msg: str, extraFrames: list[inspect.FrameInfo] = []):
+ WyppError.__init__(self, extraFrames)
+ self.msg = msg
+ self.add_note(msg)
+
diff --git a/python/code/wypp/exceptionHandler.py b/python/code/wypp/exceptionHandler.py
new file mode 100644
index 00000000..dc575c13
--- /dev/null
+++ b/python/code/wypp/exceptionHandler.py
@@ -0,0 +1,67 @@
+import sys
+import traceback
+import re
+from dataclasses import dataclass
+
+# local imports
+from constants import *
+import stacktrace
+import paths
+from myLogging import *
+import errors
+import utils
+
+_tbPattern = re.compile(r'(\s*File\s+")([^"]+)(".*)')
+def _rewriteFilenameInTracebackLine(s: str) -> str:
+ # Match the pattern: File "filename", line number, in function
+ match = re.match(_tbPattern, s)
+ if match:
+ prefix = match.group(1)
+ filename = match.group(2)
+ suffix = match.group(3)
+ canonicalized = paths.canonicalizePath(filename)
+ return prefix + canonicalized + suffix
+ else:
+ return s
+
+def _rewriteFilenameInTraceback(s: str) -> str:
+ lines = s.split('\n')
+ result = []
+ for l in lines:
+ result.append(_rewriteFilenameInTracebackLine(l))
+ return '\n'.join(result)
+
+def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr):
+ (etype, val, tb) = sys.exc_info()
+ if isinstance(val, SystemExit):
+ utils.die(val.code)
+ isWyppError = isinstance(val, errors.WyppError)
+ if isWyppError:
+ extra = val.extraFrames
+ else:
+ extra = []
+ frameList = (stacktrace.tbToFrameList(tb) if tb is not None else [])
+ if frameList and removeFirstTb:
+ frameList = frameList[1:]
+ isBug = not isWyppError and not isinstance(val, SyntaxError) and \
+ len(frameList) > 0 and stacktrace.isWyppFrame(frameList[-1])
+ stackSummary = stacktrace.limitTraceback(frameList, extra, not isBug and not isDebug())
+ header = False
+ for x in stackSummary.format():
+ if not header:
+ file.write('Traceback (most recent call last):\n')
+ header = True
+ file.write(_rewriteFilenameInTraceback(x))
+ if isWyppError:
+ s = str(val)
+ if s and s[0] != '\n':
+ file.write('\n')
+ file.write(s)
+ file.write('\n')
+ else:
+ for x in traceback.format_exception_only(etype, val):
+ file.write(x)
+ if isBug:
+ file.write(f'BUG: the error above is most likely a bug in WYPP!')
+ if exit:
+ utils.die(1)
diff --git a/python/code/wypp/i18n.py b/python/code/wypp/i18n.py
new file mode 100644
index 00000000..a9482ef0
--- /dev/null
+++ b/python/code/wypp/i18n.py
@@ -0,0 +1,348 @@
+from dataclasses import dataclass
+import location
+from typing import *
+from contextlib import contextmanager
+import lang
+import utils
+
+type Lang = Literal['en', 'de']
+
+allLanguages: list[Lang] = ['en', 'de']
+
+# If not set explicitly, the current locale is asked for the language
+_lang: Optional[Lang] = None
+
+@contextmanager
+def explicitLang(newLang: Lang):
+ """Context manager to temporarily set the language."""
+ global _lang
+ oldLang = _lang
+ _lang = newLang
+ try:
+ yield
+ finally:
+ _lang = oldLang
+
+def getLang() -> Lang:
+ if _lang:
+ return _lang
+ else:
+ return lang.pickLanguage(allLanguages, 'en')
+
+def setLang(lang: Lang):
+ global _lang
+ _lang = lang
+
+def tr(key: str, **kws) -> str:
+ match getLang():
+ case 'en':
+ tmpl = key
+ case 'de':
+ if key not in DE and utils.isUnderTest():
+ raise ValueError(f'Untranslated string: {key}')
+ tmpl = DE.get(key, key)
+ case x:
+ if utils.underTest():
+ raise ValueError(f'Unknown language: {x}')
+ return tmpl.format(**kws)
+
+DE = {
+ 'Expecting no return value when calling function `{fun}`.':
+ 'Kein Rückgabewert erwartet bei Aufruf der Funktion `{fun}`.',
+ 'Expecting no return value when calling method `{method}` of class `{cls}.':
+ 'Kein Rückgabewert erwartet bei Aufruf der Methode `{method}` aus Klasse `{cls}`.',
+ 'Expecting no return value when calling constructor of record `{cls}`.':
+ 'Kein Rückgabewert erwartet bei Aufruf des Konstruktors des Records `{cls}`.',
+
+ 'Expecting return value of type `{ty}` when calling function `{fun}`.':
+ 'Rückgabewert vom Typ `{ty}` erwartet bei Aufruf der Funktion `{fun}`.',
+ 'Expecting return value of type `{ty}` when calling method `{method}` of class `{cls}`.':
+ 'Rückgabewert vom Typ `{ty}` erwartet bei Aufruf von Methode `{method}` aus Klasse `{cls}`.',
+ 'Expecting return value of type `{ty}` when calling constructor of record `{cls}`.':
+ 'Rückgabewert vom Typ `{ty}` erwartet bei Aufruf des Konstruktors des Records `{cls}`.',
+
+ 'But the call returns a value of type `{ty}`.':
+ 'Aber der Aufruf gibt einen Wert vom Typ `{ty}` zurück.',
+
+ 'But no value returned.':
+ 'Aber kein Rückgabewert vorhanden.',
+ 'no result returned': 'kein Rückgabewert vorhanden',
+ 'Call in line {line} causes the function to return no value:':
+ 'Aufruf in Zeile {line} führt dazu, dass die Funktion keinen Wert zurückgibt:',
+
+ 'The call of function `{fun}` expects value of type `{ty}` as {arg}.':
+ 'Der Aufruf der Funktion `{fun}` erwartet Wert vom Typ `{ty}` als {arg}.',
+ 'The call of method `{method}` of class `{cls}` expects value of type `{ty}` as {arg}.':
+ 'Der Aufruf der Methode `{method}` aus Klasse `{cls}` erwartet Wert vom Typ `{ty}` als {arg}.',
+ 'The call of the constructor of record `{cls}` expects value of type `{ty}` as {arg}.':
+ 'Der Aufruf des Konstruktors des Records `{cls}` erwartet Wert vom Typ `{ty}` als {arg}.',
+
+ 'But the value given has type `{ty}`.': 'Aber der übergebene Wert hat Typ `{ty}`.',
+ 'But the default value has type `{ty}`.': 'Aber der Default-Wert hat Typ `{ty}`.',
+
+ 'File': 'Datei',
+ 'Line': 'Zeile',
+ 'Result type declared in line': 'Rückgabetyp deklariert in Zeile',
+ 'Type declared in line': 'Typ deklariert in Zeile',
+ 'Parameter declared in line': 'Parameter deklariert in Zeile',
+ 'Problematic return in line': 'Fehlerhaftes return in Zeile',
+ 'Problematic call in line': 'Fehlerhafter Aufruf in Zeile',
+ 'Call in line {line} causes the problematic return:':
+ 'Aufruf in Zeile {line} verursacht das fehlerhafte return:',
+
+ 'Parameter `{param}` of function `{fun}` requires a type annotation.':
+ 'Parameter `{param}` der Funktion `{fun}` benötigt eine Typannotation.',
+ 'Parameter `{param}` of method `{method}` from class `{cls}` requires a type annotation.':
+ 'Parameter `{param}` der Methode `{method}` aus Klasse `{cls}` benötigt eine Typannotation.',
+ 'Parameter `{param}` of constructor of record `{cls}` requires a type annotation.':
+ 'Parameter `{param}` des Konstruktors des Records `{cls}` benötigt eine Typannotation.',
+
+ 'Attribute `{name}` of record `{record}` required a type annotation.':
+ 'Attribut `{name}` des Records `{record}` benötigt eine Typannotation.',
+
+ 'invalid type `{ty}`':
+ 'ungültiger Typ `{ty}`',
+ 'Cannot set attribute to value of type `{ty}`.':
+ 'Das Attribute kann nicht auf einen Wert vom Typ `{ty}` gesetzt werden.',
+ 'Problematic assignment in line': 'Fehlerhafte Zuweisung in Zeile',
+ 'Attribute `{attrName}` of record `{recordName}` declared with type `{ty}.`':
+ 'Attribut `{attrName}` des Records `{recordName}` deklariert als Typ `{ty}`.',
+
+ 'argument count mismatch': 'Anzahl der Argument passt nicht',
+ 'Call in line': 'Aufruf in Zeile',
+ 'Function `{fun}` takes ': 'Funktion `{fun}` benötigt ',
+ 'Function `{fun}` takes at least ': 'Funktion `{fun}` benötigt mindestens ',
+ 'Function `{fun}` takes at most ': 'Funktion `{fun}` akzeptiert höchstens ',
+ 'Method `{method}` of class `{cls}` takes ': 'Methode `{method}` der Klasse `{cls}` benötigt ',
+ 'Method `{method}` of class `{cls}` takes at least ': 'Methode `{method}` der Klasse `{cls}` benötigt mindestens ',
+ 'Method `{method}` of class `{cls}` takes at most ': 'Methode `{method}` der Klasse `{cls}` akzeptiert höchstens ',
+ 'Constructor of record `{cls}` takes ': 'Konstruktor des Records `{cls}` benötigt ',
+ 'Constructor of record `{cls}` takes at least ': 'Konstruktor des Records `{cls}` benötigt mindestens ',
+ 'Constructor of record `{cls}` takes at most ': 'Konstruktor des Records `{cls}` akzeptiert höchstens ',
+ 'Given: ': 'Gegeben: ',
+
+ 'Default value for parameter `{paramName}` of function `{fun}` must have type `{ty}`.':
+ 'Default-Wert des Parameters `{paramName}` der Funktion `{fun}` muss vom Typ `{ty}` sein.',
+ 'Default value for parameter `{paramName}` of method `{method}` in class `{cls}` must have type `{ty}`.':
+ 'Default-Wert des Parameters `{paramName}` der Methode `{method}` aus Klasse `{cls}` muss vom Typ `{ty}` sein.',
+ 'Default value for attribute `{paramName}` of record `{cls}` must have type `{ty}`.':
+ 'Default-Wert des Attributs `{paramName}` des Records `{cls}` muss vom Typ `{ty}` sein.',
+
+ 'Unknown attribute {attrName} for record {clsName}':
+ 'Attribut {attrName} ist nicht bekannt für Record {clsName}',
+ 'Did you mean `{ty}`?': 'Wolltest du `{ty}` schreiben?',
+
+ 'unknown keyword argument': 'unbekanntes Schlüsselwort-Argument',
+ 'Function `{fun}` does not accept keyword argument `{name}`.':
+ 'Funktion `{fun}` akzeptiert kein Schlüsselwort-Argument `{name}`.',
+ 'Method `{method}` from class `{cls}` does not accept keyword argument `{name}`.':
+ 'Methode `{method}` der Klasse `{cls}` akzeptiert kein Schlüsselwort-Argument `{name}`.',
+ 'Constructor of record `{cls}` does not accept keyword argument `{name}`.':
+ 'Konstruktor des Records `{cls}` akzeptiert kein Schlüsselwort-Argument `{name}`.',
+
+ 'invalid record definition': 'ungültige Record-Definition'
+}
+
+def expectingNoReturn(cn: location.CallableName) -> str:
+ match cn.kind:
+ case 'function':
+ return tr('Expecting no return value when calling function `{fun}`.',
+ fun=cn.name)
+ case location.ClassMember('method', cls):
+ return tr('Expecting no return value when calling method `{method}` of class `{cls}.',
+ method=cn.name, cls=cls)
+ case location.ClassMember('recordConstructor', cls):
+ return tr('Expecting no return value when calling constructor of record `{cls}`.',
+ cls=cls)
+ raise ValueError(f'Unexpected: {cn}')
+
+def wrongReturnValue(ty: str) -> str:
+ return tr('But the call returns a value of type `{ty}`.', ty=ty)
+
+def unexpectedReturn(line: int) -> str:
+ return tr('Call in line {line} causes the problematic return:', line=line)
+
+def unexpectedNoReturn(line: int) -> str:
+ return tr('Call in line {line} causes the function to return no value:', line=line)
+
+def expectingReturnOfType(cn: location.CallableName, ty: str) -> str:
+ match cn.kind:
+ case 'function':
+ return tr('Expecting return value of type `{ty}` when calling function `{fun}`.',
+ fun=cn.name, ty=ty)
+ case location.ClassMember('method', cls):
+ return tr('Expecting return value of type `{ty}` when calling method `{method}` of class `{cls}`.',
+ method=cn.name, cls=cls, ty=ty)
+ case location.ClassMember('recordConstructor', cls):
+ return tr('Expecting return value of type `{ty}` when calling constructor of record `{cls}`.',
+ cls=cls, ty=ty)
+ raise ValueError(f'Unexpected: {cn}')
+
+def noReturnValue() -> str:
+ return tr('But no value returned.')
+
+def transArg(pos: int):
+ match getLang():
+ case 'en':
+ match pos:
+ case 1: return '1st argument'
+ case 2: return '2nd argument'
+ case 3: return '3rd argument'
+ case _: return f'{pos}th argument'
+ case 'de':
+ match pos:
+ case 1: return 'erstes Argument'
+ case 2: return 'zweites Argument'
+ case 3: return 'drittes Argument'
+ case 4: return 'viertes Argument'
+ case 5: return 'fünftes Argument'
+ case 6: return 'sechstes Argument'
+ case 7: return 'siebtes Argument'
+ case 8: return 'achtes Argument'
+ case 9: return 'neuntes Argument'
+ case _: return f'{pos}. Argument'
+ case l:
+ raise ValueError(f'Unexpected language: {l}')
+
+
+def expectingArgumentOfTy(cn: location.CallableName, ty: str, pos: int | str) -> str:
+ if isinstance(pos, int):
+ arg = transArg(pos)
+ else:
+ match getLang():
+ case 'en':
+ arg = f'argument `{pos}`'
+ case 'de':
+ arg = f'Argument `{pos}`'
+ match cn.kind:
+ case 'function':
+ return tr('The call of function `{fun}` expects value of type `{ty}` as {arg}.',
+ fun=cn.name, ty=ty, arg=arg)
+ case location.ClassMember('method', cls):
+ return tr('The call of method `{method}` of class `{cls}` expects value of type `{ty}` as {arg}.',
+ method=cn.name, cls=cls, ty=ty, arg=arg)
+ case location.ClassMember('recordConstructor', cls):
+ return tr('The call of the constructor of record `{cls}` expects value of type `{ty}` as {arg}.',
+ cls=cls, ty=ty, arg=arg)
+ raise ValueError(f'Unexpected: {cn}')
+
+def expectingDefaultValueOfTy(cn: location.CallableName, ty: str, paramName: str) -> str:
+ match cn.kind:
+ case 'function':
+ return tr('Default value for parameter `{paramName}` of function `{fun}` must have type `{ty}`.',
+ paramName=paramName, fun=cn.name, ty=ty)
+ case location.ClassMember('method', cls):
+ return tr('Default value for parameter `{paramName}` of method `{method}` in class `{cls}` must have type `{ty}`.',
+ paramName=paramName, method=cn.name, cls=cls, ty=ty)
+ case location.ClassMember('recordConstructor', cls):
+ return tr('Default value for attribute `{paramName}` of record `{cls}` must have type `{ty}`.',
+ paramName=paramName, cls=cls, ty=ty)
+ raise ValueError(f'Unexpected: {cn}')
+
+def argCount(n: int) -> str:
+ match getLang():
+ case 'en':
+ if n == 0:
+ return 'no arguments'
+ elif n == 1:
+ return '1 argument'
+ else:
+ return f'{n} arguments'
+ case 'de':
+ if n == 0:
+ return 'keine Argumente'
+ elif n == 1:
+ return '1 Argument'
+ else:
+ return f'{n} Argumente'
+
+def argCountExact(cn: location.CallableName, expected: int) -> str:
+ match cn.kind:
+ case 'function':
+ header = tr('Function `{fun}` takes ', fun=cn.name)
+ case location.ClassMember('method', cls):
+ header = tr('Method `{method}` of class `{cls}` takes ',
+ method=cn.name, cls=cls)
+ case location.ClassMember('recordConstructor', cls):
+ header = tr('Constructor of record `{cls}` takes ',
+ cls=cls)
+ case _:
+ raise ValueError(f'Unexpected: {cn}')
+ return header + argCount(expected) + '.'
+
+def argCountMin(cn: location.CallableName, expected: int) -> str:
+ match cn.kind:
+ case 'function':
+ header = tr('Function `{fun}` takes at least ', fun=cn.name)
+ case location.ClassMember('method', cls):
+ header = tr('Method `{method}` of class `{cls}` takes at least ',
+ method=cn.name, cls=cls)
+ case location.ClassMember('recordConstructor', cls):
+ header = tr('Constructor of record `{cls}` takes at least ',
+ cls=cls)
+ case _:
+ raise ValueError(f'Unexpected: {cn}')
+ return header + argCount(expected) + '.'
+
+def argCountMax(cn: location.CallableName, expected: int) -> str:
+ match cn.kind:
+ case 'function':
+ header = tr('Function `{fun}` takes at most ', fun=cn.name)
+ case location.ClassMember('method', cls):
+ header = tr('Method `{method}` of class `{cls}` takes at most ',
+ method=cn.name, cls=cls)
+ case location.ClassMember('recordConstructor', cls):
+ header = tr('Constructor of record `{cls}` takes at most ',
+ cls=cls)
+ case _:
+ raise ValueError(f'Unexpected: {cn}')
+ return header + argCount(expected) + '.'
+
+def realArgumentTy(ty: str) -> str:
+ return tr('But the value given has type `{ty}`.', ty=ty)
+
+def realDefaultValueTy(ty: str) -> str:
+ return tr('But the default value has type `{ty}`.', ty=ty)
+
+def realSetAttrTy(ty: str) -> str:
+ return tr('Cannot set attribute to value of type `{ty}`.', ty=ty)
+
+def expectingTypeAnnotation(cn: location.CallableName, param: str) -> str:
+ match cn.kind:
+ case 'function':
+ return tr('Parameter `{param}` of function `{fun}` requires a type annotation.',
+ fun=cn.name, param=param)
+ case location.ClassMember('method', cls):
+ return tr('Parameter `{param}` of method `{method}` from class `{cls}` requires a type annotation.',
+ method=cn.name, cls=cls, param=param)
+ case location.ClassMember('recordConstructor', cls):
+ return tr('Parameter `{param}` of constructor of record `{cls}` requires a type annotation.',
+ cls=cls, param=param)
+ raise ValueError(f'Unexpected: {cn}')
+
+def noTypeAnnotationForAttribute(attrName: str, recordName: str) -> str:
+ return tr('Attribute `{name}` of record `{record}` required a type annotation.',
+ name=attrName, record=recordName)
+
+def invalidTy(ty: Any) -> str:
+ return tr('invalid type `{ty}`', ty=ty)
+
+def didYouMean(ty: str) -> str:
+ return tr('Did you mean `{ty}`?', ty=ty)
+
+def recordAttrDeclTy(recordName: str, attrName: str, ty: Any) -> str:
+ return tr('Attribute `{attrName}` of record `{recordName}` declared with type `{ty}.`',
+ recordName=recordName, attrName=attrName, ty=ty)
+
+def unknownKeywordArgument(cn: location.CallableName, name: str) -> str:
+ match cn.kind:
+ case 'function':
+ return tr('Function `{fun}` does not accept keyword argument `{name}`.',
+ fun=cn.name, name=name)
+ case location.ClassMember('method', cls):
+ return tr('Method `{method}` from class `{cls}` does not accept keyword argument `{name}`.',
+ method=cn.name, cls=cls, name=name)
+ case location.ClassMember('recordConstructor', cls):
+ return tr('Constructor of record `{cls}` does not accept keyword argument `{name}`.',
+ cls=cls, name=name)
+ raise ValueError(f'Unexpected: {cn}')
diff --git a/python/code/wypp/instrument.py b/python/code/wypp/instrument.py
new file mode 100644
index 00000000..68178e6d
--- /dev/null
+++ b/python/code/wypp/instrument.py
@@ -0,0 +1,180 @@
+from typing import *
+import os
+import ast
+import importlib
+import importlib.abc
+from importlib.machinery import ModuleSpec, SourceFileLoader
+from importlib.util import decode_source
+from collections.abc import Buffer
+import types
+from os import PathLike
+import utils
+from myLogging import *
+from contextlib import contextmanager
+import errors
+import location
+
+def parseExp(s: str) -> ast.expr:
+ match ast.parse(s):
+ case ast.Module([ast.Expr(e)], type_ignores):
+ return e
+ case m:
+ raise ValueError(f'String {repr(s)} does not parse as an expression: {m}')
+
+class Configs:
+ funConfig: ast.expr = parseExp("{'kind': 'function', 'globals': globals(), 'locals': locals()}")
+ @staticmethod
+ def methodConfig(clsName: str) -> ast.expr:
+ return parseExp("{'kind': 'method', 'className': " + repr(clsName) + ", 'globals': globals(), 'locals': locals()}")
+ immutableRecordConfig: ast.expr = parseExp('record(mutable=False, globals=globals(), locals=locals())')
+ mutableRecordConfig: ast.expr = parseExp('record(mutable=True, globals=globals(), locals=locals())')
+
+def transferLocs(old: ast.stmt | ast.expr, new: ast.stmt | ast.expr) -> Any:
+ new.lineno = old.lineno
+ new.col_offset = old.col_offset
+ new.end_lineno = old.end_lineno
+ new.end_col_offset = old.end_col_offset
+ return new
+
+def transformDecorator(e: ast.expr, path: str) -> ast.expr:
+ loc = location.Loc(path, e.lineno, e.col_offset, e.end_lineno, e.col_offset)
+ match e:
+ case ast.Name('record'):
+ return transferLocs(e, Configs.immutableRecordConfig)
+ case ast.Call(ast.Name('record'), [], kwArgs):
+ match kwArgs:
+ case [ast.keyword('mutable', ast.Constant(True))]:
+ return transferLocs(e, Configs.mutableRecordConfig)
+ case [ast.keyword('mutable', ast.Constant(False))]:
+ return transferLocs(e, Configs.immutableRecordConfig)
+ case _:
+ raise errors.WyppTypeError.invalidRecordAnnotation(loc)
+ case ast.Call(ast.Name('record'), _, _):
+ raise ValueError(f'Invalid record config')
+ case _:
+ return e
+
+def transformStmt(stmt: ast.stmt, outerClassName: Optional[str], path: str) -> ast.stmt:
+ cfg = Configs.methodConfig(outerClassName) if outerClassName else Configs.funConfig
+ wrapExp = ast.Call(ast.Name(id='wrapTypecheck', ctx=ast.Load()), [cfg], [])
+ match stmt:
+ case ast.FunctionDef(name, args, body, decorators, returns, tyComment, tyParams):
+ newBody = [transformStmt(s, outerClassName=outerClassName, path=path) for s in body]
+ x = ast.FunctionDef(name, args, newBody, decorators + [wrapExp], returns, tyComment, tyParams)
+ return transferLocs(stmt, x)
+ case ast.AsyncFunctionDef(name, args, body, decorators, returns, tyComment, tyParams):
+ newBody = [transformStmt(s, outerClassName=outerClassName, path=path) for s in body]
+ x = ast.AsyncFunctionDef(name, args, newBody, decorators + [wrapExp], returns, tyComment, tyParams)
+ return transferLocs(stmt, x)
+ case ast.ClassDef(className, bases, keywords, body, decoratorList, type_params):
+ newBody = [transformStmt(s, outerClassName=className, path=path) for s in body]
+ newDecoratorList = [transformDecorator(e, path=path) for e in decoratorList]
+ x = ast.ClassDef(className, bases, keywords, newBody, newDecoratorList, type_params)
+ return transferLocs(stmt, x)
+ case _:
+ return stmt
+
+def isImport(t: ast.stmt) -> bool:
+ match t:
+ case ast.Import(): return True
+ case ast.ImportFrom(): return True
+ case _: return False
+
+importWrapTypecheck = ast.parse("from wypp import wrapTypecheck", mode="exec").body[0]
+
+def transformModule(m: ast.Module | ast.Expression | ast.Interactive, path: str) -> ast.Module | ast.Expression | ast.Interactive:
+ match m:
+ case ast.Module(body, type_ignores):
+ newStmts = [transformStmt(stmt, outerClassName=None, path=path) for stmt in body]
+ (imports, noImports) = utils.split(newStmts, isImport)
+ # avoid inserting before from __future__
+ newStmts = imports + [importWrapTypecheck] + noImports
+ return ast.Module(newStmts, type_ignores)
+ case _:
+ return m
+
+class InstrumentingLoader(SourceFileLoader):
+ @staticmethod
+ def source_to_code(
+ data: Buffer | str | ast.Module | ast.Expression | ast.Interactive,
+ path: Buffer | str | PathLike[str] = "",
+ ) -> types.CodeType:
+ if isinstance(data, (ast.Module, ast.Expression, ast.Interactive)):
+ tree = data
+ else:
+ if isinstance(data, str):
+ source = data
+ else:
+ source = decode_source(data)
+ tree = utils._call_with_frames_removed(ast.parse, source, path, "exec")
+ if isinstance(path, PathLike):
+ pathStr = str(path)
+ elif isinstance(path, str):
+ pathStr = path
+ else:
+ pathStr = ""
+ tree = transformModule(tree, pathStr)
+ ast.fix_missing_locations(tree)
+
+ debug(
+ f"Source code of {path!r} after instrumentation:\n" +
+ "----------------------------------------------\n" +
+ ast.unparse(tree) + "\n"
+ "----------------------------------------------")
+
+ code = utils._call_with_frames_removed(compile, tree, path, "exec", 0, dont_inherit=True)
+ return code
+
+class InstrumentingFinder(importlib.abc.MetaPathFinder):
+ def __init__(self, finder, modDir: str, extraDirs: list[str]):
+ self._origFinder = finder
+ self.modDir = os.path.realpath(modDir) + '/'
+ self.extraDirs = [os.path.realpath(d) for d in extraDirs]
+
+ def find_spec(
+ self,
+ fullname: str,
+ path: Sequence[str] | None,
+ target: types.ModuleType | None = None,
+ ) -> ModuleSpec | None:
+ spec = self._origFinder.find_spec(fullname, path, target)
+ if spec is None:
+ return None
+ origin = os.path.realpath(spec.origin)
+ dirs = [self.modDir] + self.extraDirs
+ isLocalModule = False
+ for d in dirs:
+ if origin.startswith(d):
+ isLocalModule = True
+ break
+ # print(f'Module {fullname} is locale: {isLocalModule} ({origin})')
+ if spec and spec.loader and isLocalModule:
+ spec.loader = InstrumentingLoader(spec.loader.name, spec.loader.path)
+ return spec
+
+@contextmanager
+def setupFinder(modDir: str, extraDirs: list[str], typechecking: bool):
+ if not typechecking:
+ yield
+ else:
+ # Find the PathFinder
+ for finder in sys.meta_path:
+ if (
+ isinstance(finder, type)
+ and finder.__name__ == "PathFinder"
+ and hasattr(finder, "find_spec")
+ ):
+ break
+ else:
+ raise RuntimeError("Cannot find a PathFinder in sys.meta_path")
+
+ # Create and install our custom finder
+ instrumenting_finder = InstrumentingFinder(finder, modDir, extraDirs)
+ sys.meta_path.insert(0, instrumenting_finder)
+
+ try:
+ yield
+ finally:
+ # Remove our custom finder when exiting the context
+ if instrumenting_finder in sys.meta_path:
+ sys.meta_path.remove(instrumenting_finder)
diff --git a/python/code/wypp/interactive.py b/python/code/wypp/interactive.py
new file mode 100644
index 00000000..6e1ef9d7
--- /dev/null
+++ b/python/code/wypp/interactive.py
@@ -0,0 +1,63 @@
+
+import sys
+import os
+import code
+from dataclasses import dataclass
+
+# local imports
+from constants import *
+from myLogging import *
+from exceptionHandler import handleCurrentException
+
+def prepareInteractive(reset=True):
+ print()
+ if reset:
+ if os.name == 'nt':
+ # clear the terminal
+ os.system('cls')
+ else:
+ # On linux & mac use ANSI Sequence for this
+ print('\033[2J\033[H')
+
+
+HISTORY_SIZE = 1000
+
+def getHistoryFilePath():
+ envVar = 'HOME'
+ if os.name == 'nt':
+ envVar = 'USERPROFILE'
+ d = os.getenv(envVar, None)
+ if d:
+ return os.path.join(d, ".wypp_history")
+ else:
+ return None
+
+class TypecheckedInteractiveConsole(code.InteractiveConsole):
+ def showtraceback(self) -> None:
+ handleCurrentException(exit=False, removeFirstTb=True, file=sys.stdout)
+
+def enterInteractive(userDefs: dict, checkTypes: bool, loadingFailed: bool):
+ for k, v in userDefs.items():
+ globals()[k] = v
+ print()
+ if loadingFailed:
+ print('NOTE: running the code failed, some definitions might not be available!')
+ print()
+ if checkTypes:
+ consoleClass = TypecheckedInteractiveConsole
+ else:
+ consoleClass = code.InteractiveConsole
+ historyFile = getHistoryFilePath()
+ try:
+ import readline
+ readline.parse_and_bind('tab: complete')
+ if historyFile and os.path.exists(historyFile):
+ readline.read_history_file(historyFile)
+ except:
+ pass
+ try:
+ consoleClass(locals=userDefs).interact(banner="", exitmsg='')
+ finally:
+ if readline and historyFile:
+ readline.set_history_length(HISTORY_SIZE)
+ readline.write_history_file(historyFile)
diff --git a/python/code/wypp/lang.py b/python/code/wypp/lang.py
new file mode 100644
index 00000000..e19a7b6f
--- /dev/null
+++ b/python/code/wypp/lang.py
@@ -0,0 +1,51 @@
+import os
+import locale
+from _collections_abc import MutableMapping
+
+def _langFromEnv(env: MutableMapping) -> str | None:
+ # 1) GNU LANGUAGE: colon-separated fallbacks (e.g., "de:en_US:en")
+ os.getenv
+ lng = env.get("LANGUAGE")
+ if lng:
+ for part in lng.split(":"):
+ part = part.strip()
+ if part:
+ return part
+
+ # 2) POSIX locale vars in order of precedence
+ for var in ("LC_ALL", "LC_MESSAGES", "LANG"):
+ val = env.get(var)
+ if val and val not in ("C", "POSIX"):
+ return val
+
+ # 3) locale module fallback (works cross-platform-ish)
+ try:
+ locale.setlocale(locale.LC_CTYPE, "") # load user default
+ except locale.Error:
+ pass
+ lang, _enc = locale.getlocale() # e.g. "de_DE"
+ return lang
+
+def _normLang(tag: str) -> str:
+ # "de-DE.UTF-8" -> "de_DE"
+ tag = tag.replace("-", "_")
+ if "." in tag:
+ tag = tag.split(".", 1)[0]
+ return tag
+
+def pickLanguage[T: str](supported: list[T], default: T) -> T:
+ """Return best match like 'de' or 'de_DE' from supported codes."""
+ raw = _langFromEnv(os.environ)
+ if not raw:
+ return default
+ want = _normLang(raw)
+ # exact match first
+ for s in supported:
+ if _normLang(s).lower() == want.lower():
+ return s
+ # fallback to language-only match (de_DE -> de)
+ wantBase = want.split("_")[0].lower()
+ for s in supported:
+ if _normLang(s).split('_')[0].lower() == wantBase:
+ return s
+ return default
diff --git a/python/code/wypp/location.py b/python/code/wypp/location.py
new file mode 100644
index 00000000..12de0a0e
--- /dev/null
+++ b/python/code/wypp/location.py
@@ -0,0 +1,296 @@
+from __future__ import annotations
+from typing import *
+from dataclasses import dataclass
+import inspect
+import linecache
+import dis
+import ast
+import ansi
+import utils
+import myLogging
+import sys
+import abc
+import parsecache
+from parsecache import FunMatcher
+import paths
+
+@dataclass
+class Loc:
+ filename: str
+ startLine: int
+ startCol: Optional[int]
+ endLine: Optional[int]
+ endCol: Optional[int]
+
+ def __post_init__(self):
+ self.filename = paths.canonicalizePath(self.filename)
+
+ def fullSpan(self) -> Optional[tuple[int, int, int, int]]:
+ if self.startCol and self.endLine and self.endCol:
+ return (self.startLine, self.startCol, self.endLine, self.endCol)
+ else:
+ return None
+
+ def code(self) -> Optional[str]:
+ match self.fullSpan():
+ case None:
+ return None
+ case (startLine, startCol, endLine, endCol):
+ result = []
+ for lineNo in range(startLine, startLine+1):
+ line = linecache.getline(self.filename, lineNo).rstrip("\n")
+ c1 = startCol if lineNo == startLine else 0
+ c2 = endCol if lineNo == endLine else len(line)
+ result.append(line[c1:c2])
+ return '\n'.join(result)
+
+ @staticmethod
+ def fromFrameInfo(fi: inspect.FrameInfo) -> Loc:
+ default = Loc(fi.filename, fi.lineno, None, None, None)
+ if fi.positions is None:
+ return default
+ p: dis.Positions = fi.positions
+ startLine = p.lineno
+ endLine = p.end_lineno
+ startCol = p.col_offset
+ endCol = p.end_col_offset
+ if startLine is None or endLine is None or startCol is None or endCol is None:
+ return default
+ else:
+ return Loc(fi.filename, startLine, startCol, endLine, endCol)
+
+HIGHLIGHTING_ENV_VAR = 'WYPP_HIGHLIGHTING'
+type HighlightMode = Literal['color', 'text', 'off']
+
+def getHighlightMode(mode: HighlightMode | Literal['fromEnv']) -> HighlightMode:
+ if mode == 'fromEnv':
+ fromEnv = utils.getEnv(HIGHLIGHTING_ENV_VAR, lambda x: x, None)
+ match fromEnv:
+ case 'color': return 'color'
+ case 'text': return 'text'
+ case 'off': return 'off'
+ case None: return 'color'
+ case _:
+ myLogging.warn(f'Invalid highlighting mode in environment variable {HIGHLIGHTING_ENV_VAR}: {fromEnv} (supported: color, text, off)')
+ return 'off'
+ else:
+ return mode
+
+def highlight(s: str, mode: HighlightMode) -> str:
+ match mode:
+ case 'color': return ansi.red(s)
+ case 'off': return s
+ case 'text': return f'<<{s}>>'
+
+@dataclass
+class SourceLine:
+ line: str # without trailing \n
+ span: Optional[tuple[int, int]] # (inclusive, exclusive)
+
+ def highlight(self, mode: HighlightMode | Literal['fromEnv'] = 'fromEnv'):
+ mode = getHighlightMode(mode)
+ if self.span:
+ l = self.line
+ return l[:self.span[0]] + highlight(l[self.span[0]:self.span[1]], mode) + l[self.span[1]:]
+ else:
+ return self.line
+
+def highlightedLines(loc: Loc) -> list[SourceLine]:
+ match loc.fullSpan():
+ case None:
+ line = linecache.getline(loc.filename, loc.startLine).rstrip("\n")
+ return [SourceLine(line, None)]
+ case (startLine, startCol, endLine, endCol):
+ result = []
+ for lineNo in range(startLine, startLine+1):
+ line = linecache.getline(loc.filename, lineNo).rstrip("\n")
+ leadingSpaces = len(line) - len(line.lstrip())
+ c1 = startCol if lineNo == startLine else leadingSpaces
+ c2 = endCol if lineNo == endLine else len(line)
+ result.append(SourceLine(line, (c1, c2)))
+ return result
+
+@dataclass
+class ClassMember:
+ kind: Literal['method', 'recordConstructor']
+ className: str
+
+type CallableKind = Literal['function'] | ClassMember
+
+@dataclass
+class CallableName:
+ name: str
+ kind: CallableKind
+ @staticmethod
+ def mk(c: CallableInfo) -> CallableName:
+ return CallableName(c.name, c.kind)
+
+class CallableInfo(abc.ABC):
+ """
+ Class giving access to various properties of a function, method or constructor.
+ """
+ def __init__(self, kind: CallableKind):
+ self.kind: CallableKind = kind
+ @property
+ @abc.abstractmethod
+ def name(self) -> str:
+ pass
+ @abc.abstractmethod
+ def getResultTypeLocation(self) -> Optional[Loc]:
+ pass
+ @abc.abstractmethod
+ def getParamSourceLocation(self, paramName: str) -> Optional[Loc]:
+ pass
+
+class StdCallableInfo(CallableInfo):
+ """
+ Class giving access to various properties of a function
+ (arguments, result type etc.)
+ """
+ def __init__(self, f: Callable, kind: CallableKind):
+ super().__init__(kind)
+ self.file = f.__code__.co_filename
+ self.__lineno = f.__code__.co_firstlineno
+ self.__name = f.__name__
+ self.__ast = parsecache.getAST(self.file)
+
+ def __repr__(self):
+ return f'StdCallableInfo({self.name}, {self.kind})'
+
+ @property
+ def name(self):
+ return self.__name
+
+ def _findDef(self) -> Optional[ast.FunctionDef | ast.AsyncFunctionDef]:
+ m = FunMatcher(self.__name, self.__lineno)
+ match self.kind:
+ case 'function':
+ return self.__ast.getFunDef(m)
+ case ClassMember('method', clsName):
+ return self.__ast.getMethodDef(clsName, m)
+ case k:
+ raise ValueError(f'Unexpected CallableKind {k} in StdCallableInfo')
+
+ def getResultTypeLocation(self) -> Optional[Loc]:
+ """
+ Returns the location of the result type
+ """
+ node = self._findDef()
+ if not node:
+ return None
+ r = node.returns
+ if r:
+ return Loc(self.file,
+ r.lineno,
+ r.col_offset,
+ r.end_lineno,
+ r.end_col_offset)
+ else:
+ # There is no return type annotation
+ return None
+
+ def getParamSourceLocation(self, paramName: str) -> Optional[Loc]:
+ """
+ Returns the location of the parameter with the given name.
+ """
+ node = self._findDef()
+ if not node:
+ return None
+ res = None
+ for arg in node.args.args + node.args.kwonlyargs:
+ if arg.arg == paramName:
+ res = arg
+ break
+ if res is None:
+ # Look in vararg and kwarg
+ if node.args.vararg and node.args.vararg.arg == paramName:
+ res = node.args.vararg
+ if node.args.kwarg and node.args.kwarg.arg == paramName:
+ res = node.args.kwarg
+ if res is None:
+ return None
+ else:
+ return Loc(self.file,
+ res.lineno,
+ res.col_offset,
+ res.end_lineno,
+ res.end_col_offset)
+
+
+def classFilename(cls) -> str | None:
+ """Best-effort path to the file that defined `cls`."""
+ try:
+ fn = inspect.getsourcefile(cls) or inspect.getfile(cls)
+ if fn:
+ return fn
+ except TypeError:
+ pass
+ # Fallback via the owning module (works for some frozen/zip cases)
+ mod = sys.modules.get(cls.__module__)
+ if mod is not None:
+ return getattr(mod, "__file__", None) or getattr(getattr(mod, "__spec__", None), "origin", None)
+ return None
+
+class RecordConstructorInfo(CallableInfo):
+ """
+ Class giving access to various properties of a record constructor.
+ """
+ def __init__(self, cls: type):
+ super().__init__(ClassMember('recordConstructor', cls.__name__))
+ self.__cls = cls
+ def __repr__(self):
+ return f'RecordConstructorInfo({self.name})'
+ @property
+ def name(self):
+ return self.__cls.__name__
+ def getResultTypeLocation(self) -> Optional[Loc]:
+ return None
+ def getParamSourceLocation(self, paramName: str) -> Optional[Loc]:
+ file = classFilename(self.__cls)
+ if not file:
+ return None
+ ast = parsecache.getAST(file)
+ node = ast.getRecordAttr(self.name, paramName)
+ if node:
+ return Loc(file, node.lineno, node.col_offset, node.end_lineno, node.end_col_offset)
+ else:
+ return None
+
+def locationOfArgument(fi: inspect.FrameInfo, idxOrName: int | str) -> Optional[Loc]:
+ """
+ Given a stack frame with a function call f(arg1, arg2, ..., argN), returns
+ the source code location of the i-th argument.
+ """
+ loc = Loc.fromFrameInfo(fi)
+ match loc.fullSpan():
+ case (startLine, startCol, _endLine, _endCol):
+ codeOfCall = loc.code()
+ if codeOfCall is None:
+ return loc
+ try:
+ tree = ast.parse(codeOfCall)
+ except SyntaxError:
+ return loc
+ match tree:
+ case ast.Module([ast.Expr(ast.Call(_fun, args, kwArgs))]):
+ arg = None
+ if isinstance(idxOrName, int):
+ idx = idxOrName
+ if idx >= 0 and idx < len(args):
+ arg = args[idx]
+ else:
+ matching = [k.value for k in kwArgs if k.arg == idxOrName]
+ if matching:
+ arg = matching[0]
+ if arg is not None:
+ if arg.end_lineno is not None and arg.end_col_offset is not None:
+ callStartLine = startLine + arg.lineno - 1
+ callStartCol = startCol + arg.col_offset
+ callEndLine = startLine + arg.end_lineno - 1
+ if arg.lineno != arg.end_lineno:
+ callEndCol = arg.end_col_offset
+ else:
+ callEndCol = startCol + arg.end_col_offset
+ return Loc(loc.filename, callStartLine, callStartCol,
+ callEndLine, callEndCol)
+ return loc
diff --git a/python/code/wypp/myLogging.py b/python/code/wypp/myLogging.py
new file mode 100644
index 00000000..88f20469
--- /dev/null
+++ b/python/code/wypp/myLogging.py
@@ -0,0 +1,31 @@
+import utils
+import sys
+
+VERBOSE = False # set via commandline
+DEBUG = utils.getEnv("WYPP_DEBUG", bool, False)
+
+def isDebug() -> bool:
+ return DEBUG
+
+def enableVerbose():
+ global VERBOSE
+ VERBOSE = True
+
+def enableDebug():
+ global VERBOSE, DEBUG
+ VERBOSE = True
+ DEBUG = True
+
+def printStderr(s=''):
+ sys.stderr.write(s + '\n')
+
+def verbose(s):
+ if VERBOSE or DEBUG:
+ printStderr('[V] ' + str(s))
+
+def debug(s):
+ if DEBUG:
+ printStderr('[D] ' + str(s))
+
+def warn(s: str):
+ printStderr('WARN: ' + str(s))
diff --git a/python/code/wypp/myTypeguard.py b/python/code/wypp/myTypeguard.py
new file mode 100644
index 00000000..b3854ee3
--- /dev/null
+++ b/python/code/wypp/myTypeguard.py
@@ -0,0 +1,45 @@
+# Wrapper module for typeguard. Do not import typeguard directly but always via myTypeguard
+from __future__ import annotations
+from typing import *
+from dataclasses import dataclass
+from myLogging import *
+
+# We externally adjust the PYTHONPATH so that the typeguard module can be resolved
+import typeguard # type: ignore
+
+@dataclass(frozen=True)
+class Namespaces:
+ globals: dict
+ locals: dict
+ @staticmethod
+ def empty() -> Namespaces:
+ return Namespaces({}, {})
+
+@dataclass(frozen=True)
+class MatchesTyFailure:
+ # This is potentially a failure because of an invalid type
+ exception: Exception
+ ty: Any
+
+type MatchesTyResult = bool | MatchesTyFailure
+
+def matchesTy(a: Any, ty: Any, ns: Namespaces) -> MatchesTyResult:
+ try:
+ typeguard.check_type(a,
+ ty,
+ collection_check_strategy=typeguard.CollectionCheckStrategy.ALL_ITEMS,
+ ns=(ns.globals, ns.locals))
+ return True
+ except typeguard.TypeCheckError as e:
+ return False
+ except Exception as e:
+ debug(f'Exception when checking type, ns={ns}: {e}')
+ return MatchesTyFailure(e, ty)
+
+def getTypeName(t: Any) -> str:
+ res: str = typeguard._utils.get_type_name(t, ['__wypp__'])
+ wyppPrefixes = ['wypp.writeYourProgram.']
+ for p in wyppPrefixes:
+ if res.startswith(p):
+ return 'wypp.' + res[len(p):]
+ return res
diff --git a/python/code/wypp/parsecache.py b/python/code/wypp/parsecache.py
new file mode 100644
index 00000000..94810c6e
--- /dev/null
+++ b/python/code/wypp/parsecache.py
@@ -0,0 +1,147 @@
+import ast
+import os
+import linecache
+from dataclasses import dataclass
+from typing import *
+
+def _firstLineOfFun(node: ast.FunctionDef | ast.AsyncFunctionDef) -> int:
+ allLinenos = [node.lineno]
+ for e in node.decorator_list:
+ allLinenos.append(e.lineno)
+ return min(allLinenos)
+
+@dataclass(frozen=True)
+class FunMatcher:
+ name: str
+ firstlineno: Optional[int] = None
+
+ def matches(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
+ if node.name == self.name:
+ if self.firstlineno is not None:
+ return self.firstlineno == _firstLineOfFun(node)
+ else:
+ return True
+ else:
+ return False
+
+class AST:
+
+ def __init__(self, filename: str):
+ """
+ Do not instantiate, use getAST
+ """
+ self.__filename = filename
+ self.__tree = None
+ self.__treeResolved = False
+ self.__cache = {}
+
+ def __ensureTree(self):
+ if self.__treeResolved:
+ return
+ lines = linecache.getlines(self.__filename)
+ try:
+ tree = ast.parse(''.join(lines))
+ except Exception:
+ tree = None
+ self.__tree = tree
+ self.__treeResolved = True
+
+ def _collectFuns(self, nodes: list[ast.stmt], m: FunMatcher, recursive: bool,
+ results: list[ast.FunctionDef | ast.AsyncFunctionDef]):
+ for node in nodes:
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ if m.matches(node):
+ results.append(node)
+ if recursive:
+ self._collectFuns(node.body, m, True, results)
+
+ def _findFun(self, nodes: list[ast.stmt], m: FunMatcher, recursive: bool) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
+ """
+ Finds the function from the list of statements that matches m. None is returned
+ if no such function is found or if multiple matches are found.
+ """
+ results = []
+ self._collectFuns(nodes, m, recursive, results)
+ if len(results) == 1:
+ return results[0]
+ else:
+ return None
+
+ def getFunDef(self, m: FunMatcher) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
+ if m in self.__cache:
+ return self.__cache[m]
+ self.__ensureTree()
+ if self.__tree:
+ resultNode = self._findFun(self.__tree.body, m, True)
+ self.__cache[m] = resultNode
+ return resultNode
+
+ def _findClass(self, clsName: str) -> ast.ClassDef | None:
+ """
+ Finds the toplevel class with the given name. Returns None if no such class or
+ multiple classes are found.
+ """
+ targetClss = []
+ if self.__tree:
+ for node in self.__tree.body:
+ if isinstance(node, ast.ClassDef) and node.name == clsName:
+ targetClss.append(node)
+ if len(targetClss) == 1:
+ return targetClss[0]
+ else:
+ return None
+
+ def getRecordAttr(self, clsName: str, attrName: str) -> ast.AnnAssign | None:
+ """
+ Finds the attribute of the format `A: T` or `A: T = ...` with name attrName
+ in a toplevel class with name clsName. Returns None if no such class/attribute
+ comnbination or mutiples are found.
+ """
+ key = (clsName, attrName)
+ if key in self.__cache:
+ return self.__cache[key]
+ self.__ensureTree()
+ # Step 1: find the class
+ targetCls = self._findClass(clsName)
+ # Step 2: look for an AnnAssign like: A: T = ...
+ annNodes = []
+ if targetCls:
+ for stmt in targetCls.body:
+ if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
+ if stmt.target.id == attrName:
+ annNodes.append(stmt)
+ annNode = None
+ if len(annNodes) == 1:
+ annNode = annNodes[0]
+ self.__cache[key] = annNode
+ return annNode
+
+ def getMethodDef(self, clsName: str, m: FunMatcher) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
+ """
+ Finds the method in class clsName that matches m. None is returned if no such method
+ or multiple methods are found.
+ """
+ key = (clsName, m)
+ if key in self.__cache:
+ return self.__cache[key]
+ self.__ensureTree()
+ # Step 1: find the class
+ targetCls = self._findClass(clsName)
+ # Step 2: look for the method
+ funNode = None
+ if targetCls:
+ funNode = self._findFun(targetCls.body, m, False)
+ self.__cache[key] = funNode
+ # print(f'clsName={clsName}, m={m}, funNode={funNode}')
+ return funNode
+
+_cache: dict[str, AST] = {}
+
+def getAST(filename: str) -> AST:
+ filename = os.path.normpath(filename)
+ if filename not in _cache:
+ a = AST(filename)
+ _cache[filename] = a
+ return a
+ else:
+ return _cache[filename]
diff --git a/python/code/wypp/paths.py b/python/code/wypp/paths.py
new file mode 100644
index 00000000..b8dc9161
--- /dev/null
+++ b/python/code/wypp/paths.py
@@ -0,0 +1,27 @@
+import os
+from typing import *
+from contextlib import contextmanager
+
+_projectDir: Optional[str] = None
+
+@contextmanager
+def projectDir(d: str):
+ """Context manager to temporarily set the _relDir global variable."""
+ global _projectDir
+ old = _projectDir
+ _projectDir = _normPath(d)
+ if _projectDir and not _projectDir[-1] == '/':
+ _projectDir = _projectDir + '/'
+ try:
+ yield
+ finally:
+ _projectDir = old
+
+def _normPath(s: str) -> str:
+ return os.path.normpath(os.path.abspath(s))
+
+def canonicalizePath(s: str) -> str:
+ s = _normPath(s)
+ if _projectDir and s.startswith(_projectDir):
+ s = s[len(_projectDir):]
+ return s
diff --git a/python/code/wypp/records.py b/python/code/wypp/records.py
new file mode 100644
index 00000000..21eb3122
--- /dev/null
+++ b/python/code/wypp/records.py
@@ -0,0 +1,98 @@
+import typing
+import dataclasses
+import utils
+import sys
+import myTypeguard
+import errors
+import typecheck
+import location
+import stacktrace
+from utils import _call_with_frames_removed
+
+EQ_ATTRS_ATTR = '__eqAttrs__'
+
+_typeCheckingEnabled = False
+
+def init(enableTypeChecking=True):
+ global _typeCheckingEnabled
+ _typeCheckingEnabled = enableTypeChecking
+
+def _collectDataClassAttributes(cls):
+ result = dict()
+ for c in cls.mro():
+ if hasattr(c, '__kind') and c.__kind == 'record' and hasattr(c, '__annotations__'):
+ result = c.__annotations__ | result
+ return result
+
+def _checkRecordAttr(cls: typing.Any,
+ ns: myTypeguard.Namespaces,
+ name: str,
+ ty: typing.Any,
+ tyLoc: typing.Optional[location.Loc],
+ v: typing.Any):
+ if not typecheck.handleMatchesTyResult(myTypeguard.matchesTy(v, ty, ns), tyLoc):
+ fi = stacktrace.callerOutsideWypp()
+ if fi:
+ loc = location.Loc.fromFrameInfo(fi)
+ else:
+ loc = None
+ raise errors.WyppTypeError.recordAssignError(cls.__name__,
+ name,
+ ty,
+ tyLoc,
+ v,
+ loc)
+ return v
+
+def _patchDataClass(cls, mutable: bool, ns: myTypeguard.Namespaces):
+ fieldNames = [f.name for f in dataclasses.fields(cls)]
+ setattr(cls, EQ_ATTRS_ATTR, fieldNames)
+
+ if hasattr(cls, '__annotations__'):
+ # add annotions for type checked constructor.
+ cls.__kind = 'record'
+ cls.__init__.__annotations__ = _collectDataClassAttributes(cls)
+ cls.__init__ = typecheck.wrapTypecheckRecordConstructor(cls, ns)
+
+ if mutable:
+ # prevent new fields being added
+ fields = set(fieldNames)
+ info = location.RecordConstructorInfo(cls)
+ locs = {}
+ for name in fields:
+ if not name in cls.__annotations__:
+ raise errors.WyppTypeError.noTypeAnnotationForRecordAttribute(name, cls.__name__)
+ else:
+ locs[name] = info.getParamSourceLocation(name)
+ types = None # lazily initialized because forward references are not available at this point
+ oldSetattr = cls.__setattr__
+ def _setattr(obj, name, v):
+ nonlocal types
+ if types is None:
+ types = typing.get_type_hints(cls, globalns=ns.globals, localns=ns.locals,
+ include_extras=True)
+ if name in types:
+ ty = types[name]
+ tyLoc = locs[name]
+ v = _checkRecordAttr(cls, ns, name, ty, tyLoc, v)
+ oldSetattr(obj, name, v)
+ else:
+ raise errors.WyppAttributeError(f'Unknown attribute {name} for record {cls.__name__}')
+ setattr(cls, "__setattr__", lambda obj, k, v: _call_with_frames_removed(_setattr, obj, k, v))
+ return cls
+
+def record(cls=None, mutable=False, globals={}, locals={}):
+ ns = myTypeguard.Namespaces(globals, locals)
+ def wrap(cls: type):
+ newCls = dataclasses.dataclass(cls, frozen=not mutable)
+ if _typeCheckingEnabled:
+ return utils._call_with_frames_removed(_patchDataClass, newCls, mutable, ns)
+ else:
+ return newCls
+ # See if we're being called as @record or @record().
+ if cls is None:
+ # We're called with parens.
+ return wrap
+ else:
+ # We're called as @dataclass without parens.
+ return _call_with_frames_removed(wrap, cls)
diff --git a/python/code/wypp/renderTy.py b/python/code/wypp/renderTy.py
new file mode 100644
index 00000000..0b337444
--- /dev/null
+++ b/python/code/wypp/renderTy.py
@@ -0,0 +1,85 @@
+import collections.abc
+import types
+from typing import *
+import myTypeguard
+#def renderTy(t: Any) -> str:
+# if isinstance(t, str):
+# return t
+# return str(t)
+# # return typeguard._utils.get_type_name(t, ['__wypp__'])
+
+_NoneType = type(None)
+
+def renderTy(tp: Any) -> str:
+ # Does not work: return typeguard._utils.get_type_name(t, ['__wypp__'])
+ # For example, Callable[[int, bool], str] is formated as "Callable[list, str]"
+
+ if isinstance(tp, str):
+ # forward reference
+ return tp
+
+ origin = get_origin(tp)
+ args = get_args(tp)
+
+ # Simple / builtin / classes
+ if origin is None:
+ # e.g. int, str, custom classes, Any, typing.NoReturn, typing.Never
+ if tp is Any: return "Any"
+ if tp is _NoneType: return "None"
+ if tp is types.EllipsisType: return "..."
+ if isinstance(tp, list): return str(tp)
+ if isinstance(tp, tuple): return str(tp)
+ if isinstance(tp, dict): return str(tp)
+ return myTypeguard.getTypeName(tp)
+
+ # Union / Optional (PEP 604)
+ if origin is Union or origin is types.UnionType:
+ if len(args) == 2:
+ if args[0] is _NoneType and args[1] is not _NoneType:
+ return f"Optional[{renderTy(args[1])}]"
+ elif args[1] is _NoneType and args[0] is not _NoneType:
+ return f"Optional[{renderTy(args[0])}]"
+ parts = [renderTy(a) for a in args]
+ return "Union[" + ", ".join(parts) + "]"
+
+ # Annotated[T, ...]
+ if origin is Annotated:
+ base, *meta = args
+ if len(meta) >= 1 and isinstance(meta[-1], str):
+ return meta[-1]
+ metas = ", ".join(repr(m) for m in meta)
+ return f"Annotated[{renderTy(base)}, {metas}]"
+
+ # Literal[...]
+ if origin is Literal:
+ return "Literal[" + ", ".join(repr(a) for a in args) + "]"
+
+ # Callable[[A, B], R] or Callable[..., R]
+ if origin in (Callable, collections.abc.Callable):
+ params, ret = args
+ if params is Ellipsis:
+ params_s = "..."
+ else:
+ params_s = ", ".join(renderTy(p) for p in params)
+ return f"Callable[[{params_s}], {renderTy(ret)}]"
+
+ # Tuple[T1, T2] or Tuple[T, ...]
+ if origin is tuple:
+ if len(args) == 2 and args[1] is Ellipsis:
+ return f"tuple[{renderTy(args[0])}, ...]"
+ elif len(args) == 0:
+ return "tuple[()]"
+ return "tuple[" + ", ".join(renderTy(a) for a in args) + "]"
+
+ # ClassVar[T], Final[T]
+ if origin is ClassVar:
+ return f"ClassVar[{renderTy(args[0])}]"
+ if origin is Final:
+ return f"Final[{renderTy(args[0])}]"
+
+ # Parametrized generics like list[T], dict[K, V], set[T], type[T], etc.
+ name = getattr(origin, "__name__", None) or getattr(origin, "__qualname__", repr(origin))
+ if args:
+ return f"{name}[" + ", ".join(renderTy(a) for a in args) + "]"
+ else:
+ return name
diff --git a/python/src/replTester.py b/python/code/wypp/replTester.py
similarity index 88%
rename from python/src/replTester.py
rename to python/code/wypp/replTester.py
index 426a2e2c..2c5f1bdd 100644
--- a/python/src/replTester.py
+++ b/python/code/wypp/replTester.py
@@ -1,9 +1,13 @@
import sys
+import constants
+sys.path.insert(0, constants.CODE_DIR)
+
import doctest
import os
import argparse
from dataclasses import dataclass
-from runner import runCode, importUntypy, verbose, enableVerbose
+from myLogging import *
+import runCode
usage = """python3 replTester.py [ ARGUMENTS ] LIB_1 ... LIB_n --repl SAMPLE_1 ... SAMPLE_m
@@ -56,8 +60,6 @@ def parseCmdlineArgs():
libDir = os.path.dirname(__file__)
libFile = os.path.join(libDir, 'writeYourProgram.py')
defs = globals()
-importUntypy()
-# runCode(libFile, defs, [])
for lib in opts.libs:
d = os.path.dirname(lib)
@@ -66,7 +68,7 @@ def parseCmdlineArgs():
for lib in opts.libs:
verbose(f"Loading lib {lib}")
- runCode(lib, defs, [])
+ defs = runCode.runCode(lib, defs)
totalFailures = 0
totalTests = 0
@@ -79,9 +81,9 @@ def parseCmdlineArgs():
# We use our own DocTestParser to replace exception names in stacktraces
class MyDocTestParser(doctest.DocTestParser):
def get_examples(self, string, name=''):
- prefs = {'WyppTypeError: ': 'untypy.error.UntypyTypeError: ',
- 'WyppNameError: ': 'untypy.error.UntypyNameError: ',
- 'WyppAttributeError: ': 'untypy.error.UntypyAttributeError: '}
+ prefs = {'WyppTypeError: ': 'errors.WyppTypeError: ',
+ 'WyppNameError: ': 'errors.WyppNameError: ',
+ 'WyppAttributeError: ': 'errors.WyppAttributeError: '}
lines = []
for l in string.split('\n'):
for pref,repl in prefs.items():
@@ -111,7 +113,7 @@ def get_examples(self, string, name=''):
print('ERROR: No tests found at all!')
sys.exit(1)
else:
- print(f'All {totalTests} tests succeded. Great!')
+ print(f'All {totalTests} tests succeeded. Great!')
else:
print(f'ERROR: {failures} out of {tests} failed')
sys.exit(1)
diff --git a/python/code/wypp/runCode.py b/python/code/wypp/runCode.py
new file mode 100644
index 00000000..e8014e43
--- /dev/null
+++ b/python/code/wypp/runCode.py
@@ -0,0 +1,111 @@
+import sys
+import os
+import importlib
+import runpy
+from dataclasses import dataclass
+
+# local imports
+from constants import *
+import stacktrace
+import instrument
+from myLogging import *
+from exceptionHandler import handleCurrentException
+import utils
+
+class Lib:
+ def __init__(self, mod, properlyImported):
+ self.properlyImported = properlyImported
+ if not properlyImported:
+ self.initModule = mod['initModule']
+ self.resetTestCount = mod['resetTestCount']
+ self.printTestResults = mod['printTestResults']
+ self.dict = mod
+ else:
+ self.initModule = mod.initModule
+ self.resetTestCount = mod.resetTestCount
+ self.printTestResults = mod.printTestResults
+ d = {}
+ self.dict = d
+ for name in dir(mod):
+ if name and name[0] != '_':
+ d[name] = getattr(mod, name)
+
+
+@dataclass
+class RunSetup:
+ def __init__(self, pathDir: str, args: list[str]):
+ self.pathDir = pathDir
+ self.args = args
+ self.sysPathInserted = False
+ self.oldArgs = sys.argv
+ def __enter__(self):
+ if self.pathDir not in sys.path:
+ sys.path.insert(0, self.pathDir)
+ self.sysPathInserted = True
+ sys.argv = self.args
+ self.originalProfile = sys.getprofile()
+ stacktrace.installProfileHook()
+ def __exit__(self, exc_type, value, traceback):
+ sys.setprofile(self.originalProfile)
+ if self.sysPathInserted:
+ sys.path.remove(self.pathDir)
+ self.sysPathInserted = False
+ sys.argv = self.oldArgs
+
+def prepareLib(onlyCheckRunnable, enableTypeChecking):
+ libDefs = None
+ mod = 'wypp'
+ verbose('Attempting to import ' + mod)
+ wypp = importlib.import_module(mod)
+ libDefs = Lib(wypp, True)
+ verbose(f'Successfully imported module {mod} from file {wypp.__file__}')
+ libDefs.initModule(enableChecks=not onlyCheckRunnable,
+ enableTypeChecking=enableTypeChecking,
+ quiet=onlyCheckRunnable)
+ return libDefs
+
+def runCode(fileToRun, globals, doTypecheck=True, extraDirs=None) -> dict:
+ if not extraDirs:
+ extraDirs = []
+ with instrument.setupFinder(os.path.dirname(fileToRun), extraDirs, doTypecheck):
+ modName = os.path.basename(os.path.splitext(fileToRun)[0])
+ sys.dont_write_bytecode = True
+ res = runpy.run_module(modName, init_globals=globals, run_name='__wypp__', alter_sys=False)
+ return res
+
+def runStudentCode(fileToRun, globals, onlyCheckRunnable, doTypecheck=True, extraDirs=None) -> dict:
+ doRun = lambda: runCode(fileToRun, globals, doTypecheck=doTypecheck, extraDirs=extraDirs)
+ if onlyCheckRunnable:
+ try:
+ doRun()
+ except:
+ printStderr('Loading file %s crashed' % fileToRun)
+ handleCurrentException()
+ else:
+ utils.die(0)
+ return doRun()
+
+# globals already contain libDefs
+def runTestsInFile(testFile, globals, libDefs, doTypecheck=True, extraDirs=[]):
+ printStderr()
+ printStderr(f"Running tutor's tests in {testFile}")
+ libDefs.resetTestCount()
+ try:
+ runCode(testFile, globals, doTypecheck=doTypecheck, extraDirs=extraDirs)
+ except:
+ handleCurrentException()
+ return libDefs.dict['printTestResults']('Tutor: ')
+
+# globals already contain libDefs
+def performChecks(check, testFile, globals, libDefs, doTypecheck=True, extraDirs=None, loadingFailed=False):
+ prefix = ''
+ if check and testFile:
+ prefix = 'Student: '
+ testResultsStudent = libDefs.printTestResults(prefix, loadingFailed)
+ if check:
+ testResultsInstr = {'total': 0, 'failing': 0}
+ if testFile:
+ testResultsInstr = runTestsInFile(testFile, globals, libDefs, doTypecheck=doTypecheck,
+ extraDirs=extraDirs)
+ failingSum = testResultsStudent['failing'] + testResultsInstr['failing']
+ utils.die(0 if failingSum < 1 else 1)
diff --git a/python/src/runYourProgram.py b/python/code/wypp/runYourProgram.py
similarity index 80%
rename from python/src/runYourProgram.py
rename to python/code/wypp/runYourProgram.py
index cd2dd616..672b88ba 100644
--- a/python/src/runYourProgram.py
+++ b/python/code/wypp/runYourProgram.py
@@ -1,7 +1,7 @@
# coding=utf-8
+# This is the entry point into running a python file via wypp.
# NOTE: this file must have valid python 2 syntax. We want to display an error message
# when running with python 2.
-
import sys
pythonVersion = sys.version.split()[0]
if not pythonVersion.startswith('3.'):
@@ -13,5 +13,5 @@
sys.exit(1)
if __name__ == '__main__':
- import runner
- runner.main(globals())
+ import runner as r
+ r.main(globals())
diff --git a/python/code/wypp/runner.py b/python/code/wypp/runner.py
new file mode 100644
index 00000000..9dd06ada
--- /dev/null
+++ b/python/code/wypp/runner.py
@@ -0,0 +1,105 @@
+import sys
+import constants
+sys.path.insert(0, constants.CODE_DIR)
+
+import sys
+import os
+
+# local imports
+from constants import *
+import i18n
+import paths
+from myLogging import *
+import version as versionMod
+import interactive
+import runCode
+import exceptionHandler
+import cmdlineArgs
+
+requiredVersion = (3, 12, 0)
+def pythonVersionOk(v):
+ (reqMajor, reqMinor, reqMicro) = requiredVersion
+ if v.major < reqMajor or v.minor < reqMinor:
+ return False
+ if v.major == reqMajor and v.minor == reqMinor and v.micro < reqMicro:
+ return False
+ else:
+ return True
+
+def printWelcomeString(file, version, doTypecheck):
+ cwd = os.getcwd() + "/"
+ if file.startswith(cwd):
+ file = file[len(cwd):]
+ versionStr = '' if not version else 'Version %s, ' % version
+ pythonVersion = sys.version.split()[0]
+ tycheck = ''
+ if not doTypecheck:
+ tycheck = ', no typechecking'
+ printStderr('=== WELCOME to "Write Your Python Program" ' +
+ '(%sPython %s, %s%s) ===' % (versionStr, pythonVersion, file, tycheck))
+
+def main(globals, argList=None):
+ v = sys.version_info
+ if not pythonVersionOk(v):
+ vStr = sys.version.split()[0]
+ reqVStr = '.'.join([str(x) for x in requiredVersion])
+ print(f"""
+Python in version {reqVStr} or newer is required. You are still using version {vStr}, please upgrade!
+""")
+ sys.exit(1)
+
+ (args, restArgs) = cmdlineArgs.parseCmdlineArgs(argList)
+ if args.verbose:
+ enableVerbose()
+ if args.debug:
+ enableDebug()
+
+ verbose(f'VERBOSE={args.verbose}, DEBUG={args.debug}')
+
+ if args.lang:
+ if args.lang in i18n.allLanguages:
+ i18n.setLang(args.lang)
+ else:
+ printStderr(f'Unsupported language {args.lang}. Supported: ' + ', '.join(i18n.allLanguages))
+ sys.exit(1)
+
+ fileToRun: str = args.file
+ if args.changeDir:
+ os.chdir(os.path.dirname(fileToRun))
+ fileToRun = os.path.basename(fileToRun)
+
+ isInteractive = args.interactive
+ version = versionMod.readVersion()
+ if isInteractive:
+ interactive.prepareInteractive(reset=not args.noClear)
+
+ if fileToRun is None:
+ return
+
+ if not args.checkRunnable and (not args.quiet or args.verbose):
+ printWelcomeString(fileToRun, version, doTypecheck=args.checkTypes)
+
+ libDefs = runCode.prepareLib(onlyCheckRunnable=args.checkRunnable, enableTypeChecking=args.checkTypes)
+
+ with (runCode.RunSetup(os.path.dirname(fileToRun), [fileToRun] + restArgs),
+ paths.projectDir(os.path.abspath(os.getcwd()))):
+ globals['__name__'] = '__wypp__'
+ sys.modules['__wypp__'] = sys.modules['__main__']
+ loadingFailed = False
+ try:
+ verbose(f'running code in {fileToRun}')
+ globals['__file__'] = fileToRun
+ globals = runCode.runStudentCode(fileToRun, globals, args.checkRunnable,
+ doTypecheck=args.checkTypes, extraDirs=args.extraDirs)
+ except Exception as e:
+ verbose(f'Error while running code in {fileToRun}: {e}')
+ exceptionHandler.handleCurrentException(exit=not isInteractive)
+ loadingFailed = True
+
+ runCode.performChecks(args.check, args.testFile, globals, libDefs, doTypecheck=args.checkTypes,
+ extraDirs=args.extraDirs, loadingFailed=loadingFailed)
+
+ if isInteractive:
+ interactive.enterInteractive(globals, args.checkTypes, loadingFailed)
+
+
diff --git a/python/code/wypp/stacktrace.py b/python/code/wypp/stacktrace.py
new file mode 100644
index 00000000..eec485c4
--- /dev/null
+++ b/python/code/wypp/stacktrace.py
@@ -0,0 +1,120 @@
+import types
+import traceback
+import utils
+import inspect
+from typing import Optional, Any
+import os
+import sys
+from collections import deque
+
+def tbToFrameList(tb: types.TracebackType) -> list[types.FrameType]:
+ cur = tb
+ res: list[types.FrameType] = []
+ while cur:
+ res.append(cur.tb_frame)
+ cur = cur.tb_next
+ return res
+
+def isCallWithFramesRemoved(frame: types.FrameType):
+ return frame.f_code.co_name == utils._call_with_frames_removed.__name__
+
+def isCallWithNextFrameRemoved(frame: types.FrameType):
+ return frame.f_code.co_name == utils._call_with_next_frame_removed.__name__
+
+def isWyppFrame(frame: types.FrameType):
+ modName = frame.f_globals.get("__name__") or '__wypp__'
+ fname = frame.f_code.co_filename
+ directDir = os.path.basename(os.path.dirname(fname))
+ if '__wypp_runYourProgram' in frame.f_globals:
+ return True
+ for prefix in ['wypp', 'typeguard']:
+ if modName == prefix or modName.startswith(prefix + '.') or directDir == prefix:
+ return True
+ return False
+
+def isRunpyFrame(frame: types.FrameType) -> bool:
+ f = frame.f_code.co_filename
+ return f == '' or f.startswith(' traceback.StackSummary:
+ if filter:
+ # Step 1: remove all frames that appear after the first _call_with_frames_removed
+ endIdx = len(frameList)
+ for i in range(len(frameList)):
+ if isCallWithFramesRemoved(frameList[i]):
+ endIdx = i - 1
+ break
+ frameList = frameList[:endIdx]
+ # Step 2: remove those frames directly after _call_with_next_frame_removed
+ toRemove = []
+ for i in range(len(frameList)):
+ if isCallWithNextFrameRemoved(frameList[i]):
+ toRemove.append(i)
+ for i in reversed(toRemove):
+ frameList = frameList[:i-1] + frameList[i+1:]
+ # Step 3: remove leading wypp or typeguard frames
+ frameList = utils.dropWhile(frameList, lambda f: isWyppFrame(f) or isRunpyFrame(f))
+ frameList = frameList + [f.frame for f in extraFrames]
+ frames = [(f, f.f_lineno) for f in frameList]
+ return traceback.StackSummary.extract(frames)
+
+def callerOutsideWypp() -> Optional[inspect.FrameInfo]:
+ stack = inspect.stack()
+ d = os.path.dirname(stack[0].filename)
+ for f in stack:
+ if not isWyppFrame(f.frame) and not d == os.path.dirname(f.filename):
+ return f
+ return None
+
+class ReturnTracker:
+ def __init__(self, entriesToKeep: int):
+ self.__returnFrames = deque(maxlen=entriesToKeep) # a ring buffer
+ def __call__(self, frame: types.FrameType, event: str, arg: Any):
+ # event is one of 'call', 'return', 'c_call', 'c_return', or 'c_exception'
+ match event:
+ case 'call':
+ pass
+ case 'return':
+ # print(f'appending {frame} to return tracker')
+ self.__returnFrames.append(frame) # overwrite oldest when full
+ case 'c_call':
+ pass
+ case 'c_return':
+ pass
+ case 'c_exception':
+ pass
+ def getReturnFrame(self, idx: int) -> Optional[inspect.FrameInfo]:
+ try:
+ f = self.__returnFrames[idx]
+ except IndexError:
+ return None
+ if f:
+ tb = inspect.getframeinfo(f, context=1)
+ fi = inspect.FrameInfo(f, tb.filename, tb.lineno, tb.function, tb.code_context, tb.index)
+ del f
+ return fi
+ else:
+ return None
+
+# when using _call_with_next_frame_removed, we have to take the second-to-last
+# return. Hence, we keep the two most recent returns byn setting entriesToKeep = 2.
+def installProfileHook(entriesToKeep: int=2) -> ReturnTracker:
+ obj = sys.getprofile()
+ if isinstance(obj, ReturnTracker):
+ return obj
+ obj = ReturnTracker(entriesToKeep)
+ sys.setprofile(obj)
+ return obj
+
+def getReturnTracker():
+ obj = sys.getprofile()
+ if isinstance(obj, ReturnTracker):
+ return obj
+ else:
+ raise ValueError(f'No ReturnTracker set, must use installProfileHook before')
diff --git a/python/code/wypp/typecheck.py b/python/code/wypp/typecheck.py
new file mode 100644
index 00000000..bd598077
--- /dev/null
+++ b/python/code/wypp/typecheck.py
@@ -0,0 +1,196 @@
+from __future__ import annotations
+from collections.abc import Callable
+from typing import ParamSpec, TypeVar, Any, Optional, Literal
+import inspect
+from dataclasses import dataclass
+import utils
+from myTypeguard import matchesTy, MatchesTyResult, MatchesTyFailure, Namespaces
+import stacktrace
+import location
+import errors
+from myLogging import *
+
+def printVars(what: str, *l):
+ s = what + ": " + ', '.join([str(x) for x in l])
+ print(s)
+
+def isEmptyAnnotation(t: Any) -> bool:
+ return t is inspect.Signature.empty or t is inspect.Parameter.empty
+
+def isEmptySignature(sig: inspect.Signature) -> bool:
+ for x in sig.parameters:
+ p = sig.parameters[x]
+ if not isEmptyAnnotation(p.annotation):
+ return False
+ return isEmptyAnnotation(sig.return_annotation)
+
+def handleMatchesTyResult(res: MatchesTyResult, tyLoc: Optional[location.Loc]) -> bool:
+ match res:
+ case MatchesTyFailure(exc, ty):
+ if isDebug():
+ debug(f'Exception occurred while calling matchesTy with type {ty}, re-raising')
+ raise exc
+ else:
+ raise errors.WyppTypeError.invalidType(ty, tyLoc)
+ case b:
+ return b
+
+def getKind(cfg: CheckCfg, paramNames: list[str]) -> Literal['function', 'method', 'staticmethod']:
+ kind: Literal['function', 'method', 'staticmethod'] = 'function'
+ match cfg.kind:
+ case location.ClassMember('recordConstructor', _):
+ kind = 'method'
+ case location.ClassMember('method', _):
+ if len(paramNames) > 0 and paramNames[0] == 'self':
+ kind = 'method'
+ else:
+ kind = 'staticmethod'
+ case 'function':
+ kind = 'function'
+ return kind
+
+def checkSignature(sig: inspect.Signature, info: location.CallableInfo, cfg: CheckCfg) -> None:
+ paramNames = list(sig.parameters)
+ kind = getKind(cfg, paramNames)
+ for i in range(len(paramNames)):
+ name = paramNames[i]
+ p = sig.parameters[name]
+ ty = p.annotation
+ if isEmptyAnnotation(ty):
+ if i == 0 and kind == 'method':
+ pass
+ else:
+ locDecl = info.getParamSourceLocation(name)
+ raise errors.WyppTypeError.partialAnnotationError(location.CallableName.mk(info), name, locDecl)
+ if p.default is not inspect.Parameter.empty:
+ locDecl = info.getParamSourceLocation(name)
+ if not handleMatchesTyResult(matchesTy(p.default, ty, cfg.ns), locDecl):
+ raise errors.WyppTypeError.defaultError(location.CallableName.mk(info), name, locDecl, ty, p.default)
+
+def mandatoryArgCount(sig: inspect.Signature) -> int:
+ required_kinds = {
+ inspect.Parameter.POSITIONAL_ONLY,
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
+ inspect.Parameter.KEYWORD_ONLY,
+ }
+ res = 0
+ for p in sig.parameters.values():
+ if p.kind in required_kinds and p.default is inspect._empty:
+ res = res + 1
+ return res
+
+def checkArgument(p: inspect.Parameter, name: str, idx: Optional[int], a: Any,
+ locArg: Optional[location.Loc], info: location.CallableInfo, cfg: CheckCfg):
+ t = p.annotation
+ if not isEmptyAnnotation(t):
+ locDecl = info.getParamSourceLocation(name)
+ if not handleMatchesTyResult(matchesTy(a, t, cfg.ns), locDecl):
+ cn = location.CallableName.mk(info)
+ raise errors.WyppTypeError.argumentError(cn,
+ name,
+ idx,
+ locDecl,
+ t,
+ a,
+ locArg)
+def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict,
+ info: location.CallableInfo, cfg: CheckCfg) -> None:
+ debug(f'Checking arguments when calling {info}')
+ paramNames = list(sig.parameters)
+ mandatory = mandatoryArgCount(sig)
+ kind = getKind(cfg, paramNames)
+ offset = 1 if kind == 'method' else 0
+ fi = stacktrace.callerOutsideWypp()
+ callLoc = None if not fi else location.Loc.fromFrameInfo(fi)
+ cn = location.CallableName.mk(info)
+ def raiseArgMismatch():
+ raise errors.WyppTypeError.argCountMismatch(cn,
+ callLoc,
+ len(paramNames) - offset,
+ mandatory - offset,
+ len(args) - offset)
+ if len(args) + len(kwargs) < mandatory:
+ raiseArgMismatch()
+ for i in range(len(args)):
+ if i >= len(paramNames):
+ raiseArgMismatch()
+ name = paramNames[i]
+ p = sig.parameters[name]
+ locArg = None if fi is None else location.locationOfArgument(fi, i)
+ checkArgument(p, name, i - offset, args[i], locArg, info, cfg)
+ for name in kwargs:
+ if name not in sig.parameters:
+ raise errors.WyppTypeError.unknownKeywordArgument(cn, callLoc, name)
+ locArg = None if fi is None else location.locationOfArgument(fi, name)
+ checkArgument(sig.parameters[name], name, None, kwargs[name], locArg, info, cfg)
+
+def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo],
+ result: Any, info: location.CallableInfo, cfg: CheckCfg) -> None:
+ t = sig.return_annotation
+ if isEmptyAnnotation(t):
+ t = None
+ debug(f'Checking return value when calling {info}, return type: {t}')
+ locDecl = info.getResultTypeLocation()
+ if not handleMatchesTyResult(matchesTy(result, t, cfg.ns), locDecl):
+ fi = stacktrace.callerOutsideWypp()
+ if fi is not None:
+ locRes = location.Loc.fromFrameInfo(fi)
+ returnLoc = None
+ extraFrames = []
+ if returnFrame:
+ returnLoc = location.Loc.fromFrameInfo(returnFrame)
+ extraFrames = [returnFrame]
+ raise errors.WyppTypeError.resultError(location.CallableName.mk(info), locDecl, t, returnLoc, result,
+ locRes, extraFrames)
+
+
+@dataclass
+class CheckCfg:
+ kind: location.CallableKind
+ ns: Namespaces
+ @staticmethod
+ def fromDict(d: dict) -> CheckCfg:
+ k = d['kind']
+ match k:
+ case 'function':
+ kind = 'function'
+ case 'method':
+ kind = location.ClassMember('method', d['className'])
+ return CheckCfg(kind=kind, ns=Namespaces(d['globals'], d['locals']))
+
+P = ParamSpec("P")
+T = TypeVar("T")
+
+def wrapTypecheck(cfg: dict | CheckCfg, outerInfo: Optional[location.CallableInfo]=None) -> Callable[[Callable[P, T]], Callable[P, T]]:
+ if isinstance(cfg, CheckCfg):
+ checkCfg = cfg
+ else:
+ checkCfg = CheckCfg.fromDict(cfg)
+ def _wrap(f: Callable[P, T]) -> Callable[P, T]:
+ sig = inspect.signature(f)
+ if isEmptySignature(sig):
+ return f
+ if outerInfo is None:
+ # we have a regular function or method
+ info = location.StdCallableInfo(f, checkCfg.kind)
+ else:
+ # special case: constructor of a record
+ info = outerInfo
+ utils._call_with_frames_removed(checkSignature, sig, info, checkCfg)
+ def wrapped(*args, **kwargs) -> T:
+ utils._call_with_frames_removed(checkArguments, sig, args, kwargs, info, checkCfg)
+ returnTracker = stacktrace.getReturnTracker()
+ result = utils._call_with_next_frame_removed(f, *args, **kwargs)
+ retFrame = returnTracker.getReturnFrame(0)
+ utils._call_with_frames_removed(
+ checkReturn, sig, retFrame, result, info, checkCfg
+ )
+ return result
+ return wrapped
+ return _wrap
+
+def wrapTypecheckRecordConstructor(cls: type, ns: Namespaces) -> Callable:
+ checkCfg = CheckCfg.fromDict({'kind': 'method', 'className': cls.__name__,
+ 'globals': ns.globals, 'locals': ns.locals})
+ info = location.RecordConstructorInfo(cls)
+ return wrapTypecheck(checkCfg, info)(cls.__init__)
diff --git a/python/code/wypp/utils.py b/python/code/wypp/utils.py
new file mode 100644
index 00000000..736cbab9
--- /dev/null
+++ b/python/code/wypp/utils.py
@@ -0,0 +1,102 @@
+from typing import *
+import os
+import sys
+from contextlib import contextmanager
+
+P = ParamSpec("P")
+T = TypeVar("T")
+
+# The name of this function is magical. All stack frames that appear
+# nested within this function are removed from tracebacks.
+def _call_with_frames_removed(
+ f: Callable[P, T], *args: P.args, **kwargs: P.kwargs
+) -> T:
+ return f(*args, **kwargs)
+
+# The name of this function is magical. The next stack frame that appears
+# nested within this function is removed from tracebacks.
+def _call_with_next_frame_removed(
+ f: Callable[P, T], *args: P.args, **kwargs: P.kwargs
+) -> T:
+ return f(*args, **kwargs)
+
+def getEnv(name, conv, default):
+ s = os.getenv(name)
+ if s is None:
+ return default
+ try:
+ return conv(s)
+ except:
+ return default
+
+def dropWhile(l: list, f: Callable[[Any], bool]) -> list:
+ if not l:
+ return l
+ for i in range(len(l)):
+ if not f(l[i]):
+ break
+ else:
+ # All elements satisfied the condition, return empty list
+ return []
+ return l[i:]
+
+def split(l: list, f: Callable[[Any], bool]) -> tuple[list, list]:
+ """
+ span, applied to a list l and a predicate f, returns a tuple where first element is the
+ longest prefix (possibly empty) of l of elements that satisfy f and second element
+ is the remainder of the list.
+ """
+ inFirst = True
+ first = []
+ second = []
+ for x in l:
+ if inFirst:
+ if f(x):
+ first.append(x)
+ else:
+ inFirst = False
+ second.append(x)
+ else:
+ second.append(x)
+ return (first, second)
+
+
+def isUnderTest() -> bool:
+ x = os.getenv('WYPP_UNDER_TEST')
+ return x == 'True'
+
+@contextmanager
+def underTest(value: bool = True):
+ """Context manager to temporarily set WYPP_UNDER_TEST environment variable."""
+ oldValue = os.getenv('WYPP_UNDER_TEST')
+ os.environ['WYPP_UNDER_TEST'] = str(value)
+ try:
+ yield
+ finally:
+ if oldValue is None:
+ # Remove the environment variable if it didn't exist before
+ os.environ.pop('WYPP_UNDER_TEST', None)
+ else:
+ # Restore the original value
+ os.environ['WYPP_UNDER_TEST'] = oldValue
+
+
+def die(ecode: str | int | None = 1):
+ if isinstance(ecode, str):
+ sys.stderr.write(ecode)
+ sys.stderr.write('\n')
+ ecode = 1
+ elif ecode == None:
+ ecode = 0
+ if sys.flags.interactive:
+ os._exit(ecode)
+ else:
+ sys.exit(ecode)
+
+def readFile(path):
+ try:
+ with open(path, encoding='utf-8') as f:
+ return f.read()
+ except UnicodeDecodeError:
+ with open(path) as f:
+ return f.read()
diff --git a/python/code/wypp/version.py b/python/code/wypp/version.py
new file mode 100644
index 00000000..c1c8ecef
--- /dev/null
+++ b/python/code/wypp/version.py
@@ -0,0 +1,40 @@
+import os
+import json
+import subprocess
+from dataclasses import dataclass
+
+# local imports
+from constants import *
+from myLogging import *
+import utils
+
+def readGitVersion():
+ thisDir = os.path.basename(SOURCE_DIR)
+ baseDir = os.path.join(SOURCE_DIR, '..', '..')
+ if thisDir == 'src' and os.path.isdir(os.path.join(baseDir, '.git')):
+ try:
+ h = subprocess.check_output(['git', '-C', baseDir, 'rev-parse', '--short', 'HEAD'],
+ encoding='UTF-8').strip()
+ changes = subprocess.check_output(
+ ['git', '-C', baseDir, 'status', '--porcelain', '--untracked-files=no'],
+ encoding='UTF-8').strip()
+ if changes:
+ return f'git-{h}-dirty'
+ else:
+ return f'git-{h}'
+ except subprocess.CalledProcessError:
+ return 'git-?'
+ else:
+ return None
+
+def readVersion():
+ version = readGitVersion()
+ if version is not None:
+ return version
+ try:
+ content = utils.readFile(os.path.join(SOURCE_DIR, '..', '..', 'package.json'))
+ d = json.loads(content)
+ version = d['version']
+ except:
+ pass
+ return version
diff --git a/python/src/writeYourProgram.py b/python/code/wypp/writeYourProgram.py
similarity index 72%
rename from python/src/writeYourProgram.py
rename to python/code/wypp/writeYourProgram.py
index ce22370f..55902409 100644
--- a/python/src/writeYourProgram.py
+++ b/python/code/wypp/writeYourProgram.py
@@ -1,9 +1,14 @@
-# FIXME: make exceptions nicer
-import untypy
import typing
import dataclasses
import inspect
-import types
+import errors
+import typecheck
+import records
+import stacktrace
+import renderTy
+import location
+import paths
+import utils
_DEBUG = False
def _debug(s):
@@ -27,8 +32,7 @@ def _debug(s):
Callable = typing.Callable
dataclass = dataclasses.dataclass
-
-unchecked = untypy.unchecked
+record = records.record
intPositive = typing.Annotated[int, lambda i: i > 0, 'intPositive']
nat = typing.Annotated[int, lambda i: i >= 0, 'nat']
@@ -44,10 +48,8 @@ def _debug(s):
class Lock(Protocol):
def acquire(self, blocking: bool = True, timeout:int = -1) -> Any:
pass
-
def release(self) -> Any:
pass
-
def locked(self) -> Any:
pass
@@ -57,6 +59,8 @@ def locked(self) -> Any:
U = typing.TypeVar('U')
V = typing.TypeVar('V')
+# Dirty hack ahead: we patch some methods of internal class of the typing module.
+
def _literalInstanceOf(self, value):
for p in self.__args__:
# typing.Literal checks for type(value) == type(arg),
@@ -65,6 +69,11 @@ def _literalInstanceOf(self, value):
return True
return False
+# This patch is needed to be able to use a literal L to check whether a value x
+# is an instance of this literal: isinstance(x, L)
+# pyright does not know about typing._LiteralGenericAlias, we do not typecheck the following line.
+setattr(typing._LiteralGenericAlias, '__instancecheck__', _literalInstanceOf) # type: ignore
+
def _invalidCall(self, *args, **kwds):
if hasattr(self, '__name__'):
name = self.__name__
@@ -77,81 +86,35 @@ def formatArg(x):
if name == 'Literal':
return repr(x)
else:
- return x
- argStr = ', '.join([formatArg(untypy.util.typehints.qualname(x)) for x in args])
- raise untypy.error.WyppTypeError(f"Cannot call {name} like a function. Did you mean {name}[{argStr}]?")
-
-# Dirty hack ahead: we patch some methods of internal class of the typing module.
-
-# This patch is needed to be able to use a literal L to check whether a value x
-# is an instance of this literal: instanceof(x, L)
-setattr(typing._LiteralGenericAlias, '__instancecheck__', _literalInstanceOf)
+ return renderTy.renderTy(x)
+ caller = stacktrace.callerOutsideWypp()
+ loc = None if caller is None else location.Loc.fromFrameInfo(caller)
+ argStr = ', '.join([formatArg(x) for x in args])
+ tyStr = f'{name}({argStr})'
+ raise utils._call_with_frames_removed(errors.WyppTypeError.invalidType, tyStr, loc)
# This patch is needed to provide better error messages if a student passes type arguments
# with paranthesis instead of square brackets
setattr(typing._SpecialForm, '__call__', _invalidCall)
-def _collectDataClassAttributes(cls):
- result = dict()
- for c in cls.mro():
- if hasattr(c, '__kind') and c.__kind == 'record' and hasattr(c, '__annotations__'):
- result = c.__annotations__ | result
- return result
-
-
-def _patchDataClass(cls, mutable):
- fieldNames = [f.name for f in dataclasses.fields(cls)]
- setattr(cls, EQ_ATTRS_ATTR, fieldNames)
-
- if hasattr(cls, '__annotations__'):
- # add annotions for type checked constructor.
- cls.__kind = 'record'
- cls.__init__.__annotations__ = _collectDataClassAttributes(cls)
- cls.__init__.__original = cls # mark class as source of annotation
- cls.__init__ = untypy.typechecked(cls.__init__)
-
- if mutable:
- # prevent new fields being added
- fields = set(fieldNames)
-
- checker = {}
- # Note: Partial annotations are disallowed by untypy.typechecked(cls.__init__)
- # So no handling in this code is required.
- for name in fields:
- if name in cls.__annotations__:
- # This the type is wrapped in an lambda expression to allow for Forward Ref.
- # Would the lambda expression be called at this moment, it may cause an name error
- # untypy.checker fetches the annotation lazily.
- checker[name] = untypy.checker(\
- lambda cls=cls,name=name: typing.get_type_hints(cls, include_extras=True)[name],
- cls)
-
- oldSetattr = cls.__setattr__
- def _setattr(obj, k, v):
- # Note __setattr__ also gets called in the constructor.
- if k in checker:
- oldSetattr(obj, k, checker[k](v))
- elif k in fields:
- oldSetattr(obj, k, v)
+def typechecked(func=None):
+ def wrap(func):
+ if hasattr(func, '__qualname__'):
+ q = getattr(func, "__qualname__").split('.')
+ if len(q) >= 2:
+ className = q[-2]
+ cfg = {'kind': 'method', 'className': className}
else:
- raise untypy.error.WyppAttributeError(f'Unknown attribute {k} for record {cls.__name__}')
- setattr(cls, "__setattr__", _setattr)
- return cls
-
-def record(cls=None, mutable=False):
- def wrap(cls):
- newCls = dataclasses.dataclass(cls, frozen=not mutable)
- if _typeCheckingEnabled:
- return _patchDataClass(newCls, mutable)
+ cfg = {'kind': 'function'}
else:
- return newCls
- # See if we're being called as @record or @record().
- if cls is None:
+ cfg = {'kind': 'function'}
+ return typecheck.wrapTypecheck(cfg)(func)
+ if func is None:
# We're called with parens.
return wrap
else:
# We're called as @dataclass without parens.
- return wrap(cls)
+ return wrap(func)
# Tests
@@ -172,6 +135,7 @@ def initModule(enableChecks=True, enableTypeChecking=True, quiet=False):
global _checksEnabled, _typeCheckingEnabled
_checksEnabled = enableChecks
_typeCheckingEnabled = enableTypeChecking
+ records.init(enableTypeChecking)
resetTestCount()
def resetTestCount():
@@ -232,7 +196,8 @@ def checkGeneric(actual, expected, *, structuralObjEq=True, floatEqWithDelta=Tru
stack = inspect.stack()
frame = stack[2] if len(stack) > 2 else None
if frame:
- caller = f"{frame.filename}:{frame.lineno}: "
+ filename = paths.canonicalizePath(frame.filename)
+ caller = f"Datei {filename}, Zeile {frame.lineno}: "
else:
caller = ""
def fmt(x):
@@ -259,8 +224,11 @@ def checkFail(msg: str):
def uncoveredCase():
stack = inspect.stack()
- caller = stack[1] if len(stack) > 1 else None
- raise Exception(f"{caller.filename}, Zeile {caller.lineno}: ein Fall ist nicht abgedeckt")
+ if len(stack) > 1:
+ caller = stack[1]
+ raise Exception(f"{caller.filename}, Zeile {caller.lineno}: ein Fall ist nicht abgedeckt")
+ else:
+ raise Exception(f"Ein Fall ist nicht abgedeckt")
#
# Deep equality
@@ -292,9 +260,9 @@ def _objToDict(o):
d = {}
attrs = dir(o)
useAll = False
- if hasattr(o, EQ_ATTRS_ATTR):
+ if hasattr(o, records.EQ_ATTRS_ATTR):
useAll = True
- attrs = getattr(o, EQ_ATTRS_ATTR)
+ attrs = getattr(o, records.EQ_ATTRS_ATTR)
for n in attrs:
if not useAll and n and n[0] == '_':
continue
@@ -307,7 +275,6 @@ def _objEq(o1, o2, flags):
return _dictEq(_objToDict(o1), _objToDict(o2), flags)
_EPSILON = 0.00001
-EQ_ATTRS_ATTR = '__eqAttrs__'
def _useFloatEqWithDelta(flags):
return flags.get('floatEqWithDelta', False)
@@ -354,21 +321,15 @@ def deepEq(v1, v2, **flags):
return False # v1 == v2 already checked
return False
-class TodoError(Exception, untypy.error.DeliberateError):
- pass
-
def todo(msg=None):
if msg is None:
msg = 'TODO'
- raise TodoError(msg)
-
-class ImpossibleError(Exception, untypy.error.DeliberateError):
- pass
+ raise errors.TodoError(msg)
def impossible(msg=None):
if msg is None:
- msg = 'the impossible happened'
- raise ImpossibleError(msg)
+ msg = 'Das Unmögliche ist passiert!'
+ raise errors.ImpossibleError(msg)
# Additional functions and aliases
@@ -376,4 +337,4 @@ def impossible(msg=None):
math = moduleMath
-
+wrapTypecheck = typecheck.wrapTypecheck
diff --git a/python/debug.py b/python/debug.py
index 5ffdb0cb..e2660da4 100644
--- a/python/debug.py
+++ b/python/debug.py
@@ -1,4 +1,4 @@
-# Run this file with the debugger to debug wypp and untypy.
+# Run this file with the debugger to debug wypp.
# Before running, set FILE to the input file that you want
# to use for debugging.
@@ -11,7 +11,7 @@
file = os.path.join(thisDir, FILE)
args = [file]
-sys.path.insert(0, os.path.join(thisDir, 'site-lib'))
+sys.path.insert(0, os.path.join(thisDir, 'code'))
-from wypp import runner
+from wypp import runner # type: ignore
runner.main(globals(), args)
diff --git a/python/deps/untypy/.gitignore b/python/deps/untypy/.gitignore
deleted file mode 100644
index c18dd8d8..00000000
--- a/python/deps/untypy/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-__pycache__/
diff --git a/python/deps/untypy/LICENSE b/python/deps/untypy/LICENSE
deleted file mode 100644
index 45d7ebab..00000000
--- a/python/deps/untypy/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2021 CodeSteak
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/python/deps/untypy/README.rst b/python/deps/untypy/README.rst
deleted file mode 100644
index 741895cf..00000000
--- a/python/deps/untypy/README.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-untypy
-======
-
-A library for dynamic type checking in python.
-
-Current Features:
------------------
-- Simple Types like int, str, bool ...
-- Callable (e.g. lambda)
-- None
-- Any
-- Literal
-- Tuple
-- Union
-- list, dict, set
-- Optional
-- Iterator
-- Generator
-- Checking of class methods
-- Protocols
-
-
-Examples
---------
-
-run examples with
-
-.. code:: bash
-
- python -m examples.ex1
-
-.. code:: bash
-
- python -m examples.ex9
-
-Tests
------
-
-run tests with:
-
-.. code:: bash
-
- python -m unittest discover
diff --git a/python/deps/untypy/examples/ex01.py b/python/deps/untypy/examples/ex01.py
deleted file mode 100644
index ec949b9e..00000000
--- a/python/deps/untypy/examples/ex01.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from __future__ import annotations
-
-import untypy
-
-untypy.enable()
-
-from typing import Annotated
-
-IsEven = Annotated[int, lambda x: x % 2 == 0]
-
-
-def divideBy2(x: IsEven) -> int:
- return x // 2
-
-
-divideBy2(7)
-
-# IsEven = Annotated[int, lambda x: x % 2 == 0]
diff --git a/python/deps/untypy/examples/ex02.py b/python/deps/untypy/examples/ex02.py
deleted file mode 100644
index db5fd2ea..00000000
--- a/python/deps/untypy/examples/ex02.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import untypy
-
-untypy.enable()
-
-from typing import *
-
-
-class PointDisplay:
- def show(self, x: int, y: str) -> str:
- return f"({x}, {y})"
-
-
-T = TypeVar('T')
-K = TypeVar('K')
-
-
-class MyProtocol(Protocol[T, K]):
- def show(self, x: T, y: T) -> K:
- pass
-
-
-def use_my_protocol(fn: MyProtocol[int, str]) -> None:
- print(fn.show(10, 10))
-
-
-use_my_protocol(PointDisplay())
diff --git a/python/deps/untypy/examples/ex03.py b/python/deps/untypy/examples/ex03.py
deleted file mode 100644
index 291a12f4..00000000
--- a/python/deps/untypy/examples/ex03.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import untypy
-
-untypy.enable()
-
-from typing import *
-
-
-def foo(functions: list[Callable[[], str]]) -> NoReturn:
- for fn in functions:
- print(fn())
-
-
-foo([lambda: "Hello"]) # This should cause no type error
-foo([lambda: 42]) # This is a type error
diff --git a/python/deps/untypy/examples/ex04.py b/python/deps/untypy/examples/ex04.py
deleted file mode 100644
index 063eaf86..00000000
--- a/python/deps/untypy/examples/ex04.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import untypy
-
-untypy.enable()
-
-from typing import *
-import io
-
-AppendOnlyFile = Annotated[io.TextIOBase, lambda file: file.mode == 'a',
- 'A File that is opend with mode "a".']
-
-
-def log(file: AppendOnlyFile, message: str) -> NoReturn:
- file.write(message + "\n")
-
-
-file = open("mylogfile", "w")
-log(file, "Programm Started")
diff --git a/python/deps/untypy/examples/ex05.py b/python/deps/untypy/examples/ex05.py
deleted file mode 100644
index 4559efed..00000000
--- a/python/deps/untypy/examples/ex05.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import untypy
-
-untypy.enable()
-
-from typing import TypeVar, Protocol
-
-X = TypeVar('X')
-
-
-class ProtoX(Protocol):
-
- @untypy.postcondition(lambda ret: ret.startswith("a"))
- def foo(self) -> str:
- pass
-
-
-class Concrete:
- def __init__(self):
- self.x = "This is an attribute of Concrete"
-
- def foo(self) -> str:
- return "bbb"
-
-
-def meep(a: ProtoX) -> None:
- print(a.x)
- a.foo()
-
-
-meep(Concrete())
diff --git a/python/deps/untypy/examples/ex06.py b/python/deps/untypy/examples/ex06.py
deleted file mode 100644
index 7f338277..00000000
--- a/python/deps/untypy/examples/ex06.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import untypy
-
-untypy.enable()
-from typing import *
-
-EvenNumber = Annotated[int, lambda x: x % 2 == 0, "Number must be even."]
-
-
-def foo(funtions: List[Callable[[], EvenNumber]]) -> NoReturn:
- for fn in funtions:
- print(fn())
-
-
-func = lambda: 41
-foo([func]) # This is a type error
diff --git a/python/deps/untypy/examples/ex09.py b/python/deps/untypy/examples/ex09.py
deleted file mode 100644
index e5afe101..00000000
--- a/python/deps/untypy/examples/ex09.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import untypy
-
-untypy.enable()
-
-
-def show_price_for_screw(amount: int) -> None:
- base_price = 10 # cents per screw
- print(f"Screws: {amount * base_price} cents")
-
-
-show_price_for_screw("3")
diff --git a/python/deps/untypy/examples/ex10.py b/python/deps/untypy/examples/ex10.py
deleted file mode 100644
index c16affd6..00000000
--- a/python/deps/untypy/examples/ex10.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import untypy
-
-untypy.enable()
-
-from typing import Callable
-
-
-def showoperator(name: str, fn: Callable[[int, int], int]) -> None:
- print(f"5 {name} 4 = {fn(5, 4)}")
-
-
-def mul(x: int, y: float) -> int:
- return x * y
-
-
-showoperator("+", lambda x, y: x + y)
-showoperator("*", mul)
diff --git a/python/deps/untypy/runtests.sh b/python/deps/untypy/runtests.sh
deleted file mode 100755
index ac2b2bb3..00000000
--- a/python/deps/untypy/runtests.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-python3 -m unittest discover "$@"
-
diff --git a/python/deps/untypy/setup.py b/python/deps/untypy/setup.py
deleted file mode 100644
index c4bb0ea0..00000000
--- a/python/deps/untypy/setup.py
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env python
-
-from distutils.core import setup
-
-setup(name='untypy',
- version='0.0.1',
- description='Dynamic Type Checking For Python',
- author='Codesteak',
- author_email='Codesteak@shellf.art',
- url='https://github.com/CodeSteak/untypy',
- packages=[],
- )
diff --git a/python/deps/untypy/test/__init__.py b/python/deps/untypy/test/__init__.py
deleted file mode 100644
index 12cfdd65..00000000
--- a/python/deps/untypy/test/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import untypy
-
-untypy.GlobalConfig = untypy.GlobalConfig._replace(checkedprefixes=["test"])
diff --git a/python/deps/untypy/test/impl/test_any.py b/python/deps/untypy/test/impl/test_any.py
deleted file mode 100644
index 98698a80..00000000
--- a/python/deps/untypy/test/impl/test_any.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import unittest
-
-from test.util import DummyExecutionContext
-from untypy.impl.any import AnyChecker
-
-
-class TestAny(unittest.TestCase):
-
- def test_wrap(self):
- checker = AnyChecker()
-
- a = [1, 2, 3]
- res = checker.check_and_wrap(a, DummyExecutionContext())
-
- self.assertIs(a, res)
diff --git a/python/deps/untypy/test/impl/test_callable.py b/python/deps/untypy/test/impl/test_callable.py
deleted file mode 100644
index bbe772c9..00000000
--- a/python/deps/untypy/test/impl/test_callable.py
+++ /dev/null
@@ -1,78 +0,0 @@
-import unittest
-from typing import Callable
-
-from test.util import DummyExecutionContext, DummyDefaultCreationContext
-from untypy.error import UntypyTypeError
-from untypy.impl.callable import CallableFactory
-from untypy.impl.dummy_delayed import DummyDelayedType
-
-
-class TestCallable(unittest.TestCase):
- fn1: Callable
- fn2: Callable
-
- def setUp(self) -> None:
- self.checker = CallableFactory().create_from(Callable[[int, int], str], DummyDefaultCreationContext())
- self.fn1 = self.checker.check_and_wrap(lambda x, y: str(x // y), DummyExecutionContext())
- self.fn2 = self.checker.check_and_wrap(lambda x, y: x // y, DummyExecutionContext())
-
- def test_normal(self):
- self.assertEqual(self.fn1(100, 20), "5")
-
- def test_is_callable(self):
- self.assertTrue(callable(self.fn1))
-
- def test_arg_error(self):
- with self.assertRaises(UntypyTypeError) as cm:
- self.fn1(100, "20")
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Callable[[int, int], str]")
- self.assertEqual(i, " ^^^")
-
- # This file is responsable
- self.assertEqual(cm.exception.last_responsable().file, __file__)
-
- def test_ret_error(self):
- with self.assertRaises(UntypyTypeError) as cm:
- self.fn2(100, 20)
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Callable[[int, int], str]")
- self.assertEqual(i, " ^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_not_a_callable(self):
- with self.assertRaises(UntypyTypeError) as cm:
- self.checker.check_and_wrap("Hello", DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Callable[[int, int], str]")
- self.assertEqual(i, "^^^^^^^^^^^^^^^^^^^^^^^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_error_delayed(self):
- self.checker = CallableFactory().create_from(Callable[[int, int], DummyDelayedType],
- DummyDefaultCreationContext())
- fn = self.checker.check_and_wrap(lambda x, y: x // y, DummyExecutionContext())
- res = fn(1, 2)
-
- with self.assertRaises(UntypyTypeError) as cm:
- res.use()
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Callable[[int, int], DummyDelayedType]")
- self.assertEqual(i, " ^^^^^^^^^^^^^^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
diff --git a/python/deps/untypy/test/impl/test_dict.py b/python/deps/untypy/test/impl/test_dict.py
deleted file mode 100644
index 42851819..00000000
--- a/python/deps/untypy/test/impl/test_dict.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import unittest
-import collections
-
-# For running with the debugger
-#import sys
-#sys.path.insert(0, '/Users/swehr/devel/write-your-python-program/python/deps/untypy/')
-
-import untypy
-from test.util_test.untypy_test_case import dummy_caller
-import inspect
-
-class TestDict(unittest.TestCase):
- def test_equiv_with_builtin_dict(self):
- self.check(str)
- self.check(repr)
- def inPlaceAdd1(l):
- l["foo"] = 3
- (l, len(l))
- self.check(inPlaceAdd1)
- self.check(lambda l: "2" in l)
- self.check(lambda l: "42" in l)
- self.check(lambda l: l["2"])
- self.check(lambda l: l["42"])
- def update(l):
- l.update({"foo": 3})
- (l, len(l))
- self.check(update)
- def update2(l):
- l.update({"foo": 3}, chaos=100)
- (l, len(l))
- self.check(update2)
- def delete(l):
- del l["2"]
- (l, len(l))
- self.check(delete)
- def iter(d):
- acc = []
- for x in d:
- acc.append(x)
- return x
- self.check(iter)
- self.check(lambda d: d.copy())
- self.check(lambda d: d | {"42": 42, "2": 100})
- self.check(lambda d: {"42": 42, "2": 100} | d)
- self.check(lambda d: len(d))
- self.check(lambda d: d.pop("2"))
- self.check(lambda d: d.pop("42", 100))
- self.check(lambda d: d.popitem())
- self.check(lambda d: d.clear())
- self.check(lambda d: d.setdefault("1"))
- self.check(lambda d: d.setdefault("1", 50))
- self.check(lambda d: d.get("1"))
- self.check(lambda d: d.get("1", 50))
- self.check(lambda d: d.get("100"))
- self.check(lambda d: d.get("100", 50))
- self.check(lambda d: d.keys())
- self.check(lambda d: d.items())
- self.check(lambda d: d.values())
- self.check(lambda l: list(reversed(l)))
-
- def check(self, f):
- l1 = {"1": 1, "2": 2}
- l2 = l1.copy()
- try:
- refRes = f(l1)
- except Exception as e:
- refRes = e
- checker = untypy.checker(lambda ty=dict[str, int]: ty, dummy_caller)
- wrapped = checker(l2)
- self.assertFalse(l2 is wrapped)
- try:
- res = f(wrapped)
- except Exception as e:
- res = e
- if isinstance(refRes, Exception) and isinstance(res, Exception):
- self.assertEqual(str(refRes), str(res))
- elif not isinstance(refRes, Exception) and not isinstance(res, Exception):
- if isinstance(refRes, collections.abc.ValuesView):
- refRes = list(refRes)
- if isinstance(res, collections.abc.ValuesView):
- res = list(res)
- self.assertEqual(refRes, res)
- else:
- src = inspect.getsource(f)
- self.fail(f"when checking {src.strip()}: reference result: {refRes}, real result: {res}")
- self.assertEqual(l1, wrapped)
- self.assertEqual(l1, l2)
diff --git a/python/deps/untypy/test/impl/test_forwardRef.py b/python/deps/untypy/test/impl/test_forwardRef.py
deleted file mode 100644
index 3db02492..00000000
--- a/python/deps/untypy/test/impl/test_forwardRef.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import unittest
-from typing import Union, ForwardRef
-
-import untypy
-from untypy.error import UntypyTypeError
-
-Shape = Union['Square', 'Circle']
-Shape2 = Union[ForwardRef('Square'), ForwardRef('Circle')]
-
-class Square:
- pass
-
-class Circle:
- pass
-
-@untypy.typechecked
-def useShape(s: Shape) -> None:
- pass
-
-@untypy.typechecked
-def useShape2(s: Shape2) -> None:
- pass
-
-@untypy.typechecked
-def useUnion(s: Union[Square, Circle]) -> None:
- pass
-
-@untypy.typechecked
-def useSquare(s: Square) -> None:
- pass
-
-class TestForwardRef(unittest.TestCase):
-
- def test_failing(self):
- with self.assertRaises(UntypyTypeError):
- useShape(0)
- with self.assertRaises(UntypyTypeError):
- useShape2(0)
- with self.assertRaises(UntypyTypeError):
- useUnion(0)
- with self.assertRaises(UntypyTypeError):
- useSquare(Circle())
-
- def test_useSquare(self):
- useSquare(Square())
-
- def test_useShape(self):
- useShape(Square())
- useShape(Circle())
-
- def test_useShape2(self):
- useShape2(Square())
- useShape2(Circle())
-
- def test_useUnion(self):
- useUnion(Square())
- useUnion(Circle())
diff --git a/python/deps/untypy/test/impl/test_generator.py b/python/deps/untypy/test/impl/test_generator.py
deleted file mode 100644
index 11585561..00000000
--- a/python/deps/untypy/test/impl/test_generator.py
+++ /dev/null
@@ -1,167 +0,0 @@
-import unittest
-from typing import Generator
-
-from test.util import DummyDefaultCreationContext, DummyExecutionContext
-from untypy.error import UntypyTypeError
-from untypy.impl import GeneratorFactory
-from untypy.impl.dummy_delayed import DummyDelayedType
-
-
-def gen_normal() -> Generator[int, str, bool]:
- assert "a" == (yield 1)
- assert "b" == (yield 2)
- assert "c" == (yield 3)
- return True
-
-
-def gen_use_sent():
- # use dummy type to raise type exception
- (yield).use()
- return None
-
-
-def create_checker(annotation):
- return GeneratorFactory().create_from(annotation, DummyDefaultCreationContext())
-
-
-class TestGenerator(unittest.TestCase):
-
- def test_normal(self):
- checker = create_checker(Generator[int, str, bool])
-
- not_wrapped = gen_normal()
- wrapped = checker.check_and_wrap(gen_normal(), DummyExecutionContext())
-
- def test_gen_normal(generator):
- self.assertEqual(generator.send(None), 1)
- self.assertEqual(generator.send("a"), 2)
- self.assertEqual(generator.send("b"), 3)
- with self.assertRaises(StopIteration) as cm:
- generator.send("c")
-
- self.assertEqual(cm.exception.value, True)
-
- # both wrapped an unwrapped has the same behaviour
- test_gen_normal(not_wrapped)
- test_gen_normal(wrapped)
-
- def test_not_a_generator(self):
- checker = create_checker(Generator[int, str, bool])
- with self.assertRaises(UntypyTypeError) as cm:
- checker.check_and_wrap(42, DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Generator[int, str, bool]")
- self.assertEqual(i, "^^^^^^^^^^^^^^^^^^^^^^^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_yield_error(self):
- # annotation incorrect V
- checker = create_checker(Generator[str, str, bool])
- wrapped = checker.check_and_wrap(gen_normal(), DummyExecutionContext())
-
- with self.assertRaises(UntypyTypeError) as cm:
- next(wrapped)
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Generator[str, str, bool]")
- self.assertEqual(i, " ^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_send_error(self):
- # annotation incorrect V
- checker = create_checker(Generator[int, int, bool])
- wrapped = checker.check_and_wrap(gen_normal(), DummyExecutionContext())
-
- next(wrapped)
- with self.assertRaises(UntypyTypeError) as cm:
- wrapped.send("a")
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Generator[int, int, bool]")
- self.assertEqual(i, " ^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, __file__)
-
- def test_return_error(self):
- # annotation incorrect
- # Fun-Fact: bools are also ints V
- checker = create_checker(Generator[int, str, str])
-
- wrapped = checker.check_and_wrap(gen_normal(), DummyExecutionContext())
-
- wrapped.send(None)
- wrapped.send("a")
- wrapped.send("b")
- with self.assertRaises(UntypyTypeError) as cm:
- wrapped.send("c")
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Generator[int, str, str]")
- self.assertEqual(i, " ^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_yield_error_delayed(self):
- checker = create_checker(Generator[DummyDelayedType, str, bool])
- wrapped = checker.check_and_wrap(gen_normal(), DummyExecutionContext())
-
- res = next(wrapped)
- with self.assertRaises(UntypyTypeError) as cm:
- res.use()
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Generator[DummyDelayedType, str, bool]")
- self.assertEqual(i, " ^^^^^^^^^^^^^^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_send_error_delayed(self):
- checker = create_checker(Generator[None, DummyDelayedType, None])
- wrapped = checker.check_and_wrap(gen_use_sent(), DummyExecutionContext())
-
- wrapped.send(None)
- with self.assertRaises(UntypyTypeError) as cm:
- wrapped.send(42)
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Generator[None, DummyDelayedType, None]")
- self.assertEqual(i, " ^^^^^^^^^^^^^^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, __file__)
-
- def test_return_error_delayed(self):
- checker = create_checker(Generator[int, str, DummyDelayedType])
- wrapped = checker.check_and_wrap(gen_normal(), DummyExecutionContext())
-
- wrapped.send(None)
- wrapped.send("a")
- wrapped.send("b")
- with self.assertRaises(StopIteration) as si:
- wrapped.send("c")
-
- with self.assertRaises(UntypyTypeError) as cm:
- si.exception.value.use() # use dummy
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Generator[int, str, DummyDelayedType]")
- self.assertEqual(i, " ^^^^^^^^^^^^^^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
diff --git a/python/deps/untypy/test/impl/test_interface_dict.py b/python/deps/untypy/test/impl/test_interface_dict.py
deleted file mode 100644
index 540b3af4..00000000
--- a/python/deps/untypy/test/impl/test_interface_dict.py
+++ /dev/null
@@ -1,235 +0,0 @@
-from test.util_test.untypy_test_case import UntypyTestCase, dummy_caller
-from untypy.error import UntypyTypeError
-
-
-class TestInterfaceDict(UntypyTestCase):
- """
- Test's that the signatures matches the implementation.
- """
-
- def setUp(self) -> None:
- # A: No Errors
- self.good = dummy_caller(
- dict[int, str],
- {
- 1: "one",
- 2: "two",
- 3: "three",
- }
- )
-
- # B: Value error on 2
- self.valerr = dummy_caller(
- dict[int, str],
- {
- 1: "one",
- 2: 2,
- 3: "three",
- }
- )
-
- # C: Key error on 2
- self.keyerr = dummy_caller(
- dict[int, str],
- {
- 1: "one",
- "two": 2,
- 3: "three",
- }
- )
-
- def test_blame(self):
- with self.assertRaises(UntypyTypeError) as cm:
- # 42 must be str
- self.good.get(1, 42)
- self.assertBlame(cm, TestInterfaceDict.test_blame)
-
- with self.assertRaises(UntypyTypeError) as cm:
- # key must be int
- self.good.get("two")
- self.assertBlame(cm, TestInterfaceDict.test_blame)
-
- with self.assertRaises(UntypyTypeError) as cm:
- # value err
- self.valerr.get(2)
- self.assertBlame(cm, dummy_caller)
-
- def test_get(self):
- self.assertEqual(self.good.get(1), "one")
- self.assertEqual(self.good.get(4), None)
- self.assertEqual(self.good.get(4, "four"), "four")
-
- with self.assertRaises(UntypyTypeError):
- # 42 must be str
- self.good.get(1, 42)
-
- with self.assertRaises(UntypyTypeError):
- # key must be int
- self.good.get("two")
-
- with self.assertRaises(UntypyTypeError):
- # value err
- self.valerr.get(2)
-
- def test_items(self):
- self.assertEqual(
- list(self.good.items()),
- [(1, "one"), (2, "two"), (3, "three")]
- )
-
- with self.assertRaises(UntypyTypeError):
- list(self.keyerr.items())
-
- with self.assertRaises(UntypyTypeError):
- list(self.valerr.items())
-
- def test_keys(self):
- self.assertEqual(
- list(self.good.keys()),
- [1, 2, 3]
- )
-
- with self.assertRaises(UntypyTypeError):
- list(self.keyerr.keys())
-
- # values stay unchecked
- self.assertEqual(
- list(self.valerr.keys()),
- [1, 2, 3]
- )
-
- def test_pop(self):
- self.assertEqual(self.good.pop(1), "one")
- self.assertEqual(self.good.pop(4), None)
- self.assertEqual(self.good.pop(4, "four"), "four")
-
- with self.assertRaises(UntypyTypeError):
- self.good.pop("one")
-
- with self.assertRaises(UntypyTypeError):
- self.valerr.pop(2)
-
- def test_popitem(self):
- self.assertEqual(self.good.popitem(), (3, "three"))
- self.assertEqual(self.good.popitem(), (2, "two"))
- self.assertEqual(self.good.popitem(), (1, "one"))
-
- with self.assertRaises(KeyError):
- self.good.popitem()
-
- self.assertEqual(self.keyerr.popitem(), (3, "three"))
- with self.assertRaises(UntypyTypeError):
- self.keyerr.popitem()
-
- self.assertEqual(self.valerr.popitem(), (3, "three"))
- with self.assertRaises(UntypyTypeError):
- self.valerr.popitem()
-
- def test_setdefault(self):
- self.assertEqual(self.good.setdefault(1, "xxx"), "one")
-
- with self.assertRaises(UntypyTypeError):
- # Untypy does not support setdefault w/o default=XXX
- # https://github.com/skogsbaer/write-your-python-program/issues/19
- self.good.setdefault(5)
-
- self.assertEqual(self.good.setdefault(4, "four"), "four")
- self.assertEqual(self.good.get(4), "four")
-
- with self.assertRaises(UntypyTypeError):
- # 42 must be str
- self.good.setdefault(4, 42)
-
- with self.assertRaises(UntypyTypeError):
- self.valerr.setdefault(2)
-
- def test_values(self):
- self.assertEqual(
- list(self.good.values()),
- ["one", "two", "three"]
- )
-
- with self.assertRaises(UntypyTypeError):
- # This failes also, but I don't know why.
- # However the keys violate the annotation,
- # So this is fine.
- self.assertEqual(
- list(self.keyerr.values()),
- ["one", "two", "three"]
- )
-
- with self.assertRaises(UntypyTypeError):
- list(self.valerr.values())
-
- def test_contains(self):
- self.assertTrue(1 in self.good)
- self.assertFalse(4 in self.good)
- self.assertFalse("42" in self.good)
-
- def test_delitem(self):
- del self.good[1]
-
- with self.assertRaises(KeyError):
- del self.good[4]
-
- with self.assertRaises(UntypyTypeError):
- del self.good["four"]
-
- self.assertEqual(self.good, {2: "two", 3: "three"})
-
- def test_update(self):
- self.good.update({4: "four", 5: "five"})
- str_int = dummy_caller(
- dict[str, int],
- {
- "one": 1,
- }
- )
- str_int.update(four=4, five=5)
-
- with self.assertRaises(UntypyTypeError):
- self.good.update({"a": "b"})
-
- with self.assertRaises(UntypyTypeError):
- str_int.update(four="111")
-
- def test_iter(self):
- for k in self.good:
- pass
-
- for k in self.valerr:
- pass
-
- with self.assertRaises(UntypyTypeError):
- for k in self.keyerr:
- pass
-
- def test_len(self):
- self.assertEqual(len(self.good), 3)
-
- def test_reversed(self):
- self.assertEqual(list(reversed(self.good)), [3, 2, 1])
- self.assertEqual(list(reversed(self.valerr)), [3, 2, 1])
-
- with self.assertRaises(UntypyTypeError):
- list(reversed(self.keyerr))
-
- def test_getitem(self):
- self.assertEqual(self.good[1], "one")
-
- with self.assertRaises(UntypyTypeError):
- self.good["two"]
-
- with self.assertRaises(UntypyTypeError):
- self.valerr[2]
-
- def test_setiem(self):
- self.good[1] = "not one"
-
- with self.assertRaises(UntypyTypeError):
- # key err
- self.good["key"] = "one"
-
- with self.assertRaises(UntypyTypeError):
- # val err
- self.good[4] = 44
diff --git a/python/deps/untypy/test/impl/test_interface_set.py b/python/deps/untypy/test/impl/test_interface_set.py
deleted file mode 100644
index 74ecb9b5..00000000
--- a/python/deps/untypy/test/impl/test_interface_set.py
+++ /dev/null
@@ -1,90 +0,0 @@
-import unittest
-# For running with the debugger
-#import sys
-#sys.path.insert(0, '/Users/swehr/devel/write-your-python-program/python/deps/untypy/')
-
-from test.util_test.untypy_test_case import UntypyTestCase, dummy_caller
-from untypy.error import UntypyTypeError
-
-
-class TestInterfaceSet(UntypyTestCase):
- """
- Test's that the signatures matches the implementation.
- """
-
- def setUp(self) -> None:
- self.good = dummy_caller(
- set[int],
- {1, 2, 3}
- )
-
- self.bad = dummy_caller(
- set[int],
- {1, "two", 3}
- )
-
- def test_add(self):
- self.good.add(4)
-
- with self.assertRaises(UntypyTypeError):
- self.good.add("four")
-
- def test_discard(self):
- self.good.discard(3)
-
- with self.assertRaises(UntypyTypeError):
- self.good.discard("four")
-
- def test_pop(self):
- self.good.pop()
- self.good.pop()
- self.good.pop()
-
- with self.assertRaises(KeyError):
- self.good.pop()
- pass
-
- with self.assertRaises(UntypyTypeError):
- self.bad.pop()
- self.bad.pop()
- self.bad.pop()
-
- def test_remove(self):
- self.good.remove(3)
-
- with self.assertRaises(UntypyTypeError):
- self.good.remove("four")
-
- def test_update(self):
- self.good.update({5, 6, 7})
- self.good.update({8}, [9], {24})
-
- with self.assertRaises(UntypyTypeError):
- self.good.update({"four"})
-
- @unittest.skip("fails :/. Does not raise UntypyTypeError")
- #FIXME: enable once all tests are running
- def test_ior(self):
- self.good |= {4} | {7}
- self.assertEqual(self.good, {1, 2, 3, 4, 7})
-
- with self.assertRaises(UntypyTypeError):
- self.good |= {"four"}
-
- def test_contains(self):
- self.assertEqual(1 in self.good, True)
- self.assertFalse("four" in self.good)
-
- def test_iter(self):
- for k in self.good:
- pass
-
- with self.assertRaises(UntypyTypeError):
- for k in self.bad:
- pass
-def _debug():
- t = TestInterfaceSet()
- t.setUp()
- t.test_ior()
-
-# _debug()
diff --git a/python/deps/untypy/test/impl/test_list.py b/python/deps/untypy/test/impl/test_list.py
deleted file mode 100644
index ec757edc..00000000
--- a/python/deps/untypy/test/impl/test_list.py
+++ /dev/null
@@ -1,348 +0,0 @@
-import unittest
-import typing
-
-# For running with the debugger
-#import sys
-#sys.path.insert(0, '/Users/swehr/devel/write-your-python-program/python/deps/untypy/')
-
-import untypy
-from test.util import DummyExecutionContext
-from test.util_test.untypy_test_case import dummy_caller
-from untypy.error import UntypyTypeError
-from untypy.impl.dummy_delayed import DummyDelayedType
-
-
-class TestList(unittest.TestCase):
-
- def setUp(self) -> None:
- self.checker = untypy.checker(lambda ty=list[int]: ty, dummy_caller, DummyExecutionContext())
-
- self.normal_list = [0, 1, 2, 3]
- self.wrapped_list = self.checker(self.normal_list)
-
- self.faulty_normal_list = [0, 1, "2", 3]
- self.faulty_wrapped_list = self.checker(self.faulty_normal_list)
-
- def test_side_effects(self):
- self.assertEqual(self.normal_list, self.wrapped_list)
- self.normal_list.append(4)
- self.assertEqual(self.normal_list, self.wrapped_list)
- self.wrapped_list.append(4)
- self.assertEqual(self.normal_list, self.wrapped_list)
-
- def test_error_delayed(self):
- checker = untypy.checker(lambda ty=list[DummyDelayedType]: ty, dummy_caller, DummyExecutionContext())
- lst = checker([1])
-
- res = lst[0]
- with self.assertRaises(UntypyTypeError) as cm:
- res.use()
-
- (t, i) = cm.exception.next_type_and_indicator()
- self.assertEqual(t, "list[DummyDelayedType]")
-
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_not_a_list_fail(self):
- checker = untypy.checker(lambda ty=list[list[int]]: ty, dummy_caller, DummyExecutionContext())
- with self.assertRaises(UntypyTypeError) as cm:
- checker((1,2,3))
-
- def test_getitem_fail(self):
- checker = untypy.checker(lambda ty=list[list[DummyDelayedType]]: ty, dummy_caller, DummyExecutionContext())
- lst = checker([[1]])
- res1 = lst[0][0]
- with self.assertRaises(UntypyTypeError) as cm:
- res1.use()
- res2 = lst[:][:]
- self.assertEqual(res2, [[1]])
-
- def test_getitem_ok(self):
- checker = untypy.checker(lambda ty=list[list[int]]: ty, dummy_caller, DummyExecutionContext())
- lst = checker([[1]])
- res1 = lst[0][0]
- self.assertIs(int, type(res1))
- self.assertEqual(1, res1)
- res2 = lst[:][:]
- self.assertTrue(isinstance(res2, list))
- self.assertEqual([[1]], res2)
-
- def test_list_or_tuple(self):
- checker = untypy.checker(lambda ty=typing.Union[list[int], tuple[str, str]]: ty, dummy_caller, DummyExecutionContext())
- lst = checker([1,2,3])
- (x, y) = checker(("foo", "bar"))
- self.assertEqual([1,2,3], [lst[0], lst[1], lst[2]])
- self.assertEqual("foo", x)
- self.assertEqual("bar", y)
- lst_fail = checker(["foo", "bar"])
- with self.assertRaises(UntypyTypeError) as cm:
- lst_fail[0]
- with self.assertRaises(UntypyTypeError) as cm:
- checker((1, 2))
-
- def test_wrapping_resp(self):
- """
- The wrapping call is responsable for ensuring the list
- wrapped is typed correctly
- """
- with self.assertRaises(UntypyTypeError) as cm:
- var = self.faulty_wrapped_list[2]
-
- (t, i) = cm.exception.next_type_and_indicator()
-
- self.assertEqual(t, "list[int]")
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_wrapping_resp_by_side_effects(self):
- """
- The wrapping call is responsable for side effects not causing
- type errors
- """
- self.normal_list.append("4")
- with self.assertRaises(UntypyTypeError) as cm:
- var = self.wrapped_list[4]
-
- (t, i) = cm.exception.next_type_and_indicator()
-
- self.assertEqual(t, "list[int]")
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_self_resp(self):
- """
- when appending the caller of append is responsable
- """
- with self.assertRaises(UntypyTypeError) as cm:
- self.wrapped_list.append("4")
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "append(self, object: int)")
- self.assertEqual(i, " ^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, __file__)
-
- def test_not_a_list(self):
- with self.assertRaises(UntypyTypeError) as cm:
- self.checker("Hello")
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "list[int]")
- self.assertEqual(i, "^^^^^^^^^")
-
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_iterator(self):
- out = []
- for i in self.wrapped_list:
- out.append(i)
-
- self.assertEqual(out, [0, 1, 2, 3])
-
- with self.assertRaises(UntypyTypeError):
- for i in self.faulty_wrapped_list:
- out.append(i)
-
- def test_some_basic_ops(self):
- self.assertEqual(self.wrapped_list[1], 1)
- self.assertEqual(self.wrapped_list[:], [0, 1, 2, 3])
- self.assertEqual(self.wrapped_list[1:], [1, 2, 3])
-
- # Index
- self.assertEqual(self.wrapped_list.index(1), 1)
- self.assertEqual(self.wrapped_list.index(1, 0, 2), 1)
- self.assertRaises(ValueError, lambda: self.wrapped_list.index(2, start=3)) # Value Error '2' not in list
- self.assertRaises(UntypyTypeError, lambda: self.wrapped_list.index("hello")) # Wrong Argument Type
-
- self.assertEqual(self.wrapped_list, [0, 1, 2, 3])
- self.wrapped_list.append(4)
- self.assertEqual(self.wrapped_list, [0, 1, 2, 3, 4])
-
- self.wrapped_list.pop()
- self.assertEqual(self.wrapped_list, [0, 1, 2, 3])
-
- self.assertEqual(f"{self.wrapped_list}", f"{self.normal_list}")
-
- self.wrapped_list.append(4)
- self.assertEqual(self.wrapped_list + [5, 6], [0, 1, 2, 3, 4, 5, 6])
- self.assertEqual([5,6] + self.wrapped_list, [5, 6, 0, 1, 2, 3, 4])
- self.assertEqual(self.wrapped_list + self.wrapped_list, [0,1,2,3,4,0,1,2,3,4])
-
- self.wrapped_list.extend([5, 6])
- self.assertEqual(self.wrapped_list, [0, 1, 2, 3, 4, 5, 6])
-
- self.wrapped_list.insert(0, 42)
- self.assertEqual(self.wrapped_list, [42, 0, 1, 2, 3, 4, 5, 6])
-
- self.wrapped_list.remove(4)
- self.assertEqual(self.wrapped_list, [42, 0, 1, 2, 3, 5, 6])
- self.wrapped_list.remove(42)
- self.wrapped_list.remove(5)
- self.wrapped_list.remove(6)
- self.wrapped_list.remove(0)
- self.assertEqual(self.wrapped_list, [1, 2, 3])
-
- x = self.wrapped_list.pop(1)
- self.assertEqual(x, 2)
- self.assertEqual(self.wrapped_list, [1, 3])
-
- self.assertTrue(self.wrapped_list == [1,3])
- self.assertTrue(self.wrapped_list == self.wrapped_list)
- self.assertTrue([1,3] == self.wrapped_list)
-
- self.assertRaises(UntypyTypeError, lambda: self.wrapped_list.append("foo"))
- l = self.wrapped_list.copy()
- self.assertEqual(l, self.wrapped_list)
- l.append("foo") # no more wrapper
-
- def test_equiv_with_builtin_list(self):
- self.check(str)
- self.check(repr)
- self.check(lambda l: [42, 5] + l)
- self.check(lambda l: l + [42, 5])
- self.check(lambda l: 4 * l)
- self.check(lambda l: l * 3)
- def inPlaceAdd1(l):
- l += [5,6]
- self.check(inPlaceAdd1)
- def inPlaceAdd2(l):
- x = [5,6]
- x += l
- return x
- self.check(inPlaceAdd2)
- self.check(lambda l: 2 in l)
- self.check(lambda l: 42 in l)
- self.check(lambda l: l[0])
- self.check(lambda l: l[-2])
- self.check(lambda l: l[1:2])
- def extend(l):
- l.extend((5,6))
- self.check(extend)
- def sliceAssign1(l):
- l[0:2] = [5,6,7,8]
- self.check(sliceAssign1)
- def sliceAssign2(l):
- x = [0,1,2,3,4,5]
- x[1:4] = l
- return x
- self.check(sliceAssign2)
- def append(l):
- l.append(5)
- self.check(append)
- def extend1(l):
- l.extend([5,6])
- self.check(extend1)
- def extend2(l):
- x = [1,2,3,4,5,6,7]
- x.extend(l)
- return x
- self.check(extend2)
- def iter(l):
- acc = []
- for x in l:
- acc.append(l)
- return acc
- self.check(iter)
- self.check(lambda l: l.insert(1, 42))
- self.check(lambda l: l.remove(1))
- self.check(lambda l: l.pop())
- self.check(lambda l: l.pop(2))
- self.check(lambda l: l.clear())
- self.check(lambda l: l.index(4, 1, 3))
- self.check(lambda l: len(l))
- self.check(lambda l: l.count(1))
- self.check(lambda l: sorted(l))
- self.check(lambda l: l.sort())
- self.check(lambda l: l.sort(reverse=True))
- self.check(lambda l: l.sort(key=lambda i: -i))
- self.check(lambda l: list(reversed(l)))
- self.check(lambda l: l.reverse())
- self.check(lambda l: l.copy())
- self.check(lambda l: repr(l))
- self.check(lambda l: str(l))
- # ==
- self.check(lambda l: l == [1,4,2,1])
- self.check(lambda l: [1,4,2,1] == l)
- self.check2(lambda l1, l2: l1 == l2)
- self.check2(lambda l1, l2: l2 == l1)
- # !=
- self.check(lambda l: l != [1,4,2,1])
- self.check(lambda l: [1,4,2,1] != l)
- self.check2(lambda l1, l2: l1 != l2)
- self.check2(lambda l1, l2: l2 != l1)
- # <
- self.check(lambda l: l < [1,4,2])
- self.check(lambda l: [1,4,1] < l)
- self.check2(lambda l1, l2: l1 < l2)
- self.check2(lambda l1, l2: l2 < l1)
- # <=
- self.check(lambda l: l <= [1,4,2])
- self.check(lambda l: [1,4,1] <= l)
- self.check2(lambda l1, l2: l1 <= l2)
- self.check2(lambda l1, l2: l2 <= l1)
- # >
- self.check(lambda l: l > [1,4,2])
- self.check(lambda l: [1,4,1] > l)
- self.check2(lambda l1, l2: l1 > l2)
- self.check2(lambda l1, l2: l2 > l1)
- # >=
- self.check(lambda l: l >= [1,4,2])
- self.check(lambda l: [1,4,1] >= l)
- self.check2(lambda l1, l2: l1 >= l2)
- self.check2(lambda l1, l2: l2 >= l1)
-
- def check(self, f):
- l1 = [1, 4, 2, 1]
- l2 = l1.copy()
- refRes = f(l1)
- checker = untypy.checker(lambda ty=list[int]: ty, dummy_caller)
- wrapped = checker(l2)
- self.assertFalse(l1 is wrapped)
- res = f(wrapped)
- self.assertEqual(l1, wrapped)
- self.assertEqual(l1, l2)
- self.assertEqual(refRes, res)
-
- def check2(self, f):
- self.check21(f)
- self.check22(f)
-
- def check21(self, f):
- l1 = [1, 4, 2, 1]
- l11 = l1.copy()
- l12 = l1.copy()
- l1w = l1.copy()
- checker = untypy.checker(lambda ty=list[int]: ty, dummy_caller)
- wrapped1 = checker(l1w)
- self.assertFalse(l1 is wrapped1)
- refRes11 = f(l11, l12)
- res11 = f(wrapped1, wrapped1)
- self.assertEqual(l1, wrapped1)
- self.assertEqual(refRes11, res11)
-
- def check22(self, f):
- l1 = [1, 4, 2, 1]
- l2 = [1, 4, 1]
- l1w = l1.copy()
- l2w = l2.copy()
- refRes12 = f(l1, l2)
- checker = untypy.checker(lambda ty=list[int]: ty, dummy_caller)
- wrapped1 = checker(l1w)
- wrapped2 = checker(l2w)
- self.assertFalse(l1 is wrapped1)
- self.assertFalse(l2 is wrapped2)
- res12 = f(wrapped1, wrapped2)
- self.assertEqual(l1, wrapped1)
- self.assertEqual(l1, l1w)
- self.assertEqual(l2, wrapped2)
- self.assertEqual(l2, l2w)
- self.assertEqual(refRes12, res12)
-
-# def _debug():
-# t = TestList()
-# t.setUp()
-# t.test_some_basic_ops()
-#
-# _debug()
diff --git a/python/deps/untypy/test/impl/test_literal.py b/python/deps/untypy/test/impl/test_literal.py
deleted file mode 100644
index ded683e9..00000000
--- a/python/deps/untypy/test/impl/test_literal.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import unittest
-from typing import Literal
-
-from test.util import DummyExecutionContext, DummyDefaultCreationContext
-from untypy.error import UntypyTypeError
-from untypy.impl.literal import LiteralFactory
-
-
-class TestLiteral(unittest.TestCase):
-
- def setUp(self) -> None:
- self.checker = LiteralFactory().create_from(Literal[1, 2, "3"], DummyDefaultCreationContext())
-
- def test_positive_checking(self):
- self.assertEqual(self.checker.check_and_wrap(1, DummyExecutionContext()), 1)
- self.assertEqual(self.checker.check_and_wrap("3", DummyExecutionContext()), "3")
-
- def test_neg_checking(self):
- with self.assertRaises(UntypyTypeError) as cm:
- self.checker.check_and_wrap(4, DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Literal[1, 2, '3']")
- self.assertEqual(i, "^^^^^^^^^^^^^^^^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
diff --git a/python/deps/untypy/test/impl/test_none.py b/python/deps/untypy/test/impl/test_none.py
deleted file mode 100644
index 198bbbc5..00000000
--- a/python/deps/untypy/test/impl/test_none.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import unittest
-
-from test.util import DummyExecutionContext, DummyDefaultCreationContext
-from untypy.error import UntypyTypeError
-from untypy.impl.none import NoneFactory
-
-
-class TestNone(unittest.TestCase):
-
- def test_wrap(self):
- checker = NoneFactory().create_from(None, DummyDefaultCreationContext())
-
- res = checker.check_and_wrap(None, DummyExecutionContext())
- self.assertEqual(None, res)
-
- def test_wrap_of_none_type(self):
- checker = NoneFactory().create_from(type(None), DummyDefaultCreationContext())
-
- res = checker.check_and_wrap(None, DummyExecutionContext())
- self.assertEqual(None, res)
-
- def test_wrap_negative(self):
- checker = NoneFactory().create_from(None, DummyDefaultCreationContext())
-
- with self.assertRaises(UntypyTypeError) as cm:
- res = checker.check_and_wrap(12, DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "None")
- self.assertEqual(i, "^^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
diff --git a/python/deps/untypy/test/impl/test_optional.py b/python/deps/untypy/test/impl/test_optional.py
deleted file mode 100644
index f64b0e71..00000000
--- a/python/deps/untypy/test/impl/test_optional.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import unittest
-from typing import Optional
-
-from test.util import DummyExecutionContext, DummyDefaultCreationContext
-from untypy.error import UntypyTypeError
-from untypy.impl.dummy_delayed import DummyDelayedType
-from untypy.impl.optional import OptionalFactory
-
-
-class TestOptional(unittest.TestCase):
-
- def test_wrap(self):
- checker = OptionalFactory().create_from(Optional[int], DummyDefaultCreationContext())
- res = checker.check_and_wrap(1, DummyExecutionContext())
- self.assertEqual(1, res)
-
- res = checker.check_and_wrap(None, DummyExecutionContext())
- self.assertEqual(None, res)
-
- def test_wrap_negative(self):
- checker = OptionalFactory().create_from(Optional[int], DummyDefaultCreationContext())
- with self.assertRaises(UntypyTypeError) as cm:
- res = checker.check_and_wrap(23.5, DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Optional[int]")
- self.assertEqual(i, " ^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_wrap_negative_delayed(self):
- checker = OptionalFactory().create_from(Optional[DummyDelayedType], DummyDefaultCreationContext())
-
- res = checker.check_and_wrap(1, DummyExecutionContext())
-
- with self.assertRaises(UntypyTypeError) as cm:
- res.use()
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Optional[DummyDelayedType]")
- self.assertEqual(i, " ^^^^^^^^^^^^^^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
diff --git a/python/deps/untypy/test/impl/test_protocol.py b/python/deps/untypy/test/impl/test_protocol.py
deleted file mode 100644
index 2afb8a79..00000000
--- a/python/deps/untypy/test/impl/test_protocol.py
+++ /dev/null
@@ -1,280 +0,0 @@
-import unittest
-from typing import Protocol, Union, TypeVar, Generic, NoReturn
-
-import untypy
-from test.util import DummyDefaultCreationContext, DummyExecutionContext, location_of
-from untypy.error import UntypyTypeError
-from untypy.impl import ProtocolFactory, GenericFactory
-from untypy.impl.union import UnionFactory
-
-
-class A:
- pass
-
-
-class ParrentB:
- pass
-
-
-class B(ParrentB):
- pass
-
-
-class ProtoReturnB(Protocol):
- @untypy.patch
- def meth(self) -> B:
- raise NotImplementedError
-
-
-class ProtoReceiveB(Protocol):
- @untypy.patch
- def meth(self, b: B) -> None:
- raise NotImplementedError
-
-class UntypedProtocol(Protocol):
- @untypy.patch
- def meth(self, a, b):
- raise NotImplementedError
-
-class TestProtocolTestCommon(unittest.TestCase):
-
- def setUp(self) -> None:
- self.sig_b = "B"
- self.ProtoReturnB = ProtoReturnB
- self.ProtoReceiveB = ProtoReceiveB
- self.ProtoReturnBName = "ProtoReturnB"
- self.ProtoReceiveBName = "ProtoReceiveB"
- self.checker_return = ProtocolFactory().create_from(ProtoReturnB, DummyDefaultCreationContext())
- self.checker_arg = ProtocolFactory().create_from(ProtoReceiveB, DummyDefaultCreationContext())
-
- def test_not_implementing_methods(self):
- class NoMeth:
- def foo(self) -> None:
- pass
-
- with self.assertRaises(UntypyTypeError) as cm:
- self.checker_return.check_and_wrap(NoMeth(), DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, self.ProtoReturnBName)
- self.assertEqual(i, "^" * len(self.ProtoReturnBName))
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_receiving_wrong_arguments(self):
- class Concrete:
- @untypy.patch
- def meth(self, b: ParrentB) -> None:
- pass
-
- instance = self.checker_arg.check_and_wrap(Concrete(), DummyExecutionContext())
- with self.assertRaises(UntypyTypeError) as cm:
- instance.meth(A())
-
- (t, i) = cm.exception.next_type_and_indicator()
-
- self.assertEqual(t, f"meth(self: Self, b: {self.sig_b}) -> None")
- self.assertEqual(cm.exception.last_responsable().file, __file__)
-
- self.assertEqual(cm.exception.last_declared(), location_of(self.ProtoReceiveB.meth))
-
- def test_return_wrong_arguments(self):
- class Concrete:
- @untypy.patch
- def meth(self) -> B:
- return A()
-
- instance = self.checker_return.check_and_wrap(Concrete(), DummyExecutionContext())
- with self.assertRaises(UntypyTypeError) as cm:
- instance.meth()
-
- (t, i) = cm.exception.next_type_and_indicator()
-
- self.assertEqual(t, f"meth(self: Self) -> B")
- self.assertEqual(cm.exception.last_responsable().file, __file__)
- self.assertEqual(cm.exception.last_declared(), location_of(Concrete.meth))
-
- def test_concrete_wrong_argument_signature(self):
- class Concrete:
- @untypy.patch
- def meth(self, b: A) -> NoReturn:
- pass
-
- instance = self.checker_arg.check_and_wrap(Concrete(), DummyExecutionContext())
- with self.assertRaises(UntypyTypeError) as cm:
- instance.meth(B())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "meth(self: Self, b: A) -> None")
- self.assertEqual(i, " ^")
- self.assertEqual(cm.exception.last_responsable(), location_of(Concrete.meth))
- self.assertEqual(cm.exception.last_declared(), location_of(self.ProtoReceiveB.meth))
-
- def test_concrete_wrong_return_signature(self):
- class Concrete:
- @untypy.patch
- def meth(self) -> A:
- return A()
-
- instance = self.checker_return.check_and_wrap(Concrete(), DummyExecutionContext())
- with self.assertRaises(UntypyTypeError) as cm:
- instance.meth()
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, f"meth(self: Self) -> {self.sig_b}")
- self.assertEqual(cm.exception.last_responsable(), location_of(Concrete.meth))
- self.assertEqual(cm.exception.last_declared(), location_of(self.ProtoReturnB.meth))
-
- def test_double_wrapping(self):
- # there should only one layer of wrapping.
-
- class EmptyProto(Protocol):
- pass
-
- class MProto(Protocol):
- def m(self) -> int:
- pass
-
- class ConcreteMProto:
- @untypy.patch
- def m(self) -> int:
- return 42
-
- @untypy.patch
- def a(p: EmptyProto) -> None:
- b(p)
-
- @untypy.patch
- def b(p: MProto) -> None:
- p.m()
-
- # must not fail
- a(ConcreteMProto())
-
-
-class TestProtocolGenerics(TestProtocolTestCommon):
- def setUp(self) -> None:
- T = TypeVar("T")
-
- class ProtoReturnGeneric(Protocol[T]):
- @untypy.patch
- def meth(self) -> T:
- raise NotImplementedError
-
- class ProtoReceiveGeneric(Protocol[T]):
- @untypy.patch
- def meth(self, b: T) -> None:
- raise NotImplementedError
-
- self.sig_b = "B"
- self.ProtoReturnB = ProtoReturnGeneric
- self.ProtoReceiveB = ProtoReceiveGeneric
- self.ProtoReturnBName = "ProtoReturnGeneric[B]"
- self.ProtoReceiveBName = "ProtoReceiveGeneric(Protocol)"
- self.checker_return = ProtocolFactory().create_from(ProtoReturnGeneric[B], DummyDefaultCreationContext())
- self.checker_arg = ProtocolFactory().create_from(ProtoReceiveGeneric[B], DummyDefaultCreationContext())
-
-
-class TestProtocolGenericTypeRepr(unittest.TestCase):
-
- def test_protocol_type_repr(self):
- T = TypeVar("T")
-
- # There was a bug, causing T to be listet multiple Times.
- @untypy.patch
- class Proto(Generic[T]):
- @untypy.patch
- def do_it(self, a: T, b: T) -> None:
- return
-
- fac = GenericFactory().create_from(Proto[int], DummyDefaultCreationContext())
- with self.assertRaises(UntypyTypeError) as cm:
- fac.check_and_wrap(42, DummyExecutionContext())
- self.assertEqual(cm.exception.expected, "Proto[int]")
-
-
-class TestProtocolSpecific(unittest.TestCase):
-
- def test_union_protocols(self):
- class U1:
- @untypy.patch
- def meth(self) -> str:
- return "s"
-
- class U2:
- @untypy.patch
- def meth(self) -> int:
- return 42
-
- @untypy.patch
- def meth2(self) -> int:
- return 42
-
- # when wrapping order matters
- UnionFactory() \
- .create_from(Union[U1, U2], DummyDefaultCreationContext()) \
- .check_and_wrap(U1(), DummyExecutionContext()) \
- .meth()
- UnionFactory() \
- .create_from(Union[U1, U2], DummyDefaultCreationContext()) \
- .check_and_wrap(U2(), DummyExecutionContext()) \
- .meth()
- UnionFactory() \
- .create_from(Union[U2, U1], DummyDefaultCreationContext()) \
- .check_and_wrap(U1(), DummyExecutionContext()) \
- .meth()
- UnionFactory() \
- .create_from(Union[U2, U1], DummyDefaultCreationContext()) \
- .check_and_wrap(U2(), DummyExecutionContext()) \
- .meth()
-
- def test_untyped_no_match(self):
- checker = ProtocolFactory().create_from(UntypedProtocol, DummyDefaultCreationContext())
- class U0:
- @untypy.patch
- def notmeth(self, a, b):
- return 42
-
- with self.assertRaises(UntypyTypeError) as cm:
- checker.check_and_wrap(U0(), DummyExecutionContext())
- self.assertEqual(cm.exception.expected, "UntypedProtocol")
-
- def test_untyped_match(self):
- checker = ProtocolFactory().create_from(UntypedProtocol, DummyDefaultCreationContext())
- class U1:
- @untypy.patch
- def meth(self, a, b): # matches
- return 42
-
- self.assertEqual(checker.check_and_wrap(U1(), DummyExecutionContext()).meth("a", "b"), 42)
-
- def test_untyped_nomatch_signature(self):
- checker = ProtocolFactory().create_from(UntypedProtocol, DummyDefaultCreationContext())
- class U2:
- @untypy.patch
- def meth(self, a): # does not match
- pass
-
- with self.assertRaises(UntypyTypeError) as cm:
- checker.check_and_wrap(U2(), DummyExecutionContext()).meth("a", 42)
- self.assertEqual(cm.exception.expected, "UntypedProtocol")
-
- def test_untyped_narrow_signature(self):
- checker = ProtocolFactory().create_from(UntypedProtocol, DummyDefaultCreationContext())
- class U3:
- @untypy.patch
- def meth(self, a : int, b : int) -> int: # matches, but to specific
- return a + b
-
- wrapped = checker.check_and_wrap(U3(), DummyExecutionContext())
-
- self.assertEqual(wrapped.meth(10, 20), 30)
- with self.assertRaises(UntypyTypeError) as cm:
- wrapped.meth("a", 20)
-
- self.assertEqual(cm.exception.previous_chain.expected, "UntypedProtocol")
diff --git a/python/deps/untypy/test/impl/test_set.py b/python/deps/untypy/test/impl/test_set.py
deleted file mode 100644
index ae2562d9..00000000
--- a/python/deps/untypy/test/impl/test_set.py
+++ /dev/null
@@ -1,171 +0,0 @@
-import unittest
-import collections
-
-# For running with the debugger
-#import sys
-#sys.path.insert(0, '/Users/swehr/devel/write-your-python-program/python/deps/untypy/')
-
-import untypy
-from test.util_test.untypy_test_case import dummy_caller
-
-
-class TestSet(unittest.TestCase):
- def test_update(self):
- checker = untypy.checker(lambda ty=set[str]: ty, dummy_caller)
- s1 = set(["foo", "bar"])
- s2 = set(["bar", "spam"])
- s3 = s1.copy()
- s4 = s2.copy()
- s3w = checker(s3)
- s4w = checker(s4)
- refRes = s1.update(s2)
- res = s3w.update(s4w)
- self.assertEqual(refRes, res)
- self.assertEqual(s1, s3)
- self.assertEqual(s1, s3w)
- self.assertEqual(s2, s4)
- self.assertEqual(s2, s4w)
-
- def test_equiv_with_builtin_set(self):
- self.check(str)
- self.check(repr)
- self.check(lambda s: s.add(3))
- self.check(lambda s: s.add(2))
- self.check(lambda s: s.clear())
- self.check(lambda s: s.copy())
- self.check(lambda s: s.discard(1))
- self.check(lambda s: s.discard(3))
- self.check(lambda s: s.pop())
- self.check(lambda s: s.remove(1))
- self.check(lambda s: s.remove(3))
- self.check(lambda s: 1 in s)
- self.check(lambda s: 3 in s)
- self.check(lambda s: len(s))
- self.checkM(lambda s, others: s.difference(*others))
- self.checkM(lambda s, others: s.difference_update(*others))
- self.checkM(lambda s, others: s.symmetric_difference(*others))
- self.checkM(lambda s, others: s.symmetric_difference_update(*others))
- self.checkM(lambda s, others: s.union(*others))
- self.checkM(lambda s, others: s.update(*others))
- self.checkSym(lambda s1, s2: s1.issubset(s2))
- self.checkSym(lambda s1, s2: s1.issuperset(s2))
- self.checkSym(lambda s1, s2: s1.isdisjoint(s2))
- self.checkSym(lambda s1, s2: s1 < s2)
- self.checkSym(lambda s1, s2: s1 <= s2)
- self.checkSym(lambda s1, s2: s1 > s2)
- self.checkSym(lambda s1, s2: s1 >= s2)
- self.checkSym(lambda s1, s2: s1 == s2)
- self.checkSym(lambda s1, s2: s1 != s2)
- self.checkSym(lambda s1, s2: s1 & s2)
- def addAssign(s1, s2): s1 &= s2
- self.checkSym(addAssign)
- self.checkSym(lambda s1, s2: s1 | s2)
- def orAssign(s1, s2): s1 |= s2
- self.checkSym(orAssign)
- self.checkSym(lambda s1, s2: s1 ^ s2)
- def xorAssign(s1, s2): s1 ^= s2
- self.checkSym(xorAssign)
- self.checkSym(lambda s1, s2: s1 - s2)
- self.checkSym(lambda s1, s2: s1 - s2)
- def subAssign(s1, s2): s1 -= s2
- self.checkSym(subAssign)
- def iter(s):
- acc = []
- for x in s:
- acc.append(x)
- return acc
- self.check(iter)
-
- def _check(self, act1, act2, eqs):
- try:
- refRes = act1()
- except Exception as e:
- refRes = e
- try:
- res = act2()
- except Exception as e:
- res = e
- if isinstance(refRes, Exception) and isinstance(res, Exception):
- self.assertEqual(str(refRes), str(res))
- elif not isinstance(refRes, Exception) and not isinstance(res, Exception):
- self.assertEqual(refRes, res)
- else:
- self.fail(f"resRef={refRes}, res={res}")
- for (x, y) in eqs:
- self.assertEqual(x, y)
-
- def check(self, f):
- l1 = set([1,2])
- l2 = l1.copy()
- checker = untypy.checker(lambda ty=set[int]: ty, dummy_caller)
- wrapped = checker(l2)
- self.assertFalse(l2 is wrapped)
- self._check(lambda: f(l1), lambda: f(wrapped), [(l1, wrapped), (l1, l2)])
-
- def checkSym(self, f):
- s1 = None
- s2 = None
- s3 = None
- s4 = None
- s3w = None
- s4w = None
- eqs = None
- def setup():
- nonlocal s1,s2,s3,s4,s3w,s4w,eqs
- s1 = set(["foo", "bar"])
- s2 = set(["bar", "spam"])
- checker = untypy.checker(lambda ty=set[str]: ty, dummy_caller)
- s3 = s1.copy()
- s4 = s2.copy()
- s3w = checker(s3)
- s4w = checker(s4)
- eqs = [(s1, s3), (s2, s4), (s1, s3w), (s2, s4w)]
- setup()
- self._check(lambda: f(s1, s2), lambda: f(s3w, s4w), eqs)
- setup()
- self._check(lambda: f(s2, s1), lambda: f(s4w, s3w), eqs)
- setup()
- self._check(lambda: f(s1, s2), lambda: f(s3, s4w), eqs)
- setup()
- self._check(lambda: f(s2, s1), lambda: f(s4w, s3), eqs)
- setup()
- self._check(lambda: f(s1, s2), lambda: f(s3w, s4), eqs)
- setup()
- self._check(lambda: f(s2, s1), lambda: f(s4, s3w), eqs)
-
- def checkM(self, f):
- s1 = None
- s2 = None
- s3 = None
- s4 = None
- s5 = None
- s6 = None
- s4w = None
- s5w = None
- s6w = None
- eqs = None
- def setup():
- nonlocal s1,s2,s3,s4,s5,s6,s4w,s5w,s6w,eqs
- s1 = set(["foo", "bar"])
- s2 = set(["bar", "spam"])
- s3 = set(["foo", "egg"])
- checker = untypy.checker(lambda ty=set[str]: ty, dummy_caller)
- s4 = s1.copy()
- s5 = s2.copy()
- s6 = s3.copy()
- s4w = checker(s4)
- s5w = checker(s5)
- s6w = checker(s6)
- eqs = [(s1, s4), (s1, s4w), (s2, s5), (s2, s5w), (s3, s6), (s3, s6w)]
- setup()
- self._check(lambda: f(s1, [s2]), lambda: f(s4w, [s5w]), eqs)
- setup()
- self._check(lambda: f(s1, [s2]), lambda: f(s4w, [s5]), eqs)
- setup()
- self._check(lambda: f(s1, [s2]), lambda: f(s4, [s5w]), eqs)
- setup()
- self._check(lambda: f(s1, [s2,s3]), lambda: f(s4w, [s5w, s6w]), eqs)
- setup()
- self._check(lambda: f(s1, [s2,s3]), lambda: f(s4w, [s5, s6w]), eqs)
- setup()
- self._check(lambda: f(s1, [s2,s3]), lambda: f(s4, [s5w, s6]), eqs)
diff --git a/python/deps/untypy/test/impl/test_simple.py b/python/deps/untypy/test/impl/test_simple.py
deleted file mode 100644
index 6e70e37d..00000000
--- a/python/deps/untypy/test/impl/test_simple.py
+++ /dev/null
@@ -1,160 +0,0 @@
-import unittest
-from typing import Union, Callable
-
-import untypy
-from test.util import DummyExecutionContext, DummyDefaultCreationContext
-from untypy.error import UntypyTypeError, UntypyAttributeError
-from untypy.impl.simple import SimpleFactory
-from untypy.impl.union import UnionFactory
-
-
-@untypy.patch
-class A:
- pass
-
-
-@untypy.patch
-class ChildOfA(A):
- pass
-
-
-@untypy.patch
-class B:
- pass
-
-@untypy.patch
-class SomeParent:
- @untypy.patch
- def meth(self) -> str:
- return "Hello"
-
-
-@untypy.patch
-class ChildOfSomeParent(SomeParent):
- @untypy.patch
- def meth(self) -> int: # Signature does not match.
- return 42
-
-
-class TestSimple(unittest.TestCase):
-
- def test_wrap(self):
- checker = SimpleFactory().create_from(A, DummyDefaultCreationContext())
-
- a = A()
- child_a = ChildOfA()
-
- res = checker.check_and_wrap(a, DummyExecutionContext())
- self.assertIs(a, res)
- res = checker.check_and_wrap(child_a, DummyExecutionContext())
- self.assertIsNot(child_a, res) # Wrapped with AWrapper
-
- def test_attributes(self):
- a = ChildOfA()
- a.foo = 42
-
- # Note: Attributes are not checked, but they need to be accessible
-
- @untypy.patch
- def m(x: A) -> None:
- self.assertEqual(x.foo, 42)
- x.foo = 43
- self.assertEqual(x.foo, 43)
-
- m(a)
- self.assertEqual(a.foo, 43)
-
- def test_wrap_negative(self):
- checker = SimpleFactory().create_from(A, DummyDefaultCreationContext())
-
- with self.assertRaises(UntypyTypeError) as cm:
- res = checker.check_and_wrap(B(), DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "A")
- self.assertEqual(i, "^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_wrap_inheritance(self):
- checker = SimpleFactory().create_from(SomeParent, DummyDefaultCreationContext())
-
- res = checker.check_and_wrap(ChildOfSomeParent(), DummyExecutionContext())
- with self.assertRaises(UntypyTypeError):
- res.meth()
-
- def test_unions_simple_types_negative(self):
- class U1:
- def meth(self) -> None:
- pass
-
- class U2:
- def meth(self) -> None:
- pass
-
- with self.assertRaises(UntypyAttributeError):
- # Should fail because both have the same methods
- # Wrapping cannot distinguish
- UnionFactory().create_from(Union[U1, U2], DummyDefaultCreationContext())
-
- def test_unions_simple_types_negative(self):
- class U3:
- def meth(self) -> None:
- pass
-
- class U4:
- def meth(self) -> None:
- pass
-
- def somethingelse(self) -> None:
- pass
-
- # this should also be fine. A and B don't have any signatures,
- # so protocol like wrapping does not apply
- UnionFactory().create_from(Union[A, B], DummyDefaultCreationContext())
-
- # this must be fine: Names of Signatures differ.
- UnionFactory().create_from(Union[U3, U4], DummyDefaultCreationContext())
-
- def test_no_inheritance_checking_of_builtins(self):
- class SubInt(int):
- pass
-
- @untypy.patch
- def take_number(number: int) -> None:
- # SubInt should not be wrapped.
- self.assertEqual(type(number), SubInt)
-
- take_number(SubInt("42"))
-
- def test_int_as_float(self):
- @untypy.patch
- def f(x: float) -> float:
- return x + 1
- self.assertEqual(f(1), 2)
- self.assertEqual(f(1.1), 2.1)
- with self.assertRaises(UntypyTypeError):
- f("x")
-
- def test_float_not_as_int(self):
- @untypy.patch
- def f(x: int) -> int:
- return x + 1
- self.assertEqual(f(1), 2)
- with self.assertRaises(UntypyTypeError):
- f("x")
- with self.assertRaises(UntypyTypeError):
- f(3.14)
-
- def test_callable(self):
- @untypy.patch
- def f(g: Callable) -> int:
- return g(1)
- self.assertEqual(f(lambda x: x + 1), 2)
- with self.assertRaises(TypeError):
- f(lambda x: "x" + x)
- with self.assertRaises(UntypyTypeError):
- f(lambda x: "x" + str(x))
diff --git a/python/deps/untypy/test/impl/test_str.py b/python/deps/untypy/test/impl/test_str.py
deleted file mode 100644
index a3b28d79..00000000
--- a/python/deps/untypy/test/impl/test_str.py
+++ /dev/null
@@ -1,96 +0,0 @@
-import unittest
-from typing import Iterable
-
-from test.util_test.untypy_test_case import dummy_caller
-import untypy
-
-
-class TestStr(unittest.TestCase):
-
- def test_equiv_with_builtin_tuple(self):
- self.check(str)
- self.check(repr)
- self.check(lambda l: "foo" + l)
- self.check(lambda l: l + "foo")
- self.check(lambda l: 4 * l)
- self.check(lambda l: l * 3)
- self.check(lambda l: "sp" in l)
- self.check(lambda l: "xxx" in l)
- self.check(lambda l: l[0])
- self.check(lambda l: l[-2])
- self.check(lambda l: l[1:2])
- def iter(l):
- acc = []
- for x in l:
- acc.append(l)
- return acc
- self.check(iter)
- self.check(lambda l: len(l))
- self.check(lambda l: l.count("s"))
- self.check(lambda l: sorted(l))
- self.check(lambda l: list(reversed(l)))
- # ==
- self.check(lambda l: l == "spam")
- self.check(lambda l: "spam" == l)
- self.check2(lambda l1, l2: l1 == l2)
- self.check2(lambda l1, l2: l2 == l1)
- # !=
- self.check(lambda l: l != "spam")
- self.check(lambda l: "spam" != l)
- self.check2(lambda l1, l2: l1 != l2)
- self.check2(lambda l1, l2: l2 != l1)
- # <
- self.check(lambda l: l < "egg")
- self.check(lambda l: "egg" < l)
- self.check2(lambda l1, l2: l1 < l2)
- self.check2(lambda l1, l2: l2 < l1)
- # <=
- self.check(lambda l: l <= "egg")
- self.check(lambda l: "egg" <= l)
- self.check2(lambda l1, l2: l1 <= l2)
- self.check2(lambda l1, l2: l2 <= l1)
- # >
- self.check(lambda l: l > "egg")
- self.check(lambda l: "egg" > l)
- self.check2(lambda l1, l2: l1 > l2)
- self.check2(lambda l1, l2: l2 > l1)
- # >=
- self.check(lambda l: l >= "egg")
- self.check(lambda l: "egg" >= l)
- self.check2(lambda l1, l2: l1 >= l2)
- self.check2(lambda l1, l2: l2 >= l1)
-
- def check(self, f):
- l1 = "spam"
- refRes = f(l1)
- checker = untypy.checker(lambda ty=Iterable[str]: ty, dummy_caller)
- wrapped = checker(l1)
- self.assertFalse(l1 is wrapped)
- res = f(wrapped)
- self.assertEqual(l1, wrapped)
- self.assertEqual(refRes, res)
-
- def check2(self, f):
- self.check21(f)
- self.check22(f)
-
- def check21(self, f):
- l1 = "spam"
- refRes11 = f(l1, l1)
- checker = untypy.checker(lambda ty=Iterable[str]: ty, dummy_caller)
- wrapped1 = checker(l1)
- self.assertFalse(l1 is wrapped1)
- res11 = f(wrapped1, wrapped1)
- self.assertEqual(refRes11, res11)
-
- def check22(self, f):
- l1 = "spam"
- l2 = "spa"
- refRes12 = f(l1, l2)
- checker = untypy.checker(lambda ty=Iterable[str]: ty, dummy_caller)
- wrapped1 = checker(l1)
- wrapped2 = checker(l2)
- self.assertFalse(l1 is wrapped1)
- self.assertFalse(l2 is wrapped2)
- res12 = f(wrapped1, wrapped2)
- self.assertEqual(refRes12, res12)
diff --git a/python/deps/untypy/test/impl/test_tuple.py b/python/deps/untypy/test/impl/test_tuple.py
deleted file mode 100644
index a7edb3f9..00000000
--- a/python/deps/untypy/test/impl/test_tuple.py
+++ /dev/null
@@ -1,156 +0,0 @@
-import unittest
-from typing import Tuple
-
-from test.util import DummyExecutionContext, DummyDefaultCreationContext
-from test.util_test.untypy_test_case import dummy_caller
-import untypy
-from untypy.error import UntypyTypeError
-from untypy.impl.dummy_delayed import DummyDelayedType
-from untypy.impl.tuple import TupleFactory
-
-
-class TestTuple(unittest.TestCase):
-
- def test_wrap_lower_case(self):
- checker = TupleFactory().create_from(tuple[int, str], DummyDefaultCreationContext())
- res = checker.check_and_wrap((1, "2"), DummyExecutionContext())
- self.assertEqual((1, "2"), res)
-
- def test_wrap_upper_case(self):
- checker = TupleFactory().create_from(Tuple[int, str], DummyDefaultCreationContext())
- res = checker.check_and_wrap((1, "2"), DummyExecutionContext())
- self.assertEqual((1, "2"), res)
-
- def test_not_a_tuple(self):
- checker = TupleFactory().create_from(tuple[int, str], DummyDefaultCreationContext())
-
- with self.assertRaises(UntypyTypeError) as cm:
- res = checker.check_and_wrap(1, DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "tuple[int, str]")
- self.assertEqual(i, "^^^^^^^^^^^^^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_negative(self):
- checker = TupleFactory().create_from(tuple[int, str], DummyDefaultCreationContext())
-
- with self.assertRaises(UntypyTypeError) as cm:
- res = checker.check_and_wrap((1, 2), DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "tuple[int, str]")
- self.assertEqual(i, " ^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_negative_delayed(self):
- checker = TupleFactory().create_from(tuple[int, DummyDelayedType], DummyDefaultCreationContext())
-
- res = checker.check_and_wrap((1, 2), DummyExecutionContext())
- with self.assertRaises(UntypyTypeError) as cm:
- res[1].use()
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "tuple[int, DummyDelayedType]")
- self.assertEqual(i, " ^^^^^^^^^^^^^^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_equiv_with_builtin_tuple(self):
- self.check(str)
- self.check(repr)
- self.check(lambda l: (42, 5) + l)
- self.check(lambda l: l + (42, 5))
- self.check(lambda l: 4 * l)
- self.check(lambda l: l * 3)
- self.check(lambda l: 2 in l)
- self.check(lambda l: 42 in l)
- self.check(lambda l: l[0])
- self.check(lambda l: l[-2])
- self.check(lambda l: l[1:2])
- def iter(l):
- acc = []
- for x in l:
- acc.append(l)
- return acc
- self.check(iter)
- self.check(lambda l: len(l))
- self.check(lambda l: l.count(1))
- self.check(lambda l: sorted(l))
- self.check(lambda l: list(reversed(l)))
- # ==
- self.check(lambda l: l == (1,4,2,1))
- self.check(lambda l: (1,4,2,1) == l)
- self.check2(lambda l1, l2: l1 == l2)
- self.check2(lambda l1, l2: l2 == l1)
- # !=
- self.check(lambda l: l != (1,4,2,1))
- self.check(lambda l: (1,4,2,1) != l)
- self.check2(lambda l1, l2: l1 != l2)
- self.check2(lambda l1, l2: l2 != l1)
- # <
- self.check(lambda l: l < (1,4,2))
- self.check(lambda l: (1,4,1) < l)
- self.check2(lambda l1, l2: l1 < l2)
- self.check2(lambda l1, l2: l2 < l1)
- # <=
- self.check(lambda l: l <= (1,4,2))
- self.check(lambda l: (1,4,1) <= l)
- self.check2(lambda l1, l2: l1 <= l2)
- self.check2(lambda l1, l2: l2 <= l1)
- # >
- self.check(lambda l: l > (1,4,2))
- self.check(lambda l: (1,4,1) > l)
- self.check2(lambda l1, l2: l1 > l2)
- self.check2(lambda l1, l2: l2 > l1)
- # >=
- self.check(lambda l: l >= (1,4,2))
- self.check(lambda l: (1,4,1) >= l)
- self.check2(lambda l1, l2: l1 >= l2)
- self.check2(lambda l1, l2: l2 >= l1)
-
- def check(self, f):
- l1 = (1, 4, 2, 1)
- refRes = f(l1)
- checker = untypy.checker(lambda ty=tuple[int,...]: ty, dummy_caller)
- wrapped = checker(l1)
- self.assertFalse(l1 is wrapped)
- res = f(wrapped)
- self.assertEqual(l1, wrapped)
- self.assertEqual(refRes, res)
-
- def check2(self, f):
- self.check21(f)
- self.check22(f)
-
- def check21(self, f):
- l1 = (1, 4, 2, 1)
- refRes11 = f(l1, l1)
- checker = untypy.checker(lambda ty=tuple[int,...]: ty, dummy_caller)
- wrapped1 = checker(l1)
- self.assertFalse(l1 is wrapped1)
- res11 = f(wrapped1, wrapped1)
- self.assertEqual(refRes11, res11)
-
- def check22(self, f):
- l1 = (1, 4, 2, 1)
- l2 = (1, 4, 1)
- refRes12 = f(l1, l2)
- checker = untypy.checker(lambda ty=tuple[int, ...]: ty, dummy_caller)
- wrapped1 = checker(l1)
- wrapped2 = checker(l2)
- self.assertFalse(l1 is wrapped1)
- self.assertFalse(l2 is wrapped2)
- res12 = f(wrapped1, wrapped2)
- self.assertEqual(refRes12, res12)
diff --git a/python/deps/untypy/test/impl/test_union.py b/python/deps/untypy/test/impl/test_union.py
deleted file mode 100644
index 18e68e70..00000000
--- a/python/deps/untypy/test/impl/test_union.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import unittest
-from typing import Union, Callable
-
-from test.util import DummyExecutionContext, DummyDefaultCreationContext
-from untypy.error import UntypyTypeError, UntypyAttributeError
-from untypy.impl.dummy_delayed import DummyDelayedType
-from untypy.impl.union import UnionFactory
-import untypy
-
-class TestUnion(unittest.TestCase):
-
- def test_wrap(self):
- checker = UnionFactory().create_from(Union[int, str], DummyDefaultCreationContext())
- res = checker.check_and_wrap(1, DummyExecutionContext())
- self.assertEqual(1, res)
-
- res = checker.check_and_wrap("2", DummyExecutionContext())
- self.assertEqual("2", res)
-
- def test_wrap_negative(self):
- checker = UnionFactory().create_from(Union[int, str], DummyDefaultCreationContext())
- with self.assertRaises(UntypyTypeError) as cm:
- res = checker.check_and_wrap(23.5, DummyExecutionContext())
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Union[int, str]")
- self.assertEqual(i, "^^^^^^^^^^^^^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_wrap_negative_delayed(self):
- checker = UnionFactory().create_from(Union[DummyDelayedType, str], DummyDefaultCreationContext())
-
- res = checker.check_and_wrap(1, DummyExecutionContext())
-
- with self.assertRaises(UntypyTypeError) as cm:
- res.use()
-
- (t, i) = cm.exception.next_type_and_indicator()
- i = i.rstrip()
-
- self.assertEqual(t, "Union[DummyDelayedType, str]")
- self.assertEqual(i, " ^^^^^^^^^^^^^^^^")
-
- # This DummyExecutionContext is responsable
- self.assertEqual(cm.exception.last_responsable().file, "dummy")
-
- def test_not_allowing_multiple_callables(self):
- with self.assertRaises(UntypyAttributeError):
- checker = UnionFactory().create_from(Union[int, str, Callable[[int], str], Callable[[str], str]],
- DummyDefaultCreationContext())
-
- with self.assertRaises(UntypyAttributeError):
- checker = UnionFactory().create_from(Union[int, Callable[[int], str],
- Union[Callable[[str], str], list[int]]],
- DummyDefaultCreationContext())
-
- def test_union(self):
- @untypy.patch
- def f(x: Union[str, float]) -> Union[str, float]:
- return x
- self.assertEqual(f(1), 1)
- self.assertEqual(f(3.14), 3.14)
- self.assertEqual(f("x"), "x")
- with self.assertRaises(UntypyTypeError):
- f(None)
- with self.assertRaises(UntypyTypeError):
- f([1])
diff --git a/python/deps/untypy/test/patching_dummy/a/__init__.py b/python/deps/untypy/test/patching_dummy/a/__init__.py
deleted file mode 100644
index 369e1ccc..00000000
--- a/python/deps/untypy/test/patching_dummy/a/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from .a_sub import fn_two
-
-
-def fn_one(x: int) -> None:
- pass
diff --git a/python/deps/untypy/test/patching_dummy/a/a_sub.py b/python/deps/untypy/test/patching_dummy/a/a_sub.py
deleted file mode 100644
index 2745d71a..00000000
--- a/python/deps/untypy/test/patching_dummy/a/a_sub.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def fn_two(x: int) -> None:
- pass
diff --git a/python/deps/untypy/test/patching_dummy/argument_types.py b/python/deps/untypy/test/patching_dummy/argument_types.py
deleted file mode 100644
index da2f8106..00000000
--- a/python/deps/untypy/test/patching_dummy/argument_types.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from typing import Tuple
-
-
-def kwargs(a: int, b: str, c: bool) -> Tuple[bool, int, str]:
- return c, a, b
-
-
-def default_args(a: int, b: str = "hello") -> str:
- return b
diff --git a/python/deps/untypy/test/patching_dummy/async_gen.py b/python/deps/untypy/test/patching_dummy/async_gen.py
deleted file mode 100644
index 752ce8f0..00000000
--- a/python/deps/untypy/test/patching_dummy/async_gen.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import asyncio
-
-
-async def some_coroutine_generator(x: int) -> str:
- await asyncio.sleep(0.0000001)
- yield "1"
- yield "2"
- yield "3"
-
-
-async def some_coroutine(x: int) -> str:
- await asyncio.sleep(0.0000001)
- return str(x)
-
-
-def some_generator() -> int
diff --git a/python/deps/untypy/test/patching_dummy/b/__init__.py b/python/deps/untypy/test/patching_dummy/b/__init__.py
deleted file mode 100644
index 26da4915..00000000
--- a/python/deps/untypy/test/patching_dummy/b/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from .b_sub import fn_two
-
-
-def fn_one(x: int) -> None:
- pass
diff --git a/python/deps/untypy/test/patching_dummy/b/b_sub.py b/python/deps/untypy/test/patching_dummy/b/b_sub.py
deleted file mode 100644
index 2745d71a..00000000
--- a/python/deps/untypy/test/patching_dummy/b/b_sub.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def fn_two(x: int) -> None:
- pass
diff --git a/python/deps/untypy/test/patching_dummy/c.py b/python/deps/untypy/test/patching_dummy/c.py
deleted file mode 100644
index 80f56399..00000000
--- a/python/deps/untypy/test/patching_dummy/c.py
+++ /dev/null
@@ -1,5 +0,0 @@
-def fn_one(x: int) -> None:
- pass
-
-
-untypy.enable()
diff --git a/python/deps/untypy/test/patching_dummy/patching_classes.py b/python/deps/untypy/test/patching_dummy/patching_classes.py
deleted file mode 100644
index a088ba5f..00000000
--- a/python/deps/untypy/test/patching_dummy/patching_classes.py
+++ /dev/null
@@ -1,6 +0,0 @@
-class A:
- def __init__(self, y: int):
- self.y = y
-
- def add(self, x: int) -> int:
- return x + self.y
diff --git a/python/deps/untypy/test/patching_dummy/patching_does_not_change_signature.py b/python/deps/untypy/test/patching_dummy/patching_does_not_change_signature.py
deleted file mode 100644
index 5ba12547..00000000
--- a/python/deps/untypy/test/patching_dummy/patching_does_not_change_signature.py
+++ /dev/null
@@ -1,7 +0,0 @@
-def fun(x: int) -> str:
- return str(x)
-
-
-class SomeClass:
- def meth(self, x: int) -> str:
- return str(x)
diff --git a/python/deps/untypy/test/patching_dummy/unpatched.py b/python/deps/untypy/test/patching_dummy/unpatched.py
deleted file mode 100644
index 582d5317..00000000
--- a/python/deps/untypy/test/patching_dummy/unpatched.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def fn_one(x: int) -> None:
- pass
diff --git a/python/deps/untypy/test/test_patching.py b/python/deps/untypy/test/test_patching.py
deleted file mode 100644
index b1136e2d..00000000
--- a/python/deps/untypy/test/test_patching.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from __future__ import annotations
-
-import unittest
-
-import untypy
-
-
-@untypy.patch
-class C:
- @untypy.patch
- def foo(self: C, d: D) -> C:
- return C()
-
-
-@untypy.patch
-class D:
- pass
-
-
-class TestRecursion(unittest.TestCase):
-
- def test_recursion(self):
- # should not fail
- C().foo(D())
diff --git a/python/deps/untypy/test/test_standalone_checker.py b/python/deps/untypy/test/test_standalone_checker.py
deleted file mode 100644
index 66190e8c..00000000
--- a/python/deps/untypy/test/test_standalone_checker.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import unittest
-import untypy
-from untypy.error import UntypyTypeError, Location
-
-
-class TestStandaloneChecker(unittest.TestCase):
-
- def test_standalone(self):
- ch = untypy.checker(lambda: int, TestStandaloneChecker.test_standalone)
-
- self.assertEqual(ch(42), 42)
-
- def myfunc(x):
- ch(x)
-
- with self.assertRaises(UntypyTypeError) as cm:
- myfunc("hello")
-
- self.assertEqual(cm.exception.expected, 'int')
- self.assertEqual(cm.exception.last_declared(), Location.from_code(TestStandaloneChecker.test_standalone))
- self.assertIn('myfunc("hello")', cm.exception.last_responsable().source_lines)
\ No newline at end of file
diff --git a/python/deps/untypy/test/util.py b/python/deps/untypy/test/util.py
deleted file mode 100644
index 6e20f8f2..00000000
--- a/python/deps/untypy/test/util.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from typing import TypeVar, Any
-
-from untypy.error import UntypyTypeError, Frame, Location
-from untypy.impl import DefaultCreationContext
-from untypy.interfaces import ExecutionContext, WrappedFunction
-
-
-class DummyExecutionContext(ExecutionContext):
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- (t, i) = err.next_type_and_indicator()
-
- return err.with_frame(Frame(
- t,
- i,
- declared=None,
- responsable=Location(
- file="dummy",
- line_no=0,
- line_span=1
- )
- ))
-
-
-class DummyDefaultCreationContext(DefaultCreationContext):
-
- def __init__(self, typevars: dict[TypeVar, Any] = dict()):
- super().__init__(typevars.copy(), Location(
- file="dummy",
- line_no=0,
- line_span=1
- ), checkedpkgprefixes=["test"])
-
-
-def location_of(fn):
- return WrappedFunction.find_location(fn)
diff --git a/python/deps/untypy/test/util_test/test_return_traces.py b/python/deps/untypy/test/util_test/test_return_traces.py
deleted file mode 100644
index 5c9e3337..00000000
--- a/python/deps/untypy/test/util_test/test_return_traces.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import ast
-import unittest
-
-from untypy.util.return_traces import ReturnTraceManager, ReturnTracesTransformer
-
-
-class TestAstTransform(unittest.TestCase):
-
- def test_ast_transform(self):
- src = """
-def foo(flag: bool) -> int:
- print('Hello World')
- if flag:
- return 1
- else:
- return 'you stupid'
- """
- target = """
-def foo(flag: bool) -> int:
- print('Hello World')
- if flag:
- untypy._before_return(0)
- return 1
- else:
- untypy._before_return(1)
- return 'you stupid'
- """
-
- tree = ast.parse(src)
- mgr = ReturnTraceManager()
- ReturnTracesTransformer("", mgr).visit(tree)
- ast.fix_missing_locations(tree)
- self.assertEqual(ast.unparse(tree).strip(), target.strip())
- self.assertEqual(mgr.get(0), ("", 5))
- self.assertEqual(mgr.get(1), ("", 7))
-
diff --git a/python/deps/untypy/test/util_test/test_wrapper.py b/python/deps/untypy/test/util_test/test_wrapper.py
deleted file mode 100644
index 9d3d921d..00000000
--- a/python/deps/untypy/test/util_test/test_wrapper.py
+++ /dev/null
@@ -1,170 +0,0 @@
-from untypy.util.wrapper import wrap
-import unittest
-import collections
-
-class C:
- def __init__(self, content):
- self.content = content
- def __eq__(self, other):
- if not isinstance(other, C):
- return False
- else:
- return self.content == other.content
- def __repr__(self):
- return f'C({self.content})'
- def __str__(self):
- return self.__repr__()
- def foo(self):
- return 1
-
-class WrapperTests(unittest.TestCase):
- def test_wrapObject(self):
- c1 = C(42)
- c2 = wrap(C(42), {'foo': lambda self: 0})
- self.assertEqual(str(c2), 'C(42)')
- self.assertTrue(c1 == c2)
- self.assertTrue(c2 == c1)
- self.assertTrue(isinstance(c2, C))
- self.assertEqual(c2.foo(), 0)
- self.assertEqual(c1.foo(), 1)
-
- def test_wrapList(self):
- l = wrap([1,2,3], {'__str__': lambda self: 'XXX'})
- self.assertEqual(repr(l), '[1, 2, 3]')
- self.assertEqual(str(l), 'XXX')
- self.assertTrue(isinstance(l, list))
- acc = []
- for x in l:
- acc.append(x+1)
- self.assertEqual(acc, [2,3,4])
- self.assertEqual([0] + l, [0,1,2,3])
- self.assertEqual(l + [4], [1,2,3,4])
- self.assertTrue(l == [1,2,3])
- self.assertTrue([1,2,3] == l)
- self.assertEqual(3, len(l))
- l.append(4)
- self.assertEqual(4, len(l))
- self.assertEqual([1,2,3,4], l)
- f = l.append
- flag = False
- def myAppend(x):
- nonlocal flag
- flag = True
- f(x)
- setattr(l, 'append', myAppend)
- l.append(5)
- self.assertEqual(5, len(l))
- self.assertEqual([1,2,3,4,5], l)
- self.assertTrue(flag)
-
- def test_wrapList2(self):
- # This is what happens in the protocol checker: the function is called on the original
- # value
- orig = [1,2,3]
- l = wrap(orig, {'append': lambda self, x: orig.append(x)})
- self.assertTrue([1,2,3] == l)
- self.assertEqual(3, len(l))
- l.append(4)
- self.assertEqual(4, len(l))
- self.assertEqual([1,2,3,4], l)
-
- def _test_api_complete(self, obj, ignore=[]):
- wrapped = wrap(obj, {})
- expectedModule = 'untypy.util.wrapper'
- blacklist = ['__class__', '__delattr__', '__class_getitem__', '__dict__', '__dir__',
- '__doc__', '__extra__', '__format__', '__getattribute__', '__init__',
- '__init_subclass__', '__module__', '__setattr__', '__subclasshook__',
- '__weakref__', '__wrapped__', '_DictWrapper__marker', '__setstate__',
- '__getstate__', '__firstlineno__', '__static_attributes__'
- ] + ignore
- for x in dir(obj):
- if x in blacklist:
- continue
- a = getattr(wrapped, x)
- if not hasattr(a, '__module__'):
- self.fail(f'Attribute {x} not defined. obj={obj}, a={a}, wrapped={wrapped}')
- elif a.__module__ != expectedModule:
- self.fail(f'Attrribute {x} not defined in {expectedModule}')
-
- def test_list_api_complete(self):
- self._test_api_complete([])
-
- def test_set_api_complete(self):
- self._test_api_complete(set())
-
- def test_dict_api_complete(self):
- self._test_api_complete({}, ignore=['fromkeys'])
-
- def test_wrapTuple(self):
- l = wrap((1,2,3), {'__str__': lambda self: 'XXX'})
- self.assertEqual(repr(l), '(1, 2, 3)')
- self.assertEqual(str(l), 'XXX')
- self.assertTrue(isinstance(l, tuple))
- acc = []
- for x in l:
- acc.append(x+1)
- self.assertEqual(acc, [2,3,4])
- self.assertEqual((0,) + l, (0,1,2,3))
- self.assertEqual(l + (4,), (1,2,3,4))
- self.assertTrue(l == (1,2,3))
- self.assertTrue((1,2,3) == l)
-
- def test_wrapString(self):
- l = wrap("123", {'__str__': lambda self: 'XXX'})
- self.assertEqual(repr(l), "'123'")
- self.assertEqual(str(l), 'XXX')
- self.assertTrue(isinstance(l, str))
- acc = []
- for x in l:
- acc.append(x)
- self.assertEqual(acc, ['1', '2', '3'])
- self.assertEqual('0' + l, '0123')
- self.assertEqual(l + '4', '1234')
- self.assertTrue(l == "123")
- self.assertTrue("123" == l)
-
- def test_wrapDict(self):
- d = wrap({'1': 1, '2': 2, '3': 3}, {'__str__': lambda self: 'YYY', 'foo': lambda self: 11})
- self.assertEqual(repr(d), "{'1': 1, '2': 2, '3': 3}")
- self.assertEqual(str(d), 'YYY')
- self.assertTrue(isinstance(d, dict))
- acc = []
- for x in d:
- acc.append(x)
- self.assertEqual(acc, ['1', '2', '3'])
- self.assertEqual(len(d), 3)
- self.assertTrue(d == {'1': 1, '2': 2, '3': 3})
- self.assertTrue({'1': 1, '2': 2, '3': 3} == d)
- self.assertEqual(d.foo(), 11)
- self.assertEqual(d.copy(), {'1': 1, '2': 2, '3': 3})
-
- def test_wrapViews(self):
- d = {'1': 1, '2': 2}
- # keys
- self.assertTrue(isinstance(d.keys(), collections.abc.KeysView))
- kv = wrap(d.keys(), {})
- self.assertTrue(isinstance(kv, collections.abc.KeysView))
- acc = []
- for x in kv:
- acc.append(x)
- self.assertEqual(['1','2'], acc)
- # items
- iv = wrap(d.items(), {})
- self.assertTrue(isinstance(iv, collections.abc.ItemsView))
- acc = []
- for x in iv:
- acc.append(x)
- self.assertEqual([('1', 1),('2', 2)], acc)
- # values
- vv = wrap(d.values(), {})
- self.assertTrue(isinstance(vv, collections.abc.ValuesView))
- acc = []
- for x in vv:
- acc.append(x)
- self.assertEqual([1,2], acc)
-
- def test_name(self):
- l = wrap([1,2,3], {}, "NameOfWrapper")
- self.assertEqual(str(type(l)), "")
- c = wrap(C(1), {}, "blub")
- self.assertEqual(str(type(c)), "")
diff --git a/python/deps/untypy/test/util_test/untypy_test_case.py b/python/deps/untypy/test/util_test/untypy_test_case.py
deleted file mode 100644
index 52228f37..00000000
--- a/python/deps/untypy/test/util_test/untypy_test_case.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import unittest
-
-import untypy
-from untypy.error import Location
-
-
-def dummy_caller_2(ch, arg):
- return ch(arg)
-
-
-def dummy_caller(typ, arg):
- """
- Sets up callstack so that this function is blamed for creation.
- untypy.checker uses the function that called the function that
- invokes the checker.
- """
- ch = untypy.checker(lambda ty=typ: ty, dummy_caller)
- return dummy_caller_2(ch, arg)
-
-
-class UntypyTestCase(unittest.TestCase):
-
- def assertBlame(self, cm, blamed):
- ok = cm.exception.last_responsable() in Location.from_code(blamed)
- if not ok:
- print(cm.exception.last_responsable())
- print("not in")
- print(Location.from_code(blamed))
- self.assertTrue(ok)
-
diff --git a/python/deps/untypy/untypy/__init__.py b/python/deps/untypy/untypy/__init__.py
deleted file mode 100644
index 5f451216..00000000
--- a/python/deps/untypy/untypy/__init__.py
+++ /dev/null
@@ -1,187 +0,0 @@
-import ast
-import inspect
-import sys
-from types import ModuleType
-from typing import Optional, Any, Union, Callable
-
-from .patching import wrap_function, patch_class, wrap_class, DefaultConfig
-from .patching.ast_transformer import UntypyAstTransformer, did_no_code_run_before_untypy_enable, \
- UntypyAstImportTransformer
-from .patching.import_hook import install_import_hook
-from .patching.standalone_checker import StandaloneChecker
-from .util import condition
-from .util.return_traces import ReturnTracesTransformer, before_return, GlobalReturnTraceManager
-from .util.tranformer_combinator import TransformerCombinator
-from .util import debug
-
-GlobalConfig = DefaultConfig
-
-"""
-This function is called before any return statement, to store which was the last return.
-For this the AST is transformed using ReturnTracesTransformer.
-Must be in untypy so it can be used in transformed module.
-Must also be in other module, so it can be used from inside (No circular imports).
-"""
-_before_return = before_return
-
-_importhook_transformer_builder = lambda path, file: TransformerCombinator(UntypyAstTransformer(),
- ReturnTracesTransformer(file))
-
-def just_install_hook(prefixes=[]):
- global GlobalConfig
-
- def predicate(module_name):
- for p in prefixes:
- if module_name == p:
- return True
- elif module_name.startswith(p + "."):
- return True
- return False
-
- GlobalConfig = DefaultConfig._replace(checkedprefixes=[*prefixes])
- install_import_hook(predicate, _importhook_transformer_builder)
-
-
-def transform_tree(tree, file):
- UntypyAstTransformer().visit(tree)
- ReturnTracesTransformer(file).visit(tree)
- ast.fix_missing_locations(tree)
-
-
-def enable(*, recursive: bool = True, root: Union[ModuleType, str, None] = None, prefixes: list[str] = []) -> None:
- global GlobalConfig
- caller = _find_calling_module()
- exit_after = False
-
- if root is None:
- root = caller
- exit_after = True
- if caller is None:
- raise Exception("Couldn't find loading Module. This is a Bug.")
-
- rootname = root
- if hasattr(rootname, '__name__'):
- rootname = root.__name__
-
- GlobalConfig = DefaultConfig._replace(checkedprefixes=[rootname])
-
- def predicate(module_name):
- if recursive:
- # Patch if Submodule
- if module_name.startswith(rootname + "."):
- return True
- else:
- for p in prefixes:
- if module_name == p:
- return True
- elif module_name.startswith(p + "."):
- return True
- return False
- else:
- raise AssertionError("You cannot run 'untypy.enable()' twice!")
-
- transformer = _importhook_transformer_builder
- install_import_hook(predicate, transformer)
- _exec_module_patched(root, exit_after, transformer(caller.__name__.split("."), caller.__file__))
-
-
-def enable_on_imports(*prefixes):
- print("!!!! THIS FEATURE HAS UNEXPECTED SIDE EFFECTS WHEN IMPORTING ABSOLUTE SUBMODULES !!!")
- # TODO: Fix import of submodules should not change parent module.
- global GlobalConfig
- GlobalConfig = DefaultConfig._replace(checkedprefixes=[*prefixes])
- caller = _find_calling_module()
-
- def predicate(module_name: str):
- module_name = module_name.replace('__main__.', '')
- for p in prefixes:
- if module_name == p:
- return True
- elif module_name.startswith(p + "."):
- return True
- else:
- return False
-
- transformer = _importhook_transformer_builder
- install_import_hook(predicate, transformer)
- _exec_module_patched(caller, True, transformer(caller.__name__.split("."), caller.__file__))
-
-
-def _exec_module_patched(mod: ModuleType, exit_after: bool, transformer: ast.NodeTransformer):
- source = inspect.getsource(mod)
- tree = compile(source, mod.__file__, 'exec', ast.PyCF_ONLY_AST,
- dont_inherit=True, optimize=-1)
-
- if not did_no_code_run_before_untypy_enable(tree):
- raise AssertionError("Please put 'untypy.enable()' at the start of your module like so:\n"
- "\timport untypy\n"
- "\tuntypy.enable()")
-
- transformer.visit(tree)
- ReturnTracesTransformer(mod.__file__).visit(tree)
- ast.fix_missing_locations(tree)
- patched_mod = compile(tree, mod.__file__, 'exec', dont_inherit=True, optimize=-1)
- stack = list(map(lambda s: s.frame, inspect.stack()))
- try:
- exec(patched_mod, mod.__dict__)
- except Exception as e:
- while e.__traceback__.tb_frame in stack:
- e.__traceback__ = e.__traceback__.tb_next
- sys.excepthook(type(e), e, e.__traceback__)
- if exit_after: sys.exit(-1)
- if exit_after: sys.exit(0)
-
-
-def _find_calling_module() -> Optional[ModuleType]:
- for caller in inspect.stack():
- if caller.filename != __file__:
- mod = inspect.getmodule(caller.frame)
- if mod is not None:
- return mod
- return None
-
-precondition = condition.precondition
-postcondition = condition.postcondition
-
-def unchecked(fn):
- setattr(fn, "__unchecked", True)
- return fn
-
-
-def patch(a: Any) -> Any:
- global GlobalConfig
- if hasattr(a, '__unchecked'):
- return a
-
- if inspect.isfunction(a):
- return wrap_function(a, GlobalConfig)
- elif inspect.isclass(a):
- patch_class(a, GlobalConfig)
- return a
- else:
- return a
-
-
-typechecked = patch
-
-
-def wrap_import(a: Any) -> Any:
- global GlobalConfig
- if inspect.isfunction(a):
- return wrap_function(a, GlobalConfig)
- elif inspect.isclass(a) or inspect.ismodule(a):
- return wrap_class(a, GlobalConfig)
- else:
- return a
-
-
-def checker(annotation: Callable[[], Any], location: Any, ctx=None) -> Callable[[Any], Any]:
- """
- :param annotation: A functions that returns the annotation lazily. (Needed for FowardRefs)
- :param location: Where was it declared?
- :return: A type checker function
- """
- return StandaloneChecker(annotation, location, DefaultConfig, ctx)
-
-def enableDebug(d: bool):
- debug.enableDebug(d)
diff --git a/python/deps/untypy/untypy/error.py b/python/deps/untypy/untypy/error.py
deleted file mode 100644
index 0ffeda72..00000000
--- a/python/deps/untypy/untypy/error.py
+++ /dev/null
@@ -1,384 +0,0 @@
-from __future__ import annotations
-
-import inspect
-from enum import Enum
-import os
-from typing import Any, Optional, Tuple, Iterable
-
-
-def readFile(path):
- try:
- with open(path, encoding='utf-8') as f:
- return f.read()
- except UnicodeDecodeError:
- with open(path) as f:
- return f.read()
-
-def relpath(p):
- # relpath might throw an exception, at least on windows
- try:
- return os.path.relpath(p)
- except:
- return p
-
-class Location:
- file: str
- line_no: int
- line_span : int
- source_lines: Optional[str]
-
- def __init__(self, file: str, line_no: int, line_span : int):
- self.file = file
- self.line_no = line_no
- self.line_span = line_span
- self.source_lines = None
-
- def source(self) -> Optional[str]:
- if self.source_lines is None:
- try:
- self.source_lines = readFile(self.file)
- except OSError:
- self.source_lines = ''
- return self.source_lines
-
- def source_lines_span(self) -> Optional[str]:
- # This is still used for unit testing
-
- source = self.source()
- if source is None:
- return None
-
- buf = ""
-
- for i, line in enumerate(source.splitlines()):
- if (i + 1) in range(self.line_no, self.line_no + self.line_span):
- buf += f"\n{line}"
-
- return buf
-
- def __str__(self):
- return f"{relpath(self.file)}:{self.line_no}"
-
- def formatWithCode(self):
- buf = str(self)
- source = self.source()
- if not source:
- return buf
- lines = source.splitlines()
- idx = self.line_no - 1
- if idx < 0 or idx > len(lines):
- return buf
- else:
- return buf + '\n | ' + lines[idx]
-
- def __repr__(self):
- return f"Location(file={self.file.__repr__()}, line_no={self.line_no.__repr__()}, line_span={self.line_span})"
-
- def __eq__(self, other):
- if not isinstance(other, Location):
- return False
- return self.file == other.file and self.line_no == other.line_no
-
- @staticmethod
- def from_code(obj) -> Location:
- try:
- return Location(
- file=inspect.getfile(obj),
- line_no=inspect.getsourcelines(obj)[1],
- line_span=len(inspect.getsourcelines(obj)[0]),
- )
- except Exception:
- return Location(
- file=inspect.getfile(obj),
- line_no=1,
- line_span=1,
- )
-
- @staticmethod
- def from_stack(stack) -> Location:
- if isinstance(stack, inspect.FrameInfo):
- try:
- return Location(
- file=stack.filename,
- line_no=stack.lineno,
- line_span=1
- )
- except Exception:
- return Location(
- file=stack.filename,
- line_no=stack.lineno,
- line_span=1
- )
- else: # assume sys._getframe(...)
- try:
- return Location(
- file=stack.f_code.co_filename,
- line_no=stack.f_lineno,
- line_span=1
- )
- except Exception:
- return Location(
- file=stack.f_code.co_filename,
- line_no=stack.f_lineno,
- line_span=1
- )
-
- def __contains__(self, other: Location):
- file, line = (other.file, other.line_no)
- return self.file == file and line in range(self.line_no, self.line_no + self.line_span)
-
- def narrow_in_span(self, reti_loc: Tuple[str, int]):
- """
- Use new Location if inside of span of this Location
- :param reti_loc: filename and line_no
- :return: a new Location, else self
- """
- file, line = reti_loc
- if self.file == file and line in range(self.line_no, self.line_no + self.line_span):
- return Location(
- file=file,
- line_no=line,
- line_span=1
- )
- else:
- return self
-
-
-class Frame:
- type_declared: str
- indicator_line: str
-
- declared: Optional[Location]
- responsable: Optional[Location]
-
- responsibility_type: Optional[ResponsibilityType]
-
- def __init__(self, type_declared: str, indicator_line: Optional[str],
- declared: Optional[Location], responsable: Optional[Location]):
-
- self.type_declared = type_declared
- if indicator_line is None:
- indicator_line = '^' * len(type_declared)
- self.indicator_line = indicator_line
- self.declared = declared
- self.responsable = responsable
-
- def __repr__(self):
- return f'Frame({self.type_declared}, {self.declared}, {self.responsable}, {self.responsibility_type})'
-
- def __str__(self):
- buf = f"in: {self.type_declared}\n" \
- f" {self.indicator_line}\n"
-
- if self.responsable is not None:
- buf += f"{self.responsable.file}:{self.responsable.line_no}:\n" \
- f"{self.responsable.source_lines}\n" \
- f"\n"
- return buf
-
-
-class ResponsibilityType(Enum):
- IN = 0
- OUT = 1
-
- def invert(self):
- if self is ResponsibilityType.IN:
- return ResponsibilityType.OUT
- else:
- return ResponsibilityType.IN
-
-def joinLines(l: Iterable[str]) -> str:
- return '\n'.join([x.rstrip() for x in l])
-
-# Note: the visual studio code plugin uses the prefixes "caused by: " and "declared at: "
-# for finding source locations. Do not change without changing the plugin code!!
-CAUSED_BY_PREFIX = "caused by: "
-DECLARED_AT_PREFIX = "declared at: "
-
-def formatLocations(prefix: str, locs: list[Location]) -> str:
- return joinLines(map(lambda s: prefix + str(s), locs))
-
-# DeliberateError instances are not reported as bugs
-class DeliberateError:
- pass
-
-class WyppTypeError(TypeError, DeliberateError):
- pass
-
-class WyppAttributeError(AttributeError, DeliberateError):
- pass
-
-# All error types must be subclasses from UntypyError.
-class UntypyError:
- def simpleName(self):
- raise Exception('abstract method')
-
-NO_GIVEN = object()
-
-class UntypyTypeError(TypeError, UntypyError):
- given: Any # NO_GIVEN if not present
- header: str
- expected: Optional[str]
- frames: list[Frame]
- notes: list[str]
- previous_chain: Optional[UntypyTypeError]
- responsibility_type: ResponsibilityType
-
- def __init__(self,
- given: Any = NO_GIVEN,
- expected: str = None,
- frames: list[Frame] = [],
- notes: list[str] = [],
- previous_chain: Optional[UntypyTypeError] = None,
- responsibility_type: ResponsibilityType = ResponsibilityType.IN,
- header: str = ''):
- self.responsibility_type = responsibility_type
- self.given = given
- self.expected = expected
- self.frames = frames.copy()
- for frame in self.frames:
- if frame.responsibility_type is None:
- frame.responsibility_type = responsibility_type
- self.notes = notes.copy()
- self.previous_chain = previous_chain
- self.header = header
- super().__init__('\n' + self.__str__())
-
- def simpleName(self):
- return 'TypeError'
-
- def next_type_and_indicator(self) -> Tuple[str, str]:
- if len(self.frames) >= 1:
- frame = self.frames[-1]
- return frame.type_declared, frame.indicator_line
- else:
- n = 0
- if self.expected:
- n = len(self.expected)
- return self.expected, "^" * n
-
- def with_frame(self, frame: Frame) -> UntypyTypeError:
- frame.responsibility_type = self.responsibility_type
- return UntypyTypeError(self.given, self.expected, self.frames + [frame],
- self.notes, self.previous_chain, self.responsibility_type,
- self.header)
-
- def with_previous_chain(self, previous_chain: UntypyTypeError):
- return UntypyTypeError(self.given, self.expected, self.frames,
- self.notes, previous_chain, self.responsibility_type, self.header)
-
- def with_note(self, note: str):
- return UntypyTypeError(self.given, self.expected, self.frames,
- self.notes + [note], self.previous_chain, self.responsibility_type,
- self.header)
-
- def with_inverted_responsibility_type(self):
- return UntypyTypeError(self.given, self.expected, self.frames,
- self.notes, self.previous_chain, self.responsibility_type.invert(),
- self.header)
-
- def with_header(self, header: str):
- return UntypyTypeError(self.given, self.expected, self.frames,
- self.notes, self.previous_chain, self.responsibility_type, header)
-
- def last_responsable(self):
- for f in reversed(self.frames):
- if f.responsable is not None and f.responsibility_type is ResponsibilityType.IN:
- return f.responsable
- return None
-
- def last_declared(self):
- for f in reversed(self.frames):
- if f.declared is not None:
- return f.declared
- return None
-
- def __str__(self):
- declared_locs = []
- responsable_locs = []
-
- for f in self.frames:
- if f.responsable is not None and f.responsibility_type is ResponsibilityType.IN:
- s = f.responsable.formatWithCode()
- if s not in responsable_locs:
- responsable_locs.append(s)
- if f.declared is not None:
- s = str(f.declared)
- if s not in declared_locs:
- declared_locs.append(str(f.declared))
-
- cause = formatLocations(CAUSED_BY_PREFIX, responsable_locs)
- declared = formatLocations(DECLARED_AT_PREFIX, declared_locs)
-
- (ty, ind) = self.next_type_and_indicator()
-
- notes = joinLines(self.notes)
- if notes:
- notes = notes + "\n"
-
- if self.previous_chain is None:
- previous_chain = ""
- preHeader = self.header or 'got value of wrong type'
- postHeader = ''
- else:
- previous_chain = self.previous_chain.__str__().strip()
- preHeader = ''
- postHeader = self.header
- if postHeader:
- postHeader = postHeader + "\n"
- if preHeader:
- preHeader = preHeader + "\n"
- if previous_chain:
- previous_chain = previous_chain + "\n\n"
-
- ctx = ""
- if self.expected != ty:
- ctx = f"context: {ty.rstrip()}"
- ind = ind.rstrip()
- if ind:
- ctx = f"{ctx}\n {ind}"
- if ctx:
- ctx = ctx + "\n"
- given = None if self.given is NO_GIVEN else repr(self.given)
- expected = None if self.expected is None else self.expected.strip()
- if expected is not None and expected != 'None':
- expected = f'value of type {expected}'
- if given is not None:
- given = f"given: {given.rstrip()}\n"
- else:
- given = ""
- if expected is not None:
- expected = f"expected: {expected}\n"
- else:
- expected = ""
- if notes and (given or expected):
- notes += "\n"
- return (f"""{preHeader}{previous_chain}{postHeader}{notes}{given}{expected}
-{ctx}{declared}
-{cause}""")
-
-
-class UntypyAttributeError(AttributeError, UntypyError):
-
- def __init__(self, message: str, locations: list[Location] = []):
- self.message = message
- self.locations = locations.copy()
- super().__init__(self.__str__())
-
- def simpleName(self):
- return 'AttributeError'
-
- def with_location(self, loc: Location) -> UntypyAttributeError:
- return type(self)(self.message, self.locations + [loc]) # preserve type
-
- def __str__(self):
- return f"{self.message}\n{formatLocations(DECLARED_AT_PREFIX, self.locations)}"
-
-
-class UntypyNameError(UntypyAttributeError, UntypyError):
- def simpleName(self):
- return 'NameError'
-
-class UntypyAnnotationError(UntypyAttributeError, UntypyError):
- def simpleName(self):
- return 'AnnotationError'
diff --git a/python/deps/untypy/untypy/impl/__init__.py b/python/deps/untypy/untypy/impl/__init__.py
deleted file mode 100644
index d147d692..00000000
--- a/python/deps/untypy/untypy/impl/__init__.py
+++ /dev/null
@@ -1,108 +0,0 @@
-import inspect
-from typing import Any, Optional, TypeVar, List, Dict
-import typing
-
-from untypy.interfaces import CreationContext, TypeChecker, ExecutionContext
-from .alias import TypeAliasTypeFactory
-from .annotated import AnnotatedFactory
-from .any import AnyFactory
-from .callable import CallableFactory
-from .dummy_delayed import DummyDelayedFactory
-from .generator import GeneratorFactory
-from .generic import GenericFactory
-from .interface import InterfaceFactory
-from .choice import ChoiceFactory
-from .literal import LiteralFactory
-from .none import NoneFactory
-from .optional import OptionalFactory
-from .protocol import ProtocolFactory
-from .simple import SimpleFactory
-from .string_forward_refs import StringForwardRefFactory
-from .tuple import TupleFactory
-from .union import UnionFactory
-from ..error import Location, UntypyAttributeError, UntypyAnnotationError
-from ..util.debug import debug
-
-# More Specific Ones First
-_FactoryList = [
- AnyFactory(),
- NoneFactory(),
- DummyDelayedFactory(),
- AnnotatedFactory(),
- TypeAliasTypeFactory(),
- ProtocolFactory(), # must be higher then Generic
- GenericFactory(),
- CallableFactory(),
- LiteralFactory(),
- OptionalFactory(), # must be higher then Union
- UnionFactory(),
- TupleFactory(),
- GeneratorFactory(),
- InterfaceFactory(),
- ChoiceFactory(),
- StringForwardRefFactory(), # resolve types passed as strings
- # must come last
- SimpleFactory()
-]
-
-def isAnnotationValid(annot: Any):
- try:
- hash(annot)
- except TypeError:
- return False
- return True
-
-class DefaultCreationContext(CreationContext):
-
- def __init__(self, typevars: Dict[TypeVar, Any], declared_location: Location,
- checkedpkgprefixes: List[str], eval_context: Optional[Any] = None):
- self.typevars = typevars
- self.declared = declared_location
- self.checkedpkgprefixes = checkedpkgprefixes
- self._eval_context = eval_context
-
- def declared_location(self) -> Location:
- return self.declared
-
- def find_checker(self, annotation: Any) -> Optional[TypeChecker]:
- if not isAnnotationValid(annotation):
- raise UntypyAnnotationError(message="Invalid type annotation: " + str(annotation))
- for fac in _FactoryList:
- res = fac.create_from(annotation=annotation, ctx=self)
- if res is not None:
- debug(f'Created checker for {annotation} from factory {fac}')
- return res
- return None
-
- def wrap(self, err: UntypyAttributeError) -> UntypyAttributeError:
- return err.with_location(self.declared)
-
- def resolve_typevar(self, var: TypeVar) -> typing.Tuple[bool, Any]:
- # Not result may be None
- if var in self.typevars:
- return True, self.typevars[var]
- else:
- return False, None
-
- def all_typevars(self) -> List[TypeVar]:
- return list(self.typevars.keys())
-
- def with_typevars(self, typevars: Dict[TypeVar, Any]) -> CreationContext:
- tv = self.typevars.copy()
- tv.update(typevars)
- return DefaultCreationContext(tv, self.declared, self.checkedpkgprefixes, self._eval_context)
-
- def should_be_inheritance_checked(self, annotation: type) -> bool:
- m = inspect.getmodule(annotation)
-
- for pkgs in self.checkedpkgprefixes:
- # Inheritance should be checked on types
- # when the type's module or its parent lies in the "user code".
- # Inheritance of types of extern modules should be not be checked.
- if m.__name__ == pkgs or m.__name__.startswith(pkgs + "."):
- return True
-
- return False
-
- def eval_context(self):
- return self._eval_context
diff --git a/python/deps/untypy/untypy/impl/alias.py b/python/deps/untypy/untypy/impl/alias.py
deleted file mode 100644
index 559a4c85..00000000
--- a/python/deps/untypy/untypy/impl/alias.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from typing import Any, Optional
-import typing
-
-from untypy.interfaces import TypeChecker, CreationContext, TypeCheckerFactory
-
-class TypeAliasTypeFactory(TypeCheckerFactory):
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- # TypeAliasType was introduced in python 3.12
- if hasattr(typing, 'TypeAliasType'):
- if isinstance(annotation, typing.TypeAliasType):
- return ctx.find_checker(annotation.__value__)
- return None
diff --git a/python/deps/untypy/untypy/impl/annotated.py b/python/deps/untypy/untypy/impl/annotated.py
deleted file mode 100644
index f9119228..00000000
--- a/python/deps/untypy/untypy/impl/annotated.py
+++ /dev/null
@@ -1,137 +0,0 @@
-from typing import Optional, Any, Iterator
-
-from untypy.error import UntypyTypeError, UntypyAttributeError, Frame
-from untypy.interfaces import TypeCheckerFactory, TypeChecker, ExecutionContext, CreationContext
-
-
-class AnnotatedChecker:
- def check(self, arg: Any, ctx: ExecutionContext) -> None:
- pass
-
-
-class AnnotatedCheckerCallable(AnnotatedChecker):
- def __init__(self, annotated, callable):
- self.callable = callable
- self.annotated = annotated
-
- def check(self, arg: Any, ctx: ExecutionContext) -> None:
- res = self.callable(arg)
- if not res:
- exp = self.annotated.describe()
- # raise error on falsy value
- err = UntypyTypeError(
- given=arg,
- expected=exp
- )
- if self.annotated.is_anonymous:
- err = err.with_note(f"condition in {exp} does not hold")
- (t, i) = err.next_type_and_indicator()
- for info in self.annotated.info:
- err = err.with_note(" - " + info)
- raise ctx.wrap(err)
-
-
-class AnnotatedCheckerContainer(AnnotatedChecker):
- def __init__(self, annotated, cont):
- self.cont = cont
- self.annotated = annotated
-
- def check(self, arg: Any, ctx: ExecutionContext) -> None:
- if arg not in self.cont:
- # raise error on falsy value
- err = UntypyTypeError(
- given=arg,
- expected=self.annotated.describe()
- )
- if self.annotated.is_anonymous:
- err = err.with_note(f"{repr(arg)} is not in {repr(self.cont)}.")
-
- for info in self.annotated.info:
- err = err.with_note(" - " + info)
-
- raise ctx.wrap(err)
-
-
-class AnnotatedFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if hasattr(annotation, '__metadata__') and hasattr(annotation, '__origin__'):
- inner = ctx.find_checker(annotation.__origin__)
- if inner is None:
- raise ctx.wrap(UntypyAttributeError(f"Could not resolve annotation "
- f"'{repr(annotation.__origin__)}' "
- f"inside of '{annotation}'."))
-
- return AnnotatedChecker(annotation, inner, annotation.__metadata__, ctx)
- else:
- return None
-
-
-class AnnotatedChecker(TypeChecker):
- def __init__(self, annotated, inner: TypeChecker, metadata: Iterator, ctx: CreationContext):
- self.annotated = annotated
- self.inner = inner
-
- meta = []
- info = []
- for m in metadata:
- if callable(m):
- meta.append(AnnotatedCheckerCallable(self, m))
- elif isinstance(m, str):
- info.append(m)
- elif hasattr(m, '__contains__'):
- meta.append(AnnotatedCheckerContainer(self, m))
- else:
- raise ctx.wrap(UntypyAttributeError(f"Unsupported metadata '{repr(m)}' in '{repr(self.annotated)}'.\n"
- f"Only callables or objects providing __contains__ are allowed."))
- self.meta = meta
- self.info = info
- if len(info) == 1:
- self.name = info[0]
- self.info = []
- else:
- self.name = None
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- wrapped = self.inner.check_and_wrap(arg, AnnotatedCheckerExecutionContext(self, ctx))
- for ck in self.meta:
- ck.check(wrapped, ctx)
- return wrapped
-
- def describe(self) -> str:
- if self.name:
- return self.name
- elif len(self.info) > 0:
- text = ", ".join(map(lambda a: f"'{a}'", self.info))
- return f"Annotated[{text}]"
- else:
- return repr(self.annotated)
-
- def base_type(self):
- return self.inner.base_type()
-
- @property
- def is_anonymous(self):
- return self.name is None
-
-
-class AnnotatedCheckerExecutionContext(ExecutionContext):
- def __init__(self, ch: AnnotatedChecker, upper: ExecutionContext):
- self.ch = ch
- self.upper = upper
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- offset = self.ch.describe().find("[") + 1
-
- (t, i) = err.next_type_and_indicator()
- if self.ch.name is not None:
- i = "^" * len(self.ch.describe())
-
- err = err.with_frame(Frame(
- type_declared=self.ch.describe(),
- indicator_line=(" " * offset) + i,
- declared=None,
- responsable=None
- ))
-
- return self.upper.wrap(err)
diff --git a/python/deps/untypy/untypy/impl/any.py b/python/deps/untypy/untypy/impl/any.py
deleted file mode 100644
index 62671c8f..00000000
--- a/python/deps/untypy/untypy/impl/any.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from typing import Any, Optional
-
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext, ExecutionContext
-
-
-class AnyFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if annotation is Any or annotation is any:
- return AnyChecker()
- else:
- return None
-
-
-class AnyChecker(TypeChecker):
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- return arg
-
- def describe(self) -> str:
- return "Any"
-
- def base_type(self) -> type:
- return [Any]
-
-
-class SelfChecker(TypeChecker):
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- return arg
-
- def describe(self) -> str:
- return "Self"
-
- def base_type(self) -> type:
- return [Any]
diff --git a/python/deps/untypy/untypy/impl/callable.py b/python/deps/untypy/untypy/impl/callable.py
deleted file mode 100644
index 2d092e12..00000000
--- a/python/deps/untypy/untypy/impl/callable.py
+++ /dev/null
@@ -1,277 +0,0 @@
-import inspect
-import sys
-from collections.abc import Callable as AbcCallable
-from typing import Any, Optional, Callable, Union, Tuple
-
-from untypy.error import UntypyTypeError, UntypyAttributeError, Frame, Location
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext, ExecutionContext, WrappedFunction, \
- WrappedFunctionContextProvider
-# These Types are prefixed with an underscore...
-from untypy.util import ArgumentExecutionContext, ReturnExecutionContext
-import untypy.util.typedfunction as typedfun
-
-CallableTypeOne = type(Callable[[], None])
-CallableTypeTwo = type(AbcCallable[[], None])
-
-
-class CallableFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if type(annotation) == CallableTypeOne or type(annotation) == CallableTypeTwo:
- return CallableChecker(annotation, ctx)
- else:
- return None
-
-
-class CallableChecker(TypeChecker):
- return_checker: TypeChecker
- argument_checker: list[TypeChecker]
-
- def __init__(self, annotation: Union[CallableTypeOne, CallableTypeTwo], ctx: CreationContext):
- arguments_ty = annotation.__args__[:-1]
- return_ty = annotation.__args__[-1]
-
- return_checker = ctx.find_checker(return_ty)
- if return_checker is None:
- raise ctx.wrap(UntypyAttributeError(f"Return Type Annotation not found. {return_ty}"))
-
- argument_checker = []
- for arg in arguments_ty:
- checker = ctx.find_checker(arg)
- if checker is None:
- raise ctx.wrap(UntypyAttributeError(f"Argument Type Annotation not found. {arg}"))
- argument_checker.append(checker)
-
- self.return_checker = return_checker
- self.argument_checker = argument_checker
-
- def may_be_wrapped(self) -> bool:
- return True
-
- def base_type(self) -> list[Any]:
- return [Callable]
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if callable(arg):
- return TypedCallable(arg, self.return_checker, self.argument_checker, ctx)
- else:
- raise ctx.wrap(UntypyTypeError(arg, self.describe()))
-
- def describe(self) -> str:
- arguments = ", ".join(map(lambda e: e.describe(), self.argument_checker))
- return f"Callable[[{arguments}], {self.return_checker.describe()}]"
-
-
-class TypedCallable(Callable, WrappedFunction):
- return_checker: TypeChecker
- argument_checker: list[TypeChecker]
- inner: Callable
- fn: Callable
- ctx: ExecutionContext
-
- def __init__(self, inner: Callable, return_checker: TypeChecker,
- argument_checker: list[TypeChecker], ctx: ExecutionContext):
- # unwrap if wrapped function
- self.ResponsibilityType = None
- if hasattr(inner, '__wf'):
- inner = getattr(inner, '__wf')
- self.inner = inner
- self.return_checker = return_checker
- self.argument_checker = argument_checker
- self.ctx = ctx
- self.fn = WrappedFunction.find_original(self.inner)
- setattr(self, '__wf', self)
-
- def __call__(self, *args, **kwargs):
- caller = sys._getframe(1)
-
- (args, kwargs, bindings) = self.wrap_arguments(
- lambda i: TypedCallableArgumentExecutionContext(self, caller, i, self.ctx), args, kwargs)
-
- bind2 = None
- if isinstance(self.inner, WrappedFunction):
- (args, kwargs, bind2) = self.inner.wrap_arguments(lambda n:
- TypedCallableIncompatibleSingature(self, n,
- caller,
- self.ctx),
- args, kwargs)
-
- ret = self.fn(*args, **kwargs)
- if isinstance(self.inner, WrappedFunction):
- ret = self.inner.wrap_return(args, kwargs, ret, bind2, TypedCallableReturnExecutionContext(self.ctx, self, True))
-
- ret = self.wrap_return(args, kwargs, ret, bindings, TypedCallableReturnExecutionContext(self.ctx, self, False))
- return ret
-
- def get_original(self):
- return self.inner
-
- def wrap_arguments(self, ctxprv: WrappedFunctionContextProvider, args, kwargs):
- new_args = []
- i = 0
- for (arg, checker) in zip(args, self.argument_checker):
- res = checker.check_and_wrap(arg, ctxprv(i))
- new_args.append(res)
- i += 1
-
- if len(kwargs) > 0:
- raise self.ctx.wrap(UntypyTypeError(
- kwargs,
- self.describe()
- ).with_note("Keyword arguments are not supported in callable types."))
-
- bindings = None
- return new_args, kwargs, bindings
-
- def wrap_return(self, args, kwargs, ret, bindings, ctx: ExecutionContext):
- fc_pair = None
- return typedfun.wrap_return(self.return_checker, args, kwargs, ret, fc_pair, ctx)
-
- def describe(self) -> str:
- arguments = ", ".join(map(lambda e: e.describe(), self.argument_checker))
- return f"Callable[[{arguments}], {self.return_checker.describe()}]"
-
- def checker_for(self, name: str) -> TypeChecker:
- if name == 'return':
- return self.return_checker
- spec = inspect.getfullargspec(self.fn)
- try:
- ix = spec.args.index(name)
- except ValueError:
- raise self.ctx.wrap(UntypyAttributeError(
- f"No annotation found for argument {name}"
- ))
- if ix >= len(self.argument_checker):
- raise self.ctx.wrap(UntypyAttributeError(
- f"No annotation found for argument {name}"
- ))
- return self.argument_checker[ix]
-
-
-class TypedCallableIncompatibleSingature(ExecutionContext):
-
- def __init__(self, tc: TypedCallable, arg_name: str, caller, upper: ExecutionContext):
- self.arg_name = arg_name
- self.tc = tc
- self.upper = upper
- self.caller = caller
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- (original_expected, _ind) = err.next_type_and_indicator()
- err = ArgumentExecutionContext(self.tc.inner, None, self.arg_name).wrap(err)
-
- func_decl = WrappedFunction.find_location(self.tc.inner)
-
- name = WrappedFunction.find_original(self.tc.inner).__name__
-
- (decl, ind) = err.next_type_and_indicator()
- err = err.with_frame(Frame(
- decl,
- ind,
- declared=None,
- responsable=func_decl
- ))
-
- previous_chain = UntypyTypeError(
- f"def {name}{self.tc.inner.describe()}",
- f"{self.tc.describe()}"
- )
- (decl, ind) = previous_chain.next_type_and_indicator()
- previous_chain = previous_chain.with_frame(Frame(
- decl,
- ind,
- declared=func_decl,
- responsable=None
- ))
-
- err = err.with_note(
- f"Argument '{self.arg_name}' of method '{name}' violates the signature of {self.tc.describe()}.")
-
- previous_chain = self.upper.wrap(previous_chain)
-
- return err.with_previous_chain(previous_chain)
-
-
-class TypedCallableReturnExecutionContext(ExecutionContext):
- upper: ExecutionContext
- fn: TypedCallable
-
- def __init__(self, upper: ExecutionContext, fn: TypedCallable, invert: bool):
- self.upper = upper
- self.fn = fn
- self.invert = invert
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- desc = lambda s: s.describe()
- front_str = f"Callable[[{', '.join(map(desc, self.fn.argument_checker))}], "
- responsable = WrappedFunction.find_location(self.fn.inner)
-
- if self.invert:
- (next_ty, indicator) = err.next_type_and_indicator()
- err = ReturnExecutionContext(self.fn.inner).wrap(err)
- err = err.with_frame(Frame(
- f"{front_str}{next_ty}]",
- (" " * len(front_str)) + indicator,
- declared=None,
- responsable=responsable
- ))
- err = err.with_inverted_responsibility_type()
- return self.upper.wrap(err)
-
- (next_ty, indicator) = err.next_type_and_indicator()
- err = err.with_frame(Frame(
- f"{front_str}{next_ty}]",
- (" " * len(front_str)) + indicator,
- declared=None,
- responsable=responsable
- ))
-
- return self.upper.wrap(err)
-
-
-class TypedCallableArgumentExecutionContext(ExecutionContext):
- upper: ExecutionContext
- fn: TypedCallable
- stack: inspect.FrameInfo
- argument_num: int
-
- def __init__(self, fn: TypedCallable, stack: inspect.FrameInfo, argument_num: int, upper: ExecutionContext):
- self.fn = fn
- self.stack = stack
- self.argument_num = argument_num
- self.upper = upper
-
- def declared_and_indicator(self, err: UntypyTypeError) -> Tuple[str, str]:
- (next_ty, indicator) = err.next_type_and_indicator()
- front_types = []
- back_types = []
- for n, ch in enumerate(self.fn.argument_checker):
- if n < self.argument_num:
- front_types.append(ch.describe())
- elif n > self.argument_num:
- back_types.append(ch.describe())
-
- l = len(f"Callable[[{', '.join(front_types)}")
- if len(front_types) > 0:
- l += len(', ')
-
- return f"Callable[[{', '.join(front_types + [next_ty] + back_types)}], {self.fn.return_checker.describe()}]", \
- (" " * l) + indicator
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- (type_declared, indicator_line) = self.declared_and_indicator(err)
-
- declared = WrappedFunction.find_location(self.fn.inner)
-
- responsable = Location.from_stack(self.stack)
-
- frame = Frame(
- type_declared,
- indicator_line,
- declared=declared,
- responsable=responsable,
- )
-
- err = err.with_frame(frame)
- err = err.with_inverted_responsibility_type()
- return self.upper.wrap(err)
diff --git a/python/deps/untypy/untypy/impl/choice.py b/python/deps/untypy/untypy/impl/choice.py
deleted file mode 100644
index fefd5776..00000000
--- a/python/deps/untypy/untypy/impl/choice.py
+++ /dev/null
@@ -1,66 +0,0 @@
-
-from typing import Any, Optional
-from untypy.interfaces import TypeCheckerFactory, CreationContext, TypeChecker, ExecutionContext
-from untypy.util.debug import debug, isDebug
-import untypy.error
-
-# A type depending on the arguments passed to a function. Can only be used as the return type
-# of a function.
-# Written Choice[T1, ..., Tn, fun]. fun is called with the arguments of the function
-# call (possible including self and empty kwargs) and the type checkers for T1, ..., Tn.
-# It then returns a type checker.
-class Choice:
- def __init__(self, types, select_fun):
- self.types = types
- self.select_fun = select_fun
-
- def __class_getitem__(cls, args):
- if len(args) < 2:
- raise untypy.error.WyppTypeError(f'Choice requires at least two arguments: Choice[TYPE_1, ..., FUN]')
- return Choice(args[:-1], args[-1])
-
-class ChoiceFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if isinstance(annotation, Choice):
- checkers = []
- for t in annotation.types:
- checker = ctx.find_checker(t)
- if checker is None:
- return None
- checkers.append(checker)
- return ChoiceChecker(checkers, annotation.select_fun)
- else:
- return None
-
-class ChoiceChecker(TypeChecker):
-
- def __init__(self, checkers: list[TypeChecker], select_fun):
- self.checkers = checkers
- self.select_fun = select_fun
-
- def describe(self) -> str:
- l = [c.describe() for c in self.checkers]
- return f"Choice[{', '.join(l)}, ]"
-
- def may_be_wrapped(self) -> bool:
- return True
-
- def __illegal_use(self):
- raise untypy.error.WyppTypeError(f"{self.describe()} can only be used as the return type of a function or method")
-
- def base_type(self) -> list[Any]:
- self.__illegal_use()
-
- def base_type_priority(self) -> int:
- return 0
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- self.__illegal_use()
-
- def get_checker(self, args: list[Any], kwds: dict[str, Any]) -> TypeChecker:
- c = self.select_fun(*args, kwds, *self.checkers)
- if isDebug:
- all = [c.describe() for c in self.checkers]
- debug(f'Choice returned checker {c.describe()} for args={args}, kwds={kwds}. All checkers: {",".join(all)}')
- return c
diff --git a/python/deps/untypy/untypy/impl/dummy_delayed.py b/python/deps/untypy/untypy/impl/dummy_delayed.py
deleted file mode 100644
index 81b07704..00000000
--- a/python/deps/untypy/untypy/impl/dummy_delayed.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from typing import Any, Optional
-
-from untypy.error import UntypyTypeError
-from untypy.interfaces import TypeChecker, CreationContext, TypeCheckerFactory, ExecutionContext
-
-
-class DummyDelayedType:
- """
- This class is used for raising delayed type checking errors.
- """
- pass
-
-
-class DummyDelayedFactory(TypeCheckerFactory):
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if annotation is DummyDelayedType:
- return DummyDelayedChecker()
- else:
- return None
-
-
-class DummyDelayedChecker(TypeChecker):
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- return DummyDelayedWrapper(ctx)
-
- def describe(self) -> str:
- return "DummyDelayedType"
-
- def base_type(self) -> list[Any]:
- return []
-
-
-class DummyDelayedWrapper:
- upper: ExecutionContext
-
- def __init__(self, upper: ExecutionContext):
- self.upper = upper
-
- def use(self):
- raise self.upper.wrap(UntypyTypeError(
- "",
- "DummyDelayedType"
- ))
diff --git a/python/deps/untypy/untypy/impl/generator.py b/python/deps/untypy/untypy/impl/generator.py
deleted file mode 100644
index 6551bdb3..00000000
--- a/python/deps/untypy/untypy/impl/generator.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import collections.abc
-import inspect
-import sys
-from collections.abc import Generator
-from typing import Any, Optional
-from typing import Generator as OtherGenerator
-
-from untypy.error import UntypyTypeError, UntypyAttributeError, Location
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext, ExecutionContext
-from untypy.util import CompoundTypeExecutionContext, NoResponsabilityWrapper
-
-GeneratorTypeA = type(Generator[None, None, None])
-GeneratorTypeB = type(OtherGenerator[None, None, None])
-
-
-class GeneratorFactory(TypeCheckerFactory):
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if type(annotation) in [GeneratorTypeA, GeneratorTypeB] and annotation.__origin__ == collections.abc.Generator:
- if len(annotation.__args__) != 3:
- raise ctx.wrap(UntypyAttributeError(f"Expected 3 type arguments for Generator."))
-
- (yield_checker, send_checker, return_checker) = list(
- map(lambda a: ctx.find_checker(a), annotation.__args__))
-
- if yield_checker is None:
- raise ctx.wrap(UntypyAttributeError(f"The Yield Annotation of the Generator could not be resolved."))
- if send_checker is None:
- raise ctx.wrap(UntypyAttributeError(f"The Send Annotation of the Generator could not be resolved."))
- if return_checker is None:
- raise ctx.wrap(UntypyAttributeError(f"The Return Annotation of the Generator could not be resolved."))
-
- return GeneratorChecker(yield_checker, send_checker, return_checker)
- else:
- return None
-
-
-class GeneratorChecker(TypeChecker):
- yield_checker: TypeChecker
- send_checker: TypeChecker
- return_checker: TypeChecker
-
- def __init__(self, yield_checker: TypeChecker, send_checker: TypeChecker, return_checker: TypeChecker):
- self.yield_checker = yield_checker
- self.send_checker = send_checker
- self.return_checker = return_checker
-
- def may_be_wrapped(self) -> bool:
- return True
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if not inspect.isgenerator(arg):
- raise ctx.wrap(UntypyTypeError(arg, self.describe()))
-
- me = self
- yield_ctx = TypedGeneratorYieldReturnContext(arg, self, True, ctx)
- return_ctx = TypedGeneratorYieldReturnContext(arg, self, False, ctx)
-
- def wrapped():
- try:
- sent = None
- while True:
- value_yield = arg.send(sent)
- # check value_yield (arg is responsable)
- value_yield = me.yield_checker.check_and_wrap(value_yield, yield_ctx)
-
- sent = yield value_yield
-
- caller = sys._getframe(1)
-
- # check sent value (caller is responsable)
- sent = me.send_checker.check_and_wrap(sent, TypedGeneratorSendContext(caller, me, ctx))
-
- except StopIteration as e:
- # check value_returned (arg is responsable)
- ret = me.return_checker.check_and_wrap(e.value, return_ctx)
- return ret
-
- return wrapped()
-
- def describe(self) -> str:
- return f"Generator[{self.yield_checker.describe()}, {self.send_checker.describe()}, {self.return_checker.describe()}]"
-
- def base_type(self) -> Any:
- return [GeneratorType]
-
-
-class TypedGeneratorYieldReturnContext(CompoundTypeExecutionContext):
- generator: Generator[Any, Any, Any]
-
- def __init__(self, generator: Generator[Any, Any, Any], checker: GeneratorChecker, is_yield: bool,
- upper: ExecutionContext):
- self.generator = generator
- # index in checkers list
- if is_yield:
- idx = 0
- else:
- idx = 2
- super().__init__(upper, [checker.yield_checker, checker.send_checker, checker.return_checker], idx)
-
- def name(self) -> str:
- return "Generator"
-
- def responsable(self) -> Optional[Location]:
- try:
- if hasattr(self.generator, 'gi_frame'):
- return Location(
- file=inspect.getfile(self.generator.gi_frame),
- line_no=inspect.getsourcelines(self.generator.gi_frame)[1],
- line_span=len(inspect.getsourcelines(self.generator.gi_frame)[0]),
- )
- except OSError: # this call does not work all the time
- pass
- except TypeError:
- pass
- return None
-
-
-class TypedGeneratorSendContext(CompoundTypeExecutionContext):
- def __init__(self, stack: inspect.FrameInfo, checker: GeneratorChecker, upper: ExecutionContext):
- self.stack = stack
- super().__init__(NoResponsabilityWrapper(upper),
- [checker.yield_checker, checker.send_checker, checker.return_checker], 1)
-
- def name(self) -> str:
- return "Generator"
-
- def responsable(self) -> Optional[Location]:
- return Location.from_stack(self.stack)
diff --git a/python/deps/untypy/untypy/impl/generic.py b/python/deps/untypy/untypy/impl/generic.py
deleted file mode 100644
index 0f0e65b5..00000000
--- a/python/deps/untypy/untypy/impl/generic.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import typing
-from typing import Optional, TypeVar, Any
-
-from untypy.error import UntypyTypeError
-from untypy.impl.protocol import ProtocolChecker
-from untypy.interfaces import TypeCheckerFactory, CreationContext, TypeChecker, ExecutionContext
-
-class GenericProtocolChecker(ProtocolChecker):
- def protocol_type(self) -> str:
- return "generic"
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if isinstance(arg, self.proto):
- return super().check_and_wrap(arg, ctx)
- else:
- raise ctx.wrap(UntypyTypeError(
- expected=self.describe(),
- given=arg
- )).with_note(f"Type '{type(arg).__name__}' does not inherit from '{self.proto.__name__}'")
-
-
-class GenericFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- # TODO: Support other typevar features
- if type(annotation) is TypeVar:
- (found, replacement_annotation) = ctx.resolve_typevar(annotation)
- if isinstance(replacement_annotation, TypeChecker):
- inner = replacement_annotation
- elif found:
- inner = ctx.find_checker(replacement_annotation)
- else:
- inner = None
- if found:
- if inner is not None:
- return BoundTypeVar(inner, annotation)
- else:
- return None
- else:
- return UnboundTypeVar(annotation)
- elif hasattr(annotation, '__args__') and \
- hasattr(annotation, '__origin__') and \
- hasattr(annotation.__origin__, '__mro__') and \
- typing.Generic in annotation.__origin__.__mro__:
- return GenericProtocolChecker(annotation, ctx)
- else:
- return None
-
-
-class BoundTypeVar(TypeChecker):
- def __init__(self, inner: TypeChecker, typevar: TypeVar):
- self.inner = inner
- self.typevar = typevar
-
- def describe(self) -> str:
- return self.inner.describe()
-
- def may_be_wrapped(self) -> bool:
- return self.inner.may_be_wrapped()
-
- def base_type(self) -> list[Any]:
- return self.inner.base_type()
-
- def base_type_priority(self) -> int:
- return self.inner.base_type_priority()
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- return self.inner.check_and_wrap(arg, BoundTypeVarCtx(self, ctx))
-
-
-class BoundTypeVarCtx(ExecutionContext):
-
- def __init__(self, bv: BoundTypeVar, ctx: ExecutionContext):
- self.bv = bv
- self.upper = ctx
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- (nt, ni) = err.next_type_and_indicator()
-
- if nt == err.expected and nt == self.bv.inner.describe():
- err.expected = self.bv.describe()
-
- return self.upper.wrap(err)
-
-
-class UnboundTypeVar(TypeChecker):
-
- def __init__(self, typevar: TypeVar):
- self.typevar = typevar
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- return arg
-
- def describe(self) -> str:
- return str(self.typevar)
-
- def base_type(self) -> list[Any]:
- return [self.typevar]
diff --git a/python/deps/untypy/untypy/impl/interface.py b/python/deps/untypy/untypy/impl/interface.py
deleted file mode 100644
index 9ecb5957..00000000
--- a/python/deps/untypy/untypy/impl/interface.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import typing
-from collections.abc import Iterable as ABCIterable
-from collections.abc import Iterator as ABCIterator
-from collections.abc import Sequence as ABCSequence
-from typing import Optional, Any
-
-from untypy.error import UntypyAttributeError
-from untypy.impl.interfaces.iterable import Iterable, Iterator
-from untypy.impl.interfaces.sequence import Sequence
-from untypy.impl.interfaces.dict import Dict, DictLike
-from untypy.impl.interfaces.list import List
-from untypy.impl.interfaces.set import Set
-from untypy.impl.protocol import ProtocolChecker
-from untypy.interfaces import TypeCheckerFactory, TypeChecker, CreationContext
-
-InterfaceMapping = {
- dict: Dict,
- typing.Dict: Dict,
- DictLike: DictLike,
- list: List,
- typing.List: List,
- set: Set,
- typing.Set: Set,
- ABCIterable: Iterable,
- typing.Iterable: Iterable,
- typing.Iterator: Iterator,
- ABCIterator: Iterator,
- ABCSequence: Sequence,
- typing.Sequence: Sequence
-}
-
-class InterfaceFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext, omit_tyargs=False) -> Optional[TypeChecker]:
- if annotation in InterfaceMapping:
- # Assume Any if no parameters are given
- protocol = InterfaceMapping[annotation]
- bindings = protocol.__parameters__
- if len(bindings) == 0:
- raise AssertionError(f"This is a BUG. {annotation} has no generic params.")
- anys = (Any, ) * len(bindings)
- # handle Python inconsistency
- if hasattr(annotation, '__class_getitem__'):
- return self.create_from(
- annotation.__class_getitem__(anys),
- ctx,
- omit_tyargs=True
- )
- elif hasattr(annotation, '__getitem__'):
- return self.create_from(
- annotation.__getitem__(anys),
- ctx,
- omit_tyargs=True
- )
-
- elif hasattr(annotation, '__origin__') and \
- hasattr(annotation, '__args__') and annotation.__origin__ in InterfaceMapping:
- protocol = InterfaceMapping[annotation.__origin__]
- bindings = protocol.__parameters__ # args of Generic super class
- origin = annotation.__origin__
- inner_checkers = []
- for param in annotation.__args__:
- ch = ctx.find_checker(param)
- if ch is None:
- raise UntypyAttributeError(f"Could not resolve annotation {param} inside of {annotation}")
- inner_checkers.append(ch)
- if len(inner_checkers) != len(bindings):
- raise UntypyAttributeError(f"Expected {len(bindings)} type arguments inside of {annotation}")
- if omit_tyargs:
- name = f"{origin.__name__}"
- else:
- name = f"{origin.__name__}[" + (', '.join(map(lambda t: t.describe(), inner_checkers))) + "]"
- bindings = dict(zip(bindings, inner_checkers))
- ctx = ctx.with_typevars(bindings)
- return ProtocolChecker(protocol, ctx, altname=name, omit_tyargs=omit_tyargs, ty=origin)
-
- # Non Generic
- elif annotation in InterfaceMapping:
- protocol = InterfaceMapping[annotation]
- # Edge-Case `TypingSequence has no __name__, like every other class`
- if annotation == typing.Sequence:
- name = 'Sequence'
- else:
- name = annotation.__name__
- return ProtocolChecker(protocol, ctx, altname=name, omit_tyargs=omit_tyargs, ty=annotation)
- else:
- return None
diff --git a/python/deps/untypy/untypy/impl/interfaces/dict.py b/python/deps/untypy/untypy/impl/interfaces/dict.py
deleted file mode 100644
index fdb76902..00000000
--- a/python/deps/untypy/untypy/impl/interfaces/dict.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from typing import Iterator, Protocol, TypeVar, Generic, Optional, Iterable, Tuple, Any
-
-A = TypeVar("A")
-B = TypeVar("B")
-
-
-class DictLike(Protocol[A, B]):
- """
- This protocol implements a subset of dict.
- It exists solly to prevent an recursion issue
- inside of WDict
- """
-
- def __iter__(self) -> Iterator[A]:
- pass
-
- def __getitem__(self, key: A) -> B:
- pass
-
-
-K = TypeVar("K")
-V = TypeVar("V")
-
-
-# See: https://docs.python.org/3/library/stdtypes.html#typesmapping
-class Dict(Generic[K, V], dict):
- def clear(self) -> None: pass
-
- # Cannot Typecheck Copy -> Leads to endless recursion in "UntypyInterfaces"
- # def copy(self) -> dict[K,V]:
- # pass
-
- def get(self, key: K, default: Optional[V] = None) -> Optional[V]: pass
-
- def items(self) -> Iterable[Tuple[K, V]]: pass
-
- def keys(self) -> Iterable[K]: pass
-
- def pop(self, k: K, default: Optional[V] = None) -> Optional[V]: pass
-
- def popitem(self) -> Tuple[K, V]: pass
-
- # Miss-match See: https://github.com/skogsbaer/write-your-python-program/issues/19
- def setdefault(self, key: K, default: Optional[V]=None) -> V: pass
-
- def update(self, *E: Iterable[DictLike[K, V]], **F: Optional[DictLike[K, V]]) -> Any: pass
-
- def values(self) -> Iterable[V]: pass
-
- def __contains__(self, key: Any) -> bool: pass
-
- def __delitem__(self, key: K) -> None: pass
-
- def __iter__(self) -> Iterator[K]: pass
-
- def __len__(self) -> int: pass
-
- # Untypy does not support generic functions :/
- def __or__(self, other : dict) -> dict: pass
-
- def __reversed__(self) -> Iterator[K]: pass
-
- # Untypy does not support generic functions :/
- def __ror__(self, other : dict) -> dict: pass
- def __getitem__(self, key: K) -> V: pass
-
- def __setitem__(self, key: K, value: V) -> None: pass
- # FIXME: complete methods
diff --git a/python/deps/untypy/untypy/impl/interfaces/iterable.py b/python/deps/untypy/untypy/impl/interfaces/iterable.py
deleted file mode 100644
index b52c7db1..00000000
--- a/python/deps/untypy/untypy/impl/interfaces/iterable.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from __future__ import annotations
-from typing import Generic, TypeVar, Protocol
-import typing
-
-I = TypeVar("I")
-
-# Note: using typing.Iterator as the result creates an indirection that avoids an infinite
-# loop when constructing checkers.
-class Iterator(Generic[I]):
- def __next__(self) -> I:
- pass
- def __iter__(self) -> typing.Iterator[I]:
- pass
-
-class Iterable(Generic[I]):
- def __iter__(self) -> typing.Iterator[I]:
- pass
-
-class OnlyIterable(Generic[I], Protocol):
- # The __protocol_only__ flag signals that an object wrapped with the protocol should
- # only provide the methods of the protocol.
- __protocol_only__ = True
-
- def __iter__(self) -> typing.Iterator[I]:
- pass
diff --git a/python/deps/untypy/untypy/impl/interfaces/list.py b/python/deps/untypy/untypy/impl/interfaces/list.py
deleted file mode 100644
index d624d8f3..00000000
--- a/python/deps/untypy/untypy/impl/interfaces/list.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from typing import TypeVar, Generic, Iterable, Optional, Union, Any, Iterator
-import typing
-from untypy.impl.interfaces.util import overwrite
-from untypy.impl.choice import Choice
-from untypy.interfaces import CreationContext
-from untypy.util import ReturnExecutionContext
-from untypy.util.condition import postcondition
-
-I = TypeVar("I")
-
-class List(Generic[I], list):
- # doc @ https://docs.python.org/3/tutorial/datastructures.html
- # and https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
- # Exact signatures are undocumented :/
- # HINT: Argument names must match.
-
- def append(self, object: I) -> None: pass
-
- def extend(self, iterable: Iterable[I]) -> None: pass
-
- def insert(self, i: int, x: I) -> None: pass
-
- def remove(self, x: I) -> None: pass
-
- def pop(self, i: int = -1) -> Optional[I]: pass
-
- def clear(self) -> None: pass
-
- def index(self, value: I, start: Optional[int] = 0, stop: Optional[int] = 9223372036854775807) -> int:
- # get index of list
- pass
-
- def count(self, value: I) -> int: pass
-
- # inner list will check type of key.
- def sort(self, *, key: Any = None, reverse: bool = False) -> None: pass
-
- def __contains__(self, key: Any) -> bool: pass
-
- def __delitem__(self, i: Union[int, slice]): pass
-
- def __getitem__(self, i: Union[int, slice]) -> \
- Choice[I, Any, lambda self, i, kws, ti, tl: ti if isinstance(i, int) else tl]: pass
-
- def __add__(self, other: Iterable) -> Any: pass
-
- def __mul__(self, n: int) -> Any: pass
-
- def __iadd__(self, other: Iterable[I]) -> Any: pass
-
- def __imul__(self, n: int) -> Any: pass
-
- def __setitem__(self, key: Union[int, slice], value: Any) -> None: pass
-
- def __iter__(self) -> Iterator[I]: pass
- # FIXME: complete methods
diff --git a/python/deps/untypy/untypy/impl/interfaces/sequence.py b/python/deps/untypy/untypy/impl/interfaces/sequence.py
deleted file mode 100644
index 08d58371..00000000
--- a/python/deps/untypy/untypy/impl/interfaces/sequence.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from typing import TypeVar, Generic, Optional, Iterator, Optional, Union, Any
-from untypy.impl.choice import Choice
-I = TypeVar("I")
-
-
-class Sequence(Generic[I]):
- # See https://docs.python.org/3/library/collections.abc.html
-
- def __getitem__(self, i: Union[int, slice]) -> \
- Choice[I, Any, lambda self, i, kws, ti, tl: ti if isinstance(i, int) else tl]:
- pass
-
- def __len__(self) -> int:
- pass
-
- def __contains__(self, key: Any) -> bool:
- pass
-
- def index(self, value: I, start: Optional[int] = 0, stop: Optional[int] = 9223372036854775807) -> int:
- pass
-
- def count(self, value: I) -> int:
- pass
-
- # For these Methods: `iter` and `reversed` will fallback to `__getitem__` and `__len__`
- # This is just an optimization for some types.
- # Maybe for a future feature.
-
- def __iter__(self) -> Iterator[I]:
- pass
-
diff --git a/python/deps/untypy/untypy/impl/interfaces/set.py b/python/deps/untypy/untypy/impl/interfaces/set.py
deleted file mode 100644
index 145cb9b1..00000000
--- a/python/deps/untypy/untypy/impl/interfaces/set.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from typing import Generic, TypeVar, Optional, Tuple, Iterable, Any, Iterator
-from untypy.impl.interfaces.iterable import OnlyIterable
-
-I = TypeVar("I")
-
-class Set(Generic[I]):
- def add(self, other: I) -> None: pass
- def clear(self) -> None: pass
- def copy(self) -> Any: pass
- def difference(self, *others: Tuple[Iterable, ...]) -> Any: pass
- def difference_update(self, *others: Tuple[Iterable[I], ...]) -> None: pass
- def discard(self, elem: I) -> None: pass
- def intersection(self, *others: Tuple[Iterable, ...]) -> Any: pass
- def intersection_update(self, *others: Tuple[Iterable[I], ...]) -> None: pass
- def isdisjoint(self, other: set) -> bool: pass
- def issubset(self, other: set) -> bool: pass
- def issuperset(self, other: set) -> bool: pass
- def pop(self) -> Optional[I]: pass
- def remove(self, elem: I) -> None: pass
- def symmetric_difference(self, *others: Tuple[Iterable, ...]) -> Any: pass
- def symmetric_difference_update(self, *others: Tuple[Iterable[I], ...]) -> None: pass
- def union(self, *others: Tuple[Iterable, ...]) -> Any: pass
-
- # Using OnlyIterable here makes no other methods than the one provide in Iterable
- # available. This is required because the implementation of update has shortcuts
- # bypassing type checks if an element is of type set.
- def update(self, *others: Tuple[OnlyIterable[I], ...]) -> None:
- pass
-
- def __contains__(self, x: Any) -> bool: pass
- def __delattr__(self, name: str) -> None: pass
-
- def __le__(self, other: Any) -> bool: pass
- def __lt__(self, other: Any) -> bool: pass
- def __ge__(self, other: Any) -> bool: pass
- def __gt__(self, other: Any) -> bool: pass
-
- def __and__(self, other: set) -> Any: pass
- def __rand__(self, other: set) -> Any: pass
- def __iand__(self, other: set) -> Any: pass
- def __ior__(self, other: set) -> Any: pass # FIXME: result should be set[I]
- def __isub__(self, other: set) -> Any: pass
- def __ixor__(self, other: set) -> Any: pass
- def __or__(self, other: set) -> Any: pass
- def __ror__(self, other: set) -> Any: pass
- def __rxor__(self, other: set) -> Any: pass
- def __xor__(self, other: set) -> Any: pass
- def __rsub__(self, other: set) -> Any: pass
- def __sub__(self, other: set) -> Any: pass
-
- def __iter__(self) -> Iterator[I]: pass
- def __len__(self) -> int: pass
- def __setattr__(self, name: str, x: Any) -> None: pass
diff --git a/python/deps/untypy/untypy/impl/interfaces/util.py b/python/deps/untypy/untypy/impl/interfaces/util.py
deleted file mode 100644
index 42e5f170..00000000
--- a/python/deps/untypy/untypy/impl/interfaces/util.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def overwrite(typ):
- def inner(fn):
- setattr(fn, '__overwrite', typ)
- return fn
-
- return inner
diff --git a/python/deps/untypy/untypy/impl/literal.py b/python/deps/untypy/untypy/impl/literal.py
deleted file mode 100644
index 03de3e53..00000000
--- a/python/deps/untypy/untypy/impl/literal.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from typing import Any, Optional, Literal
-
-from untypy.error import UntypyTypeError
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext, ExecutionContext
-
-
-class LiteralFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if hasattr(annotation, '__origin__') and hasattr(annotation, '__args__') and annotation.__origin__ == Literal:
- return LiteralChecker(annotation.__args__)
- else:
- return None
-
-
-class LiteralChecker(TypeChecker):
- inner: list[Any]
-
- def __init__(self, inner: list[Any]):
- self.inner = inner
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if arg in self.inner:
- return arg
- else:
- raise ctx.wrap(UntypyTypeError(
- arg,
- self.describe()
- ))
-
- def base_type(self) -> list[Any]:
- return self.inner[:]
-
- def describe(self) -> str:
- strings = map(lambda v: "%r" % v, self.inner)
- return f"Literal[{', '.join(strings)}]"
diff --git a/python/deps/untypy/untypy/impl/none.py b/python/deps/untypy/untypy/impl/none.py
deleted file mode 100644
index 31180286..00000000
--- a/python/deps/untypy/untypy/impl/none.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from typing import Any, Optional, NoReturn
-
-from untypy.error import UntypyTypeError
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext, ExecutionContext
-
-
-class NoneFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if annotation is None or annotation is type(None) or annotation == NoReturn:
- return NoneChecker()
- else:
- return None
-
-
-class NoneChecker(TypeChecker):
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if arg is None:
- return arg
- else:
- raise ctx.wrap(UntypyTypeError(arg, self.describe()))
-
- def describe(self) -> str:
- return "None"
-
- def base_type(self) -> list[Any]:
- return [None]
diff --git a/python/deps/untypy/untypy/impl/optional.py b/python/deps/untypy/untypy/impl/optional.py
deleted file mode 100644
index b1577c6b..00000000
--- a/python/deps/untypy/untypy/impl/optional.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from typing import Any, Optional, Union
-
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext, ExecutionContext
-from untypy.util import CompoundTypeExecutionContext
-
-UnionType = type(Union[int, str])
-
-
-class OptionalFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if type(annotation) is UnionType and len(annotation.__args__) == 2 and annotation.__args__[1] is type(None):
- checker = ctx.find_checker(annotation.__args__[0])
- if checker is None:
- return None
- else:
- return OptionalChecker(checker)
- else:
- return None
-
-
-class OptionalChecker(TypeChecker):
- inner: TypeChecker
-
- def __init__(self, inner: TypeChecker):
- self.inner = inner
-
- def check_and_wrap(self, arg: Any, upper: ExecutionContext) -> Any:
- if arg is None:
- return arg
- else:
- ctx = OptionalExecutionContext(upper, [self.inner], 0)
- return self.inner.check_and_wrap(arg, ctx)
-
- def describe(self) -> str:
- return f"Optional[{self.inner.describe()}]"
-
- def base_type(self) -> list[Any]:
- return [self.inner.base_type()]
-
-
-class OptionalExecutionContext(CompoundTypeExecutionContext):
- def name(self):
- return "Optional"
diff --git a/python/deps/untypy/untypy/impl/protocol.py b/python/deps/untypy/untypy/impl/protocol.py
deleted file mode 100644
index e98d0385..00000000
--- a/python/deps/untypy/untypy/impl/protocol.py
+++ /dev/null
@@ -1,524 +0,0 @@
-import abc
-import inspect
-import sys
-import typing
-from typing import Protocol, Any, Optional, Callable, Union, TypeVar, Dict, Tuple
-
-import untypy.util.display as display
-from untypy.error import UntypyTypeError, UntypyAttributeError, Frame, Location, ResponsibilityType
-from untypy.impl.any import SelfChecker, AnyChecker
-from untypy.interfaces import TypeCheckerFactory, CreationContext, TypeChecker, ExecutionContext, \
- WrappedFunctionContextProvider
-from untypy.util import WrappedFunction, ArgumentExecutionContext, ReturnExecutionContext
-from untypy.util.condition import FunctionCondition
-from untypy.util.typehints import get_type_hints
-import untypy.util.wrapper as wrapper
-import untypy.util.typedfunction as typedfun
-
-class ProtocolFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if isinstance(annotation, type) and Protocol in annotation.mro():
- return ProtocolChecker(annotation, ctx)
- elif hasattr(annotation, '__args__') and \
- hasattr(annotation, '__origin__') and \
- hasattr(annotation.__origin__, '__mro__') and \
- typing.Protocol in annotation.__origin__.__mro__:
- return ProtocolChecker(annotation, ctx)
- else:
- return None
-
-
-def _find_bound_typevars(clas: type) -> Tuple[type, Dict[TypeVar, Any]]:
- if not hasattr(clas, '__args__') or not hasattr(clas, '__origin__'):
- return (clas, dict())
- if not hasattr(clas.__origin__, '__parameters__'):
- return (clas, dict())
-
- keys = clas.__origin__.__parameters__
- values = clas.__args__
-
- if len(keys) != len(values):
- raise UntypyAttributeError(f"Some unbound Parameters in {clas.__name__}. "
- f"keys={keys} do not match values={values}.",
- [Location(
- file=inspect.getfile(clas),
- line_no=inspect.getsourcelines(clas)[1],
- line_span=len(inspect.getsourcelines(clas)[0]))])
- return (clas.__origin__, dict(zip(keys, values)))
-
-
-def get_proto_members(proto: type, ctx: CreationContext) -> dict[
- str, Tuple[inspect.Signature, dict[str, TypeChecker], FunctionCondition]]:
- blacklist = ['__init__', '__class__', '__delattr__', '__dict__', '__dir__',
- '__doc__', '__getattribute__', '__getattr__', '__init_subclass__',
- '__new__', '__setattr__', '__subclasshook__', '__weakref__',
- '__abstractmethods__', '__class_getitem__',
- '__firstlineno__', '__static_attributes__']
-
- member_dict = {}
- for [name, member] in inspect.getmembers(proto):
- if name in blacklist:
- continue
-
- if inspect.isfunction(member):
- member = WrappedFunction.find_original(member)
- signature = inspect.signature(member)
-
- is_typed = len(inspect.getfullargspec(member).annotations) != 0
-
- checkers = {}
- if not is_typed:
- # Use Any for any type
- for key in signature.parameters:
- if key == 'self':
- checkers[key] = SelfChecker()
- else:
- checkers[key] = AnyChecker()
- checkers['return'] = AnyChecker()
- else:
- annotations = get_type_hints(member, ctx)
- for key in signature.parameters:
- if key == 'self':
- checkers[key] = SelfChecker()
- else:
- param = signature.parameters[key]
- if param.annotation is inspect.Parameter.empty:
- raise ctx.wrap(UntypyAttributeError(
- f"Missing annotation for argument '{key}' of function {member.__name__} "
- f"in protocol {proto.__name__}\n"))
-
- param_anot = annotations[key]
- if param_anot is proto:
- checker = SimpleInstanceOfChecker(proto, None)
- else:
- checker = ctx.find_checker(param_anot)
- if checker is None:
- raise ctx.wrap(UntypyAttributeError(f"\n\tUnsupported type annotation: {param.annotation}\n"
- f"for argument '{key}' of function {member.__name__} "
- f"in protocol {proto.__name__}.\n"))
- checkers[key] = checker
-
- if signature.return_annotation is inspect.Parameter.empty:
- return_annotation = None
- else:
- return_annotation = annotations['return']
- if return_annotation is proto: # Self as Return Type would led to endless recursion
- return_checker = SimpleInstanceOfChecker(proto, None)
- else:
- return_checker = ctx.find_checker(return_annotation)
-
- if return_checker is None:
- raise ctx.wrap(UntypyAttributeError(f"\n\tUnsupported type annotation: {signature.return_annotation}\n"
- f"for return value of function {member.__name__} "
- f"in protocol-like {proto.__name__}.\n"))
- checkers['return'] = return_checker
-
- fc = None
- if hasattr(member, '__fc'):
- fc = getattr(member, '__fc')
- member_dict[name] = (signature, checkers, fc)
- return member_dict
-
-
-class ProtocolChecker(TypeChecker):
- def __init__(self, annotation: type, ctx: CreationContext, *, altname : Optional[str] = None,
- omit_tyargs=False, ty: Optional[type]=None):
- (proto, typevars) = _find_bound_typevars(annotation)
- self.ctx = ctx.with_typevars(typevars)
- self.proto = proto
- self._members = None
- self.typevars = typevars
- self.altname = altname
- self.omit_tyargs = omit_tyargs
- self.ty = ty
-
- @property
- def members(self):
- if not self._members:
- self._members = get_proto_members(self.proto, self.ctx)
- return self._members
-
- def may_change_identity(self) -> bool:
- return True
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if hasattr(arg, '__wrapped__'):
- # no double wrapping
- arg = getattr(arg, '__wrapped__')
- simple = False
- if hasattr(self.proto, '__protocol_only__'):
- simple = getattr(self.proto, '__protocol_only__')
- if self.ty is not None and not isinstance(arg, self.ty):
- err = ctx.wrap(UntypyTypeError(
- expected=self.describe(),
- given=arg
- ))
- raise err
- return wrapForProtocol(self, arg, self.members, ctx, simple=simple)
-
- def base_type(self) -> list[Any]:
- # Prevent Classes implementing multiple Protocols in one Union by accident.
- if self.ty:
- return [self.ty]
- else:
- return [Protocol]
-
- def describe(self) -> str:
- if self.altname is not None:
- return self.altname
-
- desc = set([])
- if not self.omit_tyargs:
- for name in self.members:
- (sig, binds, cond) = self.members[name]
- for argname in sig.parameters:
- if isinstance(sig.parameters[argname].annotation, TypeVar):
- desc.add(binds[argname].describe())
- if isinstance(sig.return_annotation, TypeVar):
- desc.add(binds['return'].describe())
- if len(desc) > 0:
- # FIXME: what about the ordering of tyvars?
- return f"{self.proto.__name__}[" + (', '.join(desc)) + "]"
- else:
- return f"{self.proto.__name__}"
-
- def protocol_type(self) -> str:
- return f"protocol"
-
- def protoname(self):
- return self.describe()
-
-def isInternalProtocol(p: Any):
- if isinstance(p, ProtocolChecker):
- p = p.proto
- if hasattr(p, '__module__'):
- return 'untypy.' in p.__module__
- else:
- return False
-
-def protoMismatchErrorMessage(what: str, proto: Any):
- if isinstance(proto, ProtocolChecker):
- kind = proto.protocol_type()
- name = proto.protoname()
- else:
- kind = 'protocol'
- name = proto.__name__
- isUserDefined = True
- if isInternalProtocol(proto):
- isUserDefined = False
- if isUserDefined:
- return f"{what} does not implement {kind} {name}"
- else:
- return f"{what} is not {display.withIndefiniteArticle(name)}"
-
-def wrapForProtocol(protocolchecker: ProtocolChecker,
- originalValue: Any,
- members: dict[str, Tuple[inspect.Signature, dict[str, TypeChecker], FunctionCondition]],
- ctx: ExecutionContext,
- simple: bool):
- list_of_attr = dict()
- original = type(originalValue)
- for fnname in members:
- if not hasattr(original, fnname):
- err = ctx.wrap(UntypyTypeError(
- expected=protocolchecker.describe(),
- given=originalValue
- )).with_header(
- protoMismatchErrorMessage(original.__name__, protocolchecker.proto)
- )
- missing = []
- for fnname in members:
- if not hasattr(original, fnname):
- missing.append(fnname)
- if len(missing) == 2:
- err = err.with_note(f"It is missing the functions '{missing[0]}' and '{missing[1]}'")
- elif len(missing) == 1:
- err = err.with_note(f"It is missing the function '{missing[0]}'")
- raise err
-
- original_fn = getattr(original, fnname)
- try:
- # fails on built ins - YEAH
- original_fn_signature = inspect.signature(original_fn)
- except:
- original_fn_signature = None
-
- if hasattr(original_fn, '__wf'):
- original_fn = getattr(original_fn, '__wf')
- (sig, baseArgDict, fc) = members[fnname]
-
- if original_fn_signature is not None:
- err = None
- if len(sig.parameters) > len(original_fn_signature.parameters):
- err = f"The signature of '{fnname}' does not match. Missing required parameters."
- # Special check for self
- if 'self' in sig.parameters and 'self' not in original_fn_signature.parameters:
- err = f"The signature of '{fnname}' does not match. Missing required parameter self."
- if err is not None:
- raise ctx.wrap(UntypyTypeError(
- expected=protocolchecker.describe(),
- given=originalValue
- )).with_header(
- protoMismatchErrorMessage(original.__name__, protocolchecker.proto)
- ).with_note(err)
- paramDict = dict(zip(original_fn_signature.parameters, sig.parameters))
- else:
- paramDict = {}
- for k in sig.parameters:
- paramDict[k] = k
-
- list_of_attr[fnname] = ProtocolWrappedFunction(original_fn,
- sig,
- baseArgDict,
- protocolchecker,
- paramDict,
- fc).build()
-
- name = f"WyppTypeCheck({original.__name__}, {protocolchecker.describe()})"
- return wrapper.wrap(originalValue, list_of_attr, name, extra={'ctx': ctx}, simple=simple)
-
-def _getWrappedName(fn):
- if hasattr(fn, '__name__'):
- return fn.__name__
- elif hasattr(fn, 'fget'):
- return fn.fget.__name__
- else:
- raise ValueError(f'Cannot determine name of {fn}')
-
-def _isProperty(fn):
- return isinstance(fn, property)
-
-class ProtocolWrappedFunction(WrappedFunction):
-
- def __init__(self,
- inner: Union[Callable, WrappedFunction],
- signature: inspect.Signature,
- checker: Dict[str, TypeChecker], # maps argument names from the protocol to checkers
- protocol: ProtocolChecker,
- baseArgs: Dict[str, str], # maps arguments names of the implementing class to argument names of the protocol
- fc: FunctionCondition):
- self.inner = inner
- self.signature = signature
- self.parameters = list(self.signature.parameters.values())
- self.checker = checker
- self.baseArgs = baseArgs
- self.protocol = protocol
- self.fc = fc
- self.fast_sig = typedfun.is_fast_sig(self.parameters, self.fc)
-
- def build(self):
- fn = WrappedFunction.find_original(self.inner)
- name = _getWrappedName(fn)
- fn_of_protocol = getattr(self.protocol.proto, name)
- if hasattr(fn_of_protocol, '__wf'):
- fn_of_protocol = getattr(fn_of_protocol, '__wf')
-
- def wrapper(me, *args, **kwargs):
- inner_object = me.__wrapped__
- inner_ctx = me.__extra__['ctx']
-
- caller = sys._getframe(1)
- (args, kwargs, bind1) = self.wrap_arguments(lambda n: ArgumentExecutionContext(fn_of_protocol, caller, n),
- (inner_object, *args), kwargs)
- if isinstance(self.inner, WrappedFunction):
- (args, kwargs, bind2) = self.inner.wrap_arguments(
- lambda n: ProtocolArgumentExecutionContext(self, self.baseArgs[n], n,
- inner_object,
- inner_ctx),
- args, kwargs)
- if _isProperty(fn):
- ret = fn
- else:
- try:
- ret = fn(*args, **kwargs)
- except Exception as e:
- # If an exception occurs here, we find an additional stack frame
- # on top of the traceback. We add the __wrapped__ attribute so that
- # traceback formatting mechanism can remove this additional frame.
- e.__wrapped__ = True
- tb = e.__traceback__
- if tb:
- tb = tb.tb_next
- raise e.with_traceback(tb)
- if isinstance(self.inner, WrappedFunction):
- ret = self.inner.wrap_return(args, kwargs, ret, bind2,
- ProtocolReturnExecutionContext(self, ResponsibilityType.IN, inner_object, inner_ctx))
- return self.wrap_return(args, kwargs, ret, bind1,
- ProtocolReturnExecutionContext(self, ResponsibilityType.OUT, inner_object, inner_ctx))
-
- async def async_wrapper(*args, **kwargs):
- raise AssertionError("Not correctly implemented see wrapper")
-
- if inspect.iscoroutine(self.inner):
- w = async_wrapper
- else:
- w = wrapper
-
- setattr(w, '__wrapped__', fn)
- setattr(w, '__name__', name)
- setattr(w, '__signature__', self.signature)
- setattr(w, '__wf', self)
- return w
-
- def get_original(self):
- return self.inner
-
- def wrap_arguments(self, ctxprv: WrappedFunctionContextProvider, args, kwargs):
- return typedfun.wrap_arguments(self.parameters, self.checker, self.signature,
- self.fc, self.fast_sig, ctxprv, args, kwargs, expectSelf=True)
-
- def wrap_return(self, args, kwargs, ret, bindings, ctx: ExecutionContext):
- fc_pair = None
- if self.fc is not None:
- fc_pair = (self.fc, bindings)
- return typedfun.wrap_return(self.checker['return'], args, kwargs, ret, fc_pair, ctx)
-
- def describe(self) -> str:
- fn = WrappedFunction.find_original(self.inner)
- return f"{fn.__name__}" + str(self.signature)
-
- def checker_for(self, name: str) -> TypeChecker:
- if name == 'return':
- k = 'return'
- else:
- k = self.baseArgs[name]
- return self.checker[k]
-
- def declared(self) -> Location:
- fn = WrappedFunction.find_original(self.inner)
- return WrappedFunction.find_location(getattr(self.protocol.proto, fn.__name__))
-
-
-class ProtocolReturnExecutionContext(ExecutionContext):
- def __init__(self, wf: ProtocolWrappedFunction, invert: ResponsibilityType, me: Any, ctx: ExecutionContext):
- self.wf = wf
- self.invert = invert
- self.me = me
- self.ctx = ctx
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- err = ReturnExecutionContext(self.wf).wrap(err)
-
- if err.responsibility_type is self.invert:
- return err
- responsable = WrappedFunction.find_location(self.wf)
- (decl, ind) = err.next_type_and_indicator()
- err = err.with_inverted_responsibility_type()
- err = err.with_frame(Frame(
- decl,
- ind,
- declared=self.wf.declared(),
- responsable=responsable
- ))
-
- inner = self.wf.inner
- if isinstance(inner, WrappedFunction):
- err = err.with_note(
- f"The return value of method '{WrappedFunction.find_original(self.wf).__name__}' does violate the {self.wf.protocol.protocol_type()} '{self.wf.protocol.proto.__name__}'.")
- err = err.with_note(
- f"The annotation '{inner.checker_for('return').describe()}' is incompatible with the {self.wf.protocol.protocol_type()}'s annotation '{self.wf.checker_for('return').describe()}'\nwhen checking against the following value:")
-
- previous_chain = UntypyTypeError(
- self.me,
- f"{self.wf.protocol.protoname()}"
- ).with_header(
- protoMismatchErrorMessage(type(self.me).__name__, self.wf.protocol)
- )
-
- previous_chain = self.ctx.wrap(previous_chain)
- if isInternalProtocol(self.wf.protocol):
- return previous_chain
- else:
- return err.with_previous_chain(previous_chain)
-
-
-
-class ProtocolArgumentExecutionContext(ExecutionContext):
- def __init__(self, wf: ProtocolWrappedFunction,
- base_arg: str, this_arg: str, me: Any, ctx: ExecutionContext):
- self.wf = wf
- self.base_arg = base_arg
- self.this_arg = this_arg
- self.me = me
- self.ctx = ctx
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- protocol = self.wf.protocol.proto
-
- (original_expected, _ind) = err.next_type_and_indicator()
- err = ArgumentExecutionContext(self.wf, None, self.base_arg).wrap(err)
-
- responsable = WrappedFunction.find_location(self.wf)
-
- (decl, ind) = err.next_type_and_indicator()
- err = err.with_frame(Frame(
- decl,
- ind,
- declared=self.wf.declared(),
- responsable=responsable
- ))
-
- base_expected = self.wf.checker_for(self.this_arg).describe()
- if self.base_arg == self.this_arg:
- err = err.with_note(
- f"Argument {self.this_arg} of method {WrappedFunction.find_original(self.wf).__name__} "
- f"violates the type declared by the "
- f"{self.wf.protocol.protocol_type()} {self.wf.protocol.proto.__name__}.")
- else:
- err = err.with_note(
- f"Argument {self.this_arg} of method {WrappedFunction.find_original(self.wf).__name__} "
- f"violates the type declared for {self.base_arg} in "
- f"{self.wf.protocol.protocol_type()} {self.wf.protocol.proto.__name__}.")
- err = err.with_note(
- f"Annotation {original_expected} is incompatible with the "
- f"{self.wf.protocol.protocol_type()}'s annotation "
- f"{base_expected}.")
-
- previous_chain = UntypyTypeError(
- self.me,
- f"{self.wf.protocol.protoname()}"
- ).with_header(
- protoMismatchErrorMessage(type(self.me).__name__, self.wf.protocol)
- )
-
- # Protocols can either be declared explicit or implicit.
- if protocol in type(self.me).__mro__:
- # If it is declared explicit (e.g. Inheritance) the
- # declaration of the inheritance has to be blamed.
- previous_chain = previous_chain.with_frame(Frame(
- # /- Could also be `self.wf.describe()`, which would put "right" signature as "context:".
- # v But this info may be better suited in the note.
- *previous_chain.next_type_and_indicator(),
- declared=Location.from_code(type(self.me)),
- responsable=Location.from_code(type(self.me))
- )).with_frame(Frame(
- *previous_chain.next_type_and_indicator(),
- declared=self.wf.declared(),
- responsable=responsable
- ))
- else:
- # Else: We need to explain how this protocol was declared.
- previous_chain = self.ctx.wrap(previous_chain)
-
- if isInternalProtocol(self.wf.protocol):
- return previous_chain
- else:
- return err.with_previous_chain(previous_chain)
-
-
-class SimpleInstanceOfChecker(TypeChecker):
- def __init__(self, annotation: type, ctx: CreationContext):
- self.annotation = annotation
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if isinstance(arg, self.annotation):
- return arg
- else:
- raise ctx.wrap(UntypyTypeError(arg, self.describe()))
-
- def describe(self) -> str:
- return self.annotation.__name__
-
- def base_type(self) -> Any:
- return [self.annotation]
diff --git a/python/deps/untypy/untypy/impl/simple.py b/python/deps/untypy/untypy/impl/simple.py
deleted file mode 100644
index 4cb39da7..00000000
--- a/python/deps/untypy/untypy/impl/simple.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import abc
-from typing import Any, Optional, Callable
-
-from untypy.error import UntypyTypeError
-from untypy.impl.protocol import ProtocolChecker
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext, ExecutionContext
-
-
-class SimpleFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- ta = type(annotation)
- if ta is type or ta is abc.ABCMeta or annotation is Callable:
- return SimpleChecker(annotation, ctx)
- else:
- return None
-
-
-class ParentProtocolChecker(ProtocolChecker):
- def protocol_type(self) -> str:
- return "parent"
-
-def simpleTypeCompat(x: Any, ty: type):
- xTy = type(x)
- return xTy is ty or (ty is float and xTy is int) or \
- (ty is complex and (xTy is int or xTy is float))
-
-class SimpleChecker(TypeChecker):
- annotation: type
- always_wrap: bool = False
- parent_checker: Optional[Callable[[Any, ExecutionContext], Any]]
-
- def __init__(self, annotation: type, ctx: CreationContext):
- self.annotation = annotation
- self.always_wrap = False
-
- # use protocol like wrapping only if there are some signatures
- if ctx.should_be_inheritance_checked(annotation):
- if hasattr(annotation, '__patched'):
- p = ParentProtocolChecker(annotation, ctx)
- self.parent_checker = p.check_and_wrap
- else:
- self.parent_checker = None
-
-
- def may_be_wrapped(self) -> bool:
- return True
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if simpleTypeCompat(arg, self.annotation) and not self.always_wrap:
- return arg
- if isinstance(arg, self.annotation):
- if self.parent_checker is None:
- return arg
- else:
- return self.parent_checker(arg, ctx)
- else:
- raise ctx.wrap(UntypyTypeError(arg, self.describe()))
-
- def describe(self) -> str:
- a = self.annotation
- if hasattr(a, '__name__'):
- return a.__name__
- else:
- return str(a)
-
- def base_type(self) -> Any:
- return [self.annotation]
diff --git a/python/deps/untypy/untypy/impl/string_forward_refs.py b/python/deps/untypy/untypy/impl/string_forward_refs.py
deleted file mode 100644
index 85fa8a21..00000000
--- a/python/deps/untypy/untypy/impl/string_forward_refs.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from typing import Any, Optional
-
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext
-from untypy.util.typehints import get_type_hints
-
-
-class StringForwardRefFactory(TypeCheckerFactory):
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if type(annotation) is str:
- eval_args = [annotation, globals()]
- local = ctx.eval_context()
- if local is not None:
- eval_args.append(local)
-
- def resolver(eval_args):
- return eval(*eval_args)
-
- annotation = get_type_hints(eval_args, ctx, resolver=resolver)
- return ctx.find_checker(annotation)
diff --git a/python/deps/untypy/untypy/impl/tuple.py b/python/deps/untypy/untypy/impl/tuple.py
deleted file mode 100644
index dd5836bc..00000000
--- a/python/deps/untypy/untypy/impl/tuple.py
+++ /dev/null
@@ -1,103 +0,0 @@
-from typing import Any, Optional, Tuple
-
-from untypy.error import UntypyTypeError, Frame
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext, ExecutionContext
-from untypy.util import CompoundTypeExecutionContext
-
-TupleType = type(Tuple[str, int])
-TupleTypeB = type(tuple[str, int])
-
-
-class TupleFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- if (type(annotation) is TupleType or type(annotation) is TupleTypeB) and annotation.__origin__ == tuple:
- args = annotation.__args__
- if len(args) == 2 and args[1] == Ellipsis:
- checker = ctx.find_checker(args[0])
- if checker is None:
- return None
- else:
- return VariadicTupleChecker(checker)
- inner = []
- for arg in args:
- checker = ctx.find_checker(arg)
- if checker is None:
- return None
- else:
- inner.append(checker)
-
- return TupleChecker(inner)
- else:
- return None
-
-class VariadicTupleChecker(TypeChecker):
- inner: TypeChecker
-
- def __init__(self, inner: TypeChecker):
- self.inner = inner
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if hasattr(arg, '__wrapped__'):
- # no double wrapping
- arg = getattr(arg, '__wrapped__')
- if not type(arg) is tuple:
- raise ctx.wrap(UntypyTypeError(arg, self.describe()))
- out = []
- for elm in arg:
- out.append(self.inner.check_and_wrap(elm, VariadicTupleExecutionContext(ctx)))
- return tuple(out)
-
- def base_type(self) -> Any:
- return [tuple]
-
- def describe(self) -> str:
- desc = self.inner.describe()
- return f"tuple[{desc}, ...]"
-
-
-class VariadicTupleExecutionContext(ExecutionContext):
- upper: ExecutionContext
-
- def __init__(self, upper: ExecutionContext):
- self.upper = upper
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- next_type, indicator = err.next_type_and_indicator()
- err = err.with_frame(Frame(
- f"tuple[{next_type}, ...]",
- (" " * len("tuple[") + indicator),
- None,
- None
- ))
- return self.upper.wrap(err)
-
-class TupleChecker(TypeChecker):
- inner: list[TypeChecker]
-
- def __init__(self, inner: list[TypeChecker]):
- self.inner = inner
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- if not type(arg) is tuple or len(arg) != len(self.inner):
- raise ctx.wrap(UntypyTypeError(arg, self.describe()))
-
- out = []
- idx = 0
- for elm, checker in zip(arg, self.inner):
- out.append(checker.check_and_wrap(elm, TupleExecutionContext(ctx, self.inner, idx)))
- idx += 1
-
- return tuple(out)
-
- def base_type(self) -> Any:
- return [tuple]
-
- def describe(self) -> str:
- desc = lambda s: s.describe()
- return f"tuple[{', '.join(map(desc, self.inner))}]"
-
-
-class TupleExecutionContext(CompoundTypeExecutionContext):
- def name(self):
- return "tuple"
diff --git a/python/deps/untypy/untypy/impl/union.py b/python/deps/untypy/untypy/impl/union.py
deleted file mode 100644
index b6c973e1..00000000
--- a/python/deps/untypy/untypy/impl/union.py
+++ /dev/null
@@ -1,80 +0,0 @@
-from typing import Any, Optional, Union
-
-from untypy.error import UntypyTypeError, UntypyAttributeError
-from untypy.interfaces import TypeChecker, TypeCheckerFactory, CreationContext, ExecutionContext
-from untypy.util import CompoundTypeExecutionContext
-
-import sys
-pythonVersion = sys.version_info
-
-unionTypes = [type(Union[int, str])]
-if pythonVersion >= (3, 10):
- unionTypes.append(type(int | str))
-
-class UnionFactory(TypeCheckerFactory):
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- t = type(annotation)
- if t in unionTypes:
- inner = []
- for arg in annotation.__args__:
- checker = ctx.find_checker(arg)
- if checker is None:
- return None
- else:
- inner.append(checker)
-
- return UnionChecker(inner, ctx)
- else:
- return None
-
-
-class UnionChecker(TypeChecker):
- inner: list[TypeChecker]
-
- def __init__(self, inner: list[TypeChecker], ctx: CreationContext):
- # especially Protocols must be checked in a specific order.
- self.inner = sorted(inner, key=lambda t: -t.base_type_priority())
- dups = dict()
- for checker in inner:
- for base_type in checker.base_type():
- if base_type in dups:
- raise ctx.wrap(UntypyAttributeError(f"{checker.describe()} is in conflict with "
- f"{dups[base_type].describe()} "
- f"in {self.describe()}. "
- f"Types must be distinguishable inside a Union."
- f"\nNote: Only one protocol is allowed inside a Union. "
- f"Classes could implement multiple Protocols by accident."
- f"\nNote: Multiple callables or generics inside a Union are also unsupported."))
- else:
- dups[base_type] = checker
-
- def check_and_wrap(self, arg: Any, upper: ExecutionContext) -> Any:
- idx = 0
- for checker in self.inner:
- ctx = UnionExecutionContext(upper, self.inner, idx)
- idx += 1
- try:
- return checker.check_and_wrap(arg, ctx)
- except UntypyTypeError as _e:
- pass
-
- raise upper.wrap(UntypyTypeError(
- arg,
- self.describe()
- ))
-
- def describe(self) -> str:
- desc = lambda s: s.describe()
- return f"Union[{', '.join(map(desc, self.inner))}]"
-
- def base_type(self) -> list[Any]:
- out = []
- for checker in self.inner:
- out.extend(checker.base_type())
- return out
-
-
-class UnionExecutionContext(CompoundTypeExecutionContext):
- def name(self):
- return "Union"
diff --git a/python/deps/untypy/untypy/interfaces.py b/python/deps/untypy/untypy/interfaces.py
deleted file mode 100644
index d6ad5e17..00000000
--- a/python/deps/untypy/untypy/interfaces.py
+++ /dev/null
@@ -1,105 +0,0 @@
-from __future__ import annotations
-
-import inspect
-from typing import Optional, Any, Callable, TypeVar, List, Tuple
-
-from untypy.error import UntypyTypeError, Location, UntypyAttributeError
-
-
-class CreationContext:
- def find_checker(self, annotation: Any) -> Optional[TypeChecker]:
- raise NotImplementedError
-
- def declared_location(self) -> Location:
- raise NotImplementedError
-
- def wrap(self, err: UntypyAttributeError) -> UntypyAttributeError:
- raise NotImplementedError
-
- def resolve_typevar(self, var: TypeVar) -> Tuple[bool, Any]:
- raise NotImplementedError
-
- def all_typevars(self) -> List[TypeVar]:
- raise NotImplementedError
-
- def with_typevars(self, typevars: dict[TypeVar, Any]) -> CreationContext:
- raise NotImplementedError
-
- def should_be_inheritance_checked(self, annotation: type) -> bool:
- raise NotImplementedError
-
- def eval_context(self):
- raise NotImplementedError
-
-
-class ExecutionContext:
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- raise NotImplementedError
-
-
-class TypeChecker:
-
- def describe(self) -> str:
- raise NotImplementedError
-
- def may_be_wrapped(self) -> bool:
- return False
-
- def base_type(self) -> list[Any]:
- raise NotImplementedError(f'base_type({self})')
-
- # Higher Priority => checked first inside Union.
- def base_type_priority(self) -> int:
- return 0
-
- def check_and_wrap(self, arg: Any, ctx: ExecutionContext) -> Any:
- raise NotImplementedError
-
-
-class TypeCheckerFactory:
-
- def create_from(self, annotation: Any, ctx: CreationContext) -> Optional[TypeChecker]:
- raise NotImplementedError
-
-
-WrappedFunctionContextProvider = Callable[[str], ExecutionContext]
-
-
-class WrappedFunction:
- def get_original(self):
- raise NotImplementedError
-
- def wrap_arguments(self, ctxprv: WrappedFunctionContextProvider, args, kwargs):
- raise NotImplementedError
-
- def wrap_return(self, ret, bindings, ctx: ExecutionContext):
- raise NotImplementedError
-
- def describe(self) -> str:
- raise NotImplementedError
-
- def checker_for(self, name: str) -> TypeChecker:
- raise NotImplementedError
-
- @staticmethod
- def find_original(fn):
- if hasattr(fn, '__original'):
- return WrappedFunction.find_original(getattr(fn, '__original'))
- elif isinstance(fn, WrappedFunction):
- return WrappedFunction.find_original(fn.get_original())
- elif hasattr(fn, '__wf'):
- return WrappedFunction.find_original(getattr(fn, '__wf').get_original())
- else:
- return fn
-
- @staticmethod
- def find_location(fn) -> Optional[Location]:
- fn = WrappedFunction.find_original(fn)
- try:
- return Location(
- file=inspect.getfile(fn),
- line_no=inspect.getsourcelines(fn)[1],
- line_span=len(inspect.getsourcelines(fn)[0]),
- )
- except: # Failes on builtins
- return None
diff --git a/python/deps/untypy/untypy/patching/__init__.py b/python/deps/untypy/untypy/patching/__init__.py
deleted file mode 100644
index 35a1e6c8..00000000
--- a/python/deps/untypy/untypy/patching/__init__.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import inspect
-from collections import namedtuple
-from types import FunctionType
-from typing import Callable
-
-from untypy.error import Location
-from untypy.impl import DefaultCreationContext
-from untypy.interfaces import WrappedFunction
-from untypy.util.typedfunction import TypedFunctionBuilder
-
-Config = namedtuple('PatchConfig', ['verbose', 'checkedprefixes'])
-DefaultConfig = Config(verbose=False, checkedprefixes=[""])
-not_patching = ['__class__']
-
-GlobalPatchedList = set()
-
-
-def patch_class(clas: type, cfg: Config):
- if clas in GlobalPatchedList:
- return clas
- GlobalPatchedList.add(clas)
-
- try:
- ctx = DefaultCreationContext(
- typevars=dict(),
- declared_location=Location(
- file=inspect.getfile(clas),
- line_no=inspect.getsourcelines(clas)[1],
- line_span=len(inspect.getsourcelines(clas)[0]),
- ), checkedpkgprefixes=cfg.checkedprefixes)
- except (TypeError, OSError) as e: # Built in types
- ctx = DefaultCreationContext(
- typevars=dict(),
- declared_location=Location(
- file="",
- line_no=0,
- line_span=1
- ), checkedpkgprefixes=cfg.checkedprefixes,
- )
-
- setattr(clas, '__patched', True)
-
-def wrap_function(fn: FunctionType, cfg: Config) -> Callable:
- if len(inspect.getfullargspec(fn).annotations) > 0:
- if cfg.verbose:
- print(f"Patching Function: {fn.__name__}")
- return TypedFunctionBuilder(fn, DefaultCreationContext(
- typevars=dict(),
- declared_location=WrappedFunction.find_location(fn),
- checkedpkgprefixes=cfg.checkedprefixes, eval_context=fn.__globals__)).build()
- else:
- return fn
-
-
-def wrap_class(a: type, cfg: Config) -> Callable:
- return WrappedType(a, DefaultCreationContext(
- typevars=dict(),
- declared_location=Location.from_code(a),
- checkedpkgprefixes=cfg.checkedprefixes))
diff --git a/python/deps/untypy/untypy/patching/ast_transformer.py b/python/deps/untypy/untypy/patching/ast_transformer.py
deleted file mode 100644
index f48ee49b..00000000
--- a/python/deps/untypy/untypy/patching/ast_transformer.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import ast
-from typing import Callable, List, Optional, Any
-
-
-class UntypyAstTransformer(ast.NodeTransformer):
- def visit_Module(self, node: ast.Module):
- for i, child in enumerate(node.body):
- if isinstance(child, ast.ImportFrom) and child.module == '__future__':
- continue # from __future__ import ...
- elif isinstance(child, ast.Expr) and isinstance(child.value, ast.Constant):
- continue # module docstring
- elif _is_untypy_import(node):
- break
- else:
- node.body.insert(i, ast.Import(names=[ast.alias('untypy', None)]))
- break
-
- self.generic_visit(node)
- return node
-
- def visit_FunctionDef(self, node: ast.FunctionDef):
- node.decorator_list.insert(0, ast.Attribute(ast.Name("untypy", ast.Load()), "patch", ast.Load()))
- self.generic_visit(node)
- return node
-
- def visit_ClassDef(self, node: ast.FunctionDef):
- node.decorator_list.insert(0, ast.Attribute(ast.Name("untypy", ast.Load()), "patch", ast.Load()))
- self.generic_visit(node)
- return node
-
- def visit_Expr(self, node: ast.Expr):
- val = node.value
- if _is_untypy_patch_call(val):
- return ast.Expr(ast.Constant("# untypy.enable()"))
- else:
- self.generic_visit(node)
- return node
-
-
-class UntypyAstImportTransformer(ast.NodeTransformer):
- def __init__(self, predicate: Callable[[str], bool], module_path: List[str]):
- self.predicate = predicate
- self.module_path = module_path
-
- def resolve_relative_name(self, name: str, levels: Optional[int]) -> str:
- if levels is None:
- return name
- elif levels == 1:
- return '.'.join(self.module_path + list(name.split('.')))
- else:
- prefix = self.module_path[:-(levels - 1)]
- return '.'.join(prefix + list(name.split('.')))
-
- def visit_Module(self, node: ast.Module):
- inserts = []
- need_insert_utypy_imp = True
- need_insert_utypy_idx = 0
- for i, child in enumerate(node.body):
- if _is_untypy_import(node):
- need_insert_utypy_imp = False
- elif isinstance(child, ast.ImportFrom) and child.module == '__future__':
- need_insert_utypy_idx += 1
- elif isinstance(child, ast.ImportFrom) and self.predicate(
- self.resolve_relative_name(child.module, child.level)):
- for alias in child.names:
- if alias.asname is None:
- destname = alias.name
- else:
- destname = alias.asname
- # $destname = untypy.wrap_import($destname)
- call = ast.Call(ast.Attribute(ast.Name("untypy", ast.Load()), "wrap_import", ast.Load()),
- [ast.Name(destname, ast.Load())], [])
- expr = ast.Assign([ast.Name(destname, ast.Store())], call)
- node.body.insert(i + 1, expr)
-
- elif isinstance(child, ast.Import):
- for alias in child.names:
- if self.predicate(alias.name):
- if alias.asname is None:
- destname = alias.name
- else:
- destname = alias.asname
-
- # $destname = untypy.wrap_import($destname)
- # python ast weirdness
- def create_attribute(levels: List[str], op=ast.Store()):
- if len(levels) == 1:
- return ast.Name(levels[0], ctx=op)
- else:
- return ast.Attribute(create_attribute(levels[:-1], ast.Load()), levels[-1], ctx=op)
-
- destpath = destname.split(".")
- call = ast.Call(ast.Attribute(ast.Name("untypy", ast.Load()), "wrap_import", ast.Load()),
- [create_attribute(destpath, ast.Load())], [])
- expr = ast.Assign([create_attribute(destpath)], call)
- # inserts.append((i + 1, expr)) # insert after
- node.body.insert(i + 1, expr)
-
- # TODO BUG: Multiple imports index mix up
- # TODO BUG:
- for (idx, expr) in inserts:
- node.body.insert(idx, expr)
-
- if need_insert_utypy_imp:
- node.body.insert(need_insert_utypy_idx, ast.Import(names=[ast.alias('untypy', None)]))
-
- self.generic_visit(node)
- return node
-
- def visit_Expr(self, node: ast.Expr):
- val = node.value
- if _is_untypy_patch_call(val):
- return ast.Expr(ast.Constant("# untypy.enable()"))
- else:
- self.generic_visit(node)
- return node
-
-def _is_untypy_patch_call(node):
- if isinstance(node, ast.Expr):
- node = node.value
-
- return (isinstance(node, ast.Call)
- and isinstance(node.func, ast.Attribute)
- and isinstance(node.func.value, ast.Name)
- and node.func.value.id == 'untypy'
- and (node.func.attr == 'enable' or node.func.attr == 'enable_on_imports'))
-
-
-def _is_untypy_import(node):
- return (isinstance(node, ast.Import)
- and len(node.names) == 1
- and isinstance(node.names[0], ast.alias)
- and node.names[0].name == 'untypy')
-
-
-def did_no_code_run_before_untypy_enable(node: ast.Module) -> bool:
- for child in node.body:
- if isinstance(child, ast.ImportFrom) and child.module == '__future__':
- continue # from __future__ import ...
- elif _is_untypy_import(child):
- continue
- elif isinstance(child, ast.Expr) and isinstance(child.value, ast.Constant):
- continue # module docstring
- elif _is_untypy_patch_call(child):
- return True
- else:
- break
-
- # untypy.enable() was not first. It is still okay if this call does not exist.
- class DidNoCodeRunVisitor(ast.NodeVisitor):
- def __init__(self):
- self.has_untypycall = False
-
- def visit_Call(self, node):
- if _is_untypy_patch_call(node):
- self.has_untypycall = True
- self.generic_visit(node)
- return node
-
- visitor = DidNoCodeRunVisitor()
- visitor.visit(node)
- if visitor.has_untypycall:
- return False
- else:
- return True
diff --git a/python/deps/untypy/untypy/patching/import_hook.py b/python/deps/untypy/untypy/patching/import_hook.py
deleted file mode 100644
index ac44cb02..00000000
--- a/python/deps/untypy/untypy/patching/import_hook.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import ast
-import importlib
-from collections.abc import Callable
-from importlib.abc import MetaPathFinder
-from importlib.machinery import SourceFileLoader
-from importlib.util import decode_source
-
-
-def install_import_hook(should_patch_predicate: Callable[[str, str], bool],
- transformer: Callable[[str], ast.NodeTransformer]):
- import sys
-
- already_patched = next((f for f in sys.meta_path if isinstance(f, UntypyFinder)), None)
- if already_patched is not None:
- return
-
- original_finder = next(f for f in sys.meta_path if hasattr(f, '__name__') and f.__name__ == 'PathFinder' and hasattr(f, 'find_spec'))
- sys.meta_path.insert(0, UntypyFinder(original_finder, should_patch_predicate, transformer))
-
-
-class UntypyFinder(MetaPathFinder):
-
- def __init__(self, inner_finder: MetaPathFinder, should_patch_predicate: Callable[[str, str], bool],
- transformer: Callable[[str], ast.NodeTransformer]):
- self.inner_finder = inner_finder
- self.should_patch_predicate = should_patch_predicate
- self.transformer = transformer
-
- def find_spec(self, fullname, path=None, target=None):
- if not self.should_instrument(fullname):
- return None
-
- inner_spec = self.inner_finder.find_spec(fullname, path, target)
- if inner_spec is not None and isinstance(inner_spec.loader, SourceFileLoader):
- inner_spec.loader = UntypyLoader(inner_spec.loader.name, inner_spec.loader.path, self.transformer)
- return inner_spec
-
- def should_instrument(self, module_name: str) -> bool:
- return self.should_patch_predicate(module_name)
-
-
-class UntypyLoader(SourceFileLoader):
-
- def __init__(self, fullname, path, transformer: Callable[[str, str], ast.NodeTransformer]):
- super().__init__(fullname, path)
- self.transformer = transformer
-
- def source_to_code(self, data, path, *, _optimize=-1):
- source = decode_source(data)
- tree = compile(source, path, 'exec', ast.PyCF_ONLY_AST,
- dont_inherit=True, optimize=_optimize)
- self.transformer(self.name.split('.'), self.path).visit(tree)
- ast.fix_missing_locations(tree)
- return compile(tree, path, 'exec', dont_inherit=True, optimize=_optimize)
-
- def exec_module(self, module) -> None:
- # cache_from_source has to be patched to prevent load from cache
- # this enables patching of AST
- # See https://github.com/agronholm/typeguard/blob/89c1478bd33bcf9a7cccc2c962ebeaa034e51908/src/typeguard/importhook.py#L12
- original = getattr(importlib._bootstrap_external, 'cache_from_source')
- try:
- setattr(importlib._bootstrap_external, 'cache_from_source', lambda *args: None)
- return super().exec_module(module)
- finally:
- setattr(importlib._bootstrap_external, 'cache_from_source', original)
diff --git a/python/deps/untypy/untypy/patching/standalone_checker.py b/python/deps/untypy/untypy/patching/standalone_checker.py
deleted file mode 100644
index e31428c2..00000000
--- a/python/deps/untypy/untypy/patching/standalone_checker.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import sys
-from typing import Any, Callable
-
-from untypy.error import Location, UntypyAttributeError, UntypyTypeError, Frame, UntypyNameError
-from untypy.impl import DefaultCreationContext
-from untypy.interfaces import ExecutionContext
-
-
-class StandaloneChecker:
- def __init__(self, annotation: Callable[[], Any], declared: Any, cfg, ctx=None):
- self._checker = None
- self.annotation = annotation
- self.declared = declared
- self.cfg = cfg
- self.ctx = ctx
-
- def get_checker(self):
- if self._checker:
- return self._checker
-
- ctx = DefaultCreationContext(
- typevars=dict(),
- declared_location=Location.from_code(self.declared),
- checkedpkgprefixes=self.cfg.checkedprefixes)
- try:
- annotation = self.annotation()
- except NameError as ne:
- raise ctx.wrap(UntypyNameError(
- f"{ne}.\nType annotation could not be resolved."
- ))
-
- checker = ctx.find_checker(annotation)
- if checker is None:
- raise ctx.wrap(UntypyAttributeError(f"\n\tUnsupported type annotation: {self.annotation}\n"))
- self._checker = checker
- return checker
-
- def __call__(self, val):
- frame = sys._getframe(2)
- checker = self.get_checker()
- ctx = self.ctx or StandaloneCheckerContext(frame, self.declared)
- return checker.check_and_wrap(val, ctx)
-
- def __repr__(self):
- return f""
-
-
-class StandaloneCheckerContext(ExecutionContext):
- def __init__(self, caller, declared):
- self.caller = caller
- self.declared = declared
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- t, i = err.next_type_and_indicator()
- return err.with_frame(Frame(
- t, i,
- responsable=Location.from_stack(self.caller),
- declared=Location.from_code(self.declared)
- ))
diff --git a/python/deps/untypy/untypy/util/__init__.py b/python/deps/untypy/untypy/util/__init__.py
deleted file mode 100644
index b9e3a845..00000000
--- a/python/deps/untypy/untypy/util/__init__.py
+++ /dev/null
@@ -1,260 +0,0 @@
-import inspect
-import types
-from typing import Optional, Union, List
-
-from untypy.error import UntypyTypeError, Frame, Location
-from untypy.interfaces import ExecutionContext, TypeChecker, WrappedFunction
-from untypy.util.display import IndicatorStr
-from untypy.util.return_traces import get_last_return
-
-
-class ReplaceTypeExecutionContext(ExecutionContext):
-
- def __init__(self, upper: Optional[ExecutionContext], name: str):
- self.upper = upper
- self.name = name
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- err = err.with_frame(Frame(
- self.name,
- None, None, None
- ))
-
- if self.upper is not None:
- err = self.upper.wrap(err)
- return err
-
-
-class CompoundTypeExecutionContext(ExecutionContext):
- upper: ExecutionContext
- checkers: list[TypeChecker]
- idx: int
-
- def __init__(self, upper: ExecutionContext, checkers: list[TypeChecker], idx: int):
- self.upper = upper
- self.checkers = checkers
- self.idx = idx
-
- def declared(self) -> Optional[Location]:
- return None
-
- def responsable(self) -> Optional[Location]:
- return None
-
- def name(self) -> str:
- raise NotImplementedError
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- type_declared = self.name() + "["
- indicator = " " * len(type_declared)
-
- for i, checker in enumerate(self.checkers):
- if i == self.idx:
- next_type, next_indicator = err.next_type_and_indicator()
- type_declared += next_type
- indicator += next_indicator
- else:
- type_declared += checker.describe()
- indicator += " " * len(checker.describe())
-
- if i != len(self.checkers) - 1: # not last element
- type_declared += ", "
- indicator += " "
-
- type_declared += "]"
-
- err = err.with_frame(Frame(
- type_declared,
- indicator,
- declared=self.declared(),
- responsable=self.responsable(),
- ))
-
- return self.upper.wrap(err)
-
-
-class NoResponsabilityWrapper(ExecutionContext):
- upper: ExecutionContext
-
- def __init__(self, upper: ExecutionContext):
- self.upper = upper
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- full = self.upper.wrap(err)
-
- # now remove responsability in frames:
- frames_to_add = []
- for frame in full.frames:
- if frame not in err.frames:
- frame.responsable = None
- frames_to_add.append(frame)
-
- for frame in frames_to_add:
- err = err.with_frame(frame)
-
- for note in full.notes:
- err = err.with_note(note)
-
- if full.previous_chain is not None:
- err = err.with_previous_chain(full.previous_chain)
-
- return err
-
-
-class ReturnExecutionContext(ExecutionContext):
- fn: WrappedFunction
-
- def __init__(self, fn: WrappedFunction):
- self.reti_loc = get_last_return()
- self.fn = fn
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- (next_ty, indicator) = err.next_type_and_indicator()
- return_id = IndicatorStr(next_ty, indicator)
-
- original = WrappedFunction.find_original(self.fn)
-
- try:
- signature = inspect.signature(original) # TODO:!!! FIX BUILTINS
- front_sig = []
- for name in signature.parameters:
- front_sig.append(f"{name}: {self.fn.checker_for(name).describe()}")
- front_sig = f"{format_name(original)}(" + (", ".join(front_sig)) + ") -> "
- return_id = IndicatorStr(front_sig) + return_id
- except:
- return_id = IndicatorStr("???")
-
- declared = WrappedFunction.find_location(self.fn)
- responsable = declared
-
- if responsable is not None:
- if err.expected is not None and err.given is None:
- # Missing Return-Value?
- err = err.with_note("Did you miss a return statement?")
- last_line = responsable.line_no + responsable.line_span - 1
- responsable = responsable.narrow_in_span((responsable.file, last_line))
- else:
- responsable = responsable.narrow_in_span(self.reti_loc)
-
- return err.with_frame(Frame(
- return_id.ty,
- return_id.indicator,
- declared=declared,
- responsable=responsable,
- ))
-
-def format_name(orig):
- n = orig.__name__
- if inspect.isclass(orig):
- k = 'class'
- if hasattr(orig, '__kind'):
- k = getattr(orig, '__kind')
- if k:
- return f"{k} constructor {n}"
- return n
-
-
-class ArgumentExecutionContext(ExecutionContext):
- n: WrappedFunction
- stack: inspect.FrameInfo
- argument_name: str
-
- def __init__(self,
- fn: Union[WrappedFunction, types.FunctionType],
- stack: Optional[inspect.FrameInfo],
- argument_name: str,
- declared: Optional[Location] = None,
- upper: ExecutionContext = None):
- self.fn = fn
- self.stack = stack
- self.argument_name = argument_name
- self.declared = declared
- self.upper = upper
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- (next_ty, indicator) = err.next_type_and_indicator()
- error_id = IndicatorStr(next_ty, indicator)
-
- original = WrappedFunction.find_original(self.fn)
- try:
- signature = inspect.signature(original)
- except ValueError:
- # fails on some built-ins
- signature = inspect.signature(self.fn)
-
- wf = None
- if (hasattr(self.fn, '__wf')):
- wf = getattr(self.fn, '__wf')
- elif isinstance(self.fn, WrappedFunction):
- wf = self.fn
-
- arglist = []
- for name in signature.parameters:
- if name is self.argument_name:
- arglist.append(IndicatorStr(f"{name}: ") + error_id)
- else:
- if wf is not None:
- arglist.append(IndicatorStr(f"{name}: {wf.checker_for(name).describe()}"))
- else:
- arglist.append(IndicatorStr(f"{name}"))
-
- id = IndicatorStr(f"{format_name(original)}(") + IndicatorStr(", ").join(arglist)
-
- if wf is not None:
- id += IndicatorStr(f") -> {wf.checker_for('return').describe()}")
- else:
- id += IndicatorStr(f")")
-
- if self.declared is None:
- declared = WrappedFunction.find_location(self.fn)
- else:
- declared = self.declared
-
- if self.stack is not None:
- responsable = Location.from_stack(self.stack)
- else:
- responsable = None
-
- frame = Frame(
- id.ty,
- id.indicator,
- declared=declared,
- responsable=responsable
- )
- if self.upper:
- err = self.upper.wrap(err)
- return err.with_frame(frame)
-
-
-class GenericExecutionContext(ExecutionContext):
- def __init__(self, *, declared: Union[None, Location, List[Location]] = None,
- responsable: Union[None, Location, List[Location]] = None,
- upper_ctx: Optional[ExecutionContext] = None):
- self.declared = declared
- self.responsable = responsable
- self.upper_ctx = upper_ctx
-
- def wrap(self, err: UntypyTypeError) -> UntypyTypeError:
- declared = []
- if isinstance(self.declared, Location):
- declared.append(self.declared)
- if isinstance(self.declared, list):
- declared.extend(self.declared)
-
- responsable = []
- if isinstance(self.responsable, Location):
- responsable.append(self.responsable)
- if isinstance(self.responsable, list):
- responsable.extend(self.responsable)
-
- while len(declared) < len(responsable): declared.append(None)
- while len(declared) > len(responsable): responsable.append(None)
-
- for (d, r) in zip(declared, responsable):
- (t, i) = err.next_type_and_indicator()
- err = err.with_frame(Frame(t, i, d, r))
-
- if self.upper_ctx is not None:
- return self.upper_ctx.wrap(err)
- else:
- return err
diff --git a/python/deps/untypy/untypy/util/condition.py b/python/deps/untypy/untypy/util/condition.py
deleted file mode 100644
index 7a053740..00000000
--- a/python/deps/untypy/untypy/util/condition.py
+++ /dev/null
@@ -1,130 +0,0 @@
-import inspect
-import re
-from collections.abc import Callable
-from typing import Optional
-
-from untypy.error import UntypyAttributeError, UntypyTypeError, Frame
-from untypy.interfaces import ExecutionContext, WrappedFunction, TypeChecker
-
-WrappedFunctionContextProvider = Callable[[str], ExecutionContext]
-
-
-class FunctionCondition:
-
- def __init__(self):
- self.precondition = []
- self.postcondition = []
- self.func = None
- pass
-
- def prehook(self, boundargs, ctx: WrappedFunctionContextProvider):
- for p in self.precondition:
- bindings = {}
- for name in inspect.signature(p).parameters:
- if name in boundargs.arguments:
- bindings[name] = boundargs.arguments[name]
- else:
- raise UntypyAttributeError(
- f"Did not find argument {name} of precondition in function.",
- locations=[
- WrappedFunction.find_location(p),
- WrappedFunction.find_location(self.func),
- ]
- )
- if not p(**bindings):
- lsource = find_lambdasource(p)
- if lsource is not None:
- expected = f"passing: {lsource}"
- else:
- expected = "passing precondition"
-
- err = UntypyTypeError(
- bindings,
- expected
- )
- err = err.with_note("Failed precondition.")
- err = err.with_frame(Frame(
- expected,
- "",
- declared=WrappedFunction.find_location(p),
- responsable=None,
- ))
- raise ctx(0).wrap(err)
-
- def posthook(self, ret, boundargs, ctx: ExecutionContext, checker: TypeChecker):
- for p in self.postcondition:
- bindings = {}
- for name in inspect.signature(p).parameters:
- if name == "ret":
- bindings["ret"] = ret
- elif name == "checker":
- bindings["checker"] = checker
- elif name == "ctx":
- bindings["ctx"] = ctx
- elif name in boundargs.arguments:
- bindings[name] = boundargs.arguments[name]
- else:
- raise UntypyAttributeError(
- f"Did not find argument {name} of postcondition in function.",
- locations=[
- WrappedFunction.find_location(p),
- WrappedFunction.find_location(self.func),
- ]
- )
- if not p(**bindings):
- lsource = find_lambdasource(p)
- if lsource is not None:
- expected = f"passing: {lsource}"
- else:
- expected = "passing postcondition"
-
- given = ret.__repr__()
- err = UntypyTypeError(
- given,
- expected,
- ).with_note("Failed postcondition").with_frame(Frame(
- expected,
- "",
- declared=WrappedFunction.find_location(p),
- responsable=None,
- ))
- raise ctx.wrap(err)
-
-
-def find_lambdasource(fn) -> Optional[str]:
- """
- tries to retuns body of precondition or postcondition annotation
- """
- try:
- fn = WrappedFunction.find_original(fn)
- source = inspect.getsource(fn).split('\n')
- m = re.match(r'@[a-zA-Z_\.]+\((.*)\)', source[0])
- if m is not None and len(m.groups()) == 1:
- return m.group(1)
- except:
- return None
-
-def _condgetfc(func):
- if hasattr(func, "__fc"):
- return getattr(func, "__fc")
- else:
- fc = FunctionCondition()
- setattr(func, "__fc", fc)
- fc.func = func
- return fc
-
-
-def precondition(cond):
- def decorator(func):
- fc = _condgetfc(func)
- fc.precondition.append(cond)
- return func
- return decorator
-
-
-def postcondition(cond):
- def decorator(func):
- fc = _condgetfc(func)
- fc.postcondition.append(cond)
- return func
- return decorator
diff --git a/python/deps/untypy/untypy/util/debug.py b/python/deps/untypy/untypy/util/debug.py
deleted file mode 100644
index 9f2293be..00000000
--- a/python/deps/untypy/untypy/util/debug.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import sys
-import os
-
-def _getEnv(name, conv, default):
- s = os.getenv(name)
- if s is None:
- return default
- try:
- return conv(s)
- except:
- return default
-
-_DEBUG = _getEnv("WYPP_DEBUG", bool, False)
-
-def enableDebug(debug: bool):
- global _DEBUG
- _DEBUG = debug
-
-def isDebug():
- return _DEBUG
-
-def debug(s):
- if _DEBUG:
- sys.stderr.write('[DEBUG] ' + s + '\n')
diff --git a/python/deps/untypy/untypy/util/display.py b/python/deps/untypy/untypy/util/display.py
deleted file mode 100644
index 8e73c940..00000000
--- a/python/deps/untypy/untypy/util/display.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import inspect
-
-
-class IndicatorStr:
- ty: str
- indicator: str
-
- def __init__(self, ty: str, indicator: str = ""):
- n = 0 if ty is None else len(ty)
- while len(indicator) < n:
- indicator += " "
-
- self.ty = ty
- self.indicator = indicator
-
- def __add__(self, other):
- return IndicatorStr(self.ty + other.ty, self.indicator + other.indicator)
-
- def join(self, lst):
- return IndicatorStr(
- self.ty.join(map(lambda s: s.ty, lst)),
- self.indicator.join(map(lambda s: s.indicator, lst)),
- )
-
-
-def format_argument_values(args, kwargs):
- allargs = []
- for a in args:
- allargs.append(a.__repr__())
- for k,v in kwargs.items():
- allargs.append(k + "=" + v.__repr__())
-
- return "(" + ", ".join(allargs) + ")"
-
-
-def withIndefiniteArticle(s):
- if s:
- first = s[0]
- if first in "aeiouyAEIOUY":
- return 'an ' + s
- else:
- return 'a ' + s
- else:
- return s
diff --git a/python/deps/untypy/untypy/util/return_traces.py b/python/deps/untypy/untypy/util/return_traces.py
deleted file mode 100644
index 84eca8b0..00000000
--- a/python/deps/untypy/untypy/util/return_traces.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import ast
-from typing import *
-
-
-class ReturnTraceManager:
- """
- Stores file & line_no to every return idx
- """
- def __init__(self):
- self.lst = []
-
- def next_id(self, reti : ast.Return, file: str) -> int:
- i = len(self.lst)
- self.lst.append((file, reti.lineno))
- return i
-
- def get(self, idx : int) -> (str, int):
- return self.lst[idx]
-
-GlobalReturnTraceManager = ReturnTraceManager()
-reti_loc: int = -1
-
-
-def before_return(idx : int):
- global reti_loc
- reti_loc = idx
-
-
-def get_last_return() -> (str, int):
- global reti_loc
- if reti_loc < 0:
- return ("", 0) # this will never match any real location
-
- # Note: this location is only used if it is in the span of the located function.
- # See ReturnExecutionContext
- return GlobalReturnTraceManager.get(reti_loc)
-
-
-class ReturnTracesTransformer(ast.NodeTransformer):
-
- def __init__(self, file: str, manager=GlobalReturnTraceManager):
- self.file = file
- self.manager = manager
-
- def generic_visit(self, node) -> Any:
- # See https://docs.python.org/3/library/ast.html
- stmt_types = ["body", "orelse", "finalbody"]
-
- for stmt_type in stmt_types:
- if not hasattr(node, stmt_type):
- continue
- statements : list = getattr(node, stmt_type)
-
- if not isinstance(statements, Iterable):
- # skip lambda expressions
- continue
-
- inserts = []
- for i,s in enumerate(statements):
- if type(s) is ast.Return:
- inserts.append((i, s))
-
- # start at end so the early indexes still fit
- inserts.reverse()
-
- for index, reti in inserts:
- n = ast.Expr(value=ast.Call(
- func=ast.Attribute(value=ast.Name(id='untypy', ctx=ast.Load()), attr='_before_return', ctx=ast.Load()),
- args=[ast.Constant(value=self.manager.next_id(reti, self.file))],
- keywords=[])
- )
- statements.insert(index, n)
-
- super(ast.NodeTransformer, self).generic_visit(node)
-
diff --git a/python/deps/untypy/untypy/util/source_utils.py b/python/deps/untypy/untypy/util/source_utils.py
deleted file mode 100644
index 6847c21a..00000000
--- a/python/deps/untypy/untypy/util/source_utils.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from typing import Tuple
-
-
-class DisplayMatrix:
- chars: dict
-
- def __init__(self, src: str, max_lines=5):
- self.chars = {}
- self.max_lines = max_lines
- for y, line in enumerate(src.splitlines()):
- for x, char in enumerate(line):
- self.chars[(x, y)] = char
-
- def write(self, pos: Tuple[int, int], message: str):
- for y, line in enumerate(message.splitlines()):
- for x, char in enumerate(line):
- self.chars[(x + pos[0], y + pos[1])] = char
-
- def __str__(self):
- # Slow, but this is only in the "error" path
- max_y = 0
- max_x = 0
- for x, y in self.chars:
- max_y = max(max_y, y)
- max_x = max(max_x, x)
-
- max_y = min(max_y, self.max_lines)
- out = ""
- for y in range(max_y + 1):
- for x in range(max_x + 1):
- if (x, y) in self.chars:
- out += self.chars[(x, y)]
- else:
- out += " "
- out += "\n"
- return out
diff --git a/python/deps/untypy/untypy/util/tranformer_combinator.py b/python/deps/untypy/untypy/util/tranformer_combinator.py
deleted file mode 100644
index c86de398..00000000
--- a/python/deps/untypy/untypy/util/tranformer_combinator.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import ast
-
-
-class TransformerCombinator(ast.NodeTransformer):
-
- def __init__(self, *inner: ast.NodeTransformer):
- self.inner = inner
-
- def visit(self, node):
- for inner in self.inner:
- inner.visit(node)
diff --git a/python/deps/untypy/untypy/util/typedfunction.py b/python/deps/untypy/untypy/util/typedfunction.py
deleted file mode 100644
index 8c6624a5..00000000
--- a/python/deps/untypy/untypy/util/typedfunction.py
+++ /dev/null
@@ -1,218 +0,0 @@
-import inspect
-import sys
-from typing import Callable, Dict, Optional, Any
-
-from untypy.error import UntypyAttributeError, UntypyNameError, UntypyTypeError
-from untypy.impl.any import SelfChecker
-from untypy.impl.choice import ChoiceChecker
-from untypy.interfaces import WrappedFunction, TypeChecker, CreationContext, WrappedFunctionContextProvider, \
- ExecutionContext
-from untypy.util import ArgumentExecutionContext, ReturnExecutionContext
-from untypy.util.typehints import get_type_hints
-from untypy.util.condition import FunctionCondition
-
-class FastTypeError(TypeError):
- pass
-
-class TypedFunctionBuilder(WrappedFunction):
- inner: Callable
- signature: inspect.Signature
- _checkers: Optional[Dict[str, TypeChecker]]
-
- special_args = ['self', 'cls']
- method_name_ignore_return = ['__init__']
-
- def __init__(self, inner: Callable, ctx: CreationContext):
- self.inner = inner
- self.signature = inspect.signature(inner)
- self.parameters = list(self.signature.parameters.values())
- self.ctx = ctx
- self.fc = None
- self._checkers = None
-
- try:
- # try to detect errors like missing arguments as early as possible.
- # but not all type annotations are resolvable yet.
- # so ignore `UntypyNameError`s
- self.checkers()
- except UntypyNameError:
- pass
- except NameError:
- pass
-
- # The self.fast_sig flags tells wether the signature supports fast matching of arguments.
- # We identified (2022-05-05) that self.wrap_arguments is a performence bottleneck.
- # With type annotations, the performance was factor 8.8 slower than without type
- # annotations
- # So we now have a fastlane for the common case where there are no kw and no variable
- # arguments. For the fastlane, performance is only factor 3.7 slower.
- if hasattr(self.inner, "__fc"):
- self.fc = getattr(self.inner, "__fc")
-
- self.fast_sig = is_fast_sig(self.parameters, self.fc)
-
- def checkers(self) -> Dict[str, TypeChecker]:
- if self._checkers is not None:
- return self._checkers
-
- annotations = get_type_hints(self.inner, self.ctx)
-
- checkers = {}
- checked_keys = list(self.signature.parameters)
-
- # Remove self and cls from checking
- if len(checked_keys) > 0 and checked_keys[0] in self.special_args:
- checkers[checked_keys[0]] = SelfChecker()
- checked_keys = checked_keys[1:]
-
- for key in checked_keys:
- if self.signature.parameters[key].annotation is inspect.Parameter.empty:
- raise self.ctx.wrap(
- UntypyAttributeError(f"Missing annotation for argument '{key}' of function {self.inner.__name__}\n"
- "Partial annotation are not supported."))
- annotation = annotations[key]
- checker = self.ctx.find_checker(annotation)
- if checker is None:
- raise self.ctx.wrap(UntypyAttributeError(f"\n\tUnsupported type annotation: {annotation}\n"
- f"\tin argument '{key}'"))
- else:
- checkers[key] = checker
-
- if self.inner.__name__ in self.method_name_ignore_return:
- checkers['return'] = SelfChecker()
- else:
- if not 'return' in annotations:
- annotation = None
- else:
- annotation = annotations['return']
- return_checker = self.ctx.find_checker(annotation)
- if return_checker is None:
- raise self.ctx.wrap(UntypyAttributeError(f"\n\tUnsupported type annotation: {annotation}\n"
- f"\tin return"))
-
- checkers['return'] = return_checker
-
- self._checkers = checkers
- return checkers
-
- def build(self):
- def wrapper(*args, **kwargs):
- # first is this fn
- caller = sys._getframe(1)
- (args, kwargs, bindings) = self.wrap_arguments(lambda n: ArgumentExecutionContext(self, caller, n), args,
- kwargs)
- ret = self.inner(*args, **kwargs)
- ret = self.wrap_return(args, kwargs, ret, bindings, ReturnExecutionContext(self))
- return ret
-
- if inspect.iscoroutine(self.inner):
- raise UntypyAttributeError("Async functions are currently not supported.")
- else:
- w = wrapper
-
- setattr(w, '__wrapped__', self.inner)
- setattr(w, '__name__', self.inner.__name__)
- setattr(w, '__signature__', self.signature)
- setattr(w, '__wf', self)
-
- # Copy useful attributes
- # This is need for the detection of abstract classes
- for attr in ['__isabstractmethod__']:
- if hasattr(self.inner, attr):
- setattr(w, attr, getattr(self.inner, attr))
-
- return w
-
- def wrap_arguments(self, ctxprv: WrappedFunctionContextProvider, args, kwargs):
- return wrap_arguments(self.parameters, self.checkers(), self.signature, self.fc, self.fast_sig,
- ctxprv, args, kwargs)
-
- def wrap_return(self, args, kwargs, ret, bindings, ctx: ExecutionContext):
- fc_pair = None
- if self.fc is not None:
- fc_pair = (self.fc, bindings)
- return wrap_return(self.checkers()['return'], args, kwargs, ret, fc_pair, ctx)
-
- def describe(self):
- return str(self.signature)
-
- def get_original(self):
- return self.inner
-
- def checker_for(self, name: str) -> TypeChecker:
- return self.checkers()[name]
-
-def _wrap_arguments_fast(parameters, checkers, ctxprv: WrappedFunctionContextProvider, args):
- # Fast case: no kwargs, no rest args, self.fc is None.
- # (self.fc is used for pre- and postconditions, a rarely used feature.)
- # In this case, the order parameter names in args and self.parameters is the
- # same, so we can simply match them by position. As self.fc is None, we do not
- # need to build a inspect.BoundArguments object.
- params = parameters
- n = len(params)
- n_args = len(args)
- if n_args > n:
- raise FastTypeError(f"too many positional arguments")
- wrapped_args = [None] * n
- for i in range(n):
- p = params[i]
- name = params[i].name
- if i < n_args:
- a = args[i]
- else:
- a = p.default
- if a == inspect._empty:
- raise FastTypeError(f"missing a required argument: {name!r}")
- check = checkers[name]
- ctx = ctxprv(name)
- wrapped = check.check_and_wrap(a, ctx)
- wrapped_args[i] = wrapped
- return (wrapped_args, {}, None)
-
-def wrap_arguments(parameters, checkers, signature, fc, fast_sig,
- ctxprv: WrappedFunctionContextProvider, args, kwargs, expectSelf=False):
- if not kwargs and fast_sig:
- try:
- return _wrap_arguments_fast(parameters, checkers, ctxprv, args)
- except FastTypeError as e:
- err = UntypyTypeError(header=str(e))
- if expectSelf and "self" not in parameters:
- err = err.with_note("Hint: 'self'-parameter was omitted in declaration.")
- raise ctxprv("").wrap(err)
- try:
- bindings = signature.bind(*args, **kwargs)
- except TypeError as e:
- err = UntypyTypeError(header=str(e))
- if expectSelf and "self" not in parameters:
- err = err.with_note("Hint: 'self'-parameter was omitted in declaration.")
- raise ctxprv("").wrap(err)
- bindings.apply_defaults()
- if fc is not None:
- fc.prehook(bindings, ctxprv)
- for name in bindings.arguments:
- check = checkers[name]
- ctx = ctxprv(name)
- bindings.arguments[name] = check.check_and_wrap(bindings.arguments[name], ctx)
- return bindings.args, bindings.kwargs, bindings
-
-def is_fast_sig(parameters, fc):
- if fc:
- return False
- fast_sig = True
- for p in parameters:
- # See https://docs.python.org/3/glossary.html#term-parameter for the
- # different kinds of parameters
- if p.kind != inspect._POSITIONAL_ONLY and p.kind != inspect._POSITIONAL_OR_KEYWORD:
- fast_sig = False
- break
- return fast_sig
-
-def wrap_return(check: TypeChecker, args: list[Any], kwds: dict[str, Any], ret: Any,
- fc_pair: tuple[FunctionCondition, inspect.BoundArguments], ctx: ExecutionContext):
- if isinstance(check, ChoiceChecker):
- check = check.get_checker(args, kwds)
- result = check.check_and_wrap(ret, ctx)
- if fc_pair is not None:
- fc, bindings = fc_pair
- fc.posthook(result, bindings, ctx, check)
- return result
diff --git a/python/deps/untypy/untypy/util/typehints.py b/python/deps/untypy/untypy/util/typehints.py
deleted file mode 100644
index 8f9947c7..00000000
--- a/python/deps/untypy/untypy/util/typehints.py
+++ /dev/null
@@ -1,133 +0,0 @@
-import ast
-import inspect
-import typing
-
-from untypy.error import UntypyNameError, UntypyAttributeError
-from untypy.interfaces import CreationContext, WrappedFunction
-from untypy.util.source_utils import DisplayMatrix
-
-RULES = []
-
-
-def _default_resolver(item):
- return typing.get_type_hints(item, include_extras=True)
-
-
-def get_type_hints(item, ctx: CreationContext, resolver=_default_resolver):
- try:
- # SEE: https://www.python.org/dev/peps/pep-0563/#id7
- return resolver(item)
- except NameError as ne:
- org = WrappedFunction.find_original(item)
- if inspect.isclass(org):
- raise ctx.wrap(UntypyNameError(
- f"{ne}.\nType annotation inside of class '{qualname(org)}' could not be resolved."
- ))
- else:
- raise ctx.wrap(UntypyNameError(
- f"{ne}.\nType annotation of function '{qualname(org)}' could not be resolved."
- ))
- except Exception as e:
- # Try to find better cause in analyse
- raise analyse(item, ctx, e)
-
-
-def analyse(item, ctx: CreationContext, e) -> UntypyAttributeError:
- org = WrappedFunction.find_original(item)
- what = 'function'
- if inspect.isclass(org):
- what = 'class'
- try:
- source = inspect.getsource(item)
- except OSError:
- return ctx.wrap(
- UntypyAttributeError(f"Type annotation of {what} '{qualname(org)}' could not be resolved.")
- )
- fn_ast = ast.parse(source)
-
- for node in map_str_to_ast(fn_ast.body[0].args, fn_ast.body[0].returns):
- for rule in RULES:
- rule_result = rule(node)
- if rule_result:
- # Got a Match
- (n, message) = rule_result
- display = DisplayMatrix(source)
- display.write((n.col_offset - 1, n.lineno),
- " " + "^" * (n.end_col_offset - n.col_offset) + " - " + message)
- return ctx.wrap(
- UntypyAttributeError(f"Type annotation of {what} '{qualname(org)}' could not be resolved:\n"
- f"{e}\n"
- f"\n{display}")
- )
-
- return ctx.wrap(
- UntypyAttributeError(f"Type annotation of {what} '{qualname(org)}' could not be resolved:\n"
- f"{e}\n"))
-
-
-# some type annotations may repr. as strings
-def map_str_to_ast(*nodes):
- for outer_node in nodes:
- for node in ast.walk(outer_node):
- yield node
- if isinstance(node, ast.Constant) and isinstance(node.value, str):
- try:
- for inode in ast.walk(ast.parse(node.value, mode='eval').body):
- if hasattr(inode, 'lineno'):
- inode.lineno += node.lineno - 1
- inode.col_offset += node.col_offset + 1
- inode.end_col_offset += node.col_offset + 1
- yield inode
- except SyntaxError:
- # may not a forward ref
- pass
-
-
-# Rules
-# For Ast-Nodes see: https://docs.python.org/3/library/ast.html
-def rule_wrong_parentheses(item):
- if isinstance(item, ast.Call) and _traverse(item, 'func.id') and _traverse(item, 'args.0'):
- inner = ", ".join(map(ast.unparse, _traverse(item, 'args')))
- return (item, f"Did you mean: '{_traverse(item, 'func.id')}[{inner}]'?")
-
-
-RULES.append(rule_wrong_parentheses)
-
-
-# Helpers
-def _traverse(item, path):
- """
- Traverse item.
- Else return None.
- Path is a str separated by '.'
- """
-
- if isinstance(path, str):
- return _traverse(item, path.split("."))
-
- if len(path) == 0:
- return item
-
- head = path[0]
- tail = path[1:]
-
- if hasattr(item, head):
- return _traverse(getattr(item, head), tail)
- elif isinstance(item, list):
- try:
- idx = int(head)
- if len(item) > idx >= 0:
- return _traverse(item[idx], tail)
- except ValueError:
- return None
- else:
- return None
-
-
-def qualname(typ):
- if hasattr(typ, '__qualname__'):
- return typ.__qualname__
- elif hasattr(typ, '__name__'):
- return typ.__name__
- else:
- return str(typ)
diff --git a/python/deps/untypy/untypy/util/wrapper.py b/python/deps/untypy/untypy/util/wrapper.py
deleted file mode 100644
index 1ffe3eae..00000000
--- a/python/deps/untypy/untypy/util/wrapper.py
+++ /dev/null
@@ -1,275 +0,0 @@
-import typing
-import collections
-from untypy.util.debug import debug
-
-def _f():
- yield 0
-generatorType = type(_f())
-
-class WrapperBase:
- def __eq__(self, other):
- if hasattr(other, '__wrapped__'):
- return self.__wrapped__ == other.__wrapped__
- else:
- return self.__wrapped__ == other
- def __ne__(self, other):
- return not self.__eq__(other)
- def __hash__(self):
- return hash(self.__wrapped__)
- def __repr__(self):
- return repr(self.__wrapped__)
- def __str__(self):
- return str(self.__wrapped__)
- def __cast__(self, other):
- if type(other) == type(self) and hasattr(other, '__wrapped__'):
- return other.__wrapped__
- else:
- return other
- def __reduce__(self): return self.__wrapped__.__reduce__()
- def __reduce_ex__(self): return self.__wrapped__.__reduce_ex__()
- def __sizeof__(self): return self.__wrapped__.__sizeof__()
-
-# A wrapper for list such that the class is a subclass of the builtin list class.
-class ListWrapper(WrapperBase, list): # important: inherit from WrapperBase first
- def __new__(cls, content):
- # the constructor of list copies the list passed to it. Thus, we use an empty list.
- # IMPORTANT: we need to override *all* methods provided by list so that
- # all methods operate on the list being wrapped.
- self = super().__new__(cls, [])
- self.__wrapped__ = content
- return self
- # defined in WrapperBase: __repr__, __str__, __eq__, __hash__
- def __lt__(self, other): return self.__wrapped__.__lt__(self.__cast__(other))
- def __le__(self, other): return self.__wrapped__.__le__(self.__cast__(other))
- def __gt__(self, other): return self.__wrapped__.__gt__(self.__cast__(other))
- def __ge__(self, other): return self.__wrapped__.__ge__(self.__cast__(other))
- def __contains__(self, item): return self.__wrapped__.contains(item)
- def __len__(self): return self.__wrapped__.__len__()
- def __getitem__(self, i): return self.__wrapped__.__getitem__(i)
- def __setitem__(self, i, x): return self.__wrapped__.__setitem__(i, x)
- def __delitem__(self, i): return self.__wrapped__.__delitem__(i)
- def __add__(self, other): return self.__wrapped__.__add__(self.__cast__(other))
- def __radd__(self, other): return self.__cast__(other) + self.__wrapped__
- def __iadd__(self, other): return self.__wrapped__.__iadd__(self.__cast__(other))
- def __mul__(self, n): return self.__wrapped__.__mul__(n)
- __rmul__ = __mul__
- def __imul__(self, n): return self.__wrapped__.__imul__(n)
- def __iter__(self): return self.__wrapped__.__iter__()
- def __copy__(self): return self.__wrapped__.copy()
- def __reversed__(self): return self.__wrapped__.__reversed__()
- def append(self, item): return self.__wrapped__.append(item)
- def extend(self, iter): return self.__wrapped__.extend(iter)
- def insert(self, i, item): return self.__wrapped__.insert(i, item)
- def pop(self, i=-1): return self.__wrapped__.pop(i)
- def remove(self, item): return self.__wrapped__.remove(item)
- def clear(self): return self.__wrapped__.clear()
- def copy(self): return self.__wrapped__.copy()
- def count(self, item): return self.__wrapped__.count(item)
- def index(self, item, *args): return self.__wrapped__.index(item, *args)
- def reverse(self): return self.__wrapped__.reverse()
- def sort(self, /, *args, **kwds): return self.__wrapped__.sort(*args, **kwds)
-
-class SetWrapper(WrapperBase, set):
- def __new__(cls, content):
- self = super().__new__(cls, set())
- self.__wrapped__ = content
- return self
- def add(self, x): return self.__wrapped__.add(x)
- def clear(self): return self.__wrapped__.clear()
- def copy(self): return self.__wrapped__.copy()
- def difference(self, *others): return self.__wrapped__.difference(*others)
- def difference_update(self, *others): return self.__wrapped__.difference_update(*others)
- def discard(self, elem): return self.__wrapped__.discard(elem)
- def intersection(self, *others): return self.__wrapped__.intersection(*others)
- def intersection_update(self, *others): return self.__wrapped__.intersection_update(*others)
- def isdisjoint(self, other): return self.__wrapped__.isdisjoint(other)
- def issubset(self, other): return self.__wrapped__.issubset(other)
- def issuperset(self, other): return self.__wrapped__.issuperset(other)
- def pop(self): return self.__wrapped__.pop()
- def remove(self, elem): return self.__wrapped__.remove(elem)
- def symmetric_difference(self, *others): return self.__wrapped__.symmetric_difference(*others)
- def symmetric_difference_update(self, *others): return self.__wrapped__.symmetric_difference_update(*others)
- def union(self, *others): return self.__wrapped__.union(*others)
- def update(self, *others): return self.__wrapped__.update(*others)
- def __contains__(self, x): return self.__wrapped__.__contains__(x)
- def __le__(self, other): return self.__wrapped__.__le__(self.__cast__(other))
- def __lt__(self, other): return self.__wrapped__.__lt__(self.__cast__(other))
- def __ge__(self, other): return self.__wrapped__.__ge__(self.__cast__(other))
- def __gt__(self, other): return self.__wrapped__.__gt__(self.__cast__(other))
- def __and__(self, other): return self.__wrapped__.__and__(self.__cast__(other))
- def __rand__(self, other): return self.__wrapped__.__rand__(self.__cast__(other))
- def __iand__(self, other): return self.__wrapped__.__iand__(self.__cast__(other))
- def __ior__(self, other): return self.__wrapped__.__ior__(self.__cast__(other))
- def __ixor__(self, other): return self.__wrapped__.__ixor__(self.__cast__(other))
- def __or__(self, other): return self.__wrapped__.__or__(self.__cast__(other))
- def __ror__(self, other): return self.__wrapped__.__ror__(self.__cast__(other))
- def __rxor__(self, other): return self.__wrapped__.__rxor__(self.__cast__(other))
- def __xor__(self, other): return self.__wrapped__.__xor__(self.__cast__(other))
- def __rsub__(self, other): return self.__wrapped__.__rsub__(self.__cast__(other))
- def __sub__(self, other): return self.__wrapped__.__sub__(self.__cast__(other))
- def __isub__(self, other): return self.__wrapped__.__isub__(self.__cast__(other))
- def __iter__(self): return self.__wrapped__.__iter__()
- def __len__(self): return self.__wrapped__.__len__()
-
-class DictWrapper(WrapperBase, dict):
- def __new__(cls, content):
- self = super().__new__(cls, {})
- self.__wrapped__ = content
- return self
- def __len__(self): return self.__wrapped__.__len__()
- def __getitem__(self, key): return self.__wrapped__.__getitem__(key)
- def __setitem__(self, key, item): return self.__wrapped__.__setitem__(key, item)
- def __delitem__(self, key): return self.__wrapped__.__delitem__(key)
- def __iter__(self): return self.__wrapped__.__iter__()
- def __contains__(self, key): return self.__wrapped__.__contains__(key)
- def __or__(self, other): return self.__wrapped__.__or__(self.__cast__(other))
- def __ror__(self, other): return self.__wrapped__.__ror__(self.__cast__(other))
- def __ior__(self, other): return self.__wrapped__.__ior__(self.__cast__(other))
- def __copy__(self): return self.__wrapped__.__copy__()
- def __lt__(self, other): return self.__wrapped__.__lt__(self.__cast__(other))
- def __le__(self, other): return self.__wrapped__.__le__(self.__cast__(other))
- def __gt__(self, other): return self.__wrapped__.__gt__(self.__cast__(other))
- def __ge__(self, other): return self.__wrapped__.__ge__(self.__cast__(other))
- def copy(self): return self.__wrapped__.copy()
- def __reversed__(self): return self.__wrapped__.__reversed__()
- __marker = object()
- def pop(self, key, default=__marker):
- if default == self.__marker:
- return self.__wrapped__.pop(key)
- else:
- return self.__wrapped__.pop(key, default)
- def popitem(self): return self.__wrapped__.popitem()
- def clear(self): return self.__wrapped__.clear()
- def update(self, other=(), /, **kwds): return self.__wrapped__.update(self.__cast__(other), **kwds)
- def setdefault(self, key, default=None): return self.__wrapped__.setdefault(key, default=default)
- def get(self, key, default=None): return self.__wrapped__.get(key, default)
- def keys(self): return self.__wrapped__.keys()
- def items(self): return self.__wrapped__.items()
- def values(self): return self.__wrapped__.values()
-
-# Tuple and string wrapper are simpler because these types are immutable
-class StringWrapper(str, WrapperBase):
- def __new__(cls, content):
- self = super().__new__(cls, content)
- self.__wrapped__ = content
- return self
-
-class TupleWrapper(tuple, WrapperBase):
- def __new__(cls, content):
- self = super().__new__(cls, content)
- self.__wrapped__ = content
- return self
-
-# SimpleWrapper is a fallback for types that cannot be used as base types
-class SimpleWrapper(WrapperBase):
- def __init__(self, wrapped):
- self.__wrapped__ = wrapped
-
-class ValuesViewWrapper(SimpleWrapper):
- pass
-collections.abc.ValuesView.register(ValuesViewWrapper)
-
-class ItemsViewWrapper(SimpleWrapper):
- pass
-collections.abc.ItemsView.register(ItemsViewWrapper)
-
-class KeysViewWrapper(SimpleWrapper):
- pass
-collections.abc.KeysView.register(KeysViewWrapper)
-
-def _wrap(wrapped, methods, mod, name, extra, cls):
- if extra is None:
- extra = {}
- # Dynamically create a new class:
- # type(class_name, base_classes, class_dict)
- WrapperClass = type(
- name,
- (cls,),
- methods
- )
- WrapperClass.__module__ = mod
- w = WrapperClass(wrapped)
- w.__extra__ = extra
- return w
-
-def wrapSimple(wrapped, methods, name, extra, cls=SimpleWrapper):
- if name is None:
- name = cls.__name__
- mod = None
- else:
- if hasattr(wrapped, '__module__'):
- mod = wrapped.__module__
- else:
- mod = None
- for x in ['__next__', '__iter__']:
- if x not in methods and hasattr(wrapped, x):
- attr = getattr(wrapped, x)
- methods[x] = attr
- return _wrap(wrapped, methods, mod, name, extra, cls)
-
-def wrapObj(wrapped, methods, name, extra):
- class BaseWrapper(WrapperBase, wrapped.__class__):
- def __init__(self, wrapped):
- self.__dict__ = wrapped.__dict__
- self.__wrapped__ = wrapped
- if name is None:
- name = 'ObjectWrapper'
- if hasattr(wrapped, '__module__'):
- mod = getattr(wrapped, '__module__')
- else:
- mod = None
- return _wrap(wrapped, methods, mod, name, extra, BaseWrapper)
-
-def wrapBuiltin(wrapped, methods, name, extra, cls):
- if name is None:
- name = cls.__name__
- return _wrap(wrapped, methods, None, name, extra, cls)
-
-def wrap(obj, methods, name=None, extra=None, simple=False):
- if extra is None:
- extra = {}
- wrapper = None
- if simple:
- w = wrapSimple(obj, methods, name, extra)
- wrapper = 'SimpleWrapper'
- elif isinstance(obj, list):
- w = wrapBuiltin(obj, methods, name, extra, ListWrapper)
- wrapper = 'ListWrapper'
- elif isinstance(obj, tuple):
- w = wrapBuiltin(obj, methods, name, extra, TupleWrapper)
- wrapper = 'TupleWrapper'
- elif isinstance(obj, dict):
- w = wrapBuiltin(obj, methods, name, extra, DictWrapper)
- wrapper = 'DictWrapper'
- elif isinstance(obj, str):
- w = wrapBuiltin(obj, methods, name, extra, StringWrapper)
- wrapper = 'StringWrapper'
- elif isinstance(obj, set):
- w = wrapBuiltin(obj, methods, name, extra, SetWrapper)
- wrapper = 'SetWrapper'
- elif isinstance(obj, collections.abc.ValuesView):
- w = wrapSimple(obj, methods, name, extra, ValuesViewWrapper)
- wrapper = 'ValuesViewWrapper'
- elif isinstance(obj, collections.abc.KeysView):
- w = wrapSimple(obj, methods, name, extra, KeysViewWrapper)
- wrapper = 'KeysViewWrapper'
- elif isinstance(obj, collections.abc.ItemsView):
- w = wrapSimple(obj, methods, name, extra, ItemsViewWrapper)
- wrapper = 'ItemsViewWrapper'
- elif isinstance(obj, typing.Generic):
- w = wrapSimple(obj, methods, name, extra)
- wrapper = 'SimpleWrapper'
- elif isinstance(obj, generatorType):
- w = wrapSimple(obj, methods, name, extra)
- wrapper = 'SimpleWrapper'
- elif hasattr(obj, '__dict__'):
- w = wrapObj(obj, methods, name, extra)
- wrapper = 'ObjectWrapper'
- else:
- w = wrapSimple(obj, methods, name, extra)
- wrapper = 'SimpleWrapper'
- wname = name
- if wname is None:
- wname = str(type(w))
- debug(f"Wrapping {obj} at 0x{id(obj):09x} as {wname}, simple={simple}, wrapper=0x{id(w):09x} ({wrapper})")
- return w
diff --git a/python/file-test-data/basics/async.err b/python/file-test-data/basics/async.err
new file mode 100644
index 00000000..a17f2494
--- /dev/null
+++ b/python/file-test-data/basics/async.err
@@ -0,0 +1,25 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/async.py", line 10, in
+ asyncio.run(main())
+ File "runners.py", line ?, in run
+ return runner.run(main)
+ File "runners.py", line ?, in run
+ signal.signal(signal.SIGINT, signal.default_int_handler)
+ File "base_events.py", line ?, in run_until_complete
+ return future.result()
+ File "file-test-data/basics/async.py", line 7, in main
+ x = await foo("blub")
+
+WyppTypeError: "blub"
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/basics/async.py
+## Fehlerhafter Aufruf in Zeile 7:
+
+ x = await foo([0;31m[1m"blub"[0;0m)
+
+## Typ deklariert in Zeile 3:
+
+async def foo([0;31m[1mi: int[0;0m):
diff --git a/python/file-test-data/basics/async.err_en b/python/file-test-data/basics/async.err_en
new file mode 100644
index 00000000..e4a7d64e
--- /dev/null
+++ b/python/file-test-data/basics/async.err_en
@@ -0,0 +1,25 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/async.py", line 10, in
+ asyncio.run(main())
+ File "runners.py", line ?, in run
+ return runner.run(main)
+ File "runners.py", line ?, in run
+ signal.signal(signal.SIGINT, signal.default_int_handler)
+ File "base_events.py", line ?, in run_until_complete
+ return future.result()
+ File "file-test-data/basics/async.py", line 7, in main
+ x = await foo("blub")
+
+WyppTypeError: "blub"
+
+The call of function `foo` expects value of type `int` as 1st argument.
+But the value given has type `str`.
+
+## File file-test-data/basics/async.py
+## Problematic call in line 7:
+
+ x = await foo([0;31m[1m"blub"[0;0m)
+
+## Type declared in line 3:
+
+async def foo([0;31m[1mi: int[0;0m):
diff --git a/python/deps/untypy/requirements.txt b/python/file-test-data/basics/async.out
similarity index 100%
rename from python/deps/untypy/requirements.txt
rename to python/file-test-data/basics/async.out
diff --git a/python/deps/untypy/test-requirements.txt b/python/file-test-data/basics/async.out_en
similarity index 100%
rename from python/deps/untypy/test-requirements.txt
rename to python/file-test-data/basics/async.out_en
diff --git a/python/file-test-data/basics/async.py b/python/file-test-data/basics/async.py
new file mode 100644
index 00000000..bcc83ba0
--- /dev/null
+++ b/python/file-test-data/basics/async.py
@@ -0,0 +1,10 @@
+import asyncio
+
+async def foo(i: int):
+ return i + 1
+
+async def main():
+ x = await foo("blub")
+ print(x)
+
+asyncio.run(main())
diff --git a/python/file-test-data/basics/constructor.err b/python/file-test-data/basics/constructor.err
new file mode 100644
index 00000000..9d8fb5ad
--- /dev/null
+++ b/python/file-test-data/basics/constructor.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/constructor.py", line 5, in
+ c = C("1")
+
+WyppTypeError: "1"
+
+Der Aufruf der Methode `__init__` aus Klasse `C` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/basics/constructor.py
+## Fehlerhafter Aufruf in Zeile 5:
+
+c = [0;31m[1mC("1")[0;0m
+
+## Typ deklariert in Zeile 2:
+
+ def __init__(self, [0;31m[1mx: int[0;0m):
\ No newline at end of file
diff --git a/python/file-test-data/basics/constructor.err_en b/python/file-test-data/basics/constructor.err_en
new file mode 100644
index 00000000..44f7d2ce
--- /dev/null
+++ b/python/file-test-data/basics/constructor.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/constructor.py", line 5, in
+ c = C("1")
+
+WyppTypeError: "1"
+
+The call of method `__init__` of class `C` expects value of type `int` as 1st argument.
+But the value given has type `str`.
+
+## File file-test-data/basics/constructor.py
+## Problematic call in line 5:
+
+c = [0;31m[1mC("1")[0;0m
+
+## Type declared in line 2:
+
+ def __init__(self, [0;31m[1mx: int[0;0m):
\ No newline at end of file
diff --git a/python/deps/untypy/test/impl/__init__.py b/python/file-test-data/basics/constructor.out
similarity index 100%
rename from python/deps/untypy/test/impl/__init__.py
rename to python/file-test-data/basics/constructor.out
diff --git a/python/deps/untypy/test/patching_dummy/__init__.py b/python/file-test-data/basics/constructor.out_en
similarity index 100%
rename from python/deps/untypy/test/patching_dummy/__init__.py
rename to python/file-test-data/basics/constructor.out_en
diff --git a/python/file-test-data/basics/constructor.py b/python/file-test-data/basics/constructor.py
new file mode 100644
index 00000000..bd9ca484
--- /dev/null
+++ b/python/file-test-data/basics/constructor.py
@@ -0,0 +1,5 @@
+class C:
+ def __init__(self, x: int):
+ self.x = x
+
+c = C("1")
diff --git a/python/file-test-data/basics/constructorDataclass_ok.py b/python/file-test-data/basics/constructorDataclass_ok.py
new file mode 100644
index 00000000..5f2c3bec
--- /dev/null
+++ b/python/file-test-data/basics/constructorDataclass_ok.py
@@ -0,0 +1,8 @@
+#from dataclasses import dataclass
+from wypp import *
+
+@dataclass
+class C:
+ x: int
+
+c = C("1")
diff --git a/python/deps/untypy/test/util_test/__init__.py b/python/file-test-data/basics/constructor_ok.err
similarity index 100%
rename from python/deps/untypy/test/util_test/__init__.py
rename to python/file-test-data/basics/constructor_ok.err
diff --git a/python/file-test-data/basics/constructor_ok.out b/python/file-test-data/basics/constructor_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/constructor_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/constructor_ok.py b/python/file-test-data/basics/constructor_ok.py
new file mode 100644
index 00000000..57132fce
--- /dev/null
+++ b/python/file-test-data/basics/constructor_ok.py
@@ -0,0 +1,6 @@
+class C:
+ def __init__(self, x: int):
+ self.x = x
+
+c = C(1)
+print('ok')
diff --git a/python/file-test-data/basics/forwardRefs.err b/python/file-test-data/basics/forwardRefs.err
new file mode 100644
index 00000000..e98cc51d
--- /dev/null
+++ b/python/file-test-data/basics/forwardRefs.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/forwardRefs.py", line 12, in
+ a.foo(a)
+
+WyppTypeError: <__wypp__.A object at 0x00>
+
+Der Aufruf der Methode `foo` aus Klasse `A` erwartet Wert vom Typ `B` als erstes Argument.
+Aber der übergebene Wert hat Typ `A`.
+
+## Datei file-test-data/basics/forwardRefs.py
+## Fehlerhafter Aufruf in Zeile 12:
+
+a.foo(a)
+
+## Typ deklariert in Zeile 4:
+
+ def foo(self, [0;31m[1mb: B[0;0m):
\ No newline at end of file
diff --git a/python/file-test-data/basics/forwardRefs.err_en b/python/file-test-data/basics/forwardRefs.err_en
new file mode 100644
index 00000000..20d22f20
--- /dev/null
+++ b/python/file-test-data/basics/forwardRefs.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/forwardRefs.py", line 12, in
+ a.foo(a)
+
+WyppTypeError: <__wypp__.A object at 0x00>
+
+The call of method `foo` of class `A` expects value of type `B` as 1st argument.
+But the value given has type `A`.
+
+## File file-test-data/basics/forwardRefs.py
+## Problematic call in line 12:
+
+a.foo(a)
+
+## Type declared in line 4:
+
+ def foo(self, [0;31m[1mb: B[0;0m):
\ No newline at end of file
diff --git a/python/deps/untypy/untypy/impl/interfaces/__init__.py b/python/file-test-data/basics/forwardRefs.out
similarity index 100%
rename from python/deps/untypy/untypy/impl/interfaces/__init__.py
rename to python/file-test-data/basics/forwardRefs.out
diff --git a/python/test-data/admin.err b/python/file-test-data/basics/forwardRefs.out_en
similarity index 100%
rename from python/test-data/admin.err
rename to python/file-test-data/basics/forwardRefs.out_en
diff --git a/python/file-test-data/basics/forwardRefs.py b/python/file-test-data/basics/forwardRefs.py
new file mode 100644
index 00000000..cd11bd03
--- /dev/null
+++ b/python/file-test-data/basics/forwardRefs.py
@@ -0,0 +1,12 @@
+from __future__ import annotations
+
+class A:
+ def foo(self, b: B):
+ pass
+
+class B:
+ def bar(self, a: A):
+ pass
+
+a = A()
+a.foo(a)
diff --git a/python/test-data/declared-at-missing.out b/python/file-test-data/basics/forwardRefs_ok.err
similarity index 100%
rename from python/test-data/declared-at-missing.out
rename to python/file-test-data/basics/forwardRefs_ok.err
diff --git a/python/file-test-data/basics/forwardRefs_ok.out b/python/file-test-data/basics/forwardRefs_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/forwardRefs_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/forwardRefs_ok.py b/python/file-test-data/basics/forwardRefs_ok.py
new file mode 100644
index 00000000..c3f7fc81
--- /dev/null
+++ b/python/file-test-data/basics/forwardRefs_ok.py
@@ -0,0 +1,15 @@
+from __future__ import annotations
+
+class A:
+ def foo(self, b: B):
+ pass
+
+class B:
+ def bar(self, a: A):
+ pass
+
+a = A()
+b = B()
+a.foo(b)
+b.bar(a)
+print('ok')
diff --git a/python/file-test-data/basics/functionArg.err b/python/file-test-data/basics/functionArg.err
new file mode 100644
index 00000000..003a56f3
--- /dev/null
+++ b/python/file-test-data/basics/functionArg.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionArg.py", line 8, in
+ print(foo(foo(1, "1"), 42))
+
+WyppTypeError: "1"
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `int` als zweites Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/basics/functionArg.py
+## Fehlerhafter Aufruf in Zeile 8:
+
+ print(foo(foo(1, [0;31m[1m"1"[0;0m), 42))
+
+## Typ deklariert in Zeile 4:
+
+def foo(i: int, [0;31m[1mj: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/basics/functionArg.err_en b/python/file-test-data/basics/functionArg.err_en
new file mode 100644
index 00000000..8f4994f8
--- /dev/null
+++ b/python/file-test-data/basics/functionArg.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionArg.py", line 8, in
+ print(foo(foo(1, "1"), 42))
+
+WyppTypeError: "1"
+
+The call of function `foo` expects value of type `int` as 2nd argument.
+But the value given has type `str`.
+
+## File file-test-data/basics/functionArg.py
+## Problematic call in line 8:
+
+ print(foo(foo(1, [0;31m[1m"1"[0;0m), 42))
+
+## Type declared in line 4:
+
+def foo(i: int, [0;31m[1mj: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/modules/A/main.out b/python/file-test-data/basics/functionArg.out
similarity index 100%
rename from python/test-data/modules/A/main.out
rename to python/file-test-data/basics/functionArg.out
diff --git a/python/test-data/printModuleName.err b/python/file-test-data/basics/functionArg.out_en
similarity index 100%
rename from python/test-data/printModuleName.err
rename to python/file-test-data/basics/functionArg.out_en
diff --git a/python/file-test-data/basics/functionArg.py b/python/file-test-data/basics/functionArg.py
new file mode 100644
index 00000000..fa3619c6
--- /dev/null
+++ b/python/file-test-data/basics/functionArg.py
@@ -0,0 +1,8 @@
+def bar(s: str) -> str:
+ return s
+
+def foo(i: int, j: int) -> int:
+ return i + 1
+
+if True:
+ print(foo(foo(1, "1"), 42))
diff --git a/python/test-data/printModuleNameImport.err b/python/file-test-data/basics/functionArg_ok.err
similarity index 100%
rename from python/test-data/printModuleNameImport.err
rename to python/file-test-data/basics/functionArg_ok.err
diff --git a/python/test-data/testABC.err b/python/file-test-data/basics/functionArg_ok.err_None
similarity index 100%
rename from python/test-data/testABC.err
rename to python/file-test-data/basics/functionArg_ok.err_None
diff --git a/python/file-test-data/basics/functionArg_ok.out b/python/file-test-data/basics/functionArg_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/functionArg_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/functionArg_ok.out_None b/python/file-test-data/basics/functionArg_ok.out_None
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/functionArg_ok.out_None
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/functionArg_ok.py b/python/file-test-data/basics/functionArg_ok.py
new file mode 100644
index 00000000..9f86377b
--- /dev/null
+++ b/python/file-test-data/basics/functionArg_ok.py
@@ -0,0 +1,5 @@
+def foo(i: int) -> int:
+ return i + 1
+
+foo(1)
+print('ok')
diff --git a/python/file-test-data/basics/functionNoResult.err b/python/file-test-data/basics/functionNoResult.err
new file mode 100644
index 00000000..11e69b2c
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult.err
@@ -0,0 +1,23 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionNoResult.py", line 4, in
+ foo(1)
+ File "file-test-data/basics/functionNoResult.py", line 2, in foo
+ return "x"
+
+WyppTypeError: "x"
+
+Kein Rückgabewert erwartet bei Aufruf der Funktion `foo`.
+Aber der Aufruf gibt einen Wert vom Typ `str` zurück.
+
+## Datei file-test-data/basics/functionNoResult.py
+## Rückgabetyp deklariert in Zeile 1:
+
+def foo(i: int) -> [0;31m[1mNone[0;0m:
+
+## Fehlerhaftes return in Zeile 2:
+
+ return "x"
+
+## Aufruf in Zeile 4 verursacht das fehlerhafte return:
+
+foo(1)
\ No newline at end of file
diff --git a/python/file-test-data/basics/functionNoResult.err_en b/python/file-test-data/basics/functionNoResult.err_en
new file mode 100644
index 00000000..304cff85
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult.err_en
@@ -0,0 +1,23 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionNoResult.py", line 4, in
+ foo(1)
+ File "file-test-data/basics/functionNoResult.py", line 2, in foo
+ return "x"
+
+WyppTypeError: "x"
+
+Expecting no return value when calling function `foo`.
+But the call returns a value of type `str`.
+
+## File file-test-data/basics/functionNoResult.py
+## Result type declared in line 1:
+
+def foo(i: int) -> [0;31m[1mNone[0;0m:
+
+## Problematic return in line 2:
+
+ return "x"
+
+## Call in line 4 causes the problematic return:
+
+foo(1)
\ No newline at end of file
diff --git a/python/test-data/testABCMeta.out b/python/file-test-data/basics/functionNoResult.out
similarity index 100%
rename from python/test-data/testABCMeta.out
rename to python/file-test-data/basics/functionNoResult.out
diff --git a/python/test-data/testArgs.err b/python/file-test-data/basics/functionNoResult.out_en
similarity index 100%
rename from python/test-data/testArgs.err
rename to python/file-test-data/basics/functionNoResult.out_en
diff --git a/python/file-test-data/basics/functionNoResult.py b/python/file-test-data/basics/functionNoResult.py
new file mode 100644
index 00000000..75085123
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult.py
@@ -0,0 +1,4 @@
+def foo(i: int) -> None:
+ return "x"
+
+foo(1)
diff --git a/python/file-test-data/basics/functionNoResult2.err b/python/file-test-data/basics/functionNoResult2.err
new file mode 100644
index 00000000..c890beaa
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult2.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionNoResult2.py", line 4, in
+ foo(1)
+ File "file-test-data/basics/functionNoResult2.py", line 2, in foo
+ pass
+
+WyppTypeError: kein Rückgabewert vorhanden
+
+Rückgabewert vom Typ `int` erwartet bei Aufruf der Funktion `foo`.
+Aber kein Rückgabewert vorhanden.
+
+## Datei file-test-data/basics/functionNoResult2.py
+## Rückgabetyp deklariert in Zeile 1:
+
+def foo(i: int) -> [0;31m[1mint[0;0m:
+
+## Aufruf in Zeile 4 führt dazu, dass die Funktion keinen Wert zurückgibt:
+
+foo(1)
\ No newline at end of file
diff --git a/python/file-test-data/basics/functionNoResult2.err_en b/python/file-test-data/basics/functionNoResult2.err_en
new file mode 100644
index 00000000..627f5d4c
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult2.err_en
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionNoResult2.py", line 4, in
+ foo(1)
+ File "file-test-data/basics/functionNoResult2.py", line 2, in foo
+ pass
+
+WyppTypeError: no result returned
+
+Expecting return value of type `int` when calling function `foo`.
+But no value returned.
+
+## File file-test-data/basics/functionNoResult2.py
+## Result type declared in line 1:
+
+def foo(i: int) -> [0;31m[1mint[0;0m:
+
+## Call in line 4 causes the function to return no value:
+
+foo(1)
\ No newline at end of file
diff --git a/python/test-data/testBugSliceIndices.err b/python/file-test-data/basics/functionNoResult2.out
similarity index 100%
rename from python/test-data/testBugSliceIndices.err
rename to python/file-test-data/basics/functionNoResult2.out
diff --git a/python/test-data/testCheck.err b/python/file-test-data/basics/functionNoResult2.out_en
similarity index 100%
rename from python/test-data/testCheck.err
rename to python/file-test-data/basics/functionNoResult2.out_en
diff --git a/python/file-test-data/basics/functionNoResult2.py b/python/file-test-data/basics/functionNoResult2.py
new file mode 100644
index 00000000..167c5b3a
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult2.py
@@ -0,0 +1,4 @@
+def foo(i: int) -> int:
+ pass
+
+foo(1)
diff --git a/python/test-data/testCheck2.out b/python/file-test-data/basics/functionNoResult2_ok.err
similarity index 100%
rename from python/test-data/testCheck2.out
rename to python/file-test-data/basics/functionNoResult2_ok.err
diff --git a/python/file-test-data/basics/functionNoResult2_ok.out b/python/file-test-data/basics/functionNoResult2_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult2_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/functionNoResult2_ok.py b/python/file-test-data/basics/functionNoResult2_ok.py
new file mode 100644
index 00000000..5b658665
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult2_ok.py
@@ -0,0 +1,5 @@
+def foo(i: int) -> int:
+ return 1
+
+foo(1)
+print('ok')
diff --git a/python/file-test-data/basics/functionNoResult3.err b/python/file-test-data/basics/functionNoResult3.err
new file mode 100644
index 00000000..887347a5
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult3.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionNoResult3.py", line 4, in
+ foo(1)
+ File "file-test-data/basics/functionNoResult3.py", line 2, in foo
+ return "x"
+
+WyppTypeError: "x"
+
+Kein Rückgabewert erwartet bei Aufruf der Funktion `foo`.
+Aber der Aufruf gibt einen Wert vom Typ `str` zurück.
+
+## Datei file-test-data/basics/functionNoResult3.py
+## Fehlerhaftes return in Zeile 2:
+
+ return "x"
+
+## Aufruf in Zeile 4 verursacht das fehlerhafte return:
+
+foo(1)
\ No newline at end of file
diff --git a/python/file-test-data/basics/functionNoResult3.err_en b/python/file-test-data/basics/functionNoResult3.err_en
new file mode 100644
index 00000000..d4f2bad8
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult3.err_en
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionNoResult3.py", line 4, in
+ foo(1)
+ File "file-test-data/basics/functionNoResult3.py", line 2, in foo
+ return "x"
+
+WyppTypeError: "x"
+
+Expecting no return value when calling function `foo`.
+But the call returns a value of type `str`.
+
+## File file-test-data/basics/functionNoResult3.py
+## Problematic return in line 2:
+
+ return "x"
+
+## Call in line 4 causes the problematic return:
+
+foo(1)
\ No newline at end of file
diff --git a/python/test-data/testCheckFail.err b/python/file-test-data/basics/functionNoResult3.out
similarity index 100%
rename from python/test-data/testCheckFail.err
rename to python/file-test-data/basics/functionNoResult3.out
diff --git a/python/test-data/testClassHierarchy.err b/python/file-test-data/basics/functionNoResult3.out_en
similarity index 100%
rename from python/test-data/testClassHierarchy.err
rename to python/file-test-data/basics/functionNoResult3.out_en
diff --git a/python/test-data/testInferReturnType.py b/python/file-test-data/basics/functionNoResult3.py
similarity index 60%
rename from python/test-data/testInferReturnType.py
rename to python/file-test-data/basics/functionNoResult3.py
index f3358c0d..bb14b13b 100644
--- a/python/test-data/testInferReturnType.py
+++ b/python/file-test-data/basics/functionNoResult3.py
@@ -1,4 +1,4 @@
def foo(i: int):
- return None
+ return "x"
foo(1)
diff --git a/python/test-data/testClassRecursion.err b/python/file-test-data/basics/functionNoResult3_ok.err
similarity index 100%
rename from python/test-data/testClassRecursion.err
rename to python/file-test-data/basics/functionNoResult3_ok.err
diff --git a/python/file-test-data/basics/functionNoResult3_ok.out b/python/file-test-data/basics/functionNoResult3_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult3_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/functionNoResult3_ok.py b/python/file-test-data/basics/functionNoResult3_ok.py
new file mode 100644
index 00000000..c67b93a6
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult3_ok.py
@@ -0,0 +1,5 @@
+def foo(i: int):
+ pass
+
+foo(1)
+print('ok')
diff --git a/python/test-data/testComplex.err b/python/file-test-data/basics/functionNoResult_ok.err
similarity index 100%
rename from python/test-data/testComplex.err
rename to python/file-test-data/basics/functionNoResult_ok.err
diff --git a/python/file-test-data/basics/functionNoResult_ok.out b/python/file-test-data/basics/functionNoResult_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/functionNoResult_ok.py b/python/file-test-data/basics/functionNoResult_ok.py
new file mode 100644
index 00000000..c67b93a6
--- /dev/null
+++ b/python/file-test-data/basics/functionNoResult_ok.py
@@ -0,0 +1,5 @@
+def foo(i: int):
+ pass
+
+foo(1)
+print('ok')
diff --git a/python/file-test-data/basics/functionResult.err b/python/file-test-data/basics/functionResult.err
new file mode 100644
index 00000000..fccc0a10
--- /dev/null
+++ b/python/file-test-data/basics/functionResult.err
@@ -0,0 +1,23 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionResult.py", line 7, in
+ foo(1)
+ File "file-test-data/basics/functionResult.py", line 5, in foo
+ return "foo_" + str(i)
+
+WyppTypeError: "foo_1"
+
+Rückgabewert vom Typ `int` erwartet bei Aufruf der Funktion `foo`.
+Aber der Aufruf gibt einen Wert vom Typ `str` zurück.
+
+## Datei file-test-data/basics/functionResult.py
+## Rückgabetyp deklariert in Zeile 4:
+
+def foo(i: int) -> [0;31m[1mint[0;0m:
+
+## Fehlerhaftes return in Zeile 5:
+
+ return "foo_" + str(i)
+
+## Aufruf in Zeile 7 verursacht das fehlerhafte return:
+
+foo(1)
\ No newline at end of file
diff --git a/python/file-test-data/basics/functionResult.err_en b/python/file-test-data/basics/functionResult.err_en
new file mode 100644
index 00000000..56189aa0
--- /dev/null
+++ b/python/file-test-data/basics/functionResult.err_en
@@ -0,0 +1,23 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionResult.py", line 7, in
+ foo(1)
+ File "file-test-data/basics/functionResult.py", line 5, in foo
+ return "foo_" + str(i)
+
+WyppTypeError: "foo_1"
+
+Expecting return value of type `int` when calling function `foo`.
+But the call returns a value of type `str`.
+
+## File file-test-data/basics/functionResult.py
+## Result type declared in line 4:
+
+def foo(i: int) -> [0;31m[1mint[0;0m:
+
+## Problematic return in line 5:
+
+ return "foo_" + str(i)
+
+## Call in line 7 causes the problematic return:
+
+foo(1)
\ No newline at end of file
diff --git a/python/test-data/testComplex.out b/python/file-test-data/basics/functionResult.out
similarity index 100%
rename from python/test-data/testComplex.out
rename to python/file-test-data/basics/functionResult.out
diff --git a/python/test-data/testConcat.err b/python/file-test-data/basics/functionResult.out_en
similarity index 100%
rename from python/test-data/testConcat.err
rename to python/file-test-data/basics/functionResult.out_en
diff --git a/python/file-test-data/basics/functionResult.py b/python/file-test-data/basics/functionResult.py
new file mode 100644
index 00000000..52439efb
--- /dev/null
+++ b/python/file-test-data/basics/functionResult.py
@@ -0,0 +1,7 @@
+def bar(s: str) -> int:
+ return len(s)
+
+def foo(i: int) -> int:
+ return "foo_" + str(i)
+
+foo(1)
diff --git a/python/file-test-data/basics/functionResult2.err b/python/file-test-data/basics/functionResult2.err
new file mode 100644
index 00000000..ab040019
--- /dev/null
+++ b/python/file-test-data/basics/functionResult2.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionResult2.py", line 7, in
+ foo(1)
+ File "file-test-data/basics/functionResult2.py", line 5, in foo
+ return "foo_" + str(i)
+
+WyppTypeError: "foo_1"
+
+Kein Rückgabewert erwartet bei Aufruf der Funktion `foo`.
+Aber der Aufruf gibt einen Wert vom Typ `str` zurück.
+
+## Datei file-test-data/basics/functionResult2.py
+## Fehlerhaftes return in Zeile 5:
+
+ return "foo_" + str(i)
+
+## Aufruf in Zeile 7 verursacht das fehlerhafte return:
+
+foo(1)
\ No newline at end of file
diff --git a/python/file-test-data/basics/functionResult2.err_en b/python/file-test-data/basics/functionResult2.err_en
new file mode 100644
index 00000000..1d4c4f31
--- /dev/null
+++ b/python/file-test-data/basics/functionResult2.err_en
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/functionResult2.py", line 7, in
+ foo(1)
+ File "file-test-data/basics/functionResult2.py", line 5, in foo
+ return "foo_" + str(i)
+
+WyppTypeError: "foo_1"
+
+Expecting no return value when calling function `foo`.
+But the call returns a value of type `str`.
+
+## File file-test-data/basics/functionResult2.py
+## Problematic return in line 5:
+
+ return "foo_" + str(i)
+
+## Call in line 7 causes the problematic return:
+
+foo(1)
\ No newline at end of file
diff --git a/python/test-data/testCopy.err b/python/file-test-data/basics/functionResult2.out
similarity index 100%
rename from python/test-data/testCopy.err
rename to python/file-test-data/basics/functionResult2.out
diff --git a/python/test-data/testDeepEqBug.err b/python/file-test-data/basics/functionResult2.out_en
similarity index 100%
rename from python/test-data/testDeepEqBug.err
rename to python/file-test-data/basics/functionResult2.out_en
diff --git a/python/file-test-data/basics/functionResult2.py b/python/file-test-data/basics/functionResult2.py
new file mode 100644
index 00000000..fd81e2bf
--- /dev/null
+++ b/python/file-test-data/basics/functionResult2.py
@@ -0,0 +1,7 @@
+def bar(s: str) -> int:
+ return len(s)
+
+def foo(i: int):
+ return "foo_" + str(i)
+
+foo(1)
diff --git a/python/test-data/testDict.err b/python/file-test-data/basics/functionResult_ok.err
similarity index 100%
rename from python/test-data/testDict.err
rename to python/file-test-data/basics/functionResult_ok.err
diff --git a/python/file-test-data/basics/functionResult_ok.out b/python/file-test-data/basics/functionResult_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/functionResult_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/functionResult_ok.py b/python/file-test-data/basics/functionResult_ok.py
new file mode 100644
index 00000000..9f86377b
--- /dev/null
+++ b/python/file-test-data/basics/functionResult_ok.py
@@ -0,0 +1,5 @@
+def foo(i: int) -> int:
+ return i + 1
+
+foo(1)
+print('ok')
diff --git a/python/file-test-data/basics/iterator.err b/python/file-test-data/basics/iterator.err
new file mode 100644
index 00000000..461a166c
--- /dev/null
+++ b/python/file-test-data/basics/iterator.err
@@ -0,0 +1,23 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/iterator.py", line 6, in
+ g = my_generator()
+ File "file-test-data/basics/iterator.py", line 4, in my_generator
+ return 1
+
+WyppTypeError: 1
+
+Rückgabewert vom Typ `Iterator[int]` erwartet bei Aufruf der Funktion `my_generator`.
+Aber der Aufruf gibt einen Wert vom Typ `int` zurück.
+
+## Datei file-test-data/basics/iterator.py
+## Rückgabetyp deklariert in Zeile 3:
+
+def my_generator() -> [0;31m[1mIterator[int][0;0m:
+
+## Fehlerhaftes return in Zeile 4:
+
+ return 1
+
+## Aufruf in Zeile 6 verursacht das fehlerhafte return:
+
+g = [0;31m[1mmy_generator()[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/basics/iterator.err_en b/python/file-test-data/basics/iterator.err_en
new file mode 100644
index 00000000..ce4a4c16
--- /dev/null
+++ b/python/file-test-data/basics/iterator.err_en
@@ -0,0 +1,23 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/iterator.py", line 6, in
+ g = my_generator()
+ File "file-test-data/basics/iterator.py", line 4, in my_generator
+ return 1
+
+WyppTypeError: 1
+
+Expecting return value of type `Iterator[int]` when calling function `my_generator`.
+But the call returns a value of type `int`.
+
+## File file-test-data/basics/iterator.py
+## Result type declared in line 3:
+
+def my_generator() -> [0;31m[1mIterator[int][0;0m:
+
+## Problematic return in line 4:
+
+ return 1
+
+## Call in line 6 causes the problematic return:
+
+g = [0;31m[1mmy_generator()[0;0m
\ No newline at end of file
diff --git a/python/test-data/testDisappearingObject_01.err b/python/file-test-data/basics/iterator.out
similarity index 100%
rename from python/test-data/testDisappearingObject_01.err
rename to python/file-test-data/basics/iterator.out
diff --git a/python/test-data/testDisappearingObject_02.err b/python/file-test-data/basics/iterator.out_en
similarity index 100%
rename from python/test-data/testDisappearingObject_02.err
rename to python/file-test-data/basics/iterator.out_en
diff --git a/python/file-test-data/basics/iterator.py b/python/file-test-data/basics/iterator.py
new file mode 100644
index 00000000..cc0c7e4b
--- /dev/null
+++ b/python/file-test-data/basics/iterator.py
@@ -0,0 +1,8 @@
+from typing import *
+
+def my_generator() -> Iterator[int]:
+ return 1
+
+g = my_generator()
+for x in my_generator():
+ print(x)
diff --git a/python/test-data/testDisappearingObject_02.out b/python/file-test-data/basics/iterator_ok.err
similarity index 100%
rename from python/test-data/testDisappearingObject_02.out
rename to python/file-test-data/basics/iterator_ok.err
diff --git a/python/file-test-data/basics/iterator_ok.out b/python/file-test-data/basics/iterator_ok.out
new file mode 100644
index 00000000..7e3104bf
--- /dev/null
+++ b/python/file-test-data/basics/iterator_ok.out
@@ -0,0 +1,3 @@
+6
+7
+ok
diff --git a/python/file-test-data/basics/iterator_ok.py b/python/file-test-data/basics/iterator_ok.py
new file mode 100644
index 00000000..8baf1cb4
--- /dev/null
+++ b/python/file-test-data/basics/iterator_ok.py
@@ -0,0 +1,9 @@
+from wypp import *
+
+def my_generator() -> Iterator[int]:
+ yield 6
+ yield "7"
+
+for x in my_generator():
+ print(x)
+print('ok')
diff --git a/python/file-test-data/basics/kwargs.err b/python/file-test-data/basics/kwargs.err
new file mode 100644
index 00000000..d7c8f199
--- /dev/null
+++ b/python/file-test-data/basics/kwargs.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/kwargs.py", line 4, in
+ foo(1, y=1, z=2)
+
+WyppTypeError: 2
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `str` als Argument `z`.
+Aber der übergebene Wert hat Typ `int`.
+
+## Datei file-test-data/basics/kwargs.py
+## Fehlerhafter Aufruf in Zeile 4:
+
+foo(1, y=1, z=2)
+
+## Typ deklariert in Zeile 1:
+
+def foo(x: int, y: int, [0;31m[1mz: str[0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/basics/kwargs.err_en b/python/file-test-data/basics/kwargs.err_en
new file mode 100644
index 00000000..7a482090
--- /dev/null
+++ b/python/file-test-data/basics/kwargs.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/kwargs.py", line 4, in
+ foo(1, y=1, z=2)
+
+WyppTypeError: 2
+
+The call of function `foo` expects value of type `str` as argument `z`.
+But the value given has type `int`.
+
+## File file-test-data/basics/kwargs.py
+## Problematic call in line 4:
+
+foo(1, y=1, z=2)
+
+## Type declared in line 1:
+
+def foo(x: int, y: int, [0;31m[1mz: str[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testDoubleWrappingDicts.err b/python/file-test-data/basics/kwargs.out
similarity index 100%
rename from python/test-data/testDoubleWrappingDicts.err
rename to python/file-test-data/basics/kwargs.out
diff --git a/python/test-data/testForwardRef.err b/python/file-test-data/basics/kwargs.out_en
similarity index 100%
rename from python/test-data/testForwardRef.err
rename to python/file-test-data/basics/kwargs.out_en
diff --git a/python/file-test-data/basics/kwargs.py b/python/file-test-data/basics/kwargs.py
new file mode 100644
index 00000000..224f67ba
--- /dev/null
+++ b/python/file-test-data/basics/kwargs.py
@@ -0,0 +1,4 @@
+def foo(x: int, y: int, z: str) -> int:
+ return x + y + len(z)
+
+foo(1, y=1, z=2)
diff --git a/python/file-test-data/basics/kwargs2.err b/python/file-test-data/basics/kwargs2.err
new file mode 100644
index 00000000..0fbc3b16
--- /dev/null
+++ b/python/file-test-data/basics/kwargs2.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/kwargs2.py", line 4, in
+ foo(1, z=2, y='foo')
+
+WyppTypeError: 2
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `str` als Argument `z`.
+Aber der übergebene Wert hat Typ `int`.
+
+## Datei file-test-data/basics/kwargs2.py
+## Fehlerhafter Aufruf in Zeile 4:
+
+foo(1, z=2, y='foo')
+
+## Typ deklariert in Zeile 1:
+
+def foo(x: int, y: int, [0;31m[1mz: str[0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/basics/kwargs2.err_en b/python/file-test-data/basics/kwargs2.err_en
new file mode 100644
index 00000000..67437dae
--- /dev/null
+++ b/python/file-test-data/basics/kwargs2.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/kwargs2.py", line 4, in
+ foo(1, z=2, y='foo')
+
+WyppTypeError: 2
+
+The call of function `foo` expects value of type `str` as argument `z`.
+But the value given has type `int`.
+
+## File file-test-data/basics/kwargs2.py
+## Problematic call in line 4:
+
+foo(1, z=2, y='foo')
+
+## Type declared in line 1:
+
+def foo(x: int, y: int, [0;31m[1mz: str[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testForwardRef.out b/python/file-test-data/basics/kwargs2.out
similarity index 100%
rename from python/test-data/testForwardRef.out
rename to python/file-test-data/basics/kwargs2.out
diff --git a/python/test-data/testForwardRef1.err b/python/file-test-data/basics/kwargs2.out_en
similarity index 100%
rename from python/test-data/testForwardRef1.err
rename to python/file-test-data/basics/kwargs2.out_en
diff --git a/python/file-test-data/basics/kwargs2.py b/python/file-test-data/basics/kwargs2.py
new file mode 100644
index 00000000..95eda6c6
--- /dev/null
+++ b/python/file-test-data/basics/kwargs2.py
@@ -0,0 +1,4 @@
+def foo(x: int, y: int, z: str) -> int:
+ return x + y + len(z)
+
+foo(1, z=2, y='foo')
diff --git a/python/test-data/testForwardRef2.out b/python/file-test-data/basics/kwargs_ok.err
similarity index 100%
rename from python/test-data/testForwardRef2.out
rename to python/file-test-data/basics/kwargs_ok.err
diff --git a/python/file-test-data/basics/kwargs_ok.out b/python/file-test-data/basics/kwargs_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/kwargs_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/kwargs_ok.py b/python/file-test-data/basics/kwargs_ok.py
new file mode 100644
index 00000000..2851e3bc
--- /dev/null
+++ b/python/file-test-data/basics/kwargs_ok.py
@@ -0,0 +1,5 @@
+def foo(x: int, y: int, z: str) -> int:
+ return x + y + len(z)
+
+foo(1, z='foo', y=2)
+print('ok')
diff --git a/python/file-test-data/basics/listArg.err b/python/file-test-data/basics/listArg.err
new file mode 100644
index 00000000..57676ddc
--- /dev/null
+++ b/python/file-test-data/basics/listArg.err
@@ -0,0 +1,16 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/listArg.py", line 4, in
+ foo([1, 2, "3"])
+
+WyppTypeError: [1, 2, '3']
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `list[int]` als erstes Argument.
+
+## Datei file-test-data/basics/listArg.py
+## Fehlerhafter Aufruf in Zeile 4:
+
+foo([1, 2, "3"])
+
+## Typ deklariert in Zeile 1:
+
+def foo([0;31m[1ml: list[int][0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/basics/listArg.err_en b/python/file-test-data/basics/listArg.err_en
new file mode 100644
index 00000000..b6fc16a9
--- /dev/null
+++ b/python/file-test-data/basics/listArg.err_en
@@ -0,0 +1,16 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/listArg.py", line 4, in
+ foo([1, 2, "3"])
+
+WyppTypeError: [1, 2, '3']
+
+The call of function `foo` expects value of type `list[int]` as 1st argument.
+
+## File file-test-data/basics/listArg.py
+## Problematic call in line 4:
+
+foo([1, 2, "3"])
+
+## Type declared in line 1:
+
+def foo([0;31m[1ml: list[int][0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testForwardRef3.err b/python/file-test-data/basics/listArg.out
similarity index 100%
rename from python/test-data/testForwardRef3.err
rename to python/file-test-data/basics/listArg.out
diff --git a/python/test-data/testForwardRef4.out b/python/file-test-data/basics/listArg.out_en
similarity index 100%
rename from python/test-data/testForwardRef4.out
rename to python/file-test-data/basics/listArg.out_en
diff --git a/python/file-test-data/basics/listArg.py b/python/file-test-data/basics/listArg.py
new file mode 100644
index 00000000..2917bc3e
--- /dev/null
+++ b/python/file-test-data/basics/listArg.py
@@ -0,0 +1,4 @@
+def foo(l: list[int]) -> int:
+ return len(l)
+
+foo([1, 2, "3"])
diff --git a/python/test-data/testForwardRef5.out b/python/file-test-data/basics/listArg_ok.err
similarity index 100%
rename from python/test-data/testForwardRef5.out
rename to python/file-test-data/basics/listArg_ok.err
diff --git a/python/file-test-data/basics/listArg_ok.out b/python/file-test-data/basics/listArg_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/listArg_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/listArg_ok.py b/python/file-test-data/basics/listArg_ok.py
new file mode 100644
index 00000000..b5edecae
--- /dev/null
+++ b/python/file-test-data/basics/listArg_ok.py
@@ -0,0 +1,5 @@
+def foo(l: list[int]) -> int:
+ return len(l)
+
+foo([1, 2, 3])
+print('ok')
diff --git a/python/file-test-data/basics/listResult.err b/python/file-test-data/basics/listResult.err
new file mode 100644
index 00000000..3330f737
--- /dev/null
+++ b/python/file-test-data/basics/listResult.err
@@ -0,0 +1,22 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/listResult.py", line 5, in
+ foo([1, 2, 3])
+ File "file-test-data/basics/listResult.py", line 3, in foo
+ return l
+
+WyppTypeError: [1, 2, 3, '4']
+
+Rückgabewert vom Typ `list[int]` erwartet bei Aufruf der Funktion `foo`.
+
+## Datei file-test-data/basics/listResult.py
+## Rückgabetyp deklariert in Zeile 1:
+
+def foo(l: list[int]) -> [0;31m[1mlist[int][0;0m:
+
+## Fehlerhaftes return in Zeile 3:
+
+ return l
+
+## Aufruf in Zeile 5 verursacht das fehlerhafte return:
+
+foo([1, 2, 3])
\ No newline at end of file
diff --git a/python/file-test-data/basics/listResult.err_en b/python/file-test-data/basics/listResult.err_en
new file mode 100644
index 00000000..e41debdf
--- /dev/null
+++ b/python/file-test-data/basics/listResult.err_en
@@ -0,0 +1,22 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/listResult.py", line 5, in
+ foo([1, 2, 3])
+ File "file-test-data/basics/listResult.py", line 3, in foo
+ return l
+
+WyppTypeError: [1, 2, 3, '4']
+
+Expecting return value of type `list[int]` when calling function `foo`.
+
+## File file-test-data/basics/listResult.py
+## Result type declared in line 1:
+
+def foo(l: list[int]) -> [0;31m[1mlist[int][0;0m:
+
+## Problematic return in line 3:
+
+ return l
+
+## Call in line 5 causes the problematic return:
+
+foo([1, 2, 3])
\ No newline at end of file
diff --git a/python/test-data/testForwardRef6.err b/python/file-test-data/basics/listResult.out
similarity index 100%
rename from python/test-data/testForwardRef6.err
rename to python/file-test-data/basics/listResult.out
diff --git a/python/test-data/testForwardTypeInRecord.err b/python/file-test-data/basics/listResult.out_en
similarity index 100%
rename from python/test-data/testForwardTypeInRecord.err
rename to python/file-test-data/basics/listResult.out_en
diff --git a/python/file-test-data/basics/listResult.py b/python/file-test-data/basics/listResult.py
new file mode 100644
index 00000000..c1797727
--- /dev/null
+++ b/python/file-test-data/basics/listResult.py
@@ -0,0 +1,5 @@
+def foo(l: list[int]) -> list[int]:
+ l.append("4")
+ return l
+
+foo([1, 2, 3])
diff --git a/python/test-data/testForwardTypeInRecord2.err b/python/file-test-data/basics/listResult_ok.err
similarity index 100%
rename from python/test-data/testForwardTypeInRecord2.err
rename to python/file-test-data/basics/listResult_ok.err
diff --git a/python/file-test-data/basics/listResult_ok.out b/python/file-test-data/basics/listResult_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/listResult_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/listResult_ok.py b/python/file-test-data/basics/listResult_ok.py
new file mode 100644
index 00000000..df81782e
--- /dev/null
+++ b/python/file-test-data/basics/listResult_ok.py
@@ -0,0 +1,6 @@
+def foo(l: list[int]) -> list[int]:
+ l.append(4)
+ return l
+
+foo([1, 2, 3])
+print('ok')
diff --git a/python/file-test-data/basics/method.err b/python/file-test-data/basics/method.err
new file mode 100644
index 00000000..33d6ee80
--- /dev/null
+++ b/python/file-test-data/basics/method.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/method.py", line 9, in
+ c.method("2")
+
+WyppTypeError: "2"
+
+Der Aufruf der Methode `method` aus Klasse `C` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/basics/method.py
+## Fehlerhafter Aufruf in Zeile 9:
+
+c.method("2")
+
+## Typ deklariert in Zeile 5:
+
+ def method(self, [0;31m[1my: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/basics/method.err_en b/python/file-test-data/basics/method.err_en
new file mode 100644
index 00000000..458cc43a
--- /dev/null
+++ b/python/file-test-data/basics/method.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/method.py", line 9, in
+ c.method("2")
+
+WyppTypeError: "2"
+
+The call of method `method` of class `C` expects value of type `int` as 1st argument.
+But the value given has type `str`.
+
+## File file-test-data/basics/method.py
+## Problematic call in line 9:
+
+c.method("2")
+
+## Type declared in line 5:
+
+ def method(self, [0;31m[1my: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testFunEq.err b/python/file-test-data/basics/method.out
similarity index 100%
rename from python/test-data/testFunEq.err
rename to python/file-test-data/basics/method.out
diff --git a/python/test-data/testGetSource.out b/python/file-test-data/basics/method.out_en
similarity index 100%
rename from python/test-data/testGetSource.out
rename to python/file-test-data/basics/method.out_en
diff --git a/python/file-test-data/basics/method.py b/python/file-test-data/basics/method.py
new file mode 100644
index 00000000..5229e620
--- /dev/null
+++ b/python/file-test-data/basics/method.py
@@ -0,0 +1,9 @@
+class C:
+ def __init__(self, x: int):
+ self.x = x
+
+ def method(self, y: int) -> int:
+ return self.x
+
+c = C(1)
+c.method("2")
diff --git a/python/test-data/testHintParentheses1.out b/python/file-test-data/basics/method_ok.err
similarity index 100%
rename from python/test-data/testHintParentheses1.out
rename to python/file-test-data/basics/method_ok.err
diff --git a/python/file-test-data/basics/method_ok.out b/python/file-test-data/basics/method_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/method_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/method_ok.py b/python/file-test-data/basics/method_ok.py
new file mode 100644
index 00000000..7b7d1546
--- /dev/null
+++ b/python/file-test-data/basics/method_ok.py
@@ -0,0 +1,10 @@
+class C:
+ def __init__(self, x: int):
+ self.x = x
+
+ def method(self, y: int) -> int:
+ return self.x + y
+
+c = C(1)
+c.method(2)
+print('ok')
diff --git a/python/file-test-data/basics/mutable.err b/python/file-test-data/basics/mutable.err
new file mode 100644
index 00000000..8569f41e
--- /dev/null
+++ b/python/file-test-data/basics/mutable.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/mutable.py", line 10, in
+ p.y = "foo"
+
+WyppTypeError: "foo"
+
+Attribut `y` des Records `Point` deklariert als Typ `int`.
+Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden.
+
+## Datei file-test-data/basics/mutable.py
+## Fehlerhafte Zuweisung in Zeile 10:
+
+p.y = "foo"
+
+## Typ deklariert in Zeile 6:
+
+ [0;31m[1my: int[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/basics/mutable.err_en b/python/file-test-data/basics/mutable.err_en
new file mode 100644
index 00000000..1c39452c
--- /dev/null
+++ b/python/file-test-data/basics/mutable.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/mutable.py", line 10, in
+ p.y = "foo"
+
+WyppTypeError: "foo"
+
+Attribute `y` of record `Point` declared with type `int.`
+Cannot set attribute to value of type `str`.
+
+## File file-test-data/basics/mutable.py
+## Problematic assignment in line 10:
+
+p.y = "foo"
+
+## Type declared in line 6:
+
+ [0;31m[1my: int[0;0m
\ No newline at end of file
diff --git a/python/test-data/testHintParentheses2.out b/python/file-test-data/basics/mutable.out
similarity index 100%
rename from python/test-data/testHintParentheses2.out
rename to python/file-test-data/basics/mutable.out
diff --git a/python/test-data/testHintParentheses3.out b/python/file-test-data/basics/mutable.out_en
similarity index 100%
rename from python/test-data/testHintParentheses3.out
rename to python/file-test-data/basics/mutable.out_en
diff --git a/python/file-test-data/basics/mutable.py b/python/file-test-data/basics/mutable.py
new file mode 100644
index 00000000..fdb88c67
--- /dev/null
+++ b/python/file-test-data/basics/mutable.py
@@ -0,0 +1,10 @@
+from wypp import *
+
+@record(mutable=True)
+class Point:
+ x: int
+ y: int
+
+p = Point(1, 2)
+p.x = 5
+p.y = "foo"
diff --git a/python/file-test-data/basics/mutable2.err b/python/file-test-data/basics/mutable2.err
new file mode 100644
index 00000000..e3ab1fb4
--- /dev/null
+++ b/python/file-test-data/basics/mutable2.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/mutable2.py", line 8, in
+ p = Point(1, '2')
+
+WyppTypeError: '2'
+
+Der Aufruf des Konstruktors des Records `Point` erwartet Wert vom Typ `int` als zweites Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/basics/mutable2.py
+## Fehlerhafter Aufruf in Zeile 8:
+
+p = [0;31m[1mPoint(1, '2')[0;0m
+
+## Typ deklariert in Zeile 6:
+
+ [0;31m[1my: int[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/basics/mutable2.err_en b/python/file-test-data/basics/mutable2.err_en
new file mode 100644
index 00000000..515ba4ec
--- /dev/null
+++ b/python/file-test-data/basics/mutable2.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/mutable2.py", line 8, in
+ p = Point(1, '2')
+
+WyppTypeError: '2'
+
+The call of the constructor of record `Point` expects value of type `int` as 2nd argument.
+But the value given has type `str`.
+
+## File file-test-data/basics/mutable2.py
+## Problematic call in line 8:
+
+p = [0;31m[1mPoint(1, '2')[0;0m
+
+## Type declared in line 6:
+
+ [0;31m[1my: int[0;0m
\ No newline at end of file
diff --git a/python/test-data/testHof.err b/python/file-test-data/basics/mutable2.out
similarity index 100%
rename from python/test-data/testHof.err
rename to python/file-test-data/basics/mutable2.out
diff --git a/python/test-data/testImpossible.out b/python/file-test-data/basics/mutable2.out_en
similarity index 100%
rename from python/test-data/testImpossible.out
rename to python/file-test-data/basics/mutable2.out_en
diff --git a/python/file-test-data/basics/mutable2.py b/python/file-test-data/basics/mutable2.py
new file mode 100644
index 00000000..8e5f2161
--- /dev/null
+++ b/python/file-test-data/basics/mutable2.py
@@ -0,0 +1,8 @@
+from wypp import *
+
+@record(mutable=True)
+class Point:
+ x: int
+ y: int
+
+p = Point(1, '2')
diff --git a/python/test-data/testIndexError.out b/python/file-test-data/basics/mutable2_ok.err
similarity index 100%
rename from python/test-data/testIndexError.out
rename to python/file-test-data/basics/mutable2_ok.err
diff --git a/python/file-test-data/basics/mutable2_ok.out b/python/file-test-data/basics/mutable2_ok.out
new file mode 100644
index 00000000..337e5e81
--- /dev/null
+++ b/python/file-test-data/basics/mutable2_ok.out
@@ -0,0 +1 @@
+CourseM(name='Grundlagen der Informatik', teacher='Oelke', students=[])
diff --git a/python/file-test-data/basics/mutable2_ok.py b/python/file-test-data/basics/mutable2_ok.py
new file mode 100644
index 00000000..5300f178
--- /dev/null
+++ b/python/file-test-data/basics/mutable2_ok.py
@@ -0,0 +1,10 @@
+from wypp import *
+
+@record(mutable=True)
+class CourseM:
+ name: str
+ teacher: str
+ students: list[str]
+
+x = CourseM('Grundlagen der Informatik', 'Oelke', [])
+print(x)
diff --git a/python/test-data/testIndexSeq.err b/python/file-test-data/basics/mutable_ok.err
similarity index 100%
rename from python/test-data/testIndexSeq.err
rename to python/file-test-data/basics/mutable_ok.err
diff --git a/python/file-test-data/basics/mutable_ok.out b/python/file-test-data/basics/mutable_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/mutable_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/mutable_ok.py b/python/file-test-data/basics/mutable_ok.py
new file mode 100644
index 00000000..a1eb6aee
--- /dev/null
+++ b/python/file-test-data/basics/mutable_ok.py
@@ -0,0 +1,10 @@
+from wypp import *
+
+@record(mutable=True)
+class Point:
+ x: int
+ y: int
+
+p = Point(1, 2)
+p.x = 5
+print('ok')
diff --git a/python/file-test-data/basics/nested.err b/python/file-test-data/basics/nested.err
new file mode 100644
index 00000000..d814332f
--- /dev/null
+++ b/python/file-test-data/basics/nested.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/nested.py", line 4, in
+ foo(42)
+
+WyppTypeError: 42
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `list[list[int]]` als erstes Argument.
+Aber der übergebene Wert hat Typ `int`.
+
+## Datei file-test-data/basics/nested.py
+## Fehlerhafter Aufruf in Zeile 4:
+
+foo(42)
+
+## Typ deklariert in Zeile 1:
+
+def foo([0;31m[1ml: list[list[int]][0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testInferReturnType.err b/python/file-test-data/basics/nested.out
similarity index 100%
rename from python/test-data/testInferReturnType.err
rename to python/file-test-data/basics/nested.out
diff --git a/python/file-test-data/basics/nested.py b/python/file-test-data/basics/nested.py
new file mode 100644
index 00000000..7287f825
--- /dev/null
+++ b/python/file-test-data/basics/nested.py
@@ -0,0 +1,4 @@
+def foo(l: list[list[int]]) -> int:
+ return len(l)
+
+foo(42)
diff --git a/python/file-test-data/basics/nestedFun.err b/python/file-test-data/basics/nestedFun.err
new file mode 100644
index 00000000..84ff8062
--- /dev/null
+++ b/python/file-test-data/basics/nestedFun.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/nestedFun.py", line 9, in
+ foo(1)
+ File "file-test-data/basics/nestedFun.py", line 7, in foo
+ return bar("foo")
+
+WyppTypeError: "foo"
+
+Der Aufruf der Funktion `bar` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/basics/nestedFun.py
+## Fehlerhafter Aufruf in Zeile 7:
+
+ return bar([0;31m[1m"foo"[0;0m)
+
+## Typ deklariert in Zeile 5:
+
+ def bar([0;31m[1mj: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/basics/nestedFun.err_en b/python/file-test-data/basics/nestedFun.err_en
new file mode 100644
index 00000000..e9d766fd
--- /dev/null
+++ b/python/file-test-data/basics/nestedFun.err_en
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/nestedFun.py", line 9, in
+ foo(1)
+ File "file-test-data/basics/nestedFun.py", line 7, in foo
+ return bar("foo")
+
+WyppTypeError: "foo"
+
+The call of function `bar` expects value of type `int` as 1st argument.
+But the value given has type `str`.
+
+## File file-test-data/basics/nestedFun.py
+## Problematic call in line 7:
+
+ return bar([0;31m[1m"foo"[0;0m)
+
+## Type declared in line 5:
+
+ def bar([0;31m[1mj: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testInferReturnType.out b/python/file-test-data/basics/nestedFun.out
similarity index 100%
rename from python/test-data/testInferReturnType.out
rename to python/file-test-data/basics/nestedFun.out
diff --git a/python/test-data/testInferReturnType2.out b/python/file-test-data/basics/nestedFun.out_en
similarity index 100%
rename from python/test-data/testInferReturnType2.out
rename to python/file-test-data/basics/nestedFun.out_en
diff --git a/python/file-test-data/basics/nestedFun.py b/python/file-test-data/basics/nestedFun.py
new file mode 100644
index 00000000..0cd20063
--- /dev/null
+++ b/python/file-test-data/basics/nestedFun.py
@@ -0,0 +1,9 @@
+def bar(s: str) -> int:
+ return len(s)
+
+def foo(i: int) -> int:
+ def bar(j: int) -> int:
+ return j + 1
+ return bar("foo")
+
+foo(1)
diff --git a/python/test-data/testInferReturnType3.err b/python/file-test-data/basics/nosig_ok.err
similarity index 100%
rename from python/test-data/testInferReturnType3.err
rename to python/file-test-data/basics/nosig_ok.err
diff --git a/python/file-test-data/basics/nosig_ok.out b/python/file-test-data/basics/nosig_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/nosig_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/nosig_ok.py b/python/file-test-data/basics/nosig_ok.py
new file mode 100644
index 00000000..3b934537
--- /dev/null
+++ b/python/file-test-data/basics/nosig_ok.py
@@ -0,0 +1,14 @@
+def foo(i, j):
+ return i + j
+
+class C:
+ def bar(self, x):
+ return x + 1
+
+k = foo(1, 2)
+c = C()
+r = c.bar(k)
+if r == 4:
+ print('ok')
+else:
+ raise ValueError('unexpected result')
diff --git a/python/file-test-data/basics/optionalArgs.err b/python/file-test-data/basics/optionalArgs.err
new file mode 100644
index 00000000..100e8290
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalArgs.py", line 1, in
+ def foo(i: int, s: str=2):
+
+WyppTypeError: 2
+
+Default-Wert des Parameters `s` der Funktion `foo` muss vom Typ `str` sein.
+Aber der Default-Wert hat Typ `int`.
+
+## Datei file-test-data/basics/optionalArgs.py
+## Parameter deklariert in Zeile 1:
+
+def foo(i: int, [0;31m[1ms: str[0;0m=2):
\ No newline at end of file
diff --git a/python/file-test-data/basics/optionalArgs.err_en b/python/file-test-data/basics/optionalArgs.err_en
new file mode 100644
index 00000000..0c7ceff6
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs.err_en
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalArgs.py", line 1, in
+ def foo(i: int, s: str=2):
+
+WyppTypeError: 2
+
+Default value for parameter `s` of function `foo` must have type `str`.
+But the default value has type `int`.
+
+## File file-test-data/basics/optionalArgs.py
+## Parameter declared in line 1:
+
+def foo(i: int, [0;31m[1ms: str[0;0m=2):
\ No newline at end of file
diff --git a/python/test-data/testInferReturnType3.out b/python/file-test-data/basics/optionalArgs.out
similarity index 100%
rename from python/test-data/testInferReturnType3.out
rename to python/file-test-data/basics/optionalArgs.out
diff --git a/python/test-data/testInferReturnType4.out b/python/file-test-data/basics/optionalArgs.out_en
similarity index 100%
rename from python/test-data/testInferReturnType4.out
rename to python/file-test-data/basics/optionalArgs.out_en
diff --git a/python/file-test-data/basics/optionalArgs.py b/python/file-test-data/basics/optionalArgs.py
new file mode 100644
index 00000000..96a209d1
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs.py
@@ -0,0 +1,4 @@
+def foo(i: int, s: str=2):
+ pass
+
+foo(1)
diff --git a/python/file-test-data/basics/optionalArgs2.err b/python/file-test-data/basics/optionalArgs2.err
new file mode 100644
index 00000000..70409aa7
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs2.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalArgs2.py", line 4, in
+ foo(1)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Funktion `foo` benötigt mindestens 2 Argumente.
+Gegeben: 1 Argument
+
+## Datei file-test-data/basics/optionalArgs2.py
+## Aufruf in Zeile 4:
+
+foo(1)
\ No newline at end of file
diff --git a/python/file-test-data/basics/optionalArgs2.err_en b/python/file-test-data/basics/optionalArgs2.err_en
new file mode 100644
index 00000000..f2505277
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs2.err_en
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalArgs2.py", line 4, in
+ foo(1)
+
+WyppTypeError: argument count mismatch
+
+Function `foo` takes at least 2 arguments.
+Given: 1 argument
+
+## File file-test-data/basics/optionalArgs2.py
+## Call in line 4:
+
+foo(1)
\ No newline at end of file
diff --git a/python/test-data/testInvalidLiteral.out b/python/file-test-data/basics/optionalArgs2.out
similarity index 100%
rename from python/test-data/testInvalidLiteral.out
rename to python/file-test-data/basics/optionalArgs2.out
diff --git a/python/test-data/testIterable1.err b/python/file-test-data/basics/optionalArgs2.out_en
similarity index 100%
rename from python/test-data/testIterable1.err
rename to python/file-test-data/basics/optionalArgs2.out_en
diff --git a/python/file-test-data/basics/optionalArgs2.py b/python/file-test-data/basics/optionalArgs2.py
new file mode 100644
index 00000000..3a145537
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs2.py
@@ -0,0 +1,4 @@
+def foo(i: int, j: int, s: int=2):
+ pass
+
+foo(1)
diff --git a/python/file-test-data/basics/optionalArgs3.err b/python/file-test-data/basics/optionalArgs3.err
new file mode 100644
index 00000000..7d626a42
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs3.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalArgs3.py", line 4, in
+ foo(1, 2, 3, 4)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Funktion `foo` akzeptiert höchstens 3 Argumente.
+Gegeben: 4 Argumente
+
+## Datei file-test-data/basics/optionalArgs3.py
+## Aufruf in Zeile 4:
+
+foo(1, 2, 3, 4)
\ No newline at end of file
diff --git a/python/file-test-data/basics/optionalArgs3.err_en b/python/file-test-data/basics/optionalArgs3.err_en
new file mode 100644
index 00000000..26e80eb9
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs3.err_en
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalArgs3.py", line 4, in
+ foo(1, 2, 3, 4)
+
+WyppTypeError: argument count mismatch
+
+Function `foo` takes at most 3 arguments.
+Given: 4 arguments
+
+## File file-test-data/basics/optionalArgs3.py
+## Call in line 4:
+
+foo(1, 2, 3, 4)
\ No newline at end of file
diff --git a/python/test-data/testIterable2.err b/python/file-test-data/basics/optionalArgs3.out
similarity index 100%
rename from python/test-data/testIterable2.err
rename to python/file-test-data/basics/optionalArgs3.out
diff --git a/python/test-data/testIterable3.err b/python/file-test-data/basics/optionalArgs3.out_en
similarity index 100%
rename from python/test-data/testIterable3.err
rename to python/file-test-data/basics/optionalArgs3.out_en
diff --git a/python/file-test-data/basics/optionalArgs3.py b/python/file-test-data/basics/optionalArgs3.py
new file mode 100644
index 00000000..7c9c48d0
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs3.py
@@ -0,0 +1,4 @@
+def foo(i: int, j: int, s: int=2):
+ pass
+
+foo(1, 2, 3, 4)
diff --git a/python/file-test-data/basics/optionalArgs4.err b/python/file-test-data/basics/optionalArgs4.err
new file mode 100644
index 00000000..642f35ba
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs4.err
@@ -0,0 +1,15 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalArgs4.py", line 1, in
+ class C:
+ File "file-test-data/basics/optionalArgs4.py", line 2, in C
+ def __init__(i: int, s: str=2):
+
+WyppTypeError: 2
+
+Default-Wert des Parameters `s` der Methode `__init__` aus Klasse `C` muss vom Typ `str` sein.
+Aber der Default-Wert hat Typ `int`.
+
+## Datei file-test-data/basics/optionalArgs4.py
+## Parameter deklariert in Zeile 2:
+
+ def __init__(i: int, [0;31m[1ms: str[0;0m=2):
\ No newline at end of file
diff --git a/python/file-test-data/basics/optionalArgs4.err_en b/python/file-test-data/basics/optionalArgs4.err_en
new file mode 100644
index 00000000..bef9dca7
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs4.err_en
@@ -0,0 +1,15 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalArgs4.py", line 1, in
+ class C:
+ File "file-test-data/basics/optionalArgs4.py", line 2, in C
+ def __init__(i: int, s: str=2):
+
+WyppTypeError: 2
+
+Default value for parameter `s` of method `__init__` in class `C` must have type `str`.
+But the default value has type `int`.
+
+## File file-test-data/basics/optionalArgs4.py
+## Parameter declared in line 2:
+
+ def __init__(i: int, [0;31m[1ms: str[0;0m=2):
\ No newline at end of file
diff --git a/python/test-data/testIterable4.err b/python/file-test-data/basics/optionalArgs4.out
similarity index 100%
rename from python/test-data/testIterable4.err
rename to python/file-test-data/basics/optionalArgs4.out
diff --git a/python/test-data/testIterable5.err b/python/file-test-data/basics/optionalArgs4.out_en
similarity index 100%
rename from python/test-data/testIterable5.err
rename to python/file-test-data/basics/optionalArgs4.out_en
diff --git a/python/file-test-data/basics/optionalArgs4.py b/python/file-test-data/basics/optionalArgs4.py
new file mode 100644
index 00000000..6174209d
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs4.py
@@ -0,0 +1,5 @@
+class C:
+ def __init__(i: int, s: str=2):
+ pass
+
+C(1)
diff --git a/python/test-data/testIterable6.err b/python/file-test-data/basics/optionalArgs_ok.err
similarity index 100%
rename from python/test-data/testIterable6.err
rename to python/file-test-data/basics/optionalArgs_ok.err
diff --git a/python/file-test-data/basics/optionalArgs_ok.out b/python/file-test-data/basics/optionalArgs_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/optionalArgs_ok.py b/python/file-test-data/basics/optionalArgs_ok.py
new file mode 100644
index 00000000..3abb6630
--- /dev/null
+++ b/python/file-test-data/basics/optionalArgs_ok.py
@@ -0,0 +1,5 @@
+def foo(i: int, s: str='2'):
+ pass
+
+foo(1)
+print('ok')
diff --git a/python/file-test-data/basics/optionalAttr.err b/python/file-test-data/basics/optionalAttr.err
new file mode 100644
index 00000000..43d19e81
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalAttr.py", line 3, in
+ @record
+
+WyppTypeError: 'foo'
+
+Default-Wert des Attributs `y` des Records `C` muss vom Typ `int` sein.
+Aber der Default-Wert hat Typ `str`.
+
+## Datei file-test-data/basics/optionalAttr.py
+## Parameter deklariert in Zeile 6:
+
+ [0;31m[1my: int = 'foo'[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/basics/optionalAttr.err_en b/python/file-test-data/basics/optionalAttr.err_en
new file mode 100644
index 00000000..21401c73
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr.err_en
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalAttr.py", line 3, in
+ @record
+
+WyppTypeError: 'foo'
+
+Default value for attribute `y` of record `C` must have type `int`.
+But the default value has type `str`.
+
+## File file-test-data/basics/optionalAttr.py
+## Parameter declared in line 6:
+
+ [0;31m[1my: int = 'foo'[0;0m
\ No newline at end of file
diff --git a/python/test-data/testIterator.err b/python/file-test-data/basics/optionalAttr.out
similarity index 100%
rename from python/test-data/testIterator.err
rename to python/file-test-data/basics/optionalAttr.out
diff --git a/python/test-data/testIterator2.err b/python/file-test-data/basics/optionalAttr.out_en
similarity index 100%
rename from python/test-data/testIterator2.err
rename to python/file-test-data/basics/optionalAttr.out_en
diff --git a/python/file-test-data/basics/optionalAttr.py b/python/file-test-data/basics/optionalAttr.py
new file mode 100644
index 00000000..dbfbae84
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr.py
@@ -0,0 +1,6 @@
+from wypp import *
+
+@record
+class C:
+ x: int
+ y: int = 'foo'
diff --git a/python/file-test-data/basics/optionalAttr2.err b/python/file-test-data/basics/optionalAttr2.err
new file mode 100644
index 00000000..98e5620a
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr2.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalAttr2.py", line 9, in
+ c = C(1)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Konstruktor des Records `C` benötigt mindestens 2 Argumente.
+Gegeben: 1 Argument
+
+## Datei file-test-data/basics/optionalAttr2.py
+## Aufruf in Zeile 9:
+
+c = [0;31m[1mC(1)[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/basics/optionalAttr2.err_en b/python/file-test-data/basics/optionalAttr2.err_en
new file mode 100644
index 00000000..030f5e0a
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr2.err_en
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalAttr2.py", line 9, in
+ c = C(1)
+
+WyppTypeError: argument count mismatch
+
+Constructor of record `C` takes at least 2 arguments.
+Given: 1 argument
+
+## File file-test-data/basics/optionalAttr2.py
+## Call in line 9:
+
+c = [0;31m[1mC(1)[0;0m
\ No newline at end of file
diff --git a/python/test-data/testIterator3.err b/python/file-test-data/basics/optionalAttr2.out
similarity index 100%
rename from python/test-data/testIterator3.err
rename to python/file-test-data/basics/optionalAttr2.out
diff --git a/python/test-data/testIterator4.err b/python/file-test-data/basics/optionalAttr2.out_en
similarity index 100%
rename from python/test-data/testIterator4.err
rename to python/file-test-data/basics/optionalAttr2.out_en
diff --git a/python/file-test-data/basics/optionalAttr2.py b/python/file-test-data/basics/optionalAttr2.py
new file mode 100644
index 00000000..3eef77fe
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr2.py
@@ -0,0 +1,9 @@
+from wypp import *
+
+@record
+class C:
+ x: int
+ y: int
+ z: int = 2
+
+c = C(1)
diff --git a/python/file-test-data/basics/optionalAttr3.err b/python/file-test-data/basics/optionalAttr3.err
new file mode 100644
index 00000000..9b406713
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr3.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalAttr3.py", line 9, in
+ c = C(1, 2, 3, 4)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Konstruktor des Records `C` akzeptiert höchstens 3 Argumente.
+Gegeben: 4 Argumente
+
+## Datei file-test-data/basics/optionalAttr3.py
+## Aufruf in Zeile 9:
+
+c = [0;31m[1mC(1, 2, 3, 4)[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/basics/optionalAttr3.err_en b/python/file-test-data/basics/optionalAttr3.err_en
new file mode 100644
index 00000000..17976c56
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr3.err_en
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/optionalAttr3.py", line 9, in
+ c = C(1, 2, 3, 4)
+
+WyppTypeError: argument count mismatch
+
+Constructor of record `C` takes at most 3 arguments.
+Given: 4 arguments
+
+## File file-test-data/basics/optionalAttr3.py
+## Call in line 9:
+
+c = [0;31m[1mC(1, 2, 3, 4)[0;0m
\ No newline at end of file
diff --git a/python/test-data/testLiteral1.out b/python/file-test-data/basics/optionalAttr3.out
similarity index 100%
rename from python/test-data/testLiteral1.out
rename to python/file-test-data/basics/optionalAttr3.out
diff --git a/python/test-data/testLiteralInstanceOf.err b/python/file-test-data/basics/optionalAttr3.out_en
similarity index 100%
rename from python/test-data/testLiteralInstanceOf.err
rename to python/file-test-data/basics/optionalAttr3.out_en
diff --git a/python/file-test-data/basics/optionalAttr3.py b/python/file-test-data/basics/optionalAttr3.py
new file mode 100644
index 00000000..735db5cf
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr3.py
@@ -0,0 +1,9 @@
+from wypp import *
+
+@record
+class C:
+ x: int
+ y: int
+ z: int = 2
+
+c = C(1, 2, 3, 4)
diff --git a/python/test-data/testLockFactory.out b/python/file-test-data/basics/optionalAttr_ok.err
similarity index 100%
rename from python/test-data/testLockFactory.out
rename to python/file-test-data/basics/optionalAttr_ok.err
diff --git a/python/file-test-data/basics/optionalAttr_ok.out b/python/file-test-data/basics/optionalAttr_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/optionalAttr_ok.py b/python/file-test-data/basics/optionalAttr_ok.py
new file mode 100644
index 00000000..eb566fc6
--- /dev/null
+++ b/python/file-test-data/basics/optionalAttr_ok.py
@@ -0,0 +1,10 @@
+from wypp import *
+
+@record
+class C:
+ x: int
+ y: int = 0
+
+c1 = C(1)
+c2 = C(1, 2)
+print('ok')
diff --git a/python/file-test-data/basics/partial.err b/python/file-test-data/basics/partial.err
new file mode 100644
index 00000000..23401695
--- /dev/null
+++ b/python/file-test-data/basics/partial.err
@@ -0,0 +1,10 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/partial.py", line 1, in
+ def foo(i: int, j) -> None:
+
+WyppTypeError: Parameter `j` der Funktion `foo` benötigt eine Typannotation.
+
+## Datei file-test-data/basics/partial.py
+## Parameter deklariert in Zeile 1:
+
+def foo(i: int, [0;31m[1mj[0;0m) -> None:
\ No newline at end of file
diff --git a/python/file-test-data/basics/partial.err_en b/python/file-test-data/basics/partial.err_en
new file mode 100644
index 00000000..6f3cb523
--- /dev/null
+++ b/python/file-test-data/basics/partial.err_en
@@ -0,0 +1,10 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/partial.py", line 1, in
+ def foo(i: int, j) -> None:
+
+WyppTypeError: Parameter `j` of function `foo` requires a type annotation.
+
+## File file-test-data/basics/partial.py
+## Parameter declared in line 1:
+
+def foo(i: int, [0;31m[1mj[0;0m) -> None:
\ No newline at end of file
diff --git a/python/test-data/testLockFactory2.out b/python/file-test-data/basics/partial.out
similarity index 100%
rename from python/test-data/testLockFactory2.out
rename to python/file-test-data/basics/partial.out
diff --git a/python/test-data/testMissingReturn.out b/python/file-test-data/basics/partial.out_en
similarity index 100%
rename from python/test-data/testMissingReturn.out
rename to python/file-test-data/basics/partial.out_en
diff --git a/python/file-test-data/basics/partial.py b/python/file-test-data/basics/partial.py
new file mode 100644
index 00000000..5ed50475
--- /dev/null
+++ b/python/file-test-data/basics/partial.py
@@ -0,0 +1,4 @@
+def foo(i: int, j) -> None:
+ pass
+
+foo(1, 2)
diff --git a/python/file-test-data/basics/record.err b/python/file-test-data/basics/record.err
new file mode 100644
index 00000000..b2daf4fd
--- /dev/null
+++ b/python/file-test-data/basics/record.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/record.py", line 8, in
+ p = Person("Alice", "30")
+
+WyppTypeError: "30"
+
+Der Aufruf des Konstruktors des Records `Person` erwartet Wert vom Typ `int` als zweites Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/basics/record.py
+## Fehlerhafter Aufruf in Zeile 8:
+
+p = [0;31m[1mPerson("Alice", "30")[0;0m
+
+## Typ deklariert in Zeile 6:
+
+ [0;31m[1mage: int[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/basics/record.err_en b/python/file-test-data/basics/record.err_en
new file mode 100644
index 00000000..d3e78da1
--- /dev/null
+++ b/python/file-test-data/basics/record.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/record.py", line 8, in
+ p = Person("Alice", "30")
+
+WyppTypeError: "30"
+
+The call of the constructor of record `Person` expects value of type `int` as 2nd argument.
+But the value given has type `str`.
+
+## File file-test-data/basics/record.py
+## Problematic call in line 8:
+
+p = [0;31m[1mPerson("Alice", "30")[0;0m
+
+## Type declared in line 6:
+
+ [0;31m[1mage: int[0;0m
\ No newline at end of file
diff --git a/python/test-data/testNameErrorBug.err b/python/file-test-data/basics/record.out
similarity index 100%
rename from python/test-data/testNameErrorBug.err
rename to python/file-test-data/basics/record.out
diff --git a/python/test-data/testNameErrorBug.out b/python/file-test-data/basics/record.out_en
similarity index 100%
rename from python/test-data/testNameErrorBug.out
rename to python/file-test-data/basics/record.out_en
diff --git a/python/file-test-data/basics/record.py b/python/file-test-data/basics/record.py
new file mode 100644
index 00000000..5adf60f5
--- /dev/null
+++ b/python/file-test-data/basics/record.py
@@ -0,0 +1,8 @@
+from wypp import *
+
+@record
+class Person:
+ name: str
+ age: int
+
+p = Person("Alice", "30")
diff --git a/python/test-data/testNums.out b/python/file-test-data/basics/record_ok.err
similarity index 100%
rename from python/test-data/testNums.out
rename to python/file-test-data/basics/record_ok.err
diff --git a/python/file-test-data/basics/record_ok.out b/python/file-test-data/basics/record_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/record_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/record_ok.py b/python/file-test-data/basics/record_ok.py
new file mode 100644
index 00000000..3b5eb905
--- /dev/null
+++ b/python/file-test-data/basics/record_ok.py
@@ -0,0 +1,9 @@
+from wypp import *
+
+@record
+class Person:
+ name: str
+ age: int
+
+p = Person("Alice", 30)
+print('ok')
diff --git a/python/test-data/testOriginalTypeNames.err b/python/file-test-data/basics/sample_ok.err
similarity index 100%
rename from python/test-data/testOriginalTypeNames.err
rename to python/file-test-data/basics/sample_ok.err
diff --git a/python/file-test-data/basics/sample_ok.out b/python/file-test-data/basics/sample_ok.out
new file mode 100644
index 00000000..aaf9a472
--- /dev/null
+++ b/python/file-test-data/basics/sample_ok.out
@@ -0,0 +1 @@
+14 Tests, alle erfolgreich 😀
diff --git a/python/file-test-data/basics/sample_ok.py b/python/file-test-data/basics/sample_ok.py
new file mode 100644
index 00000000..d117e9fb
--- /dev/null
+++ b/python/file-test-data/basics/sample_ok.py
@@ -0,0 +1,132 @@
+from wypp import *
+
+Drink = Literal["Tea", "Coffee"]
+
+# berechnet wieviele Tassen ich von einem Getränk trinken darf
+def canDrink(d: Drink) -> int:
+ if d == "Tea":
+ return 5
+ elif d == "Coffee":
+ return 1
+
+# A shape is one of the following:
+# - a circle (Circle)
+# - a square (Square)
+# - an overlay of two shapes (Overlay)
+type Shape = Union['Circle', 'Square', 'Overlay']
+
+# A point consists of
+# - x (float)
+# - y (float)
+@record
+class Point:
+ x: float
+ y: float
+# Point: (float, float) -> Point
+# For some Point p
+# p.x: float
+# p.y: float
+
+# point at x=10, y=20
+p1 = Point(10, 20)
+
+# point at x=30, y=50
+p2 = Point(30, 50)
+
+# point at x=40, y=30
+p3 = Point(40, 30)
+
+# A circle consists of
+# - center (Point)
+# - radius (float)
+@record
+class Circle:
+ center: Point
+ radius: float
+
+# Circle: (Point, float) -> Circle
+# For some circle c
+# c.center: Point
+# c.radius: float
+
+# circle at p2 with radius=20
+c1 = Circle(p2, 20)
+
+# circle at p3 with radius=15
+c2 = Circle(p3, 15)
+
+# A square (parallel to the coordinate system) consists of
+# - lower-left corner (Point)
+# - size (float)
+@record
+class Square:
+ corner: Point
+ size: float
+
+# square at p1 with size=40
+s1 = Square(p1, 40)
+
+# Square: (Point, float) -> Square
+# For some square s
+# s.corner: Point
+# s.size: float
+
+# An overlay consists of
+# - top (Shape)
+# - bottom (Shape)
+@record
+class Overlay:
+ top: Shape
+ bottom: Shape
+
+# Overlay: (Shape, Shape) -> Overlay
+# For some overlay:
+# o.top: Shape
+# o.bottom: Shape
+
+# overlay of circle c1 and square s1
+o1 = Overlay(c1, s1)
+# Overlay of overlay o1 and circle c2
+o2 = Overlay(o1, c2)
+
+# Calculate the distance between two points
+def distance(p1: Point, p2: Point) -> float:
+ w = p1.x - p2.x
+ h = p1.y - p2.y
+ dist = math.sqrt(w**2 + h**2)
+ return dist
+
+# Is a point within a shape?
+def pointInShape(point: Point, shape: Shape) -> bool:
+ px = point.x
+ py = point.y
+ if type(shape) == Circle:
+ return distance(point, shape.center) <= shape.radius
+ elif type(shape) == Square:
+ corner = shape.corner
+ size = shape.size
+ return (
+ px >= corner.x and
+ px <= corner.x + size and
+ py >= corner.y and
+ py <= corner.y + size
+ )
+ elif type(shape) == Overlay:
+ return pointInShape(point, shape.top) or pointInShape(point, shape.bottom)
+ else:
+ return impossible()
+
+check(pointInShape(p2, c1), True)
+check(pointInShape(p3, c2), True)
+check(pointInShape(Point(51, 50), c1), False)
+check(pointInShape(Point(11, 21), s1), True)
+check(pointInShape(Point(49, 59), s1), True)
+check(pointInShape(Point(9, 21), s1), False)
+check(pointInShape(Point(11, 19), s1), False)
+check(pointInShape(Point(51, 59), s1), False)
+check(pointInShape(Point(49, 61), s1), False)
+check(pointInShape(Point(40, 30), c2), True)
+check(pointInShape(Point(40, 30), o2), True)
+check(pointInShape(Point(0, 0), o2), False)
+check(pointInShape(Point(30, 65), o2), True)
+check(pointInShape(Point(40, 17), o2), True)
diff --git a/python/file-test-data/basics/stack.err b/python/file-test-data/basics/stack.err
new file mode 100644
index 00000000..b99a6f58
--- /dev/null
+++ b/python/file-test-data/basics/stack.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/stack.py", line 7, in
+ factorial(5)
+ File "file-test-data/basics/stack.py", line 5, in factorial
+ return factorial(n - 1) * n
+ File "file-test-data/basics/stack.py", line 5, in factorial
+ return factorial(n - 1) * n
+ File "file-test-data/basics/stack.py", line 5, in factorial
+ return factorial(n - 1) * n
+ [Previous line repeated 2 more times]
+ File "file-test-data/basics/stack.py", line 3, in factorial
+ raise ValueError('kein Bock')
+ValueError: kein Bock
\ No newline at end of file
diff --git a/python/test-data/testRecordSetTypeForwardRef.out b/python/file-test-data/basics/stack.out
similarity index 100%
rename from python/test-data/testRecordSetTypeForwardRef.out
rename to python/file-test-data/basics/stack.out
diff --git a/python/file-test-data/basics/stack.py b/python/file-test-data/basics/stack.py
new file mode 100644
index 00000000..96c2f3bf
--- /dev/null
+++ b/python/file-test-data/basics/stack.py
@@ -0,0 +1,7 @@
+def factorial(n: int) -> int:
+ if n == 0:
+ raise ValueError('kein Bock')
+ else:
+ return factorial(n - 1) * n
+
+factorial(5)
diff --git a/python/file-test-data/basics/staticmethod.err b/python/file-test-data/basics/staticmethod.err
new file mode 100644
index 00000000..dbffb013
--- /dev/null
+++ b/python/file-test-data/basics/staticmethod.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/staticmethod.py", line 6, in
+ C.method("2")
+
+WyppTypeError: "2"
+
+Der Aufruf der Methode `method` aus Klasse `C` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/basics/staticmethod.py
+## Fehlerhafter Aufruf in Zeile 6:
+
+C.method("2")
+
+## Typ deklariert in Zeile 3:
+
+ def method([0;31m[1my: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/basics/staticmethod.err_en b/python/file-test-data/basics/staticmethod.err_en
new file mode 100644
index 00000000..70642fac
--- /dev/null
+++ b/python/file-test-data/basics/staticmethod.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/staticmethod.py", line 6, in
+ C.method("2")
+
+WyppTypeError: "2"
+
+The call of method `method` of class `C` expects value of type `int` as 1st argument.
+But the value given has type `str`.
+
+## File file-test-data/basics/staticmethod.py
+## Problematic call in line 6:
+
+C.method("2")
+
+## Type declared in line 3:
+
+ def method([0;31m[1my: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testRecordSetTypes.out b/python/file-test-data/basics/staticmethod.out
similarity index 100%
rename from python/test-data/testRecordSetTypes.out
rename to python/file-test-data/basics/staticmethod.out
diff --git a/python/test-data/testRecordTypes.out b/python/file-test-data/basics/staticmethod.out_en
similarity index 100%
rename from python/test-data/testRecordTypes.out
rename to python/file-test-data/basics/staticmethod.out_en
diff --git a/python/file-test-data/basics/staticmethod.py b/python/file-test-data/basics/staticmethod.py
new file mode 100644
index 00000000..c177637c
--- /dev/null
+++ b/python/file-test-data/basics/staticmethod.py
@@ -0,0 +1,6 @@
+class C:
+ @staticmethod
+ def method(y: int) -> int:
+ return y
+
+C.method("2")
diff --git a/python/test-data/testTodo.out b/python/file-test-data/basics/staticmethod_ok.err
similarity index 100%
rename from python/test-data/testTodo.out
rename to python/file-test-data/basics/staticmethod_ok.err
diff --git a/python/file-test-data/basics/staticmethod_ok.out b/python/file-test-data/basics/staticmethod_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/basics/staticmethod_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/basics/staticmethod_ok.py b/python/file-test-data/basics/staticmethod_ok.py
new file mode 100644
index 00000000..90b56179
--- /dev/null
+++ b/python/file-test-data/basics/staticmethod_ok.py
@@ -0,0 +1,7 @@
+class C:
+ @staticmethod
+ def method(y: int) -> int:
+ return y
+
+C.method(2)
+print('ok')
diff --git a/python/file-test-data/basics/testCallable.err b/python/file-test-data/basics/testCallable.err
new file mode 100644
index 00000000..5fe14904
--- /dev/null
+++ b/python/file-test-data/basics/testCallable.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/testCallable.py", line 6, in
+ foo(42)
+
+WyppTypeError: 42
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Callable[[int, bool], str]` als erstes Argument.
+Aber der übergebene Wert hat Typ `int`.
+
+## Datei file-test-data/basics/testCallable.py
+## Fehlerhafter Aufruf in Zeile 6:
+
+foo(42)
+
+## Typ deklariert in Zeile 3:
+
+def foo([0;31m[1mf: Callable[[int, bool], str][0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testTraceback2.out b/python/file-test-data/basics/testCallable.out
similarity index 100%
rename from python/test-data/testTraceback2.out
rename to python/file-test-data/basics/testCallable.out
diff --git a/python/file-test-data/basics/testCallable.py b/python/file-test-data/basics/testCallable.py
new file mode 100644
index 00000000..a5776112
--- /dev/null
+++ b/python/file-test-data/basics/testCallable.py
@@ -0,0 +1,6 @@
+from typing import Callable
+
+def foo(f: Callable[[int, bool], str]) -> int:
+ return 1
+
+foo(42)
diff --git a/python/file-test-data/basics/tooFewArgs.err b/python/file-test-data/basics/tooFewArgs.err
new file mode 100644
index 00000000..6d7678ef
--- /dev/null
+++ b/python/file-test-data/basics/tooFewArgs.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/tooFewArgs.py", line 4, in
+ foo(1)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Funktion `foo` benötigt 2 Argumente.
+Gegeben: 1 Argument
+
+## Datei file-test-data/basics/tooFewArgs.py
+## Aufruf in Zeile 4:
+
+foo(1)
\ No newline at end of file
diff --git a/python/test-data/testTraceback3.out b/python/file-test-data/basics/tooFewArgs.out
similarity index 100%
rename from python/test-data/testTraceback3.out
rename to python/file-test-data/basics/tooFewArgs.out
diff --git a/python/file-test-data/basics/tooFewArgs.py b/python/file-test-data/basics/tooFewArgs.py
new file mode 100644
index 00000000..846d7580
--- /dev/null
+++ b/python/file-test-data/basics/tooFewArgs.py
@@ -0,0 +1,4 @@
+def foo(i: int, j: int):
+ pass
+
+foo(1)
diff --git a/python/file-test-data/basics/tooFewAttrs.err b/python/file-test-data/basics/tooFewAttrs.err
new file mode 100644
index 00000000..37b19e92
--- /dev/null
+++ b/python/file-test-data/basics/tooFewAttrs.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/tooFewAttrs.py", line 8, in
+ c = C(1)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Konstruktor des Records `C` benötigt 2 Argumente.
+Gegeben: 1 Argument
+
+## Datei file-test-data/basics/tooFewAttrs.py
+## Aufruf in Zeile 8:
+
+c = [0;31m[1mC(1)[0;0m
\ No newline at end of file
diff --git a/python/test-data/testTypeKeyword.err b/python/file-test-data/basics/tooFewAttrs.out
similarity index 100%
rename from python/test-data/testTypeKeyword.err
rename to python/file-test-data/basics/tooFewAttrs.out
diff --git a/python/file-test-data/basics/tooFewAttrs.py b/python/file-test-data/basics/tooFewAttrs.py
new file mode 100644
index 00000000..a62dcce3
--- /dev/null
+++ b/python/file-test-data/basics/tooFewAttrs.py
@@ -0,0 +1,8 @@
+from wypp import *
+
+@record
+class C:
+ x: int
+ y: int
+
+c = C(1)
diff --git a/python/file-test-data/basics/tooManyArgs.err b/python/file-test-data/basics/tooManyArgs.err
new file mode 100644
index 00000000..c9a0ba95
--- /dev/null
+++ b/python/file-test-data/basics/tooManyArgs.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/tooManyArgs.py", line 4, in
+ foo(1, 2, 3)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Funktion `foo` benötigt 2 Argumente.
+Gegeben: 3 Argumente
+
+## Datei file-test-data/basics/tooManyArgs.py
+## Aufruf in Zeile 4:
+
+foo(1, 2, 3)
\ No newline at end of file
diff --git a/python/file-test-data/basics/tooManyArgs.err_en b/python/file-test-data/basics/tooManyArgs.err_en
new file mode 100644
index 00000000..31e349f4
--- /dev/null
+++ b/python/file-test-data/basics/tooManyArgs.err_en
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/tooManyArgs.py", line 4, in
+ foo(1, 2, 3)
+
+WyppTypeError: argument count mismatch
+
+Function `foo` takes 2 arguments.
+Given: 3 arguments
+
+## File file-test-data/basics/tooManyArgs.py
+## Call in line 4:
+
+foo(1, 2, 3)
\ No newline at end of file
diff --git a/python/test-data/testTypes1.out b/python/file-test-data/basics/tooManyArgs.out
similarity index 100%
rename from python/test-data/testTypes1.out
rename to python/file-test-data/basics/tooManyArgs.out
diff --git a/python/test-data/testTypes2.err-notypes b/python/file-test-data/basics/tooManyArgs.out_en
similarity index 100%
rename from python/test-data/testTypes2.err-notypes
rename to python/file-test-data/basics/tooManyArgs.out_en
diff --git a/python/file-test-data/basics/tooManyArgs.py b/python/file-test-data/basics/tooManyArgs.py
new file mode 100644
index 00000000..f3c9e7a4
--- /dev/null
+++ b/python/file-test-data/basics/tooManyArgs.py
@@ -0,0 +1,4 @@
+def foo(i: int, j: int):
+ pass
+
+foo(1, 2, 3)
diff --git a/python/file-test-data/basics/tooManyAttrs.err b/python/file-test-data/basics/tooManyAttrs.err
new file mode 100644
index 00000000..8747fc40
--- /dev/null
+++ b/python/file-test-data/basics/tooManyAttrs.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/tooManyAttrs.py", line 8, in
+ c = C(1, 2, 3)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Konstruktor des Records `C` benötigt 2 Argumente.
+Gegeben: 3 Argumente
+
+## Datei file-test-data/basics/tooManyAttrs.py
+## Aufruf in Zeile 8:
+
+c = [0;31m[1mC(1, 2, 3)[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/basics/tooManyAttrs.err_en b/python/file-test-data/basics/tooManyAttrs.err_en
new file mode 100644
index 00000000..af5fe59b
--- /dev/null
+++ b/python/file-test-data/basics/tooManyAttrs.err_en
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/tooManyAttrs.py", line 8, in
+ c = C(1, 2, 3)
+
+WyppTypeError: argument count mismatch
+
+Constructor of record `C` takes 2 arguments.
+Given: 3 arguments
+
+## File file-test-data/basics/tooManyAttrs.py
+## Call in line 8:
+
+c = [0;31m[1mC(1, 2, 3)[0;0m
\ No newline at end of file
diff --git a/python/test-data/testTypes2.out b/python/file-test-data/basics/tooManyAttrs.out
similarity index 100%
rename from python/test-data/testTypes2.out
rename to python/file-test-data/basics/tooManyAttrs.out
diff --git a/python/test-data/testTypesCollections1.out b/python/file-test-data/basics/tooManyAttrs.out_en
similarity index 100%
rename from python/test-data/testTypesCollections1.out
rename to python/file-test-data/basics/tooManyAttrs.out_en
diff --git a/python/file-test-data/basics/tooManyAttrs.py b/python/file-test-data/basics/tooManyAttrs.py
new file mode 100644
index 00000000..d6230a7f
--- /dev/null
+++ b/python/file-test-data/basics/tooManyAttrs.py
@@ -0,0 +1,8 @@
+from wypp import *
+
+@record
+class C:
+ x: int
+ y: int
+
+c = C(1, 2, 3)
diff --git a/python/file-test-data/basics/typeAlias.err b/python/file-test-data/basics/typeAlias.err
new file mode 100644
index 00000000..c7216234
--- /dev/null
+++ b/python/file-test-data/basics/typeAlias.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/typeAlias.py", line 7, in
+ foo('foo')
+
+WyppTypeError: 'foo'
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `T` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/basics/typeAlias.py
+## Fehlerhafter Aufruf in Zeile 7:
+
+foo('foo')
+
+## Typ deklariert in Zeile 3:
+
+def foo([0;31m[1mi: T[0;0m) -> T:
\ No newline at end of file
diff --git a/python/file-test-data/basics/typeAlias.err_en b/python/file-test-data/basics/typeAlias.err_en
new file mode 100644
index 00000000..62b6d3f5
--- /dev/null
+++ b/python/file-test-data/basics/typeAlias.err_en
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/typeAlias.py", line 7, in
+ foo('foo')
+
+WyppTypeError: 'foo'
+
+The call of function `foo` expects value of type `T` as 1st argument.
+But the value given has type `str`.
+
+## File file-test-data/basics/typeAlias.py
+## Problematic call in line 7:
+
+foo('foo')
+
+## Type declared in line 3:
+
+def foo([0;31m[1mi: T[0;0m) -> T:
\ No newline at end of file
diff --git a/python/test-data/testTypesCollections3.out b/python/file-test-data/basics/typeAlias.out
similarity index 100%
rename from python/test-data/testTypesCollections3.out
rename to python/file-test-data/basics/typeAlias.out
diff --git a/python/test-data/testTypesCollections4.out b/python/file-test-data/basics/typeAlias.out_en
similarity index 100%
rename from python/test-data/testTypesCollections4.out
rename to python/file-test-data/basics/typeAlias.out_en
diff --git a/python/file-test-data/basics/typeAlias.py b/python/file-test-data/basics/typeAlias.py
new file mode 100644
index 00000000..04a0bcd3
--- /dev/null
+++ b/python/file-test-data/basics/typeAlias.py
@@ -0,0 +1,7 @@
+type T = int
+
+def foo(i: T) -> T:
+ return i
+
+foo(1)
+foo('foo')
diff --git a/python/file-test-data/basics/typeAlias2.err b/python/file-test-data/basics/typeAlias2.err
new file mode 100644
index 00000000..45d2db1d
--- /dev/null
+++ b/python/file-test-data/basics/typeAlias2.err
@@ -0,0 +1,16 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/typeAlias2.py", line 7, in
+ foo(['foo'])
+
+WyppTypeError: ['foo']
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `list[T]` als erstes Argument.
+
+## Datei file-test-data/basics/typeAlias2.py
+## Fehlerhafter Aufruf in Zeile 7:
+
+foo(['foo'])
+
+## Typ deklariert in Zeile 3:
+
+def foo([0;31m[1mx: list[T][0;0m):
\ No newline at end of file
diff --git a/python/file-test-data/basics/typeAlias2.err_en b/python/file-test-data/basics/typeAlias2.err_en
new file mode 100644
index 00000000..457dd101
--- /dev/null
+++ b/python/file-test-data/basics/typeAlias2.err_en
@@ -0,0 +1,16 @@
+Traceback (most recent call last):
+ File "file-test-data/basics/typeAlias2.py", line 7, in
+ foo(['foo'])
+
+WyppTypeError: ['foo']
+
+The call of function `foo` expects value of type `list[T]` as 1st argument.
+
+## File file-test-data/basics/typeAlias2.py
+## Problematic call in line 7:
+
+foo(['foo'])
+
+## Type declared in line 3:
+
+def foo([0;31m[1mx: list[T][0;0m):
\ No newline at end of file
diff --git a/python/test-data/testTypesCollections5.out b/python/file-test-data/basics/typeAlias2.out
similarity index 100%
rename from python/test-data/testTypesCollections5.out
rename to python/file-test-data/basics/typeAlias2.out
diff --git a/python/test-data/testTypesDict1.out b/python/file-test-data/basics/typeAlias2.out_en
similarity index 100%
rename from python/test-data/testTypesDict1.out
rename to python/file-test-data/basics/typeAlias2.out_en
diff --git a/python/file-test-data/basics/typeAlias2.py b/python/file-test-data/basics/typeAlias2.py
new file mode 100644
index 00000000..69881c0a
--- /dev/null
+++ b/python/file-test-data/basics/typeAlias2.py
@@ -0,0 +1,7 @@
+type T = int
+
+def foo(x: list[T]):
+ pass
+
+foo([1,2])
+foo(['foo'])
diff --git a/python/test-data/testTypesDict3.out b/python/file-test-data/extras/admin_ok.err
similarity index 100%
rename from python/test-data/testTypesDict3.out
rename to python/file-test-data/extras/admin_ok.err
diff --git a/python/test-data/admin.out b/python/file-test-data/extras/admin_ok.out
similarity index 100%
rename from python/test-data/admin.out
rename to python/file-test-data/extras/admin_ok.out
diff --git a/python/test-data/admin.py b/python/file-test-data/extras/admin_ok.py
similarity index 100%
rename from python/test-data/admin.py
rename to python/file-test-data/extras/admin_ok.py
diff --git a/python/file-test-data/extras/declared-at-missing.err b/python/file-test-data/extras/declared-at-missing.err
new file mode 100644
index 00000000..4393d1f0
--- /dev/null
+++ b/python/file-test-data/extras/declared-at-missing.err
@@ -0,0 +1,16 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/declared-at-missing.py", line 22, in
+ semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, ))
+
+WyppTypeError: (Course(name='Programmierung 1', teacher='Wehr', students=()),)
+
+Der Aufruf des Konstruktors des Records `Semester` erwartet Wert vom Typ `tuple[CourseM, ...]` als drittes Argument.
+
+## Datei file-test-data/extras/declared-at-missing.py
+## Fehlerhafter Aufruf in Zeile 22:
+
+semester1_2020 = [0;31m[1mSemester('AKI', '1. Semester 2020/21', (prog1, ))[0;0m
+
+## Typ deklariert in Zeile 19:
+
+ [0;31m[1mcourses: tuple[CourseM, ...][0;0m
\ No newline at end of file
diff --git a/python/test-data/testTypesDict4.out b/python/file-test-data/extras/declared-at-missing.out
similarity index 100%
rename from python/test-data/testTypesDict4.out
rename to python/file-test-data/extras/declared-at-missing.out
diff --git a/python/test-data/declared-at-missing.py b/python/file-test-data/extras/declared-at-missing.py
similarity index 100%
rename from python/test-data/declared-at-missing.py
rename to python/file-test-data/extras/declared-at-missing.py
diff --git a/python/file-test-data/extras/invalidRecord.err b/python/file-test-data/extras/invalidRecord.err
new file mode 100644
index 00000000..4665becc
--- /dev/null
+++ b/python/file-test-data/extras/invalidRecord.err
@@ -0,0 +1,7 @@
+
+WyppTypeError: ungültige Record-Definition
+
+## Datei file-test-data/extras/invalidRecord.py
+## Zeile 1:
+
+@[0;31m[1m[0;0mrecord(mut=True)
diff --git a/python/test-data/testTypesHigherOrderFuns2.err b/python/file-test-data/extras/invalidRecord.out
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns2.err
rename to python/file-test-data/extras/invalidRecord.out
diff --git a/python/file-test-data/extras/invalidRecord.py b/python/file-test-data/extras/invalidRecord.py
new file mode 100644
index 00000000..9b14e61b
--- /dev/null
+++ b/python/file-test-data/extras/invalidRecord.py
@@ -0,0 +1,4 @@
+@record(mut=True)
+class C:
+ pass
+
diff --git a/python/file-test-data/extras/invalidType.err b/python/file-test-data/extras/invalidType.err
new file mode 100644
index 00000000..739a7f86
--- /dev/null
+++ b/python/file-test-data/extras/invalidType.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/invalidType.py", line 9, in
+ foo()
+
+WyppTypeError: ungültiger Typ `Union(list(int), list[float])`
+
+Wolltest du `Union[list(int), list[float]]` schreiben?
+
+## Datei file-test-data/extras/invalidType.py
+## Typ deklariert in Zeile 6:
+
+def foo() -> [0;31m[1mUnion(list(int), list[float])[0;0m:
\ No newline at end of file
diff --git a/python/test-data/testTypesHigherOrderFuns3.out b/python/file-test-data/extras/invalidType.out
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns3.out
rename to python/file-test-data/extras/invalidType.out
diff --git a/python/file-test-data/extras/invalidType.py b/python/file-test-data/extras/invalidType.py
new file mode 100644
index 00000000..77595170
--- /dev/null
+++ b/python/file-test-data/extras/invalidType.py
@@ -0,0 +1,9 @@
+from __future__ import annotations
+from wypp import *
+# See https://github.com/skogsbaer/write-your-python-program/issues/61
+
+# Tests 'return'
+def foo() -> Union(list(int), list[float]):
+ pass
+
+foo()
diff --git a/python/file-test-data/extras/invalidType2.err b/python/file-test-data/extras/invalidType2.err
new file mode 100644
index 00000000..abf47854
--- /dev/null
+++ b/python/file-test-data/extras/invalidType2.err
@@ -0,0 +1,4 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/invalidType2.py", line 5, in
+ T = Union(list(int), list[float])
+TypeError: 'type' object is not iterable
\ No newline at end of file
diff --git a/python/test-data/testTypesHigherOrderFuns4.err b/python/file-test-data/extras/invalidType2.out
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns4.err
rename to python/file-test-data/extras/invalidType2.out
diff --git a/python/file-test-data/extras/invalidType2.py b/python/file-test-data/extras/invalidType2.py
new file mode 100644
index 00000000..1757ecb7
--- /dev/null
+++ b/python/file-test-data/extras/invalidType2.py
@@ -0,0 +1,11 @@
+from __future__ import annotations
+from wypp import *
+# See https://github.com/skogsbaer/write-your-python-program/issues/61
+
+T = Union(list(int), list[float])
+
+# Tests 'return'
+def foo() -> T:
+ pass
+
+foo()
diff --git a/python/file-test-data/extras/invalidType3.err b/python/file-test-data/extras/invalidType3.err
new file mode 100644
index 00000000..8a772f48
--- /dev/null
+++ b/python/file-test-data/extras/invalidType3.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/invalidType3.py", line 9, in
+ foo()
+
+WyppTypeError: ungültiger Typ `Optional(list(int), list[float])`
+
+Wolltest du `Optional[list(int), list[float]]` schreiben?
+
+## Datei file-test-data/extras/invalidType3.py
+## Typ deklariert in Zeile 6:
+
+def foo() -> [0;31m[1mOptional(list(int), list[float])[0;0m:
\ No newline at end of file
diff --git a/python/test-data/testTypesHigherOrderFuns5.err b/python/file-test-data/extras/invalidType3.out
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns5.err
rename to python/file-test-data/extras/invalidType3.out
diff --git a/python/file-test-data/extras/invalidType3.py b/python/file-test-data/extras/invalidType3.py
new file mode 100644
index 00000000..cf8418ba
--- /dev/null
+++ b/python/file-test-data/extras/invalidType3.py
@@ -0,0 +1,9 @@
+from __future__ import annotations
+from wypp import *
+# See https://github.com/skogsbaer/write-your-python-program/issues/61
+
+# Tests 'return'
+def foo() -> Optional(list(int), list[float]):
+ pass
+
+foo()
diff --git a/python/file-test-data/extras/invalidType4.err b/python/file-test-data/extras/invalidType4.err
new file mode 100644
index 00000000..b261d290
--- /dev/null
+++ b/python/file-test-data/extras/invalidType4.err
@@ -0,0 +1,10 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/invalidType4.py", line 9, in
+ foo()
+
+WyppTypeError: ungültiger Typ `Optional[list[int], list[float]]`
+
+## Datei file-test-data/extras/invalidType4.py
+## Typ deklariert in Zeile 6:
+
+def foo() -> [0;31m[1mOptional[list[int], list[float]][0;0m:
\ No newline at end of file
diff --git a/python/test-data/testTypesProtos1.out b/python/file-test-data/extras/invalidType4.out
similarity index 100%
rename from python/test-data/testTypesProtos1.out
rename to python/file-test-data/extras/invalidType4.out
diff --git a/python/file-test-data/extras/invalidType4.py b/python/file-test-data/extras/invalidType4.py
new file mode 100644
index 00000000..36f511df
--- /dev/null
+++ b/python/file-test-data/extras/invalidType4.py
@@ -0,0 +1,9 @@
+from __future__ import annotations
+from wypp import *
+# See https://github.com/skogsbaer/write-your-python-program/issues/61
+
+# Tests 'return'
+def foo() -> Optional[list[int], list[float]]:
+ pass
+
+foo()
diff --git a/python/file-test-data/extras/main.err b/python/file-test-data/extras/main.err
new file mode 100644
index 00000000..4e925967
--- /dev/null
+++ b/python/file-test-data/extras/main.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/main.py", line 4, in
+ print(mod.foo(1))
+ File "file-test-data/modules/B/mod.py", line 5, in foo
+ return bar(i)
+
+WyppTypeError: 1
+
+Der Aufruf der Funktion `bar` erwartet Wert vom Typ `str` als erstes Argument.
+Aber der übergebene Wert hat Typ `int`.
+
+## Datei file-test-data/modules/B/mod.py
+## Fehlerhafter Aufruf in Zeile 5:
+
+ return bar([0;31m[1mi[0;0m)
+
+## Typ deklariert in Zeile 1:
+
+def bar([0;31m[1ms: str[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testTypesProtos2.out b/python/file-test-data/extras/main.out
similarity index 100%
rename from python/test-data/testTypesProtos2.out
rename to python/file-test-data/extras/main.out
diff --git a/python/file-test-data/extras/main.py b/python/file-test-data/extras/main.py
new file mode 100644
index 00000000..175213cc
--- /dev/null
+++ b/python/file-test-data/extras/main.py
@@ -0,0 +1,4 @@
+# WYPP_TEST_CONFIG: {"args": ["--extra-dir", "file-test-data/modules/B"], "pythonPath": "file-test-data/modules/B"}
+import mod
+
+print(mod.foo(1))
diff --git a/python/test-data/testTypesProtos3.out b/python/file-test-data/extras/printModuleNameImport_ok.err
similarity index 100%
rename from python/test-data/testTypesProtos3.out
rename to python/file-test-data/extras/printModuleNameImport_ok.err
diff --git a/python/test-data/printModuleNameImport.out b/python/file-test-data/extras/printModuleNameImport_ok.out
similarity index 55%
rename from python/test-data/printModuleNameImport.out
rename to python/file-test-data/extras/printModuleNameImport_ok.out
index c23481b5..fbf92154 100644
--- a/python/test-data/printModuleNameImport.out
+++ b/python/file-test-data/extras/printModuleNameImport_ok.out
@@ -1,4 +1,4 @@
-printModuleName
+printModuleName_ok
True
__wypp__
True
diff --git a/python/file-test-data/extras/printModuleNameImport_ok.py b/python/file-test-data/extras/printModuleNameImport_ok.py
new file mode 100644
index 00000000..aeff565d
--- /dev/null
+++ b/python/file-test-data/extras/printModuleNameImport_ok.py
@@ -0,0 +1,12 @@
+# WYPP_TEST_CONFIG: {"typecheck": "both"}
+import wypp
+import printModuleName_ok
+print(__name__)
+
+class C:
+ pass
+
+import sys
+print(sys.modules.get(C.__module__) is not None)
+print(sys.modules.get(printModuleName_ok.C.__module__) is not None)
+
diff --git a/python/test-data/testTypesProtos5.err b/python/file-test-data/extras/printModuleName_ok.err
similarity index 100%
rename from python/test-data/testTypesProtos5.err
rename to python/file-test-data/extras/printModuleName_ok.err
diff --git a/python/test-data/printModuleName.out b/python/file-test-data/extras/printModuleName_ok.out
similarity index 100%
rename from python/test-data/printModuleName.out
rename to python/file-test-data/extras/printModuleName_ok.out
diff --git a/python/test-data/printModuleName.py b/python/file-test-data/extras/printModuleName_ok.py
similarity index 83%
rename from python/test-data/printModuleName.py
rename to python/file-test-data/extras/printModuleName_ok.py
index 82c7cf94..cb049d40 100644
--- a/python/test-data/printModuleName.py
+++ b/python/file-test-data/extras/printModuleName_ok.py
@@ -1,5 +1,6 @@
# Should print 'wypp' when loaded via the RUN button.
# When imported, it should print 'printModuleName'
+# WYPP_TEST_CONFIG: {"typecheck": "both"}
import wypp
print(__name__)
diff --git a/python/test-data/testABCMeta.err b/python/file-test-data/extras/testABCMeta.err
similarity index 54%
rename from python/test-data/testABCMeta.err
rename to python/file-test-data/extras/testABCMeta.err
index 2bba158d..4ffca11e 100644
--- a/python/test-data/testABCMeta.err
+++ b/python/file-test-data/extras/testABCMeta.err
@@ -1,4 +1,4 @@
Traceback (most recent call last):
- File "test-data/testABCMeta.py", line 28, in
+ File "file-test-data/extras/testABCMeta.py", line 28, in
Circle(Point(0, 0), 1)
-TypeError: Can't instantiate abstract class Circle without an implementation for abstract method 'area'
+TypeError: Can't instantiate abstract class Circle without an implementation for abstract method 'area'
\ No newline at end of file
diff --git a/python/test-data/testTypesProtos6.out b/python/file-test-data/extras/testABCMeta.out
similarity index 100%
rename from python/test-data/testTypesProtos6.out
rename to python/file-test-data/extras/testABCMeta.out
diff --git a/python/test-data/testABCMeta.py b/python/file-test-data/extras/testABCMeta.py
similarity index 100%
rename from python/test-data/testABCMeta.py
rename to python/file-test-data/extras/testABCMeta.py
diff --git a/python/test-data/testTypesProtos7.out b/python/file-test-data/extras/testABC_ok.err
similarity index 100%
rename from python/test-data/testTypesProtos7.out
rename to python/file-test-data/extras/testABC_ok.err
diff --git a/python/test-data/testABC.out b/python/file-test-data/extras/testABC_ok.out
similarity index 100%
rename from python/test-data/testABC.out
rename to python/file-test-data/extras/testABC_ok.out
diff --git a/python/test-data/testABC.py b/python/file-test-data/extras/testABC_ok.py
similarity index 100%
rename from python/test-data/testABC.py
rename to python/file-test-data/extras/testABC_ok.py
diff --git a/python/test-data/testTypesProtos8.out b/python/file-test-data/extras/testArgs_ok.err
similarity index 100%
rename from python/test-data/testTypesProtos8.out
rename to python/file-test-data/extras/testArgs_ok.err
diff --git a/python/file-test-data/extras/testArgs_ok.out b/python/file-test-data/extras/testArgs_ok.out
new file mode 100644
index 00000000..d6d31d2c
--- /dev/null
+++ b/python/file-test-data/extras/testArgs_ok.out
@@ -0,0 +1 @@
+['file-test-data/extras/testArgs_ok.py', 'ARG_1', 'ARG_2']
diff --git a/python/file-test-data/extras/testArgs_ok.py b/python/file-test-data/extras/testArgs_ok.py
new file mode 100644
index 00000000..1b88936f
--- /dev/null
+++ b/python/file-test-data/extras/testArgs_ok.py
@@ -0,0 +1,4 @@
+# WYPP_TEST_CONFIG: {"args": ["ARG_1", "ARG_2"]}
+import wypp
+import sys
+print(sys.argv)
diff --git a/python/test-data/testTypesProtos9.out b/python/file-test-data/extras/testBugSliceIndices_ok.err
similarity index 100%
rename from python/test-data/testTypesProtos9.out
rename to python/file-test-data/extras/testBugSliceIndices_ok.err
diff --git a/python/test-data/testBugSliceIndices.out b/python/file-test-data/extras/testBugSliceIndices_ok.out
similarity index 100%
rename from python/test-data/testBugSliceIndices.out
rename to python/file-test-data/extras/testBugSliceIndices_ok.out
diff --git a/python/test-data/testBugSliceIndices.py b/python/file-test-data/extras/testBugSliceIndices_ok.py
similarity index 100%
rename from python/test-data/testBugSliceIndices.py
rename to python/file-test-data/extras/testBugSliceIndices_ok.py
diff --git a/python/test-data/testTypesSet1.out b/python/file-test-data/extras/testCheckFail_ok.err
similarity index 100%
rename from python/test-data/testTypesSet1.out
rename to python/file-test-data/extras/testCheckFail_ok.err
diff --git a/python/test-data/testCheckFail.out b/python/file-test-data/extras/testCheckFail_ok.out
similarity index 100%
rename from python/test-data/testCheckFail.out
rename to python/file-test-data/extras/testCheckFail_ok.out
diff --git a/python/test-data/testCheckFail.py b/python/file-test-data/extras/testCheckFail_ok.py
similarity index 57%
rename from python/test-data/testCheckFail.py
rename to python/file-test-data/extras/testCheckFail_ok.py
index 1c9981ca..1ea3c05c 100644
--- a/python/test-data/testCheckFail.py
+++ b/python/file-test-data/extras/testCheckFail_ok.py
@@ -1,3 +1,4 @@
+# WYPP_TEST_CONFIG: {"typecheck": "both"}
from wypp import *
checkFail('something bad happened')
diff --git a/python/test-data/testTypesSet3.out b/python/file-test-data/extras/testCheck_ok.err
similarity index 100%
rename from python/test-data/testTypesSet3.out
rename to python/file-test-data/extras/testCheck_ok.err
diff --git a/python/file-test-data/extras/testCheck_ok.out b/python/file-test-data/extras/testCheck_ok.out
new file mode 100644
index 00000000..b4ed27cd
--- /dev/null
+++ b/python/file-test-data/extras/testCheck_ok.out
@@ -0,0 +1,5 @@
+FEHLER in Datei file-test-data/extras/testCheck_ok.py, Zeile 13: Erwartet wird 2, aber das Ergebnis ist 1
+FEHLER in Datei file-test-data/extras/testCheck_ok.py, Zeile 14: Erwartet wird 'xyz', aber das Ergebnis ist 'abc'
+FEHLER in Datei file-test-data/extras/testCheck_ok.py, Zeile 15: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Point(x=1, y=2)
+FEHLER in Datei file-test-data/extras/testCheck_ok.py, Zeile 16: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Name(firstName='Max', lastName='Müller')
+4 Tests, 4 Fehler 🙁
diff --git a/python/test-data/testCheck.py b/python/file-test-data/extras/testCheck_ok.py
similarity index 100%
rename from python/test-data/testCheck.py
rename to python/file-test-data/extras/testCheck_ok.py
diff --git a/python/test-data/testTypesSubclassing1.out b/python/file-test-data/extras/testClassHierarchy_ok.err
similarity index 100%
rename from python/test-data/testTypesSubclassing1.out
rename to python/file-test-data/extras/testClassHierarchy_ok.err
diff --git a/python/test-data/testClassHierarchy.out b/python/file-test-data/extras/testClassHierarchy_ok.out
similarity index 100%
rename from python/test-data/testClassHierarchy.out
rename to python/file-test-data/extras/testClassHierarchy_ok.out
diff --git a/python/test-data/testClassHierarchy.py b/python/file-test-data/extras/testClassHierarchy_ok.py
similarity index 100%
rename from python/test-data/testClassHierarchy.py
rename to python/file-test-data/extras/testClassHierarchy_ok.py
diff --git a/python/test-data/testTypesWrapperEq.err b/python/file-test-data/extras/testClassRecursion_ok.err
similarity index 100%
rename from python/test-data/testTypesWrapperEq.err
rename to python/file-test-data/extras/testClassRecursion_ok.err
diff --git a/python/test-data/testClassRecursion.out b/python/file-test-data/extras/testClassRecursion_ok.out
similarity index 100%
rename from python/test-data/testClassRecursion.out
rename to python/file-test-data/extras/testClassRecursion_ok.out
diff --git a/python/test-data/testClassRecursion.py b/python/file-test-data/extras/testClassRecursion_ok.py
similarity index 100%
rename from python/test-data/testClassRecursion.py
rename to python/file-test-data/extras/testClassRecursion_ok.py
diff --git a/python/test-data/testUnion.err b/python/file-test-data/extras/testComplex_ok.err
similarity index 100%
rename from python/test-data/testUnion.err
rename to python/file-test-data/extras/testComplex_ok.err
diff --git a/python/test-data/testUnion2.err b/python/file-test-data/extras/testComplex_ok.out
similarity index 100%
rename from python/test-data/testUnion2.err
rename to python/file-test-data/extras/testComplex_ok.out
diff --git a/python/test-data/testComplex.py b/python/file-test-data/extras/testComplex_ok.py
similarity index 100%
rename from python/test-data/testComplex.py
rename to python/file-test-data/extras/testComplex_ok.py
diff --git a/python/test-data/testUnion3.err b/python/file-test-data/extras/testConcat_ok.err
similarity index 100%
rename from python/test-data/testUnion3.err
rename to python/file-test-data/extras/testConcat_ok.err
diff --git a/python/test-data/testConcat.out b/python/file-test-data/extras/testConcat_ok.out
similarity index 100%
rename from python/test-data/testConcat.out
rename to python/file-test-data/extras/testConcat_ok.out
diff --git a/python/test-data/testConcat.py b/python/file-test-data/extras/testConcat_ok.py
similarity index 100%
rename from python/test-data/testConcat.py
rename to python/file-test-data/extras/testConcat_ok.py
diff --git a/python/test-data/testUnionLiteral.err b/python/file-test-data/extras/testCopy_ok.err
similarity index 100%
rename from python/test-data/testUnionLiteral.err
rename to python/file-test-data/extras/testCopy_ok.err
diff --git a/python/test-data/testCopy.out b/python/file-test-data/extras/testCopy_ok.out
similarity index 100%
rename from python/test-data/testCopy.out
rename to python/file-test-data/extras/testCopy_ok.out
diff --git a/python/test-data/testCopy.py b/python/file-test-data/extras/testCopy_ok.py
similarity index 100%
rename from python/test-data/testCopy.py
rename to python/file-test-data/extras/testCopy_ok.py
diff --git a/python/test-data/testUnionLiteral.out b/python/file-test-data/extras/testDeepEqBug_ok.err
similarity index 100%
rename from python/test-data/testUnionLiteral.out
rename to python/file-test-data/extras/testDeepEqBug_ok.err
diff --git a/python/file-test-data/extras/testDeepEqBug_ok.out b/python/file-test-data/extras/testDeepEqBug_ok.out
new file mode 100644
index 00000000..bdcb1c6b
--- /dev/null
+++ b/python/file-test-data/extras/testDeepEqBug_ok.out
@@ -0,0 +1,2 @@
+FEHLER in Datei file-test-data/extras/testDeepEqBug_ok.py, Zeile 61: Erwartet wird SemesterM(degreeProgram='AKI', semester='1. Semester 2021/22', courses=[CourseM(name='Programmierung 1', teacher='Wehr', students=[]), CourseM(name='Grundlagen der Informatik', teacher='Oelke', students=[])]), aber das Ergebnis ist SemesterM(degreeProgram='AKI', semester='1. Semester 2021/22', courses=[CourseM(name='Programmierung 1', teacher='Wehr', students=['1234', '9876']), CourseM(name='Grundlagen der Informatik', teacher='Oelke', students=['1234'])])
+1 Test, 1 Fehler 🙁
diff --git a/python/test-data/testDeepEqBug.py b/python/file-test-data/extras/testDeepEqBug_ok.py
similarity index 100%
rename from python/test-data/testDeepEqBug.py
rename to python/file-test-data/extras/testDeepEqBug_ok.py
diff --git a/python/test-data/testUnionOfUnion.err b/python/file-test-data/extras/testDict_ok.err
similarity index 100%
rename from python/test-data/testUnionOfUnion.err
rename to python/file-test-data/extras/testDict_ok.err
diff --git a/python/test-data/testDict.out b/python/file-test-data/extras/testDict_ok.out
similarity index 100%
rename from python/test-data/testDict.out
rename to python/file-test-data/extras/testDict_ok.out
diff --git a/python/test-data/testDict.py b/python/file-test-data/extras/testDict_ok.py
similarity index 100%
rename from python/test-data/testDict.py
rename to python/file-test-data/extras/testDict_ok.py
diff --git a/python/test-data/testUnsortableDicts.err b/python/file-test-data/extras/testDoubleWrappingDicts_ok.err
similarity index 100%
rename from python/test-data/testUnsortableDicts.err
rename to python/file-test-data/extras/testDoubleWrappingDicts_ok.err
diff --git a/python/test-data/testDoubleWrappingDicts.out b/python/file-test-data/extras/testDoubleWrappingDicts_ok.out
similarity index 100%
rename from python/test-data/testDoubleWrappingDicts.out
rename to python/file-test-data/extras/testDoubleWrappingDicts_ok.out
diff --git a/python/test-data/testDoubleWrappingDicts.py b/python/file-test-data/extras/testDoubleWrappingDicts_ok.py
similarity index 100%
rename from python/test-data/testDoubleWrappingDicts.py
rename to python/file-test-data/extras/testDoubleWrappingDicts_ok.py
diff --git a/python/test-data/testWrap.err b/python/file-test-data/extras/testForwardRef1_ok.err
similarity index 100%
rename from python/test-data/testWrap.err
rename to python/file-test-data/extras/testForwardRef1_ok.err
diff --git a/python/test-data/testForwardRef1.out b/python/file-test-data/extras/testForwardRef1_ok.out
similarity index 100%
rename from python/test-data/testForwardRef1.out
rename to python/file-test-data/extras/testForwardRef1_ok.out
diff --git a/python/test-data/testForwardRef1.py b/python/file-test-data/extras/testForwardRef1_ok.py
similarity index 100%
rename from python/test-data/testForwardRef1.py
rename to python/file-test-data/extras/testForwardRef1_ok.py
diff --git a/python/file-test-data/extras/testForwardRef2.err b/python/file-test-data/extras/testForwardRef2.err
new file mode 100644
index 00000000..87cf3893
--- /dev/null
+++ b/python/file-test-data/extras/testForwardRef2.err
@@ -0,0 +1,10 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testForwardRef2.py", line 10, in
+ t = Test(FooX())
+
+WyppTypeError: ungültiger Typ `Foo`
+
+## Datei file-test-data/extras/testForwardRef2.py
+## Typ deklariert in Zeile 2:
+
+ def __init__(self, [0;31m[1mfoo: 'Foo'[0;0m):
\ No newline at end of file
diff --git a/python/test-data/testWrap2.err b/python/file-test-data/extras/testForwardRef2.out
similarity index 100%
rename from python/test-data/testWrap2.err
rename to python/file-test-data/extras/testForwardRef2.out
diff --git a/python/test-data/testForwardRef2.py b/python/file-test-data/extras/testForwardRef2.py
similarity index 100%
rename from python/test-data/testForwardRef2.py
rename to python/file-test-data/extras/testForwardRef2.py
diff --git a/python/test-data/testWrapperError.out b/python/file-test-data/extras/testForwardRef3_ok.err
similarity index 100%
rename from python/test-data/testWrapperError.out
rename to python/file-test-data/extras/testForwardRef3_ok.err
diff --git a/python/test-data/testForwardRef3.out b/python/file-test-data/extras/testForwardRef3_ok.out
similarity index 100%
rename from python/test-data/testForwardRef3.out
rename to python/file-test-data/extras/testForwardRef3_ok.out
diff --git a/python/test-data/testForwardRef3.py b/python/file-test-data/extras/testForwardRef3_ok.py
similarity index 100%
rename from python/test-data/testForwardRef3.py
rename to python/file-test-data/extras/testForwardRef3_ok.py
diff --git a/python/file-test-data/extras/testForwardRef4.err b/python/file-test-data/extras/testForwardRef4.err
new file mode 100644
index 00000000..b644f3f6
--- /dev/null
+++ b/python/file-test-data/extras/testForwardRef4.err
@@ -0,0 +1,10 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testForwardRef4.py", line 11, in
+ t = Test(Foo())
+
+WyppTypeError: ungültiger Typ `FooX`
+
+## Datei file-test-data/extras/testForwardRef4.py
+## Typ deklariert in Zeile 5:
+
+ [0;31m[1mfoo: 'FooX'[0;0m
diff --git a/python/test-data/testWrongKeywordArg.out b/python/file-test-data/extras/testForwardRef4.out
similarity index 100%
rename from python/test-data/testWrongKeywordArg.out
rename to python/file-test-data/extras/testForwardRef4.out
diff --git a/python/test-data/testForwardRef4.py b/python/file-test-data/extras/testForwardRef4.py
similarity index 100%
rename from python/test-data/testForwardRef4.py
rename to python/file-test-data/extras/testForwardRef4.py
diff --git a/python/file-test-data/extras/testForwardRef5.err b/python/file-test-data/extras/testForwardRef5.err
new file mode 100644
index 00000000..c13aa2dd
--- /dev/null
+++ b/python/file-test-data/extras/testForwardRef5.err
@@ -0,0 +1,16 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testForwardRef5.py", line 22, in
+ garage = Garage(cars=[Car(color='red'), "Not A Car"])
+
+WyppTypeError: [Car(color='red'), 'Not A Car']
+
+Der Aufruf des Konstruktors des Records `Garage` erwartet Wert vom Typ `list[Car]` als Argument `cars`.
+
+## Datei file-test-data/extras/testForwardRef5.py
+## Fehlerhafter Aufruf in Zeile 22:
+
+garage = Garage(cars=[0;31m[1m[Car(color='red'), "Not A Car"][0;0m)
+
+## Typ deklariert in Zeile 11:
+
+ [0;31m[1mcars: list['Car'][0;0m
\ No newline at end of file
diff --git a/python/test-data/testWrongKeywordArg2.out b/python/file-test-data/extras/testForwardRef5.out
similarity index 100%
rename from python/test-data/testWrongKeywordArg2.out
rename to python/file-test-data/extras/testForwardRef5.out
diff --git a/python/test-data/testForwardRef5.py b/python/file-test-data/extras/testForwardRef5.py
similarity index 100%
rename from python/test-data/testForwardRef5.py
rename to python/file-test-data/extras/testForwardRef5.py
diff --git a/python/test-data/testWrongNumOfArguments.out b/python/file-test-data/extras/testForwardRef6_ok.err
similarity index 100%
rename from python/test-data/testWrongNumOfArguments.out
rename to python/file-test-data/extras/testForwardRef6_ok.err
diff --git a/python/test-data/testForwardRef6.out b/python/file-test-data/extras/testForwardRef6_ok.out
similarity index 100%
rename from python/test-data/testForwardRef6.out
rename to python/file-test-data/extras/testForwardRef6_ok.out
diff --git a/python/test-data/testForwardRef6.py b/python/file-test-data/extras/testForwardRef6_ok.py
similarity index 100%
rename from python/test-data/testForwardRef6.py
rename to python/file-test-data/extras/testForwardRef6_ok.py
diff --git a/python/test-data/testWrongNumOfArguments2.out b/python/file-test-data/extras/testForwardRef_ok.err
similarity index 100%
rename from python/test-data/testWrongNumOfArguments2.out
rename to python/file-test-data/extras/testForwardRef_ok.err
diff --git a/python/test-data/wrong-caused-by.out b/python/file-test-data/extras/testForwardRef_ok.out
similarity index 100%
rename from python/test-data/wrong-caused-by.out
rename to python/file-test-data/extras/testForwardRef_ok.out
diff --git a/python/test-data/testForwardRef.py b/python/file-test-data/extras/testForwardRef_ok.py
similarity index 100%
rename from python/test-data/testForwardRef.py
rename to python/file-test-data/extras/testForwardRef_ok.py
diff --git a/python/file-test-data/extras/testForwardTypeInRecord2_ok.err b/python/file-test-data/extras/testForwardTypeInRecord2_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testForwardTypeInRecord.out b/python/file-test-data/extras/testForwardTypeInRecord2_ok.out
similarity index 100%
rename from python/test-data/testForwardTypeInRecord.out
rename to python/file-test-data/extras/testForwardTypeInRecord2_ok.out
diff --git a/python/test-data/testForwardTypeInRecord2.py b/python/file-test-data/extras/testForwardTypeInRecord2_ok.py
similarity index 100%
rename from python/test-data/testForwardTypeInRecord2.py
rename to python/file-test-data/extras/testForwardTypeInRecord2_ok.py
diff --git a/python/file-test-data/extras/testForwardTypeInRecord_ok.err b/python/file-test-data/extras/testForwardTypeInRecord_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testForwardTypeInRecord2.out b/python/file-test-data/extras/testForwardTypeInRecord_ok.out
similarity index 100%
rename from python/test-data/testForwardTypeInRecord2.out
rename to python/file-test-data/extras/testForwardTypeInRecord_ok.out
diff --git a/python/test-data/testForwardTypeInRecord.py b/python/file-test-data/extras/testForwardTypeInRecord_ok.py
similarity index 100%
rename from python/test-data/testForwardTypeInRecord.py
rename to python/file-test-data/extras/testForwardTypeInRecord_ok.py
diff --git a/python/file-test-data/extras/testFunEq_ok.err b/python/file-test-data/extras/testFunEq_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testFunEq_ok.out b/python/file-test-data/extras/testFunEq_ok.out
new file mode 100644
index 00000000..7ec80606
--- /dev/null
+++ b/python/file-test-data/extras/testFunEq_ok.out
@@ -0,0 +1,2 @@
+FEHLER in Datei file-test-data/extras/testFunEq_ok.py, Zeile 9: Erwartet wird , aber das Ergebnis ist
+1 Test, 1 Fehler 🙁
diff --git a/python/test-data/testFunEq.py b/python/file-test-data/extras/testFunEq_ok.py
similarity index 100%
rename from python/test-data/testFunEq.py
rename to python/file-test-data/extras/testFunEq_ok.py
diff --git a/python/file-test-data/extras/testGetSource.err b/python/file-test-data/extras/testGetSource.err
new file mode 100644
index 00000000..4a8b3cb7
--- /dev/null
+++ b/python/file-test-data/extras/testGetSource.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testGetSource.py", line 11, in
+ Art = Literal('klein','mittag') # <= problem is here
+
+WyppTypeError: ungültiger Typ `Literal('klein', 'mittag')`
+
+Wolltest du `Literal['klein', 'mittag']` schreiben?
+
+## Datei file-test-data/extras/testGetSource.py
+## Typ deklariert in Zeile 11:
+
+Art = [0;31m[1mLiteral('klein','mittag')[0;0m # <= problem is here
diff --git a/python/file-test-data/extras/testGetSource.out b/python/file-test-data/extras/testGetSource.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testGetSource.py b/python/file-test-data/extras/testGetSource.py
similarity index 100%
rename from python/test-data/testGetSource.py
rename to python/file-test-data/extras/testGetSource.py
diff --git a/python/file-test-data/extras/testHintParentheses1.err b/python/file-test-data/extras/testHintParentheses1.err
new file mode 100644
index 00000000..7cf31405
--- /dev/null
+++ b/python/file-test-data/extras/testHintParentheses1.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testHintParentheses1.py", line 8, in
+ check(foo([1,2,3]), 3)
+
+WyppTypeError: ungültiger Typ `list(int)`
+
+Wolltest du `list[int]` schreiben?
+
+## Datei file-test-data/extras/testHintParentheses1.py
+## Typ deklariert in Zeile 5:
+
+def foo([0;31m[1ml: list(int)[0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testHintParentheses1.out b/python/file-test-data/extras/testHintParentheses1.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testHintParentheses1.py b/python/file-test-data/extras/testHintParentheses1.py
similarity index 100%
rename from python/test-data/testHintParentheses1.py
rename to python/file-test-data/extras/testHintParentheses1.py
diff --git a/python/file-test-data/extras/testHintParentheses2.err b/python/file-test-data/extras/testHintParentheses2.err
new file mode 100644
index 00000000..7f253075
--- /dev/null
+++ b/python/file-test-data/extras/testHintParentheses2.err
@@ -0,0 +1,10 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testHintParentheses2.py", line 8, in
+ foo(1, {})
+
+WyppTypeError: ungültiger Typ `dict[1, list(int)]`
+
+## Datei file-test-data/extras/testHintParentheses2.py
+## Typ deklariert in Zeile 5:
+
+def foo(a: 'int', [0;31m[1mb: 'dict[1, list(int)]'[0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testHintParentheses2.out b/python/file-test-data/extras/testHintParentheses2.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testHintParentheses2.py b/python/file-test-data/extras/testHintParentheses2.py
similarity index 85%
rename from python/test-data/testHintParentheses2.py
rename to python/file-test-data/extras/testHintParentheses2.py
index 6f05ce0d..98456c55 100644
--- a/python/test-data/testHintParentheses2.py
+++ b/python/file-test-data/extras/testHintParentheses2.py
@@ -3,4 +3,6 @@
# Uses types wrapped in strings
def foo(a: 'int', b: 'dict[1, list(int)]') -> int:
- return len(l)
\ No newline at end of file
+ return len(b)
+
+foo(1, {})
diff --git a/python/file-test-data/extras/testHintParentheses3.err b/python/file-test-data/extras/testHintParentheses3.err
new file mode 100644
index 00000000..e5d29b1b
--- /dev/null
+++ b/python/file-test-data/extras/testHintParentheses3.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testHintParentheses3.py", line 9, in
+ foo()
+
+WyppTypeError: ungültiger Typ `Union(list, str)`
+
+Wolltest du `Union[list, str]` schreiben?
+
+## Datei file-test-data/extras/testHintParentheses3.py
+## Typ deklariert in Zeile 6:
+
+def foo() -> [0;31m[1mUnion(list, str)[0;0m:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testHintParentheses3.out b/python/file-test-data/extras/testHintParentheses3.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testHintParentheses3.py b/python/file-test-data/extras/testHintParentheses3.py
similarity index 96%
rename from python/test-data/testHintParentheses3.py
rename to python/file-test-data/extras/testHintParentheses3.py
index 225f0b11..d93e038e 100644
--- a/python/test-data/testHintParentheses3.py
+++ b/python/file-test-data/extras/testHintParentheses3.py
@@ -5,3 +5,5 @@
# Tests 'return'
def foo() -> Union(list, str):
pass
+
+foo()
diff --git a/python/file-test-data/extras/testHof_ok.err b/python/file-test-data/extras/testHof_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testHof.out b/python/file-test-data/extras/testHof_ok.out
similarity index 100%
rename from python/test-data/testHof.out
rename to python/file-test-data/extras/testHof_ok.out
diff --git a/python/test-data/testHof.py b/python/file-test-data/extras/testHof_ok.py
similarity index 100%
rename from python/test-data/testHof.py
rename to python/file-test-data/extras/testHof_ok.py
diff --git a/python/file-test-data/extras/testImpossible.err b/python/file-test-data/extras/testImpossible.err
new file mode 100644
index 00000000..19e40e8d
--- /dev/null
+++ b/python/file-test-data/extras/testImpossible.err
@@ -0,0 +1,7 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testImpossible.py", line 3, in
+ impossible()
+ File "code/wypp/writeYourProgram.py", line 332, in impossible
+ raise errors.ImpossibleError(msg)
+
+Das Unmögliche ist passiert!
diff --git a/python/file-test-data/extras/testImpossible.out b/python/file-test-data/extras/testImpossible.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testImpossible.py b/python/file-test-data/extras/testImpossible.py
similarity index 100%
rename from python/test-data/testImpossible.py
rename to python/file-test-data/extras/testImpossible.py
diff --git a/python/file-test-data/extras/testIndexError.err b/python/file-test-data/extras/testIndexError.err
new file mode 100644
index 00000000..81e0bc3a
--- /dev/null
+++ b/python/file-test-data/extras/testIndexError.err
@@ -0,0 +1,6 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testIndexError.py", line 6, in
+ foo([1,2,3])
+ File "file-test-data/extras/testIndexError.py", line 3, in foo
+ x = l[42]
+IndexError: list index out of range
\ No newline at end of file
diff --git a/python/file-test-data/extras/testIndexError.out b/python/file-test-data/extras/testIndexError.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIndexError.py b/python/file-test-data/extras/testIndexError.py
similarity index 100%
rename from python/test-data/testIndexError.py
rename to python/file-test-data/extras/testIndexError.py
diff --git a/python/file-test-data/extras/testIndexSeq_ok.err b/python/file-test-data/extras/testIndexSeq_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIndexSeq.out b/python/file-test-data/extras/testIndexSeq_ok.out
similarity index 100%
rename from python/test-data/testIndexSeq.out
rename to python/file-test-data/extras/testIndexSeq_ok.out
diff --git a/python/test-data/testIndexSeq.py b/python/file-test-data/extras/testIndexSeq_ok.py
similarity index 100%
rename from python/test-data/testIndexSeq.py
rename to python/file-test-data/extras/testIndexSeq_ok.py
diff --git a/python/file-test-data/extras/testInvalidLiteral.err b/python/file-test-data/extras/testInvalidLiteral.err
new file mode 100644
index 00000000..85d327ec
--- /dev/null
+++ b/python/file-test-data/extras/testInvalidLiteral.err
@@ -0,0 +1,10 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testInvalidLiteral.py", line 8, in
+ gameFull([['x']])
+
+WyppTypeError: ungültiger Typ `list[list[['x', 'o', '-']]]`
+
+## Datei file-test-data/extras/testInvalidLiteral.py
+## Typ deklariert in Zeile 5:
+
+def gameFull([0;31m[1mgame:Game[0;0m)->bool:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testInvalidLiteral.out b/python/file-test-data/extras/testInvalidLiteral.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testInvalidLiteral.py b/python/file-test-data/extras/testInvalidLiteral.py
similarity index 84%
rename from python/test-data/testInvalidLiteral.py
rename to python/file-test-data/extras/testInvalidLiteral.py
index 3ef2938e..20a311a3 100644
--- a/python/test-data/testInvalidLiteral.py
+++ b/python/file-test-data/extras/testInvalidLiteral.py
@@ -4,3 +4,5 @@
Game = list[list[Mark]]
def gameFull(game:Game)->bool:
pass
+
+gameFull([['x']])
diff --git a/python/file-test-data/extras/testIterable1_ok.err b/python/file-test-data/extras/testIterable1_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterable1.out b/python/file-test-data/extras/testIterable1_ok.out
similarity index 100%
rename from python/test-data/testIterable1.out
rename to python/file-test-data/extras/testIterable1_ok.out
diff --git a/python/test-data/testIterable1.py b/python/file-test-data/extras/testIterable1_ok.py
similarity index 100%
rename from python/test-data/testIterable1.py
rename to python/file-test-data/extras/testIterable1_ok.py
diff --git a/python/file-test-data/extras/testIterable2_ok.err b/python/file-test-data/extras/testIterable2_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterable2.out b/python/file-test-data/extras/testIterable2_ok.out
similarity index 100%
rename from python/test-data/testIterable2.out
rename to python/file-test-data/extras/testIterable2_ok.out
diff --git a/python/test-data/testIterable2.py b/python/file-test-data/extras/testIterable2_ok.py
similarity index 100%
rename from python/test-data/testIterable2.py
rename to python/file-test-data/extras/testIterable2_ok.py
diff --git a/python/file-test-data/extras/testIterable3_ok.err b/python/file-test-data/extras/testIterable3_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterable3.out b/python/file-test-data/extras/testIterable3_ok.out
similarity index 100%
rename from python/test-data/testIterable3.out
rename to python/file-test-data/extras/testIterable3_ok.out
diff --git a/python/test-data/testIterable3.py b/python/file-test-data/extras/testIterable3_ok.py
similarity index 100%
rename from python/test-data/testIterable3.py
rename to python/file-test-data/extras/testIterable3_ok.py
diff --git a/python/file-test-data/extras/testIterable4_ok.err b/python/file-test-data/extras/testIterable4_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterable4.out b/python/file-test-data/extras/testIterable4_ok.out
similarity index 100%
rename from python/test-data/testIterable4.out
rename to python/file-test-data/extras/testIterable4_ok.out
diff --git a/python/test-data/testIterable4.py b/python/file-test-data/extras/testIterable4_ok.py
similarity index 100%
rename from python/test-data/testIterable4.py
rename to python/file-test-data/extras/testIterable4_ok.py
diff --git a/python/file-test-data/extras/testIterable5_ok.err b/python/file-test-data/extras/testIterable5_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterable5.out b/python/file-test-data/extras/testIterable5_ok.out
similarity index 100%
rename from python/test-data/testIterable5.out
rename to python/file-test-data/extras/testIterable5_ok.out
diff --git a/python/test-data/testIterable5.py b/python/file-test-data/extras/testIterable5_ok.py
similarity index 100%
rename from python/test-data/testIterable5.py
rename to python/file-test-data/extras/testIterable5_ok.py
diff --git a/python/file-test-data/extras/testIterable6_ok.err b/python/file-test-data/extras/testIterable6_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterable6.out b/python/file-test-data/extras/testIterable6_ok.out
similarity index 100%
rename from python/test-data/testIterable6.out
rename to python/file-test-data/extras/testIterable6_ok.out
diff --git a/python/test-data/testIterable6.py b/python/file-test-data/extras/testIterable6_ok.py
similarity index 100%
rename from python/test-data/testIterable6.py
rename to python/file-test-data/extras/testIterable6_ok.py
diff --git a/python/file-test-data/extras/testIterable7_ok.err b/python/file-test-data/extras/testIterable7_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testIterable7_ok.out b/python/file-test-data/extras/testIterable7_ok.out
new file mode 100644
index 00000000..db562fb0
--- /dev/null
+++ b/python/file-test-data/extras/testIterable7_ok.out
@@ -0,0 +1,3 @@
+start of foo
+end of foo
+[15]
diff --git a/python/test-data/testIterable7.py b/python/file-test-data/extras/testIterable7_ok.py
similarity index 100%
rename from python/test-data/testIterable7.py
rename to python/file-test-data/extras/testIterable7_ok.py
diff --git a/python/file-test-data/extras/testIterableImplicitAny.err b/python/file-test-data/extras/testIterableImplicitAny.err
new file mode 100644
index 00000000..33552986
--- /dev/null
+++ b/python/file-test-data/extras/testIterableImplicitAny.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testIterableImplicitAny.py", line 13, in
+ foo(NotIterable())
+
+WyppTypeError: NotIterable
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Iterable` als erstes Argument.
+Aber der übergebene Wert hat Typ `NotIterable`.
+
+## Datei file-test-data/extras/testIterableImplicitAny.py
+## Fehlerhafter Aufruf in Zeile 13:
+
+foo(NotIterable())
+
+## Typ deklariert in Zeile 7:
+
+def foo([0;31m[1mit: Iterable[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testIterableImplicitAny.out b/python/file-test-data/extras/testIterableImplicitAny.out
similarity index 100%
rename from python/test-data/testIterableImplicitAny.out
rename to python/file-test-data/extras/testIterableImplicitAny.out
diff --git a/python/test-data/testIterableImplicitAny.py b/python/file-test-data/extras/testIterableImplicitAny.py
similarity index 100%
rename from python/test-data/testIterableImplicitAny.py
rename to python/file-test-data/extras/testIterableImplicitAny.py
diff --git a/python/file-test-data/extras/testIterator2_ok.err b/python/file-test-data/extras/testIterator2_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterator.out b/python/file-test-data/extras/testIterator2_ok.out
similarity index 100%
rename from python/test-data/testIterator.out
rename to python/file-test-data/extras/testIterator2_ok.out
diff --git a/python/test-data/testIterator2.py b/python/file-test-data/extras/testIterator2_ok.py
similarity index 100%
rename from python/test-data/testIterator2.py
rename to python/file-test-data/extras/testIterator2_ok.py
diff --git a/python/file-test-data/extras/testIterator3_ok.err b/python/file-test-data/extras/testIterator3_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterator3.out b/python/file-test-data/extras/testIterator3_ok.out
similarity index 100%
rename from python/test-data/testIterator3.out
rename to python/file-test-data/extras/testIterator3_ok.out
diff --git a/python/test-data/testIterator3.py b/python/file-test-data/extras/testIterator3_ok.py
similarity index 100%
rename from python/test-data/testIterator3.py
rename to python/file-test-data/extras/testIterator3_ok.py
diff --git a/python/file-test-data/extras/testIterator4_ok.err b/python/file-test-data/extras/testIterator4_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterator4.out b/python/file-test-data/extras/testIterator4_ok.out
similarity index 100%
rename from python/test-data/testIterator4.out
rename to python/file-test-data/extras/testIterator4_ok.out
diff --git a/python/test-data/testIterator4.py b/python/file-test-data/extras/testIterator4_ok.py
similarity index 100%
rename from python/test-data/testIterator4.py
rename to python/file-test-data/extras/testIterator4_ok.py
diff --git a/python/file-test-data/extras/testIterator_ok.err b/python/file-test-data/extras/testIterator_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testIterator2.out b/python/file-test-data/extras/testIterator_ok.out
similarity index 100%
rename from python/test-data/testIterator2.out
rename to python/file-test-data/extras/testIterator_ok.out
diff --git a/python/test-data/testIterator.py b/python/file-test-data/extras/testIterator_ok.py
similarity index 100%
rename from python/test-data/testIterator.py
rename to python/file-test-data/extras/testIterator_ok.py
diff --git a/python/file-test-data/extras/testLiteral1.err b/python/file-test-data/extras/testLiteral1.err
new file mode 100644
index 00000000..14bb807e
--- /dev/null
+++ b/python/file-test-data/extras/testLiteral1.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testLiteral1.py", line 3, in
+ T = Literal('a', 'b')
+
+WyppTypeError: ungültiger Typ `Literal('a', 'b')`
+
+Wolltest du `Literal['a', 'b']` schreiben?
+
+## Datei file-test-data/extras/testLiteral1.py
+## Typ deklariert in Zeile 3:
+
+T = [0;31m[1mLiteral('a', 'b')[0;0m
diff --git a/python/file-test-data/extras/testLiteral1.out b/python/file-test-data/extras/testLiteral1.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testLiteral1.py b/python/file-test-data/extras/testLiteral1.py
similarity index 100%
rename from python/test-data/testLiteral1.py
rename to python/file-test-data/extras/testLiteral1.py
diff --git a/python/file-test-data/extras/testLiteralInstanceOf_ok.err b/python/file-test-data/extras/testLiteralInstanceOf_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testLiteralInstanceOf.out b/python/file-test-data/extras/testLiteralInstanceOf_ok.out
similarity index 74%
rename from python/test-data/testLiteralInstanceOf.out
rename to python/file-test-data/extras/testLiteralInstanceOf_ok.out
index 483b67ec..34be1684 100644
--- a/python/test-data/testLiteralInstanceOf.out
+++ b/python/file-test-data/extras/testLiteralInstanceOf_ok.out
@@ -1,8 +1,9 @@
True
+False
True
True
True
-True
+False
True
True
True
diff --git a/python/test-data/testLiteralInstanceOf.py b/python/file-test-data/extras/testLiteralInstanceOf_ok.py
similarity index 84%
rename from python/test-data/testLiteralInstanceOf.py
rename to python/file-test-data/extras/testLiteralInstanceOf_ok.py
index 9f653f11..32b97afb 100644
--- a/python/test-data/testLiteralInstanceOf.py
+++ b/python/file-test-data/extras/testLiteralInstanceOf_ok.py
@@ -1,12 +1,13 @@
from wypp import *
print(isinstance(1, Literal[1,2,3]))
-print(not isinstance(1, Literal["1", "2"]))
+print(isinstance(1, Literal["1", "2"]))
# nested
print(isinstance(1, Literal[1, Literal[Literal[2], 3]]))
print(isinstance(2, Literal[1, Literal[Literal[2], 3]]))
print(isinstance(3, Literal[1, Literal[Literal[2], 3]]))
+print(isinstance(4, Literal[1, Literal[Literal[2], 3]]))
print(Literal[1, Literal[Literal[2], 3]] == Literal[1, 2, 3])
print(Literal[1, Literal[Literal[2], 3]] != Literal[1, 2, 3, 4])
diff --git a/python/file-test-data/extras/testLockFactory.err b/python/file-test-data/extras/testLockFactory.err
new file mode 100644
index 00000000..933f618c
--- /dev/null
+++ b/python/file-test-data/extras/testLockFactory.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testLockFactory.py", line 15, in
+ foo("not a lock")
+
+WyppTypeError: "not a lock"
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `LockFactory` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/extras/testLockFactory.py
+## Fehlerhafter Aufruf in Zeile 15:
+
+foo("not a lock")
+
+## Typ deklariert in Zeile 6:
+
+def foo([0;31m[1mlock: wypp.LockFactory[0;0m) -> None:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testLockFactory.out b/python/file-test-data/extras/testLockFactory.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testLockFactory.py b/python/file-test-data/extras/testLockFactory.py
similarity index 100%
rename from python/test-data/testLockFactory.py
rename to python/file-test-data/extras/testLockFactory.py
diff --git a/python/file-test-data/extras/testLockFactory2.err b/python/file-test-data/extras/testLockFactory2.err
new file mode 100644
index 00000000..be3a9241
--- /dev/null
+++ b/python/file-test-data/extras/testLockFactory2.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testLockFactory2.py", line 14, in
+ foo("not a lock")
+
+WyppTypeError: "not a lock"
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `wypp.Lock` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/extras/testLockFactory2.py
+## Fehlerhafter Aufruf in Zeile 14:
+
+foo("not a lock")
+
+## Typ deklariert in Zeile 6:
+
+def foo([0;31m[1ml: wypp.Lock[0;0m) -> None:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testLockFactory2.out b/python/file-test-data/extras/testLockFactory2.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testLockFactory2.py b/python/file-test-data/extras/testLockFactory2.py
similarity index 100%
rename from python/test-data/testLockFactory2.py
rename to python/file-test-data/extras/testLockFactory2.py
diff --git a/python/file-test-data/extras/testLockFactory_ok.err b/python/file-test-data/extras/testLockFactory_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testLockFactory_ok.out b/python/file-test-data/extras/testLockFactory_ok.out
new file mode 100644
index 00000000..9766475a
--- /dev/null
+++ b/python/file-test-data/extras/testLockFactory_ok.out
@@ -0,0 +1 @@
+ok
diff --git a/python/file-test-data/extras/testLockFactory_ok.py b/python/file-test-data/extras/testLockFactory_ok.py
new file mode 100644
index 00000000..3d1e1613
--- /dev/null
+++ b/python/file-test-data/extras/testLockFactory_ok.py
@@ -0,0 +1,14 @@
+from wypp import *
+import wypp
+import threading
+# See: https://github.com/skogsbaer/write-your-python-program/issues/77
+
+def foo(lock: wypp.LockFactory) -> None:
+ l = lock()
+ l.acquire()
+ # ...
+ l.release()
+ pass
+
+foo(threading.Lock)
+print('ok')
diff --git a/python/file-test-data/extras/testMissingReturn.err b/python/file-test-data/extras/testMissingReturn.err
new file mode 100644
index 00000000..14babc70
--- /dev/null
+++ b/python/file-test-data/extras/testMissingReturn.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testMissingReturn.py", line 6, in
+ print(billigStrom(500))
+ File "file-test-data/extras/testMissingReturn.py", line 4, in billigStrom
+ pass
+
+WyppTypeError: kein Rückgabewert vorhanden
+
+Rückgabewert vom Typ `float` erwartet bei Aufruf der Funktion `billigStrom`.
+Aber kein Rückgabewert vorhanden.
+
+## Datei file-test-data/extras/testMissingReturn.py
+## Rückgabetyp deklariert in Zeile 3:
+
+def billigStrom(kwh: float) -> [0;31m[1mfloat[0;0m:
+
+## Aufruf in Zeile 6 führt dazu, dass die Funktion keinen Wert zurückgibt:
+
+print([0;31m[1mbilligStrom(500)[0;0m)
\ No newline at end of file
diff --git a/python/file-test-data/extras/testMissingReturn.out b/python/file-test-data/extras/testMissingReturn.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testMissingReturn.py b/python/file-test-data/extras/testMissingReturn.py
similarity index 100%
rename from python/test-data/testMissingReturn.py
rename to python/file-test-data/extras/testMissingReturn.py
diff --git a/python/file-test-data/extras/testNameErrorBug_ok.err b/python/file-test-data/extras/testNameErrorBug_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testNameErrorBug_ok.out b/python/file-test-data/extras/testNameErrorBug_ok.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testNameErrorBug.py b/python/file-test-data/extras/testNameErrorBug_ok.py
similarity index 100%
rename from python/test-data/testNameErrorBug.py
rename to python/file-test-data/extras/testNameErrorBug_ok.py
diff --git a/python/file-test-data/extras/testOriginalTypeNames_ok.err b/python/file-test-data/extras/testOriginalTypeNames_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testOriginalTypeNames_ok.out b/python/file-test-data/extras/testOriginalTypeNames_ok.out
new file mode 100644
index 00000000..614cfd32
--- /dev/null
+++ b/python/file-test-data/extras/testOriginalTypeNames_ok.out
@@ -0,0 +1 @@
+
diff --git a/python/test-data/testOriginalTypeNames.py b/python/file-test-data/extras/testOriginalTypeNames_ok.py
similarity index 100%
rename from python/test-data/testOriginalTypeNames.py
rename to python/file-test-data/extras/testOriginalTypeNames_ok.py
diff --git a/python/file-test-data/extras/testRecordSetTypeForwardRef.err b/python/file-test-data/extras/testRecordSetTypeForwardRef.err
new file mode 100644
index 00000000..a4b6d328
--- /dev/null
+++ b/python/file-test-data/extras/testRecordSetTypeForwardRef.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testRecordSetTypeForwardRef.py", line 15, in
+ m()
+ File "file-test-data/extras/testRecordSetTypeForwardRef.py", line 13, in m
+ r.x = "hello"
+
+WyppTypeError: "hello"
+
+Attribut `x` des Records `Record` deklariert als Typ `A`.
+Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden.
+
+## Datei file-test-data/extras/testRecordSetTypeForwardRef.py
+## Fehlerhafte Zuweisung in Zeile 13:
+
+ [0;31m[1mr.x[0;0m = "hello"
+
+## Typ deklariert in Zeile 6:
+
+ [0;31m[1mx: A[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/extras/testRecordSetTypeForwardRef.out b/python/file-test-data/extras/testRecordSetTypeForwardRef.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testRecordSetTypeForwardRef.py b/python/file-test-data/extras/testRecordSetTypeForwardRef.py
similarity index 100%
rename from python/test-data/testRecordSetTypeForwardRef.py
rename to python/file-test-data/extras/testRecordSetTypeForwardRef.py
diff --git a/python/file-test-data/extras/testRecordSetTypes.err b/python/file-test-data/extras/testRecordSetTypes.err
new file mode 100644
index 00000000..ce400994
--- /dev/null
+++ b/python/file-test-data/extras/testRecordSetTypes.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testRecordSetTypes.py", line 12, in
+ m()
+ File "file-test-data/extras/testRecordSetTypes.py", line 10, in m
+ r.x = "hello"
+
+WyppTypeError: "hello"
+
+Attribut `x` des Records `Record` deklariert als Typ `int`.
+Das Attribute kann nicht auf einen Wert vom Typ `str` gesetzt werden.
+
+## Datei file-test-data/extras/testRecordSetTypes.py
+## Fehlerhafte Zuweisung in Zeile 10:
+
+ [0;31m[1mr.x[0;0m = "hello"
+
+## Typ deklariert in Zeile 5:
+
+ [0;31m[1mx : int[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/extras/testRecordSetTypes.out b/python/file-test-data/extras/testRecordSetTypes.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testRecordSetTypes.py b/python/file-test-data/extras/testRecordSetTypes.py
similarity index 100%
rename from python/test-data/testRecordSetTypes.py
rename to python/file-test-data/extras/testRecordSetTypes.py
diff --git a/python/file-test-data/extras/testRecordTypes.err b/python/file-test-data/extras/testRecordTypes.err
new file mode 100644
index 00000000..aac97f56
--- /dev/null
+++ b/python/file-test-data/extras/testRecordTypes.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testRecordTypes.py", line 8, in
+ p = Point(1, '5')
+
+WyppTypeError: '5'
+
+Der Aufruf des Konstruktors des Records `Point` erwartet Wert vom Typ `int` als zweites Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/extras/testRecordTypes.py
+## Fehlerhafter Aufruf in Zeile 8:
+
+p = [0;31m[1mPoint(1, '5')[0;0m
+
+## Typ deklariert in Zeile 6:
+
+ [0;31m[1my: int[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/extras/testRecordTypes.out b/python/file-test-data/extras/testRecordTypes.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testRecordTypes.py b/python/file-test-data/extras/testRecordTypes.py
similarity index 100%
rename from python/test-data/testRecordTypes.py
rename to python/file-test-data/extras/testRecordTypes.py
diff --git a/python/file-test-data/extras/testTodo.err b/python/file-test-data/extras/testTodo.err
new file mode 100644
index 00000000..3764a715
--- /dev/null
+++ b/python/file-test-data/extras/testTodo.err
@@ -0,0 +1,7 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTodo.py", line 3, in
+ todo()
+ File "code/wypp/writeYourProgram.py", line 327, in todo
+ raise errors.TodoError(msg)
+
+TODO
diff --git a/python/file-test-data/extras/testTodo.out b/python/file-test-data/extras/testTodo.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTodo.py b/python/file-test-data/extras/testTodo.py
similarity index 100%
rename from python/test-data/testTodo.py
rename to python/file-test-data/extras/testTodo.py
diff --git a/python/file-test-data/extras/testTraceback.err b/python/file-test-data/extras/testTraceback.err
new file mode 100644
index 00000000..df26c7f1
--- /dev/null
+++ b/python/file-test-data/extras/testTraceback.err
@@ -0,0 +1,6 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTraceback.py", line 9, in
+ foo(lst)
+ File "file-test-data/extras/testTraceback.py", line 7, in foo
+ print(lst[10])
+IndexError: list index out of range
\ No newline at end of file
diff --git a/python/test-data/testTraceback.out b/python/file-test-data/extras/testTraceback.out
similarity index 100%
rename from python/test-data/testTraceback.out
rename to python/file-test-data/extras/testTraceback.out
diff --git a/python/test-data/testTraceback.py b/python/file-test-data/extras/testTraceback.py
similarity index 100%
rename from python/test-data/testTraceback.py
rename to python/file-test-data/extras/testTraceback.py
diff --git a/python/test-data/testTraceback2.err b/python/file-test-data/extras/testTraceback2.err
similarity index 58%
rename from python/test-data/testTraceback2.err
rename to python/file-test-data/extras/testTraceback2.err
index 82834546..d6703684 100644
--- a/python/test-data/testTraceback2.err
+++ b/python/file-test-data/extras/testTraceback2.err
@@ -1,4 +1,4 @@
- File "test-data/testTraceback2.py", line 3
+ File "testTraceback2.py", line ?
lst = [1,2,3
^
SyntaxError: '[' was never closed
diff --git a/python/file-test-data/extras/testTraceback2.out b/python/file-test-data/extras/testTraceback2.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTraceback2.py b/python/file-test-data/extras/testTraceback2.py
similarity index 100%
rename from python/test-data/testTraceback2.py
rename to python/file-test-data/extras/testTraceback2.py
diff --git a/python/file-test-data/extras/testTraceback3.err b/python/file-test-data/extras/testTraceback3.err
new file mode 100644
index 00000000..7bc652e2
--- /dev/null
+++ b/python/file-test-data/extras/testTraceback3.err
@@ -0,0 +1,4 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTraceback3.py", line 2, in
+ print([1,2,3][10])
+IndexError: list index out of range
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTraceback3.out b/python/file-test-data/extras/testTraceback3.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTraceback3.py b/python/file-test-data/extras/testTraceback3.py
similarity index 100%
rename from python/test-data/testTraceback3.py
rename to python/file-test-data/extras/testTraceback3.py
diff --git a/python/file-test-data/extras/testTypeKeyword_ok.err b/python/file-test-data/extras/testTypeKeyword_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypeKeyword.out b/python/file-test-data/extras/testTypeKeyword_ok.out
similarity index 100%
rename from python/test-data/testTypeKeyword.out
rename to python/file-test-data/extras/testTypeKeyword_ok.out
diff --git a/python/test-data/testTypeKeyword.py b/python/file-test-data/extras/testTypeKeyword_ok.py
similarity index 82%
rename from python/test-data/testTypeKeyword.py
rename to python/file-test-data/extras/testTypeKeyword_ok.py
index a9125795..d98ceb0b 100644
--- a/python/test-data/testTypeKeyword.py
+++ b/python/file-test-data/extras/testTypeKeyword_ok.py
@@ -1,3 +1,4 @@
+# WYPP_TEST_CONFIG: {"typecheck": "both"}
from __future__ import annotations
from wypp import *
diff --git a/python/file-test-data/extras/testTypes1.err b/python/file-test-data/extras/testTypes1.err
new file mode 100644
index 00000000..ca748a76
--- /dev/null
+++ b/python/file-test-data/extras/testTypes1.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypes1.py", line 4, in
+ inc("1")
+
+WyppTypeError: "1"
+
+Der Aufruf der Funktion `inc` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/extras/testTypes1.py
+## Fehlerhafter Aufruf in Zeile 4:
+
+inc("1")
+
+## Typ deklariert in Zeile 1:
+
+def inc([0;31m[1mx: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testTypes1.err-notypes b/python/file-test-data/extras/testTypes1.err-notypes
similarity index 100%
rename from python/test-data/testTypes1.err-notypes
rename to python/file-test-data/extras/testTypes1.err-notypes
diff --git a/python/file-test-data/extras/testTypes1.out b/python/file-test-data/extras/testTypes1.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypes1.py b/python/file-test-data/extras/testTypes1.py
similarity index 100%
rename from python/test-data/testTypes1.py
rename to python/file-test-data/extras/testTypes1.py
diff --git a/python/file-test-data/extras/testTypes2.err b/python/file-test-data/extras/testTypes2.err
new file mode 100644
index 00000000..fa96aea6
--- /dev/null
+++ b/python/file-test-data/extras/testTypes2.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypes2.py", line 4, in
+ inc("1")
+
+WyppTypeError: "1"
+
+Der Aufruf der Funktion `inc` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/extras/testTypes2.py
+## Fehlerhafter Aufruf in Zeile 4:
+
+inc("1")
+
+## Typ deklariert in Zeile 1:
+
+def inc([0;31m[1mx: int[0;0m) -> int:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypes2.err-notypes b/python/file-test-data/extras/testTypes2.err-notypes
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testTypes2.out b/python/file-test-data/extras/testTypes2.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypes2.py b/python/file-test-data/extras/testTypes2.py
similarity index 100%
rename from python/test-data/testTypes2.py
rename to python/file-test-data/extras/testTypes2.py
diff --git a/python/file-test-data/extras/testTypes2_ok.py b/python/file-test-data/extras/testTypes2_ok.py
new file mode 100644
index 00000000..c99dca5c
--- /dev/null
+++ b/python/file-test-data/extras/testTypes2_ok.py
@@ -0,0 +1,5 @@
+# WYPP_TEST_CONFIG: {"typecheck": false}
+def inc(x: int) -> int:
+ return x
+
+inc("1")
diff --git a/python/file-test-data/extras/testTypesDict3.err b/python/file-test-data/extras/testTypesDict3.err
new file mode 100644
index 00000000..09717b3d
--- /dev/null
+++ b/python/file-test-data/extras/testTypesDict3.err
@@ -0,0 +1,22 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesDict3.py", line 11, in
+ foo({'y': func})
+ File "file-test-data/extras/testTypesDict3.py", line 8, in foo
+ return res
+
+WyppTypeError: ['xxx', 42]
+
+Rückgabewert vom Typ `list[str]` erwartet bei Aufruf der Funktion `foo`.
+
+## Datei file-test-data/extras/testTypesDict3.py
+## Rückgabetyp deklariert in Zeile 3:
+
+def foo(d: dict[str, Callable[[], str]]) -> [0;31m[1mlist[str][0;0m:
+
+## Fehlerhaftes return in Zeile 8:
+
+ return res
+
+## Aufruf in Zeile 11 verursacht das fehlerhafte return:
+
+foo({'y': func})
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesDict3.out b/python/file-test-data/extras/testTypesDict3.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesDict3.py b/python/file-test-data/extras/testTypesDict3.py
similarity index 100%
rename from python/test-data/testTypesDict3.py
rename to python/file-test-data/extras/testTypesDict3.py
diff --git a/python/file-test-data/extras/testTypesDict4.err b/python/file-test-data/extras/testTypesDict4.err
new file mode 100644
index 00000000..195ea6af
--- /dev/null
+++ b/python/file-test-data/extras/testTypesDict4.err
@@ -0,0 +1,24 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesDict4.py", line 14, in
+ bar({'y': func}) # error
+ File "file-test-data/extras/testTypesDict4.py", line 11, in bar
+ return foo(d)
+ File "file-test-data/extras/testTypesDict4.py", line 8, in foo
+ return res
+
+WyppTypeError: [42, 'x']
+
+Rückgabewert vom Typ `list[str]` erwartet bei Aufruf der Funktion `foo`.
+
+## Datei file-test-data/extras/testTypesDict4.py
+## Rückgabetyp deklariert in Zeile 3:
+
+def foo(d: dict[str, Callable[[], str]]) -> [0;31m[1mlist[str][0;0m:
+
+## Fehlerhaftes return in Zeile 8:
+
+ return res
+
+## Aufruf in Zeile 11 verursacht das fehlerhafte return:
+
+ return [0;31m[1mfoo(d)[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesDict4.out b/python/file-test-data/extras/testTypesDict4.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesDict4.py b/python/file-test-data/extras/testTypesDict4.py
similarity index 100%
rename from python/test-data/testTypesDict4.py
rename to python/file-test-data/extras/testTypesDict4.py
diff --git a/python/file-test-data/extras/testTypesHigherOrderFuns.err b/python/file-test-data/extras/testTypesHigherOrderFuns.err
new file mode 100644
index 00000000..ab4217f9
--- /dev/null
+++ b/python/file-test-data/extras/testTypesHigherOrderFuns.err
@@ -0,0 +1,22 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesHigherOrderFuns.py", line 10, in
+ map(["hello", "1"], lambda x: x)
+ File "file-test-data/extras/testTypesHigherOrderFuns.py", line 7, in map
+ return res
+
+WyppTypeError: ['hello', '1']
+
+Rückgabewert vom Typ `list[int]` erwartet bei Aufruf der Funktion `map`.
+
+## Datei file-test-data/extras/testTypesHigherOrderFuns.py
+## Rückgabetyp deklariert in Zeile 3:
+
+def map(container: Iterable[str], fun: Callable[[str], int]) -> [0;31m[1mlist[int][0;0m:
+
+## Fehlerhaftes return in Zeile 7:
+
+ return res
+
+## Aufruf in Zeile 10 verursacht das fehlerhafte return:
+
+map(["hello", "1"], lambda x: x)
\ No newline at end of file
diff --git a/python/test-data/testTypesHigherOrderFuns.out b/python/file-test-data/extras/testTypesHigherOrderFuns.out
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns.out
rename to python/file-test-data/extras/testTypesHigherOrderFuns.out
diff --git a/python/test-data/testTypesHigherOrderFuns.py b/python/file-test-data/extras/testTypesHigherOrderFuns.py
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns.py
rename to python/file-test-data/extras/testTypesHigherOrderFuns.py
diff --git a/python/file-test-data/extras/testTypesHigherOrderFuns2_ok.err b/python/file-test-data/extras/testTypesHigherOrderFuns2_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesHigherOrderFuns2.out b/python/file-test-data/extras/testTypesHigherOrderFuns2_ok.out
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns2.out
rename to python/file-test-data/extras/testTypesHigherOrderFuns2_ok.out
diff --git a/python/test-data/testTypesHigherOrderFuns2.py b/python/file-test-data/extras/testTypesHigherOrderFuns2_ok.py
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns2.py
rename to python/file-test-data/extras/testTypesHigherOrderFuns2_ok.py
diff --git a/python/file-test-data/extras/testTypesHigherOrderFuns3.err b/python/file-test-data/extras/testTypesHigherOrderFuns3.err
new file mode 100644
index 00000000..28bdb129
--- /dev/null
+++ b/python/file-test-data/extras/testTypesHigherOrderFuns3.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesHigherOrderFuns3.py", line 38, in
+ homePoints: Callable[[GameResult], int] = mkGamePoints(42)
+
+WyppTypeError: 42
+
+Der Aufruf der Funktion `mkGamePoints` erwartet Wert vom Typ `Callable[[int, int], bool]` als erstes Argument.
+Aber der übergebene Wert hat Typ `int`.
+
+## Datei file-test-data/extras/testTypesHigherOrderFuns3.py
+## Fehlerhafter Aufruf in Zeile 38:
+
+homePoints: Callable[[GameResult], int] = mkGamePoints([0;31m[1m42[0;0m)
+
+## Typ deklariert in Zeile 35:
+
+def mkGamePoints([0;31m[1mcmp: Callable[[int, int], bool][0;0m) -> Callable[[GameResult], int]:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesHigherOrderFuns3.out b/python/file-test-data/extras/testTypesHigherOrderFuns3.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesHigherOrderFuns3.py b/python/file-test-data/extras/testTypesHigherOrderFuns3.py
similarity index 94%
rename from python/test-data/testTypesHigherOrderFuns3.py
rename to python/file-test-data/extras/testTypesHigherOrderFuns3.py
index 15319d0a..4777f85c 100644
--- a/python/test-data/testTypesHigherOrderFuns3.py
+++ b/python/file-test-data/extras/testTypesHigherOrderFuns3.py
@@ -35,7 +35,7 @@ def gamePoints(game: GameResult, cmp: Callable[[int, int], bool]) -> int:
def mkGamePoints(cmp: Callable[[int, int], bool]) -> Callable[[GameResult], int]:
return lambda game: gamePoints(game, cmp)
-homePoints: Callable[[GameResult], int] = mkGamePoints(lambda g, h: "foo")
+homePoints: Callable[[GameResult], int] = mkGamePoints(42)
guestPoints: Callable[[GameResult], int] = mkGamePoints(lambda g, h: h > g)
check(homePoints(game1), 0)
diff --git a/python/file-test-data/extras/testTypesHigherOrderFuns4_ok.err b/python/file-test-data/extras/testTypesHigherOrderFuns4_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesHigherOrderFuns4.out b/python/file-test-data/extras/testTypesHigherOrderFuns4_ok.out
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns4.out
rename to python/file-test-data/extras/testTypesHigherOrderFuns4_ok.out
diff --git a/python/test-data/testTypesHigherOrderFuns4.py b/python/file-test-data/extras/testTypesHigherOrderFuns4_ok.py
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns4.py
rename to python/file-test-data/extras/testTypesHigherOrderFuns4_ok.py
diff --git a/python/file-test-data/extras/testTypesHigherOrderFuns5_ok.err b/python/file-test-data/extras/testTypesHigherOrderFuns5_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesHigherOrderFuns5.out b/python/file-test-data/extras/testTypesHigherOrderFuns5_ok.out
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns5.out
rename to python/file-test-data/extras/testTypesHigherOrderFuns5_ok.out
diff --git a/python/test-data/testTypesHigherOrderFuns5.py b/python/file-test-data/extras/testTypesHigherOrderFuns5_ok.py
similarity index 100%
rename from python/test-data/testTypesHigherOrderFuns5.py
rename to python/file-test-data/extras/testTypesHigherOrderFuns5_ok.py
diff --git a/python/file-test-data/extras/testTypesProtos1.err b/python/file-test-data/extras/testTypesProtos1.err
new file mode 100644
index 00000000..1dbb65d6
--- /dev/null
+++ b/python/file-test-data/extras/testTypesProtos1.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesProtos1.py", line 21, in
+ doSomething(Dog())
+ File "file-test-data/extras/testTypesProtos1.py", line 19, in doSomething
+ print(a.makeSound(3.14))
+
+WyppTypeError: 3.14
+
+Der Aufruf der Methode `makeSound` aus Klasse `Dog` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `float`.
+
+## Datei file-test-data/extras/testTypesProtos1.py
+## Fehlerhafter Aufruf in Zeile 19:
+
+ print([0;31m[1ma.makeSound(3.14)[0;0m)
+
+## Typ deklariert in Zeile 13:
+
+ def makeSound(self, [0;31m[1mloadness: int[0;0m) -> str:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesProtos1.out b/python/file-test-data/extras/testTypesProtos1.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesProtos1.py b/python/file-test-data/extras/testTypesProtos1.py
similarity index 100%
rename from python/test-data/testTypesProtos1.py
rename to python/file-test-data/extras/testTypesProtos1.py
diff --git a/python/file-test-data/extras/testTypesProtos2.err b/python/file-test-data/extras/testTypesProtos2.err
new file mode 100644
index 00000000..f25564ef
--- /dev/null
+++ b/python/file-test-data/extras/testTypesProtos2.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesProtos2.py", line 21, in
+ doSomething(Dog())
+ File "file-test-data/extras/testTypesProtos2.py", line 19, in doSomething
+ print(a.makeSound(3.14))
+
+WyppTypeError: 3.14
+
+Der Aufruf der Methode `makeSound` aus Klasse `Dog` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `float`.
+
+## Datei file-test-data/extras/testTypesProtos2.py
+## Fehlerhafter Aufruf in Zeile 19:
+
+ print([0;31m[1ma.makeSound(3.14)[0;0m)
+
+## Typ deklariert in Zeile 13:
+
+ def makeSound(self, [0;31m[1mloadness: int[0;0m) -> str:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesProtos2.out b/python/file-test-data/extras/testTypesProtos2.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesProtos2.py b/python/file-test-data/extras/testTypesProtos2.py
similarity index 100%
rename from python/test-data/testTypesProtos2.py
rename to python/file-test-data/extras/testTypesProtos2.py
diff --git a/python/file-test-data/extras/testTypesProtos3.err b/python/file-test-data/extras/testTypesProtos3.err
new file mode 100644
index 00000000..e8d33de9
--- /dev/null
+++ b/python/file-test-data/extras/testTypesProtos3.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesProtos3.py", line 21, in
+ doSomething(Dog())
+ File "file-test-data/extras/testTypesProtos3.py", line 19, in doSomething
+ print(a.makeSound(3.14))
+
+WyppTypeError:
+
+Der Aufruf der Methode `makeSound` aus Klasse `Dog` erwartet Wert vom Typ `int` als erstes Argument.
+Aber der übergebene Wert hat Typ `Dog`.
+
+## Datei file-test-data/extras/testTypesProtos3.py
+## Fehlerhafter Aufruf in Zeile 19:
+
+ print(a.makeSound([0;31m[1m3.14[0;0m))
+
+## Typ deklariert in Zeile 13:
+
+ def makeSound([0;31m[1mloadness: int[0;0m) -> str: # self parameter omitted!
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesProtos3.out b/python/file-test-data/extras/testTypesProtos3.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesProtos3.py b/python/file-test-data/extras/testTypesProtos3.py
similarity index 100%
rename from python/test-data/testTypesProtos3.py
rename to python/file-test-data/extras/testTypesProtos3.py
diff --git a/python/file-test-data/extras/testTypesProtos4.err b/python/file-test-data/extras/testTypesProtos4.err
new file mode 100644
index 00000000..a41011f4
--- /dev/null
+++ b/python/file-test-data/extras/testTypesProtos4.err
@@ -0,0 +1,21 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesProtos4.py", line 27, in
+ print(foo(ConcreteWrong()))
+ File "file-test-data/extras/testTypesProtos4.py", line 24, in foo
+ return fn(2)
+ File "file-test-data/extras/testTypesProtos4.py", line 20, in
+ return lambda x: bar(x) # invalid call of bar with argument of type int
+
+WyppTypeError: 2
+
+Der Aufruf der Funktion `bar` erwartet Wert vom Typ `str` als erstes Argument.
+Aber der übergebene Wert hat Typ `int`.
+
+## Datei file-test-data/extras/testTypesProtos4.py
+## Fehlerhafter Aufruf in Zeile 20:
+
+ return lambda x: bar([0;31m[1mx[0;0m) # invalid call of bar with argument of type int
+
+## Typ deklariert in Zeile 15:
+
+def bar([0;31m[1ms: str[0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testTypesProtos4.out b/python/file-test-data/extras/testTypesProtos4.out
similarity index 100%
rename from python/test-data/testTypesProtos4.out
rename to python/file-test-data/extras/testTypesProtos4.out
diff --git a/python/test-data/testTypesProtos4.py b/python/file-test-data/extras/testTypesProtos4.py
similarity index 100%
rename from python/test-data/testTypesProtos4.py
rename to python/file-test-data/extras/testTypesProtos4.py
diff --git a/python/file-test-data/extras/testTypesProtos5_ok.err b/python/file-test-data/extras/testTypesProtos5_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesProtos5.out b/python/file-test-data/extras/testTypesProtos5_ok.out
similarity index 100%
rename from python/test-data/testTypesProtos5.out
rename to python/file-test-data/extras/testTypesProtos5_ok.out
diff --git a/python/test-data/testTypesProtos5.py b/python/file-test-data/extras/testTypesProtos5_ok.py
similarity index 100%
rename from python/test-data/testTypesProtos5.py
rename to python/file-test-data/extras/testTypesProtos5_ok.py
diff --git a/python/file-test-data/extras/testTypesProtos6.err b/python/file-test-data/extras/testTypesProtos6.err
new file mode 100644
index 00000000..e20d49fd
--- /dev/null
+++ b/python/file-test-data/extras/testTypesProtos6.err
@@ -0,0 +1,25 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesProtos6.py", line 57, in
+ print(computeTotalSize(root))
+ File "file-test-data/extras/testTypesProtos6.py", line 50, in computeTotalSize
+ fs.accept(visitor)
+ File "file-test-data/extras/testTypesProtos6.py", line 19, in accept
+ visitor.visitDirectory(self)
+ File "file-test-data/extras/testTypesProtos6.py", line 41, in visitDirectory
+ c.accept(self)
+ File "file-test-data/extras/testTypesProtos6.py", line 28, in accept
+ visitor.visitFile(self)
+
+WyppTypeError: <__wypp__.File object at 0x00>
+
+Der Aufruf der Methode `visitFile` aus Klasse `TotalSizeVisitor` erwartet Wert vom Typ `str` als erstes Argument.
+Aber der übergebene Wert hat Typ `File`.
+
+## Datei file-test-data/extras/testTypesProtos6.py
+## Fehlerhafter Aufruf in Zeile 28:
+
+ [0;31m[1mvisitor.visitFile(self)[0;0m
+
+## Typ deklariert in Zeile 42:
+
+ def visitFile(self, [0;31m[1mfile: str[0;0m):
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesProtos6.out b/python/file-test-data/extras/testTypesProtos6.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesProtos6.py b/python/file-test-data/extras/testTypesProtos6.py
similarity index 100%
rename from python/test-data/testTypesProtos6.py
rename to python/file-test-data/extras/testTypesProtos6.py
diff --git a/python/file-test-data/extras/testTypesProtos7.err b/python/file-test-data/extras/testTypesProtos7.err
new file mode 100644
index 00000000..d61b751e
--- /dev/null
+++ b/python/file-test-data/extras/testTypesProtos7.err
@@ -0,0 +1,25 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesProtos7.py", line 76, in
+ print(computeTotalSize(root))
+ File "file-test-data/extras/testTypesProtos7.py", line 69, in computeTotalSize
+ fs.accept(visitor)
+ File "file-test-data/extras/testTypesProtos7.py", line 36, in accept
+ visitor.visitDirectory(self)
+ File "file-test-data/extras/testTypesProtos7.py", line 60, in visitDirectory
+ c.accept(self)
+ File "file-test-data/extras/testTypesProtos7.py", line 47, in accept
+ visitor.visitFile(self)
+
+WyppTypeError: File('notes.txt')
+
+Der Aufruf der Methode `visitFile` aus Klasse `TotalSizeVisitor` erwartet Wert vom Typ `str` als erstes Argument.
+Aber der übergebene Wert hat Typ `File`.
+
+## Datei file-test-data/extras/testTypesProtos7.py
+## Fehlerhafter Aufruf in Zeile 47:
+
+ [0;31m[1mvisitor.visitFile(self)[0;0m
+
+## Typ deklariert in Zeile 61:
+
+ def visitFile(self, [0;31m[1mf: str[0;0m):
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesProtos7.out b/python/file-test-data/extras/testTypesProtos7.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesProtos7.py b/python/file-test-data/extras/testTypesProtos7.py
similarity index 100%
rename from python/test-data/testTypesProtos7.py
rename to python/file-test-data/extras/testTypesProtos7.py
diff --git a/python/file-test-data/extras/testTypesProtos8.err b/python/file-test-data/extras/testTypesProtos8.err
new file mode 100644
index 00000000..a6ff1267
--- /dev/null
+++ b/python/file-test-data/extras/testTypesProtos8.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesProtos8.py", line 12, in
+ bar(Sub())
+ File "file-test-data/extras/testTypesProtos8.py", line 10, in bar
+ b.foo(1, "foo")
+
+WyppTypeError: "foo"
+
+Der Aufruf der Methode `foo` aus Klasse `Sub` erwartet Wert vom Typ `float` als zweites Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/extras/testTypesProtos8.py
+## Fehlerhafter Aufruf in Zeile 10:
+
+ [0;31m[1mb.foo(1, "foo")[0;0m
+
+## Typ deklariert in Zeile 6:
+
+ def foo(self, y: int, [0;31m[1mx: float[0;0m):
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesProtos8.out b/python/file-test-data/extras/testTypesProtos8.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesProtos8.py b/python/file-test-data/extras/testTypesProtos8.py
similarity index 100%
rename from python/test-data/testTypesProtos8.py
rename to python/file-test-data/extras/testTypesProtos8.py
diff --git a/python/file-test-data/extras/testTypesProtos9.err b/python/file-test-data/extras/testTypesProtos9.err
new file mode 100644
index 00000000..5d271c3d
--- /dev/null
+++ b/python/file-test-data/extras/testTypesProtos9.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesProtos9.py", line 12, in
+ bar(Sub())
+ File "file-test-data/extras/testTypesProtos9.py", line 10, in bar
+ b.foo(1, "foo")
+
+WyppTypeError: "foo"
+
+Der Aufruf der Methode `foo` aus Klasse `Sub` erwartet Wert vom Typ `float` als zweites Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/extras/testTypesProtos9.py
+## Fehlerhafter Aufruf in Zeile 10:
+
+ [0;31m[1mb.foo(1, "foo")[0;0m
+
+## Typ deklariert in Zeile 6:
+
+ def foo(self, subX: int, [0;31m[1msubY: float[0;0m):
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesProtos9.out b/python/file-test-data/extras/testTypesProtos9.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesProtos9.py b/python/file-test-data/extras/testTypesProtos9.py
similarity index 100%
rename from python/test-data/testTypesProtos9.py
rename to python/file-test-data/extras/testTypesProtos9.py
diff --git a/python/file-test-data/extras/testTypesRecordInheritance.err b/python/file-test-data/extras/testTypesRecordInheritance.err
new file mode 100644
index 00000000..cb44a0b4
--- /dev/null
+++ b/python/file-test-data/extras/testTypesRecordInheritance.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesRecordInheritance.py", line 18, in
+ Point3D(1,2, "foo")
+
+WyppTypeError: "foo"
+
+Der Aufruf des Konstruktors des Records `Point3D` erwartet Wert vom Typ `int` als drittes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/extras/testTypesRecordInheritance.py
+## Fehlerhafter Aufruf in Zeile 18:
+
+Point3D(1,2, "foo")
+
+## Typ deklariert in Zeile 11:
+
+ [0;31m[1mz: int[0;0m
\ No newline at end of file
diff --git a/python/test-data/testTypesRecordInheritance.out b/python/file-test-data/extras/testTypesRecordInheritance.out
similarity index 100%
rename from python/test-data/testTypesRecordInheritance.out
rename to python/file-test-data/extras/testTypesRecordInheritance.out
diff --git a/python/test-data/testTypesRecordInheritance.py b/python/file-test-data/extras/testTypesRecordInheritance.py
similarity index 100%
rename from python/test-data/testTypesRecordInheritance.py
rename to python/file-test-data/extras/testTypesRecordInheritance.py
diff --git a/python/file-test-data/extras/testTypesReturn.err b/python/file-test-data/extras/testTypesReturn.err
new file mode 100644
index 00000000..cea0d411
--- /dev/null
+++ b/python/file-test-data/extras/testTypesReturn.err
@@ -0,0 +1,23 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesReturn.py", line 8, in
+ foo(False)
+ File "file-test-data/extras/testTypesReturn.py", line 6, in foo
+ return 'you stupid'
+
+WyppTypeError: 'you stupid'
+
+Rückgabewert vom Typ `int` erwartet bei Aufruf der Funktion `foo`.
+Aber der Aufruf gibt einen Wert vom Typ `str` zurück.
+
+## Datei file-test-data/extras/testTypesReturn.py
+## Rückgabetyp deklariert in Zeile 1:
+
+def foo(flag: bool) -> [0;31m[1mint[0;0m:
+
+## Fehlerhaftes return in Zeile 6:
+
+ return 'you stupid'
+
+## Aufruf in Zeile 8 verursacht das fehlerhafte return:
+
+foo(False)
\ No newline at end of file
diff --git a/python/test-data/testTypesReturn.out b/python/file-test-data/extras/testTypesReturn.out
similarity index 100%
rename from python/test-data/testTypesReturn.out
rename to python/file-test-data/extras/testTypesReturn.out
diff --git a/python/test-data/testTypesReturn.py b/python/file-test-data/extras/testTypesReturn.py
similarity index 100%
rename from python/test-data/testTypesReturn.py
rename to python/file-test-data/extras/testTypesReturn.py
diff --git a/python/file-test-data/extras/testTypesSequence1.err b/python/file-test-data/extras/testTypesSequence1.err
new file mode 100644
index 00000000..cd7927a8
--- /dev/null
+++ b/python/file-test-data/extras/testTypesSequence1.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesSequence1.py", line 10, in
+ foo(1) # should fail
+
+WyppTypeError: 1
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Sequence` als erstes Argument.
+Aber der übergebene Wert hat Typ `int`.
+
+## Datei file-test-data/extras/testTypesSequence1.py
+## Fehlerhafter Aufruf in Zeile 10:
+
+foo(1) # should fail
+
+## Typ deklariert in Zeile 3:
+
+def foo([0;31m[1mseq: Sequence[0;0m) -> None:
\ No newline at end of file
diff --git a/python/test-data/testTypesSequence1.out b/python/file-test-data/extras/testTypesSequence1.out
similarity index 100%
rename from python/test-data/testTypesSequence1.out
rename to python/file-test-data/extras/testTypesSequence1.out
diff --git a/python/test-data/testTypesSequence1.py b/python/file-test-data/extras/testTypesSequence1.py
similarity index 100%
rename from python/test-data/testTypesSequence1.py
rename to python/file-test-data/extras/testTypesSequence1.py
diff --git a/python/file-test-data/extras/testTypesSequence2.err b/python/file-test-data/extras/testTypesSequence2.err
new file mode 100644
index 00000000..de4b451b
--- /dev/null
+++ b/python/file-test-data/extras/testTypesSequence2.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesSequence2.py", line 11, in
+ foo("Hello!") # should fail
+
+WyppTypeError: "Hello!"
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `Sequence[int]` als erstes Argument.
+Aber der übergebene Wert hat Typ `str`.
+
+## Datei file-test-data/extras/testTypesSequence2.py
+## Fehlerhafter Aufruf in Zeile 11:
+
+foo("Hello!") # should fail
+
+## Typ deklariert in Zeile 3:
+
+def foo([0;31m[1mseq: Sequence[int][0;0m) -> None:
\ No newline at end of file
diff --git a/python/test-data/testTypesSequence2.out b/python/file-test-data/extras/testTypesSequence2.out
similarity index 73%
rename from python/test-data/testTypesSequence2.out
rename to python/file-test-data/extras/testTypesSequence2.out
index d19f48dc..5e77e5dc 100644
--- a/python/test-data/testTypesSequence2.out
+++ b/python/file-test-data/extras/testTypesSequence2.out
@@ -7,5 +7,3 @@
(4, 5)
4
5
-'Hello!'
-Hello!
diff --git a/python/test-data/testTypesSequence2.py b/python/file-test-data/extras/testTypesSequence2.py
similarity index 100%
rename from python/test-data/testTypesSequence2.py
rename to python/file-test-data/extras/testTypesSequence2.py
diff --git a/python/file-test-data/extras/testTypesSubclassing1.err b/python/file-test-data/extras/testTypesSubclassing1.err
new file mode 100644
index 00000000..ecfbeb53
--- /dev/null
+++ b/python/file-test-data/extras/testTypesSubclassing1.err
@@ -0,0 +1,19 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesSubclassing1.py", line 29, in
+ feedAnimal(dog)
+ File "file-test-data/extras/testTypesSubclassing1.py", line 26, in feedAnimal
+ a.feed(AnimalFood('some cat food'))
+
+WyppTypeError:
+
+Der Aufruf der Methode `feed` aus Klasse `Dog` erwartet Wert vom Typ `DogFood` als erstes Argument.
+Aber der übergebene Wert hat Typ `AnimalFood`.
+
+## Datei file-test-data/extras/testTypesSubclassing1.py
+## Fehlerhafter Aufruf in Zeile 26:
+
+ [0;31m[1ma.feed(AnimalFood('some cat food'))[0;0m
+
+## Typ deklariert in Zeile 22:
+
+ def feed(self, [0;31m[1mfood: DogFood[0;0m) -> None:
\ No newline at end of file
diff --git a/python/file-test-data/extras/testTypesSubclassing1.out b/python/file-test-data/extras/testTypesSubclassing1.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesSubclassing1.py b/python/file-test-data/extras/testTypesSubclassing1.py
similarity index 100%
rename from python/test-data/testTypesSubclassing1.py
rename to python/file-test-data/extras/testTypesSubclassing1.py
diff --git a/python/file-test-data/extras/testTypesTuple1.err b/python/file-test-data/extras/testTypesTuple1.err
new file mode 100644
index 00000000..71647939
--- /dev/null
+++ b/python/file-test-data/extras/testTypesTuple1.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testTypesTuple1.py", line 5, in
+ foo(1)
+
+WyppTypeError: 1
+
+Der Aufruf der Funktion `foo` erwartet Wert vom Typ `tuple[int, ...]` als erstes Argument.
+Aber der übergebene Wert hat Typ `int`.
+
+## Datei file-test-data/extras/testTypesTuple1.py
+## Fehlerhafter Aufruf in Zeile 5:
+
+foo(1)
+
+## Typ deklariert in Zeile 1:
+
+def foo([0;31m[1ml: tuple[int, ...][0;0m) -> int:
\ No newline at end of file
diff --git a/python/test-data/testTypesTuple1.out b/python/file-test-data/extras/testTypesTuple1.out
similarity index 100%
rename from python/test-data/testTypesTuple1.out
rename to python/file-test-data/extras/testTypesTuple1.out
diff --git a/python/test-data/testTypesTuple1.py b/python/file-test-data/extras/testTypesTuple1.py
similarity index 100%
rename from python/test-data/testTypesTuple1.py
rename to python/file-test-data/extras/testTypesTuple1.py
diff --git a/python/file-test-data/extras/testTypesWrapperEq_ok.err b/python/file-test-data/extras/testTypesWrapperEq_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testTypesWrapperEq.out b/python/file-test-data/extras/testTypesWrapperEq_ok.out
similarity index 100%
rename from python/test-data/testTypesWrapperEq.out
rename to python/file-test-data/extras/testTypesWrapperEq_ok.out
diff --git a/python/test-data/testTypesWrapperEq.py b/python/file-test-data/extras/testTypesWrapperEq_ok.py
similarity index 100%
rename from python/test-data/testTypesWrapperEq.py
rename to python/file-test-data/extras/testTypesWrapperEq_ok.py
diff --git a/python/file-test-data/extras/testUnion2_ok.err b/python/file-test-data/extras/testUnion2_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testUnion2.out b/python/file-test-data/extras/testUnion2_ok.out
similarity index 100%
rename from python/test-data/testUnion2.out
rename to python/file-test-data/extras/testUnion2_ok.out
diff --git a/python/test-data/testUnion2.py b/python/file-test-data/extras/testUnion2_ok.py
similarity index 100%
rename from python/test-data/testUnion2.py
rename to python/file-test-data/extras/testUnion2_ok.py
diff --git a/python/file-test-data/extras/testUnion3_ok.err b/python/file-test-data/extras/testUnion3_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testUnion3.out b/python/file-test-data/extras/testUnion3_ok.out
similarity index 100%
rename from python/test-data/testUnion3.out
rename to python/file-test-data/extras/testUnion3_ok.out
diff --git a/python/test-data/testUnion3.py b/python/file-test-data/extras/testUnion3_ok.py
similarity index 100%
rename from python/test-data/testUnion3.py
rename to python/file-test-data/extras/testUnion3_ok.py
diff --git a/python/file-test-data/extras/testUnionLiteral_ok.err b/python/file-test-data/extras/testUnionLiteral_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testUnionLiteral_ok.out b/python/file-test-data/extras/testUnionLiteral_ok.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testUnionLiteral.py b/python/file-test-data/extras/testUnionLiteral_ok.py
similarity index 100%
rename from python/test-data/testUnionLiteral.py
rename to python/file-test-data/extras/testUnionLiteral_ok.py
diff --git a/python/file-test-data/extras/testUnionOfUnion_ok.err b/python/file-test-data/extras/testUnionOfUnion_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testUnionOfUnion.out b/python/file-test-data/extras/testUnionOfUnion_ok.out
similarity index 100%
rename from python/test-data/testUnionOfUnion.out
rename to python/file-test-data/extras/testUnionOfUnion_ok.out
diff --git a/python/test-data/testUnionOfUnion.py b/python/file-test-data/extras/testUnionOfUnion_ok.py
similarity index 100%
rename from python/test-data/testUnionOfUnion.py
rename to python/file-test-data/extras/testUnionOfUnion_ok.py
diff --git a/python/file-test-data/extras/testUnion_ok.err b/python/file-test-data/extras/testUnion_ok.err
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testUnion.out b/python/file-test-data/extras/testUnion_ok.out
similarity index 100%
rename from python/test-data/testUnion.out
rename to python/file-test-data/extras/testUnion_ok.out
diff --git a/python/test-data/testUnion.py b/python/file-test-data/extras/testUnion_ok.py
similarity index 100%
rename from python/test-data/testUnion.py
rename to python/file-test-data/extras/testUnion_ok.py
diff --git a/python/file-test-data/extras/testWrongKeywordArg.err b/python/file-test-data/extras/testWrongKeywordArg.err
new file mode 100644
index 00000000..77c95bd0
--- /dev/null
+++ b/python/file-test-data/extras/testWrongKeywordArg.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testWrongKeywordArg.py", line 4, in
+ foo(x=4)
+
+WyppTypeError: unbekanntes Schlüsselwort-Argument
+
+Funktion `foo` akzeptiert kein Schlüsselwort-Argument `x`.
+
+## Datei file-test-data/extras/testWrongKeywordArg.py
+## Fehlerhafter Aufruf in Zeile 4:
+
+foo(x=4)
\ No newline at end of file
diff --git a/python/file-test-data/extras/testWrongKeywordArg.out b/python/file-test-data/extras/testWrongKeywordArg.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testWrongKeywordArg.py b/python/file-test-data/extras/testWrongKeywordArg.py
similarity index 100%
rename from python/test-data/testWrongKeywordArg.py
rename to python/file-test-data/extras/testWrongKeywordArg.py
diff --git a/python/file-test-data/extras/testWrongKeywordArg2.err b/python/file-test-data/extras/testWrongKeywordArg2.err
new file mode 100644
index 00000000..c759db46
--- /dev/null
+++ b/python/file-test-data/extras/testWrongKeywordArg2.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testWrongKeywordArg2.py", line 8, in
+ p = Point(foo=3, y=4)
+
+WyppTypeError: unbekanntes Schlüsselwort-Argument
+
+Konstruktor des Records `Point` akzeptiert kein Schlüsselwort-Argument `foo`.
+
+## Datei file-test-data/extras/testWrongKeywordArg2.py
+## Fehlerhafter Aufruf in Zeile 8:
+
+p = [0;31m[1mPoint(foo=3, y=4)[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/extras/testWrongKeywordArg2.out b/python/file-test-data/extras/testWrongKeywordArg2.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testWrongKeywordArg2.py b/python/file-test-data/extras/testWrongKeywordArg2.py
similarity index 100%
rename from python/test-data/testWrongKeywordArg2.py
rename to python/file-test-data/extras/testWrongKeywordArg2.py
diff --git a/python/file-test-data/extras/testWrongKeywordArg3.err b/python/file-test-data/extras/testWrongKeywordArg3.err
new file mode 100644
index 00000000..1053346e
--- /dev/null
+++ b/python/file-test-data/extras/testWrongKeywordArg3.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testWrongKeywordArg3.py", line 8, in
+ p = Point(x=3, y=4, z=4)
+
+WyppTypeError: unbekanntes Schlüsselwort-Argument
+
+Konstruktor des Records `Point` akzeptiert kein Schlüsselwort-Argument `z`.
+
+## Datei file-test-data/extras/testWrongKeywordArg3.py
+## Fehlerhafter Aufruf in Zeile 8:
+
+p = [0;31m[1mPoint(x=3, y=4, z=4)[0;0m
\ No newline at end of file
diff --git a/python/file-test-data/extras/testWrongKeywordArg3.out b/python/file-test-data/extras/testWrongKeywordArg3.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testWrongKeywordArg3.py b/python/file-test-data/extras/testWrongKeywordArg3.py
new file mode 100644
index 00000000..54940e6d
--- /dev/null
+++ b/python/file-test-data/extras/testWrongKeywordArg3.py
@@ -0,0 +1,8 @@
+from wypp import *
+
+@record
+class Point:
+ x: int
+ y: int
+
+p = Point(x=3, y=4, z=4)
diff --git a/python/file-test-data/extras/testWrongKeywordArg4.err b/python/file-test-data/extras/testWrongKeywordArg4.err
new file mode 100644
index 00000000..2163bbd2
--- /dev/null
+++ b/python/file-test-data/extras/testWrongKeywordArg4.err
@@ -0,0 +1,12 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testWrongKeywordArg4.py", line 4, in
+ foo(kw=1, x=4)
+
+WyppTypeError: unbekanntes Schlüsselwort-Argument
+
+Funktion `foo` akzeptiert kein Schlüsselwort-Argument `x`.
+
+## Datei file-test-data/extras/testWrongKeywordArg4.py
+## Fehlerhafter Aufruf in Zeile 4:
+
+foo(kw=1, x=4)
\ No newline at end of file
diff --git a/python/file-test-data/extras/testWrongKeywordArg4.out b/python/file-test-data/extras/testWrongKeywordArg4.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testWrongKeywordArg4.py b/python/file-test-data/extras/testWrongKeywordArg4.py
new file mode 100644
index 00000000..5eae9fd0
--- /dev/null
+++ b/python/file-test-data/extras/testWrongKeywordArg4.py
@@ -0,0 +1,4 @@
+def foo(kw: int) -> int:
+ return kw
+
+foo(kw=1, x=4)
diff --git a/python/file-test-data/extras/testWrongKeywordArg5.err b/python/file-test-data/extras/testWrongKeywordArg5.err
new file mode 100644
index 00000000..0c2390bf
--- /dev/null
+++ b/python/file-test-data/extras/testWrongKeywordArg5.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testWrongKeywordArg5.py", line 4, in
+ foo()
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Funktion `foo` benötigt 1 Argument.
+Gegeben: keine Argumente
+
+## Datei file-test-data/extras/testWrongKeywordArg5.py
+## Aufruf in Zeile 4:
+
+foo()
\ No newline at end of file
diff --git a/python/file-test-data/extras/testWrongKeywordArg5.out b/python/file-test-data/extras/testWrongKeywordArg5.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/file-test-data/extras/testWrongKeywordArg5.py b/python/file-test-data/extras/testWrongKeywordArg5.py
new file mode 100644
index 00000000..05c01a30
--- /dev/null
+++ b/python/file-test-data/extras/testWrongKeywordArg5.py
@@ -0,0 +1,4 @@
+def foo(kw: int) -> int:
+ return kw
+
+foo()
diff --git a/python/file-test-data/extras/testWrongNumOfArguments.err b/python/file-test-data/extras/testWrongNumOfArguments.err
new file mode 100644
index 00000000..6e4ffebd
--- /dev/null
+++ b/python/file-test-data/extras/testWrongNumOfArguments.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testWrongNumOfArguments.py", line 4, in
+ foo(1)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Funktion `foo` benötigt 2 Argumente.
+Gegeben: 1 Argument
+
+## Datei file-test-data/extras/testWrongNumOfArguments.py
+## Aufruf in Zeile 4:
+
+foo(1)
\ No newline at end of file
diff --git a/python/file-test-data/extras/testWrongNumOfArguments.out b/python/file-test-data/extras/testWrongNumOfArguments.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testWrongNumOfArguments.py b/python/file-test-data/extras/testWrongNumOfArguments.py
similarity index 100%
rename from python/test-data/testWrongNumOfArguments.py
rename to python/file-test-data/extras/testWrongNumOfArguments.py
diff --git a/python/file-test-data/extras/testWrongNumOfArguments2.err b/python/file-test-data/extras/testWrongNumOfArguments2.err
new file mode 100644
index 00000000..3c767145
--- /dev/null
+++ b/python/file-test-data/extras/testWrongNumOfArguments2.err
@@ -0,0 +1,13 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/testWrongNumOfArguments2.py", line 6, in
+ c.foo(1)
+
+WyppTypeError: Anzahl der Argument passt nicht
+
+Methode `foo` der Klasse `C` benötigt 2 Argumente.
+Gegeben: 1 Argument
+
+## Datei file-test-data/extras/testWrongNumOfArguments2.py
+## Aufruf in Zeile 6:
+
+c.foo(1)
\ No newline at end of file
diff --git a/python/file-test-data/extras/testWrongNumOfArguments2.out b/python/file-test-data/extras/testWrongNumOfArguments2.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/testWrongNumOfArguments2.py b/python/file-test-data/extras/testWrongNumOfArguments2.py
similarity index 100%
rename from python/test-data/testWrongNumOfArguments2.py
rename to python/file-test-data/extras/testWrongNumOfArguments2.py
diff --git a/python/file-test-data/extras/wrong-caused-by.err b/python/file-test-data/extras/wrong-caused-by.err
new file mode 100644
index 00000000..ca0e627c
--- /dev/null
+++ b/python/file-test-data/extras/wrong-caused-by.err
@@ -0,0 +1,17 @@
+Traceback (most recent call last):
+ File "file-test-data/extras/wrong-caused-by.py", line 31, in
+ mainStreetM.turnIntoStreet(redCarM)
+
+WyppTypeError: CarM(licensePlate='OG PY 123', color='rot')
+
+Der Aufruf der Methode `turnIntoStreet` aus Klasse `StreetM` erwartet Wert vom Typ `Car` als erstes Argument.
+Aber der übergebene Wert hat Typ `CarM`.
+
+## Datei file-test-data/extras/wrong-caused-by.py
+## Fehlerhafter Aufruf in Zeile 31:
+
+mainStreetM.turnIntoStreet(redCarM)
+
+## Typ deklariert in Zeile 10:
+
+ def turnIntoStreet(self: StreetM, [0;31m[1mcar: Car[0;0m) -> None:
\ No newline at end of file
diff --git a/python/file-test-data/extras/wrong-caused-by.out b/python/file-test-data/extras/wrong-caused-by.out
new file mode 100644
index 00000000..e69de29b
diff --git a/python/test-data/wrong-caused-by.py b/python/file-test-data/extras/wrong-caused-by.py
similarity index 100%
rename from python/test-data/wrong-caused-by.py
rename to python/file-test-data/extras/wrong-caused-by.py
diff --git a/python/test-data/fileWithBothImports.py b/python/file-test-data/imports/fileWithBothImports.py
similarity index 100%
rename from python/test-data/fileWithBothImports.py
rename to python/file-test-data/imports/fileWithBothImports.py
diff --git a/python/test-data/fileWithImport.py b/python/file-test-data/imports/fileWithImport.py
similarity index 100%
rename from python/test-data/fileWithImport.py
rename to python/file-test-data/imports/fileWithImport.py
diff --git a/python/test-data/fileWithoutImport.py b/python/file-test-data/imports/fileWithoutImport.py
similarity index 100%
rename from python/test-data/fileWithoutImport.py
rename to python/file-test-data/imports/fileWithoutImport.py
diff --git a/python/test-data/localMod.py b/python/file-test-data/imports/localMod.py
similarity index 100%
rename from python/test-data/localMod.py
rename to python/file-test-data/imports/localMod.py
diff --git a/python/test-data/modules/B/mod.py b/python/file-test-data/modules/B/mod.py
similarity index 100%
rename from python/test-data/modules/B/mod.py
rename to python/file-test-data/modules/B/mod.py
diff --git a/python/fileTests b/python/fileTests
deleted file mode 100755
index 807f8523..00000000
--- a/python/fileTests
+++ /dev/null
@@ -1,310 +0,0 @@
-#!/usr/bin/env bash
-
-# FIXME (sw, 2021-09-16): we should migrate this mess to tests/integrationTests.py!
-
-set -e
-
-cd $(dirname $0)
-
-d=$(pwd)
-t=$(mktemp)
-
-if [ "$1" == "--help" ]; then
- echo "USAGE: $0 [--start-at] [FILE]"
- exit 1
-fi
-
-startAt=false
-if [ "$1" == "--start-at" ]; then
- startAt=true
- shift
-fi
-FILE="$1"
-
-started=false
-
-function skip()
-{
- # return 1 if test should be run
- # return 0 if test should be skipped
- if [ ! -z "$FILE" ]; then
- if [ "$startAt" == true -a "$started" == true ]; then
- return 1
- fi
- if [ "$FILE" == "$1" ]; then
- started=true
- return 1
- fi
- return 0
- else
- return 1
- fi
-}
-
-echo "Running file tests, siteDir=$siteDir ..."
-echo "Writing logs to $t"
-
-#PY="coverage run --append"
-PY=python3
-
-function check()
-{
- if skip "$1"; then
- return
- fi
- echo "Checking with $1"
- pushd /tmp > /dev/null
- dir=$(mktemp -d)
- echo "Installing into $dir ..."
- echo "Run 1"
- PYTHONPATH=$dir WYPP_INSTALL_DIR=$dir $PY $d/src/runYourProgram.py --install-mode install --quiet --check $d/"$1" >> "$t"
- rm -rf "$dir"
- mkdir "$dir"
- echo "Run 2"
- PYTHONPATH=$dir WYPP_INSTALL_DIR=$dir $PY $d/src/runYourProgram.py --install-mode install --quiet --check $d/"$1" >> "$t"
- echo "Run 3"
- PYTHONPATH=$dir WYPP_INSTALL_DIR=$dir $PY $d/src/runYourProgram.py --check --install-mode assertInstall --quiet $d/"$1" >> "$t"
- rm -f "$dir/untypy/__init__.py"
- echo "Run 4"
- PYTHONPATH=$dir WYPP_INSTALL_DIR=$dir $PY $d/src/runYourProgram.py --install-mode install --quiet --check $d/"$1" >> "$t"
- echo "Run 5"
- PYTHONPATH=$dir WYPP_INSTALL_DIR=$dir $PY $d/src/runYourProgram.py --check --install-mode assertInstall --quiet $d/"$1" >> "$t"
- popd > /dev/null
- rm -rf "$dir"
-}
-check test-data/fileWithImport.py
-check test-data/fileWithoutImport.py
-check test-data/fileWithBothImports.py
-check test-data/fileWithRecursiveTypes.py
-
-version=$(python3 -V | sed 's/Python //g' | sed 's/\.[^.]*$//g')
-fullVersion=$(python3 -V | sed 's/Python //g')
-
-function fix_output()
-{
- sed 's/at 0x[0-9a-f][0-9a-f]*>/at 0x00>/g' | \
- sed 's| File "/[^"]*"| File ""|g'
-}
-
-# First argument: whether to do type checking or not
-# Second argument: expected exit code. If given as X:Y, then X is the exit code with active
-# type checking, and Y is the exit code without type checking.
-# Third argument: input file
-# The remaining arguments are passed to src/runYourProgram.py
-function checkWithOutputAux()
-{
- local tycheck="$1"
- local expectedEcode=$2
- local file="$3"
- if skip "$file"; then
- return
- fi
- echo "Checking $file"
- shift 3
- tycheckOpt=""
- suffixes="${fullVersion} ${version}"
- if [ "$tycheck" == "no" ]; then
- tycheckOpt="--no-typechecking"
- suffixes="${fullVersion}-notypes ${fullVersion} ${version}-notypes ${version} notypes"
- fi
- if echo "$expectedEcode" | grep ':' > /dev/null; then
- if [ "$tycheck" == "no" ]; then
- expectedEcode=$(echo "$expectedEcode" | sed 's/^.*://g')
- else
- expectedEcode=$(echo "$expectedEcode" | sed 's/:.*$//g')
- fi
- fi
- local expectedOut="${file%.py}.out"
- if [ ! -f "$expectedOut" ]; then
- echo "File $expectedOut does not exist"
- exit 1
- fi
- local expectedErr="${file%.py}.err"
- if [ ! -f "$expectedErr" ]; then
- echo "File $expectedErr does not exist"
- exit 1
- fi
- for suf in $suffixes; do
- if [ -e "${expectedOut}-${suf}" ]; then
- expectedOut="${expectedOut}-${suf}"
- fi
- if [ -e "${expectedErr}-${suf}" ]; then
- expectedErr="${expectedErr}-${suf}"
- fi
- done
- local t=$(mktemp)
- local out=$t.out
- local err=$t.err
- local errtemp=$t.errtemp
- local outtemp=$t.outtemp
- set +e
- echo "Checking $file (typecheck: $tycheck)"
- local p=$d/site-lib
- if [ ! -z $PP ]; then
- p=$p:$PP
- fi
- PYTHONPATH=$p $PY $d/src/runYourProgram.py --quiet $tycheckOpt "$file" "$@" 2>> "$errtemp" > "$outtemp"
- ecode=$?
- set -e
- cat "$errtemp" | fix_output > "$err"
- cat "$outtemp" | fix_output > "$out"
- if [ $ecode != $expectedEcode ]; then
- echo "Expected exit code $expectedEcode got $ecode for test $file"
- echo "Stderr:"
- cat "$err"
- echo "Stdout:"
- cat "$out"
- exit 1
- fi
- if ! diff -u $expectedOut $out; then
- echo "Wrong output on stdout for $file ($expectedOut contains the expected output)"
- echo "Full output: $out"
- exit 1
- fi
- if ! diff -u $expectedErr $err; then
- echo "Wrong output on stderr for $file ($expectedErr contains the expected output)"
- echo "Full output: $err"
- exit 1
- fi
- rm -f "$out"
- rm -f "$err"
-}
-
-function checkWithOutput()
-{
- checkWithOutputAux yes "$@"
- checkWithOutputAux no "$@"
-}
-
-checkWithOutput 1 test-data/testTraceback.py
-checkWithOutput 1 test-data/testTraceback2.py
-checkWithOutput 1 test-data/testTraceback3.py
-checkWithOutput 0 test-data/testArgs.py ARG_1 ARG_2
-checkWithOutput 0 test-data/printModuleName.py
-checkWithOutput 0 test-data/printModuleNameImport.py
-checkWithOutput 1 test-data/testTypes1.py
-checkWithOutput 1 test-data/testIndexError.py
-checkWithOutput 1 test-data/testWrapperError.py
-checkWithOutput 0 test-data/testCheckFail.py
-checkWithOutput 1:0 test-data/testTypes2.py
-checkWithOutputAux yes 1 test-data/testABCMeta.py
-checkWithOutputAux yes 0 test-data/testClassHierarchy.py
-checkWithOutputAux yes 1 test-data/testTypesCollections1.py
-checkWithOutputAux yes 1 test-data/testTypesCollections2.py
-checkWithOutputAux yes 1 test-data/testTypesCollections3.py # see #5
-checkWithOutputAux yes 1 test-data/testTypesCollections4.py
-checkWithOutputAux yes 1 test-data/testTypesProtos1.py
-checkWithOutputAux yes 1 test-data/testTypesProtos2.py
-checkWithOutputAux yes 1 test-data/testTypesProtos3.py
-checkWithOutputAux yes 1 test-data/testTypesProtos4.py
-checkWithOutputAux yes 1 test-data/testTypesSubclassing1.py
-checkWithOutputAux yes 1 test-data/testTypesHigherOrderFuns.py
-checkWithOutputAux yes 0 test-data/testTypesHigherOrderFuns2.py # see #6
-checkWithOutputAux yes 1 test-data/testTypesHigherOrderFuns3.py
-checkWithOutputAux yes 0 test-data/testTypesHigherOrderFuns4.py
-checkWithOutputAux yes 0 test-data/testTypesHigherOrderFuns5.py
-checkWithOutputAux yes 1 test-data/testTypesRecordInheritance.py
-checkWithOutputAux yes 1 test-data/testRecordSetTypes.py
-checkWithOutputAux yes 1 test-data/testRecordSetTypeForwardRef.py
-checkWithOutputAux yes 0 test-data/testForwardRef.py
-checkWithOutputAux yes 0 test-data/testForwardRef1.py
-checkWithOutputAux yes 1 test-data/testForwardRef2.py
-checkWithOutputAux yes 0 test-data/testForwardRef3.py
-checkWithOutputAux yes 1 test-data/testForwardRef4.py
-checkWithOutputAux yes 1 test-data/testForwardRef5.py
-checkWithOutputAux yes 0 test-data/testForwardRef6.py
-checkWithOutputAux yes 1 test-data/testHintParentheses1.py
-checkWithOutputAux yes 1 test-data/testHintParentheses2.py
-checkWithOutputAux yes 1 test-data/testHintParentheses3.py
-checkWithOutputAux yes 1 test-data/testTypesReturn.py
-checkWithOutputAux yes 1 test-data/testMissingReturn.py
-checkWithOutputAux yes 1 test-data/testTypesSequence1.py
-checkWithOutputAux yes 1 test-data/testTypesSequence2.py
-checkWithOutputAux yes 1 test-data/testTypesTuple1.py
-checkWithOutputAux yes 1 test-data/wrong-caused-by.py
-checkWithOutputAux yes 1 test-data/declared-at-missing.py
-checkWithOutputAux yes 1 test-data/testTypesSet1.py
-checkWithOutputAux yes 1 test-data/testTypesSet2.py
-checkWithOutputAux yes 1 test-data/testTypesSet3.py
-checkWithOutputAux yes 1 test-data/testTypesSet4.py
-checkWithOutputAux yes 1 test-data/testTypesDict1.py
-checkWithOutputAux yes 1 test-data/testTypesDict2.py
-checkWithOutputAux yes 1 test-data/testTypesDict3.py
-checkWithOutputAux yes 1 test-data/testTypesDict4.py
-checkWithOutputAux yes 1 test-data/testIterableImplicitAny.py
-checkWithOutputAux yes 0 test-data/testDoubleWrappingDicts.py
-checkWithOutputAux yes 0 test-data/testClassRecursion.py
-checkWithOutputAux yes 1 test-data/testWrongNumOfArguments.py
-checkWithOutputAux yes 1 test-data/testWrongNumOfArguments2.py
-checkWithOutputAux yes 0 test-data/testLiteralInstanceOf.py
-checkWithOutputAux yes 1 test-data/testWrongKeywordArg.py
-checkWithOutputAux yes 1 test-data/testWrongKeywordArg2.py
-checkWithOutputAux yes 0 test-data/testComplex.py
-checkWithOutputAux yes 0 test-data/testUnionLiteral.py
-checkWithOutputAux yes 0 test-data/testCheck.py
-checkWithOutputAux yes 0 test-data/testNameErrorBug.py
-checkWithOutputAux yes 0 test-data/testOriginalTypeNames.py
-checkWithOutputAux yes 0 test-data/testDeepEqBug.py
-checkWithOutputAux yes 1 test-data/testLockFactory.py
-checkWithOutputAux yes 1 test-data/testLockFactory2.py
-checkWithOutputAux yes 1 test-data/testGetSource.py
-checkWithOutputAux yes 0 test-data/testDict.py #see #87
-checkWithOutputAux yes 0 test-data/testFunEq.py #see #78
-checkWithOutputAux yes 0 test-data/testBugSliceIndices.py #see #92
-checkWithOutputAux yes 0 test-data/testABC.py
-checkWithOutputAux yes 0 test-data/testTypesWrapperEq.py
-checkWithOutputAux yes 0 test-data/testTypesProtos5.py
-checkWithOutputAux yes 1 test-data/testTypesProtos6.py
-checkWithOutputAux yes 1 test-data/testTypesProtos7.py
-checkWithOutputAux yes 1 test-data/testTypesProtos8.py
-checkWithOutputAux yes 1 test-data/testTypesProtos9.py
-checkWithOutputAux yes 0 test-data/testIterable1.py
-checkWithOutputAux yes 0 test-data/testIterable2.py
-checkWithOutputAux yes 0 test-data/testIterable3.py
-checkWithOutputAux yes 0 test-data/testIterable4.py
-checkWithOutputAux yes 0 test-data/testIterable5.py
-checkWithOutputAux yes 0 test-data/testIterable6.py
-checkWithOutputAux yes 1 test-data/testIterable7.py
-checkWithOutputAux yes 0 test-data/testIterator.py
-checkWithOutputAux yes 0 test-data/testIterator2.py
-checkWithOutputAux yes 0 test-data/testIterator3.py
-checkWithOutputAux yes 0 test-data/testIterator4.py
-checkWithOutputAux yes 0 test-data/testConcat.py
-checkWithOutputAux yes 0 test-data/testCopy.py
-checkWithOutputAux yes 0 test-data/testHof.py
-checkWithOutputAux yes 0 test-data/testIndexSeq.py
-checkWithOutputAux yes 0 test-data/testWrap.py
-checkWithOutputAux yes 0 test-data/testWrap2.py
-checkWithOutputAux yes 1 test-data/testTodo.py
-checkWithOutputAux yes 1 test-data/testImpossible.py
-checkWithOutputAux yes 1 test-data/testInvalidLiteral.py
-checkWithOutputAux yes 0 test-data/admin.py
-PP=test-data/modules/B checkWithOutputAux yes 1 test-data/modules/A/main.py \
- --extra-dir test-data/modules/B
-checkWithOutputAux yes 0 test-data/testUnion.py
-checkWithOutputAux yes 0 test-data/testUnion2.py
-checkWithOutputAux yes 0 test-data/testUnion3.py
-checkWithOutputAux yes 1 test-data/testLiteral1.py
-checkWithOutputAux yes 0 test-data/testForwardTypeInRecord.py
-checkWithOutputAux yes 0 test-data/testForwardTypeInRecord2.py
-checkWithOutputAux yes 0 test-data/testUnionOfUnion.py
-checkWithOutputAux yes 1 test-data/testRecordTypes.py
-checkWithOutputAux yes 0 test-data/testDisappearingObject_01.py
-checkWithOutputAux yes 0 test-data/testDisappearingObject_02.py
-
-function is_min_version()
-{
- min="$1"
- v="$2"
- smaller=$(echo -e "$min\n$v" | sort -V | head -1)
- if [ "$smaller" == "$min" ]; then
- return 0;
- else
- return 1;
- fi
-}
-version=$(python3 --version | sed 's/Python //g')
-
-if is_min_version "3.12" "$version"; then
- checkWithOutput 0 test-data/testTypeKeyword.py
-fi
diff --git a/python/fileTests.py b/python/fileTests.py
new file mode 100644
index 00000000..32f7b345
--- /dev/null
+++ b/python/fileTests.py
@@ -0,0 +1,17 @@
+from pathlib import Path
+from fileTestsLib import *
+
+directories = [Path("file-test-data/basics"),
+ Path("file-test-data/extras")]
+
+#directories = [Path("file-test-data/basics")]
+#directories = [Path("file-test-data/extras")]
+
+for d in directories:
+ for file in d.iterdir():
+ if file.is_file():
+ name = file.as_posix()
+ if name.endswith('.py'):
+ check(name)
+
+globalCtx.results.finish()
diff --git a/python/fileTestsLib.py b/python/fileTestsLib.py
new file mode 100644
index 00000000..a889445e
--- /dev/null
+++ b/python/fileTestsLib.py
@@ -0,0 +1,448 @@
+from __future__ import annotations
+from dataclasses import dataclass
+import os
+from typing import *
+import sys
+import subprocess
+import tempfile
+import argparse
+import re
+import shutil
+import json
+import re
+
+GLOBAL_CHECK_OUTPUTS = True
+
+GLOBAL_RECORD_ALL = False # Should be False, write actual output to all expected output files
+
+@dataclass(frozen=True)
+class TestOpts:
+ cmd: str
+ baseDir: str
+ startAt: Optional[str]
+ only: Optional[str]
+ keepGoing: bool
+ record: Optional[str]
+ lang: Optional[str]
+
+def parseArgs() -> TestOpts:
+ parser = argparse.ArgumentParser(
+ description="Run tests with specified options.",
+ usage="USAGE: ./fileTests OPTIONS"
+ )
+
+ # Define the command-line arguments
+ parser.add_argument("--start-at", type=str, help="Start with test in FILE")
+ parser.add_argument("--only", type=str, help="Run only the test in FILE")
+ parser.add_argument("--continue", action="store_true",
+ dest="keepGoing", default=False,
+ help="Continue with tests after first error")
+ parser.add_argument('--record', dest='record',
+ type=str, help='Record the expected output for the given file.')
+ parser.add_argument('--lang', dest='lang',
+ type=str, help='Display error messages in this language (either en or de, only for recording).')
+
+ # Parse the arguments
+ args = parser.parse_args()
+
+ scriptDir = os.path.dirname(__file__)
+ return TestOpts(
+ cmd=f'{scriptDir}/code/wypp/runYourProgram.py',
+ baseDir=scriptDir,
+ startAt=args.start_at,
+ only=args.only,
+ keepGoing=args.keepGoing,
+ record=args.record,
+ lang=args.lang
+ )
+
+defaultLang = 'de'
+
+TestStatus = Literal['passed', 'failed', 'skipped']
+
+@dataclass
+class TestResults:
+ passed: list[str]
+ failed: list[str]
+ skipped: list[str]
+ def storeTestResult(self, testFail: str, result: TestStatus):
+ if result == 'passed':
+ self.passed.append(testFail)
+ elif result == 'failed':
+ self.failed.append(testFail)
+ elif result == 'skipped':
+ self.skipped.append(testFail)
+ def finish(self):
+ total = len(self.passed) + len(self.skipped) + len(self.failed)
+ print()
+ print(80 * '-')
+ print("Tests finished")
+ print()
+ print(f"Total: {total}")
+ print(f"Passed: {len(self.passed)}")
+ print(f"Skipped: {len(self.skipped)}")
+ print(f"Failed: {len(self.failed)}")
+ if self.failed:
+ print()
+ print("Failed tests:")
+ for test in self.failed:
+ print(f" {test}")
+ sys.exit(1)
+
+@dataclass(frozen=True)
+class TestContext:
+ opts: TestOpts
+ results: TestResults
+
+globalCtx = TestContext(
+ opts=parseArgs(),
+ results=TestResults(passed=[], failed=[], skipped=[])
+)
+
+def readFile(filePath: str) -> str:
+ with open(filePath, "r") as f:
+ return f.read()
+
+def readFileIfExists(filePath: str) -> str:
+ if not os.path.exists(filePath):
+ return ''
+ else:
+ return readFile(filePath)
+
+def getVersionedFile(base: str, typcheck: bool, lang: Optional[str]) -> str:
+ if lang is None:
+ lang = defaultLang
+ if lang != defaultLang:
+ base = f'{base}_{lang}'
+ v = sys.version_info
+ suffixes = [f'{v.major}.{v.minor}', f'{v.major}.{v.minor}.{v.micro}']
+ if not typcheck:
+ l = []
+ for x in suffixes:
+ l.append(f'{x}-notypes')
+ l.append(x)
+ l.append('notypes')
+ suffixes = l
+ for suffix in suffixes:
+ filePath = f"{base}-{suffix}"
+ if os.path.exists(filePath):
+ return filePath
+ return base
+
+_started = False
+def shouldSkip(testFile: str, ctx: TestContext, minVersion: Optional[tuple[int, int]]) -> bool:
+ """
+ Determines if a test should be skipped based on the context and minimum version.
+ """
+ global _started
+ opts = ctx.opts
+ if opts.startAt:
+ if _started:
+ return False
+ elif testFile == opts.startAt:
+ _started = True
+ return False
+ else:
+ return True
+ if opts.only and testFile != opts.only:
+ return True
+ if minVersion:
+ v = sys.version_info
+ if (v.major, v.minor) < minVersion:
+ return True
+ return False
+
+def checkOutputOk(testFile: str, outputType: str, expectedFile: str, actualFile: str) -> bool:
+ expected = readFileIfExists(expectedFile).strip()
+ actual = readFileIfExists(actualFile).strip()
+ if expected != actual:
+ print(f"Test {testFile} {outputType} output mismatch:")
+ subprocess.run(['diff', '-u', expectedFile, actualFile])
+ if GLOBAL_RECORD_ALL:
+ with open(expectedFile, 'w') as f:
+ f.write(actual)
+ return True
+ else:
+ return False
+ else:
+ return True
+
+def checkInstall(testFile: str, ctx: TestContext=globalCtx):
+ if shouldSkip(testFile, ctx, None):
+ ctx.results.storeTestResult(testFile, 'skipped')
+ return
+ with tempfile.TemporaryDirectory() as d:
+ def run(args: list[str]):
+ cmd = [sys.executable, ctx.opts.cmd, '--quiet']
+ cmd.extend(args)
+ cmd.append(os.path.join(ctx.opts.baseDir, testFile))
+ env = os.environ.copy()
+ env['PYTHONPATH'] = d
+ env['WYPP_INSTALL_DIR'] = d
+ subprocess.run(
+ cmd,
+ check=True,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ env=env
+ )
+ sys.stdout.write(f'Install test {testFile} ...')
+ run(['--install-mode', 'install', '--check'])
+ subprocess.run(f'rm -rf {d} && mkdir {d}', shell=True, check=True)
+ run(['--install-mode', 'install', '--check'])
+ run(['--check', '--install-mode', 'assertInstall'])
+ subprocess.run(f'rm -f {d}/untypy/__init__.py', shell=True, check=True)
+ run(['--install-mode', 'install', '--check'])
+ run(['--check', '--install-mode', 'assertInstall'])
+ sys.stdout.write(' OK\n')
+
+def fixOutput(filePath: str):
+ """
+ Fixes the output file by removing specific lines and patterns.
+ """
+ content = readFile(filePath)
+ content = re.sub(r'at 0x[0-9a-f][0-9a-f]*>', 'at 0x00>', content, flags=re.MULTILINE) # Remove memory addresses
+ content = re.sub(r' File "/[^"]*/([^"/]+)", line \d+', ' File "\\1", line ?', content, flags=re.MULTILINE) # Remove absolute file paths from traceback
+ with open(filePath, 'w') as f:
+ f.write(content)
+
+def readAnswer(question: str, allowed: list[str]) -> str:
+ while True:
+ answer = input(question)
+ if answer in allowed:
+ return answer
+ print(f'Answer must be one of {allowed}. Try again!')
+
+def _runTest(testFile: str,
+ exitCode: int,
+ typecheck: bool,
+ args: list[str],
+ actualStdoutFile: str,
+ actualStderrFile: str,
+ pythonPath: list[str],
+ what: str,
+ lang: str,
+ ctx: TestContext) -> Literal['failed'] | None:
+ # Prepare the command
+ cmd = [sys.executable, ctx.opts.cmd, '--quiet']
+ if not typecheck:
+ cmd.append('--no-typechecking')
+ cmd.append(testFile)
+ cmd.append('--lang')
+ cmd.append(lang)
+ cmd.extend(args)
+ env = os.environ.copy()
+ env['PYTHONPATH'] = os.pathsep.join([os.path.join(ctx.opts.baseDir, 'code')] + pythonPath)
+ env['WYPP_UNDER_TEST'] = 'True'
+ with open(actualStdoutFile, 'w') as stdoutFile, \
+ open(actualStderrFile, 'w') as stderrFile:
+ # Run the command
+ result = subprocess.run(
+ cmd,
+ stdout=stdoutFile,
+ stderr=stderrFile,
+ text=True,
+ env=env
+ )
+ # Check exit code
+ if result.returncode != exitCode:
+ print(f"Test {testFile}{what} failed: Expected exit code {exitCode}, got {result.returncode}")
+ return 'failed'
+
+def _checkForLang(testFile: str,
+ exitCode: int,
+ typecheck: bool,
+ args: list[str],
+ pythonPath: list[str],
+ checkOutputs: bool,
+ lang: str,
+ ctx: TestContext,
+ what: str) -> TestStatus:
+ # Prepare expected output files
+ baseFile = os.path.splitext(testFile)[0]
+ expectedStdoutFile = getVersionedFile(f"{baseFile}.out", typcheck=typecheck, lang=lang)
+ expectedStderrFile = getVersionedFile(f"{baseFile}.err", typcheck=typecheck, lang=lang)
+
+ with tempfile.TemporaryDirectory() as d:
+ actualStdoutFile = os.path.join(d, 'stdout.txt')
+ actualStderrFile = os.path.join(d, 'stderr.txt')
+ r = _runTest(testFile, exitCode, typecheck, args, actualStdoutFile, actualStderrFile,
+ pythonPath, what, lang, ctx)
+ if r is not None:
+ return r
+
+ fixOutput(actualStdoutFile)
+ fixOutput(actualStderrFile)
+
+ # Checkout outputs
+ if checkOutputs and GLOBAL_CHECK_OUTPUTS:
+ if not checkOutputOk(testFile + what, 'stdout', expectedStdoutFile, actualStdoutFile):
+ return 'failed'
+ if not checkOutputOk(testFile + what, 'stderr', expectedStderrFile, actualStderrFile):
+ return 'failed'
+
+ # If all checks passed
+ whatLang = ''
+ if lang != defaultLang:
+ whatLang = f' ({lang})'
+ print(f"{testFile}{what}{whatLang} OK")
+ return 'passed'
+
+def _check(testFile: str,
+ exitCode: int,
+ typecheck: bool,
+ args: list[str],
+ pythonPath: list[str],
+ minVersion: Optional[tuple[int, int]],
+ checkOutputs: bool,
+ ctx: TestContext,
+ what: str) -> TestStatus:
+ status1 = _checkForLang(testFile, exitCode, typecheck, args, pythonPath, checkOutputs, defaultLang, ctx, what)
+ baseFile = os.path.splitext(testFile)[0]
+ enOut = getVersionedFile(f"{baseFile}.out", typcheck=typecheck, lang='en')
+ enErr = getVersionedFile(f"{baseFile}.err", typcheck=typecheck, lang='en')
+ if os.path.exists(enOut) or os.path.exists(enErr):
+ status2 = _checkForLang(testFile, exitCode, typecheck, args, pythonPath, checkOutputs, 'en', ctx, what)
+ else:
+ status2 = 'passed'
+ if status1 != 'passed':
+ return status1
+ elif status2 != 'passed':
+ return status2
+ else:
+ return 'passed'
+
+def guessExitCode(testFile: str) -> int:
+ return 0 if testFile.endswith('_ok.py') else 1
+
+_CONFIG_RE = re.compile(r'^# WYPP_TEST_CONFIG:\s*(\{.*\})\s*$')
+
+@dataclass
+class WyppTestConfig:
+ typecheck: Literal[True, False, "both"]
+ args: list[str]
+ pythonPath: Optional[str]
+ @staticmethod
+ def default() -> WyppTestConfig:
+ return WyppTestConfig(typecheck=True, args=[], pythonPath=None)
+
+def readWyppTestConfig(path: str, *, max_lines: int = 5) -> WyppTestConfig:
+ """
+ Read a line like `# WYPP_TEST_CONFIG: {"typecheck": false}` from the first
+ `max_lines` lines of the file at `path` and return it as a dict.
+ Returns {} if not present.
+ """
+ validKeys = ['typecheck', 'args', 'pythonPath']
+ with open(path, "r", encoding="utf-8") as f:
+ for lineno in range(1, max_lines + 1):
+ line = f.readline()
+ if not line:
+ break
+ m = _CONFIG_RE.match(line)
+ if m:
+ payload = m.group(1)
+ j = json.loads(payload)
+ for k in j:
+ if k not in validKeys:
+ raise ValueError(f'Unknown key {k} in config for file {path}')
+ typecheck = j.get('typecheck', True)
+ args = j.get('args', [])
+ pythonPath = j.get('pythonPath')
+ return WyppTestConfig(typecheck=typecheck, args=args, pythonPath=pythonPath)
+ return WyppTestConfig.default()
+
+def checkNoConfig(testFile: str,
+ exitCode: int = 1,
+ typecheck: bool = True,
+ args: list[str] = [],
+ pythonPath: list[str] = [],
+ minVersion: Optional[tuple[int, int]] = None,
+ checkOutputs: bool = True,
+ ctx: TestContext = globalCtx,
+ what: str = ''):
+ if guessExitCode(testFile) == 0:
+ exitCode = 0
+ status = _check(testFile, exitCode, typecheck, args, pythonPath, minVersion, checkOutputs, ctx, what)
+ ctx.results.storeTestResult(testFile, status)
+ if status == 'failed':
+ if not ctx.opts.keepGoing:
+ ctx.results.finish()
+
+def check(testFile: str,
+ exitCode: int = 1,
+ minVersion: Optional[tuple[int, int]] = None,
+ checkOutputs: bool = True,
+ ctx: TestContext = globalCtx,):
+ if shouldSkip(testFile, ctx, minVersion):
+ return 'skipped'
+ cfg = readWyppTestConfig(testFile)
+ args = cfg.args
+ pythonPath = []
+ if cfg.pythonPath:
+ pythonPath = cfg.pythonPath.split(':')
+ if cfg.typecheck == 'both':
+ checkNoConfig(testFile, exitCode, typecheck=True, args=args,
+ pythonPath=pythonPath, minVersion=minVersion, checkOutputs=checkOutputs,
+ ctx=ctx, what=' (typecheck)')
+ checkNoConfig(testFile, exitCode, typecheck=False, args=args,
+ pythonPath=pythonPath, minVersion=minVersion, checkOutputs=checkOutputs,
+ ctx=ctx, what=' (no typecheck)')
+ else:
+ what = ' (no typecheck)' if not cfg.typecheck else ''
+ checkNoConfig(testFile, exitCode, typecheck=cfg.typecheck, args=args,
+ pythonPath=pythonPath, minVersion=minVersion, checkOutputs=checkOutputs,
+ ctx=ctx, what=what)
+
+def checkBasic(testFile: str, ctx: TestContext = globalCtx):
+ check(testFile, checkOutputs=False, ctx=ctx)
+
+def record(testFile: str):
+ """
+ Runs filePath and stores the output in the expected files.
+ """
+ baseFile = os.path.splitext(testFile)[0]
+ exitCode = guessExitCode(testFile)
+ cfg = readWyppTestConfig(testFile)
+ typecheck = cfg.typecheck
+ if typecheck == 'both':
+ typecheck = True
+ args = cfg.args
+ pythonPath = []
+ if cfg.pythonPath:
+ pythonPath = cfg.pythonPath.split(':')
+ what = ''
+ ctx = globalCtx
+ def display(filename: str, where: str):
+ x = readFile(filename)
+ if x:
+ print(f'--- Output on {where} ---')
+ print(x)
+ print('------------------------')
+ else:
+ print(f'No output on {where}')
+ with tempfile.TemporaryDirectory() as d:
+ actualStdoutFile = os.path.join(d, 'stdout.txt')
+ actualStderrFile = os.path.join(d, 'stderr.txt')
+ result = _runTest(testFile, exitCode, typecheck, args, actualStdoutFile, actualStderrFile,
+ pythonPath, what, ctx.opts.lang or defaultLang, ctx)
+ if result is not None:
+ print(f'Test did not produce the expected exit code. Aborting')
+ sys.exit(1)
+ display(actualStdoutFile, 'stdout')
+ display(actualStderrFile, 'stderr')
+ answer = readAnswer('Store the output as the new expected output? (y/n) ', ['y', 'n'])
+ if answer:
+ fixOutput(actualStdoutFile)
+ fixOutput(actualStderrFile)
+ expectedStdoutFile = getVersionedFile(f"{baseFile}.out", typcheck=typecheck, lang=ctx.opts.lang)
+ expectedStderrFile = getVersionedFile(f"{baseFile}.err", typcheck=typecheck, lang=ctx.opts.lang)
+ shutil.copy(actualStdoutFile, expectedStdoutFile)
+ shutil.copy(actualStderrFile, expectedStderrFile)
+ print(f'Stored expected output in {expectedStdoutFile} and {expectedStderrFile}')
+ else:
+ print('Aborting')
+
+if __name__ == '__main__':
+ if globalCtx.opts.record is not None:
+ record(globalCtx.opts.record)
+ sys.exit(0)
diff --git a/python/test-data/repl-test-checks.py b/python/integration-test-data/repl-test-checks.py
similarity index 100%
rename from python/test-data/repl-test-checks.py
rename to python/integration-test-data/repl-test-checks.py
diff --git a/python/test-data/repl-test-lib.py b/python/integration-test-data/repl-test-lib.py
similarity index 100%
rename from python/test-data/repl-test-lib.py
rename to python/integration-test-data/repl-test-lib.py
diff --git a/python/test-data/scope-bug-peter.py b/python/integration-test-data/scope-bug-peter.py
similarity index 100%
rename from python/test-data/scope-bug-peter.py
rename to python/integration-test-data/scope-bug-peter.py
diff --git a/python/test-data/student-submission-bad.py b/python/integration-test-data/student-submission-bad.py
similarity index 100%
rename from python/test-data/student-submission-bad.py
rename to python/integration-test-data/student-submission-bad.py
diff --git a/python/test-data/student-submission-tests-tyerror.py b/python/integration-test-data/student-submission-tests-tyerror.py
similarity index 100%
rename from python/test-data/student-submission-tests-tyerror.py
rename to python/integration-test-data/student-submission-tests-tyerror.py
diff --git a/python/test-data/student-submission-tests.py b/python/integration-test-data/student-submission-tests.py
similarity index 100%
rename from python/test-data/student-submission-tests.py
rename to python/integration-test-data/student-submission-tests.py
diff --git a/python/test-data/student-submission-tyerror.py b/python/integration-test-data/student-submission-tyerror.py
similarity index 100%
rename from python/test-data/student-submission-tyerror.py
rename to python/integration-test-data/student-submission-tyerror.py
diff --git a/python/test-data/student-submission.py b/python/integration-test-data/student-submission.py
similarity index 100%
rename from python/test-data/student-submission.py
rename to python/integration-test-data/student-submission.py
diff --git a/python/integration-test-data/testTypes2.py b/python/integration-test-data/testTypes2.py
new file mode 100644
index 00000000..593392d3
--- /dev/null
+++ b/python/integration-test-data/testTypes2.py
@@ -0,0 +1,4 @@
+def inc(x: int) -> int:
+ return x
+
+inc("1")
diff --git a/python/test-data/testTypes3.py b/python/integration-test-data/testTypes3.py
similarity index 100%
rename from python/test-data/testTypes3.py
rename to python/integration-test-data/testTypes3.py
diff --git a/python/test-data/testTypesInteractive.py b/python/integration-test-data/testTypesInteractive.py
similarity index 100%
rename from python/test-data/testTypesInteractive.py
rename to python/integration-test-data/testTypesInteractive.py
diff --git a/python/test-data/testTypesSet1.py b/python/integration-test-data/testTypesSet1_ok.py
similarity index 100%
rename from python/test-data/testTypesSet1.py
rename to python/integration-test-data/testTypesSet1_ok.py
diff --git a/python/integration-tests/shell.py b/python/integration-tests/shell.py
index 321cbb0c..fac20c29 100644
--- a/python/integration-tests/shell.py
+++ b/python/integration-tests/shell.py
@@ -407,6 +407,18 @@ def f():
debug('Not running exit action')
atexit.register(f)
+def safeRm(f):
+ try:
+ rm(f)
+ except Exception:
+ pass
+
+def safeRmdir(f, b):
+ try:
+ rmdir(f, b)
+ except Exception:
+ pass
+
# deleteAtExit is one of the following:
# - True: the file is deleted unconditionally
# - 'ifSuccess': the file is deleted if the program exists with code 0
@@ -414,13 +426,13 @@ def f():
def mkTempFile(suffix='', prefix='', dir=None, deleteAtExit=True):
f = tempfile.mktemp(suffix, prefix, dir)
if deleteAtExit:
- registerAtExit(lambda: rm(f), deleteAtExit)
+ registerAtExit(lambda: safeRm(f), deleteAtExit)
return f
def mkTempDir(suffix='', prefix='tmp', dir=None, deleteAtExit=True):
d = tempfile.mkdtemp(suffix, prefix, dir)
if deleteAtExit:
- registerAtExit(lambda: rmdir(d, True), deleteAtExit)
+ registerAtExit(lambda: safeRmdir(d, True), deleteAtExit)
return d
class tempDir:
diff --git a/python/integration-tests/testIntegration.py b/python/integration-tests/testIntegration.py
index 3a1b0341..49082b25 100644
--- a/python/integration-tests/testIntegration.py
+++ b/python/integration-tests/testIntegration.py
@@ -3,9 +3,9 @@
import os
def runWithFlags(path, flags, onError, input=''):
- cmd = f'python3 src/runYourProgram.py {" ".join(flags)} {path}'
+ cmd = f'python3 code/wypp/runYourProgram.py {" ".join(flags)} {path}'
res = shell.run(cmd, input=input, captureStdout=True, stderrToStdout=True, onError=onError,
- env={'PYTHONPATH': f'./site-lib'})
+ env={'PYTHONPATH': f'./code'})
return res
def run(path, input='', tycheck=True, ecode=0):
@@ -33,156 +33,62 @@ def stripTrailingWs(s):
print(f'Output of integration tests goes to {LOG_FILE}')
LOG_REDIR = f'>> {LOG_FILE} 2>&1'
-class TypeTests(unittest.TestCase):
- def test_enumOk(self):
- out = runInteractive('test-data/typeEnums.py', 'colorToNumber("red")')
- self.assertEqual(['0'], out)
-
- def test_enumTypeError(self):
- out = runInteractive('test-data/typeEnums.py', 'colorToNumber(1)')[0]
- self.assertIn("expected: value of type Literal['red', 'yellow', 'green']", out)
-
- def test_recordOk(self):
- rec = 'test-data/typeRecords.py'
- out1 = runInteractive(rec, 'Person("stefan", 42)')
- self.assertEqual(["Person(name='stefan', age=42)"], out1)
- out2 = runInteractive(rec, 'incAge(Person("stefan", 42))')
- self.assertEqual(["Person(name='stefan', age=43)"], out2)
-
- def test_recordFail1(self):
- rec = 'test-data/typeRecords.py'
- out = runInteractive(rec, 'Person("stefan", 42.3)')
- expected = f"""Traceback (most recent call last):
- File "", line 1, in
-WyppTypeError: got value of wrong type
-given: 42.3
-expected: value of type int
-
-context: record constructor Person(name: str, age: int) -> Self
- ^^^"""
-
- real = '\n'.join(out)
- self.assertTrue(real.startswith(expected))
-
- def test_recordFail2(self):
- rec = 'test-data/typeRecords.py'
- out = runInteractive(rec, 'mutableIncAge(Person("stefan", 42))')[0]
- self.assertIn('expected: value of type MutablePerson', out)
-
- def test_recordMutableOk(self):
- rec = 'test-data/typeRecords.py'
- out1 = runInteractive(rec, 'MutablePerson("stefan", 42)')
- self.assertEqual(["MutablePerson(name='stefan', age=42)"], out1)
- out2 = runInteractive(rec, 'p = MutablePerson("stefan", 42)\nmutableIncAge(p)\np')
- self.assertEqual(['', '', "MutablePerson(name='stefan', age=43)"], out2)
-
- def test_mutableRecordFail1(self):
- rec = 'test-data/typeRecords.py'
- out = runInteractive(rec, 'MutablePerson("stefan", 42.3)')[0]
- self.assertIn('expected: value of type int', out)
-
- def test_mutableRecordFail2(self):
- rec = 'test-data/typeRecords.py'
- out = runInteractive(rec, 'incAge(MutablePerson("stefan", 42))')[0]
- self.assertIn('expected: value of type Person', out)
-
- @unittest.skip
- def test_mutableRecordFail3(self):
- rec = 'test-data/typeRecords.py'
- out = runInteractive(rec, 'p = MutablePerson("stefan", 42)\np.age = 42.4')
- self.assertIn('expected: value of type int', out)
-
- def test_union(self):
- out = runInteractive('test-data/typeUnion.py', """formatAnimal(myCat)
-formatAnimal(myParrot)
-formatAnimal(None)
- """)
- self.assertEqual("'Cat Pumpernickel'", out[0])
- self.assertEqual("\"Parrot Mike says: Let's go to the punkrock show\"", out[1])
- self.assertIn('given: None\nexpected: value of type Union[Cat, Parrot]', out[2])
-
class StudentSubmissionTests(unittest.TestCase):
def check(self, file, testFile, ecode, tycheck=True):
flags = ['--check']
if not tycheck:
flags.append('--no-typechecking')
- cmd = f"python3 src/runYourProgram.py {' '.join(flags)} --test-file {testFile} {file} {LOG_REDIR}"
+ cmd = f"python3 code/wypp/runYourProgram.py {' '.join(flags)} --test-file {testFile} {file} {LOG_REDIR}"
res = shell.run(cmd, onError='ignore')
self.assertEqual(ecode, res.exitcode)
def test_goodSubmission(self):
- self.check("test-data/student-submission.py", "test-data/student-submission-tests.py", 0)
- self.check("test-data/student-submission.py", "test-data/student-submission-tests.py", 0,
+ self.check("integration-test-data/student-submission.py", "integration-test-data/student-submission-tests.py", 0)
+ self.check("integration-test-data/student-submission.py", "integration-test-data/student-submission-tests.py", 0,
tycheck=False)
def test_badSubmission(self):
- self.check("test-data/student-submission-bad.py",
- "test-data/student-submission-tests.py", 1)
- self.check("test-data/student-submission-bad.py",
- "test-data/student-submission-tests.py", 1, tycheck=False)
+ self.check("integration-test-data/student-submission-bad.py",
+ "integration-test-data/student-submission-tests.py", 1)
+ self.check("integration-test-data/student-submission-bad.py",
+ "integration-test-data/student-submission-tests.py", 1, tycheck=False)
def test_submissionWithTypeErrors(self):
- self.check("test-data/student-submission-tyerror.py",
- "test-data/student-submission-tests.py", 1)
- self.check("test-data/student-submission-tyerror.py",
- "test-data/student-submission-tests.py", 0, tycheck=False)
- self.check("test-data/student-submission.py",
- "test-data/student-submission-tests-tyerror.py", 1)
- self.check("test-data/student-submission.py",
- "test-data/student-submission-tests-tyerror.py", 0, tycheck=False)
+ self.check("integration-test-data/student-submission-tyerror.py",
+ "integration-test-data/student-submission-tests.py", 1)
+ self.check("integration-test-data/student-submission-tyerror.py",
+ "integration-test-data/student-submission-tests.py", 0, tycheck=False)
+ self.check("integration-test-data/student-submission.py",
+ "integration-test-data/student-submission-tests-tyerror.py", 1)
+ self.check("integration-test-data/student-submission.py",
+ "integration-test-data/student-submission-tests-tyerror.py", 0, tycheck=False)
class InteractiveTests(unittest.TestCase):
def test_scopeBugPeter(self):
- out = runInteractive('test-data/scope-bug-peter.py', 'local_test()\nprint(spam)')
+ out = runInteractive('integration-test-data/scope-bug-peter.py', 'local_test()\nprint(spam)')
self.assertIn('IT WORKS', out)
def test_types1(self):
- out = runInteractive('test-data/testTypesInteractive.py', 'inc(3)')
+ out = runInteractive('integration-test-data/testTypesInteractive.py', 'inc(3)')
self.assertEqual(['4'], out)
def test_types2(self):
- out = runInteractive('test-data/testTypesInteractive.py', 'inc("3")')[0]
- expected = """Traceback (most recent call last):
- File "", line 1, in
-WyppTypeError: got value of wrong type
-given: '3'
-expected: value of type int
-
-context: inc(x: int) -> int
- ^^^
-declared at: test-data/testTypesInteractive.py:1
-caused by: :1"""
- self.assertEqual(expected, out)
-
- def test_types3(self):
- out = runInteractive('test-data/testTypesInteractive.py',
- 'def f(x: int) -> int: return x\n\nf("x")')[1]
- self.assertIn('expected: value of type int', out)
-
- def test_types4(self):
- out = runInteractive('test-data/testTypesInteractive.py',
- 'def f(x: int) -> int: return x\n\nf(3)')
- self.assertEqual(['...', '3'], out)
-
- def test_types5(self):
- out = runInteractive('test-data/testTypesInteractive.py',
- 'def f(x: int) -> int: return x\n\nf("x")',
- tycheck=False)
- self.assertEqual(['...', "'x'"], out)
+ out = runInteractive('integration-test-data/testTypesInteractive.py', 'inc("3")')[0]
+ self.assertIn('The call of function `inc` expects value of type `int` as 1st argument', out)
def test_typesInImportedModule1(self):
- out = run('test-data/testTypes3.py', ecode=1)
- self.assertIn('expected: value of type int', out)
+ out = run('integration-test-data/testTypes3.py', ecode=1)
+ self.assertIn('The call of function `inc` expects value of type `int` as 1st argument', out)
def test_typesInImportedModule2(self):
- out = run('test-data/testTypes3.py', tycheck=False)
+ out = run('integration-test-data/testTypes3.py', tycheck=False)
self.assertEqual('END', out)
class ReplTesterTests(unittest.TestCase):
def test_replTester(self):
d = shell.pwd()
- cmd = f'python3 {d}/src/replTester.py {d}/test-data/repl-test-lib.py --repl {d}/test-data/repl-test-checks.py'
+ cmd = f'python3 {d}/code/wypp/replTester.py {d}/integration-test-data/repl-test-lib.py --repl {d}/integration-test-data/repl-test-checks.py'
res = shell.run(cmd, captureStdout=True, onError='die', cwd='/tmp')
- self.assertIn('All 1 tests succeded. Great!', res.stdout)
+ self.assertIn('All 1 tests succeeded. Great!', res.stdout)
diff --git a/python/pyrightconfig.json b/python/pyrightconfig.json
new file mode 100644
index 00000000..1aeadf45
--- /dev/null
+++ b/python/pyrightconfig.json
@@ -0,0 +1,10 @@
+{
+ // See https://microsoft.github.io/pyright/#/configuration?id=pyright-configuration
+ "reportWildcardImportFromLibrary": false,
+ "reportMissingTypeStubs": false,
+ "exclude": ["file-test-data", "integration-test-data"],
+ "ignore": ["code/typing_extensions.py", "code/typeguard"],
+ "extraPaths": ["code/wypp"],
+ "typeCheckingMode": "basic"
+}
+
diff --git a/python/run b/python/run
index e61be5c5..91861067 100755
--- a/python/run
+++ b/python/run
@@ -1,11 +1,13 @@
#!/bin/bash
-PY=python3.13
+if [ -z "$PY" ]; then
+ PY=python3.13
+fi
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
OPTS="--quiet"
# OPTS="--verbose"
-PYTHONPATH="$SCRIPT_DIR"/site-lib:"$PYTHONPATH" $PY "$SCRIPT_DIR"/src/runYourProgram.py \
+PYTHONPATH="$SCRIPT_DIR"/code:"$PYTHONPATH" $PY "$SCRIPT_DIR"/code/wypp/runYourProgram.py \
--no-clear $OPTS "$@"
exit $?
diff --git a/python/run-repl-tester b/python/run-repl-tester
index 3321433d..7ff5b149 100755
--- a/python/run-repl-tester
+++ b/python/run-repl-tester
@@ -2,4 +2,4 @@
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
-PYTHONPATH="$SCRIPT_DIR"/site-lib/ python3 "$SCRIPT_DIR"/src/replTester.py "$@"
+PYTHONPATH="$SCRIPT_DIR"/code/ python3 "$SCRIPT_DIR"/code/wypp/replTester.py "$@"
diff --git a/python/setup.py b/python/setup.py
index bc0e9e21..0bb8a63d 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -1,7 +1,6 @@
#!/usr/bin/env python
-from distutils.core import setup
-import setuptools
+from setuptools import setup, find_packages
import os
import json
@@ -32,8 +31,12 @@ def readVersion():
author='Stefan Wehr',
author_email='stefan.wehr@hs-offenburg.de',
url='https://github.com/skogsbaer/write-your-python-program',
- package_dir={'wypp': 'src', 'untypy': 'deps/untypy/untypy'},
- packages=['wypp'] + setuptools.find_packages("deps/untypy", exclude=['test', 'test.*']),
+ package_dir={
+ 'wypp': 'code/wypp',
+ 'typeguard': 'code/typeguard',
+ 'typing_extensions': 'code'
+ },
+ packages=['wypp', 'typing_extensions', 'typeguard'],
python_requires='>=3.12.0',
scripts=['wypp']
)
diff --git a/python/site-lib/README b/python/site-lib/README
deleted file mode 100644
index d2769afc..00000000
--- a/python/site-lib/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory contains symlinks for wypp and untypy.
-
-Add this directory to PYTHONPATH if you want to import wypp and untypy in-place.
diff --git a/python/site-lib/untypy b/python/site-lib/untypy
deleted file mode 120000
index db337ec2..00000000
--- a/python/site-lib/untypy
+++ /dev/null
@@ -1 +0,0 @@
-../deps/untypy/untypy/
\ No newline at end of file
diff --git a/python/site-lib/wypp b/python/site-lib/wypp
deleted file mode 120000
index e057607e..00000000
--- a/python/site-lib/wypp
+++ /dev/null
@@ -1 +0,0 @@
-../src/
\ No newline at end of file
diff --git a/python/src/runner.py b/python/src/runner.py
deleted file mode 100644
index 8e2f514e..00000000
--- a/python/src/runner.py
+++ /dev/null
@@ -1,604 +0,0 @@
-import sys
-import os
-import os.path
-import argparse
-import json
-import traceback
-import shutil
-import site
-import importlib
-import re
-import code
-import ast
-from modulefinder import ModuleFinder
-from pathlib import Path
-import subprocess
-
-__wypp_runYourProgram = 1
-
-def die(ecode=1):
- if sys.flags.interactive:
- os._exit(ecode)
- else:
- sys.exit(ecode)
-
-def getEnv(name, conv, default):
- s = os.getenv(name)
- if s is None:
- return default
- try:
- return conv(s)
- except:
- return default
-
-VERBOSE = False # set via commandline
-DEBUG = getEnv("WYPP_DEBUG", bool, False)
-
-def enableVerbose():
- global VERBOSE
- VERBOSE = True
-
-LIB_DIR = os.path.dirname(__file__)
-INSTALLED_MODULE_NAME = 'wypp'
-FILES_TO_INSTALL = ['writeYourProgram.py', 'drawingLib.py', '__init__.py']
-
-UNTYPY_DIR = os.path.join(LIB_DIR, "..", "deps", "untypy", "untypy")
-UNTYPY_MODULE_NAME = 'untypy'
-SITELIB_DIR = os.path.join(LIB_DIR, "..", "site-lib")
-
-def verbose(s):
- if VERBOSE or DEBUG:
- printStderr('[V] ' + str(s))
-
-def printStderr(s=''):
- sys.stderr.write(s + '\n')
-
-class InstallMode:
- dontInstall = 'dontInstall'
- installOnly = 'installOnly'
- install = 'install'
- assertInstall = 'assertInstall'
- allModes = [dontInstall, installOnly, install, assertInstall]
-
-def parseCmdlineArgs(argList):
- parser = argparse.ArgumentParser(description='Run Your Program!',
- formatter_class=argparse.RawTextHelpFormatter)
- parser.add_argument('--check-runnable', dest='checkRunnable', action='store_const',
- const=True, default=False,
- help='Abort with exit code 1 if loading the file raises errors')
- parser.add_argument('--check', dest='check', action='store_const',
- const=True, default=False,
- help='Abort with exit code 1 if there are test errors.')
- parser.add_argument('--install-mode', dest='installMode', type=str,
- default=InstallMode.dontInstall,
- help="""The install mode can be one of the following:
-- dontInstall do not install the wypp library (default)
-- installOnly install the wypp library and quit
-- install install the wypp library and continue even if installation fails
-- assertInstall check whether wypp is installed
-""")
- parser.add_argument('--verbose', dest='verbose', action='store_const',
- const=True, default=False,
- help='Be verbose')
- parser.add_argument('--debug', dest='debug', action='store_const',
- const=True, default=False,
- help='Enable debugging')
- parser.add_argument('--quiet', dest='quiet', action='store_const',
- const=True, default=False, help='Be extra quiet')
- parser.add_argument('--no-clear', dest='noClear', action='store_const',
- const=True, default=False, help='Do not clear the terminal')
- parser.add_argument('--test-file', dest='testFile',
- type=str, help='Run additional tests contained in this file.')
- parser.add_argument('--extra-dir', dest='extraDirs', action='append', type=str,
- help='Also typechecks files contained in the given directory.\n' \
- 'By default, only files in the same directory as the main file are\n' \
- 'checked.')
- parser.add_argument('--change-directory', dest='changeDir', action='store_const',
- const=True, default=False,
- help='Change to the directory of FILE before running')
- parser.add_argument('--interactive', dest='interactive', action='store_const',
- const=True, default=False,
- help='Run REPL after the programm has finished')
- parser.add_argument('--no-typechecking', dest='checkTypes', action='store_const',
- const=False, default=True,
- help='Do not check type annotations')
- parser.add_argument('file', metavar='FILE',
- help='The file to run', nargs='?')
- if argList is None:
- argList = sys.argv[1:]
- try:
- args, restArgs = parser.parse_known_args(argList)
- except SystemExit as ex:
- die(ex.code)
- if args.file and not args.file.endswith('.py'):
- printStderr(f'ERROR: file {args.file} is not a python file')
- die()
- if args.installMode not in InstallMode.allModes:
- printStderr(f'ERROR: invalid install mode {args.installMode}.')
- die()
- return (args, restArgs)
-
-def readFile(path):
- try:
- with open(path, encoding='utf-8') as f:
- return f.read()
- except UnicodeDecodeError:
- with open(path) as f:
- return f.read()
-
-def readGitVersion():
- thisDir = os.path.basename(LIB_DIR)
- baseDir = os.path.join(LIB_DIR, '..', '..')
- if thisDir == 'src' and os.path.isdir(os.path.join(baseDir, '.git')):
- try:
- h = subprocess.check_output(['git', '-C', baseDir, 'rev-parse', '--short', 'HEAD'],
- encoding='UTF-8').strip()
- changes = subprocess.check_output(
- ['git', '-C', baseDir, 'status', '--porcelain', '--untracked-files=no'],
- encoding='UTF-8').strip()
- if changes:
- return f'git-{h}-dirty'
- else:
- return f'git-{h}'
- except subprocess.CalledProcessError:
- return 'git-?'
- else:
- return None
-
-def readVersion():
- version = readGitVersion()
- if version is not None:
- return version
- try:
- content = readFile(os.path.join(LIB_DIR, '..', '..', 'package.json'))
- d = json.loads(content)
- version = d['version']
- except:
- pass
- return version
-
-def printWelcomeString(file, version, useUntypy):
- cwd = os.getcwd() + "/"
- if file.startswith(cwd):
- file = file[len(cwd):]
- versionStr = '' if not version else 'Version %s, ' % version
- pythonVersion = sys.version.split()[0]
- tycheck = ''
- if not useUntypy:
- tycheck = ', no typechecking'
- printStderr('=== WELCOME to "Write Your Python Program" ' +
- '(%sPython %s, %s%s) ===' % (versionStr, pythonVersion, file, tycheck))
-
-def isSameFile(f1, f2):
- x = readFile(f1)
- y = readFile(f2)
- return x == y
-
-def installFromDir(srcDir, targetDir, mod, files=None):
- verbose(f'Installing from {srcDir} to {targetDir}/{mod}')
- if files is None:
- files = [p.relative_to(srcDir) for p in Path(srcDir).rglob('*.py')]
- else:
- files = [Path(f) for f in files]
- installDir = os.path.join(targetDir, mod)
- os.makedirs(installDir, exist_ok=True)
- installedFiles = sorted([p.relative_to(installDir) for p in Path(installDir).rglob('*.py')])
- wantedFiles = sorted(files)
- if installedFiles == wantedFiles:
- for i in range(len(installedFiles)):
- f1 = os.path.join(installDir, installedFiles[i])
- f2 = os.path.join(srcDir, wantedFiles[i])
- if not isSameFile(f1, f2):
- verbose(f'{f1} and {f2} differ')
- break
- else:
- # no break, all files equal
- verbose(f'All files from {srcDir} already installed in {targetDir}/{mod}')
- return True
- else:
- verbose(f'Installed files {installedFiles} and wanted files {wantedFiles} are different')
- for f in installedFiles:
- p = os.path.join(installDir, f)
- os.remove(p)
- d = os.path.join(installDir, mod)
- for f in wantedFiles:
- src = os.path.join(srcDir, f)
- tgt = os.path.join(installDir, f)
- os.makedirs(os.path.dirname(tgt), exist_ok=True)
- shutil.copyfile(src, tgt)
- verbose(f'Finished installation from {srcDir} to {targetDir}/{mod}')
- return False
-
-def installLib(mode):
- verbose("installMode=" + mode)
- if mode == InstallMode.dontInstall:
- verbose("No installation of WYPP should be performed")
- return
- targetDir = os.getenv('WYPP_INSTALL_DIR', site.USER_SITE)
- try:
- allEq1 = installFromDir(LIB_DIR, targetDir, INSTALLED_MODULE_NAME, FILES_TO_INSTALL)
- allEq2 = installFromDir(UNTYPY_DIR, targetDir, UNTYPY_MODULE_NAME)
- if allEq1 and allEq2:
- verbose(f'WYPP library in {targetDir} already up to date')
- if mode == InstallMode.installOnly:
- printStderr(f'WYPP library in {targetDir} already up to date')
- return
- else:
- if mode == InstallMode.assertInstall:
- printStderr("The WYPP library was not installed before running this command.")
- die(1)
- printStderr(f'The WYPP library has been successfully installed in {targetDir}')
- except Exception as e:
- printStderr('Installation of the WYPP library failed: ' + str(e))
- if mode == InstallMode.installOnly:
- raise e
- if mode == InstallMode.installOnly:
- printStderr('Exiting after installation of the WYPP library')
- die(0)
-
-class Lib:
- def __init__(self, mod, properlyImported):
- self.properlyImported = properlyImported
- if not properlyImported:
- self.initModule = mod['initModule']
- self.resetTestCount = mod['resetTestCount']
- self.printTestResults = mod['printTestResults']
- self.dict = mod
- else:
- self.initModule = mod.initModule
- self.resetTestCount = mod.resetTestCount
- self.printTestResults = mod.printTestResults
- d = {}
- self.dict = d
- for name in dir(mod):
- if name and name[0] != '_':
- d[name] = getattr(mod, name)
-
-def prepareLib(onlyCheckRunnable, enableTypeChecking):
- libDefs = None
- mod = INSTALLED_MODULE_NAME
- verbose('Attempting to import ' + mod)
- wypp = importlib.import_module(mod)
- libDefs = Lib(wypp, True)
- verbose('Successfully imported module ' + mod + ' from file ' + wypp.__file__)
- libDefs.initModule(enableChecks=not onlyCheckRunnable,
- enableTypeChecking=enableTypeChecking,
- quiet=onlyCheckRunnable)
- return libDefs
-
-importRe = importRe = re.compile(r'^import\s+wypp\s*$|^from\s+wypp\s+import\s+\*\s*$')
-
-def findWyppImport(fileName):
- lines = []
- try:
- with open(fileName) as f:
- lines = f.readlines()
- except Exception as e:
- verbose('Failed to read code from %s: %s' % (fileName, e))
- for l in lines:
- if importRe.match(l):
- return True
- return False
-
-def findImportedModules(path, file):
- finder = ModuleFinder(path=path)
- try:
- finder.run_script(file)
- except:
- return []
- realdirs = [os.path.realpath(p) for p in path]
- res = []
- for name, mod in finder.modules.items():
- if name != '__main__' and mod.__file__:
- realp = os.path.realpath(mod.__file__)
- good = False
- for d in realdirs:
- if realp.startswith(d):
- good = True
- break
- if good:
- res.append(name)
- return res
-
-class RunSetup:
- def __init__(self, sysPath):
- self.sysPath = sysPath
- self.sysPathInserted = False
- def __enter__(self):
- if self.sysPath not in sys.path:
- sys.path.insert(0, self.sysPath)
- self.sysPathInserted = True
- def __exit__(self, exc_type, value, traceback):
- if self.sysPathInserted:
- sys.path.remove(self.sysPath)
- self.sysPathInserted = False
-
-def runCode(fileToRun, globals, args, useUntypy=True, extraDirs=None):
- if not extraDirs:
- extraDirs = []
- localDir = os.path.dirname(fileToRun)
-
- with RunSetup(localDir):
- codeTxt = readFile(fileToRun)
- flags = 0
- if useUntypy:
- verbose(f'finding modules imported by {fileToRun}')
- importedMods = findImportedModules([localDir] + extraDirs, fileToRun)
- verbose('finished finding modules, now installing import hook on ' + repr(importedMods))
- untypy.enableDebug(DEBUG)
- untypy.just_install_hook(importedMods + ['__wypp__'])
- verbose(f"transforming {fileToRun} for typechecking")
- tree = compile(codeTxt, fileToRun, 'exec', flags=(flags | ast.PyCF_ONLY_AST),
- dont_inherit=True, optimize=-1)
- untypy.transform_tree(tree, fileToRun)
- verbose(f'done with transformation of {fileToRun}')
- code = tree
- else:
- code = codeTxt
- compiledCode = compile(code, fileToRun, 'exec', flags=flags, dont_inherit=True)
- oldArgs = sys.argv
- try:
- sys.argv = [fileToRun] + args
- exec(compiledCode, globals)
- finally:
- sys.argv = oldArgs
-
-def runStudentCode(fileToRun, globals, onlyCheckRunnable, args, useUntypy=True, extraDirs=None):
- doRun = lambda: runCode(fileToRun, globals, args, useUntypy=useUntypy, extraDirs=extraDirs)
- if onlyCheckRunnable:
- try:
- doRun()
- except:
- printStderr('Loading file %s crashed' % fileToRun)
- handleCurrentException()
- else:
- die(0)
- doRun()
-
-# globals already contain libDefs
-def runTestsInFile(testFile, globals, libDefs, useUntypy=True, extraDirs=[]):
- printStderr()
- printStderr(f"Running tutor's tests in {testFile}")
- libDefs.resetTestCount()
- try:
- runCode(testFile, globals, [], useUntypy=useUntypy, extraDirs=extraDirs)
- except:
- handleCurrentException()
- return libDefs.dict['printTestResults']('Tutor: ')
-
-# globals already contain libDefs
-def performChecks(check, testFile, globals, libDefs, useUntypy=True, extraDirs=None, loadingFailed=False):
- prefix = ''
- if check and testFile:
- prefix = 'Student: '
- testResultsStudent = libDefs.printTestResults(prefix, loadingFailed)
- if check:
- testResultsInstr = {'total': 0, 'failing': 0}
- if testFile:
- testResultsInstr = runTestsInFile(testFile, globals, libDefs, useUntypy=useUntypy,
- extraDirs=extraDirs)
- failingSum = testResultsStudent['failing'] + testResultsInstr['failing']
- die(0 if failingSum < 1 else 1)
-
-def prepareInteractive(reset=True):
- print()
- if reset:
- if os.name == 'nt':
- # clear the terminal
- os.system('cls')
- else:
- # On linux & mac use ANSI Sequence for this
- print('\033[2J\033[H')
-
-def enterInteractive(userDefs):
- for k, v in userDefs.items():
- globals()[k] = v
- print()
-
-def tbToFrameList(tb):
- cur = tb
- res = []
- while cur:
- res.append(cur.tb_frame)
- cur = cur.tb_next
- return res
-
-def isWyppFrame(frame):
- modName = frame.f_globals["__name__"]
- return '__wypp_runYourProgram' in frame.f_globals or \
- modName == 'untypy' or modName.startswith('untypy.') or \
- modName == 'wypp' or modName.startswith('wypp.')
-
-def ignoreFrame(frame):
- if DEBUG:
- return False
- return isWyppFrame(frame)
-
-# Returns a StackSummary object
-def limitTraceback(frameList, isBug):
- frames = [(f, f.f_lineno) for f in frameList if isBug or not ignoreFrame(f)]
- return traceback.StackSummary.extract(frames)
-
-def handleCurrentException(exit=True, removeFirstTb=False, file=sys.stderr):
- (etype, val, tb) = sys.exc_info()
- if isinstance(val, SystemExit):
- die(val.code)
- frameList = tbToFrameList(tb)
- if frameList and removeFirstTb:
- frameList = frameList[1:]
- if frameList and getattr(val, '__wrapped__', False):
- # We remove the stack frame of the wrapper
- frameList = frameList[:-1]
- isWyppError = isinstance(val, untypy.error.UntypyError)
- isBug = not isWyppError and not isinstance(val, SyntaxError) and \
- not isinstance(val, untypy.error.DeliberateError) and frameList \
- and isWyppFrame(frameList[-1])
- stackSummary = limitTraceback(frameList, isBug)
- header = False
- for x in stackSummary.format():
- if not header:
- file.write('Traceback (most recent call last):\n')
- header = True
- file.write(x)
- if isinstance(val, untypy.error.UntypyError):
- name = 'Wypp' + val.simpleName()
- file.write(name)
- s = str(val)
- if s and s[0] != '\n':
- file.write(': ')
- file.write(s)
- file.write('\n')
- else:
- for x in traceback.format_exception_only(etype, val):
- file.write(x)
- if isBug:
- file.write(f'BUG: the error above is most likely a bug in WYPP!')
- if exit:
- die(1)
-
-HISTORY_SIZE = 1000
-
-def getHistoryFilePath():
- envVar = 'HOME'
- if os.name == 'nt':
- envVar = 'USERPROFILE'
- d = os.getenv(envVar, None)
- if d:
- return os.path.join(d, ".wypp_history")
- else:
- return None
-
-# We cannot import untypy at the top of the file because we might have to install it first.
-def importUntypy():
- global untypy
- try:
- import untypy
- except ModuleNotFoundError as e:
- printStderr(f"Module untypy not found, sys.path={sys.path}: {e}")
- die(1)
-
-requiredVersion = (3, 12, 0)
-def versionOk(v):
- (reqMajor, reqMinor, reqMicro) = requiredVersion
- if v.major < reqMajor or v.minor < reqMinor:
- return False
- if v.major == reqMajor and v.minor == reqMinor and v.micro < reqMicro:
- return False
- else:
- return True
-
-def main(globals, argList=None):
- v = sys.version_info
- if not versionOk(v):
- vStr = sys.version.split()[0]
- reqVStr = '.'.join([str(x) for x in requiredVersion])
- print(f"""
-Python in version {reqVStr} or newer is required. You are still using version {vStr}, please upgrade!
-""")
- sys.exit(1)
-
- (args, restArgs) = parseCmdlineArgs(argList)
- global VERBOSE
- if args.verbose:
- VERBOSE = True
- global DEBUG
- if args.debug:
- DEBUG = True
-
- verbose(f'VERBOSE={VERBOSE}, DEBUG={DEBUG}')
-
- installLib(args.installMode)
- if args.installMode == InstallMode.dontInstall:
- if SITELIB_DIR not in sys.path:
- sys.path.insert(0, SITELIB_DIR)
- else:
- if site.USER_SITE not in sys.path:
- if not site.ENABLE_USER_SITE:
- printStderr(f"User site-packages disabled ({site.USER_SITE}. This might cause problems importing wypp or untypy.")
- else:
- verbose(f"Adding user site-package directory {site.USER_SITE} to sys.path")
- sys.path.append(site.USER_SITE)
- importUntypy()
-
- fileToRun = args.file
- if args.changeDir:
- os.chdir(os.path.dirname(fileToRun))
- fileToRun = os.path.basename(fileToRun)
-
- isInteractive = args.interactive
- version = readVersion()
- if isInteractive:
- prepareInteractive(reset=not args.noClear)
-
- if fileToRun is None:
- return
- if not args.checkRunnable and (not args.quiet or args.verbose):
- printWelcomeString(fileToRun, version, useUntypy=args.checkTypes)
-
- libDefs = prepareLib(onlyCheckRunnable=args.checkRunnable, enableTypeChecking=args.checkTypes)
-
- globals['__name__'] = '__wypp__'
- sys.modules['__wypp__'] = sys.modules['__main__']
- loadingFailed = False
- try:
- verbose(f'running code in {fileToRun}')
- globals['__file__'] = fileToRun
- runStudentCode(fileToRun, globals, args.checkRunnable, restArgs,
- useUntypy=args.checkTypes, extraDirs=args.extraDirs)
- except Exception as e:
- verbose(f'Error while running code in {fileToRun}: {e}')
- handleCurrentException(exit=not isInteractive)
- loadingFailed = True
-
- performChecks(args.check, args.testFile, globals, libDefs, useUntypy=args.checkTypes,
- extraDirs=args.extraDirs, loadingFailed=loadingFailed)
-
- if isInteractive:
- enterInteractive(globals)
- if loadingFailed:
- print('NOTE: running the code failed, some definitions might not be available!')
- print()
- if args.checkTypes:
- consoleClass = TypecheckedInteractiveConsole
- else:
- consoleClass = code.InteractiveConsole
- historyFile = getHistoryFilePath()
- try:
- import readline
- readline.parse_and_bind('tab: complete')
- if historyFile and os.path.exists(historyFile):
- readline.read_history_file(historyFile)
- except:
- pass
- try:
- consoleClass(locals=globals).interact(banner="", exitmsg='')
- finally:
- if readline and historyFile:
- readline.set_history_length(HISTORY_SIZE)
- readline.write_history_file(historyFile)
-
-class TypecheckedInteractiveConsole(code.InteractiveConsole):
-
- def showtraceback(self) -> None:
- handleCurrentException(exit=False, removeFirstTb=True, file=sys.stdout)
-
- def runsource(self, source, filename="", symbol="single"):
- try:
- code = self.compile(source, filename, symbol)
- except (OverflowError, SyntaxError, ValueError):
- self.showsyntaxerror(filename)
- return False
- if code is None:
- return True
- try:
- import ast
- tree = compile("\n".join(self.buffer), filename, symbol, flags=ast.PyCF_ONLY_AST, dont_inherit=True, optimize=-1)
- untypy.transform_tree(tree, filename)
- code = compile(tree, filename, symbol)
- except Exception as e:
- if hasattr(e, 'text') and e.text == "":
- pass
- else:
- traceback.print_tb(e.__traceback__)
- self.runcode(code)
- return False
diff --git a/python/test-data/declared-at-missing.err b/python/test-data/declared-at-missing.err
deleted file mode 100644
index 937cd8db..00000000
--- a/python/test-data/declared-at-missing.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/declared-at-missing.py", line 22, in
- semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, ))
-WyppTypeError: got value of wrong type
-given: Course(name='Programmierung 1', teacher='Wehr', students=())
-expected: value of type CourseM
-
-context: record constructor Semester(degreeProgram: str, semester: str, courses: tuple[CourseM, ...]) -> Self
- ^^^^^^^
-declared at: test-data/declared-at-missing.py:16
-caused by: test-data/declared-at-missing.py:22
- | semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, ))
diff --git a/python/test-data/declared-at-missing.err-3.10 b/python/test-data/declared-at-missing.err-3.10
deleted file mode 100644
index 92b7295e..00000000
--- a/python/test-data/declared-at-missing.err-3.10
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/declared-at-missing.py", line 22, in
- semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, ))
-WyppTypeError: got value of wrong type
-given: Course(name='Programmierung 1', teacher='Wehr', students=())
-expected: value of type CourseM
-
-context: record constructor Semester(degreeProgram: str, semester: str, courses: tuple[CourseM, ...]) -> Self
- ^^^^^^^
-declared at: test-data/declared-at-missing.py:15
-caused by: test-data/declared-at-missing.py:22
- | semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, ))
diff --git a/python/test-data/declared-at-missing.err-3.11 b/python/test-data/declared-at-missing.err-3.11
deleted file mode 100644
index 92b7295e..00000000
--- a/python/test-data/declared-at-missing.err-3.11
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/declared-at-missing.py", line 22, in
- semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, ))
-WyppTypeError: got value of wrong type
-given: Course(name='Programmierung 1', teacher='Wehr', students=())
-expected: value of type CourseM
-
-context: record constructor Semester(degreeProgram: str, semester: str, courses: tuple[CourseM, ...]) -> Self
- ^^^^^^^
-declared at: test-data/declared-at-missing.py:15
-caused by: test-data/declared-at-missing.py:22
- | semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, ))
diff --git a/python/test-data/declared-at-missing.err-3.12 b/python/test-data/declared-at-missing.err-3.12
deleted file mode 100644
index 92b7295e..00000000
--- a/python/test-data/declared-at-missing.err-3.12
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/declared-at-missing.py", line 22, in
- semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, ))
-WyppTypeError: got value of wrong type
-given: Course(name='Programmierung 1', teacher='Wehr', students=())
-expected: value of type CourseM
-
-context: record constructor Semester(degreeProgram: str, semester: str, courses: tuple[CourseM, ...]) -> Self
- ^^^^^^^
-declared at: test-data/declared-at-missing.py:15
-caused by: test-data/declared-at-missing.py:22
- | semester1_2020 = Semester('AKI', '1. Semester 2020/21', (prog1, ))
diff --git a/python/test-data/fileWithRecursiveTypes.py b/python/test-data/fileWithRecursiveTypes.py
deleted file mode 100644
index cd7bfd59..00000000
--- a/python/test-data/fileWithRecursiveTypes.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from __future__ import annotations
-
-class C:
- def foo(self: C, d: D) -> C:
- return C
-
-class D:
- pass
diff --git a/python/test-data/fileWithStarImport.py b/python/test-data/fileWithStarImport.py
deleted file mode 100644
index 2be1696e..00000000
--- a/python/test-data/fileWithStarImport.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from wypp import *
-
-def use(x):
- pass
-
-use(record)
-use(list[int])
diff --git a/python/test-data/modules/A/main.err b/python/test-data/modules/A/main.err
deleted file mode 100644
index 0060cc41..00000000
--- a/python/test-data/modules/A/main.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/modules/A/main.py", line 3, in
- mod.foo(1)
- File "", line 5, in foo
- return bar(i)
-WyppTypeError: got value of wrong type
-given: 1
-expected: value of type str
-
-context: bar(s: str) -> int
- ^^^
-declared at: test-data/modules/B/mod.py:1
-caused by: test-data/modules/B/mod.py:5
- | return bar(i)
diff --git a/python/test-data/modules/A/main.py b/python/test-data/modules/A/main.py
deleted file mode 100644
index f641d76c..00000000
--- a/python/test-data/modules/A/main.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import mod
-
-mod.foo(1)
diff --git a/python/test-data/perf.py b/python/test-data/perf.py
deleted file mode 100644
index 6b3ce38e..00000000
--- a/python/test-data/perf.py
+++ /dev/null
@@ -1,125 +0,0 @@
-from __future__ import annotations
-import unittest
-from wypp import *
-
-# Measured 2022-05-05 on a Macbook pro with a 2,3 GHz 8-Core Intel Core i9
-#
-# * Without type annotations
-#
-# Run 1
-# ['0.004', '0.258', '0.054']
-# Run 2
-# ['0.004', '0.258', '0.054']
-# Run 3
-# ['0.005', '0.262', '0.055']
-# Median of total: 0.317
-#
-# * With type annotations (slow way)
-#
-# Run 1
-# ['0.005', '0.271', '2.548']
-# Run 2
-# ['0.005', '0.272', '2.509']
-# Run 3
-# ['0.005', '0.271', '2.526']
-# Median of total: 2.803
-# Factor: 8.8
-#
-# * With type annotations (fast way)
-# Run 1
-# ['0.005', '0.126', '1.128']
-# Run 2
-# ['0.005', '0.120', '1.072']
-# Run 3
-# ['0.005', '0.119', '1.073']
-# Median of total: 1.197
-# Factor: 3.7
-
-@dataclass
-class SearchTreeNode:
- key: any
- value: any
- left: Optional[SearchTreeNode]
- right: Optional[SearchTreeNode]
-
-class SearchTree:
- def __init__(self, root = None):
- self.root = root
-
- # SEITENEFFEKT: verändert den Suchbaum
- def insert(self, key: any, value: any):
- self.root = self.__insertAux(self.root, key, value)
-
- def __insertAux(self, node: Optional[SearchTreeNode], key: any, value: any) -> SearchTreeNode:
- if node is None:
- return SearchTreeNode(key, value, None, None)
- elif node.key == key:
- node.value = value
- return node
- elif key < node.key:
- node.left = self.__insertAux(node.left, key, value)
- return node
- else: # key > node.key
- node.right = self.__insertAux(node.right, key, value)
- return node
-
-def inorder(node: any):
- if node is not None:
- inorder(node.left)
- inorder(node.right)
-
-# Tests
-import random
-
-def randomInts(n, low, high):
- res = []
- for _ in range(n):
- i = random.randint(low, high)
- res.append(i)
- return res
-
-def assertSearchTree(node):
- if node == None:
- return
- inorder(node.left)
- inorder(node.right)
- assertSearchTree(node.left)
- assertSearchTree(node.right)
- return
-
-def measure(start, times, i):
- import time
- end = time.time()
- times[i] = times[i] + (end - start)
- return end
-
-def run(f):
- times = [0,0,0]
- import time
- for _ in range(100):
- start = time.time()
- l = randomInts(30, 0, 50)
- t = SearchTree()
- for i in l:
- start = measure(start, times, 0)
- t.insert(i, "value_" + str(i))
- start = measure(start, times, 1)
- f(t.root)
- start = measure(start, times, 2)
- print([f'{x:.3f}' for x in times])
-
-def bench(f):
- import time
- times = []
- for i in [1,2,3]:
- print(f'Run {i}')
- start = time.time()
- run(f)
- end = time.time()
- times.append(end - start)
- times.sort()
- print(f'Median of total: {times[1]:.3f}')
-
-import cProfile
-#cProfile.run('run()', 'profile')
-bench(assertSearchTree)
diff --git a/python/test-data/printModuleNameImport.py b/python/test-data/printModuleNameImport.py
deleted file mode 100644
index bae36788..00000000
--- a/python/test-data/printModuleNameImport.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import wypp
-import printModuleName
-print(__name__)
-
-class C:
- pass
-
-import sys
-print(sys.modules.get(C.__module__) is not None)
-print(sys.modules.get(printModuleName.C.__module__) is not None)
-
diff --git a/python/test-data/testABCMeta.err-3.10 b/python/test-data/testABCMeta.err-3.10
deleted file mode 100644
index c6c172cb..00000000
--- a/python/test-data/testABCMeta.err-3.10
+++ /dev/null
@@ -1,4 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testABCMeta.py", line 28, in
- Circle(Point(0, 0), 1)
-TypeError: Can't instantiate abstract class Circle with abstract method area
diff --git a/python/test-data/testABCMeta.err-3.11 b/python/test-data/testABCMeta.err-3.11
deleted file mode 100644
index c6c172cb..00000000
--- a/python/test-data/testABCMeta.err-3.11
+++ /dev/null
@@ -1,4 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testABCMeta.py", line 28, in
- Circle(Point(0, 0), 1)
-TypeError: Can't instantiate abstract class Circle with abstract method area
diff --git a/python/test-data/testArgs.out b/python/test-data/testArgs.out
deleted file mode 100644
index 982a2823..00000000
--- a/python/test-data/testArgs.out
+++ /dev/null
@@ -1 +0,0 @@
-['test-data/testArgs.py', 'ARG_1', 'ARG_2']
diff --git a/python/test-data/testArgs.py b/python/test-data/testArgs.py
deleted file mode 100644
index 1174435a..00000000
--- a/python/test-data/testArgs.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import wypp
-import sys
-print(sys.argv)
diff --git a/python/test-data/testCheck.out b/python/test-data/testCheck.out
deleted file mode 100644
index 29568d00..00000000
--- a/python/test-data/testCheck.out
+++ /dev/null
@@ -1,5 +0,0 @@
-FEHLER in test-data/testCheck.py:13: Erwartet wird 2, aber das Ergebnis ist 1
-FEHLER in test-data/testCheck.py:14: Erwartet wird 'xyz', aber das Ergebnis ist 'abc'
-FEHLER in test-data/testCheck.py:15: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Point(x=1, y=2)
-FEHLER in test-data/testCheck.py:16: Erwartet wird Point(x=1, y=3), aber das Ergebnis ist Name(firstName='Max', lastName='Müller')
-4 Tests, 4 Fehler 🙁
diff --git a/python/test-data/testCheck2.err b/python/test-data/testCheck2.err
deleted file mode 100644
index 3ee8955f..00000000
--- a/python/test-data/testCheck2.err
+++ /dev/null
@@ -1,4 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testCheck2.py", line 3, in
- check(1, 2, 3)
-TypeError: check() takes 2 positional arguments but 3 were given
diff --git a/python/test-data/testCheck2.py b/python/test-data/testCheck2.py
deleted file mode 100644
index 6afe96bb..00000000
--- a/python/test-data/testCheck2.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from wypp import *
-
-check(1, 2, 3)
diff --git a/python/test-data/testDeepEqBug.out b/python/test-data/testDeepEqBug.out
deleted file mode 100644
index 7cdbb33e..00000000
--- a/python/test-data/testDeepEqBug.out
+++ /dev/null
@@ -1,2 +0,0 @@
-FEHLER in test-data/testDeepEqBug.py:61: Erwartet wird SemesterM(degreeProgram='AKI', semester='1. Semester 2021/22', courses=[CourseM(name='Programmierung 1', teacher='Wehr', students=[]), CourseM(name='Grundlagen der Informatik', teacher='Oelke', students=[])]), aber das Ergebnis ist SemesterM(degreeProgram='AKI', semester='1. Semester 2021/22', courses=[CourseM(name='Programmierung 1', teacher='Wehr', students=['1234', '9876']), CourseM(name='Grundlagen der Informatik', teacher='Oelke', students=['1234'])])
-1 Test, 1 Fehler 🙁
diff --git a/python/test-data/testDisappearingObject_01.out b/python/test-data/testDisappearingObject_01.out
deleted file mode 100644
index 3252461b..00000000
--- a/python/test-data/testDisappearingObject_01.out
+++ /dev/null
@@ -1,2 +0,0 @@
-['subdir']
-Directory('stefan')
diff --git a/python/test-data/testDisappearingObject_01.py b/python/test-data/testDisappearingObject_01.py
deleted file mode 100644
index aabc05fa..00000000
--- a/python/test-data/testDisappearingObject_01.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# This test comes from a bug reported by a student, 2025-05-8
-from __future__ import annotations
-from wypp import *
-import gc
-
-class FileSystemEntry:
- def __init__(self, name: str):
- self.name = name
- def getName(self) -> str:
- return self.name
- def getContent(self) -> str:
- raise Exception('No content')
- def getChildren(self) -> list[FileSystemEntry]:
- return []
- def findChild(self, name: str) -> FileSystemEntry:
- for c in self.getChildren():
- if c.getName() == name:
- return c
- raise Exception('No child with name ' + name)
- def addChild(self, child: FileSystemEntry) -> None:
- raise Exception('Cannot add child to ' + repr(self))
-
-class Directory(FileSystemEntry):
- def __init__(self, name: str, children: list[FileSystemEntry] = []):
- super().__init__(name)
- self.__children = children
- def getChildren(self) -> list[FileSystemEntry]:
- return self.__children
- def addChild(self, child: FileSystemEntry):
- self.__children.append(child)
- def __repr__(self):
- return 'Directory(' + repr(self.getName()) + ')'
-
-class File:
- def __init__(self, name: str, content: str):
- raise ValueError()
- super().__init__(name)
- self.content = content
- def getContent(self) -> str:
- return self.content
-
-class Link(FileSystemEntry):
- def __init__(self, name: str, linkTarget: FileSystemEntry):
- super().__init__(name)
- self.__linkTarget = linkTarget
- def getChildren(self) -> list[FileSystemEntry]:
- return self.__linkTarget.getChildren()
- def getContent(self) -> str:
- return self.__linkTarget.getContent()
- def addChild(self, child: FileSystemEntry):
- self.__linkTarget.addChild(child)
- def __repr__(self):
- return ('Link(' + repr(self.getName()) + ' -> ' +
- repr(self.__linkTarget) + ')')
- def getLinkTarget(self) -> FileSystemEntry:
- return self.__linkTarget
-
-class CryptoFile:
- def __init__(self, name: str, content: str):
- raise ValueError()
- super().__init__(name)
- self.content = content
- def getContent(self) -> str:
- return 'CRYPTO_' + 'X'*len(self.content)
- def __repr__(self):
- return 'CryptoFile(' + repr(self.getName())
-
-def test_link():
- stefan = Directory('stefan', [])
- wehr = Link('wehr', stefan)
- l1 = [x.getName() for x in wehr.getChildren()]
- wehr.addChild(Directory('subdir', []))
- l2 = [x.getName() for x in wehr.getChildren()]
- print(l2)
- print(stefan)
-
-test_link()
diff --git a/python/test-data/testDisappearingObject_02.py b/python/test-data/testDisappearingObject_02.py
deleted file mode 100644
index 97d8daca..00000000
--- a/python/test-data/testDisappearingObject_02.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# This test comes from a bug reported by a student, 2025-05-8
-from __future__ import annotations
-from wypp import *
-from abc import ABC, abstractmethod
-
-class FileSystemEntry(ABC):
- def __init__(self, name: str):
- self.__name = name
- def getName(self) -> str:
- return self.__name
- def getContent(self) -> str:
- raise Exception('No content')
- def getChildren(self) -> list[FileSystemEntry]:
- return []
- def findChild(self, name: str) -> FileSystemEntry:
- for c in self.getChildren():
- if c.getName() == name:
- return c
- raise Exception('No child with name ' + name)
- def addChild(self, child: FileSystemEntry) -> None:
- raise Exception('Cannot add child to ' + repr(self))
-
-class Directory(FileSystemEntry):
- def __init__(self, name: str, children: list[FileSystemEntry] = []):
- super().__init__(name)
- self.__children = children[:]
- def getChildren(self) -> list[FileSystemEntry]:
- return self.__children[:]
- def addChild(self, child: FileSystemEntry):
- self.__children.append(child)
- def __repr__(self):
- return 'Directory(' + repr(self.getName()) + ')'
-
-class File(FileSystemEntry):
- def __init__(self, name: str, content: str):
- super().__init__(name)
- self.__content = content
- def getContent(self) -> str:
- return self.__content
- def __repr__(self):
- return 'File(' + repr(self.getName()) + ')'
-
-class Link(FileSystemEntry):
- def __init__(self, name: str, linkTarget: FileSystemEntry):
- super().__init__(name)
- self.__linkTarget = linkTarget
- def getChildren(self) -> list[FileSystemEntry]:
- return self.__linkTarget.getChildren()
- def getContent(self) -> str:
- return self.__linkTarget.getContent()
- def addChild(self, child: FileSystemEntry):
- self.__linkTarget.addChild(child)
- def __repr__(self):
- return ('Link(' + repr(self.getName()) + ' -> ' +
- repr(self.__linkTarget) + ')')
- def getLinkTarget(self) -> FileSystemEntry:
- return self.__linkTarget
-
-def test_link():
- stefan = Directory('stefan', [File('notes.txt', 'Notiz')])
- wehr = Link('wehr', stefan)
- l1 = [x.getName() for x in wehr.getChildren()]
- wehr.addChild(Directory('subdir', []))
- l2 = [x.getName() for x in stefan.getChildren()]
- l3 = [x.getName() for x in wehr.getChildren()]
-
-
-test_link()
diff --git a/python/test-data/testForwardRef2.err b/python/test-data/testForwardRef2.err
deleted file mode 100644
index dadfaeb9..00000000
--- a/python/test-data/testForwardRef2.err
+++ /dev/null
@@ -1,6 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testForwardRef2.py", line 10, in
- t = Test(FooX())
-WyppNameError: name 'Foo' is not defined.
-Type annotation of function 'Test.__init__' could not be resolved.
-declared at: test-data/testForwardRef2.py:2
diff --git a/python/test-data/testForwardRef4.err b/python/test-data/testForwardRef4.err
deleted file mode 100644
index 261721fc..00000000
--- a/python/test-data/testForwardRef4.err
+++ /dev/null
@@ -1,6 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testForwardRef4.py", line 11, in
- t = Test(Foo())
-WyppNameError: name 'FooX' is not defined.
-Type annotation inside of class 'Test' could not be resolved.
-declared at: test-data/testForwardRef4.py:4
diff --git a/python/test-data/testForwardRef4.err-3.10 b/python/test-data/testForwardRef4.err-3.10
deleted file mode 100644
index 41fc248c..00000000
--- a/python/test-data/testForwardRef4.err-3.10
+++ /dev/null
@@ -1,6 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testForwardRef4.py", line 11, in
- t = Test(Foo())
-WyppNameError: name 'FooX' is not defined.
-Type annotation inside of class 'Test' could not be resolved.
-declared at: test-data/testForwardRef4.py:3
diff --git a/python/test-data/testForwardRef4.err-3.11 b/python/test-data/testForwardRef4.err-3.11
deleted file mode 100644
index 41fc248c..00000000
--- a/python/test-data/testForwardRef4.err-3.11
+++ /dev/null
@@ -1,6 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testForwardRef4.py", line 11, in
- t = Test(Foo())
-WyppNameError: name 'FooX' is not defined.
-Type annotation inside of class 'Test' could not be resolved.
-declared at: test-data/testForwardRef4.py:3
diff --git a/python/test-data/testForwardRef4.err-3.12 b/python/test-data/testForwardRef4.err-3.12
deleted file mode 100644
index 41fc248c..00000000
--- a/python/test-data/testForwardRef4.err-3.12
+++ /dev/null
@@ -1,6 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testForwardRef4.py", line 11, in
- t = Test(Foo())
-WyppNameError: name 'FooX' is not defined.
-Type annotation inside of class 'Test' could not be resolved.
-declared at: test-data/testForwardRef4.py:3
diff --git a/python/test-data/testForwardRef5.err b/python/test-data/testForwardRef5.err
deleted file mode 100644
index 67974d66..00000000
--- a/python/test-data/testForwardRef5.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testForwardRef5.py", line 23, in
- for car in garage.cars:
-WyppTypeError: list is not a list[Car]
-given: [Car(color='red'), 'Not A Car']
-expected: value of type list[Car]
-
-context: record constructor Garage(cars: list[Car]) -> Self
- ^^^^^^^^^
-declared at: test-data/testForwardRef5.py:10
-caused by: test-data/testForwardRef5.py:23
- | for car in garage.cars:
diff --git a/python/test-data/testForwardRef5.err-3.10 b/python/test-data/testForwardRef5.err-3.10
deleted file mode 100644
index 45c30a41..00000000
--- a/python/test-data/testForwardRef5.err-3.10
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testForwardRef5.py", line 23, in
- for car in garage.cars:
-WyppTypeError: list is not a list[Car]
-given: [Car(color='red'), 'Not A Car']
-expected: value of type list[Car]
-
-context: record constructor Garage(cars: list[Car]) -> Self
- ^^^^^^^^^
-declared at: test-data/testForwardRef5.py:9
-caused by: test-data/testForwardRef5.py:23
- | for car in garage.cars:
diff --git a/python/test-data/testForwardRef5.err-3.11 b/python/test-data/testForwardRef5.err-3.11
deleted file mode 100644
index 45c30a41..00000000
--- a/python/test-data/testForwardRef5.err-3.11
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testForwardRef5.py", line 23, in
- for car in garage.cars:
-WyppTypeError: list is not a list[Car]
-given: [Car(color='red'), 'Not A Car']
-expected: value of type list[Car]
-
-context: record constructor Garage(cars: list[Car]) -> Self
- ^^^^^^^^^
-declared at: test-data/testForwardRef5.py:9
-caused by: test-data/testForwardRef5.py:23
- | for car in garage.cars:
diff --git a/python/test-data/testForwardRef5.err-3.12 b/python/test-data/testForwardRef5.err-3.12
deleted file mode 100644
index 45c30a41..00000000
--- a/python/test-data/testForwardRef5.err-3.12
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testForwardRef5.py", line 23, in
- for car in garage.cars:
-WyppTypeError: list is not a list[Car]
-given: [Car(color='red'), 'Not A Car']
-expected: value of type list[Car]
-
-context: record constructor Garage(cars: list[Car]) -> Self
- ^^^^^^^^^
-declared at: test-data/testForwardRef5.py:9
-caused by: test-data/testForwardRef5.py:23
- | for car in garage.cars:
diff --git a/python/test-data/testFunEq.out b/python/test-data/testFunEq.out
deleted file mode 100644
index 45440caf..00000000
--- a/python/test-data/testFunEq.out
+++ /dev/null
@@ -1,2 +0,0 @@
-FEHLER in test-data/testFunEq.py:9: Erwartet wird , aber das Ergebnis ist
-1 Test, 1 Fehler 🙁
diff --git a/python/test-data/testGetSource.err b/python/test-data/testGetSource.err
deleted file mode 100644
index 4e07caab..00000000
--- a/python/test-data/testGetSource.err
+++ /dev/null
@@ -1,4 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testGetSource.py", line 11, in
- Art = Literal('klein','mittag') # <= problem is here
-untypy.error.WyppTypeError: Cannot call Literal like a function. Did you mean Literal['klein', 'mittag']?
diff --git a/python/test-data/testHintParentheses1.err b/python/test-data/testHintParentheses1.err
deleted file mode 100644
index ed624e35..00000000
--- a/python/test-data/testHintParentheses1.err
+++ /dev/null
@@ -1,10 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testHintParentheses1.py", line 5, in
- def foo(l: list(int)) -> int:
-WyppAttributeError: Type annotation of function 'foo' could not be resolved:
-'type' object is not iterable
-
-def foo(l: list(int)) -> int:
- return ^^^^^^^^^ - Did you mean: 'list[int]'?
-
-declared at: test-data/testHintParentheses1.py:5
diff --git a/python/test-data/testHintParentheses2.err b/python/test-data/testHintParentheses2.err
deleted file mode 100644
index 9bba0373..00000000
--- a/python/test-data/testHintParentheses2.err
+++ /dev/null
@@ -1,10 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testHintParentheses2.py", line 5, in
- def foo(a: 'int', b: 'dict[1, list(int)]') -> int:
-WyppAttributeError: Type annotation of function 'foo' could not be resolved:
-'type' object is not iterable
-
-def foo(a: 'int', b: 'dict[1, list(int)]') -> int:
- return len(l) ^^^^^^^^^ - Did you mean: 'list[int]'?
-
-declared at: test-data/testHintParentheses2.py:5
diff --git a/python/test-data/testHintParentheses3.err b/python/test-data/testHintParentheses3.err
deleted file mode 100644
index 5ac7b6ba..00000000
--- a/python/test-data/testHintParentheses3.err
+++ /dev/null
@@ -1,10 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testHintParentheses3.py", line 6, in
- def foo() -> Union(list, str):
-WyppAttributeError: Type annotation of function 'foo' could not be resolved:
-Cannot call Union like a function. Did you mean Union[list, str]?
-
-def foo() -> Union(list, str):
- pass ^^^^^^^^^^^^^^^^ - Did you mean: 'Union[list, str]'?
-
-declared at: test-data/testHintParentheses3.py:6
diff --git a/python/test-data/testImpossible.err b/python/test-data/testImpossible.err
deleted file mode 100644
index a9195b86..00000000
--- a/python/test-data/testImpossible.err
+++ /dev/null
@@ -1,4 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testImpossible.py", line 3, in
- impossible()
-wypp.writeYourProgram.ImpossibleError: the impossible happened
diff --git a/python/test-data/testIndexError.err b/python/test-data/testIndexError.err
deleted file mode 100644
index e39537b0..00000000
--- a/python/test-data/testIndexError.err
+++ /dev/null
@@ -1,6 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testIndexError.py", line 6, in
- foo([1,2,3])
- File "test-data/testIndexError.py", line 3, in foo
- x = l[42]
-IndexError: list index out of range
diff --git a/python/test-data/testInferReturnType2.err b/python/test-data/testInferReturnType2.err
deleted file mode 100644
index 8f93d5b4..00000000
--- a/python/test-data/testInferReturnType2.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testInferReturnType2.py", line 6, in
- foo(2) # not OK
-WyppTypeError: got value of wrong type
-given: 'yuck'
-expected: None
-
-context: foo(i: int) -> None
- ^^^^
-declared at: test-data/testInferReturnType2.py:1
-caused by: test-data/testInferReturnType2.py:3
- | return "yuck"
diff --git a/python/test-data/testInferReturnType2.py b/python/test-data/testInferReturnType2.py
deleted file mode 100644
index e9405bba..00000000
--- a/python/test-data/testInferReturnType2.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def foo(i: int):
- if i == 2:
- return "yuck"
-
-foo(1) # OK
-foo(2) # not OK
diff --git a/python/test-data/testInferReturnType3.py b/python/test-data/testInferReturnType3.py
deleted file mode 100644
index 043c28a5..00000000
--- a/python/test-data/testInferReturnType3.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from wypp import *
-from typing import Protocol
-import abc
-
-class Animal(Protocol):
- @abc.abstractmethod
- def makeSound(self, loadness: float):
- pass
-
-class Dog:
- def makeSound(self, loadness: float) -> None:
- pass
-
-class Cat:
- def makeSound(self, loadness: float):
- pass
-
-def foo(animal: Animal):
- animal.makeSound(1.0)
-
-foo(Dog())
-foo(Cat())
diff --git a/python/test-data/testInferReturnType4.err b/python/test-data/testInferReturnType4.err
deleted file mode 100644
index 65f963fe..00000000
--- a/python/test-data/testInferReturnType4.err
+++ /dev/null
@@ -1,28 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testInferReturnType4.py", line 17, in
- foo(Dog())
- File "test-data/testInferReturnType4.py", line 15, in foo
- animal.makeSound(1.0)
-WyppTypeError: Dog does not implement protocol Animal
-given: <__wypp__.Dog object at 0x10b81e710>
-expected: value of type Animal
-
-context: foo(animal: Animal) -> None
- ^^^^^^
-declared at: test-data/testInferReturnType4.py:14
-caused by: test-data/testInferReturnType4.py:17
- | foo(Dog())
-
-The return value of method 'makeSound' does violate the protocol 'Animal'.
-The annotation 'int' is incompatible with the protocol's annotation 'None'
-when checking against the following value:
-
-given: 1
-expected: None
-
-context: makeSound(self: Self, loadness: float) -> None
- ^^^^
-declared at: test-data/testInferReturnType4.py:11
-declared at: test-data/testInferReturnType4.py:7
-caused by: test-data/testInferReturnType4.py:12
- | return 1
diff --git a/python/test-data/testInferReturnType4.py b/python/test-data/testInferReturnType4.py
deleted file mode 100644
index e083d14e..00000000
--- a/python/test-data/testInferReturnType4.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from wypp import *
-from typing import Protocol
-import abc
-
-class Animal(Protocol):
- @abc.abstractmethod
- def makeSound(self, loadness: float):
- pass
-
-class Dog:
- def makeSound(self, loadness: float) -> int:
- return 1
-
-def foo(animal: Animal):
- animal.makeSound(1.0)
-
-foo(Dog())
diff --git a/python/test-data/testInvalidLiteral.err b/python/test-data/testInvalidLiteral.err
deleted file mode 100644
index 084153ae..00000000
--- a/python/test-data/testInvalidLiteral.err
+++ /dev/null
@@ -1,5 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testInvalidLiteral.py", line 5, in
- def gameFull(game:Game)->bool:
-WyppAnnotationError: Invalid type annotation: list[list[['x', 'o', '-']]]
-
diff --git a/python/test-data/testIterable7.err b/python/test-data/testIterable7.err
deleted file mode 100644
index 7cea4234..00000000
--- a/python/test-data/testIterable7.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testIterable7.py", line 10, in
- print(list(i))
-WyppTypeError: generator is not an Iterable[str]
-given:
-expected: value of type Iterable[str]
-
-context: foo(i: int) -> Iterable[str]
- ^^^^^^^^^^^^^
-declared at: test-data/testIterable7.py:4
-caused by: test-data/testIterable7.py:4
- | def foo(i: int) -> Iterable[str]:
diff --git a/python/test-data/testIterable7.out b/python/test-data/testIterable7.out
deleted file mode 100644
index 82a4ce68..00000000
--- a/python/test-data/testIterable7.out
+++ /dev/null
@@ -1 +0,0 @@
-start of foo
diff --git a/python/test-data/testIterableImplicitAny.err b/python/test-data/testIterableImplicitAny.err
deleted file mode 100644
index 46f37bc8..00000000
--- a/python/test-data/testIterableImplicitAny.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testIterableImplicitAny.py", line 13, in
- foo(NotIterable())
-WyppTypeError: got value of wrong type
-given: NotIterable
-expected: value of type Iterable
-
-context: foo(it: Iterable) -> int
- ^^^^^^^^
-declared at: test-data/testIterableImplicitAny.py:7
-caused by: test-data/testIterableImplicitAny.py:13
- | foo(NotIterable())
diff --git a/python/test-data/testLiteral1.err b/python/test-data/testLiteral1.err
deleted file mode 100644
index 1805a4e8..00000000
--- a/python/test-data/testLiteral1.err
+++ /dev/null
@@ -1,4 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testLiteral1.py", line 3, in
- T = Literal('a', 'b')
-untypy.error.WyppTypeError: Cannot call Literal like a function. Did you mean Literal['a', 'b']?
diff --git a/python/test-data/testLockFactory.err b/python/test-data/testLockFactory.err
deleted file mode 100644
index dd8c2e9d..00000000
--- a/python/test-data/testLockFactory.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testLockFactory.py", line 15, in
- foo("not a lock")
-WyppTypeError: got value of wrong type
-given: 'not a lock'
-expected: value of type Callable[[], Lock]
-
-context: foo(lock: LockFactory) -> None
- ^^^^^^^^^^^
-declared at: test-data/testLockFactory.py:6
-caused by: test-data/testLockFactory.py:15
- | foo("not a lock")
diff --git a/python/test-data/testLockFactory2.err b/python/test-data/testLockFactory2.err
deleted file mode 100644
index dea8d5ad..00000000
--- a/python/test-data/testLockFactory2.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testLockFactory2.py", line 14, in
- foo("not a lock")
-WyppTypeError: str does not implement protocol Lock
-given: 'not a lock'
-expected: value of type Lock
-
-context: foo(l: Lock) -> None
- ^^^^
-declared at: test-data/testLockFactory2.py:6
-caused by: test-data/testLockFactory2.py:14
- | foo("not a lock")
diff --git a/python/test-data/testMissingReturn.err b/python/test-data/testMissingReturn.err
deleted file mode 100644
index b3d3300b..00000000
--- a/python/test-data/testMissingReturn.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testMissingReturn.py", line 6, in
- print(billigStrom(500))
-WyppTypeError: got value of wrong type
-Did you miss a return statement?
-
-given: None
-expected: value of type float
-
-context: billigStrom(kwh: float) -> float
- ^^^^^
-declared at: test-data/testMissingReturn.py:3
-caused by: test-data/testMissingReturn.py:4
- | pass
diff --git a/python/test-data/testNums.err b/python/test-data/testNums.err
deleted file mode 100644
index 6ae51a3b..00000000
--- a/python/test-data/testNums.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testNums.py", line 8, in
- f(-4)
-WyppTypeError: got value of wrong type
-given: -4
-expected: value of type floatNonNegative
-
-context: f(x: floatNonNegative) -> float
- ^^^^^^^^^^^^^^^^
-declared at: test-data/testNums.py:3
-caused by: test-data/testNums.py:8
- | f(-4)
diff --git a/python/test-data/testNums.py b/python/test-data/testNums.py
deleted file mode 100644
index 3e8cc62f..00000000
--- a/python/test-data/testNums.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from wypp import *
-
-def f(x: floatNonNegative) -> float:
- return x
-
-f(0.0)
-f(1)
-f(-4)
diff --git a/python/test-data/testOriginalTypeNames.out b/python/test-data/testOriginalTypeNames.out
deleted file mode 100644
index cff31dc1..00000000
--- a/python/test-data/testOriginalTypeNames.out
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/python/test-data/testRecordSetTypeForwardRef.err b/python/test-data/testRecordSetTypeForwardRef.err
deleted file mode 100644
index f1494801..00000000
--- a/python/test-data/testRecordSetTypeForwardRef.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordSetTypeForwardRef.py", line 15, in
- m()
- File "test-data/testRecordSetTypeForwardRef.py", line 13, in m
- r.x = "hello"
-WyppTypeError: got value of wrong type
-given: 'hello'
-expected: value of type A
-
-declared at: test-data/testRecordSetTypeForwardRef.py:5
-caused by: test-data/testRecordSetTypeForwardRef.py:13
- | r.x = "hello"
diff --git a/python/test-data/testRecordSetTypeForwardRef.err-3.10 b/python/test-data/testRecordSetTypeForwardRef.err-3.10
deleted file mode 100644
index 361ff706..00000000
--- a/python/test-data/testRecordSetTypeForwardRef.err-3.10
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordSetTypeForwardRef.py", line 14, in
- m()
- File "test-data/testRecordSetTypeForwardRef.py", line 12, in m
- r.x = "hello"
-WyppTypeError: got value of wrong type
-given: 'hello'
-expected: value of type A
-
-declared at: test-data/testRecordSetTypeForwardRef.py:3
-caused by: test-data/testRecordSetTypeForwardRef.py:12
- | r.x = "hello"
diff --git a/python/test-data/testRecordSetTypeForwardRef.err-3.11 b/python/test-data/testRecordSetTypeForwardRef.err-3.11
deleted file mode 100644
index 361ff706..00000000
--- a/python/test-data/testRecordSetTypeForwardRef.err-3.11
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordSetTypeForwardRef.py", line 14, in
- m()
- File "test-data/testRecordSetTypeForwardRef.py", line 12, in m
- r.x = "hello"
-WyppTypeError: got value of wrong type
-given: 'hello'
-expected: value of type A
-
-declared at: test-data/testRecordSetTypeForwardRef.py:3
-caused by: test-data/testRecordSetTypeForwardRef.py:12
- | r.x = "hello"
diff --git a/python/test-data/testRecordSetTypeForwardRef.err-3.12 b/python/test-data/testRecordSetTypeForwardRef.err-3.12
deleted file mode 100644
index 3068bc74..00000000
--- a/python/test-data/testRecordSetTypeForwardRef.err-3.12
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordSetTypeForwardRef.py", line 15, in
- m()
- File "test-data/testRecordSetTypeForwardRef.py", line 13, in m
- r.x = "hello"
-WyppTypeError: got value of wrong type
-given: 'hello'
-expected: value of type A
-
-declared at: test-data/testRecordSetTypeForwardRef.py:4
-caused by: test-data/testRecordSetTypeForwardRef.py:13
- | r.x = "hello"
diff --git a/python/test-data/testRecordSetTypes.err b/python/test-data/testRecordSetTypes.err
deleted file mode 100644
index 04ffa90e..00000000
--- a/python/test-data/testRecordSetTypes.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordSetTypes.py", line 12, in
- m()
- File "test-data/testRecordSetTypes.py", line 10, in m
- r.x = "hello"
-WyppTypeError: got value of wrong type
-given: 'hello'
-expected: value of type int
-
-declared at: test-data/testRecordSetTypes.py:4
-caused by: test-data/testRecordSetTypes.py:10
- | r.x = "hello"
diff --git a/python/test-data/testRecordSetTypes.err-3.10 b/python/test-data/testRecordSetTypes.err-3.10
deleted file mode 100644
index 3a5b1593..00000000
--- a/python/test-data/testRecordSetTypes.err-3.10
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordSetTypes.py", line 12, in
- m()
- File "test-data/testRecordSetTypes.py", line 10, in m
- r.x = "hello"
-WyppTypeError: got value of wrong type
-given: 'hello'
-expected: value of type int
-
-declared at: test-data/testRecordSetTypes.py:3
-caused by: test-data/testRecordSetTypes.py:10
- | r.x = "hello"
diff --git a/python/test-data/testRecordSetTypes.err-3.11 b/python/test-data/testRecordSetTypes.err-3.11
deleted file mode 100644
index 3a5b1593..00000000
--- a/python/test-data/testRecordSetTypes.err-3.11
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordSetTypes.py", line 12, in
- m()
- File "test-data/testRecordSetTypes.py", line 10, in m
- r.x = "hello"
-WyppTypeError: got value of wrong type
-given: 'hello'
-expected: value of type int
-
-declared at: test-data/testRecordSetTypes.py:3
-caused by: test-data/testRecordSetTypes.py:10
- | r.x = "hello"
diff --git a/python/test-data/testRecordSetTypes.err-3.12 b/python/test-data/testRecordSetTypes.err-3.12
deleted file mode 100644
index 3a5b1593..00000000
--- a/python/test-data/testRecordSetTypes.err-3.12
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordSetTypes.py", line 12, in
- m()
- File "test-data/testRecordSetTypes.py", line 10, in m
- r.x = "hello"
-WyppTypeError: got value of wrong type
-given: 'hello'
-expected: value of type int
-
-declared at: test-data/testRecordSetTypes.py:3
-caused by: test-data/testRecordSetTypes.py:10
- | r.x = "hello"
diff --git a/python/test-data/testRecordTypes.err b/python/test-data/testRecordTypes.err
deleted file mode 100644
index 84554590..00000000
--- a/python/test-data/testRecordTypes.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordTypes.py", line 8, in
- p = Point(1, '5')
-WyppTypeError: got value of wrong type
-given: '5'
-expected: value of type int
-
-context: record constructor Point(x: int, y: int) -> Self
- ^^^
-declared at: test-data/testRecordTypes.py:4
-caused by: test-data/testRecordTypes.py:8
- | p = Point(1, '5')
diff --git a/python/test-data/testRecordTypes.err-3.12 b/python/test-data/testRecordTypes.err-3.12
deleted file mode 100644
index 37fbf43c..00000000
--- a/python/test-data/testRecordTypes.err-3.12
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testRecordTypes.py", line 8, in
- p = Point(1, '5')
-WyppTypeError: got value of wrong type
-given: '5'
-expected: value of type int
-
-context: record constructor Point(x: int, y: int) -> Self
- ^^^
-declared at: test-data/testRecordTypes.py:3
-caused by: test-data/testRecordTypes.py:8
- | p = Point(1, '5')
diff --git a/python/test-data/testTodo.err b/python/test-data/testTodo.err
deleted file mode 100644
index c19527ae..00000000
--- a/python/test-data/testTodo.err
+++ /dev/null
@@ -1,4 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTodo.py", line 3, in
- todo()
-wypp.writeYourProgram.TodoError: TODO
diff --git a/python/test-data/testTraceback.err b/python/test-data/testTraceback.err
deleted file mode 100644
index cd5a0661..00000000
--- a/python/test-data/testTraceback.err
+++ /dev/null
@@ -1,6 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTraceback.py", line 9, in
- foo(lst)
- File "test-data/testTraceback.py", line 7, in foo
- print(lst[10])
-IndexError: list index out of range
diff --git a/python/test-data/testTraceback2.err-3.10.0 b/python/test-data/testTraceback2.err-3.10.0
deleted file mode 100644
index 2ac0a7ee..00000000
--- a/python/test-data/testTraceback2.err-3.10.0
+++ /dev/null
@@ -1,4 +0,0 @@
- File "test-data/testTraceback2.py", line 3
- lst = [1,2,3
-
-SyntaxError: '[' was never closed
diff --git a/python/test-data/testTraceback2.err-3.9 b/python/test-data/testTraceback2.err-3.9
deleted file mode 100644
index 714d0a88..00000000
--- a/python/test-data/testTraceback2.err-3.9
+++ /dev/null
@@ -1,4 +0,0 @@
- File "test-data/testTraceback2.py", line 3
- lst = [1,2,3
- ^
-SyntaxError: unexpected EOF while parsing
diff --git a/python/test-data/testTraceback3.err b/python/test-data/testTraceback3.err
deleted file mode 100644
index 6a207b3e..00000000
--- a/python/test-data/testTraceback3.err
+++ /dev/null
@@ -1,4 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTraceback3.py", line 2, in
- print([1,2,3][10])
-IndexError: list index out of range
diff --git a/python/test-data/testTypes1.err b/python/test-data/testTypes1.err
deleted file mode 100644
index bd64b177..00000000
--- a/python/test-data/testTypes1.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypes1.py", line 4, in
- inc("1")
-WyppTypeError: got value of wrong type
-given: '1'
-expected: value of type int
-
-context: inc(x: int) -> int
- ^^^
-declared at: test-data/testTypes1.py:1
-caused by: test-data/testTypes1.py:4
- | inc("1")
diff --git a/python/test-data/testTypes2.err b/python/test-data/testTypes2.err
deleted file mode 100644
index 18c2778b..00000000
--- a/python/test-data/testTypes2.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypes2.py", line 4, in
- inc("1")
-WyppTypeError: got value of wrong type
-given: '1'
-expected: value of type int
-
-context: inc(x: int) -> int
- ^^^
-declared at: test-data/testTypes2.py:1
-caused by: test-data/testTypes2.py:4
- | inc("1")
diff --git a/python/test-data/testTypesCollections1.err b/python/test-data/testTypesCollections1.err
deleted file mode 100644
index 773e0b8e..00000000
--- a/python/test-data/testTypesCollections1.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesCollections1.py", line 7, in
- appendSomething(l)
- File "test-data/testTypesCollections1.py", line 4, in appendSomething
- l.append("foo")
-WyppTypeError: got value of wrong type
-given: 'foo'
-expected: value of type int
-
-context: append(self, object: int)
- ^^^
-declared at: site-lib/untypy/impl/interfaces/list.py:17
-caused by: test-data/testTypesCollections1.py:4
- | l.append("foo")
diff --git a/python/test-data/testTypesCollections1.py b/python/test-data/testTypesCollections1.py
deleted file mode 100644
index be569e10..00000000
--- a/python/test-data/testTypesCollections1.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from wypp import *
-
-def appendSomething(l: list[int]) -> None:
- l.append("foo")
-
-l = [1,2,3]
-appendSomething(l)
-print(l)
diff --git a/python/test-data/testTypesCollections2.err b/python/test-data/testTypesCollections2.err
deleted file mode 100644
index 472a9d3a..00000000
--- a/python/test-data/testTypesCollections2.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesCollections2.py", line 10, in
- foo([lambda: "1", lambda: 42]) # error because the 2nd functions returns an int
- File "test-data/testTypesCollections2.py", line 6, in foo
- res.append(f())
-WyppTypeError: list is not a list[Callable[[], str]]
-given: [ at 0x00>, at 0x00>]
-expected: value of type list[Callable[[], str]]
-
-context: foo(l: list[Callable[[], str]]) -> list[str]
- ^^^^^^^^^^^^^^^^^^^^^^^
-declared at: test-data/testTypesCollections2.py:3
-caused by: test-data/testTypesCollections2.py:10
- | foo([lambda: "1", lambda: 42]) # error because the 2nd functions returns an int
diff --git a/python/test-data/testTypesCollections2.out b/python/test-data/testTypesCollections2.out
deleted file mode 100644
index 189744a2..00000000
--- a/python/test-data/testTypesCollections2.out
+++ /dev/null
@@ -1 +0,0 @@
-['1', '2']
diff --git a/python/test-data/testTypesCollections2.py b/python/test-data/testTypesCollections2.py
deleted file mode 100644
index 4e9c4997..00000000
--- a/python/test-data/testTypesCollections2.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from wypp import *
-
-def foo(l: list[Callable[[], str]]) -> list[str]:
- res = []
- for f in l:
- res.append(f())
- return res
-
-print(foo([lambda: "1", lambda: "2"]))
-foo([lambda: "1", lambda: 42]) # error because the 2nd functions returns an int
diff --git a/python/test-data/testTypesCollections3.err b/python/test-data/testTypesCollections3.err
deleted file mode 100644
index b6d40c46..00000000
--- a/python/test-data/testTypesCollections3.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesCollections3.py", line 7, in
- appendSomething(l, [])
- File "test-data/testTypesCollections3.py", line 4, in appendSomething
- l.append("foo")
-WyppTypeError: got value of wrong type
-given: 'foo'
-expected: value of type int
-
-context: append(self, object: int)
- ^^^
-declared at: site-lib/untypy/impl/interfaces/list.py:17
-caused by: test-data/testTypesCollections3.py:4
- | l.append("foo")
diff --git a/python/test-data/testTypesCollections3.py b/python/test-data/testTypesCollections3.py
deleted file mode 100644
index 0bd390ec..00000000
--- a/python/test-data/testTypesCollections3.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from wypp import *
-
-def appendSomething(l: list[int], l2: list[int]) -> None:
- l.append("foo")
-
-l = [1,2,3]
-appendSomething(l, [])
-print(l)
diff --git a/python/test-data/testTypesCollections4.err b/python/test-data/testTypesCollections4.err
deleted file mode 100644
index 2b2ba1b3..00000000
--- a/python/test-data/testTypesCollections4.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesCollections4.py", line 11, in
- foo([func])
- File "test-data/testTypesCollections4.py", line 7, in foo
- res.append(f())
-WyppTypeError: got value of wrong type
-given: 42
-expected: value of type str
-
-context: __next__(self: Self) -> Callable[[], str]
- ^^^
-declared at: test-data/testTypesCollections4.py:4
-caused by: test-data/testTypesCollections4.py:4
- | l.append(lambda: 42) # error
diff --git a/python/test-data/testTypesCollections4.py b/python/test-data/testTypesCollections4.py
deleted file mode 100644
index 6e410ed2..00000000
--- a/python/test-data/testTypesCollections4.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from wypp import *
-
-def foo(l: list[Callable[[], str]]) -> list[str]:
- l.append(lambda: 42) # error
- res = []
- for f in l:
- res.append(f())
- return res
-
-func = lambda: "xxx"
-foo([func])
diff --git a/python/test-data/testTypesCollections5.err b/python/test-data/testTypesCollections5.err
deleted file mode 100644
index ff67384d..00000000
--- a/python/test-data/testTypesCollections5.err
+++ /dev/null
@@ -1,16 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesCollections5.py", line 11, in
- foo([func]) # error
- File "test-data/testTypesCollections5.py", line 7, in foo
- res.append(f())
-WyppTypeError: got value of wrong type
-given: 42
-expected: value of type str
-
-context: foo(l: list[Callable[[], str]]) -> list[str]
- ^^^^^^^^^^^^^^^^^^^^^^^
-declared at: test-data/testTypesCollections5.py:3
-caused by: test-data/testTypesCollections5.py:10
- | func = lambda: 42
-caused by: test-data/testTypesCollections5.py:11
- | foo([func]) # error
diff --git a/python/test-data/testTypesCollections5.py b/python/test-data/testTypesCollections5.py
deleted file mode 100644
index bbc5001f..00000000
--- a/python/test-data/testTypesCollections5.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from wypp import *
-
-def foo(l: list[Callable[[], str]]) -> list[str]:
- l.append(lambda: 'x') # ok
- res = []
- for f in l:
- res.append(f())
- return res
-
-func = lambda: 42
-foo([func]) # error
diff --git a/python/test-data/testTypesDict1.err b/python/test-data/testTypesDict1.err
deleted file mode 100644
index 4abbc36a..00000000
--- a/python/test-data/testTypesDict1.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesDict1.py", line 7, in
- appendSomething(d)
- File "test-data/testTypesDict1.py", line 4, in appendSomething
- d['x'] = "foo"
-WyppTypeError: got value of wrong type
-given: 'foo'
-expected: value of type int
-
-context: __setitem__(self, key, value: int)
- ^^^
-declared at: site-lib/untypy/impl/interfaces/dict.py:67
-caused by: test-data/testTypesDict1.py:4
- | d['x'] = "foo"
diff --git a/python/test-data/testTypesDict1.py b/python/test-data/testTypesDict1.py
deleted file mode 100644
index 09866b3f..00000000
--- a/python/test-data/testTypesDict1.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from wypp import *
-
-def appendSomething(d: dict[str, int]) -> None:
- d['x'] = "foo"
-
-d = {'1': 1, '2': 2}
-appendSomething(d)
-print(d)
diff --git a/python/test-data/testTypesDict2.err b/python/test-data/testTypesDict2.err
deleted file mode 100644
index 0887b3ac..00000000
--- a/python/test-data/testTypesDict2.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesDict2.py", line 10, in
- foo({'x': lambda: "1", 'y': lambda: 42}) # error because the 2nd functions returns an int
- File "test-data/testTypesDict2.py", line 6, in foo
- res.append(f())
-WyppTypeError: dict is not a dict[str, Callable[[], str]]
-given: {'x': at 0x00>, 'y': at 0x00>}
-expected: value of type dict[str, Callable[[], str]]
-
-context: foo(d: dict[str, Callable[[], str]]) -> list[str]
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-declared at: test-data/testTypesDict2.py:3
-caused by: test-data/testTypesDict2.py:10
- | foo({'x': lambda: "1", 'y': lambda: 42}) # error because the 2nd functions returns an int
diff --git a/python/test-data/testTypesDict2.out b/python/test-data/testTypesDict2.out
deleted file mode 100644
index 189744a2..00000000
--- a/python/test-data/testTypesDict2.out
+++ /dev/null
@@ -1 +0,0 @@
-['1', '2']
diff --git a/python/test-data/testTypesDict2.py b/python/test-data/testTypesDict2.py
deleted file mode 100644
index b2d4f129..00000000
--- a/python/test-data/testTypesDict2.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from wypp import *
-
-def foo(d: dict[str, Callable[[], str]]) -> list[str]:
- res = []
- for _, f in d.items():
- res.append(f())
- return res
-
-print(foo({'x': lambda: "1", 'y': lambda: "2"}))
-foo({'x': lambda: "1", 'y': lambda: 42}) # error because the 2nd functions returns an int
diff --git a/python/test-data/testTypesDict3.err b/python/test-data/testTypesDict3.err
deleted file mode 100644
index 3cb5e1ea..00000000
--- a/python/test-data/testTypesDict3.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesDict3.py", line 11, in
- foo({'y': func})
- File "test-data/testTypesDict3.py", line 7, in foo
- res.append(f())
-WyppTypeError: got value of wrong type
-given: 42
-expected: value of type str
-
-context: __next__(self: Self) -> tuple[str, Callable[[], str]]
- ^^^
-declared at: test-data/testTypesDict3.py:4
-caused by: test-data/testTypesDict3.py:4
- | d['x'] = lambda: 42 # error
diff --git a/python/test-data/testTypesDict4.err b/python/test-data/testTypesDict4.err
deleted file mode 100644
index 14446596..00000000
--- a/python/test-data/testTypesDict4.err
+++ /dev/null
@@ -1,16 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesDict4.py", line 14, in
- bar({'y': func}) # error
- File "test-data/testTypesDict4.py", line 11, in bar
- return foo(d)
- File "test-data/testTypesDict4.py", line 7, in foo
- res.append(f())
-WyppTypeError: dict is not a dict[str, Callable[[], str]]
-given: {'y': at 0x00>, 'x': }
-expected: value of type dict[str, Callable[[], str]]
-
-context: foo(d: dict[str, Callable[[], str]]) -> list[str]
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-declared at: test-data/testTypesDict4.py:3
-caused by: test-data/testTypesDict4.py:11
- | return foo(d)
diff --git a/python/test-data/testTypesHigherOrderFuns.err b/python/test-data/testTypesHigherOrderFuns.err
deleted file mode 100644
index 977eef08..00000000
--- a/python/test-data/testTypesHigherOrderFuns.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesHigherOrderFuns.py", line 10, in
- map(["hello", "1"], lambda x: x)
- File "test-data/testTypesHigherOrderFuns.py", line 6, in map
- res.append(fun(x))
-WyppTypeError: got value of wrong type
-given: 'hello'
-expected: value of type int
-
-context: map(container: Iterable[str], fun: Callable[[str], int]) -> list[int]
- ^^^
-declared at: test-data/testTypesHigherOrderFuns.py:3
-caused by: test-data/testTypesHigherOrderFuns.py:10
- | map(["hello", "1"], lambda x: x)
diff --git a/python/test-data/testTypesHigherOrderFuns3.err b/python/test-data/testTypesHigherOrderFuns3.err
deleted file mode 100644
index 7600c7ea..00000000
--- a/python/test-data/testTypesHigherOrderFuns3.err
+++ /dev/null
@@ -1,17 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesHigherOrderFuns3.py", line 41, in
- check(homePoints(game1), 0)
- File "test-data/testTypesHigherOrderFuns3.py", line 36, in
- return lambda game: gamePoints(game, cmp)
- File "test-data/testTypesHigherOrderFuns3.py", line 30, in gamePoints
- elif cmp(h, g):
-WyppTypeError: got value of wrong type
-given: 'foo'
-expected: value of type bool
-
-context: gamePoints(game: GameResult, cmp: Callable[[int, int], bool]) -> int
- ^^^^
-declared at: test-data/testTypesHigherOrderFuns3.py:38
-declared at: test-data/testTypesHigherOrderFuns3.py:25
-caused by: test-data/testTypesHigherOrderFuns3.py:38
- | homePoints: Callable[[GameResult], int] = mkGamePoints(lambda g, h: "foo")
diff --git a/python/test-data/testTypesProtos1.err b/python/test-data/testTypesProtos1.err
deleted file mode 100644
index bcc00914..00000000
--- a/python/test-data/testTypesProtos1.err
+++ /dev/null
@@ -1,27 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesProtos1.py", line 21, in
- doSomething(Dog())
- File "test-data/testTypesProtos1.py", line 19, in doSomething
- print(a.makeSound(3.14))
-WyppTypeError: Dog does not implement protocol Animal
-given:
-expected: value of type Animal
-
-context: doSomething(a: Animal) -> None
- ^^^^^^
-declared at: test-data/testTypesProtos1.py:18
-caused by: test-data/testTypesProtos1.py:21
- | doSomething(Dog())
-
-Argument loadness of method makeSound violates the type declared by the protocol Animal.
-Annotation int is incompatible with the protocol's annotation float.
-
-given: 3.14
-expected: value of type int
-
-context: makeSound(self: Self, loadness: int) -> str
- ^^^
-declared at: test-data/testTypesProtos1.py:13
-declared at: test-data/testTypesProtos1.py:8
-caused by: test-data/testTypesProtos1.py:13
- | def makeSound(self, loadness: int) -> str:
diff --git a/python/test-data/testTypesProtos2.err b/python/test-data/testTypesProtos2.err
deleted file mode 100644
index 2af72e27..00000000
--- a/python/test-data/testTypesProtos2.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesProtos2.py", line 21, in
- doSomething(Dog())
- File "test-data/testTypesProtos2.py", line 19, in doSomething
- print(a.makeSound(3.14))
-WyppTypeError: too many positional arguments
-Hint: 'self'-parameter was omitted in declaration.
-
-context: makeSound(loadness: float) -> str
-declared at: test-data/testTypesProtos2.py:8
-caused by: test-data/testTypesProtos2.py:19
- | print(a.makeSound(3.14))
diff --git a/python/test-data/testTypesProtos3.err b/python/test-data/testTypesProtos3.err
deleted file mode 100644
index 0a3b18e1..00000000
--- a/python/test-data/testTypesProtos3.err
+++ /dev/null
@@ -1,14 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesProtos3.py", line 21, in
- doSomething(Dog())
-WyppTypeError: Dog does not implement protocol Animal
-The signature of 'makeSound' does not match. Missing required parameter self.
-
-given:
-expected: value of type Animal
-
-context: doSomething(a: Animal) -> None
- ^^^^^^
-declared at: test-data/testTypesProtos3.py:18
-caused by: test-data/testTypesProtos3.py:21
- | doSomething(Dog())
diff --git a/python/test-data/testTypesProtos4.err b/python/test-data/testTypesProtos4.err
deleted file mode 100644
index d47cc1ff..00000000
--- a/python/test-data/testTypesProtos4.err
+++ /dev/null
@@ -1,16 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesProtos4.py", line 27, in
- print(foo(ConcreteWrong()))
- File "test-data/testTypesProtos4.py", line 24, in foo
- return fn(2)
- File "test-data/testTypesProtos4.py", line 20, in
- return lambda x: bar(x) # invalid call of bar with argument of type int
-WyppTypeError: got value of wrong type
-given: 2
-expected: value of type str
-
-context: bar(s: str) -> int
- ^^^
-declared at: test-data/testTypesProtos4.py:15
-caused by: test-data/testTypesProtos4.py:20
- | return lambda x: bar(x) # invalid call of bar with argument of type int
diff --git a/python/test-data/testTypesProtos6.err b/python/test-data/testTypesProtos6.err
deleted file mode 100644
index 20a107ce..00000000
--- a/python/test-data/testTypesProtos6.err
+++ /dev/null
@@ -1,34 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesProtos6.py", line 57, in
- print(computeTotalSize(root))
- File "test-data/testTypesProtos6.py", line 50, in computeTotalSize
- fs.accept(visitor)
- File "test-data/testTypesProtos6.py", line 19, in accept
- visitor.visitDirectory(self)
- File "test-data/testTypesProtos6.py", line 41, in visitDirectory
- c.accept(self)
- File "test-data/testTypesProtos6.py", line 28, in accept
- visitor.visitFile(self)
-WyppTypeError: TotalSizeVisitor does not implement parent FileSystemVisitor
-given: <__wypp__.TotalSizeVisitor object at 0x00>
-expected: value of type FileSystemVisitor
-
-declared at: test-data/testTypesProtos6.py:36
-declared at: test-data/testTypesProtos6.py:33
-caused by: test-data/testTypesProtos6.py:36
- | class TotalSizeVisitor(FileSystemVisitor):
-caused by: test-data/testTypesProtos6.py:42
- | def visitFile(self, file: str):
-
-Argument file of method visitFile violates the type declared by the parent FileSystemVisitor.
-Annotation str is incompatible with the parent's annotation File.
-
-given: <__wypp__.File object at 0x00>
-expected: value of type str
-
-context: visitFile(self: Self, file: str) -> None
- ^^^
-declared at: test-data/testTypesProtos6.py:42
-declared at: test-data/testTypesProtos6.py:33
-caused by: test-data/testTypesProtos6.py:42
- | def visitFile(self, file: str):
diff --git a/python/test-data/testTypesProtos7.err b/python/test-data/testTypesProtos7.err
deleted file mode 100644
index a15b77bc..00000000
--- a/python/test-data/testTypesProtos7.err
+++ /dev/null
@@ -1,33 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesProtos7.py", line 76, in
- print(computeTotalSize(root))
- File "test-data/testTypesProtos7.py", line 69, in computeTotalSize
- fs.accept(visitor)
- File "test-data/testTypesProtos7.py", line 36, in accept
- visitor.visitDirectory(self)
- File "test-data/testTypesProtos7.py", line 60, in visitDirectory
- c.accept(self)
- File "test-data/testTypesProtos7.py", line 47, in accept
- visitor.visitFile(self)
-WyppTypeError: TotalSizeVisitor does not implement parent FileSystemVisitor
-given: <__wypp__.TotalSizeVisitor object at 0x00>
-expected: value of type FileSystemVisitor
-
-declared at: test-data/testTypesProtos7.py:55
-declared at: test-data/testTypesProtos7.py:52
-caused by: test-data/testTypesProtos7.py:55
- | class TotalSizeVisitor(FileSystemVisitor):
-caused by: test-data/testTypesProtos7.py:61
- | def visitFile(self, f: str):
-
-Argument f of method visitFile violates the type declared for file in parent FileSystemVisitor.
-Annotation str is incompatible with the parent's annotation File.
-
-given: File('notes.txt')
-expected: value of type str
-
-context: visitFile(self: Self, f: File) -> None
-declared at: test-data/testTypesProtos7.py:61
-declared at: test-data/testTypesProtos7.py:52
-caused by: test-data/testTypesProtos7.py:61
- | def visitFile(self, f: str):
diff --git a/python/test-data/testTypesProtos8.err b/python/test-data/testTypesProtos8.err
deleted file mode 100644
index e7375274..00000000
--- a/python/test-data/testTypesProtos8.err
+++ /dev/null
@@ -1,28 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesProtos8.py", line 12, in
- bar(Sub())
- File "test-data/testTypesProtos8.py", line 10, in bar
- b.foo(1, "foo")
-WyppTypeError: Sub does not implement parent Base
-given: <__wypp__.Sub object at 0x00>
-expected: value of type Base
-
-declared at: test-data/testTypesProtos8.py:5
-declared at: test-data/testTypesProtos8.py:2
-caused by: test-data/testTypesProtos8.py:5
- | class Sub(Base):
-caused by: test-data/testTypesProtos8.py:6
- | def foo(self, y: int, x: float):
-
-Argument x of method foo violates the type declared for y in parent Base.
-Annotation float is incompatible with the parent's annotation str.
-
-given: 'foo'
-expected: value of type float
-
-context: foo(self: Self, y: float, x: str) -> None
- ^^^^^
-declared at: test-data/testTypesProtos8.py:6
-declared at: test-data/testTypesProtos8.py:2
-caused by: test-data/testTypesProtos8.py:6
- | def foo(self, y: int, x: float):
diff --git a/python/test-data/testTypesProtos9.err b/python/test-data/testTypesProtos9.err
deleted file mode 100644
index 9785e612..00000000
--- a/python/test-data/testTypesProtos9.err
+++ /dev/null
@@ -1,27 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesProtos9.py", line 12, in
- bar(Sub())
- File "test-data/testTypesProtos9.py", line 10, in bar
- b.foo(1, "foo")
-WyppTypeError: Sub does not implement parent Base
-given: <__wypp__.Sub object at 0x00>
-expected: value of type Base
-
-declared at: test-data/testTypesProtos9.py:5
-declared at: test-data/testTypesProtos9.py:2
-caused by: test-data/testTypesProtos9.py:5
- | class Sub(Base):
-caused by: test-data/testTypesProtos9.py:6
- | def foo(self, subX: int, subY: float):
-
-Argument subY of method foo violates the type declared for y in parent Base.
-Annotation float is incompatible with the parent's annotation str.
-
-given: 'foo'
-expected: value of type float
-
-context: foo(self: Self, subX: int, subY: str) -> None
-declared at: test-data/testTypesProtos9.py:6
-declared at: test-data/testTypesProtos9.py:2
-caused by: test-data/testTypesProtos9.py:6
- | def foo(self, subX: int, subY: float):
diff --git a/python/test-data/testTypesRecordInheritance.err b/python/test-data/testTypesRecordInheritance.err
deleted file mode 100644
index 34a56d0b..00000000
--- a/python/test-data/testTypesRecordInheritance.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesRecordInheritance.py", line 18, in
- Point3D(1,2, "foo")
-WyppTypeError: got value of wrong type
-given: 'foo'
-expected: value of type int
-
-context: record constructor Point3D(x: int, y: int, z: int) -> Self
- ^^^
-declared at: test-data/testTypesRecordInheritance.py:10
-caused by: test-data/testTypesRecordInheritance.py:18
- | Point3D(1,2, "foo")
diff --git a/python/test-data/testTypesRecordInheritance.err-3.10 b/python/test-data/testTypesRecordInheritance.err-3.10
deleted file mode 100644
index b86d1393..00000000
--- a/python/test-data/testTypesRecordInheritance.err-3.10
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesRecordInheritance.py", line 18, in
- Point3D(1,2, "foo")
-WyppTypeError: got value of wrong type
-given: 'foo'
-expected: value of type int
-
-context: record constructor Point3D(x: int, y: int, z: int) -> Self
- ^^^
-declared at: test-data/testTypesRecordInheritance.py:9
-caused by: test-data/testTypesRecordInheritance.py:18
- | Point3D(1,2, "foo")
diff --git a/python/test-data/testTypesRecordInheritance.err-3.11 b/python/test-data/testTypesRecordInheritance.err-3.11
deleted file mode 100644
index b86d1393..00000000
--- a/python/test-data/testTypesRecordInheritance.err-3.11
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesRecordInheritance.py", line 18, in
- Point3D(1,2, "foo")
-WyppTypeError: got value of wrong type
-given: 'foo'
-expected: value of type int
-
-context: record constructor Point3D(x: int, y: int, z: int) -> Self
- ^^^
-declared at: test-data/testTypesRecordInheritance.py:9
-caused by: test-data/testTypesRecordInheritance.py:18
- | Point3D(1,2, "foo")
diff --git a/python/test-data/testTypesRecordInheritance.err-3.12 b/python/test-data/testTypesRecordInheritance.err-3.12
deleted file mode 100644
index b86d1393..00000000
--- a/python/test-data/testTypesRecordInheritance.err-3.12
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesRecordInheritance.py", line 18, in
- Point3D(1,2, "foo")
-WyppTypeError: got value of wrong type
-given: 'foo'
-expected: value of type int
-
-context: record constructor Point3D(x: int, y: int, z: int) -> Self
- ^^^
-declared at: test-data/testTypesRecordInheritance.py:9
-caused by: test-data/testTypesRecordInheritance.py:18
- | Point3D(1,2, "foo")
diff --git a/python/test-data/testTypesReturn.err b/python/test-data/testTypesReturn.err
deleted file mode 100644
index 6325880e..00000000
--- a/python/test-data/testTypesReturn.err
+++ /dev/null
@@ -1,12 +0,0 @@
-Traceback (most recent call last):
- File "test-data/testTypesReturn.py", line 8, in